From 8be0781b492149c5bbfa507e1f1d17d1a19b81ed Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Wed, 2 Mar 2022 19:49:03 +0100 Subject: [PATCH] Restructure code and add new features (#37) * Make synth parameters configurable * Add class CConfig, which holds the configuration * Add template config file minidexed.ini * Register panic handler in CKernel to allow to display assertions * Fix: Performance timer did not show correct percent value with HDMI * Add class CDexedAdapter Some Dexed methods require to be guarded from being interrupted by other Dexed calls. This is done in the class CDexedAdapter. * Add class CUserInterface The user interface should be implemented here. As a start it supports showing the program number and name on the LCD display. The LCD output is buffered, so that LCD writes from an IRQ handler are possible. * Move MIDI handling from CMiniDexed to specific classes * CMIDIDevice is the generic MIDI handler * CMIDIKeyboard handles USB audio class MIDI devices * CSerialMIDIDevice handles the serial MIDI device * Now all MIDI inputs can work simultaneous * Program change and bank select work with serial MIDI * Add headers to all files * Include voices.c in sysexfileloader.cpp * Cleanup Makefile * Support headless operation on Raspberry Pi 4 Some code cleanup for src/kernel.* * Code cleanup for src/minidexed.* Move implementation of constructors to minidexed.cpp Reorder member variables * Support multiple USB MIDI inputs at once * Maximum 2 inputs on Raspberry Pi 1-3 * Maximum 4 inputs on Raspberry Pi 4 * Suppress frequent messages in MIDI dump * Use minidexed.txt * Document `SoundDevice` in `minidexed.ini` Co-authored-by: probonopd --- .github/workflows/build.yml | 4 +- README.md | 6 +- build.sh | 3 + src/Makefile | 11 +- src/Rules.mk | 1 + src/circle_stdlib_app.h | 10 +- src/config.cpp | 132 +++++++++++ src/config.h | 97 ++++++++ src/dexedadapter.h | 70 ++++++ src/kernel.cpp | 65 +++++- src/kernel.h | 29 ++- src/mididevice.cpp | 150 ++++++++++++ src/mididevice.h | 45 ++++ src/midikeyboard.cpp | 105 +++++++++ src/midikeyboard.h | 65 ++++++ src/minidexed.cpp | 453 ++++++++++++++---------------------- src/minidexed.h | 157 ++++++------- src/minidexed.ini | 26 +++ src/perftimer.cpp | 16 ++ src/perftimer.h | 16 ++ src/serialmididevice.cpp | 98 ++++++++ src/serialmididevice.h | 52 +++++ src/sysexfileloader.cpp | 19 +- src/sysexfileloader.h | 16 ++ src/userinterface.cpp | 107 +++++++++ src/userinterface.h | 52 +++++ 26 files changed, 1413 insertions(+), 392 deletions(-) create mode 100644 src/config.cpp create mode 100644 src/config.h create mode 100644 src/dexedadapter.h create mode 100644 src/mididevice.cpp create mode 100644 src/mididevice.h create mode 100644 src/midikeyboard.cpp create mode 100644 src/midikeyboard.h create mode 100644 src/minidexed.ini create mode 100644 src/serialmididevice.cpp create mode 100644 src/serialmididevice.h create mode 100644 src/userinterface.cpp create mode 100644 src/userinterface.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05bd3e8..f87276c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,8 +63,8 @@ jobs: mkdir -p sdcard cp -r ./circle-stdlib/libs/circle/boot/* sdcard rm -rf sdcard/config*.txt sdcard/README sdcard/Makefile sdcard/armstub sdcard/COPYING.linux - cp ./src/config.txt ./src/*img sdcard/ - echo "usbspeed=full sounddev=sndpwm" > sdcard/cmdline.txt + cp ./src/config.txt ./src/minidexed.txt ./src/*img sdcard/ + echo "usbspeed=full" > sdcard/cmdline.txt cd sdcard cp ../kernels/* . || true zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d).zip * diff --git a/README.md b/README.md index b71aad3..55f6fad 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ I am wondering whether we can run multiple Dexed instances, in order to recreate * Unzip * Put the files into the root directory of a FAT32 formatted partition on SD/microSD card * Put SD/microSD card into Raspberry Pi 1, 2, 3 or 4 (Zero and Zero 2 can probably be used but need HDMI or a supported i2c DAC for audio out) -* Attach headphones to the headphone jack using `sounddev=sndpwm` in `cmdline.txt` (default) -* Alternatively, attach a PCM5102A or PCM5122 based DAC and select i2c sound output using `sounddev=sndi2s | sndhdmi` in `cmdline.txt` -* Alternatively, attach a HDMI display with sound and select HDMI sound output using `sounddev=sndhdmi` in `cmdline.txt` (this may introduce slight latency) +* Attach headphones to the headphone jack using `SoundDevice=pwm` in `minidexed.ini` (default) +* Alternatively, attach a PCM5102A or PCM5122 based DAC and select i2c sound output using `SoundDevice=i2s | sndhdmi` in `minidexed.ini` +* Alternatively, attach a HDMI display with sound and select HDMI sound output using `SoundDevice=hdmi` in `minidexed.ini` (this may introduce slight latency) * Attach a MIDI keyboard via USB * Boot * Stat playing diff --git a/build.sh b/build.sh index 2d52b92..e529a66 100755 --- a/build.sh +++ b/build.sh @@ -26,6 +26,9 @@ make -j cd libs/circle/addon/display/ make clean || true make -j +cd ../Properties/ +make clean || true +make -j cd ../../../.. cd .. diff --git a/src/Makefile b/src/Makefile index 8204121..7d334d2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,7 +5,9 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib SYNTH_DEXED_DIR = ../Synth_Dexed/src -OBJS = main.o kernel.o minidexed.o pckeyboard.o sysexfileloader.o perftimer.o \ +OBJS = main.o kernel.o minidexed.o config.o userinterface.o \ + mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ + sysexfileloader.o perftimer.o \ $(SYNTH_DEXED_DIR)/synth_dexed.o INCLUDE += -I $(SYNTH_DEXED_DIR) @@ -13,10 +15,3 @@ INCLUDE += -I $(SYNTH_DEXED_DIR) EXTRACLEAN = $(SYNTH_DEXED_DIR)/*.o $(SYNTH_DEXED_DIR)/*.d include ./Rules.mk - -%.o: %.cc - @echo " CPP $@" - @$(CPP) $(CPPFLAGS) -c -o $@ $< - -%.d: %.cc - @$(CPP) $(CPPFLAGS) -M -MG -MT $*.o -MT $@ -MF $@ $< diff --git a/src/Rules.mk b/src/Rules.mk index 1b5ea06..3552503 100644 --- a/src/Rules.mk +++ b/src/Rules.mk @@ -18,6 +18,7 @@ LIBS += \ $(NEWLIBDIR)/lib/libc.a \ $(NEWLIBDIR)/lib/libcirclenewlib.a \ $(CIRCLEHOME)/addon/display/libdisplay.a \ + $(CIRCLEHOME)/addon/Properties/libproperties.a \ $(CIRCLEHOME)/addon/SDCard/libsdcard.a \ $(CIRCLEHOME)/lib/usb/libusb.a \ $(CIRCLEHOME)/lib/input/libinput.a \ diff --git a/src/circle_stdlib_app.h b/src/circle_stdlib_app.h index 170a39d..75cbde2 100644 --- a/src/circle_stdlib_app.h +++ b/src/circle_stdlib_app.h @@ -97,6 +97,7 @@ public: : CStdlibApp (kernel), mScreenUnbuffered (mOptions.GetWidth (), mOptions.GetHeight ()), mScreen (&mScreenUnbuffered), + mbScreenAvailable (false), mTimer (&mInterrupt), mLogger (mOptions.GetLogLevel (), &mTimer) { @@ -109,10 +110,7 @@ public: return false; } - if (!mScreenUnbuffered.Initialize ()) - { - return false; - } + mbScreenAvailable = mScreenUnbuffered.Initialize (); #if 0 if (!mSerial.Initialize (115200)) { @@ -138,6 +136,7 @@ protected: CScreenDevice mScreenUnbuffered; //CSerialDevice mSerial; CWriteBufferDevice mScreen; + bool mbScreenAvailable; CTimer mTimer; CLogger mLogger; }; @@ -164,7 +163,8 @@ public: mUSBHCI (&mInterrupt, &mTimer, TRUE), mEMMC (&mInterrupt, &mTimer, &mActLED), #if !defined(__aarch64__) || !defined(LEAVE_QEMU_ON_HALT) - mConsole (&mScreen, TRUE) + //mConsole (&mScreen, TRUE) + mConsole (&mNullDevice, &mScreen) #else mConsole (&mScreen) #endif diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..b9a7ec6 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,132 @@ +// +// config.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#include "config.h" + +CConfig::CConfig (FATFS *pFileSystem) +: m_Properties ("minidexed.ini", pFileSystem) +{ +} + +CConfig::~CConfig (void) +{ +} + +void CConfig::Load (void) +{ + m_Properties.Load (); + + m_SoundDevice = m_Properties.GetString ("SoundDevice", "pwm"); + + m_nSampleRate = m_Properties.GetNumber ("SampleRate", 48000); + m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 256); + m_nDACI2CAddress = m_Properties.GetNumber ("DACI2CAddress", 0); + + m_nMIDIBaudRate = m_Properties.GetNumber ("MIDIBaudRate", 31250); + + m_bLCDEnabled = m_Properties.GetNumber ("LCDEnabled", 0) != 0; + m_nLCDPinEnable = m_Properties.GetNumber ("LCDPinEnable", 17); + m_nLCDPinRegisterSelect = m_Properties.GetNumber ("LCDPinRegisterSelect", 18); + m_nLCDPinReadWrite = m_Properties.GetNumber ("LCDPinReadWrite", 19); + m_nLCDPinData4 = m_Properties.GetNumber ("LCDPinData4", 22); + m_nLCDPinData5 = m_Properties.GetNumber ("LCDPinData5", 23); + m_nLCDPinData6 = m_Properties.GetNumber ("LCDPinData6", 24); + m_nLCDPinData7 = m_Properties.GetNumber ("LCDPinData7", 25); + + m_bMIDIDumpEnabled = m_Properties.GetNumber ("MIDIDumpEnabled", 0) != 0; + m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; +} + +const char *CConfig::GetSoundDevice (void) const +{ + return m_SoundDevice.c_str (); +} + +unsigned CConfig::GetSampleRate (void) const +{ + return m_nSampleRate; +} + +unsigned CConfig::GetChunkSize (void) const +{ + return m_nChunkSize; +} + +unsigned CConfig::GetDACI2CAddress (void) const +{ + return m_nDACI2CAddress; +} + +unsigned CConfig::GetMIDIBaudRate (void) const +{ + return m_nMIDIBaudRate; +} + +bool CConfig::GetLCDEnabled (void) const +{ + return m_bLCDEnabled; +} + +unsigned CConfig::GetLCDPinEnable (void) const +{ + return m_nLCDPinEnable; +} + +unsigned CConfig::GetLCDPinRegisterSelect (void) const +{ + return m_nLCDPinRegisterSelect; +} + +unsigned CConfig::GetLCDPinReadWrite (void) const +{ + return m_nLCDPinReadWrite; +} + +unsigned CConfig::GetLCDPinData4 (void) const +{ + return m_nLCDPinData4; +} + +unsigned CConfig::GetLCDPinData5 (void) const +{ + return m_nLCDPinData5; +} + +unsigned CConfig::GetLCDPinData6 (void) const +{ + return m_nLCDPinData6; +} + +unsigned CConfig::GetLCDPinData7 (void) const +{ + return m_nLCDPinData7; +} + +bool CConfig::GetMIDIDumpEnabled (void) const +{ + return m_bMIDIDumpEnabled; +} + +bool CConfig::GetProfileEnabled (void) const +{ + return m_bProfileEnabled; +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..698bca4 --- /dev/null +++ b/src/config.h @@ -0,0 +1,97 @@ +// +// config.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _config_h +#define _config_h + +#include +#include +#include + +class CConfig // Configuration for MiniDexed +{ +public: + static const unsigned MaxNotes = 16; // polyphony + +#if RASPPI <= 3 + static const unsigned MaxUSBMIDIDevices = 2; +#else + static const unsigned MaxUSBMIDIDevices = 4; +#endif + + static const unsigned LCDColumns = 16; // HD44780 LCD + static const unsigned LCDRows = 2; + +public: + CConfig (FATFS *pFileSystem); + ~CConfig (void); + + void Load (void); + + // Sound device + const char *GetSoundDevice (void) const; + unsigned GetSampleRate (void) const; + unsigned GetChunkSize (void) const; + unsigned GetDACI2CAddress (void) const; // 0 for auto probing + + // MIDI + unsigned GetMIDIBaudRate (void) const; + + // HD44780 LCD + // GPIO pin numbers are chip numbers, not header positions + bool GetLCDEnabled (void) const; + unsigned GetLCDPinEnable (void) const; + unsigned GetLCDPinRegisterSelect (void) const; + unsigned GetLCDPinReadWrite (void) const; // set to 0 if not connected + unsigned GetLCDPinData4 (void) const; + unsigned GetLCDPinData5 (void) const; + unsigned GetLCDPinData6 (void) const; + unsigned GetLCDPinData7 (void) const; + + // Debug + bool GetMIDIDumpEnabled (void) const; + bool GetProfileEnabled (void) const; + +private: + CPropertiesFatFsFile m_Properties; + + std::string m_SoundDevice; + unsigned m_nSampleRate; + unsigned m_nChunkSize; + unsigned m_nDACI2CAddress; + + unsigned m_nMIDIBaudRate; + + bool m_bLCDEnabled; + unsigned m_nLCDPinEnable; + unsigned m_nLCDPinRegisterSelect; + unsigned m_nLCDPinReadWrite; + unsigned m_nLCDPinData4; + unsigned m_nLCDPinData5; + unsigned m_nLCDPinData6; + unsigned m_nLCDPinData7; + + bool m_bMIDIDumpEnabled; + bool m_bProfileEnabled; +}; + +#endif diff --git a/src/dexedadapter.h b/src/dexedadapter.h new file mode 100644 index 0000000..0322dd4 --- /dev/null +++ b/src/dexedadapter.h @@ -0,0 +1,70 @@ +// +// dexedadapter.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _dexedadapter_h +#define _dexedadapter_h + +#include +#include +#include + +// Some Dexed methods require to be guarded from being interrupted +// by other Dexed calls. This is done herein. + +class CDexedAdapter : public Dexed +{ +public: + CDexedAdapter (uint8_t maxnotes, int rate) + : Dexed (maxnotes, rate) + { + } + + void loadVoiceParameters (uint8_t* data) + { + m_SpinLock.Acquire (); + Dexed::loadVoiceParameters (data); + m_SpinLock.Release (); + } + + void keyup (int16_t pitch) + { + m_SpinLock.Acquire (); + Dexed::keyup (pitch); + m_SpinLock.Release (); + } + + void keydown (int16_t pitch, uint8_t velo) + { + m_SpinLock.Acquire (); + Dexed::keydown (pitch, velo); + m_SpinLock.Release (); + } + + void getSamples (uint16_t n_samples, int16_t* buffer) + { + m_SpinLock.Acquire (); + Dexed::getSamples (n_samples, buffer); + m_SpinLock.Release (); + } + +private: + CSpinLock m_SpinLock; +}; + +#endif diff --git a/src/kernel.cpp b/src/kernel.cpp index 29a2fab..c658854 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -1,24 +1,46 @@ // // kernel.cpp // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #include "kernel.h" -#include #include #include -#include "voices.c" +#include +#include LOGMODULE ("kernel"); +CKernel *CKernel::s_pThis = 0; + CKernel::CKernel (void) : CStdlibAppStdio ("minidexed"), + m_Config (&mFileSystem), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), m_pDexed (0) { + s_pThis = this; + // mActLED.Blink (5); // show we are alive } CKernel::~CKernel(void) { + s_pThis = 0; } bool CKernel::Initialize (void) @@ -28,27 +50,33 @@ bool CKernel::Initialize (void) return FALSE; } + mLogger.RegisterPanicHandler (PanicHandler); + + m_Config.Load (); + // select the sound device - const char *pSoundDevice = mOptions.GetSoundDevice (); - if (strcmp (pSoundDevice, "sndi2s") == 0) + const char *pSoundDevice = m_Config.GetSoundDevice (); + if (strcmp (pSoundDevice, "i2s") == 0) { LOGNOTE ("I2S mode"); - m_pDexed = new CMiniDexedI2S (16, SAMPLE_RATE, &mInterrupt, &m_I2CMaster); + m_pDexed = new CMiniDexedI2S (&m_Config, &mInterrupt, &m_I2CMaster); } - else if (strcmp (pSoundDevice, "sndhdmi") == 0) + else if (strcmp (pSoundDevice, "hdmi") == 0) { LOGNOTE ("HDMI mode"); - m_pDexed = new CMiniDexedHDMI (16, SAMPLE_RATE, &mInterrupt); + m_pDexed = new CMiniDexedHDMI (&m_Config, &mInterrupt); } else { LOGNOTE ("PWM mode"); - m_pDexed = new CMiniDexedPWM (16, SAMPLE_RATE, &mInterrupt); + m_pDexed = new CMiniDexedPWM (&m_Config, &mInterrupt); } + assert (m_pDexed); + if (!m_pDexed->Initialize ()) { return FALSE; @@ -59,16 +87,29 @@ bool CKernel::Initialize (void) CStdlibApp::TShutdownMode CKernel::Run (void) { - std::cout << "Hello MiniDexed!\n"; + assert (m_pDexed); - while(42==42) + while (42 == 42) { boolean bUpdated = mUSBHCI.UpdatePlugAndPlay (); m_pDexed->Process(bUpdated); - mScreen.Update (); + if (mbScreenAvailable) + { + mScreen.Update (); + } } return ShutdownHalt; -} \ No newline at end of file +} + +void CKernel::PanicHandler (void) +{ + EnableIRQs (); + + if (s_pThis->mbScreenAvailable) + { + s_pThis->mScreen.Update (4096); + } +} diff --git a/src/kernel.h b/src/kernel.h index 2802979..5d626f9 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -1,11 +1,28 @@ // // kernel.h // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #ifndef _kernel_h #define _kernel_h #include "circle_stdlib_app.h" #include +#include "config.h" #include "minidexed.h" enum TShutdownMode @@ -25,10 +42,16 @@ public: TShutdownMode Run (void); +private: + static void PanicHandler (void); + private: // do not change this order - CI2CMaster m_I2CMaster; - CMiniDexed *m_pDexed; + CConfig m_Config; + CI2CMaster m_I2CMaster; + CMiniDexed *m_pDexed; + + static CKernel *s_pThis; }; -#endif \ No newline at end of file +#endif diff --git a/src/mididevice.cpp b/src/mididevice.cpp new file mode 100644 index 0000000..8b0d570 --- /dev/null +++ b/src/mididevice.cpp @@ -0,0 +1,150 @@ +// +// mididevice.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#include "mididevice.h" +#include "minidexed.h" +#include "config.h" +#include +#include + +#define MIDI_NOTE_OFF 0b1000 +#define MIDI_NOTE_ON 0b1001 +#define MIDI_AFTERTOUCH 0b1010 // TODO +#define MIDI_CONTROL_CHANGE 0b1011 + #define MIDI_CC_BANK_SELECT_MSB 0 // TODO + #define MIDI_CC_BANK_SELECT_LSB 32 +#define MIDI_PROGRAM_CHANGE 0b1100 +#define MIDI_PITCH_BEND 0b1110 + +#define MIDI_TIMING_CLOCK 0xF8 +#define MIDI_ACTIVE_SENSING 0xFE + +CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig) +: m_pSynthesizer (pSynthesizer), + m_pConfig (pConfig) +{ +} + +CMIDIDevice::~CMIDIDevice (void) +{ + m_pSynthesizer = 0; +} + +void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable) +{ + assert (m_pSynthesizer != 0); + + // The packet contents are just normal MIDI data - see + // https://www.midi.org/specifications/item/table-1-summary-of-midi-message + + if (m_pConfig->GetMIDIDumpEnabled ()) + { + switch (nLength) + { + case 1: + if ( pMessage[0] != MIDI_TIMING_CLOCK + && pMessage[0] != MIDI_ACTIVE_SENSING) + { + printf ("MIDI %u: %02X\n", nCable, (unsigned) pMessage[0]); + } + break; + + case 2: + printf ("MIDI %u: %02X %02X\n", nCable, + (unsigned) pMessage[0], (unsigned) pMessage[1]); + break; + + case 3: + printf ("MIDI %u: %02X %02X %02X\n", nCable, + (unsigned) pMessage[0], (unsigned) pMessage[1], + (unsigned) pMessage[2]); + break; + } + } + + if (nLength < 2) + { + return; + } + + u8 ucStatus = pMessage[0]; + // TODO: u8 ucChannel = ucStatus & 0x0F; + u8 ucType = ucStatus >> 4; + u8 ucKeyNumber = pMessage[1]; + u8 ucVelocity = pMessage[2]; + + switch (ucType) + { + case MIDI_NOTE_ON: + if (nLength < 3) + { + break; + } + + if (ucVelocity > 0) + { + if (ucVelocity <= 127) + { + m_pSynthesizer->keydown (ucKeyNumber, ucVelocity); + } + } + else + { + m_pSynthesizer->keyup (ucKeyNumber); + } + break; + + case MIDI_NOTE_OFF: + if (nLength < 3) + { + break; + } + + m_pSynthesizer->keyup (ucKeyNumber); + break; + + case MIDI_CONTROL_CHANGE: + if (nLength < 3) + { + break; + } + + switch (pMessage[1]) + { + case MIDI_CC_BANK_SELECT_LSB: + m_pSynthesizer->BankSelectLSB (pMessage[2]); + break; + } + break; + + case MIDI_PROGRAM_CHANGE: + m_pSynthesizer->ProgramChange (pMessage[1]); + break; + + case MIDI_PITCH_BEND: + m_pSynthesizer->setPitchbend (pMessage[1]); + break; + + default: + break; + } +} diff --git a/src/mididevice.h b/src/mididevice.h new file mode 100644 index 0000000..a595d1e --- /dev/null +++ b/src/mididevice.h @@ -0,0 +1,45 @@ +// +// mididevice.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _mididevice_h +#define _mididevice_h + +#include "config.h" +#include + +class CMiniDexed; + +class CMIDIDevice +{ +public: + CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig); + ~CMIDIDevice (void); + +protected: + void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); + +private: + CMiniDexed *m_pSynthesizer; + CConfig *m_pConfig; +}; + +#endif diff --git a/src/midikeyboard.cpp b/src/midikeyboard.cpp new file mode 100644 index 0000000..240d0b3 --- /dev/null +++ b/src/midikeyboard.cpp @@ -0,0 +1,105 @@ +// +// midikeyboard.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#include "midikeyboard.h" +#include +#include + +CMIDIKeyboard *CMIDIKeyboard::s_pThis[MaxInstances] = {0}; + +TMIDIPacketHandler * const CMIDIKeyboard::s_pMIDIPacketHandler[MaxInstances] = +{ + MIDIPacketHandler0, + MIDIPacketHandler1, + MIDIPacketHandler2, + MIDIPacketHandler3 +}; + +CMIDIKeyboard::CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, unsigned nInstance) +: CMIDIDevice (pSynthesizer, pConfig), + m_nInstance (nInstance), + m_pMIDIDevice (0) +{ + assert (m_nInstance < MaxInstances); + s_pThis[m_nInstance] = this; + + m_DeviceName.Format ("umidi%u", nInstance+1); +} + +CMIDIKeyboard::~CMIDIKeyboard (void) +{ + assert (m_nInstance < MaxInstances); + s_pThis[m_nInstance] = 0; +} + +void CMIDIKeyboard::Process (boolean bPlugAndPlayUpdated) +{ + if (!bPlugAndPlayUpdated) + { + return; + } + + if (m_pMIDIDevice == 0) + { + m_pMIDIDevice = + (CUSBMIDIDevice *) CDeviceNameService::Get ()->GetDevice (m_DeviceName, FALSE); + if (m_pMIDIDevice != 0) + { + assert (m_nInstance < MaxInstances); + m_pMIDIDevice->RegisterPacketHandler (s_pMIDIPacketHandler[m_nInstance]); + + m_pMIDIDevice->RegisterRemovedHandler (DeviceRemovedHandler, this); + } + } +} + +void CMIDIKeyboard::MIDIPacketHandler0 (unsigned nCable, u8 *pPacket, unsigned nLength) +{ + assert (s_pThis[0] != 0); + s_pThis[0]->MIDIMessageHandler (pPacket, nLength, nCable); +} + +void CMIDIKeyboard::MIDIPacketHandler1 (unsigned nCable, u8 *pPacket, unsigned nLength) +{ + assert (s_pThis[1] != 0); + s_pThis[1]->MIDIMessageHandler (pPacket, nLength, nCable); +} + +void CMIDIKeyboard::MIDIPacketHandler2 (unsigned nCable, u8 *pPacket, unsigned nLength) +{ + assert (s_pThis[2] != 0); + s_pThis[2]->MIDIMessageHandler (pPacket, nLength, nCable); +} + +void CMIDIKeyboard::MIDIPacketHandler3 (unsigned nCable, u8 *pPacket, unsigned nLength) +{ + assert (s_pThis[3] != 0); + s_pThis[3]->MIDIMessageHandler (pPacket, nLength, nCable); +} + +void CMIDIKeyboard::DeviceRemovedHandler (CDevice *pDevice, void *pContext) +{ + CMIDIKeyboard *pThis = static_cast (pContext); + assert (pThis != 0); + + pThis->m_pMIDIDevice = 0; +} diff --git a/src/midikeyboard.h b/src/midikeyboard.h new file mode 100644 index 0000000..386d83b --- /dev/null +++ b/src/midikeyboard.h @@ -0,0 +1,65 @@ +// +// midikeyboard.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _midikeyboard_h +#define _midikeyboard_h + +#include "mididevice.h" +#include "config.h" +#include +#include +#include +#include + +class CMiniDexed; + +class CMIDIKeyboard : public CMIDIDevice +{ +public: + static const unsigned MaxInstances = 4; + +public: + CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, unsigned nInstance = 0); + ~CMIDIKeyboard (void); + + void Process (boolean bPlugAndPlayUpdated); + +private: + static void MIDIPacketHandler0 (unsigned nCable, u8 *pPacket, unsigned nLength); + static void MIDIPacketHandler1 (unsigned nCable, u8 *pPacket, unsigned nLength); + static void MIDIPacketHandler2 (unsigned nCable, u8 *pPacket, unsigned nLength); + static void MIDIPacketHandler3 (unsigned nCable, u8 *pPacket, unsigned nLength); + + static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); + +private: + unsigned m_nInstance; + CString m_DeviceName; + + CUSBMIDIDevice * volatile m_pMIDIDevice; + + static CMIDIKeyboard *s_pThis[MaxInstances]; + + static TMIDIPacketHandler * const s_pMIDIPacketHandler[MaxInstances]; +}; + +#endif diff --git a/src/minidexed.cpp b/src/minidexed.cpp index a67877c..14d7251 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1,363 +1,272 @@ // // minidexed.cpp // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #include "minidexed.h" -#include "perftimer.h" -#include +#include #include - -#define MIDI_DUMP -#define PROFILE - -#define MIDI_NOTE_OFF 0b1000 -#define MIDI_NOTE_ON 0b1001 -#define MIDI_AFTERTOUCH 0xA0 -#define MIDI_CONTROL_CHANGE 0xB0 - #define MIDI_CC_BANK_SELECT_MSB 0 // TODO: not supported - #define MIDI_CC_BANK_SELECT_LSB 32 -#define MIDI_PROGRAM_CHANGE 0xC0 -#define MIDI_PITCH_BEND 0xE0 - -CMiniDexed *CMiniDexed::s_pThis = 0; - -extern uint8_t voices_bank[1][32][156]; - -#ifdef PROFILE -CPerformanceTimer GetChunkTimer ("GetChunk", 1000000U * CHUNK_SIZE/2 / SAMPLE_RATE); -#endif +#include + +LOGMODULE ("minidexed"); + +CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt) +: CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()), + m_pConfig (pConfig), + m_UI (this, pConfig), + m_PCKeyboard (this), + m_SerialMIDI (this, pInterrupt, pConfig), + m_bUseSerial (false), + m_GetChunkTimer ("GetChunk", + 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), + m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) +{ + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) + { + m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i); + assert (m_pMIDIKeyboard[i]); + } +}; bool CMiniDexed::Initialize (void) { - m_SysExFileLoader.Load (); + if (!m_UI.Initialize ()) + { + return false; + } - if (!m_Serial.Initialize(31250)) - { - return false; - } + m_SysExFileLoader.Load (); - - if (!m_LCD.Initialize ()) - { - return FALSE; - } + if (m_SerialMIDI.Initialize ()) + { + LOGNOTE ("Serial MIDI interface enabled"); - m_bUseSerial = true; + m_bUseSerial = true; + } - activate(); + activate (); - s_pThis->ChangeProgram(0); - s_pThis->setTranspose(24); + ProgramChange (0); + setTranspose (24); - return true; + return true; } -void CMiniDexed::Process(boolean bPlugAndPlayUpdated) +void CMiniDexed::Process (bool bPlugAndPlayUpdated) { -#ifdef PROFILE - GetChunkTimer.Dump (); -#endif - - if (m_pMIDIDevice != 0) + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) { - return; - } - - if (bPlugAndPlayUpdated) - { - m_pMIDIDevice = - (CUSBMIDIDevice *) CDeviceNameService::Get ()->GetDevice ("umidi1", FALSE); - if (m_pMIDIDevice != 0) - { - m_pMIDIDevice->RegisterRemovedHandler (USBDeviceRemovedHandler); - m_pMIDIDevice->RegisterPacketHandler (MIDIPacketHandler); - - return; - } + assert (m_pMIDIKeyboard[i]); + m_pMIDIKeyboard[i]->Process (bPlugAndPlayUpdated); } m_PCKeyboard.Process (bPlugAndPlayUpdated); - if (!m_bUseSerial) + if (m_bUseSerial) { - return; + m_SerialMIDI.Process (); } - // Read serial MIDI data - u8 Buffer[20]; - int nResult = m_Serial.Read (Buffer, sizeof Buffer); - if (nResult <= 0) - { - return; - } + m_UI.Process (); - // Process MIDI messages - // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message - for (int i = 0; i < nResult; i++) + if (m_bProfileEnabled) { - u8 uchData = Buffer[i]; - - switch (m_nSerialState) - { - case 0: - MIDIRestart: - if ((uchData & 0xE0) == 0x80) // Note on or off, all channels - { - m_SerialMessage[m_nSerialState++] = uchData; - } - break; - - case 1: - case 2: - if (uchData & 0x80) // got status when parameter expected - { - m_nSerialState = 0; - - goto MIDIRestart; - } - - m_SerialMessage[m_nSerialState++] = uchData; - - if (m_nSerialState == 3) // message is complete - { - MIDIPacketHandler (0, m_SerialMessage, sizeof m_SerialMessage); - - m_nSerialState = 0; - } - break; - - default: - assert (0); - break; - } + m_GetChunkTimer.Dump (); } } -void CMiniDexed::MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength) +void CMiniDexed::BankSelectLSB (unsigned nBankLSB) { - assert (s_pThis != 0); - - // The packet contents are just normal MIDI data - see - // https://www.midi.org/specifications/item/table-1-summary-of-midi-message - -#ifdef MIDI_DUMP - switch (nLength) - { - case 1: - printf ("MIDI %u: %02X\n", nCable, (unsigned) pPacket[0]); - break; - - case 2: - printf ("MIDI %u: %02X %02X\n", nCable, - (unsigned) pPacket[0], (unsigned) pPacket[1]); - break; - - case 3: - printf ("MIDI %u: %02X %02X %02X\n", nCable, - (unsigned) pPacket[0], (unsigned) pPacket[1], (unsigned) pPacket[2]); - break; - } -#endif - - if (pPacket[0] == MIDI_CONTROL_CHANGE) + if (nBankLSB > 127) { - if (pPacket[1] == MIDI_CC_BANK_SELECT_LSB) - { - if (pPacket[2] > 127) - { - return; - } - - printf ("Select voice bank %u\n", (unsigned) pPacket[2]+1); // MIDI numbering starts with 0, user interface with 1 - s_pThis->m_SysExFileLoader.SelectVoiceBank (pPacket[2]); - } - return; } - if (pPacket[0] == MIDI_PROGRAM_CHANGE) - { - s_pThis->ChangeProgram(pPacket[1]); - return; - } + // MIDI numbering starts with 0, user interface with 1 + printf ("Select voice bank %u\n", nBankLSB+1); - if (pPacket[0] == MIDI_PITCH_BEND) - { - s_pThis->setPitchbend((unsigned) pPacket[1]); - - return; - } + m_SysExFileLoader.SelectVoiceBank (nBankLSB); +} - if (nLength < 3) +void CMiniDexed::ProgramChange (unsigned nProgram) +{ + if (nProgram > 31) { return; } - u8 ucStatus = pPacket[0]; - //u8 ucChannel = ucStatus & 0x0F; - u8 ucType = ucStatus >> 4; - u8 ucKeyNumber = pPacket[1]; - u8 ucVelocity = pPacket[2]; + uint8_t Buffer[156]; + m_SysExFileLoader.GetVoice (nProgram, Buffer); + loadVoiceParameters (Buffer); - if (ucType == MIDI_NOTE_ON) - { - s_pThis->keydown(ucKeyNumber,ucVelocity); - } - else if (ucType == MIDI_NOTE_OFF) - { - s_pThis->keyup(ucKeyNumber); - } + m_UI.ProgramChanged (nProgram); } -void CMiniDexed::ChangeProgram(unsigned program) { - if(program > 31) { - return; - } - printf ("Loading voice %u\n", (unsigned) program+1); // MIDI numbering starts with 0, user interface with 1 - uint8_t Buffer[156]; - s_pThis->m_SysExFileLoader.GetVoice (program, Buffer); - s_pThis->loadVoiceParameters(Buffer); - char buf_name[11]; - memset(buf_name, 0, 11); // Initialize with 0x00 chars - s_pThis->setName(buf_name); - printf ("%s\n", buf_name); - // Print to optional HD44780 display - s_pThis->LCDWrite("\x1B[?25l"); // cursor off - CString String; - String.Format ("\n\r%i\n\r%s", program+1, buf_name); // MIDI numbering starts with 0, user interface with 1 - s_pThis->LCDWrite ((const char *) String); -} +//// PWM ////////////////////////////////////////////////////////////////////// -void CMiniDexed::USBDeviceRemovedHandler (CDevice *pDevice, void *pContext) +CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt) +: CMiniDexed (pConfig, pInterrupt), + CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize ()) { - if (s_pThis->m_pMIDIDevice == (CUSBMIDIDevice *) pDevice) - { - s_pThis->m_pMIDIDevice = 0; - } } bool CMiniDexedPWM::Initialize (void) { - if (!CMiniDexed::Initialize()) - { - return false; - } + if (!CMiniDexed::Initialize ()) + { + return false; + } - return Start (); + return Start (); } -unsigned CMiniDexedPWM::GetChunk(u32 *pBuffer, unsigned nChunkSize) +unsigned CMiniDexedPWM::GetChunk (u32 *pBuffer, unsigned nChunkSize) { -#ifdef PROFILE - GetChunkTimer.Start(); -#endif - - unsigned nResult = nChunkSize; + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } - int16_t int16_buf[nChunkSize/2]; + unsigned nResult = nChunkSize; - getSamples(nChunkSize/2, int16_buf); + int16_t SampleBuffer[nChunkSize/2]; + getSamples (nChunkSize/2, SampleBuffer); - for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer - { - s32 nSample = int16_buf[i++]; - nSample += 32768; - nSample *= GetRangeMax()/2; - nSample /= 32768; + for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer + { + s32 nSample = SampleBuffer[i++]; + nSample += 32768; + nSample *= GetRangeMax()/2; + nSample /= 32768; - *pBuffer++ = nSample; // 2 stereo channels - *pBuffer++ = nSample; - } + *pBuffer++ = nSample; // 2 stereo channels + *pBuffer++ = nSample; + } -#ifdef PROFILE - GetChunkTimer.Stop(); -#endif + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop (); + } - return(nResult); + return nResult; }; +//// I2S ////////////////////////////////////////////////////////////////////// + +CMiniDexedI2S::CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt, + CI2CMaster *pI2CMaster) +: CMiniDexed (pConfig, pInterrupt), + CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize (), false, pI2CMaster, + pConfig->GetDACI2CAddress ()) +{ +} + bool CMiniDexedI2S::Initialize (void) { - if (!CMiniDexed::Initialize()) - { - return false; - } + if (!CMiniDexed::Initialize ()) + { + return false; + } - return Start (); + return Start (); } -unsigned CMiniDexedI2S::GetChunk(u32 *pBuffer, unsigned nChunkSize) +unsigned CMiniDexedI2S::GetChunk (u32 *pBuffer, unsigned nChunkSize) { -#ifdef PROFILE - GetChunkTimer.Start(); -#endif - - unsigned nResult = nChunkSize; + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } - int16_t int16_buf[nChunkSize/2]; + unsigned nResult = nChunkSize; - getSamples(nChunkSize/2, int16_buf); + int16_t SampleBuffer[nChunkSize/2]; + getSamples (nChunkSize/2, SampleBuffer); - for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer - { - s32 nSample = int16_buf[i++]; - nSample <<= 8; + for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer + { + s32 nSample = SampleBuffer[i++]; + nSample <<= 8; - *pBuffer++ = nSample; // 2 stereo channels - *pBuffer++ = nSample; - } + *pBuffer++ = nSample; // 2 stereo channels + *pBuffer++ = nSample; + } -#ifdef PROFILE - GetChunkTimer.Stop(); -#endif + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop (); + } - return(nResult); + return nResult; }; +//// HDMI ///////////////////////////////////////////////////////////////////// + +CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt) +: CMiniDexed (pConfig, pInterrupt), + CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize ()) +{ +} + bool CMiniDexedHDMI::Initialize (void) { - if (!CMiniDexed::Initialize()) - { - return false; - } + if (!CMiniDexed::Initialize ()) + { + return false; + } - return Start (); + return Start (); } unsigned CMiniDexedHDMI::GetChunk(u32 *pBuffer, unsigned nChunkSize) { -#ifdef PROFILE - GetChunkTimer.Start(); -#endif - - unsigned nResult = nChunkSize; - - int16_t int16_buf[nChunkSize/2]; - unsigned nFrame = 0; + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } - getSamples(nChunkSize/2, int16_buf); + unsigned nResult = nChunkSize; - for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer - { - s32 nSample = int16_buf[i++]; - nSample <<= 8; + int16_t SampleBuffer[nChunkSize/2]; + getSamples (nChunkSize/2, SampleBuffer); - nSample = ConvertIEC958Sample (nSample, nFrame); + unsigned nFrame = 0; + for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer + { + s32 nSample = SampleBuffer[i++]; + nSample <<= 8; - if (++nFrame == IEC958_FRAMES_PER_BLOCK) - nFrame = 0; + nSample = ConvertIEC958Sample (nSample, nFrame); + if (++nFrame == IEC958_FRAMES_PER_BLOCK) + { + nFrame = 0; + } - *pBuffer++ = nSample; // 2 stereo channels - *pBuffer++ = nSample; - } + *pBuffer++ = nSample; // 2 stereo channels + *pBuffer++ = nSample; + } -#ifdef PROFILE - GetChunkTimer.Stop(); -#endif + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop(); + } - return(nResult); + return nResult; }; - -void CMiniDexed::LCDWrite (const char *pString) -{ - m_LCD.Write (pString, strlen (pString)); -} diff --git a/src/minidexed.h b/src/minidexed.h index da23ab8..5e82ebe 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -1,117 +1,104 @@ // // minidexed.h // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #ifndef _minidexed_h #define _minidexed_h -#include +#include "dexedadapter.h" +#include "config.h" +#include "userinterface.h" +#include "sysexfileloader.h" +#include "midikeyboard.h" +#include "pckeyboard.h" +#include "serialmididevice.h" +#include "perftimer.h" #include -#include +#include #include #include -#include -#include -#include #include #include #include -#include "sysexfileloader.h" -#include "pckeyboard.h" -#include -#define SAMPLE_RATE 48000 +class CMiniDexed : public CDexedAdapter +{ +public: + CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt); -#define CHUNK_SIZE (256) -#define CHUNK_SIZE_HDMI (384 * 6) + virtual bool Initialize (void); -#define DAC_I2C_ADDRESS 0 // I2C slave address of the DAC (0 for auto probing) + void Process (bool bPlugAndPlayUpdated); -// HD44780 LCD configuration -#define COLUMNS 16 -#define ROWS 2 -// GPIO pins (Brcm numbering) -#define EN_PIN 17 // Enable -#define RS_PIN 18 // Register Select -#define RW_PIN 19 // Read/Write (set to 0 if not connected) -#define D4_PIN 22 // Data 4 -#define D5_PIN 23 // Data 5 -#define D6_PIN 24 // Data 6 -#define D7_PIN 25 // Data 7 + void BankSelectLSB (unsigned nBankLSB); + void ProgramChange (unsigned nProgram); -class CMiniDexed : public Dexed -{ - public: - CMiniDexed(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt) -: Dexed(max_notes,(int)sample_rate), - m_pMIDIDevice (0), - m_PCKeyboard (this), - m_Serial (pInterrupt, TRUE), - m_bUseSerial (FALSE), - m_nSerialState (0), - m_LCD (COLUMNS, ROWS, D4_PIN, D5_PIN, D6_PIN, D7_PIN, EN_PIN, RS_PIN, RW_PIN) - { - s_pThis = this; - }; - - virtual bool Initialize (void); - void Process(boolean bPlugAndPlayUpdated); - private: - void LCDWrite (const char *pString); - protected: - static void MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength); - static void KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6]); - static void ChangeProgram(unsigned program); - static void USBDeviceRemovedHandler (CDevice *pDevice, void *pContext); - CUSBMIDIDevice * volatile m_pMIDIDevice; - CPCKeyboard m_PCKeyboard; - CSerialDevice m_Serial; - boolean m_bUseSerial; - unsigned m_nSerialState; - u8 m_SerialMessage[3]; - CSysExFileLoader m_SysExFileLoader; - private: - CHD44780Device m_LCD; - - static CMiniDexed *s_pThis; +private: + CConfig *m_pConfig; + + CUserInterface m_UI; + CSysExFileLoader m_SysExFileLoader; + + CMIDIKeyboard *m_pMIDIKeyboard[CConfig::MaxUSBMIDIDevices]; + CPCKeyboard m_PCKeyboard; + CSerialMIDIDevice m_SerialMIDI; + bool m_bUseSerial; + +protected: + CPerformanceTimer m_GetChunkTimer; + bool m_bProfileEnabled; }; +//// PWM ////////////////////////////////////////////////////////////////////// + class CMiniDexedPWM : public CMiniDexed, public CPWMSoundBaseDevice { - public: - CMiniDexedPWM(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt) -: CMiniDexed(max_notes,(int)sample_rate, pInterrupt), - CPWMSoundBaseDevice (pInterrupt, sample_rate, CHUNK_SIZE) - { - } - - bool Initialize (void); - unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); +public: + CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt); + + bool Initialize (void); + + unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); }; +//// I2S ////////////////////////////////////////////////////////////////////// + class CMiniDexedI2S : public CMiniDexed, public CI2SSoundBaseDevice { - public: - CMiniDexedI2S(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt, CI2CMaster *pI2CMaster) -: CMiniDexed(max_notes,(int)sample_rate, pInterrupt), - CI2SSoundBaseDevice (pInterrupt, sample_rate, CHUNK_SIZE, FALSE, pI2CMaster, DAC_I2C_ADDRESS) - { - } - - bool Initialize (void); - unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); +public: + CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt, + CI2CMaster *pI2CMaster); + + bool Initialize (void); + + unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); }; +//// HDMI ///////////////////////////////////////////////////////////////////// + class CMiniDexedHDMI : public CMiniDexed, public CHDMISoundBaseDevice { - public: - CMiniDexedHDMI(uint8_t max_notes, uint16_t sample_rate, CInterruptSystem *pInterrupt) -: CMiniDexed(max_notes,(int)sample_rate, pInterrupt), - CHDMISoundBaseDevice (pInterrupt, sample_rate, CHUNK_SIZE_HDMI) - { - } - - bool Initialize (void); - unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); +public: + CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt); + + bool Initialize (void); + + unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); }; #endif diff --git a/src/minidexed.ini b/src/minidexed.ini new file mode 100644 index 0000000..6f7ae4c --- /dev/null +++ b/src/minidexed.ini @@ -0,0 +1,26 @@ +# +# minidexed.ini +# + +# Sound device +SoundDevice=pwm +SampleRate=48000 +#ChunkSize=256 +DACI2CAddress=0 + +# MIDI +MIDIBaudRate=31250 + +# HD44780 LCD +LCDEnabled=1 +LCDPinEnable=17 +LCDPinRegisterSelect=18 +LCDPinReadWrite=19 +LCDPinData4=22 +LCDPinData5=23 +LCDPinData6=24 +LCDPinData7=25 + +# Debug +MIDIDumpEnabled=1 +ProfileEnabled=1 diff --git a/src/perftimer.cpp b/src/perftimer.cpp index 802d81b..5a21471 100644 --- a/src/perftimer.cpp +++ b/src/perftimer.cpp @@ -1,6 +1,22 @@ // // perftimer.cpp // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #include "perftimer.h" #include diff --git a/src/perftimer.h b/src/perftimer.h index 436ffa5..537b6d0 100644 --- a/src/perftimer.h +++ b/src/perftimer.h @@ -1,6 +1,22 @@ // // perftimer.h // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #ifndef _perftimer_h #define _perftimer_h diff --git a/src/serialmididevice.cpp b/src/serialmididevice.cpp new file mode 100644 index 0000000..cd7ad47 --- /dev/null +++ b/src/serialmididevice.cpp @@ -0,0 +1,98 @@ +// +// serialmididevice.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#include "serialmididevice.h" +#include + +CSerialMIDIDevice::CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, + CConfig *pConfig) +: CMIDIDevice (pSynthesizer, pConfig), + m_pConfig (pConfig), + m_Serial (pInterrupt, TRUE), + m_nSerialState (0) +{ +} + +CSerialMIDIDevice::~CSerialMIDIDevice (void) +{ + m_nSerialState = 255; +} + +boolean CSerialMIDIDevice::Initialize (void) +{ + assert (m_pConfig); + return m_Serial.Initialize (m_pConfig->GetMIDIBaudRate ()); +} + +void CSerialMIDIDevice::Process (void) +{ + // Read serial MIDI data + u8 Buffer[100]; + int nResult = m_Serial.Read (Buffer, sizeof Buffer); + if (nResult <= 0) + { + return; + } + + // Process MIDI messages + // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message + for (int i = 0; i < nResult; i++) + { + u8 uchData = Buffer[i]; + + switch (m_nSerialState) + { + case 0: + MIDIRestart: + if ( (uchData & 0x80) == 0x80 // status byte, all channels + && (uchData & 0xF0) != 0xF0) // ignore system messages + { + m_SerialMessage[m_nSerialState++] = uchData; + } + break; + + case 1: + case 2: + if (uchData & 0x80) // got status when parameter expected + { + m_nSerialState = 0; + + goto MIDIRestart; + } + + m_SerialMessage[m_nSerialState++] = uchData; + + if ( (m_SerialMessage[0] & 0xE0) == 0xC0 + || m_nSerialState == 3) // message is complete + { + MIDIMessageHandler (m_SerialMessage, m_nSerialState); + + m_nSerialState = 0; + } + break; + + default: + assert (0); + break; + } + } +} diff --git a/src/serialmididevice.h b/src/serialmididevice.h new file mode 100644 index 0000000..82c2618 --- /dev/null +++ b/src/serialmididevice.h @@ -0,0 +1,52 @@ +// +// serialmididevice.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _serialmididevice_h +#define _serialmididevice_h + +#include "mididevice.h" +#include "config.h" +#include +#include +#include + +class CMiniDexed; + +class CSerialMIDIDevice : public CMIDIDevice +{ +public: + CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, CConfig *pConfig); + ~CSerialMIDIDevice (void); + + boolean Initialize (void); + + void Process (void); + +private: + CConfig *m_pConfig; + + CSerialDevice m_Serial; + unsigned m_nSerialState; + u8 m_SerialMessage[3]; +}; + +#endif diff --git a/src/sysexfileloader.cpp b/src/sysexfileloader.cpp index 968242d..63d8601 100644 --- a/src/sysexfileloader.cpp +++ b/src/sysexfileloader.cpp @@ -1,6 +1,22 @@ // // sysexfileloader.cpp // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #include "sysexfileloader.h" #include #include @@ -8,8 +24,7 @@ #include #include #include - -extern uint8_t voices_bank[1][32][156]; +#include "voices.c" LOGMODULE ("syxfile"); diff --git a/src/sysexfileloader.h b/src/sysexfileloader.h index c90af4d..8b0ebab 100644 --- a/src/sysexfileloader.h +++ b/src/sysexfileloader.h @@ -3,6 +3,22 @@ // // See: https://github.com/asb2m10/dexed/blob/master/Documentation/sysex-format.txt // +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// #ifndef _sysexfileloader_h #define _sysexfileloader_h diff --git a/src/userinterface.cpp b/src/userinterface.cpp new file mode 100644 index 0000000..7281118 --- /dev/null +++ b/src/userinterface.cpp @@ -0,0 +1,107 @@ +// +// userinterface.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#include "userinterface.h" +#include "minidexed.h" +#include +#include +#include +#include +#include + +LOGMODULE ("ui"); + +CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CConfig *pConfig) +: m_pMiniDexed (pMiniDexed), + m_pConfig (pConfig), + m_pLCD (0), + m_pLCDBuffered (0) +{ +} + +CUserInterface::~CUserInterface (void) +{ + delete m_pLCDBuffered; + delete m_pLCD; +} + +bool CUserInterface::Initialize (void) +{ + assert (m_pConfig); + + if (m_pConfig->GetLCDEnabled ()) + { + m_pLCD = new CHD44780Device (CConfig::LCDColumns, CConfig::LCDRows, + m_pConfig->GetLCDPinData4 (), + m_pConfig->GetLCDPinData5 (), + m_pConfig->GetLCDPinData6 (), + m_pConfig->GetLCDPinData7 (), + m_pConfig->GetLCDPinEnable (), + m_pConfig->GetLCDPinRegisterSelect (), + m_pConfig->GetLCDPinReadWrite ()); + assert (m_pLCD); + + if (!m_pLCD->Initialize ()) + { + return false; + } + + m_pLCDBuffered = new CWriteBufferDevice (m_pLCD); + assert (m_pLCDBuffered); + + LCDWrite ("\x1B[?25l"); // cursor off + + LOGDBG ("LCD initialized"); + } + + return true; +} + +void CUserInterface::Process (void) +{ + if (m_pLCDBuffered) + { + m_pLCDBuffered->Update (); + } +} + +void CUserInterface::ProgramChanged (unsigned nProgram) +{ + nProgram++; // MIDI numbering starts with 0, user interface with 1 + + // fetch program name from Dexed instance + char ProgramName[11]; + memset (ProgramName, 0, sizeof ProgramName); + assert (m_pMiniDexed); + m_pMiniDexed->setName (ProgramName); + + printf ("Loading voice %u: \"%s\"\n", nProgram, ProgramName); + + CString String; + String.Format ("\n\r%u\n\r%s", nProgram, ProgramName); + LCDWrite (String); +} + +void CUserInterface::LCDWrite (const char *pString) +{ + if (m_pLCDBuffered) + { + m_pLCDBuffered->Write (pString, strlen (pString)); + } +} diff --git a/src/userinterface.h b/src/userinterface.h new file mode 100644 index 0000000..5bb24f0 --- /dev/null +++ b/src/userinterface.h @@ -0,0 +1,52 @@ +// +// userinterface.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _userinterface_h +#define _userinterface_h + +#include "config.h" +#include +#include + +class CMiniDexed; + +class CUserInterface +{ +public: + CUserInterface (CMiniDexed *pMiniDexed, CConfig *pConfig); + ~CUserInterface (void); + + bool Initialize (void); + + void Process (void); + + void ProgramChanged (unsigned nProgram); // 0 .. 127 + +private: + void LCDWrite (const char *pString); // Print to optional HD44780 display + +private: + CMiniDexed *m_pMiniDexed; + CConfig *m_pConfig; + + CHD44780Device *m_pLCD; + CWriteBufferDevice *m_pLCDBuffered; +}; + +#endif