From d6025f194319fea7a205621a2aa81bd29ca0fd2f Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Sat, 19 Mar 2022 14:47:18 +0100 Subject: [PATCH] Support multiple Dexed instances (#50) * dexedadapter: Protect setSustain() with spin-lock too * Initial support for multiple Dexed instances * Currently 2 similar instances, which work with the same parameters * Only 1 instance with max. 8 notes polyphony on RPi 1 * Chunk size needs to be increased on RPi 1 * Support 8 Dexed instances (TGs) with multi-core * Core 1 kicks core 2 and 3 and processes two TGs then * Cores 2 and 3 wait for a kick and process three TGs each then * When all cores are ready, the output will be summed up * All 8 TGs generate the same sound at the moment * The maximum chunk size is limited to 4096 now * Maintain voice bank number per TG * Support TG select in UI * Active TG number is shown on LCD display * Next TG is selected by double click * MIDI receive and PC keyboard are still in omni mode * pckeyboard: Fake MIDI events * on MIDI cable 0, channel 0 * instead of calling key[down|up]() * derive from class CMIDIDevice * ui: Precede screen messages with TG number * Configure MIDI mapping from UI * ui: New menu item "MIDI" for configures assigned MIDI channel * ui: Holding switch for at least one second returns to menu home * ui: Do not show TG instance, if there is only one * By default TG1 is in omni mode, all other TGs are not assigned * config: Default chunk size is 1024 without multi-core --- src/config.cpp | 4 + src/config.h | 17 ++- src/dexedadapter.h | 7 ++ src/mididevice.cpp | 180 +++++++++++++++------------ src/mididevice.h | 14 +++ src/minidexed.cpp | 266 +++++++++++++++++++++++++++++++++++++--- src/minidexed.h | 42 ++++++- src/pckeyboard.cpp | 14 +-- src/pckeyboard.h | 8 +- src/sysexfileloader.cpp | 23 ++-- src/sysexfileloader.h | 7 +- src/userinterface.cpp | 124 ++++++++++++++----- src/userinterface.h | 17 ++- 13 files changed, 551 insertions(+), 172 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index a31e1a4..0242548 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -38,7 +38,11 @@ void CConfig::Load (void) m_SoundDevice = m_Properties.GetString ("SoundDevice", "pwm"); m_nSampleRate = m_Properties.GetNumber ("SampleRate", 48000); +#ifdef ARM_ALLOW_MULTI_CORE m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 256); +#else + m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 1024); +#endif m_nDACI2CAddress = m_Properties.GetNumber ("DACI2CAddress", 0); m_nMIDIBaudRate = m_Properties.GetNumber ("MIDIBaudRate", 31250); diff --git a/src/config.h b/src/config.h index 1846743..f94e62e 100644 --- a/src/config.h +++ b/src/config.h @@ -25,12 +25,27 @@ #include #include +#include #include class CConfig // Configuration for MiniDexed { public: - static const unsigned MaxNotes = 16; // polyphony +#ifndef ARM_ALLOW_MULTI_CORE + static const unsigned ToneGenerators = 1; +#else + static const unsigned TGsCore1 = 2; // process 2 TGs on core 1 + static const unsigned TGsCore23 = 3; // process 3 TGs on core 2 and 3 each + static const unsigned ToneGenerators = TGsCore1 + 2*TGsCore23; +#endif + +#if RASPPI == 1 + static const unsigned MaxNotes = 8; // polyphony +#else + static const unsigned MaxNotes = 16; +#endif + + static const unsigned MaxChunkSize = 4096; #if RASPPI <= 3 static const unsigned MaxUSBMIDIDevices = 2; diff --git a/src/dexedadapter.h b/src/dexedadapter.h index 3e85c1b..7f2badc 100644 --- a/src/dexedadapter.h +++ b/src/dexedadapter.h @@ -70,6 +70,13 @@ public: m_SpinLock.Release (); } + void setSustain (bool sustain) + { + m_SpinLock.Acquire (); + Dexed::setSustain (sustain); + m_SpinLock.Release (); + } + private: CSpinLock m_SpinLock; }; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index d5559dd..33032ac 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -45,6 +45,10 @@ CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig) : m_pSynthesizer (pSynthesizer), m_pConfig (pConfig) { + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + m_ChannelMap[nTG] = Disabled; + } } CMIDIDevice::~CMIDIDevice (void) @@ -52,6 +56,18 @@ CMIDIDevice::~CMIDIDevice (void) m_pSynthesizer = 0; } +void CMIDIDevice::SetChannel (u8 ucChannel, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + m_ChannelMap[nTG] = ucChannel; +} + +u8 CMIDIDevice::GetChannel (unsigned nTG) const +{ + assert (nTG < CConfig::ToneGenerators); + return m_ChannelMap[nTG]; +} + void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable) { assert (m_pSynthesizer != 0); @@ -67,17 +83,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign if ( pMessage[0] != MIDI_TIMING_CLOCK && pMessage[0] != MIDI_ACTIVE_SENSING) { - printf ("MIDI %u: %02X\n", nCable, (unsigned) pMessage[0]); + printf ("MIDI%u: %02X\n", nCable, (unsigned) pMessage[0]); } break; case 2: - printf ("MIDI %u: %02X %02X\n", nCable, + printf ("MIDI%u: %02X %02X\n", nCable, (unsigned) pMessage[0], (unsigned) pMessage[1]); break; case 3: - printf ("MIDI %u: %02X %02X %02X\n", nCable, + printf ("MIDI%u: %02X %02X %02X\n", nCable, (unsigned) pMessage[0], (unsigned) pMessage[1], (unsigned) pMessage[2]); break; @@ -89,87 +105,93 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign return; } - u8 ucStatus = pMessage[0]; - // TODO: u8 ucChannel = ucStatus & 0x0F; - u8 ucType = ucStatus >> 4; - u8 ucKeyNumber = pMessage[1]; - u8 ucVelocity = pMessage[2]; + u8 ucStatus = pMessage[0]; + u8 ucChannel = ucStatus & 0x0F; + u8 ucType = ucStatus >> 4; - switch (ucType) + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { - case MIDI_NOTE_ON: - if (nLength < 3) + if ( m_ChannelMap[nTG] == ucChannel + || m_ChannelMap[nTG] == OmniMode) { - break; - } - - if (ucVelocity > 0) - { - if (ucVelocity <= 127) + switch (ucType) { - m_pSynthesizer->keydown (ucKeyNumber, ucVelocity); + case MIDI_NOTE_ON: + if (nLength < 3) + { + break; + } + + if (pMessage[2] > 0) + { + if (pMessage[2] <= 127) + { + m_pSynthesizer->keydown (pMessage[1], + pMessage[2], nTG); + } + } + else + { + m_pSynthesizer->keyup (pMessage[1], nTG); + } + break; + + case MIDI_NOTE_OFF: + if (nLength < 3) + { + break; + } + + m_pSynthesizer->keyup (pMessage[1], nTG); + break; + + case MIDI_CONTROL_CHANGE: + if (nLength < 3) + { + break; + } + + switch (pMessage[1]) + { + case MIDI_CC_MODULATION: + m_pSynthesizer->setModWheel (pMessage[2], nTG); + m_pSynthesizer->ControllersRefresh (nTG); + break; + + case MIDI_CC_VOLUME: + m_pSynthesizer->SetVolume (pMessage[2], nTG); + break; + + case MIDI_CC_BANK_SELECT_LSB: + m_pSynthesizer->BankSelectLSB (pMessage[2], nTG); + break; + + case MIDI_CC_BANK_SUSTAIN: + m_pSynthesizer->setSustain (pMessage[2] >= 64, nTG); + break; + } + break; + + case MIDI_PROGRAM_CHANGE: + m_pSynthesizer->ProgramChange (pMessage[1], nTG); + break; + + case MIDI_PITCH_BEND: { + if (nLength < 3) + { + break; + } + + s16 nValue = pMessage[1]; + nValue |= (s16) pMessage[2] << 7; + nValue -= 0x2000; + + m_pSynthesizer->setPitchbend (nValue, nTG); + } break; + + default: + break; } } - 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_MODULATION: - m_pSynthesizer->setModWheel (pMessage[2]); - m_pSynthesizer->ControllersRefresh (); - break; - - case MIDI_CC_VOLUME: - m_pSynthesizer->SetVolume (pMessage[2]); - break; - - case MIDI_CC_BANK_SELECT_LSB: - m_pSynthesizer->BankSelectLSB (pMessage[2]); - break; - - case MIDI_CC_BANK_SUSTAIN: - m_pSynthesizer->setSustain (pMessage[2] >= 64); - break; - } - break; - - case MIDI_PROGRAM_CHANGE: - m_pSynthesizer->ProgramChange (pMessage[1]); - break; - - case MIDI_PITCH_BEND: { - if (nLength < 3) - { - break; - } - - s16 nValue = pMessage[1]; - nValue |= (s16) pMessage[2] << 7; - nValue -= 0x2000; - - m_pSynthesizer->setPitchbend (nValue); - } break; - - default: - break; } } diff --git a/src/mididevice.h b/src/mididevice.h index a595d1e..166351d 100644 --- a/src/mididevice.h +++ b/src/mididevice.h @@ -30,16 +30,30 @@ class CMiniDexed; class CMIDIDevice { +public: + enum TChannel + { + Channels = 16, + OmniMode = Channels, + Disabled, + ChannelUnknown + }; + public: CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig); ~CMIDIDevice (void); + void SetChannel (u8 ucChannel, unsigned nTG); + u8 GetChannel (unsigned nTG) const; + protected: void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); private: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; + + u8 m_ChannelMap[CConfig::ToneGenerators]; }; #endif diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 4e4d8cf..762cf3d 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -31,13 +31,13 @@ LOGMODULE ("minidexed"); CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster) -: CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()), +: #ifdef ARM_ALLOW_MULTI_CORE CMultiCoreSupport (CMemorySystem::Get ()), #endif m_pConfig (pConfig), m_UI (this, pGPIOManager, pConfig), - m_PCKeyboard (this), + m_PCKeyboard (this, pConfig), m_SerialMIDI (this, pInterrupt, pConfig), m_bUseSerial (false), m_pSoundDevice (0), @@ -45,6 +45,18 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) { + assert (m_pConfig); + + for (unsigned i = 0; i < CConfig::ToneGenerators; i++) + { + m_nVoiceBankID[i] = 0; + + m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); + assert (m_pTG[i]); + + m_pTG[i]->activate (); + } + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) { m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i); @@ -75,6 +87,13 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_pSoundDevice = new CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), pConfig->GetChunkSize ()); } + +#ifdef ARM_ALLOW_MULTI_CORE + for (unsigned nCore = 0; nCore < CORES; nCore++) + { + m_CoreStatus[nCore] = CoreStatusInit; + } +#endif }; bool CMiniDexed::Initialize (void) @@ -96,14 +115,20 @@ bool CMiniDexed::Initialize (void) m_bUseSerial = true; } - activate (); + for (unsigned i = 0; i < CConfig::ToneGenerators; i++) + { + assert (m_pTG[i]); + + SetVolume (100, i); + ProgramChange (0, i); - SetVolume (100); - ProgramChange (0); - setTranspose (24); + m_pTG[i]->setTranspose (24); - setPBController (12, 1); - setMWController (99, 7, 0); + m_pTG[i]->setPBController (12, 1); + m_pTG[i]->setMWController (99, 7, 0); + } + + SetMIDIChannel (CMIDIDevice::OmniMode, 0); // setup and start the sound device if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) @@ -161,13 +186,58 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) void CMiniDexed::Run (unsigned nCore) { + assert (1 <= nCore && nCore < CORES); + if (nCore == 1) { - while (1) + m_CoreStatus[nCore] = CoreStatusIdle; // core 1 ready + + // wait for cores 2 and 3 to be ready + for (unsigned nCore = 2; nCore < CORES; nCore++) + { + while (m_CoreStatus[nCore] != CoreStatusIdle) + { + // just wait + } + } + + while (m_CoreStatus[nCore] != CoreStatusExit) { ProcessSound (); } } + else // core 2 and 3 + { + while (1) + { + m_CoreStatus[nCore] = CoreStatusIdle; // ready to be kicked + while (m_CoreStatus[nCore] == CoreStatusIdle) + { + // just wait + } + + // now kicked from core 1 + + if (m_CoreStatus[nCore] == CoreStatusExit) + { + m_CoreStatus[nCore] = CoreStatusUnknown; + + break; + } + + assert (m_CoreStatus[nCore] == CoreStatusBusy); + + // process the TGs, assigned to this core (2 or 3) + + assert (m_nFramesToProcess <= CConfig::MaxChunkSize); + unsigned nTG = CConfig::TGsCore1 + (nCore-2)*CConfig::TGsCore23; + for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) + { + assert (m_pTG[nTG]); + m_pTG[nTG]->getSamples (m_nFramesToProcess, m_OutputLevel[nTG]); + } + } + } } #endif @@ -177,44 +247,158 @@ CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) return &m_SysExFileLoader; } -void CMiniDexed::BankSelectLSB (unsigned nBankLSB) +void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) { if (nBankLSB > 127) { return; } - m_SysExFileLoader.SelectVoiceBank (nBankLSB); + assert (nTG < CConfig::ToneGenerators); + m_nVoiceBankID[nTG] = nBankLSB; - m_UI.BankSelected (nBankLSB); + m_UI.BankSelected (nBankLSB, nTG); } -void CMiniDexed::ProgramChange (unsigned nProgram) +void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) { if (nProgram > 31) { return; } + assert (nTG < CConfig::ToneGenerators); uint8_t Buffer[156]; - m_SysExFileLoader.GetVoice (nProgram, Buffer); - loadVoiceParameters (Buffer); + m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer); - m_UI.ProgramChanged (nProgram); + assert (m_pTG[nTG]); + m_pTG[nTG]->loadVoiceParameters (Buffer); + + m_UI.ProgramChanged (nProgram, nTG); } -void CMiniDexed::SetVolume (unsigned nVolume) +void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) { if (nVolume > 127) { return; } - setGain (nVolume / 127.0); + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setGain (nVolume / 127.0); + + m_UI.VolumeChanged (nVolume, nTG); +} + +void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) + { + assert (m_pMIDIKeyboard[i]); + m_pMIDIKeyboard[i]->SetChannel (uchChannel, nTG); + } + + m_PCKeyboard.SetChannel (uchChannel, nTG); + + if (m_bUseSerial) + { + m_SerialMIDI.SetChannel (uchChannel, nTG); + } + + m_UI.MIDIChannelChanged (uchChannel, nTG); +} + +void CMiniDexed::keyup (int16_t pitch, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->keyup (pitch); +} + +void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->keydown (pitch, velocity); +} + +void CMiniDexed::setSustain(bool sustain, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setSustain (sustain); +} + +void CMiniDexed::setModWheel (uint8_t value, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setModWheel (value); +} + +void CMiniDexed::setPitchbend (int16_t value, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setPitchbend (value); +} + +void CMiniDexed::ControllersRefresh (unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->ControllersRefresh (); +} + +std::string CMiniDexed::GetVoiceName (unsigned nTG) +{ + char VoiceName[11]; + memset (VoiceName, 0, sizeof VoiceName); + + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setName (VoiceName); + + std::string Result (VoiceName); + + return Result; +} + +#ifndef ARM_ALLOW_MULTI_CORE + +void CMiniDexed::ProcessSound (void) +{ + assert (m_pSoundDevice); + + unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail (); + if (nFrames >= m_nQueueSizeFrames/2) + { + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } + + int16_t SampleBuffer[nFrames]; + m_pTG[0]->getSamples (nFrames, SampleBuffer); - m_UI.VolumeChanged (nVolume); + if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) + != (int) sizeof SampleBuffer) + { + LOGERR ("Sound data dropped"); + } + + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop (); + } + } } +#else // #ifdef ARM_ALLOW_MULTI_CORE + void CMiniDexed::ProcessSound (void) { assert (m_pSoundDevice); @@ -227,8 +411,48 @@ void CMiniDexed::ProcessSound (void) m_GetChunkTimer.Start (); } + m_nFramesToProcess = nFrames; + + // kick secondary cores + for (unsigned nCore = 2; nCore < CORES; nCore++) + { + assert (m_CoreStatus[nCore] == CoreStatusIdle); + m_CoreStatus[nCore] = CoreStatusBusy; + } + + // process the TGs assigned to core 1 + assert (nFrames <= CConfig::MaxChunkSize); + for (unsigned i = 0; i < CConfig::TGsCore1; i++) + { + assert (m_pTG[i]); + m_pTG[i]->getSamples (nFrames, m_OutputLevel[i]); + } + + // wait for cores 2 and 3 to complete their work + for (unsigned nCore = 2; nCore < CORES; nCore++) + { + while (m_CoreStatus[nCore] != CoreStatusIdle) + { + // just wait + } + } + + // now mix the output of all TGs int16_t SampleBuffer[nFrames]; - getSamples (nFrames, SampleBuffer); + assert (CConfig::ToneGenerators == 8); + for (unsigned i = 0; i < nFrames; i++) + { + int32_t nSample = m_OutputLevel[0][i] + + m_OutputLevel[1][i] + + m_OutputLevel[2][i] + + m_OutputLevel[3][i] + + m_OutputLevel[4][i] + + m_OutputLevel[5][i] + + m_OutputLevel[6][i] + + m_OutputLevel[7][i]; + + SampleBuffer[i] = (int16_t) (nSample / CConfig::ToneGenerators); + } if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) != (int) sizeof SampleBuffer) @@ -242,3 +466,5 @@ void CMiniDexed::ProcessSound (void) } } } + +#endif diff --git a/src/minidexed.h b/src/minidexed.h index 17ca610..144e53b 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -29,6 +29,7 @@ #include "serialmididevice.h" #include "perftimer.h" #include +#include #include #include #include @@ -36,9 +37,9 @@ #include #include -class CMiniDexed : public CDexedAdapter +class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE - , public CMultiCoreSupport +: public CMultiCoreSupport #endif { public: @@ -55,16 +56,41 @@ public: CSysExFileLoader *GetSysExFileLoader (void); - void BankSelectLSB (unsigned nBankLSB); - void ProgramChange (unsigned nProgram); - void SetVolume (unsigned nVolume); + void BankSelectLSB (unsigned nBankLSB, unsigned nTG); + void ProgramChange (unsigned nProgram, unsigned nTG); + void SetVolume (unsigned nVolume, unsigned nTG); + void SetMIDIChannel (uint8_t uchChannel, unsigned nTG); + + void keyup (int16_t pitch, unsigned nTG); + void keydown (int16_t pitch, uint8_t velocity, unsigned nTG); + + void setSustain (bool sustain, unsigned nTG); + void setModWheel (uint8_t value, unsigned nTG); + void setPitchbend (int16_t value, unsigned nTG); + void ControllersRefresh (unsigned nTG); + + std::string GetVoiceName (unsigned nTG); private: void ProcessSound (void); +#ifdef ARM_ALLOW_MULTI_CORE + enum TCoreStatus + { + CoreStatusInit, + CoreStatusIdle, + CoreStatusBusy, + CoreStatusExit, + CoreStatusUnknown + }; +#endif + private: CConfig *m_pConfig; + CDexedAdapter *m_pTG[CConfig::ToneGenerators]; + unsigned m_nVoiceBankID[CConfig::ToneGenerators]; + CUserInterface m_UI; CSysExFileLoader m_SysExFileLoader; @@ -76,6 +102,12 @@ private: CSoundBaseDevice *m_pSoundDevice; unsigned m_nQueueSizeFrames; +#ifdef ARM_ALLOW_MULTI_CORE + volatile TCoreStatus m_CoreStatus[CORES]; + volatile unsigned m_nFramesToProcess; + int16_t m_OutputLevel[CConfig::ToneGenerators][CConfig::MaxChunkSize]; +#endif + CPerformanceTimer m_GetChunkTimer; bool m_bProfileEnabled; }; diff --git a/src/pckeyboard.cpp b/src/pckeyboard.cpp index 153cc31..a3f6878 100644 --- a/src/pckeyboard.cpp +++ b/src/pckeyboard.cpp @@ -18,7 +18,6 @@ // along with this program. If not, see . // #include "pckeyboard.h" -#include "minidexed.h" #include #include #include @@ -61,8 +60,8 @@ static TKeyInfo KeyTable[] = CPCKeyboard *CPCKeyboard::s_pThis = 0; -CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer) -: m_pSynthesizer (pSynthesizer), +CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig) +: CMIDIDevice (pSynthesizer, pConfig), m_pKeyboard (0) { s_pThis = this; @@ -72,8 +71,6 @@ CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer) CPCKeyboard::~CPCKeyboard (void) { - m_pSynthesizer = 0; - s_pThis = 0; } @@ -100,7 +97,6 @@ void CPCKeyboard::Process (boolean bPlugAndPlayUpdated) void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6]) { assert (s_pThis != 0); - assert (s_pThis->m_pSynthesizer != 0); // report released keys for (unsigned i = 0; i < 6; i++) @@ -112,7 +108,8 @@ void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned u8 ucKeyNumber = GetKeyNumber (ucKeyCode); if (ucKeyNumber != 0) { - s_pThis->m_pSynthesizer->keyup (ucKeyNumber); + u8 NoteOff[] = {0x80, ucKeyNumber, 0}; + s_pThis->MIDIMessageHandler (NoteOff, sizeof NoteOff); } } } @@ -127,7 +124,8 @@ void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned u8 ucKeyNumber = GetKeyNumber (ucKeyCode); if (ucKeyNumber != 0) { - s_pThis->m_pSynthesizer->keydown (ucKeyNumber, 100); + u8 NoteOn[] = {0x90, ucKeyNumber, 100}; + s_pThis->MIDIMessageHandler (NoteOn, sizeof NoteOn); } } } diff --git a/src/pckeyboard.h b/src/pckeyboard.h index 26fc991..6baf520 100644 --- a/src/pckeyboard.h +++ b/src/pckeyboard.h @@ -20,16 +20,18 @@ #ifndef _pckeyboard_h #define _pckeyboard_h +#include "mididevice.h" +#include "config.h" #include #include #include class CMiniDexed; -class CPCKeyboard +class CPCKeyboard : public CMIDIDevice { public: - CPCKeyboard (CMiniDexed *pSynthesizer); + CPCKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig); ~CPCKeyboard (void); void Process (boolean bPlugAndPlayUpdated); @@ -44,8 +46,6 @@ private: static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); private: - CMiniDexed *m_pSynthesizer; - CUSBKeyboardDevice * volatile m_pKeyboard; u8 m_LastKeys[6]; diff --git a/src/sysexfileloader.cpp b/src/sysexfileloader.cpp index a22a9a5..126703f 100644 --- a/src/sysexfileloader.cpp +++ b/src/sysexfileloader.cpp @@ -45,8 +45,7 @@ uint8_t CSysExFileLoader::s_DefaultVoice[SizeSingleVoice] = // FM-Piano }; CSysExFileLoader::CSysExFileLoader (const char *pDirName) -: m_DirName (pDirName), - m_nBankID (0) +: m_DirName (pDirName) { m_DirName += "/voice"; @@ -168,22 +167,14 @@ std::string CSysExFileLoader::GetBankName (unsigned nBankID) return "NO NAME"; } -void CSysExFileLoader::SelectVoiceBank (unsigned nBankID) +void CSysExFileLoader::GetVoice (unsigned nBankID, unsigned nVoiceID, uint8_t *pVoiceData) { - if (nBankID <= MaxVoiceBankID) - { - m_nBankID = nBankID; - } -} - -void CSysExFileLoader::GetVoice (unsigned nVoiceID, uint8_t *pVoiceData) -{ - if (nVoiceID <= VoicesPerBank) + if ( nBankID <= MaxVoiceBankID + && nVoiceID <= VoicesPerBank) { - assert (m_nBankID <= MaxVoiceBankID); - if (m_pVoiceBank[m_nBankID]) + if (m_pVoiceBank[nBankID]) { - DecodePackedVoice (m_pVoiceBank[m_nBankID]->Voice[nVoiceID], pVoiceData); + DecodePackedVoice (m_pVoiceBank[nBankID]->Voice[nVoiceID], pVoiceData); return; } @@ -191,7 +182,7 @@ void CSysExFileLoader::GetVoice (unsigned nVoiceID, uint8_t *pVoiceData) { // Use default voices_bank instead of s_DefaultVoice for bank 0, // if the bank was not successfully loaded from disk. - if (m_nBankID == 0) + if (nBankID == 0) { memcpy (pVoiceData, voices_bank[0][nVoiceID], SizeSingleVoice); diff --git a/src/sysexfileloader.h b/src/sysexfileloader.h index fb5d0e6..bf7c918 100644 --- a/src/sysexfileloader.h +++ b/src/sysexfileloader.h @@ -58,9 +58,8 @@ public: std::string GetBankName (unsigned nBankID); // 0 .. 127 - void SelectVoiceBank (unsigned nBankID); // 0 .. 127 - - void GetVoice (unsigned nVoiceID, // 0 .. 31 + void GetVoice (unsigned nBankID, // 0 .. 127 + unsigned nVoiceID, // 0 .. 31 uint8_t *pVoiceData); // returns unpacked format (156 bytes) private: @@ -72,8 +71,6 @@ private: TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1]; std::string m_BankFileName[MaxVoiceBankID+1]; - unsigned m_nBankID; - static uint8_t s_DefaultVoice[SizeSingleVoice]; }; diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 6f573e2..ee9bf4b 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -37,10 +37,15 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag m_pLCDBuffered (0), m_pRotaryEncoder (0), m_UIMode (UIModeVoiceSelect), - m_nBank (0), - m_nProgram (0), - m_nVolume (0) + m_nTG (0) { + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + m_nBank[nTG] = 0; + m_nProgram[nTG] = 0; + m_nVolume[nTG] = 0; + m_uchMIDIChannel[nTG] = CMIDIDevice::Disabled; + } } CUserInterface::~CUserInterface (void) @@ -108,62 +113,100 @@ void CUserInterface::Process (void) } } -void CUserInterface::BankSelected (unsigned nBankLSB) +void CUserInterface::BankSelected (unsigned nBankLSB, unsigned nTG) { assert (nBankLSB < 128); - m_nBank = nBankLSB; + assert (nTG < CConfig::ToneGenerators); + m_nBank[nTG] = nBankLSB; assert (m_pMiniDexed); std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB); // MIDI numbering starts with 0, user interface with 1 - printf ("Select voice bank %u: \"%s\"\n", nBankLSB+1, BankName.c_str ()); + printf ("TG%u: Select voice bank %u: \"%s\"\n", nTG+1, nBankLSB+1, BankName.c_str ()); - if (m_UIMode == UIModeBankSelect) + if ( m_UIMode == UIModeBankSelect + && m_nTG == nTG) { + CString TG; + TG.Format ("TG%u", nTG+1); + CString String; String.Format ("%u", nBankLSB+1); - DisplayWrite (String, "BANK", BankName.c_str ()); + DisplayWrite (TG, "BANK", String, BankName.c_str ()); } } -void CUserInterface::ProgramChanged (unsigned nProgram) +void CUserInterface::ProgramChanged (unsigned nProgram, unsigned nTG) { assert (nProgram < 128); - m_nProgram = nProgram; + assert (nTG < CConfig::ToneGenerators); + m_nProgram[nTG] = 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); + std::string VoiceName = m_pMiniDexed->GetVoiceName (nTG); - printf ("Loading voice %u: \"%s\"\n", nProgram, ProgramName); + printf ("TG%u: Loading voice %u: \"%s\"\n", nTG+1, nProgram, VoiceName.c_str ()); - if (m_UIMode == UIModeVoiceSelect) + if ( m_UIMode == UIModeVoiceSelect + && m_nTG == nTG) { + CString TG; + TG.Format ("TG%u", nTG+1); + CString String; String.Format ("%u", nProgram); - DisplayWrite (String, "VOICE", ProgramName); + DisplayWrite (TG, "VOICE", String, VoiceName.c_str ()); } } -void CUserInterface::VolumeChanged (unsigned nVolume) +void CUserInterface::VolumeChanged (unsigned nVolume, unsigned nTG) { assert (nVolume < 128); - m_nVolume = nVolume; + assert (nTG < CConfig::ToneGenerators); + m_nVolume[nTG] = nVolume; - if (m_UIMode == UIModeVolume) + if ( m_UIMode == UIModeVolume + && m_nTG == nTG) { + CString TG; + TG.Format ("TG%u", nTG+1); + char VolumeBar[CConfig::LCDColumns+1]; memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character VolumeBar[nVolume * CConfig::LCDColumns / 127] = '\0'; - DisplayWrite ("", "VOLUME", VolumeBar); + DisplayWrite (TG, "VOLUME", VolumeBar); + } +} + +void CUserInterface::MIDIChannelChanged (uint8_t uchChannel, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + m_uchMIDIChannel[nTG] = uchChannel; + + if ( m_UIMode == UIModeMIDI + && m_nTG == nTG) + { + CString TG; + TG.Format ("TG%u", nTG+1); + + CString String; + switch (uchChannel) + { + case CMIDIDevice::OmniMode: String = "OMNI"; break; + case CMIDIDevice::Disabled: String = "OFF"; break; + + default: + String.Format ("%u", (unsigned) uchChannel+1); + break; + } + + DisplayWrite (TG, "MIDI", "CHANNEL", (const char *) String); } } @@ -174,6 +217,12 @@ void CUserInterface::DisplayWrite (const char *pInstance, const char *pMenu, assert (pMenu); assert (pParam); + // Do not show instance, if there is only one. + if (CConfig::ToneGenerators == 1) + { + pInstance = ""; + } + CString Msg ("\x1B[H"); // cursor home // first line @@ -238,6 +287,13 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) } break; + case CKY040::EventSwitchDoubleClick: + if (++m_nTG == CConfig::ToneGenerators) + { + m_nTG = 0; + } + break; + case CKY040::EventSwitchHold: if (m_pRotaryEncoder->GetHoldSeconds () >= 3) { @@ -245,7 +301,12 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) reboot (); } - return; + else + { + m_UIMode = UIModeStart; + m_nTG = 0; + } + break; default: return; @@ -254,23 +315,23 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) switch (m_UIMode) { case UIModeBankSelect: - if (m_nBank + nStep < 128) + if (m_nBank[m_nTG] + nStep < 128) { - m_pMiniDexed->BankSelectLSB (m_nBank + nStep); + m_pMiniDexed->BankSelectLSB (m_nBank[m_nTG] + nStep, m_nTG); } break; case UIModeVoiceSelect: - if (m_nProgram + nStep < 32) + if (m_nProgram[m_nTG] + nStep < 32) { - m_pMiniDexed->ProgramChange (m_nProgram + nStep); + m_pMiniDexed->ProgramChange (m_nProgram[m_nTG] + nStep, m_nTG); } break; case UIModeVolume: { const int Increment = 128 / CConfig::LCDColumns; - int nVolume = m_nVolume + nStep*Increment; + int nVolume = m_nVolume[m_nTG] + nStep*Increment; if (nVolume < 0) { nVolume = 0; @@ -280,9 +341,16 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) nVolume = 127; } - m_pMiniDexed->SetVolume (nVolume); + m_pMiniDexed->SetVolume (nVolume, m_nTG); } break; + case UIModeMIDI: + if ((uint8_t) (m_uchMIDIChannel[m_nTG] + nStep) < CMIDIDevice::ChannelUnknown) + { + m_pMiniDexed->SetMIDIChannel (m_uchMIDIChannel[m_nTG] + nStep, m_nTG); + } + break; + default: break; } diff --git a/src/userinterface.h b/src/userinterface.h index 6e34d99..05d7d61 100644 --- a/src/userinterface.h +++ b/src/userinterface.h @@ -25,6 +25,7 @@ #include #include #include +#include class CMiniDexed; @@ -38,9 +39,10 @@ public: void Process (void); - void BankSelected (unsigned nBankLSB); // 0 .. 127 - void ProgramChanged (unsigned nProgram); // 0 .. 127 - void VolumeChanged (unsigned nVolume); // 0 .. 127 + void BankSelected (unsigned nBankLSB, unsigned nTG); // 0 .. 127 + void ProgramChanged (unsigned nProgram, unsigned nTG); // 0 .. 127 + void VolumeChanged (unsigned nVolume, unsigned nTG); // 0 .. 127 + void MIDIChannelChanged (uint8_t uchChannel, unsigned nTG); private: // Print to display in this format: @@ -63,6 +65,7 @@ private: UIModeVoiceSelect = UIModeStart, UIModeBankSelect, UIModeVolume, + UIModeMIDI, UIModeUnknown }; @@ -78,9 +81,11 @@ private: TUIMode m_UIMode; - unsigned m_nBank; - unsigned m_nProgram; - unsigned m_nVolume; + unsigned m_nTG; + unsigned m_nBank[CConfig::ToneGenerators]; + unsigned m_nProgram[CConfig::ToneGenerators]; + unsigned m_nVolume[CConfig::ToneGenerators]; + uint8_t m_uchMIDIChannel[CConfig::ToneGenerators]; }; #endif