From ce6810a8b7799c70ac38a26e62c4c77071f88eb9 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 27 Apr 2025 16:29:32 +0200 Subject: [PATCH 01/10] Initial Unison support Note that this doesn't increase the total number of TGs, the other ones will just stay silent. Menu integration still to be improved. --- src/minidexed.cpp | 199 ++++++++++++++++++++++++++------------ src/minidexed.h | 8 +- src/performanceconfig.cpp | 49 +++++++++- src/performanceconfig.h | 9 ++ src/uimenu.cpp | 36 ++++--- 5 files changed, 218 insertions(+), 83 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1e925ed..97f5b5c 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -31,6 +31,9 @@ #include #include "arm_float_to_q23.h" +// Forward declaration for getPhysicalTG +static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); + const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; #define FTPUSERNAME "admin" @@ -126,6 +129,9 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_pTG[i]->setEngineType(pConfig->GetEngineType ()); m_pTG[i]->activate (); } + m_nUnisonVoices[i] = 1; + m_nUnisonDetune[i] = 0; + m_nUnisonSpread[i] = 0; } unsigned nUSBGadgetPin = pConfig->GetUSBGadgetPin(); @@ -848,9 +854,34 @@ void CMiniDexed::keyup (int16_t pitch, unsigned nTG) assert (m_pTG[nTG]); pitch = ApplyNoteLimits (pitch, nTG); - if (pitch >= 0) - { - m_pTG[nTG]->keyup (pitch); + if (pitch < 0) return; + + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + + int baseDetune = m_nMasterTune[nTG]; + unsigned basePan = m_nPan[nTG]; + unsigned unisonDetune = m_nUnisonDetune[nTG]; + unsigned unisonSpread = m_nUnisonSpread[nTG]; + + for (unsigned v = 0; v < unisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + if (physicalTG >= m_nToneGenerators) break; + // Ensure virtual TG plays the same voice as logical TG + if (physicalTG != nTG) { + uint8_t voiceData[156]; + m_pTG[nTG]->getVoiceData(voiceData); + m_pTG[physicalTG]->loadVoiceParameters(voiceData); + } + float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; + float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; + int detune = baseDetune + (int)detuneOffset; + unsigned pan = basePan + (int)panOffset; + detune = constrain(detune, -99, 99); + pan = constrain((int)pan, 0, 127); + m_pTG[physicalTG]->setMasterTune((int8_t)detune); + tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); + m_pTG[physicalTG]->keyup(pitch); } } @@ -862,9 +893,30 @@ void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) assert (m_pTG[nTG]); pitch = ApplyNoteLimits (pitch, nTG); - if (pitch >= 0) - { - m_pTG[nTG]->keydown (pitch, velocity); + if (pitch < 0) return; + + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + unsigned maxLogicalTGs = m_nToneGenerators / unisonVoices; + if (nTG >= maxLogicalTGs) return; // Don't exceed available physical TGs + + int baseDetune = m_nMasterTune[nTG]; + unsigned basePan = m_nPan[nTG]; + unsigned unisonDetune = m_nUnisonDetune[nTG]; + unsigned unisonSpread = m_nUnisonSpread[nTG]; + + for (unsigned v = 0; v < unisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + if (physicalTG >= m_nToneGenerators) break; + float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; + float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; + int detune = baseDetune + (int)detuneOffset; + unsigned pan = basePan + (int)panOffset; + detune = constrain(detune, -99, 99); + pan = constrain((int)pan, 0, 127); + m_pTG[physicalTG]->setMasterTune((int8_t)detune); + tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); + m_pTG[physicalTG]->keydown(pitch, velocity); } } @@ -1129,11 +1181,21 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT break; case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; - - default: - assert (0); - break; + + case TGParameterUnisonVoices: + m_nUnisonVoices[nTG] = constrain(nValue, 1, 4); + break; + case TGParameterUnisonDetune: + m_nUnisonDetune[nTG] = constrain(nValue, 0, 99); + break; + case TGParameterUnisonSpread: + m_nUnisonSpread[nTG] = constrain(nValue, 0, 99); + break; + case TGParameterUnknown: + // No action needed for unknown parameter + break; } + } int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) @@ -1180,7 +1242,12 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) case TGParameterATAmplitude: return getModController(3, 2, nTG); case TGParameterATEGBias: return getModController(3, 3, nTG); - + case TGParameterUnisonVoices: + return m_nUnisonVoices[nTG]; + case TGParameterUnisonDetune: + return m_nUnisonDetune[nTG]; + case TGParameterUnisonSpread: + return m_nUnisonSpread[nTG]; default: assert (0); return 0; @@ -1802,11 +1869,11 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) memcpy(voice, data, sizeof(uint8_t)*161); // fix voice name - for (uint8_t i = 0; i < 10; i++) + for (uint8_t i = 0; i <10; i++) { if (voice[151 + i] > 126) // filter characters - voice[151 + i] = 32; - } + voice[151 +i] = 32; + } m_pTG[nTG]->loadVoiceParameters(&voice[6]); m_pTG[nTG]->doRefreshVoice(); @@ -2024,58 +2091,60 @@ bool CMiniDexed::DoSavePerformanceNewFile (void) void CMiniDexed::LoadPerformanceParameters(void) { for (unsigned nTG = 0; nTG < CConfig::AllToneGenerators; nTG++) - { - - BankSelect (m_PerformanceConfig.GetBankNumber (nTG), nTG); - ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); - SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); - SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG); - SetPan (m_PerformanceConfig.GetPan (nTG), nTG); - SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG); - SetCutoff (m_PerformanceConfig.GetCutoff (nTG), nTG); - SetResonance (m_PerformanceConfig.GetResonance (nTG), nTG); - setPitchbendRange (m_PerformanceConfig.GetPitchBendRange (nTG), nTG); - setPitchbendStep (m_PerformanceConfig.GetPitchBendStep (nTG), nTG); - setPortamentoMode (m_PerformanceConfig.GetPortamentoMode (nTG), nTG); - setPortamentoGlissando (m_PerformanceConfig.GetPortamentoGlissando (nTG), nTG); - setPortamentoTime (m_PerformanceConfig.GetPortamentoTime (nTG), nTG); - - m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); - m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); - m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); - - if(m_PerformanceConfig.VoiceDataFilled(nTG)) - { - uint8_t* tVoiceData = m_PerformanceConfig.GetVoiceDataFromTxt(nTG); - m_pTG[nTG]->loadVoiceParameters(tVoiceData); - setOPMask(0b111111, nTG); - } - setMonoMode(m_PerformanceConfig.GetMonoMode(nTG) ? 1 : 0, nTG); - SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG); - - setModWheelRange (m_PerformanceConfig.GetModulationWheelRange (nTG), nTG); - setModWheelTarget (m_PerformanceConfig.GetModulationWheelTarget (nTG), nTG); - setFootControllerRange (m_PerformanceConfig.GetFootControlRange (nTG), nTG); - setFootControllerTarget (m_PerformanceConfig.GetFootControlTarget (nTG), nTG); - setBreathControllerRange (m_PerformanceConfig.GetBreathControlRange (nTG), nTG); - setBreathControllerTarget (m_PerformanceConfig.GetBreathControlTarget (nTG), nTG); - setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG); - setAftertouchTarget (m_PerformanceConfig.GetAftertouchTarget (nTG), nTG); - + { + BankSelect (m_PerformanceConfig.GetBankNumber (nTG), nTG); + ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); + SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); + SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG); + SetPan (m_PerformanceConfig.GetPan (nTG), nTG); + SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG); + SetCutoff (m_PerformanceConfig.GetCutoff (nTG), nTG); + SetResonance (m_PerformanceConfig.GetResonance (nTG), nTG); + setPitchbendRange (m_PerformanceConfig.GetPitchBendRange (nTG), nTG); + setPitchbendStep (m_PerformanceConfig.GetPitchBendStep (nTG), nTG); + setPortamentoMode (m_PerformanceConfig.GetPortamentoMode (nTG), nTG); + setPortamentoGlissando (m_PerformanceConfig.GetPortamentoGlissando (nTG), nTG); + setPortamentoTime (m_PerformanceConfig.GetPortamentoTime (nTG), nTG); + + m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); + m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); + m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); + if(m_PerformanceConfig.VoiceDataFilled(nTG)) + { + uint8_t* tVoiceData = m_PerformanceConfig.GetVoiceDataFromTxt(nTG); + m_pTG[nTG]->loadVoiceParameters(tVoiceData); + setOPMask(0b111111, nTG); } + setMonoMode(m_PerformanceConfig.GetMonoMode(nTG) ? 1 : 0, nTG); + SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG); + + setModWheelRange (m_PerformanceConfig.GetModulationWheelRange (nTG), nTG); + setModWheelTarget (m_PerformanceConfig.GetModulationWheelTarget (nTG), nTG); + setFootControllerRange (m_PerformanceConfig.GetFootControlRange (nTG), nTG); + setFootControllerTarget (m_PerformanceConfig.GetFootControlTarget (nTG), nTG); + setBreathControllerRange (m_PerformanceConfig.GetBreathControlRange (nTG), nTG); + setBreathControllerTarget (m_PerformanceConfig.GetBreathControlTarget (nTG), nTG); + setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG); + setAftertouchTarget (m_PerformanceConfig.GetAftertouchTarget (nTG), nTG); + + // Unison parameters + m_nUnisonVoices[nTG] = m_PerformanceConfig.GetUnisonVoices(nTG); + m_nUnisonDetune[nTG] = m_PerformanceConfig.GetUnisonDetune(nTG); + m_nUnisonSpread[nTG] = m_PerformanceConfig.GetUnisonSpread(nTG); + } - // Effects - SetParameter (ParameterCompressorEnable, m_PerformanceConfig.GetCompressorEnable () ? 1 : 0); - SetParameter (ParameterReverbEnable, m_PerformanceConfig.GetReverbEnable () ? 1 : 0); - SetParameter (ParameterReverbSize, m_PerformanceConfig.GetReverbSize ()); - SetParameter (ParameterReverbHighDamp, m_PerformanceConfig.GetReverbHighDamp ()); - SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); - SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); - SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); - SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); + // Effects + SetParameter (ParameterCompressorEnable, m_PerformanceConfig.GetCompressorEnable () ? 1 : 0); + SetParameter (ParameterReverbEnable, m_PerformanceConfig.GetReverbEnable () ? 1 : 0); + SetParameter (ParameterReverbSize, m_PerformanceConfig.GetReverbSize ()); + SetParameter (ParameterReverbHighDamp, m_PerformanceConfig.GetReverbHighDamp ()); + SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); + SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); + SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); + SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); - m_UI.DisplayChanged (); + m_UI.DisplayChanged (); } std::string CMiniDexed::GetNewPerformanceDefaultName(void) @@ -2493,3 +2562,9 @@ bool CMiniDexed::InitNetwork() return false; } } + +// Forward declaration and definition for getPhysicalTG +static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); +static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices) { + return logicalTG * unisonVoices + unisonVoice; +} diff --git a/src/minidexed.h b/src/minidexed.h index 1658482..744cc0f 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -222,6 +222,10 @@ public: TGParameterATAmplitude, TGParameterATEGBias, + TGParameterUnisonVoices, + TGParameterUnisonDetune, + TGParameterUnisonSpread, + TGParameterUnknown }; @@ -305,7 +309,9 @@ private: int m_nNoteShift[CConfig::AllToneGenerators]; unsigned m_nReverbSend[CConfig::AllToneGenerators]; - + unsigned m_nUnisonVoices[CConfig::AllToneGenerators]; + unsigned m_nUnisonDetune[CConfig::AllToneGenerators]; + unsigned m_nUnisonSpread[CConfig::AllToneGenerators]; uint8_t m_nRawVoiceData[156]; diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index d0ce2de..ee25b21 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -209,8 +209,15 @@ bool CPerformanceConfig::Load (void) PropertyName.Format ("AftertouchTarget%u", nTG+1); m_nAftertouchTarget[nTG] = m_Properties.GetNumber (PropertyName, 0); - - } + + // Unison parameters + PropertyName.Format ("UnisonVoices%u", nTG+1); + m_nUnisonVoices[nTG] = m_Properties.GetNumber(PropertyName, 1); + PropertyName.Format ("UnisonDetune%u", nTG+1); + m_nUnisonDetune[nTG] = m_Properties.GetNumber(PropertyName, 0); + PropertyName.Format ("UnisonSpread%u", nTG+1); + m_nUnisonSpread[nTG] = m_Properties.GetNumber(PropertyName, 0); + } m_bCompressorEnable = m_Properties.GetNumber ("CompressorEnable", 1) != 0; @@ -326,9 +333,16 @@ bool CPerformanceConfig::Save (void) m_Properties.SetNumber (PropertyName, m_nAftertouchRange[nTG]); PropertyName.Format ("AftertouchTarget%u", nTG+1); - m_Properties.SetNumber (PropertyName, m_nAftertouchTarget[nTG]); - - } + m_Properties.SetNumber (PropertyName, m_nAftertouchTarget[nTG]); + + // Unison parameters + PropertyName.Format ("UnisonVoices%u", nTG+1); + m_Properties.SetNumber(PropertyName, m_nUnisonVoices[nTG]); + PropertyName.Format ("UnisonDetune%u", nTG+1); + m_Properties.SetNumber(PropertyName, m_nUnisonDetune[nTG]); + PropertyName.Format ("UnisonSpread%u", nTG+1); + m_Properties.SetNumber(PropertyName, m_nUnisonSpread[nTG]); + } m_Properties.SetNumber ("CompressorEnable", m_bCompressorEnable ? 1 : 0); @@ -1328,3 +1342,28 @@ bool CPerformanceConfig::IsValidPerformanceBank(unsigned nBankID) } return true; } + +unsigned CPerformanceConfig::GetUnisonVoices(unsigned nTG) const { + assert(nTG < CConfig::AllToneGenerators); + return m_nUnisonVoices[nTG]; +} +unsigned CPerformanceConfig::GetUnisonDetune(unsigned nTG) const { + assert(nTG < CConfig::AllToneGenerators); + return m_nUnisonDetune[nTG]; +} +unsigned CPerformanceConfig::GetUnisonSpread(unsigned nTG) const { + assert(nTG < CConfig::AllToneGenerators); + return m_nUnisonSpread[nTG]; +} +void CPerformanceConfig::SetUnisonVoices(unsigned nValue, unsigned nTG) { + assert(nTG < CConfig::AllToneGenerators); + m_nUnisonVoices[nTG] = nValue; +} +void CPerformanceConfig::SetUnisonDetune(unsigned nValue, unsigned nTG) { + assert(nTG < CConfig::AllToneGenerators); + m_nUnisonDetune[nTG] = nValue; +} +void CPerformanceConfig::SetUnisonSpread(unsigned nValue, unsigned nTG) { + assert(nTG < CConfig::AllToneGenerators); + m_nUnisonSpread[nTG] = nValue; +} diff --git a/src/performanceconfig.h b/src/performanceconfig.h index 916a2ee..8670b7f 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -70,6 +70,12 @@ public: unsigned GetBreathControlTarget (unsigned nTG) const; // 0 .. 7 unsigned GetAftertouchRange (unsigned nTG) const; // 0 .. 99 unsigned GetAftertouchTarget (unsigned nTG) const; // 0 .. 7 + unsigned GetUnisonVoices(unsigned nTG) const; // 1..4 + unsigned GetUnisonDetune(unsigned nTG) const; // 0..99 + unsigned GetUnisonSpread(unsigned nTG) const; // 0..99 + void SetUnisonVoices(unsigned nValue, unsigned nTG); + void SetUnisonDetune(unsigned nValue, unsigned nTG); + void SetUnisonSpread(unsigned nValue, unsigned nTG); void SetBankNumber (unsigned nValue, unsigned nTG); void SetVoiceNumber (unsigned nValue, unsigned nTG); @@ -182,6 +188,9 @@ private: unsigned m_nBreathControlTarget[CConfig::AllToneGenerators]; unsigned m_nAftertouchRange[CConfig::AllToneGenerators]; unsigned m_nAftertouchTarget[CConfig::AllToneGenerators]; + unsigned m_nUnisonVoices[CConfig::AllToneGenerators]; + unsigned m_nUnisonDetune[CConfig::AllToneGenerators]; + unsigned m_nUnisonSpread[CConfig::AllToneGenerators]; unsigned m_nLastPerformance; unsigned m_nActualPerformance = 0; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 1475342..5abf42b 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -71,22 +71,25 @@ const CUIMenu::TMenuItem CUIMenu::s_MainMenu[] = const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] = { - {"Voice", EditProgramNumber}, - {"Bank", EditVoiceBankNumber}, - {"Volume", EditTGParameter, 0, CMiniDexed::TGParameterVolume}, + {"Voice", EditProgramNumber}, + {"Bank", EditVoiceBankNumber}, + {"Volume", EditTGParameter, 0, CMiniDexed::TGParameterVolume}, #ifdef ARM_ALLOW_MULTI_CORE - {"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan}, - {"Reverb-Send", EditTGParameter, 0, CMiniDexed::TGParameterReverbSend}, + {"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan}, + {"Reverb-Send", EditTGParameter, 0, CMiniDexed::TGParameterReverbSend}, #endif - {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, - {"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff}, - {"Resonance", EditTGParameter, 0, CMiniDexed::TGParameterResonance}, - {"Pitch Bend", MenuHandler, s_EditPitchBendMenu}, - {"Portamento", MenuHandler, s_EditPortamentoMenu}, - {"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode}, - {"Modulation", MenuHandler, s_ModulationMenu}, - {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, - {"Edit Voice", MenuHandler, s_EditVoiceMenu}, + {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, + {"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff}, + {"Resonance", EditTGParameter, 0, CMiniDexed::TGParameterResonance}, + {"Unison Voices", EditTGParameter, 0, CMiniDexed::TGParameterUnisonVoices}, + {"Unison Detune", EditTGParameter, 0, CMiniDexed::TGParameterUnisonDetune}, + {"Unison Spread", EditTGParameter, 0, CMiniDexed::TGParameterUnisonSpread}, + {"Pitch Bend", MenuHandler, s_EditPitchBendMenu}, + {"Portamento", MenuHandler, s_EditPortamentoMenu}, + {"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode}, + {"Modulation", MenuHandler, s_ModulationMenu}, + {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, + {"Edit Voice", MenuHandler, s_EditVoiceMenu}, {0} }; @@ -265,7 +268,10 @@ const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] {0, 99, 1}, //AT Range {0, 1, 1, ToOnOff}, //AT Pitch {0, 1, 1, ToOnOff}, //AT Amp - {0, 1, 1, ToOnOff} //AT EGBias + {0, 1, 1, ToOnOff}, //AT EGBias + {1, 4, 1}, // Unison Voices + {0, 99, 1}, // Unison Detune + {0, 99, 1}, // Unison Spread }; // must match DexedVoiceParameters in Synth_Dexed From c114768ec766042c12910ae8b2377cf447674241 Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 11:56:28 +0200 Subject: [PATCH 02/10] Do not create auxiliary TGs on the fly --- src/effect_mixer.hpp | 16 +++++ src/minidexed.cpp | 136 +++++++++++++++++++++++++++---------------- src/minidexed.h | 3 +- 3 files changed, 103 insertions(+), 52 deletions(-) diff --git a/src/effect_mixer.hpp b/src/effect_mixer.hpp index 44184ab..e4d2e44 100644 --- a/src/effect_mixer.hpp +++ b/src/effect_mixer.hpp @@ -55,6 +55,22 @@ public: multiplier[channel] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2 } + // Add a raw parameter to allow bypassing the powf(gain, 4) curve + void gain(uint8_t channel, float32_t gain, bool raw) + { + if (channel >= NN) return; + + if (gain > MAX_GAIN) + gain = MAX_GAIN; + else if (gain < MIN_GAIN) + gain = MIN_GAIN; + if (raw) { + multiplier[channel] = gain; + } else { + multiplier[channel] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2 + } + } + void gain(float32_t gain) { for (uint8_t i = 0; i < NN; i++) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 97f5b5c..a60a64b 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -30,6 +30,18 @@ #include #include #include "arm_float_to_q23.h" +#include "common.h" + +// --- Macro for propagating parameter changes to all unison voices --- +#define FOR_AUX_UNISON_TGS(nTG, code) \ + { \ + unsigned unisonVoices = m_nUnisonVoices[nTG]; \ + if (unisonVoices < 1) unisonVoices = 1; \ + for (unsigned v = 0; v < unisonVoices; ++v) { \ + unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); \ + if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; \ + code; \ + }} // Forward declaration for getPhysicalTG static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); @@ -132,6 +144,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nUnisonVoices[i] = 1; m_nUnisonDetune[i] = 0; m_nUnisonSpread[i] = 0; + memset(m_noteOnCount[i], 0, sizeof(m_noteOnCount[i])); } unsigned nUSBGadgetPin = pConfig->GetUSBGadgetPin(); @@ -727,15 +740,11 @@ void CMiniDexed::SetExpression (unsigned nExpression, unsigned nTG) void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) { nPan=constrain((int)nPan,0,127); - assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return; // Not an active TG - + if (nTG >= m_nToneGenerators) return; m_nPan[nTG] = nPan; - tg_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f)); reverb_send_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f)); - m_UI.ParameterChanged (); } @@ -756,50 +765,42 @@ void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG) void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) { nMasterTune=constrain((int)nMasterTune,-99,99); - assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return; // Not an active TG - + if (nTG >= m_nToneGenerators) return; m_nMasterTune[nTG] = nMasterTune; - assert (m_pTG[nTG]); m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune); - m_UI.ParameterChanged (); } void CMiniDexed::SetCutoff (int nCutoff, unsigned nTG) { nCutoff = constrain (nCutoff, 0, 99); - assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return; // Not an active TG - + if (nTG >= m_nToneGenerators) return; m_nCutoff[nTG] = nCutoff; - assert (m_pTG[nTG]); m_pTG[nTG]->setFilterCutoff (mapfloat (nCutoff, 0, 99, 0.0f, 1.0f)); - + FOR_AUX_UNISON_TGS(nTG, { + m_pTG[physicalTG]->setFilterCutoff(mapfloat(nCutoff, 0, 99, 0.0f, 1.0f)); + }) m_UI.ParameterChanged (); } void CMiniDexed::SetResonance (int nResonance, unsigned nTG) { nResonance = constrain (nResonance, 0, 99); - assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return; // Not an active TG - + if (nTG >= m_nToneGenerators) return; m_nResonance[nTG] = nResonance; - assert (m_pTG[nTG]); m_pTG[nTG]->setFilterResonance (mapfloat (nResonance, 0, 99, 0.0f, 1.0f)); - + FOR_AUX_UNISON_TGS(nTG, { + m_pTG[physicalTG]->setFilterResonance(mapfloat(nResonance, 0, 99, 0.0f, 1.0f)); + }) m_UI.ParameterChanged (); } - - void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) { assert (nTG < CConfig::AllToneGenerators); @@ -867,12 +868,7 @@ void CMiniDexed::keyup (int16_t pitch, unsigned nTG) for (unsigned v = 0; v < unisonVoices; ++v) { unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); if (physicalTG >= m_nToneGenerators) break; - // Ensure virtual TG plays the same voice as logical TG - if (physicalTG != nTG) { - uint8_t voiceData[156]; - m_pTG[nTG]->getVoiceData(voiceData); - m_pTG[physicalTG]->loadVoiceParameters(voiceData); - } + // Per-voice detune and pan float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; int detune = baseDetune + (int)detuneOffset; @@ -897,17 +893,16 @@ void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) unsigned unisonVoices = m_nUnisonVoices[nTG]; if (unisonVoices < 1) unisonVoices = 1; - unsigned maxLogicalTGs = m_nToneGenerators / unisonVoices; - if (nTG >= maxLogicalTGs) return; // Don't exceed available physical TGs int baseDetune = m_nMasterTune[nTG]; unsigned basePan = m_nPan[nTG]; unsigned unisonDetune = m_nUnisonDetune[nTG]; unsigned unisonSpread = m_nUnisonSpread[nTG]; - for (unsigned v = 0; v < unisonVoices; ++v) { + for (unsigned v = 0; v < unisonVoices; ++v) { unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); if (physicalTG >= m_nToneGenerators) break; + // Per-voice detune and pan float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; int detune = baseDetune + (int)detuneOffset; @@ -1175,27 +1170,38 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT case TGParameterATAmplitude: setModController(3, 2, nValue, nTG); break; case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break; + case TGParameterUnisonVoices: { + unsigned oldUnisonVoices = m_nUnisonVoices[nTG]; + unsigned newUnisonVoices = constrain(nValue, 1, 4); + if (newUnisonVoices < oldUnisonVoices) { + // For each note and each physical TG that is no longer mapped, send keyup if needed + for (unsigned midiNote = 0; midiNote < 128; ++midiNote) { + for (unsigned v = newUnisonVoices; v < oldUnisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, oldUnisonVoices); + if (physicalTG >= m_nToneGenerators) continue; + m_pTG[physicalTG]->keyup(midiNote); + } + } + } + m_nUnisonVoices[nTG] = newUnisonVoices; + break; + } + case TGParameterUnisonDetune: + m_nUnisonDetune[nTG] = constrain(nValue, 0, 99); + break; + case TGParameterUnisonSpread: + m_nUnisonSpread[nTG] = constrain(nValue, 0, 99); + break; case TGParameterMIDIChannel: - assert (0 <= nValue && nValue <= 255); - SetMIDIChannel ((uint8_t) nValue, nTG); + SetMIDIChannel(nValue, nTG); + break; + case TGParameterReverbSend: + SetReverbSend(nValue, nTG); + break; + case TGParameterUnknown: + // No action needed for unknown parameter break; - - case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; - - case TGParameterUnisonVoices: - m_nUnisonVoices[nTG] = constrain(nValue, 1, 4); - break; - case TGParameterUnisonDetune: - m_nUnisonDetune[nTG] = constrain(nValue, 0, 99); - break; - case TGParameterUnisonSpread: - m_nUnisonSpread[nTG] = constrain(nValue, 0, 99); - break; - case TGParameterUnknown: - // No action needed for unknown parameter - break; } - } int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) @@ -1470,7 +1476,22 @@ void CMiniDexed::ProcessSound (void) { for (uint8_t i = 0; i < m_nToneGenerators; i++) { + unsigned unisonVoices = 1; + // Find which logical TG this physical TG belongs to + for (unsigned lTG = 0; lTG < m_nToneGenerators; ++lTG) { + unsigned uv = m_nUnisonVoices[lTG]; + if (uv < 1) uv = 1; + unsigned start = lTG * uv; + unsigned end = start + uv; + if (i >= start && i < end) { + unisonVoices = uv; + break; + } + } + float gainComp = 1.0f / sqrtf((float)unisonVoices); + tg_mixer->gain(i, gainComp, true); tg_mixer->doAddMix(i,m_OutputLevel[i]); + reverb_send_mixer->gain(i, gainComp * mapfloat(m_nReverbSend[i],0,99,0.0f,1.0f), true); reverb_send_mixer->doAddMix(i,m_OutputLevel[i]); } // END TG mixing @@ -1667,6 +1688,10 @@ void CMiniDexed::setMonoMode(uint8_t mono, uint8_t nTG) m_bMonoMode[nTG]= mono != 0; m_pTG[nTG]->setMonoMode(constrain(mono, 0, 1)); m_pTG[nTG]->doRefreshVoice(); + FOR_AUX_UNISON_TGS(nTG, { + m_pTG[physicalTG]->setMonoMode(constrain(mono, 0, 1)); + m_pTG[physicalTG]->doRefreshVoice(); + }) m_UI.ParameterChanged (); } @@ -1878,7 +1903,11 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) m_pTG[nTG]->loadVoiceParameters(&voice[6]); m_pTG[nTG]->doRefreshVoice(); setOPMask(0b111111, nTG); - + FOR_AUX_UNISON_TGS(nTG, { + m_pTG[physicalTG]->loadVoiceParameters(&voice[6]); + m_pTG[physicalTG]->doRefreshVoice(); + setOPMask(0b111111, physicalTG); + }) m_UI.ParameterChanged (); } @@ -1939,6 +1968,10 @@ void CMiniDexed::setOPMask(uint8_t uchOPMask, uint8_t nTG) { m_uchOPMask[nTG] = uchOPMask; m_pTG[nTG]->setOPAll (m_uchOPMask[nTG]); + FOR_AUX_UNISON_TGS(nTG, { + m_uchOPMask[physicalTG] = uchOPMask; + m_pTG[physicalTG]->setOPAll(uchOPMask); + }) } void CMiniDexed::setMasterVolume(float32_t vol) @@ -2564,7 +2597,8 @@ bool CMiniDexed::InitNetwork() } // Forward declaration and definition for getPhysicalTG -static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices) { + // Default mapping: physical TGs are grouped by logical TG, unison voices are consecutive + // e.g. for 4 unison voices: TG0: 0,1,2,3; TG1: 4,5,6,7, etc. return logicalTG * unisonVoices + unisonVoice; } diff --git a/src/minidexed.h b/src/minidexed.h index 744cc0f..7151e66 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -314,7 +314,8 @@ private: unsigned m_nUnisonSpread[CConfig::AllToneGenerators]; uint8_t m_nRawVoiceData[156]; - + uint8_t m_noteOnCount[CConfig::AllToneGenerators][128]; + float32_t nMasterVolume; CUserInterface m_UI; From c55fa12135c51cbca05927017b9eb0a5071e1ea9 Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 12:19:08 +0200 Subject: [PATCH 03/10] Apply voice change in TG to auxiliary TGs --- src/minidexed.cpp | 54 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index a60a64b..fd6cdd8 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -679,6 +679,29 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) m_pTG[nTG]->loadVoiceParameters (Buffer); setOPMask(0b111111, nTG); + // Propagate to auxiliary TGs for unison + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + int baseDetune = m_nMasterTune[nTG]; + unsigned basePan = m_nPan[nTG]; + unsigned unisonDetune = m_nUnisonDetune[nTG]; + unsigned unisonSpread = m_nUnisonSpread[nTG]; + for (unsigned v = 0; v < unisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; + m_pTG[physicalTG]->loadVoiceParameters(Buffer); + setOPMask(0b111111, physicalTG); + // Apply per-voice detune and pan + float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; + float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; + int detune = baseDetune + (int)detuneOffset; + unsigned pan = basePan + (int)panOffset; + detune = constrain(detune, -99, 99); + pan = constrain((int)pan, 0, 127); + m_pTG[physicalTG]->setMasterTune((int8_t)detune); + tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); + } + if (m_pConfig->GetMIDIAutoVoiceDumpOnPC()) { // Only do the voice dump back out over MIDI if we have a specific @@ -1636,9 +1659,9 @@ bool CMiniDexed::DoSavePerformance (void) m_PerformanceConfig.SetPortamentoGlissando (m_nPortamentoGlissando[nTG], nTG); m_PerformanceConfig.SetPortamentoTime (m_nPortamentoTime[nTG], nTG); - m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG); - m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG); - m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG); + m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); + m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); + m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); if (nTG < m_pConfig->GetToneGenerators()) { m_pTG[nTG]->getVoiceData(m_nRawVoiceData); @@ -1898,16 +1921,35 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) { if (voice[151 + i] > 126) // filter characters voice[151 +i] = 32; - } + } m_pTG[nTG]->loadVoiceParameters(&voice[6]); m_pTG[nTG]->doRefreshVoice(); setOPMask(0b111111, nTG); - FOR_AUX_UNISON_TGS(nTG, { + + // Propagate to auxiliary TGs for unison + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + int baseDetune = m_nMasterTune[nTG]; + unsigned basePan = m_nPan[nTG]; + unsigned unisonDetune = m_nUnisonDetune[nTG]; + unsigned unisonSpread = m_nUnisonSpread[nTG]; + for (unsigned v = 0; v < unisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->loadVoiceParameters(&voice[6]); m_pTG[physicalTG]->doRefreshVoice(); setOPMask(0b111111, physicalTG); - }) + // Apply per-voice detune and pan + float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; + float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; + int detune = baseDetune + (int)detuneOffset; + unsigned pan = basePan + (int)panOffset; + detune = constrain(detune, -99, 99); + pan = constrain((int)pan, 0, 127); + m_pTG[physicalTG]->setMasterTune((int8_t)detune); + tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); + } m_UI.ParameterChanged (); } From 5b66c8dd704b31c753f33521029bc19094a9aa35 Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 22:18:05 +0200 Subject: [PATCH 04/10] Improve menu structure --- src/uimenu.cpp | 16 +++++++++++----- src/uimenu.h | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 5abf42b..e9d41a2 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -81,9 +81,7 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] = {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, {"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff}, {"Resonance", EditTGParameter, 0, CMiniDexed::TGParameterResonance}, - {"Unison Voices", EditTGParameter, 0, CMiniDexed::TGParameterUnisonVoices}, - {"Unison Detune", EditTGParameter, 0, CMiniDexed::TGParameterUnisonDetune}, - {"Unison Spread", EditTGParameter, 0, CMiniDexed::TGParameterUnisonSpread}, + {"Unison", MenuHandler, s_UnisonMenu}, {"Pitch Bend", MenuHandler, s_EditPitchBendMenu}, {"Portamento", MenuHandler, s_EditPortamentoMenu}, {"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode}, @@ -93,6 +91,14 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] = {0} }; +const CUIMenu::TMenuItem CUIMenu::s_UnisonMenu[] = +{ + {"Voices", EditTGParameter, 0, CMiniDexed::TGParameterUnisonVoices}, + {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterUnisonDetune}, + {"Spread", EditTGParameter, 0, CMiniDexed::TGParameterUnisonSpread}, + {0} +}; + const CUIMenu::TMenuItem CUIMenu::s_EffectsMenu[] = { {"Compress", EditGlobalParameter, 0, CMiniDexed::ParameterCompressorEnable}, @@ -1104,7 +1110,7 @@ string CUIMenu::GetOPValueString (unsigned nOPParameter, int nValue) string CUIMenu::ToVolume (int nValue) { - static const size_t MaxChars = CConfig::LCDColumns-2; + static const std::size_t MaxChars = CConfig::LCDColumns-2; char VolumeBar[MaxChars+1]; memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character VolumeBar[nValue * MaxChars / 127] = '\0'; @@ -1115,7 +1121,7 @@ string CUIMenu::ToVolume (int nValue) string CUIMenu::ToPan (int nValue) { assert (CConfig::LCDColumns == 16); - static const size_t MaxChars = CConfig::LCDColumns-3; + static const std::size_t MaxChars = CConfig::LCDColumns-3; char PanMarker[MaxChars+1] = "......:......"; unsigned nIndex = nValue * MaxChars / 127; if (nIndex == MaxChars) diff --git a/src/uimenu.h b/src/uimenu.h index 6c72f36..cbeb6ac 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -57,11 +57,6 @@ public: }; public: - CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed, CConfig *pConfig); - - void EventHandler (TMenuEvent Event); - -private: typedef void TMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event); struct TMenuItem @@ -72,6 +67,17 @@ private: unsigned Parameter; }; + static const TMenuItem s_UnisonMenu[]; + static void EditTGParameter(CUIMenu *pUIMenu, TMenuEvent Event); + static void EditVoiceParameter(CUIMenu *pUIMenu, TMenuEvent Event); + static void EditOPParameter(CUIMenu *pUIMenu, TMenuEvent Event); + static void SavePerformance(CUIMenu *pUIMenu, TMenuEvent Event); + +public: + CUIMenu(CUserInterface *pUI, CMiniDexed *pMiniDexed, CConfig *pConfig); + void EventHandler(TMenuEvent Event); + +private: typedef std::string TToString (int nValue); struct TParameter @@ -87,10 +93,6 @@ private: static void EditGlobalParameter (CUIMenu *pUIMenu, TMenuEvent Event); static void EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); static void EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event); - static void EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event); - static void EditVoiceParameter (CUIMenu *pUIMenu, TMenuEvent Event); - static void EditOPParameter (CUIMenu *pUIMenu, TMenuEvent Event); - static void SavePerformance (CUIMenu *pUIMenu, TMenuEvent Event); static void EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event); static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event); static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event); From 279fd2525b741771d37b6acde162a02cf94367ae Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 23:14:44 +0200 Subject: [PATCH 05/10] Prevent crackle when voice is sent from Dexed --- src/minidexed.cpp | 57 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 537d15f..febb900 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1194,21 +1194,45 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break; case TGParameterUnisonVoices: { - unsigned oldUnisonVoices = m_nUnisonVoices[nTG]; - unsigned newUnisonVoices = constrain(nValue, 1, 4); - if (newUnisonVoices < oldUnisonVoices) { - // For each note and each physical TG that is no longer mapped, send keyup if needed - for (unsigned midiNote = 0; midiNote < 128; ++midiNote) { - for (unsigned v = newUnisonVoices; v < oldUnisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, oldUnisonVoices); - if (physicalTG >= m_nToneGenerators) continue; - m_pTG[physicalTG]->keyup(midiNote); - } + unsigned oldUnisonVoices = m_nUnisonVoices[nTG]; + unsigned newUnisonVoices = constrain(nValue, 1, 4); + if (newUnisonVoices < oldUnisonVoices) { + // For each note and each physical TG that is no longer mapped, send keyup if needed + for (unsigned midiNote = 0; midiNote < 128; ++midiNote) { + for (unsigned v = newUnisonVoices; v < oldUnisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, oldUnisonVoices); + if (physicalTG >= m_nToneGenerators) continue; + m_pTG[physicalTG]->keyup(midiNote); } } - m_nUnisonVoices[nTG] = newUnisonVoices; - break; + } else if (newUnisonVoices > oldUnisonVoices) { + // Configure new auxiliary TGs with the current voice data + uint8_t voiceData[161]; + m_pTG[nTG]->getVoiceData(&voiceData[6]); // getVoiceData expects pointer to voice data (skipping 6 bytes header) + for (unsigned v = oldUnisonVoices; v < newUnisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, newUnisonVoices); + if (physicalTG >= m_nToneGenerators) continue; + m_pTG[physicalTG]->loadVoiceParameters(&voiceData[6]); + m_pTG[physicalTG]->doRefreshVoice(); + setOPMask(0b111111, physicalTG); + // Apply per-voice detune and pan + int baseDetune = m_nMasterTune[nTG]; + unsigned basePan = m_nPan[nTG]; + unsigned unisonDetune = m_nUnisonDetune[nTG]; + unsigned unisonSpread = m_nUnisonSpread[nTG]; + float detuneOffset = ((float)v - (newUnisonVoices - 1) / 2.0f) * (float)unisonDetune; + float panOffset = ((float)v - (newUnisonVoices - 1) / 2.0f) * (float)unisonSpread; + int detune = baseDetune + (int)detuneOffset; + unsigned pan = basePan + (int)panOffset; + detune = constrain(detune, -99, 99); + pan = constrain((int)pan, 0, 127); + m_pTG[physicalTG]->setMasterTune((int8_t)detune); + tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); + } } + m_nUnisonVoices[nTG] = newUnisonVoices; + break; + } case TGParameterUnisonDetune: m_nUnisonDetune[nTG] = constrain(nValue, 0, 99); break; @@ -1951,6 +1975,15 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) m_pTG[physicalTG]->setMasterTune((int8_t)detune); tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); } + + // Silence any unused auxiliary TGs (to prevent leftover sound/crackle) + for (unsigned v = unisonVoices; v < 4; ++v) { // 4 is the max unison voices + unsigned physicalTG = getPhysicalTG(nTG, v, 4); + if (physicalTG >= m_nToneGenerators) continue; + m_pTG[physicalTG]->notesOff(); + m_pTG[physicalTG]->panic(); + } + m_UI.ParameterChanged (); } From 3dd2e423a511ecd05fd439f5aafea090e851275c Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 23:27:34 +0200 Subject: [PATCH 06/10] Save unison values in performance --- src/minidexed.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index febb900..1429bc6 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1679,7 +1679,7 @@ bool CMiniDexed::DoSavePerformance (void) m_PerformanceConfig.SetCutoff (m_nCutoff[nTG], nTG); m_PerformanceConfig.SetResonance (m_nResonance[nTG], nTG); m_PerformanceConfig.SetPitchBendRange (m_nPitchBendRange[nTG], nTG); - m_PerformanceConfig.SetPitchBendStep (m_nPitchBendStep[nTG], nTG); + m_PerformanceConfig.SetPitchBendStep (m_nPitchBendStep[nTG], nTG); m_PerformanceConfig.SetPortamentoMode (m_nPortamentoMode[nTG], nTG); m_PerformanceConfig.SetPortamentoGlissando (m_nPortamentoGlissando[nTG], nTG); m_PerformanceConfig.SetPortamentoTime (m_nPortamentoTime[nTG], nTG); @@ -1705,8 +1705,11 @@ bool CMiniDexed::DoSavePerformance (void) m_PerformanceConfig.SetBreathControlTarget (m_nBreathControlTarget[nTG], nTG); m_PerformanceConfig.SetAftertouchRange (m_nAftertouchRange[nTG], nTG); m_PerformanceConfig.SetAftertouchTarget (m_nAftertouchTarget[nTG], nTG); - m_PerformanceConfig.SetReverbSend (m_nReverbSend[nTG], nTG); + + m_PerformanceConfig.SetUnisonVoices(m_nUnisonVoices[nTG], nTG); + m_PerformanceConfig.SetUnisonDetune(m_nUnisonDetune[nTG], nTG); + m_PerformanceConfig.SetUnisonSpread(m_nUnisonSpread[nTG], nTG); } m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]); From 02863193a0d37a4ebbefadcbb301f6fc307f7948 Mon Sep 17 00:00:00 2001 From: probonopd Date: Fri, 2 May 2025 00:10:43 +0200 Subject: [PATCH 07/10] Fix navigation --- src/minidexed.cpp | 12 ++++-------- src/uimenu.cpp | 23 +++++++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1429bc6..3f465bb 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -44,7 +44,10 @@ }} // Forward declaration for getPhysicalTG -static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); +static constexpr unsigned maxUnisonVoices = 4; +static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned /*unisonVoices*/) { + return logicalTG * maxUnisonVoices + unisonVoice; +} const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; @@ -2674,10 +2677,3 @@ bool CMiniDexed::InitNetwork() return false; } } - -// Forward declaration and definition for getPhysicalTG -static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices) { - // Default mapping: physical TGs are grouped by logical TG, unison voices are consecutive - // e.g. for 4 unison voices: TG0: 0,1,2,3; TG1: 4,5,6,7, etc. - return logicalTG * unisonVoices + unisonVoice; -} diff --git a/src/uimenu.cpp b/src/uimenu.cpp index e9d41a2..6c1252f 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -746,9 +746,16 @@ void CUIMenu::EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event) } } -void CUIMenu::EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event) +void CUIMenu::EditTGParameter(CUIMenu *pUIMenu, TMenuEvent Event) { - unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1]; + unsigned nTG = 0; + // Always determine the correct logical TG from the menu stack + if (pUIMenu->m_nCurrentMenuDepth >= 2) { + nTG = pUIMenu->m_nMenuStackParameter[1]; + } else if (pUIMenu->m_nCurrentMenuDepth >= 1) { + nTG = pUIMenu->m_nMenuStackParameter[0]; + } + if (nTG >= pUIMenu->m_nToneGenerators) nTG = 0; CMiniDexed::TTGParameter Param = (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter; const TParameter &rParam = s_TGParameter[Param]; @@ -788,15 +795,15 @@ void CUIMenu::EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event) return; } - string TG ("TG"); - TG += to_string (nTG+1); + std::string TG ("TG"); + TG += std::to_string (nTG+1); - string Value = GetTGValueString (Param, pUIMenu->m_pMiniDexed->GetTGParameter (Param, nTG)); + std::string Value = GetTGValueString (Param, pUIMenu->m_pMiniDexed->GetTGParameter (Param, nTG)); pUIMenu->m_pUI->DisplayWrite (TG.c_str (), - pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, - Value.c_str (), - nValue > rParam.Minimum, nValue < rParam.Maximum); + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + Value.c_str (), + nValue > rParam.Minimum, nValue < rParam.Maximum); } void CUIMenu::EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event) // second menu level. Redundant code but in order to not modified original code From 84b39cb1cee21708f1c7fcbd95c3462de4b1dbd7 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 3 May 2025 15:05:39 +0200 Subject: [PATCH 08/10] Still something mixed up regarding which TGs are configured/playing? --- src/config.h | 12 ++--- src/minidexed.cpp | 110 +++++++++++++++++++++++----------------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/config.h b/src/config.h index aaf2476..c5cb7e8 100644 --- a/src/config.h +++ b/src/config.h @@ -47,17 +47,17 @@ public: #if (RASPPI==4 || RASPPI==5) // Pi 4 and 5 quad core // These are max values, default is to support 8 in total with optional 16 TGs - 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 TGsCore1Opt = 2; // process optional additional 2 TGs on core 1 - static const unsigned TGsCore23Opt = 3; // process optional additional 3 TGs on core 2 and 3 each + static const unsigned TGsCore1 = 2*4; // process 2 TGs on core 1 + static const unsigned TGsCore23 = 3*4; // process 3 TGs on core 2 and 3 each + static const unsigned TGsCore1Opt = 2*4; // process optional additional 2 TGs on core 1 + static const unsigned TGsCore23Opt = 3*4; // process optional additional 3 TGs on core 2 and 3 each static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23; static const unsigned AllToneGenerators = TGsCore1 + TGsCore1Opt + 2*TGsCore23 + 2*TGsCore23Opt; static const unsigned DefToneGenerators = MinToneGenerators; #else // Pi 2 or 3 quad core - 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 TGsCore1 = 2*4; // process 2 TGs on core 1 + static const unsigned TGsCore23 = 3*4; // process 3 TGs on core 2 and 3 each static const unsigned TGsCore1Opt = 0; static const unsigned TGsCore23Opt = 0; static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 3f465bb..7b81bca 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -673,6 +673,8 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) assert (nTG < CConfig::AllToneGenerators); if (nTG >= m_nToneGenerators) return; // Not an active TG + LOGNOTE("Voice change: logicalTG=%u, program=%u", nTG, nProgram); + m_nProgram[nTG] = nProgram; uint8_t Buffer[156]; @@ -691,6 +693,7 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) unsigned unisonSpread = m_nUnisonSpread[nTG]; for (unsigned v = 0; v < unisonVoices; ++v) { unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + LOGNOTE(" affected physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v); if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->loadVoiceParameters(Buffer); setOPMask(0b111111, physicalTG); @@ -1311,62 +1314,57 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) } } -void CMiniDexed::SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG) -{ - assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return; // Not an active TG - - assert (m_pTG[nTG]); - assert (nOP <= 6); - - if (nOP < 6) - { - nOP = 5 - nOP; // OPs are in reverse order - - if (uchOffset == DEXED_OP_ENABLE) - { - if (uchValue) - { - setOPMask(m_uchOPMask[nTG] | 1 << nOP, nTG); - } - else - { - setOPMask(m_uchOPMask[nTG] & ~(1 << nOP), nTG); - } - - - return; - } - } - - uchOffset += nOP * 21; - assert (uchOffset < 156); - - m_pTG[nTG]->setVoiceDataElement (uchOffset, uchValue); -} - -uint8_t CMiniDexed::GetVoiceParameter (uint8_t uchOffset, unsigned nOP, unsigned nTG) -{ - assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return 0; // Not an active TG - - assert (m_pTG[nTG]); - assert (nOP <= 6); - - if (nOP < 6) - { - nOP = 5 - nOP; // OPs are in reverse order - - if (uchOffset == DEXED_OP_ENABLE) - { - return !!(m_uchOPMask[nTG] & (1 << nOP)); - } - } - - uchOffset += nOP * 21; - assert (uchOffset < 156); - - return m_pTG[nTG]->getVoiceDataElement (uchOffset); +void CMiniDexed::SetVoiceParameter(uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG) +{ + assert(nTG < CConfig::AllToneGenerators); + if (nTG >= m_nToneGenerators) return; // Not an active TG + + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + LOGNOTE("SetVoiceParameter: logicalTG=%u, unisonVoices=%u", nTG, unisonVoices); + for (unsigned v = 0; v < unisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + LOGNOTE(" -> physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v); + if (physicalTG >= m_nToneGenerators) break; + assert(m_pTG[physicalTG]); + unsigned op = nOP; + if (op < 6) { + op = 5 - op; // OPs are in reverse order + if (uchOffset == DEXED_OP_ENABLE) { + if (uchValue) { + setOPMask(m_uchOPMask[physicalTG] | 1 << op, physicalTG); + } else { + setOPMask(m_uchOPMask[physicalTG] & ~(1 << op), physicalTG); + } + continue; + } + } + uint8_t offset = uchOffset + op * 21; + assert(offset < 156); + m_pTG[physicalTG]->setVoiceDataElement(offset, uchValue); + } +} + +uint8_t CMiniDexed::GetVoiceParameter(uint8_t uchOffset, unsigned nOP, unsigned nTG) +{ + assert(nTG < CConfig::AllToneGenerators); + if (nTG >= m_nToneGenerators) return 0; // Not an active TG + + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + unsigned physicalTG = getPhysicalTG(nTG, 0, unisonVoices); // Use first physical TG for this logical TG + LOGNOTE("GetVoiceParameter: logicalTG=%u, unisonVoices=%u, physicalTG=%u", nTG, unisonVoices, physicalTG); + assert(m_pTG[physicalTG]); + unsigned op = nOP; + if (op < 6) { + op = 5 - op; // OPs are in reverse order + if (uchOffset == DEXED_OP_ENABLE) { + return !!(m_uchOPMask[physicalTG] & (1 << op)); + } + } + uint8_t offset = uchOffset + op * 21; + assert(offset < 156); + return m_pTG[physicalTG]->getVoiceDataElement(offset); } std::string CMiniDexed::GetVoiceName (unsigned nTG) From e08312740798d64755e9f2da25f60d63cd89defc Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 3 May 2025 16:14:18 +0200 Subject: [PATCH 09/10] Mixup is fixed --- src/minidexed.cpp | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 7b81bca..e8d1171 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -38,7 +38,7 @@ unsigned unisonVoices = m_nUnisonVoices[nTG]; \ if (unisonVoices < 1) unisonVoices = 1; \ for (unsigned v = 0; v < unisonVoices; ++v) { \ - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); \ + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); \ if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; \ code; \ }} @@ -692,7 +692,7 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) unsigned unisonDetune = m_nUnisonDetune[nTG]; unsigned unisonSpread = m_nUnisonSpread[nTG]; for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); LOGNOTE(" affected physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v); if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->loadVoiceParameters(Buffer); @@ -895,7 +895,7 @@ void CMiniDexed::keyup (int16_t pitch, unsigned nTG) unsigned unisonSpread = m_nUnisonSpread[nTG]; for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); if (physicalTG >= m_nToneGenerators) break; // Per-voice detune and pan float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; @@ -928,8 +928,10 @@ void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) unsigned unisonDetune = m_nUnisonDetune[nTG]; unsigned unisonSpread = m_nUnisonSpread[nTG]; + LOGNOTE("Note ON: pitch=%d, velocity=%u, logicalTG=%u, unisonVoices=%u", pitch, velocity, nTG, unisonVoices); for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); + LOGNOTE(" playing physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v); if (physicalTG >= m_nToneGenerators) break; // Per-voice detune and pan float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; @@ -1201,12 +1203,12 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT case TGParameterUnisonVoices: { unsigned oldUnisonVoices = m_nUnisonVoices[nTG]; - unsigned newUnisonVoices = constrain(nValue, 1, 4); + unsigned newUnisonVoices = constrain(nValue, 1, maxUnisonVoices); if (newUnisonVoices < oldUnisonVoices) { // For each note and each physical TG that is no longer mapped, send keyup if needed for (unsigned midiNote = 0; midiNote < 128; ++midiNote) { for (unsigned v = newUnisonVoices; v < oldUnisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, oldUnisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); if (physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->keyup(midiNote); } @@ -1216,7 +1218,7 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT uint8_t voiceData[161]; m_pTG[nTG]->getVoiceData(&voiceData[6]); // getVoiceData expects pointer to voice data (skipping 6 bytes header) for (unsigned v = oldUnisonVoices; v < newUnisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, newUnisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); if (physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->loadVoiceParameters(&voiceData[6]); m_pTG[physicalTG]->doRefreshVoice(); @@ -1323,7 +1325,7 @@ void CMiniDexed::SetVoiceParameter(uint8_t uchOffset, uint8_t uchValue, unsigned if (unisonVoices < 1) unisonVoices = 1; LOGNOTE("SetVoiceParameter: logicalTG=%u, unisonVoices=%u", nTG, unisonVoices); for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); LOGNOTE(" -> physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v); if (physicalTG >= m_nToneGenerators) break; assert(m_pTG[physicalTG]); @@ -1352,7 +1354,7 @@ uint8_t CMiniDexed::GetVoiceParameter(uint8_t uchOffset, unsigned nOP, unsigned unsigned unisonVoices = m_nUnisonVoices[nTG]; if (unisonVoices < 1) unisonVoices = 1; - unsigned physicalTG = getPhysicalTG(nTG, 0, unisonVoices); // Use first physical TG for this logical TG + unsigned physicalTG = getPhysicalTG(nTG, 0, maxUnisonVoices); // Use first physical TG for this logical TG LOGNOTE("GetVoiceParameter: logicalTG=%u, unisonVoices=%u, physicalTG=%u", nTG, unisonVoices, physicalTG); assert(m_pTG[physicalTG]); unsigned op = nOP; @@ -1964,7 +1966,7 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) unsigned unisonDetune = m_nUnisonDetune[nTG]; unsigned unisonSpread = m_nUnisonSpread[nTG]; for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->loadVoiceParameters(&voice[6]); m_pTG[physicalTG]->doRefreshVoice(); @@ -1979,10 +1981,9 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) m_pTG[physicalTG]->setMasterTune((int8_t)detune); tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); } - // Silence any unused auxiliary TGs (to prevent leftover sound/crackle) for (unsigned v = unisonVoices; v < 4; ++v) { // 4 is the max unison voices - unsigned physicalTG = getPhysicalTG(nTG, v, 4); + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); if (physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->notesOff(); m_pTG[physicalTG]->panic(); @@ -2009,6 +2010,15 @@ int16_t CMiniDexed::checkSystemExclusive(const uint8_t* pMessage,const uint16_t assert (m_pTG[nTG]); + // Log which TGs are affected by SysEx voice load + unsigned unisonVoices = m_nUnisonVoices[nTG]; + if (unisonVoices < 1) unisonVoices = 1; + LOGNOTE("SysEx voice load: logicalTG=%u, unisonVoices=%u", nTG, unisonVoices); + for (unsigned v = 0; v < unisonVoices; ++v) { + unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); + LOGNOTE(" affected physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v); + } + return(m_pTG[nTG]->checkSystemExclusive(pMessage, nLength)); } From cf83c636845951fe3d36f6dd069c8b86f329162d Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 3 May 2025 16:54:17 +0200 Subject: [PATCH 10/10] Don't hardcode number [ci skip] --- src/minidexed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index e8d1171..09c7ea7 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1982,7 +1982,7 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG) tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); } // Silence any unused auxiliary TGs (to prevent leftover sound/crackle) - for (unsigned v = unisonVoices; v < 4; ++v) { // 4 is the max unison voices + for (unsigned v = unisonVoices; v < maxUnisonVoices; ++v) { // maxUnisonVoices is the max unison voices unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices); if (physicalTG >= m_nToneGenerators) continue; m_pTG[physicalTG]->notesOff();