pull/892/merge
probonopd 3 days ago committed by GitHub
commit 0c859792c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 12
      src/config.h
  2. 16
      src/effect_mixer.hpp
  3. 351
      src/minidexed.cpp
  4. 9
      src/minidexed.h
  5. 39
      src/performanceconfig.cpp
  6. 9
      src/performanceconfig.h
  7. 33
      src/uimenu.cpp
  8. 20
      src/uimenu.h

@ -47,17 +47,17 @@ public:
#if (RASPPI==4 || RASPPI==5) #if (RASPPI==4 || RASPPI==5)
// Pi 4 and 5 quad core // Pi 4 and 5 quad core
// These are max values, default is to support 8 in total with optional 16 TGs // 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 TGsCore1 = 2*4; // process 2 TGs on core 1
static const unsigned TGsCore23 = 3; // process 3 TGs on core 2 and 3 each static const unsigned TGsCore23 = 3*4; // 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 TGsCore1Opt = 2*4; // 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 TGsCore23Opt = 3*4; // process optional additional 3 TGs on core 2 and 3 each
static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23; static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23;
static const unsigned AllToneGenerators = TGsCore1 + TGsCore1Opt + 2*TGsCore23 + 2*TGsCore23Opt; static const unsigned AllToneGenerators = TGsCore1 + TGsCore1Opt + 2*TGsCore23 + 2*TGsCore23Opt;
static const unsigned DefToneGenerators = MinToneGenerators; static const unsigned DefToneGenerators = MinToneGenerators;
#else #else
// Pi 2 or 3 quad core // Pi 2 or 3 quad core
static const unsigned TGsCore1 = 2; // process 2 TGs on core 1 static const unsigned TGsCore1 = 2*4; // process 2 TGs on core 1
static const unsigned TGsCore23 = 3; // process 3 TGs on core 2 and 3 each static const unsigned TGsCore23 = 3*4; // process 3 TGs on core 2 and 3 each
static const unsigned TGsCore1Opt = 0; static const unsigned TGsCore1Opt = 0;
static const unsigned TGsCore23Opt = 0; static const unsigned TGsCore23Opt = 0;
static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23; static const unsigned MinToneGenerators = TGsCore1 + 2*TGsCore23;

@ -55,6 +55,22 @@ public:
multiplier[channel] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2 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) void gain(float32_t gain)
{ {
for (uint8_t i = 0; i < NN; i++) for (uint8_t i = 0; i < NN; i++)

@ -30,6 +30,24 @@
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include "arm_float_to_q23.h" #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 WLANFirmwarePath[] = "SD:firmware/";
const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; 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]->setEngineType(pConfig->GetEngineType ());
m_pTG[i]->activate (); 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(); unsigned nUSBGadgetPin = pConfig->GetUSBGadgetPin();
@ -651,6 +673,8 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
assert (nTG < CConfig::AllToneGenerators); assert (nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return; // Not an active TG if (nTG >= m_nToneGenerators) return; // Not an active TG
LOGNOTE("Voice change: logicalTG=%u, program=%u", nTG, nProgram);
m_nProgram[nTG] = nProgram; m_nProgram[nTG] = nProgram;
uint8_t Buffer[156]; uint8_t Buffer[156];
@ -660,6 +684,30 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
m_pTG[nTG]->loadVoiceParameters (Buffer); m_pTG[nTG]->loadVoiceParameters (Buffer);
setOPMask(0b111111, nTG); 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()) if (m_pConfig->GetMIDIAutoVoiceDumpOnPC())
{ {
// Only do the voice dump back out over MIDI if we have a specific // 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) void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
{ {
nPan=constrain((int)nPan,0,127); nPan=constrain((int)nPan,0,127);
assert (nTG < CConfig::AllToneGenerators); assert (nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return; // Not an active TG if (nTG >= m_nToneGenerators) return;
m_nPan[nTG] = nPan; m_nPan[nTG] = nPan;
tg_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f)); 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)); reverb_send_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f));
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
} }
@ -750,50 +794,42 @@ void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG)
void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG)
{ {
nMasterTune=constrain((int)nMasterTune,-99,99); nMasterTune=constrain((int)nMasterTune,-99,99);
assert (nTG < CConfig::AllToneGenerators); assert (nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return; // Not an active TG if (nTG >= m_nToneGenerators) return;
m_nMasterTune[nTG] = nMasterTune; m_nMasterTune[nTG] = nMasterTune;
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune); m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune);
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetCutoff (int nCutoff, unsigned nTG) void CMiniDexed::SetCutoff (int nCutoff, unsigned nTG)
{ {
nCutoff = constrain (nCutoff, 0, 99); nCutoff = constrain (nCutoff, 0, 99);
assert (nTG < CConfig::AllToneGenerators); assert (nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return; // Not an active TG if (nTG >= m_nToneGenerators) return;
m_nCutoff[nTG] = nCutoff; m_nCutoff[nTG] = nCutoff;
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->setFilterCutoff (mapfloat (nCutoff, 0, 99, 0.0f, 1.0f)); 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 (); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetResonance (int nResonance, unsigned nTG) void CMiniDexed::SetResonance (int nResonance, unsigned nTG)
{ {
nResonance = constrain (nResonance, 0, 99); nResonance = constrain (nResonance, 0, 99);
assert (nTG < CConfig::AllToneGenerators); assert (nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return; // Not an active TG if (nTG >= m_nToneGenerators) return;
m_nResonance[nTG] = nResonance; m_nResonance[nTG] = nResonance;
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->setFilterResonance (mapfloat (nResonance, 0, 99, 0.0f, 1.0f)); 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 (); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
{ {
assert (nTG < CConfig::AllToneGenerators); assert (nTG < CConfig::AllToneGenerators);
@ -848,9 +884,29 @@ void CMiniDexed::keyup (int16_t pitch, unsigned nTG)
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
pitch = ApplyNoteLimits (pitch, nTG); pitch = ApplyNoteLimits (pitch, nTG);
if (pitch >= 0) if (pitch < 0) return;
{
m_pTG[nTG]->keyup (pitch); 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]); assert (m_pTG[nTG]);
pitch = ApplyNoteLimits (pitch, nTG); pitch = ApplyNoteLimits (pitch, nTG);
if (pitch >= 0) if (pitch < 0) return;
{
m_pTG[nTG]->keydown (pitch, velocity); 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 TGParameterATAmplitude: setModController(3, 2, nValue, nTG); break;
case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break; case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break;
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 TGParameterMIDIChannel: case TGParameterMIDIChannel:
assert (0 <= nValue && nValue <= 255); SetMIDIChannel(nValue, nTG);
SetMIDIChannel ((uint8_t) nValue, nTG);
break; break;
case TGParameterReverbSend:
case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; SetReverbSend(nValue, nTG);
break;
default: case TGParameterUnknown:
assert (0); // No action needed for unknown parameter
break; break;
} }
} }
@ -1181,7 +1304,12 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG)
case TGParameterATAmplitude: return getModController(3, 2, nTG); case TGParameterATAmplitude: return getModController(3, 2, nTG);
case TGParameterATEGBias: return getModController(3, 3, 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: default:
assert (0); assert (0);
return 0; return 0;
@ -1193,33 +1321,30 @@ void CMiniDexed::SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigne
assert(nTG < CConfig::AllToneGenerators); assert(nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return; // Not an active TG if (nTG >= m_nToneGenerators) return; // Not an active TG
assert (m_pTG[nTG]); unsigned unisonVoices = m_nUnisonVoices[nTG];
assert (nOP <= 6); if (unisonVoices < 1) unisonVoices = 1;
LOGNOTE("SetVoiceParameter: logicalTG=%u, unisonVoices=%u", nTG, unisonVoices);
if (nOP < 6) for (unsigned v = 0; v < unisonVoices; ++v) {
{ unsigned physicalTG = getPhysicalTG(nTG, v, maxUnisonVoices);
nOP = 5 - nOP; // OPs are in reverse order LOGNOTE(" -> physicalTG=%u (logicalTG=%u, unisonVoice=%u)", physicalTG, nTG, v);
if (physicalTG >= m_nToneGenerators) break;
if (uchOffset == DEXED_OP_ENABLE) assert(m_pTG[physicalTG]);
{ unsigned op = nOP;
if (uchValue) if (op < 6) {
{ op = 5 - op; // OPs are in reverse order
setOPMask(m_uchOPMask[nTG] | 1 << nOP, nTG); if (uchOffset == DEXED_OP_ENABLE) {
if (uchValue) {
setOPMask(m_uchOPMask[physicalTG] | 1 << op, physicalTG);
} else {
setOPMask(m_uchOPMask[physicalTG] & ~(1 << op), physicalTG);
} }
else continue;
{
setOPMask(m_uchOPMask[nTG] & ~(1 << nOP), nTG);
} }
return;
} }
uint8_t offset = uchOffset + op * 21;
assert(offset < 156);
m_pTG[physicalTG]->setVoiceDataElement(offset, uchValue);
} }
uchOffset += nOP * 21;
assert (uchOffset < 156);
m_pTG[nTG]->setVoiceDataElement (uchOffset, uchValue);
} }
uint8_t CMiniDexed::GetVoiceParameter(uint8_t uchOffset, unsigned nOP, unsigned nTG) uint8_t CMiniDexed::GetVoiceParameter(uint8_t uchOffset, unsigned nOP, unsigned nTG)
@ -1227,23 +1352,21 @@ uint8_t CMiniDexed::GetVoiceParameter (uint8_t uchOffset, unsigned nOP, unsigned
assert(nTG < CConfig::AllToneGenerators); assert(nTG < CConfig::AllToneGenerators);
if (nTG >= m_nToneGenerators) return 0; // Not an active TG if (nTG >= m_nToneGenerators) return 0; // Not an active TG
assert (m_pTG[nTG]); unsigned unisonVoices = m_nUnisonVoices[nTG];
assert (nOP <= 6); if (unisonVoices < 1) unisonVoices = 1;
unsigned physicalTG = getPhysicalTG(nTG, 0, maxUnisonVoices); // Use first physical TG for this logical TG
if (nOP < 6) LOGNOTE("GetVoiceParameter: logicalTG=%u, unisonVoices=%u, physicalTG=%u", nTG, unisonVoices, physicalTG);
{ assert(m_pTG[physicalTG]);
nOP = 5 - nOP; // OPs are in reverse order unsigned op = nOP;
if (op < 6) {
if (uchOffset == DEXED_OP_ENABLE) op = 5 - op; // OPs are in reverse order
{ if (uchOffset == DEXED_OP_ENABLE) {
return !!(m_uchOPMask[nTG] & (1 << nOP)); return !!(m_uchOPMask[physicalTG] & (1 << op));
} }
} }
uint8_t offset = uchOffset + op * 21;
uchOffset += nOP * 21; assert(offset < 156);
assert (uchOffset < 156); return m_pTG[physicalTG]->getVoiceDataElement(offset);
return m_pTG[nTG]->getVoiceDataElement (uchOffset);
} }
std::string CMiniDexed::GetVoiceName (unsigned nTG) std::string CMiniDexed::GetVoiceName (unsigned nTG)
@ -1408,7 +1531,22 @@ void CMiniDexed::ProcessSound (void)
{ {
for (uint8_t i = 0; i < m_nToneGenerators; i++) 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]); 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]); reverb_send_mixer->doAddMix(i,m_OutputLevel[i]);
} }
// END TG mixing // END TG mixing
@ -1553,9 +1691,9 @@ bool CMiniDexed::DoSavePerformance (void)
m_PerformanceConfig.SetPortamentoGlissando (m_nPortamentoGlissando[nTG], nTG); m_PerformanceConfig.SetPortamentoGlissando (m_nPortamentoGlissando[nTG], nTG);
m_PerformanceConfig.SetPortamentoTime (m_nPortamentoTime[nTG], nTG); m_PerformanceConfig.SetPortamentoTime (m_nPortamentoTime[nTG], nTG);
m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG); m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG);
m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG); m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG);
m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG); m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG);
if (nTG < m_pConfig->GetToneGenerators()) if (nTG < m_pConfig->GetToneGenerators())
{ {
m_pTG[nTG]->getVoiceData(m_nRawVoiceData); m_pTG[nTG]->getVoiceData(m_nRawVoiceData);
@ -1574,8 +1712,11 @@ bool CMiniDexed::DoSavePerformance (void)
m_PerformanceConfig.SetBreathControlTarget (m_nBreathControlTarget[nTG], nTG); m_PerformanceConfig.SetBreathControlTarget (m_nBreathControlTarget[nTG], nTG);
m_PerformanceConfig.SetAftertouchRange (m_nAftertouchRange[nTG], nTG); m_PerformanceConfig.SetAftertouchRange (m_nAftertouchRange[nTG], nTG);
m_PerformanceConfig.SetAftertouchTarget (m_nAftertouchTarget[nTG], nTG); m_PerformanceConfig.SetAftertouchTarget (m_nAftertouchTarget[nTG], nTG);
m_PerformanceConfig.SetReverbSend (m_nReverbSend[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]); 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_bMonoMode[nTG]= mono != 0;
m_pTG[nTG]->setMonoMode(constrain(mono, 0, 1)); m_pTG[nTG]->setMonoMode(constrain(mono, 0, 1));
m_pTG[nTG]->doRefreshVoice(); m_pTG[nTG]->doRefreshVoice();
FOR_AUX_UNISON_TGS(nTG, {
m_pTG[physicalTG]->setMonoMode(constrain(mono, 0, 1));
m_pTG[physicalTG]->doRefreshVoice();
})
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
} }
@ -1817,6 +1962,37 @@ void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG)
m_pTG[nTG]->doRefreshVoice(); m_pTG[nTG]->doRefreshVoice();
setOPMask(0b111111, nTG); 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 (); m_UI.ParameterChanged ();
} }
@ -1838,6 +2014,15 @@ int16_t CMiniDexed::checkSystemExclusive(const uint8_t* pMessage,const uint16_t
assert (m_pTG[nTG]); 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)); 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_uchOPMask[nTG] = uchOPMask;
m_pTG[nTG]->setOPAll (m_uchOPMask[nTG]); 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) void CMiniDexed::setMasterVolume(float32_t vol)
@ -2030,7 +2219,6 @@ void CMiniDexed::LoadPerformanceParameters(void)
{ {
for (unsigned nTG = 0; nTG < CConfig::AllToneGenerators; nTG++) for (unsigned nTG = 0; nTG < CConfig::AllToneGenerators; nTG++)
{ {
BankSelect (m_PerformanceConfig.GetBankNumber (nTG), nTG); BankSelect (m_PerformanceConfig.GetBankNumber (nTG), nTG);
ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG);
SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG);
@ -2067,7 +2255,10 @@ void CMiniDexed::LoadPerformanceParameters(void)
setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG); setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG);
setAftertouchTarget (m_PerformanceConfig.GetAftertouchTarget (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 // Effects

@ -222,6 +222,10 @@ public:
TGParameterATAmplitude, TGParameterATAmplitude,
TGParameterATEGBias, TGParameterATEGBias,
TGParameterUnisonVoices,
TGParameterUnisonDetune,
TGParameterUnisonSpread,
TGParameterUnknown TGParameterUnknown
}; };
@ -305,9 +309,12 @@ private:
int m_nNoteShift[CConfig::AllToneGenerators]; int m_nNoteShift[CConfig::AllToneGenerators];
unsigned m_nReverbSend[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_nRawVoiceData[156];
uint8_t m_noteOnCount[CConfig::AllToneGenerators][128];
float32_t nMasterVolume; float32_t nMasterVolume;

@ -209,6 +209,13 @@ bool CPerformanceConfig::Load (void)
PropertyName.Format ("AftertouchTarget%u", nTG+1); PropertyName.Format ("AftertouchTarget%u", nTG+1);
m_nAftertouchTarget[nTG] = m_Properties.GetNumber (PropertyName, 0); 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; m_bCompressorEnable = m_Properties.GetNumber ("CompressorEnable", 1) != 0;
@ -327,6 +334,13 @@ bool CPerformanceConfig::Save (void)
PropertyName.Format ("AftertouchTarget%u", nTG+1); 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); m_Properties.SetNumber ("CompressorEnable", m_bCompressorEnable ? 1 : 0);
@ -1314,3 +1328,28 @@ bool CPerformanceConfig::IsValidPerformanceBank(unsigned nBankID)
} }
return true; 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;
}

@ -70,6 +70,12 @@ public:
unsigned GetBreathControlTarget (unsigned nTG) const; // 0 .. 7 unsigned GetBreathControlTarget (unsigned nTG) const; // 0 .. 7
unsigned GetAftertouchRange (unsigned nTG) const; // 0 .. 99 unsigned GetAftertouchRange (unsigned nTG) const; // 0 .. 99
unsigned GetAftertouchTarget (unsigned nTG) const; // 0 .. 7 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 SetBankNumber (unsigned nValue, unsigned nTG);
void SetVoiceNumber (unsigned nValue, unsigned nTG); void SetVoiceNumber (unsigned nValue, unsigned nTG);
@ -182,6 +188,9 @@ private:
unsigned m_nBreathControlTarget[CConfig::AllToneGenerators]; unsigned m_nBreathControlTarget[CConfig::AllToneGenerators];
unsigned m_nAftertouchRange[CConfig::AllToneGenerators]; unsigned m_nAftertouchRange[CConfig::AllToneGenerators];
unsigned m_nAftertouchTarget[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_nLastPerformance;
unsigned m_nActualPerformance = 0; unsigned m_nActualPerformance = 0;

@ -81,6 +81,7 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] =
{"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune},
{"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff}, {"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff},
{"Resonance", EditTGParameter, 0, CMiniDexed::TGParameterResonance}, {"Resonance", EditTGParameter, 0, CMiniDexed::TGParameterResonance},
{"Unison", MenuHandler, s_UnisonMenu},
{"Pitch Bend", MenuHandler, s_EditPitchBendMenu}, {"Pitch Bend", MenuHandler, s_EditPitchBendMenu},
{"Portamento", MenuHandler, s_EditPortamentoMenu}, {"Portamento", MenuHandler, s_EditPortamentoMenu},
{"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode}, {"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode},
@ -90,6 +91,14 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] =
{0} {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[] = const CUIMenu::TMenuItem CUIMenu::s_EffectsMenu[] =
{ {
{"Compress", EditGlobalParameter, 0, CMiniDexed::ParameterCompressorEnable}, {"Compress", EditGlobalParameter, 0, CMiniDexed::ParameterCompressorEnable},
@ -265,7 +274,10 @@ const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown]
{0, 99, 1}, //AT Range {0, 99, 1}, //AT Range
{0, 1, 1, ToOnOff}, //AT Pitch {0, 1, 1, ToOnOff}, //AT Pitch
{0, 1, 1, ToOnOff}, //AT Amp {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 // must match DexedVoiceParameters in Synth_Dexed
@ -736,7 +748,14 @@ 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; CMiniDexed::TTGParameter Param = (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter;
const TParameter &rParam = s_TGParameter[Param]; const TParameter &rParam = s_TGParameter[Param];
@ -776,10 +795,10 @@ void CUIMenu::EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event)
return; return;
} }
string TG ("TG"); std::string TG ("TG");
TG += to_string (nTG+1); 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_pUI->DisplayWrite (TG.c_str (),
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
@ -1098,7 +1117,7 @@ string CUIMenu::GetOPValueString (unsigned nOPParameter, int nValue)
string CUIMenu::ToVolume (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]; char VolumeBar[MaxChars+1];
memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character
VolumeBar[nValue * MaxChars / 127] = '\0'; VolumeBar[nValue * MaxChars / 127] = '\0';
@ -1109,7 +1128,7 @@ string CUIMenu::ToVolume (int nValue)
string CUIMenu::ToPan (int nValue) string CUIMenu::ToPan (int nValue)
{ {
assert (CConfig::LCDColumns == 16); 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] = "......:......"; char PanMarker[MaxChars+1] = "......:......";
unsigned nIndex = nValue * MaxChars / 127; unsigned nIndex = nValue * MaxChars / 127;
if (nIndex == MaxChars) if (nIndex == MaxChars)

@ -57,11 +57,6 @@ public:
}; };
public: public:
CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed, CConfig *pConfig);
void EventHandler (TMenuEvent Event);
private:
typedef void TMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event); typedef void TMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event);
struct TMenuItem struct TMenuItem
@ -72,6 +67,17 @@ private:
unsigned Parameter; 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); typedef std::string TToString (int nValue);
struct TParameter struct TParameter
@ -87,10 +93,6 @@ private:
static void EditGlobalParameter (CUIMenu *pUIMenu, TMenuEvent Event); static void EditGlobalParameter (CUIMenu *pUIMenu, TMenuEvent Event);
static void EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); static void EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event);
static void EditProgramNumber (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 EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event);
static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event); static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event);
static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event); static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event);

Loading…
Cancel
Save