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
pull/51/head
Rene Stange 3 years ago committed by GitHub
parent ae302e42ab
commit d6025f1943
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/config.cpp
  2. 17
      src/config.h
  3. 7
      src/dexedadapter.h
  4. 58
      src/mididevice.cpp
  5. 14
      src/mididevice.h
  6. 266
      src/minidexed.cpp
  7. 42
      src/minidexed.h
  8. 14
      src/pckeyboard.cpp
  9. 8
      src/pckeyboard.h
  10. 23
      src/sysexfileloader.cpp
  11. 7
      src/sysexfileloader.h
  12. 124
      src/userinterface.cpp
  13. 17
      src/userinterface.h

@ -38,7 +38,11 @@ void CConfig::Load (void)
m_SoundDevice = m_Properties.GetString ("SoundDevice", "pwm"); m_SoundDevice = m_Properties.GetString ("SoundDevice", "pwm");
m_nSampleRate = m_Properties.GetNumber ("SampleRate", 48000); m_nSampleRate = m_Properties.GetNumber ("SampleRate", 48000);
#ifdef ARM_ALLOW_MULTI_CORE
m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 256); 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_nDACI2CAddress = m_Properties.GetNumber ("DACI2CAddress", 0);
m_nMIDIBaudRate = m_Properties.GetNumber ("MIDIBaudRate", 31250); m_nMIDIBaudRate = m_Properties.GetNumber ("MIDIBaudRate", 31250);

@ -25,12 +25,27 @@
#include <fatfs/ff.h> #include <fatfs/ff.h>
#include <Properties/propertiesfatfsfile.h> #include <Properties/propertiesfatfsfile.h>
#include <circle/sysconfig.h>
#include <string> #include <string>
class CConfig // Configuration for MiniDexed class CConfig // Configuration for MiniDexed
{ {
public: 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 #if RASPPI <= 3
static const unsigned MaxUSBMIDIDevices = 2; static const unsigned MaxUSBMIDIDevices = 2;

@ -70,6 +70,13 @@ public:
m_SpinLock.Release (); m_SpinLock.Release ();
} }
void setSustain (bool sustain)
{
m_SpinLock.Acquire ();
Dexed::setSustain (sustain);
m_SpinLock.Release ();
}
private: private:
CSpinLock m_SpinLock; CSpinLock m_SpinLock;
}; };

@ -45,6 +45,10 @@ CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig)
: m_pSynthesizer (pSynthesizer), : m_pSynthesizer (pSynthesizer),
m_pConfig (pConfig) m_pConfig (pConfig)
{ {
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
m_ChannelMap[nTG] = Disabled;
}
} }
CMIDIDevice::~CMIDIDevice (void) CMIDIDevice::~CMIDIDevice (void)
@ -52,6 +56,18 @@ CMIDIDevice::~CMIDIDevice (void)
m_pSynthesizer = 0; 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) void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable)
{ {
assert (m_pSynthesizer != 0); assert (m_pSynthesizer != 0);
@ -67,17 +83,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
if ( pMessage[0] != MIDI_TIMING_CLOCK if ( pMessage[0] != MIDI_TIMING_CLOCK
&& pMessage[0] != MIDI_ACTIVE_SENSING) && pMessage[0] != MIDI_ACTIVE_SENSING)
{ {
printf ("MIDI %u: %02X\n", nCable, (unsigned) pMessage[0]); printf ("MIDI%u: %02X\n", nCable, (unsigned) pMessage[0]);
} }
break; break;
case 2: case 2:
printf ("MIDI %u: %02X %02X\n", nCable, printf ("MIDI%u: %02X %02X\n", nCable,
(unsigned) pMessage[0], (unsigned) pMessage[1]); (unsigned) pMessage[0], (unsigned) pMessage[1]);
break; break;
case 3: 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[0], (unsigned) pMessage[1],
(unsigned) pMessage[2]); (unsigned) pMessage[2]);
break; break;
@ -90,11 +106,14 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
} }
u8 ucStatus = pMessage[0]; u8 ucStatus = pMessage[0];
// TODO: u8 ucChannel = ucStatus & 0x0F; u8 ucChannel = ucStatus & 0x0F;
u8 ucType = ucStatus >> 4; u8 ucType = ucStatus >> 4;
u8 ucKeyNumber = pMessage[1];
u8 ucVelocity = pMessage[2];
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
if ( m_ChannelMap[nTG] == ucChannel
|| m_ChannelMap[nTG] == OmniMode)
{
switch (ucType) switch (ucType)
{ {
case MIDI_NOTE_ON: case MIDI_NOTE_ON:
@ -103,16 +122,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
break; break;
} }
if (ucVelocity > 0) if (pMessage[2] > 0)
{ {
if (ucVelocity <= 127) if (pMessage[2] <= 127)
{ {
m_pSynthesizer->keydown (ucKeyNumber, ucVelocity); m_pSynthesizer->keydown (pMessage[1],
pMessage[2], nTG);
} }
} }
else else
{ {
m_pSynthesizer->keyup (ucKeyNumber); m_pSynthesizer->keyup (pMessage[1], nTG);
} }
break; break;
@ -122,7 +142,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
break; break;
} }
m_pSynthesizer->keyup (ucKeyNumber); m_pSynthesizer->keyup (pMessage[1], nTG);
break; break;
case MIDI_CONTROL_CHANGE: case MIDI_CONTROL_CHANGE:
@ -134,26 +154,26 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
switch (pMessage[1]) switch (pMessage[1])
{ {
case MIDI_CC_MODULATION: case MIDI_CC_MODULATION:
m_pSynthesizer->setModWheel (pMessage[2]); m_pSynthesizer->setModWheel (pMessage[2], nTG);
m_pSynthesizer->ControllersRefresh (); m_pSynthesizer->ControllersRefresh (nTG);
break; break;
case MIDI_CC_VOLUME: case MIDI_CC_VOLUME:
m_pSynthesizer->SetVolume (pMessage[2]); m_pSynthesizer->SetVolume (pMessage[2], nTG);
break; break;
case MIDI_CC_BANK_SELECT_LSB: case MIDI_CC_BANK_SELECT_LSB:
m_pSynthesizer->BankSelectLSB (pMessage[2]); m_pSynthesizer->BankSelectLSB (pMessage[2], nTG);
break; break;
case MIDI_CC_BANK_SUSTAIN: case MIDI_CC_BANK_SUSTAIN:
m_pSynthesizer->setSustain (pMessage[2] >= 64); m_pSynthesizer->setSustain (pMessage[2] >= 64, nTG);
break; break;
} }
break; break;
case MIDI_PROGRAM_CHANGE: case MIDI_PROGRAM_CHANGE:
m_pSynthesizer->ProgramChange (pMessage[1]); m_pSynthesizer->ProgramChange (pMessage[1], nTG);
break; break;
case MIDI_PITCH_BEND: { case MIDI_PITCH_BEND: {
@ -166,10 +186,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
nValue |= (s16) pMessage[2] << 7; nValue |= (s16) pMessage[2] << 7;
nValue -= 0x2000; nValue -= 0x2000;
m_pSynthesizer->setPitchbend (nValue); m_pSynthesizer->setPitchbend (nValue, nTG);
} break; } break;
default: default:
break; break;
} }
}
}
} }

@ -30,16 +30,30 @@ class CMiniDexed;
class CMIDIDevice class CMIDIDevice
{ {
public:
enum TChannel
{
Channels = 16,
OmniMode = Channels,
Disabled,
ChannelUnknown
};
public: public:
CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig); CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig);
~CMIDIDevice (void); ~CMIDIDevice (void);
void SetChannel (u8 ucChannel, unsigned nTG);
u8 GetChannel (unsigned nTG) const;
protected: protected:
void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0);
private: private:
CMiniDexed *m_pSynthesizer; CMiniDexed *m_pSynthesizer;
CConfig *m_pConfig; CConfig *m_pConfig;
u8 m_ChannelMap[CConfig::ToneGenerators];
}; };
#endif #endif

@ -31,13 +31,13 @@ LOGMODULE ("minidexed");
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster) CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster)
: CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()), :
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
CMultiCoreSupport (CMemorySystem::Get ()), CMultiCoreSupport (CMemorySystem::Get ()),
#endif #endif
m_pConfig (pConfig), m_pConfig (pConfig),
m_UI (this, pGPIOManager, pConfig), m_UI (this, pGPIOManager, pConfig),
m_PCKeyboard (this), m_PCKeyboard (this, pConfig),
m_SerialMIDI (this, pInterrupt, pConfig), m_SerialMIDI (this, pInterrupt, pConfig),
m_bUseSerial (false), m_bUseSerial (false),
m_pSoundDevice (0), m_pSoundDevice (0),
@ -45,6 +45,18 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()),
m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) 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++) for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
{ {
m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, 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 (), m_pSoundDevice = new CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ()); pConfig->GetChunkSize ());
} }
#ifdef ARM_ALLOW_MULTI_CORE
for (unsigned nCore = 0; nCore < CORES; nCore++)
{
m_CoreStatus[nCore] = CoreStatusInit;
}
#endif
}; };
bool CMiniDexed::Initialize (void) bool CMiniDexed::Initialize (void)
@ -96,14 +115,20 @@ bool CMiniDexed::Initialize (void)
m_bUseSerial = true; m_bUseSerial = true;
} }
activate (); for (unsigned i = 0; i < CConfig::ToneGenerators; i++)
{
assert (m_pTG[i]);
SetVolume (100, i);
ProgramChange (0, i);
SetVolume (100); m_pTG[i]->setTranspose (24);
ProgramChange (0);
setTranspose (24); m_pTG[i]->setPBController (12, 1);
m_pTG[i]->setMWController (99, 7, 0);
}
setPBController (12, 1); SetMIDIChannel (CMIDIDevice::OmniMode, 0);
setMWController (99, 7, 0);
// setup and start the sound device // setup and start the sound device
if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ()))
@ -161,13 +186,58 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated)
void CMiniDexed::Run (unsigned nCore) void CMiniDexed::Run (unsigned nCore)
{ {
assert (1 <= nCore && nCore < CORES);
if (nCore == 1) 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 (); 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 #endif
@ -177,44 +247,158 @@ CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void)
return &m_SysExFileLoader; return &m_SysExFileLoader;
} }
void CMiniDexed::BankSelectLSB (unsigned nBankLSB) void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG)
{ {
if (nBankLSB > 127) if (nBankLSB > 127)
{ {
return; 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) if (nProgram > 31)
{ {
return; return;
} }
assert (nTG < CConfig::ToneGenerators);
uint8_t Buffer[156]; uint8_t Buffer[156];
m_SysExFileLoader.GetVoice (nProgram, Buffer); m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer);
loadVoiceParameters (Buffer);
assert (m_pTG[nTG]);
m_pTG[nTG]->loadVoiceParameters (Buffer);
m_UI.ProgramChanged (nProgram); m_UI.ProgramChanged (nProgram, nTG);
} }
void CMiniDexed::SetVolume (unsigned nVolume) void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG)
{ {
if (nVolume > 127) if (nVolume > 127)
{ {
return; 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);
if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer)
!= (int) sizeof SampleBuffer)
{
LOGERR ("Sound data dropped");
}
m_UI.VolumeChanged (nVolume); if (m_bProfileEnabled)
{
m_GetChunkTimer.Stop ();
}
}
} }
#else // #ifdef ARM_ALLOW_MULTI_CORE
void CMiniDexed::ProcessSound (void) void CMiniDexed::ProcessSound (void)
{ {
assert (m_pSoundDevice); assert (m_pSoundDevice);
@ -227,8 +411,48 @@ void CMiniDexed::ProcessSound (void)
m_GetChunkTimer.Start (); 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]; 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) if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer)
!= (int) sizeof SampleBuffer) != (int) sizeof SampleBuffer)
@ -242,3 +466,5 @@ void CMiniDexed::ProcessSound (void)
} }
} }
} }
#endif

@ -29,6 +29,7 @@
#include "serialmididevice.h" #include "serialmididevice.h"
#include "perftimer.h" #include "perftimer.h"
#include <stdint.h> #include <stdint.h>
#include <string>
#include <circle/types.h> #include <circle/types.h>
#include <circle/interrupt.h> #include <circle/interrupt.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
@ -36,9 +37,9 @@
#include <circle/multicore.h> #include <circle/multicore.h>
#include <circle/soundbasedevice.h> #include <circle/soundbasedevice.h>
class CMiniDexed : public CDexedAdapter class CMiniDexed
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
, public CMultiCoreSupport : public CMultiCoreSupport
#endif #endif
{ {
public: public:
@ -55,16 +56,41 @@ public:
CSysExFileLoader *GetSysExFileLoader (void); CSysExFileLoader *GetSysExFileLoader (void);
void BankSelectLSB (unsigned nBankLSB); void BankSelectLSB (unsigned nBankLSB, unsigned nTG);
void ProgramChange (unsigned nProgram); void ProgramChange (unsigned nProgram, unsigned nTG);
void SetVolume (unsigned nVolume); 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: private:
void ProcessSound (void); void ProcessSound (void);
#ifdef ARM_ALLOW_MULTI_CORE
enum TCoreStatus
{
CoreStatusInit,
CoreStatusIdle,
CoreStatusBusy,
CoreStatusExit,
CoreStatusUnknown
};
#endif
private: private:
CConfig *m_pConfig; CConfig *m_pConfig;
CDexedAdapter *m_pTG[CConfig::ToneGenerators];
unsigned m_nVoiceBankID[CConfig::ToneGenerators];
CUserInterface m_UI; CUserInterface m_UI;
CSysExFileLoader m_SysExFileLoader; CSysExFileLoader m_SysExFileLoader;
@ -76,6 +102,12 @@ private:
CSoundBaseDevice *m_pSoundDevice; CSoundBaseDevice *m_pSoundDevice;
unsigned m_nQueueSizeFrames; 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; CPerformanceTimer m_GetChunkTimer;
bool m_bProfileEnabled; bool m_bProfileEnabled;
}; };

@ -18,7 +18,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#include "pckeyboard.h" #include "pckeyboard.h"
#include "minidexed.h"
#include <circle/devicenameservice.h> #include <circle/devicenameservice.h>
#include <circle/util.h> #include <circle/util.h>
#include <assert.h> #include <assert.h>
@ -61,8 +60,8 @@ static TKeyInfo KeyTable[] =
CPCKeyboard *CPCKeyboard::s_pThis = 0; CPCKeyboard *CPCKeyboard::s_pThis = 0;
CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer) CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig)
: m_pSynthesizer (pSynthesizer), : CMIDIDevice (pSynthesizer, pConfig),
m_pKeyboard (0) m_pKeyboard (0)
{ {
s_pThis = this; s_pThis = this;
@ -72,8 +71,6 @@ CPCKeyboard::CPCKeyboard (CMiniDexed *pSynthesizer)
CPCKeyboard::~CPCKeyboard (void) CPCKeyboard::~CPCKeyboard (void)
{ {
m_pSynthesizer = 0;
s_pThis = 0; s_pThis = 0;
} }
@ -100,7 +97,6 @@ void CPCKeyboard::Process (boolean bPlugAndPlayUpdated)
void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6]) void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6])
{ {
assert (s_pThis != 0); assert (s_pThis != 0);
assert (s_pThis->m_pSynthesizer != 0);
// report released keys // report released keys
for (unsigned i = 0; i < 6; i++) for (unsigned i = 0; i < 6; i++)
@ -112,7 +108,8 @@ void CPCKeyboard::KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned
u8 ucKeyNumber = GetKeyNumber (ucKeyCode); u8 ucKeyNumber = GetKeyNumber (ucKeyCode);
if (ucKeyNumber != 0) 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); u8 ucKeyNumber = GetKeyNumber (ucKeyCode);
if (ucKeyNumber != 0) if (ucKeyNumber != 0)
{ {
s_pThis->m_pSynthesizer->keydown (ucKeyNumber, 100); u8 NoteOn[] = {0x90, ucKeyNumber, 100};
s_pThis->MIDIMessageHandler (NoteOn, sizeof NoteOn);
} }
} }
} }

@ -20,16 +20,18 @@
#ifndef _pckeyboard_h #ifndef _pckeyboard_h
#define _pckeyboard_h #define _pckeyboard_h
#include "mididevice.h"
#include "config.h"
#include <circle/usb/usbkeyboard.h> #include <circle/usb/usbkeyboard.h>
#include <circle/device.h> #include <circle/device.h>
#include <circle/types.h> #include <circle/types.h>
class CMiniDexed; class CMiniDexed;
class CPCKeyboard class CPCKeyboard : public CMIDIDevice
{ {
public: public:
CPCKeyboard (CMiniDexed *pSynthesizer); CPCKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig);
~CPCKeyboard (void); ~CPCKeyboard (void);
void Process (boolean bPlugAndPlayUpdated); void Process (boolean bPlugAndPlayUpdated);
@ -44,8 +46,6 @@ private:
static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); static void DeviceRemovedHandler (CDevice *pDevice, void *pContext);
private: private:
CMiniDexed *m_pSynthesizer;
CUSBKeyboardDevice * volatile m_pKeyboard; CUSBKeyboardDevice * volatile m_pKeyboard;
u8 m_LastKeys[6]; u8 m_LastKeys[6];

@ -45,8 +45,7 @@ uint8_t CSysExFileLoader::s_DefaultVoice[SizeSingleVoice] = // FM-Piano
}; };
CSysExFileLoader::CSysExFileLoader (const char *pDirName) CSysExFileLoader::CSysExFileLoader (const char *pDirName)
: m_DirName (pDirName), : m_DirName (pDirName)
m_nBankID (0)
{ {
m_DirName += "/voice"; m_DirName += "/voice";
@ -168,22 +167,14 @@ std::string CSysExFileLoader::GetBankName (unsigned nBankID)
return "NO NAME"; return "NO NAME";
} }
void CSysExFileLoader::SelectVoiceBank (unsigned nBankID) void CSysExFileLoader::GetVoice (unsigned nBankID, unsigned nVoiceID, uint8_t *pVoiceData)
{ {
if (nBankID <= MaxVoiceBankID) if ( nBankID <= MaxVoiceBankID
{ && nVoiceID <= VoicesPerBank)
m_nBankID = nBankID;
}
}
void CSysExFileLoader::GetVoice (unsigned nVoiceID, uint8_t *pVoiceData)
{
if (nVoiceID <= VoicesPerBank)
{ {
assert (m_nBankID <= MaxVoiceBankID); if (m_pVoiceBank[nBankID])
if (m_pVoiceBank[m_nBankID])
{ {
DecodePackedVoice (m_pVoiceBank[m_nBankID]->Voice[nVoiceID], pVoiceData); DecodePackedVoice (m_pVoiceBank[nBankID]->Voice[nVoiceID], pVoiceData);
return; return;
} }
@ -191,7 +182,7 @@ void CSysExFileLoader::GetVoice (unsigned nVoiceID, uint8_t *pVoiceData)
{ {
// Use default voices_bank instead of s_DefaultVoice for bank 0, // Use default voices_bank instead of s_DefaultVoice for bank 0,
// if the bank was not successfully loaded from disk. // if the bank was not successfully loaded from disk.
if (m_nBankID == 0) if (nBankID == 0)
{ {
memcpy (pVoiceData, voices_bank[0][nVoiceID], SizeSingleVoice); memcpy (pVoiceData, voices_bank[0][nVoiceID], SizeSingleVoice);

@ -58,9 +58,8 @@ public:
std::string GetBankName (unsigned nBankID); // 0 .. 127 std::string GetBankName (unsigned nBankID); // 0 .. 127
void SelectVoiceBank (unsigned nBankID); // 0 .. 127 void GetVoice (unsigned nBankID, // 0 .. 127
unsigned nVoiceID, // 0 .. 31
void GetVoice (unsigned nVoiceID, // 0 .. 31
uint8_t *pVoiceData); // returns unpacked format (156 bytes) uint8_t *pVoiceData); // returns unpacked format (156 bytes)
private: private:
@ -72,8 +71,6 @@ private:
TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1]; TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1];
std::string m_BankFileName[MaxVoiceBankID+1]; std::string m_BankFileName[MaxVoiceBankID+1];
unsigned m_nBankID;
static uint8_t s_DefaultVoice[SizeSingleVoice]; static uint8_t s_DefaultVoice[SizeSingleVoice];
}; };

@ -37,10 +37,15 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag
m_pLCDBuffered (0), m_pLCDBuffered (0),
m_pRotaryEncoder (0), m_pRotaryEncoder (0),
m_UIMode (UIModeVoiceSelect), m_UIMode (UIModeVoiceSelect),
m_nBank (0), m_nTG (0)
m_nProgram (0),
m_nVolume (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) 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); assert (nBankLSB < 128);
m_nBank = nBankLSB; assert (nTG < CConfig::ToneGenerators);
m_nBank[nTG] = nBankLSB;
assert (m_pMiniDexed); assert (m_pMiniDexed);
std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB); std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB);
// MIDI numbering starts with 0, user interface with 1 // 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; CString String;
String.Format ("%u", nBankLSB+1); 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); assert (nProgram < 128);
m_nProgram = nProgram; assert (nTG < CConfig::ToneGenerators);
m_nProgram[nTG] = nProgram;
nProgram++; // MIDI numbering starts with 0, user interface with 1 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); 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; CString String;
String.Format ("%u", nProgram); 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); 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]; char VolumeBar[CConfig::LCDColumns+1];
memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character
VolumeBar[nVolume * CConfig::LCDColumns / 127] = '\0'; 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 (pMenu);
assert (pParam); assert (pParam);
// Do not show instance, if there is only one.
if (CConfig::ToneGenerators == 1)
{
pInstance = "";
}
CString Msg ("\x1B[H"); // cursor home CString Msg ("\x1B[H"); // cursor home
// first line // first line
@ -238,6 +287,13 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
} }
break; break;
case CKY040::EventSwitchDoubleClick:
if (++m_nTG == CConfig::ToneGenerators)
{
m_nTG = 0;
}
break;
case CKY040::EventSwitchHold: case CKY040::EventSwitchHold:
if (m_pRotaryEncoder->GetHoldSeconds () >= 3) if (m_pRotaryEncoder->GetHoldSeconds () >= 3)
{ {
@ -245,7 +301,12 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
reboot (); reboot ();
} }
return; else
{
m_UIMode = UIModeStart;
m_nTG = 0;
}
break;
default: default:
return; return;
@ -254,23 +315,23 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
switch (m_UIMode) switch (m_UIMode)
{ {
case UIModeBankSelect: 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; break;
case UIModeVoiceSelect: 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; break;
case UIModeVolume: { case UIModeVolume: {
const int Increment = 128 / CConfig::LCDColumns; const int Increment = 128 / CConfig::LCDColumns;
int nVolume = m_nVolume + nStep*Increment; int nVolume = m_nVolume[m_nTG] + nStep*Increment;
if (nVolume < 0) if (nVolume < 0)
{ {
nVolume = 0; nVolume = 0;
@ -280,9 +341,16 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
nVolume = 127; nVolume = 127;
} }
m_pMiniDexed->SetVolume (nVolume); m_pMiniDexed->SetVolume (nVolume, m_nTG);
} break; } 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: default:
break; break;
} }

@ -25,6 +25,7 @@
#include <display/hd44780device.h> #include <display/hd44780device.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
#include <circle/writebuffer.h> #include <circle/writebuffer.h>
#include <stdint.h>
class CMiniDexed; class CMiniDexed;
@ -38,9 +39,10 @@ public:
void Process (void); void Process (void);
void BankSelected (unsigned nBankLSB); // 0 .. 127 void BankSelected (unsigned nBankLSB, unsigned nTG); // 0 .. 127
void ProgramChanged (unsigned nProgram); // 0 .. 127 void ProgramChanged (unsigned nProgram, unsigned nTG); // 0 .. 127
void VolumeChanged (unsigned nVolume); // 0 .. 127 void VolumeChanged (unsigned nVolume, unsigned nTG); // 0 .. 127
void MIDIChannelChanged (uint8_t uchChannel, unsigned nTG);
private: private:
// Print to display in this format: // Print to display in this format:
@ -63,6 +65,7 @@ private:
UIModeVoiceSelect = UIModeStart, UIModeVoiceSelect = UIModeStart,
UIModeBankSelect, UIModeBankSelect,
UIModeVolume, UIModeVolume,
UIModeMIDI,
UIModeUnknown UIModeUnknown
}; };
@ -78,9 +81,11 @@ private:
TUIMode m_UIMode; TUIMode m_UIMode;
unsigned m_nBank; unsigned m_nTG;
unsigned m_nProgram; unsigned m_nBank[CConfig::ToneGenerators];
unsigned m_nVolume; unsigned m_nProgram[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators];
uint8_t m_uchMIDIChannel[CConfig::ToneGenerators];
}; };
#endif #endif

Loading…
Cancel
Save