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/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 40c69b7..3865b67 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -30,6 +30,24 @@ #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, maxUnisonVoices); \ + if (physicalTG == nTG || physicalTG >= m_nToneGenerators) continue; \ + code; \ + }} + +// Forward declaration for getPhysicalTG +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"; @@ -126,6 +144,10 @@ 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; + memset(m_noteOnCount[i], 0, sizeof(m_noteOnCount[i])); } unsigned nUSBGadgetPin = pConfig->GetUSBGadgetPin(); @@ -651,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]; @@ -660,6 +684,30 @@ 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, maxUnisonVoices); + 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); + // 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 @@ -721,15 +769,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 (); } @@ -750,50 +794,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); @@ -848,9 +884,29 @@ 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, maxUnisonVoices); + 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; + 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 +918,31 @@ 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; + + int baseDetune = m_nMasterTune[nTG]; + unsigned basePan = m_nPan[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, 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; + 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); } } @@ -1123,16 +1201,61 @@ 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 TGParameterMIDIChannel: - assert (0 <= nValue && nValue <= 255); - SetMIDIChannel ((uint8_t) nValue, nTG); + case TGParameterUnisonVoices: { + unsigned oldUnisonVoices = m_nUnisonVoices[nTG]; + 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, maxUnisonVoices); + if (physicalTG >= m_nToneGenerators) continue; + m_pTG[physicalTG]->keyup(midiNote); + } + } + } 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, maxUnisonVoices); + 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; + case TGParameterUnisonSpread: + m_nUnisonSpread[nTG] = constrain(nValue, 0, 99); break; - case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; - - default: - assert (0); + case TGParameterMIDIChannel: + SetMIDIChannel(nValue, nTG); + break; + case TGParameterReverbSend: + SetReverbSend(nValue, nTG); + break; + case TGParameterUnknown: + // No action needed for unknown parameter break; } } @@ -1181,69 +1304,69 @@ 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; } } -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, maxUnisonVoices); + 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, 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; + 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) @@ -1408,7 +1531,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 @@ -1548,14 +1686,14 @@ 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); - 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); @@ -1574,8 +1712,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]); @@ -1605,6 +1746,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 (); } @@ -1807,16 +1952,47 @@ 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(); 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, maxUnisonVoices); + 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)); + } + // Silence any unused auxiliary TGs (to prevent leftover sound/crackle) + 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(); + m_pTG[physicalTG]->panic(); + } + m_UI.ParameterChanged (); } @@ -1838,6 +2014,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)); } @@ -1877,6 +2062,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) @@ -2029,58 +2218,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); } - - // 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 (); + 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 ()); + + m_UI.DisplayChanged (); } std::string CMiniDexed::GetNewPerformanceDefaultName(void) diff --git a/src/minidexed.h b/src/minidexed.h index 1658482..7151e66 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -222,6 +222,10 @@ public: TGParameterATAmplitude, TGParameterATEGBias, + TGParameterUnisonVoices, + TGParameterUnisonDetune, + TGParameterUnisonSpread, + TGParameterUnknown }; @@ -305,10 +309,13 @@ 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]; - + uint8_t m_noteOnCount[CConfig::AllToneGenerators][128]; + float32_t nMasterVolume; CUserInterface m_UI; diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index dfff8a9..de561c8 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -208,8 +208,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; @@ -325,9 +332,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); @@ -1314,3 +1328,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..6c1252f 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -71,22 +71,31 @@ 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", MenuHandler, s_UnisonMenu}, + {"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} +}; + +const CUIMenu::TMenuItem CUIMenu::s_UnisonMenu[] = +{ + {"Voices", EditTGParameter, 0, CMiniDexed::TGParameterUnisonVoices}, + {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterUnisonDetune}, + {"Spread", EditTGParameter, 0, CMiniDexed::TGParameterUnisonSpread}, {0} }; @@ -265,7 +274,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 @@ -734,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]; @@ -776,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 @@ -1098,7 +1117,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'; @@ -1109,7 +1128,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);