Support hierarchic menus (#61)

* Support hierarchic menus

This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.

* uimenu: Make the tables const

* uimenu: Add sub-menu "Edit Voice"

Menu items can be re-sorted, if necessary.

* uimenu: Add "Reverb" sub-menu

* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
pull/67/head
Rene Stange 3 years ago committed by GitHub
parent 3b51ed8477
commit 4610a2a2fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Makefile
  2. 149
      src/minidexed.cpp
  3. 42
      src/minidexed.h
  4. 798
      src/uimenu.cpp
  5. 133
      src/uimenu.h
  6. 291
      src/userinterface.cpp
  7. 44
      src/userinterface.h

@ -6,7 +6,7 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib
SYNTH_DEXED_DIR = ../Synth_Dexed/src SYNTH_DEXED_DIR = ../Synth_Dexed/src
CMSIS_DIR = ../CMSIS_5/CMSIS CMSIS_DIR = ../CMSIS_5/CMSIS
OBJS = main.o kernel.o minidexed.o config.o userinterface.o \ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \
mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o performanceconfig.o perftimer.o \ sysexfileloader.o performanceconfig.o perftimer.o \
effect_platervbstereo.o effect_platervbstereo.o

@ -55,7 +55,11 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
for (unsigned i = 0; i < CConfig::ToneGenerators; i++) for (unsigned i = 0; i < CConfig::ToneGenerators; i++)
{ {
m_nVoiceBankID[i] = 0; m_nVoiceBankID[i] = 0;
m_nProgram[i] = 0;
m_nVolume[i] = 100;
m_nPan[i] = 64; m_nPan[i] = 64;
m_nMasterTune[i] = 0;
m_nMIDIChannel[i] = CMIDIDevice::Disabled;
m_nNoteLimitLow[i] = 0; m_nNoteLimitLow[i] = 0;
m_nNoteLimitHigh[i] = 127; m_nNoteLimitHigh[i] = 127;
@ -111,12 +115,12 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
// BEGIN setup reverb // BEGIN setup reverb
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate());
reverb->size(0.7); SetParameter (ParameterReverbSize, 70);
reverb->hidamp(0.5); SetParameter (ParameterReverbHighDamp, 50);
reverb->lodamp(0.5); SetParameter (ParameterReverbLowDamp, 50);
reverb->lowpass(0.3); SetParameter (ParameterReverbLowPass, 30);
reverb->diffusion(0.2); SetParameter (ParameterReverbDiffusion, 20);
reverb->send(0.8); SetParameter (ParameterReverbSend, 80);
// END setup reverb // END setup reverb
}; };
@ -304,7 +308,7 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG)
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nVoiceBankID[nTG] = nBankLSB; m_nVoiceBankID[nTG] = nBankLSB;
m_UI.BankSelected (nBankLSB, nTG); m_UI.ParameterChanged ();
} }
void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
@ -315,13 +319,15 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
} }
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nProgram[nTG] = nProgram;
uint8_t Buffer[156]; uint8_t Buffer[156];
m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer); m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer);
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->loadVoiceParameters (Buffer); m_pTG[nTG]->loadVoiceParameters (Buffer);
m_UI.ProgramChanged (nProgram, nTG); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG)
@ -332,10 +338,12 @@ void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG)
} }
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nVolume[nTG] = nVolume;
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->setGain (nVolume / 127.0); m_pTG[nTG]->setGain (nVolume / 127.0);
m_UI.VolumeChanged (nVolume, nTG); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
@ -348,7 +356,7 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nPan[nTG] = nPan; m_nPan[nTG] = nPan;
m_UI.PanChanged (nPan, nTG); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG)
@ -359,15 +367,18 @@ void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG)
} }
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
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.MasterTuneChanged (nMasterTune, nTG); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
{ {
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nMIDIChannel[nTG] = uchChannel;
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
{ {
@ -386,7 +397,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
unsigned nActiveTGs = 0; unsigned nActiveTGs = 0;
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{ {
if (m_PCKeyboard.GetChannel (nTG) != CMIDIDevice::Disabled) if (m_nMIDIChannel[nTG] != CMIDIDevice::Disabled)
{ {
nActiveTGs++; nActiveTGs++;
} }
@ -397,7 +408,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
m_nActiveTGsLog2 = Log2[nActiveTGs]; m_nActiveTGsLog2 = Log2[nActiveTGs];
#endif #endif
m_UI.MIDIChannelChanged (uchChannel, nTG); m_UI.ParameterChanged ();
} }
void CMiniDexed::keyup (int16_t pitch, unsigned nTG) void CMiniDexed::keyup (int16_t pitch, unsigned nTG)
@ -473,6 +484,116 @@ void CMiniDexed::ControllersRefresh (unsigned nTG)
m_pTG[nTG]->ControllersRefresh (); m_pTG[nTG]->ControllersRefresh ();
} }
void CMiniDexed::SetParameter (TParameter Parameter, int nValue)
{
assert (reverb);
assert (Parameter < ParameterUnknown);
m_nParameter[Parameter] = nValue;
float fValue = nValue / 99.0;
m_ReverbSpinLock.Acquire ();
switch (Parameter)
{
case ParameterReverbSize: reverb->size (fValue); break;
case ParameterReverbHighDamp: reverb->hidamp (fValue); break;
case ParameterReverbLowDamp: reverb->lodamp (fValue); break;
case ParameterReverbLowPass: reverb->lowpass (fValue); break;
case ParameterReverbDiffusion: reverb->diffusion (fValue); break;
case ParameterReverbSend: reverb->send (fValue); break;
default:
assert (0);
break;
}
m_ReverbSpinLock.Release ();
}
int CMiniDexed::GetParameter (TParameter Parameter)
{
assert (Parameter < ParameterUnknown);
return m_nParameter[Parameter];
}
void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
switch (Parameter)
{
case TGParameterVoiceBank: BankSelectLSB (nValue, nTG); break;
case TGParameterProgram: ProgramChange (nValue, nTG); break;
case TGParameterVolume: SetVolume (nValue, nTG); break;
case TGParameterPan: SetPan (nValue, nTG); break;
case TGParameterMasterTune: SetMasterTune (nValue, nTG); break;
case TGParameterMIDIChannel:
assert (0 <= nValue && nValue <= 255);
SetMIDIChannel ((uint8_t) nValue, nTG);
break;
default:
assert (0);
break;
}
}
int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
switch (Parameter)
{
case TGParameterVoiceBank: return m_nVoiceBankID[nTG];
case TGParameterProgram: return m_nProgram[nTG];
case TGParameterVolume: return m_nVolume[nTG];
case TGParameterPan: return m_nPan[nTG];
case TGParameterMasterTune: return m_nMasterTune[nTG];
case TGParameterMIDIChannel: return m_nMIDIChannel[nTG];
default:
assert (0);
return 0;
}
}
void CMiniDexed::SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
assert (nOP <= 6);
if (nOP < 6)
{
nOP = 5 - nOP; // OPs are in reverse order
}
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::ToneGenerators);
assert (m_pTG[nTG]);
assert (nOP <= 6);
if (nOP < 6)
{
nOP = 5 - nOP; // OPs are in reverse order
}
uchOffset += nOP * 21;
assert (uchOffset < 156);
return m_pTG[nTG]->getVoiceDataElement (uchOffset);
}
std::string CMiniDexed::GetVoiceName (unsigned nTG) std::string CMiniDexed::GetVoiceName (unsigned nTG)
{ {
char VoiceName[11]; char VoiceName[11];
@ -596,7 +717,9 @@ void CMiniDexed::ProcessSound (void)
} }
// BEGIN adding reverb // BEGIN adding reverb
m_ReverbSpinLock.Acquire ();
reverb->doReverb(nFrames,SampleBuffer); reverb->doReverb(nFrames,SampleBuffer);
m_ReverbSpinLock.Release ();
// END adding reverb // END adding reverb
if (m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) != (int) sizeof SampleBuffer) if (m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) != (int) sizeof SampleBuffer)

@ -38,6 +38,7 @@
#include <circle/i2cmaster.h> #include <circle/i2cmaster.h>
#include <circle/multicore.h> #include <circle/multicore.h>
#include <circle/soundbasedevice.h> #include <circle/soundbasedevice.h>
#include <circle/spinlock.h>
#include "effect_platervbstereo.h" #include "effect_platervbstereo.h"
class CMiniDexed class CMiniDexed
@ -74,6 +75,39 @@ public:
void setPitchbend (int16_t value, unsigned nTG); void setPitchbend (int16_t value, unsigned nTG);
void ControllersRefresh (unsigned nTG); void ControllersRefresh (unsigned nTG);
enum TParameter
{
ParameterReverbSize,
ParameterReverbHighDamp,
ParameterReverbLowDamp,
ParameterReverbLowPass,
ParameterReverbDiffusion,
ParameterReverbSend,
ParameterUnknown
};
void SetParameter (TParameter Parameter, int nValue);
int GetParameter (TParameter Parameter);
enum TTGParameter
{
TGParameterVoiceBank,
TGParameterProgram,
TGParameterVolume,
TGParameterPan,
TGParameterMasterTune,
TGParameterMIDIChannel,
TGParameterUnknown
};
void SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG);
int GetTGParameter (TTGParameter Parameter, unsigned nTG);
// access (global or OP-related) parameter of the active voice of a TG
static const unsigned NoOP = 6; // for global parameters
void SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG);
uint8_t GetVoiceParameter (uint8_t uchOffset, unsigned nOP, unsigned nTG);
std::string GetVoiceName (unsigned nTG); std::string GetVoiceName (unsigned nTG);
private: private:
@ -95,9 +129,16 @@ private:
private: private:
CConfig *m_pConfig; CConfig *m_pConfig;
int m_nParameter[ParameterUnknown]; // global (non-TG) parameters
CDexedAdapter *m_pTG[CConfig::ToneGenerators]; CDexedAdapter *m_pTG[CConfig::ToneGenerators];
unsigned m_nVoiceBankID[CConfig::ToneGenerators]; unsigned m_nVoiceBankID[CConfig::ToneGenerators];
unsigned m_nProgram[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators];
unsigned m_nPan[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators];
int m_nMasterTune[CConfig::ToneGenerators];
unsigned m_nMIDIChannel[CConfig::ToneGenerators];
unsigned m_nNoteLimitLow[CConfig::ToneGenerators]; unsigned m_nNoteLimitLow[CConfig::ToneGenerators];
unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; unsigned m_nNoteLimitHigh[CConfig::ToneGenerators];
@ -127,6 +168,7 @@ private:
bool m_bProfileEnabled; bool m_bProfileEnabled;
AudioEffectPlateReverb* reverb; AudioEffectPlateReverb* reverb;
CSpinLock m_ReverbSpinLock;
}; };
#endif #endif

@ -0,0 +1,798 @@
//
// uimenu.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// Original author of this class:
// R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "uimenu.h"
#include "minidexed.h"
#include "mididevice.h"
#include "userinterface.h"
#include "sysexfileloader.h"
#include "config.h"
#include <circle/sysconfig.h>
#include <assert.h>
using namespace std;
const CUIMenu::TMenuItem CUIMenu::s_MenuRoot[] =
{
{"MiniDexed", MenuHandler, s_MainMenu},
{0}
};
const CUIMenu::TMenuItem CUIMenu::s_MainMenu[] =
{
{"TG1", MenuHandler, s_TGMenu, 0},
#ifdef ARM_ALLOW_MULTI_CORE
{"TG2", MenuHandler, s_TGMenu, 1},
{"TG3", MenuHandler, s_TGMenu, 2},
{"TG4", MenuHandler, s_TGMenu, 3},
{"TG5", MenuHandler, s_TGMenu, 4},
{"TG6", MenuHandler, s_TGMenu, 5},
{"TG7", MenuHandler, s_TGMenu, 6},
{"TG8", MenuHandler, s_TGMenu, 7},
{"Reverb", MenuHandler, s_ReverbMenu},
#endif
{0}
};
const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] =
{
{"Voice", EditProgramNumber},
{"Bank", EditVoiceBankNumber},
{"Volume", EditTGParameter, 0, CMiniDexed::TGParameterVolume},
#ifdef ARM_ALLOW_MULTI_CORE
{"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan},
#endif
{"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune},
{"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel},
{"Edit Voice", MenuHandler, s_EditVoiceMenu},
{0}
};
#ifdef ARM_ALLOW_MULTI_CORE
const CUIMenu::TMenuItem CUIMenu::s_ReverbMenu[] =
{
{"Size", EditGlobalParameter, 0, CMiniDexed::ParameterReverbSize},
{"High damp", EditGlobalParameter, 0, CMiniDexed::ParameterReverbHighDamp},
{"Low damp", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowDamp},
{"Low pass", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowPass},
{"Diffusion", EditGlobalParameter, 0, CMiniDexed::ParameterReverbDiffusion},
{"Send", EditGlobalParameter, 0, CMiniDexed::ParameterReverbSend},
{0}
};
#endif
const CUIMenu::TMenuItem CUIMenu::s_EditVoiceMenu[] =
{
{"OP1", MenuHandler, s_OperatorMenu, 0},
{"OP2", MenuHandler, s_OperatorMenu, 1},
{"OP3", MenuHandler, s_OperatorMenu, 2},
{"OP4", MenuHandler, s_OperatorMenu, 3},
{"OP5", MenuHandler, s_OperatorMenu, 4},
{"OP6", MenuHandler, s_OperatorMenu, 5},
{"P EG Rate 1", EditVoiceParameter, 0, DEXED_PITCH_EG_R1},
{"P EG Rate 2", EditVoiceParameter, 0, DEXED_PITCH_EG_R2},
{"P EG Rate 3", EditVoiceParameter, 0, DEXED_PITCH_EG_R3},
{"P EG Rate 4", EditVoiceParameter, 0, DEXED_PITCH_EG_R4},
{"P EG Level 1",EditVoiceParameter, 0, DEXED_PITCH_EG_L1},
{"P EG Level 2",EditVoiceParameter, 0, DEXED_PITCH_EG_L2},
{"P EG Level 3",EditVoiceParameter, 0, DEXED_PITCH_EG_L3},
{"P EG Level 4",EditVoiceParameter, 0, DEXED_PITCH_EG_L4},
{"Algorithm", EditVoiceParameter, 0, DEXED_ALGORITHM},
{"Feedback", EditVoiceParameter, 0, DEXED_FEEDBACK},
{"Osc Key Sync",EditVoiceParameter, 0, DEXED_OSC_KEY_SYNC},
{"LFO Speed", EditVoiceParameter, 0, DEXED_LFO_SPEED},
{"LFO Delay", EditVoiceParameter, 0, DEXED_LFO_DELAY},
{"LFO PMD", EditVoiceParameter, 0, DEXED_LFO_PITCH_MOD_DEP},
{"LFO AMD", EditVoiceParameter, 0, DEXED_LFO_AMP_MOD_DEP},
{"LFO Sync", EditVoiceParameter, 0, DEXED_LFO_SYNC},
{"LFO Wave", EditVoiceParameter, 0, DEXED_LFO_WAVE},
{"P Mod Sens.", EditVoiceParameter, 0, DEXED_LFO_PITCH_MOD_SENS},
{"Transpose", EditVoiceParameter, 0, DEXED_TRANSPOSE},
{0}
};
const CUIMenu::TMenuItem CUIMenu::s_OperatorMenu[] =
{
{"EG Rate 1", EditOPParameter, 0, DEXED_OP_EG_R1},
{"EG Rate 2", EditOPParameter, 0, DEXED_OP_EG_R2},
{"EG Rate 3", EditOPParameter, 0, DEXED_OP_EG_R3},
{"EG Rate 4", EditOPParameter, 0, DEXED_OP_EG_R4},
{"EG Level 1", EditOPParameter, 0, DEXED_OP_EG_L1},
{"EG Level 2", EditOPParameter, 0, DEXED_OP_EG_L2},
{"EG Level 3", EditOPParameter, 0, DEXED_OP_EG_L3},
{"EG Level 4", EditOPParameter, 0, DEXED_OP_EG_L4},
{"Break Point", EditOPParameter, 0, DEXED_OP_LEV_SCL_BRK_PT},
{"L Key Depth", EditOPParameter, 0, DEXED_OP_SCL_LEFT_DEPTH},
{"R Key Depth", EditOPParameter, 0, DEXED_OP_SCL_RGHT_DEPTH},
{"L Key Scale", EditOPParameter, 0, DEXED_OP_SCL_LEFT_CURVE},
{"R Key Scale", EditOPParameter, 0, DEXED_OP_SCL_RGHT_CURVE},
{"Rate Scaling",EditOPParameter, 0, DEXED_OP_OSC_RATE_SCALE},
{"A Mod Sens.", EditOPParameter, 0, DEXED_OP_AMP_MOD_SENS},
{"K Vel. Sens.",EditOPParameter, 0, DEXED_OP_KEY_VEL_SENS},
{"Output Level",EditOPParameter, 0, DEXED_OP_OUTPUT_LEV},
{"Osc Mode", EditOPParameter, 0, DEXED_OP_OSC_MODE},
{"Freq Coarse", EditOPParameter, 0, DEXED_OP_FREQ_COARSE},
{"Freq Fine", EditOPParameter, 0, DEXED_OP_FREQ_FINE},
{"Osc Detune", EditOPParameter, 0, DEXED_OP_OSC_DETUNE},
{0}
};
// must match CMiniDexed::TParameter
const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::TGParameterUnknown] =
{
{0, 99, 1}, // ParameterReverbSize
{0, 99, 1}, // ParameterReverbHighDamp
{0, 99, 1}, // ParameterReverbLowDamp
{0, 99, 1}, // ParameterReverbLowPass
{0, 99, 1}, // ParameterReverbDiffusion
{0, 99, 1}, // ParameterReverbSend
};
// must match CMiniDexed::TTGParameter
const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] =
{
{0, CSysExFileLoader::MaxVoiceBankID, 1}, // TGParameterVoiceBank
{0, CSysExFileLoader::VoicesPerBank-1, 1}, // TGParameterProgram
{0, 127, 8, ToVolume}, // TGParameterVolume
{0, 127, 8, ToPan}, // TGParameterPan
{-99, 99, 1}, // TGParameterMasterTune
{0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel} // TGParameterMIDIChannel
};
// must match DexedVoiceParameters in Synth_Dexed
const CUIMenu::TParameter CUIMenu::s_VoiceParameter[] =
{
{0, 99, 1}, // DEXED_PITCH_EG_R1
{0, 99, 1}, // DEXED_PITCH_EG_R2
{0, 99, 1}, // DEXED_PITCH_EG_R3
{0, 99, 1}, // DEXED_PITCH_EG_R4
{0, 99, 1}, // DEXED_PITCH_EG_L1
{0, 99, 1}, // DEXED_PITCH_EG_L2
{0, 99, 1}, // DEXED_PITCH_EG_L3
{0, 99, 1}, // DEXED_PITCH_EG_L4
{0, 31, 1, ToAlgorithm}, // DEXED_ALGORITHM
{0, 7, 1}, // DEXED_FEEDBACK
{0, 1, 1, ToOnOff}, // DEXED_OSC_KEY_SYNC
{0, 99, 1}, // DEXED_LFO_SPEED
{0, 99, 1}, // DEXED_LFO_DELAY
{0, 99, 1}, // DEXED_LFO_PITCH_MOD_DEP
{0, 99, 1}, // DEXED_LFO_AMP_MOD_DEP
{0, 1, 1, ToOnOff}, // DEXED_LFO_SYNC
{0, 5, 1, ToLFOWaveform}, // DEXED_LFO_WAVE
{0, 7, 1}, // DEXED_LFO_PITCH_MOD_SENS
{0, 48, 1, ToTransposeNote} // DEXED_TRANSPOSE
};
// must match DexedVoiceOPParameters in Synth_Dexed
const CUIMenu::TParameter CUIMenu::s_OPParameter[] =
{
{0, 99, 1}, // DEXED_OP_EG_R1
{0, 99, 1}, // DEXED_OP_EG_R2
{0, 99, 1}, // DEXED_OP_EG_R3
{0, 99, 1}, // DEXED_OP_EG_R4
{0, 99, 1}, // DEXED_OP_EG_L1
{0, 99, 1}, // DEXED_OP_EG_L2
{0, 99, 1}, // DEXED_OP_EG_L3
{0, 99, 1}, // DEXED_OP_EG_L4
{0, 99, 1, ToBreakpointNote}, // DEXED_OP_LEV_SCL_BRK_PT
{0, 99, 1}, // DEXED_OP_SCL_LEFT_DEPTH
{0, 99, 1}, // DEXED_OP_SCL_RGHT_DEPTH
{0, 3, 1, ToKeyboardCurve}, // DEXED_OP_SCL_LEFT_CURVE
{0, 3, 1, ToKeyboardCurve}, // DEXED_OP_SCL_RGHT_CURVE
{0, 7, 1}, // DEXED_OP_OSC_RATE_SCALE
{0, 3, 1}, // DEXED_OP_AMP_MOD_SENS
{0, 7, 1}, // DEXED_OP_KEY_VEL_SENS
{0, 99, 1}, // DEXED_OP_OUTPUT_LEV
{0, 1, 1, ToOscillatorMode}, // DEXED_OP_OSC_MODE
{0, 31, 1}, // DEXED_OP_FREQ_COARSE
{0, 99, 1}, // DEXED_OP_FREQ_FINE
{0, 14, 1, ToOscillatorDetune} // DEXED_OP_OSC_DETUNE
};
const char CUIMenu::s_NoteName[100][4] =
{
"A1", "A#1", "B1", "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1",
"A2", "A#2", "B2", "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2",
"A3", "A#3", "B3", "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3",
"A4", "A#4", "B4", "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4",
"A5", "A#5", "B5", "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5",
"A6", "A#6", "B6", "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6",
"A7", "A#7", "B7", "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7",
"A8", "A#8", "B8", "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8",
"A9", "A#9", "B9", "C9"
};
static const unsigned NoteC3 = 27;
CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed)
: m_pUI (pUI),
m_pMiniDexed (pMiniDexed),
m_pParentMenu (s_MenuRoot),
m_pCurrentMenu (s_MainMenu),
m_nCurrentMenuItem (0),
m_nCurrentSelection (0),
m_nCurrentParameter (0),
m_nCurrentMenuDepth (0)
{
}
void CUIMenu::EventHandler (TMenuEvent Event)
{
switch (Event)
{
case MenuEventBack: // pop menu
if (m_nCurrentMenuDepth)
{
m_nCurrentMenuDepth--;
m_pParentMenu = m_MenuStackParent[m_nCurrentMenuDepth];
m_pCurrentMenu = m_MenuStackMenu[m_nCurrentMenuDepth];
m_nCurrentMenuItem = m_nMenuStackItem[m_nCurrentMenuDepth];
m_nCurrentSelection = m_nMenuStackSelection[m_nCurrentMenuDepth];
m_nCurrentParameter = m_nMenuStackParameter[m_nCurrentMenuDepth];
EventHandler (MenuEventUpdate);
}
break;
case MenuEventHome:
m_pParentMenu = s_MenuRoot;
m_pCurrentMenu = s_MainMenu;
m_nCurrentMenuItem = 0;
m_nCurrentSelection = 0;
m_nCurrentParameter = 0;
m_nCurrentMenuDepth = 0;
EventHandler (MenuEventUpdate);
break;
default:
(*m_pParentMenu[m_nCurrentMenuItem].Handler) (this, Event);
break;
}
}
void CUIMenu::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event)
{
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventSelect: // push menu
assert (pUIMenu->m_nCurrentMenuDepth < MaxMenuDepth);
pUIMenu->m_MenuStackParent[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pParentMenu;
pUIMenu->m_MenuStackMenu[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pCurrentMenu;
pUIMenu->m_nMenuStackItem[pUIMenu->m_nCurrentMenuDepth]
= pUIMenu->m_nCurrentMenuItem;
pUIMenu->m_nMenuStackSelection[pUIMenu->m_nCurrentMenuDepth]
= pUIMenu->m_nCurrentSelection;
pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth]
= pUIMenu->m_nCurrentParameter;
pUIMenu->m_nCurrentMenuDepth++;
pUIMenu->m_pParentMenu = pUIMenu->m_pCurrentMenu;
pUIMenu->m_nCurrentParameter =
pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter;
pUIMenu->m_pCurrentMenu =
pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].MenuItem;
pUIMenu->m_nCurrentMenuItem = pUIMenu->m_nCurrentSelection;
pUIMenu->m_nCurrentSelection = 0;
break;
case MenuEventStepDown:
if (pUIMenu->m_nCurrentSelection > 0)
{
pUIMenu->m_nCurrentSelection--;
}
break;
case MenuEventStepUp:
++pUIMenu->m_nCurrentSelection;
if (!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name) // more entries?
{
pUIMenu->m_nCurrentSelection--;
}
break;
default:
assert (0);
break;
}
if (pUIMenu->m_pCurrentMenu) // if this is another menu?
{
pUIMenu->m_pUI->DisplayWrite (
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
"",
pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name,
pUIMenu->m_nCurrentSelection > 0,
!!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name);
}
else
{
pUIMenu->EventHandler (MenuEventUpdate); // no, update parameter display
}
}
void CUIMenu::EditGlobalParameter (CUIMenu *pUIMenu, TMenuEvent Event)
{
CMiniDexed::TParameter Param = (CMiniDexed::TParameter) pUIMenu->m_nCurrentParameter;
const TParameter &rParam = s_GlobalParameter[Param];
int nValue = pUIMenu->m_pMiniDexed->GetParameter (Param);
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
nValue -= rParam.Increment;
if (nValue < rParam.Minimum)
{
nValue = rParam.Minimum;
}
pUIMenu->m_pMiniDexed->SetParameter (Param, nValue);
break;
case MenuEventStepUp:
nValue += rParam.Increment;
if (nValue > rParam.Maximum)
{
nValue = rParam.Maximum;
}
pUIMenu->m_pMiniDexed->SetParameter (Param, nValue);
break;
default:
return;
}
const char *pMenuName =
pUIMenu->m_MenuStackParent[pUIMenu->m_nCurrentMenuDepth-1]
[pUIMenu->m_nMenuStackItem[pUIMenu->m_nCurrentMenuDepth-1]].Name;
string Value = GetGlobalValueString (Param, pUIMenu->m_pMiniDexed->GetParameter (Param));
pUIMenu->m_pUI->DisplayWrite (pMenuName,
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
Value.c_str (),
nValue > rParam.Minimum, nValue < rParam.Maximum);
}
void CUIMenu::EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event)
{
unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1];
int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterVoiceBank, nTG);
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
if (--nValue < 0)
{
nValue = 0;
}
pUIMenu->m_pMiniDexed->SetTGParameter (
CMiniDexed::TGParameterVoiceBank, nValue, nTG);
break;
case MenuEventStepUp:
if (++nValue > (int) CSysExFileLoader::MaxVoiceBankID)
{
nValue = CSysExFileLoader::MaxVoiceBankID;
}
pUIMenu->m_pMiniDexed->SetTGParameter (
CMiniDexed::TGParameterVoiceBank, nValue, nTG);
break;
default:
return;
}
string TG ("TG");
TG += to_string (nTG+1);
string Value = to_string (nValue+1) + "="
+ pUIMenu->m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nValue);
pUIMenu->m_pUI->DisplayWrite (TG.c_str (),
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
Value.c_str (),
nValue > 0, nValue < (int) CSysExFileLoader::MaxVoiceBankID);
}
void CUIMenu::EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event)
{
unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1];
int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterProgram, nTG);
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
if (--nValue < 0)
{
nValue = 0;
}
pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nValue, nTG);
break;
case MenuEventStepUp:
if (++nValue > (int) CSysExFileLoader::VoicesPerBank-1)
{
nValue = CSysExFileLoader::VoicesPerBank-1;
}
pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nValue, nTG);
break;
default:
return;
}
string TG ("TG");
TG += to_string (nTG+1);
string Value = to_string (nValue+1) + "=" + pUIMenu->m_pMiniDexed->GetVoiceName (nTG);
pUIMenu->m_pUI->DisplayWrite (TG.c_str (),
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
Value.c_str (),
nValue > 0, nValue < (int) CSysExFileLoader::VoicesPerBank-1);
}
void CUIMenu::EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event)
{
unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1];
CMiniDexed::TTGParameter Param = (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter;
const TParameter &rParam = s_TGParameter[Param];
int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (Param, nTG);
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
nValue -= rParam.Increment;
if (nValue < rParam.Minimum)
{
nValue = rParam.Minimum;
}
pUIMenu->m_pMiniDexed->SetTGParameter (Param, nValue, nTG);
break;
case MenuEventStepUp:
nValue += rParam.Increment;
if (nValue > rParam.Maximum)
{
nValue = rParam.Maximum;
}
pUIMenu->m_pMiniDexed->SetTGParameter (Param, nValue, nTG);
break;
default:
return;
}
string TG ("TG");
TG += to_string (nTG+1);
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);
}
void CUIMenu::EditVoiceParameter (CUIMenu *pUIMenu, TMenuEvent Event)
{
unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2];
unsigned nParam = pUIMenu->m_nCurrentParameter;
const TParameter &rParam = s_VoiceParameter[nParam];
int nValue = pUIMenu->m_pMiniDexed->GetVoiceParameter (nParam, CMiniDexed::NoOP, nTG);
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
nValue -= rParam.Increment;
if (nValue < rParam.Minimum)
{
nValue = rParam.Minimum;
}
pUIMenu->m_pMiniDexed->SetVoiceParameter (nParam, nValue, CMiniDexed::NoOP, nTG);
break;
case MenuEventStepUp:
nValue += rParam.Increment;
if (nValue > rParam.Maximum)
{
nValue = rParam.Maximum;
}
pUIMenu->m_pMiniDexed->SetVoiceParameter (nParam, nValue, CMiniDexed::NoOP, nTG);
break;
default:
return;
}
string TG ("TG");
TG += to_string (nTG+1);
string Value = GetVoiceValueString (nParam, nValue);
pUIMenu->m_pUI->DisplayWrite (TG.c_str (),
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
Value.c_str (),
nValue > rParam.Minimum, nValue < rParam.Maximum);
}
void CUIMenu::EditOPParameter (CUIMenu *pUIMenu, TMenuEvent Event)
{
unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2];
unsigned nOP = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1];
unsigned nParam = pUIMenu->m_nCurrentParameter;
const TParameter &rParam = s_OPParameter[nParam];
int nValue = pUIMenu->m_pMiniDexed->GetVoiceParameter (nParam, nOP, nTG);
switch (Event)
{
case MenuEventUpdate:
break;
case MenuEventStepDown:
nValue -= rParam.Increment;
if (nValue < rParam.Minimum)
{
nValue = rParam.Minimum;
}
pUIMenu->m_pMiniDexed->SetVoiceParameter (nParam, nValue, nOP, nTG);
break;
case MenuEventStepUp:
nValue += rParam.Increment;
if (nValue > rParam.Maximum)
{
nValue = rParam.Maximum;
}
pUIMenu->m_pMiniDexed->SetVoiceParameter (nParam, nValue, nOP, nTG);
break;
default:
return;
}
string OP ("OP");
OP += to_string (nOP+1);
string Value = GetOPValueString (nParam, nValue);
pUIMenu->m_pUI->DisplayWrite (OP.c_str (),
pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name,
Value.c_str (),
nValue > rParam.Minimum, nValue < rParam.Maximum);
}
string CUIMenu::GetGlobalValueString (unsigned nParameter, int nValue)
{
string Result;
assert (nParameter < sizeof CUIMenu::s_GlobalParameter / sizeof CUIMenu::s_GlobalParameter[0]);
CUIMenu::TToString *pToString = CUIMenu::s_GlobalParameter[nParameter].ToString;
if (pToString)
{
Result = (*pToString) (nValue);
}
else
{
Result = to_string (nValue);
}
return Result;
}
string CUIMenu::GetTGValueString (unsigned nTGParameter, int nValue)
{
string Result;
assert (nTGParameter < sizeof CUIMenu::s_TGParameter / sizeof CUIMenu::s_TGParameter[0]);
CUIMenu::TToString *pToString = CUIMenu::s_TGParameter[nTGParameter].ToString;
if (pToString)
{
Result = (*pToString) (nValue);
}
else
{
Result = to_string (nValue);
}
return Result;
}
string CUIMenu::GetVoiceValueString (unsigned nVoiceParameter, int nValue)
{
string Result;
assert (nVoiceParameter < sizeof CUIMenu::s_VoiceParameter / sizeof CUIMenu::s_VoiceParameter[0]);
CUIMenu::TToString *pToString = CUIMenu::s_VoiceParameter[nVoiceParameter].ToString;
if (pToString)
{
Result = (*pToString) (nValue);
}
else
{
Result = to_string (nValue);
}
return Result;
}
string CUIMenu::GetOPValueString (unsigned nOPParameter, int nValue)
{
string Result;
assert (nOPParameter < sizeof CUIMenu::s_OPParameter / sizeof CUIMenu::s_OPParameter[0]);
CUIMenu::TToString *pToString = CUIMenu::s_OPParameter[nOPParameter].ToString;
if (pToString)
{
Result = (*pToString) (nValue);
}
else
{
Result = to_string (nValue);
}
return Result;
}
string CUIMenu::ToVolume (int nValue)
{
static const 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';
return VolumeBar;
}
string CUIMenu::ToPan (int nValue)
{
assert (CConfig::LCDColumns == 16);
static const size_t MaxChars = CConfig::LCDColumns-3;
char PanMarker[MaxChars+1] = "......:......";
unsigned nIndex = nValue * MaxChars / 127;
if (nIndex == MaxChars)
{
nIndex--;
}
PanMarker[nIndex] = '\xFF'; // 0xFF is the block character
return PanMarker;
}
string CUIMenu::ToMIDIChannel (int nValue)
{
switch (nValue)
{
case CMIDIDevice::OmniMode: return "Omni";
case CMIDIDevice::Disabled: return "Off";
default: return to_string (nValue+1);
}
}
string CUIMenu::ToAlgorithm (int nValue)
{
return to_string (nValue + 1);
}
string CUIMenu::ToOnOff (int nValue)
{
static const char *OnOff[] = {"Off", "On"};
assert ((unsigned) nValue < sizeof OnOff / sizeof OnOff[0]);
return OnOff[nValue];
}
string CUIMenu::ToLFOWaveform (int nValue)
{
static const char *Waveform[] = {"Triangle", "Saw down", "Saw up",
"Square", "Sine", "Sample/Hold"};
assert ((unsigned) nValue < sizeof Waveform / sizeof Waveform[0]);
return Waveform[nValue];
}
string CUIMenu::ToTransposeNote (int nValue)
{
nValue += NoteC3 - 24;
assert ((unsigned) nValue < sizeof s_NoteName / sizeof s_NoteName[0]);
return s_NoteName[nValue];
}
string CUIMenu::ToBreakpointNote (int nValue)
{
assert ((unsigned) nValue < sizeof s_NoteName / sizeof s_NoteName[0]);
return s_NoteName[nValue];
}
string CUIMenu::ToKeyboardCurve (int nValue)
{
static const char *Curve[] = {"-Lin", "-Exp", "+Exp", "+Lin"};
assert ((unsigned) nValue < sizeof Curve / sizeof Curve[0]);
return Curve[nValue];
}
string CUIMenu::ToOscillatorMode (int nValue)
{
static const char *Mode[] = {"Ratio", "Fixed"};
assert ((unsigned) nValue < sizeof Mode / sizeof Mode[0]);
return Mode[nValue];
}
string CUIMenu::ToOscillatorDetune (int nValue)
{
string Result;
nValue -= 7;
if (nValue > 0)
{
Result = "+" + to_string (nValue);
}
else
{
Result = to_string (nValue);
}
return Result;
}

@ -0,0 +1,133 @@
//
// uimenu.h
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// Original author of this class:
// R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _uimenu_h
#define _uimenu_h
#include <string>
class CMiniDexed;
class CUserInterface;
class CUIMenu
{
private:
static const unsigned MaxMenuDepth = 5;
public:
enum TMenuEvent
{
MenuEventUpdate,
MenuEventSelect,
MenuEventBack,
MenuEventHome,
MenuEventStepDown,
MenuEventStepUp,
MenuEventUnknown
};
public:
CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed);
void EventHandler (TMenuEvent Event);
private:
typedef void TMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event);
struct TMenuItem
{
const char *Name;
TMenuHandler *Handler;
const TMenuItem *MenuItem;
unsigned Parameter;
};
typedef std::string TToString (int nValue);
struct TParameter
{
int Minimum;
int Maximum;
int Increment;
TToString *ToString;
};
private:
static void MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event);
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 std::string GetGlobalValueString (unsigned nParameter, int nValue);
static std::string GetTGValueString (unsigned nTGParameter, int nValue);
static std::string GetVoiceValueString (unsigned nVoiceParameter, int nValue);
static std::string GetOPValueString (unsigned nOPParameter, int nValue);
static std::string ToVolume (int nValue);
static std::string ToPan (int nValue);
static std::string ToMIDIChannel (int nValue);
static std::string ToAlgorithm (int nValue);
static std::string ToOnOff (int nValue);
static std::string ToLFOWaveform (int nValue);
static std::string ToTransposeNote (int nValue);
static std::string ToBreakpointNote (int nValue);
static std::string ToKeyboardCurve (int nValue);
static std::string ToOscillatorMode (int nValue);
static std::string ToOscillatorDetune (int nValue);
private:
CUserInterface *m_pUI;
CMiniDexed *m_pMiniDexed;
const TMenuItem *m_pParentMenu;
const TMenuItem *m_pCurrentMenu;
unsigned m_nCurrentMenuItem;
unsigned m_nCurrentSelection;
unsigned m_nCurrentParameter;
const TMenuItem *m_MenuStackParent[MaxMenuDepth];
const TMenuItem *m_MenuStackMenu[MaxMenuDepth];
unsigned m_nMenuStackItem[MaxMenuDepth];
unsigned m_nMenuStackSelection[MaxMenuDepth];
unsigned m_nMenuStackParameter[MaxMenuDepth];
unsigned m_nCurrentMenuDepth;
static const TMenuItem s_MenuRoot[];
static const TMenuItem s_MainMenu[];
static const TMenuItem s_TGMenu[];
static const TMenuItem s_ReverbMenu[];
static const TMenuItem s_EditVoiceMenu[];
static const TMenuItem s_OperatorMenu[];
static const TParameter s_GlobalParameter[];
static const TParameter s_TGParameter[];
static const TParameter s_VoiceParameter[];
static const TParameter s_OPParameter[];
static const char s_NoteName[100][4];
};
#endif

@ -22,9 +22,7 @@
#include <circle/logger.h> #include <circle/logger.h>
#include <circle/string.h> #include <circle/string.h>
#include <circle/startup.h> #include <circle/startup.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <string>
#include <assert.h> #include <assert.h>
LOGMODULE ("ui"); LOGMODULE ("ui");
@ -36,18 +34,8 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag
m_pLCD (0), m_pLCD (0),
m_pLCDBuffered (0), m_pLCDBuffered (0),
m_pRotaryEncoder (0), m_pRotaryEncoder (0),
m_UIMode (UIModeVoiceSelect), m_Menu (this, pMiniDexed)
m_nTG (0)
{ {
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
m_nBank[nTG] = 0;
m_nProgram[nTG] = 0;
m_nVolume[nTG] = 0;
m_nPan[nTG] = 64;
m_nMasterTune[nTG] = 0;
m_uchMIDIChannel[nTG] = CMIDIDevice::Disabled;
}
} }
CUserInterface::~CUserInterface (void) CUserInterface::~CUserInterface (void)
@ -104,6 +92,8 @@ bool CUserInterface::Initialize (void)
LOGDBG ("Rotary encoder initialized"); LOGDBG ("Rotary encoder initialized");
} }
m_Menu.EventHandler (CUIMenu::MenuEventUpdate);
return true; return true;
} }
@ -115,166 +105,24 @@ void CUserInterface::Process (void)
} }
} }
void CUserInterface::BankSelected (unsigned nBankLSB, unsigned nTG) void CUserInterface::ParameterChanged (void)
{ {
assert (nBankLSB < 128); m_Menu.EventHandler (CUIMenu::MenuEventUpdate);
assert (nTG < CConfig::ToneGenerators);
m_nBank[nTG] = nBankLSB;
assert (m_pMiniDexed);
std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB);
// MIDI numbering starts with 0, user interface with 1
printf ("TG%u: Select voice bank %u: \"%s\"\n", nTG+1, nBankLSB+1, BankName.c_str ());
if ( m_UIMode == UIModeBankSelect
&& m_nTG == nTG)
{
CString TG;
TG.Format ("TG%u", nTG+1);
CString String;
String.Format ("%u", nBankLSB+1);
DisplayWrite (TG, "BANK", String, BankName.c_str ());
}
}
void CUserInterface::ProgramChanged (unsigned nProgram, unsigned nTG)
{
assert (nProgram < 128);
assert (nTG < CConfig::ToneGenerators);
m_nProgram[nTG] = nProgram;
nProgram++; // MIDI numbering starts with 0, user interface with 1
assert (m_pMiniDexed);
std::string VoiceName = m_pMiniDexed->GetVoiceName (nTG);
printf ("TG%u: Loading voice %u: \"%s\"\n", nTG+1, nProgram, VoiceName.c_str ());
if ( m_UIMode == UIModeVoiceSelect
&& m_nTG == nTG)
{
CString TG;
TG.Format ("TG%u", nTG+1);
CString String;
String.Format ("%u", nProgram);
DisplayWrite (TG, "VOICE", String, VoiceName.c_str ());
}
}
void CUserInterface::VolumeChanged (unsigned nVolume, unsigned nTG)
{
assert (nVolume < 128);
assert (nTG < CConfig::ToneGenerators);
m_nVolume[nTG] = nVolume;
if ( m_UIMode == UIModeVolume
&& m_nTG == nTG)
{
CString TG;
TG.Format ("TG%u", nTG+1);
char VolumeBar[CConfig::LCDColumns+1];
memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character
VolumeBar[nVolume * CConfig::LCDColumns / 127] = '\0';
DisplayWrite (TG, "VOLUME", VolumeBar);
}
} }
void CUserInterface::PanChanged (unsigned nPan, unsigned nTG) void CUserInterface::DisplayWrite (const char *pMenu, const char *pParam, const char *pValue,
bool bArrowDown, bool bArrowUp)
{ {
assert (nPan < 128);
assert (nTG < CConfig::ToneGenerators);
m_nPan[nTG] = nPan;
if ( m_UIMode == UIModePan
&& m_nTG == nTG)
{
CString TG;
TG.Format ("TG%u", nTG+1);
assert (CConfig::LCDColumns == 16);
char PanMarker[CConfig::LCDColumns] = ".......:.......";
unsigned nIndex = nPan * (CConfig::LCDColumns-1) / 127;
if (nIndex == CConfig::LCDColumns-1)
{
nIndex--;
}
PanMarker[nIndex] = '\xFF';
DisplayWrite (TG, "PAN", PanMarker);
}
}
void CUserInterface::MasterTuneChanged (int nMasterTune, unsigned nTG)
{
assert (-99 <= nMasterTune && nMasterTune <= 99);
assert (nTG < CConfig::ToneGenerators);
m_nMasterTune[nTG] = nMasterTune;
if ( m_UIMode == UIModeMasterTune
&& m_nTG == nTG)
{
CString TG;
TG.Format ("TG%u", nTG+1);
CString String;
String.Format ("%d", nMasterTune);
DisplayWrite (TG, "MASTER TUNE", "DETUNE", (const char *) String);
}
}
void CUserInterface::MIDIChannelChanged (uint8_t uchChannel, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
m_uchMIDIChannel[nTG] = uchChannel;
if ( m_UIMode == UIModeMIDI
&& m_nTG == nTG)
{
CString TG;
TG.Format ("TG%u", nTG+1);
CString String;
switch (uchChannel)
{
case CMIDIDevice::OmniMode: String = "OMNI"; break;
case CMIDIDevice::Disabled: String = "OFF"; break;
default:
String.Format ("%u", (unsigned) uchChannel+1);
break;
}
DisplayWrite (TG, "MIDI", "CHANNEL", (const char *) String);
}
}
void CUserInterface::DisplayWrite (const char *pInstance, const char *pMenu,
const char *pParam, const char *pValue)
{
assert (pInstance);
assert (pMenu); assert (pMenu);
assert (pParam); assert (pParam);
assert (pValue);
// Do not show instance, if there is only one.
if (CConfig::ToneGenerators == 1)
{
pInstance = "";
}
CString Msg ("\x1B[H"); // cursor home CString Msg ("\x1B[H"); // cursor home
// first line // first line
Msg.Append (pInstance); Msg.Append (pParam);
size_t nLen = strlen (pInstance) + strlen (pMenu); size_t nLen = strlen (pParam) + strlen (pMenu);
if (nLen < CConfig::LCDColumns) if (nLen < CConfig::LCDColumns)
{ {
for (unsigned i = CConfig::LCDColumns-nLen; i > 0; i--) for (unsigned i = CConfig::LCDColumns-nLen; i > 0; i--)
@ -286,16 +134,30 @@ void CUserInterface::DisplayWrite (const char *pInstance, const char *pMenu,
Msg.Append (pMenu); Msg.Append (pMenu);
// second line // second line
CString ParamValue (pParam); CString Value (" ");
if (pValue) if (bArrowDown)
{
Value = "\x7F"; // arrow left character
}
Value.Append (pValue);
if (bArrowUp)
{
if (Value.GetLength () < CConfig::LCDColumns-1)
{ {
ParamValue.Append ("="); for (unsigned i = CConfig::LCDColumns-Value.GetLength ()-1; i > 0; i--)
ParamValue.Append (pValue); {
Value.Append (" ");
}
}
Value.Append ("\x7E"); // arrow right character
} }
Msg.Append (ParamValue); Msg.Append (Value);
if (ParamValue.GetLength () < CConfig::LCDColumns) if (Value.GetLength () < CConfig::LCDColumns)
{ {
Msg.Append ("\x1B[K"); // clear end of line Msg.Append ("\x1B[K"); // clear end of line
} }
@ -313,31 +175,22 @@ void CUserInterface::LCDWrite (const char *pString)
void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
{ {
int nStep = 0;
switch (Event) switch (Event)
{ {
case CKY040::EventClockwise: case CKY040::EventClockwise:
nStep = 1; m_Menu.EventHandler (CUIMenu::MenuEventStepUp);
break; break;
case CKY040::EventCounterclockwise: case CKY040::EventCounterclockwise:
nStep = -1; m_Menu.EventHandler (CUIMenu::MenuEventStepDown);
break; break;
case CKY040::EventSwitchClick: case CKY040::EventSwitchClick:
m_UIMode = static_cast <TUIMode> (m_UIMode+1); m_Menu.EventHandler (CUIMenu::MenuEventSelect);
if (m_UIMode == UIModeUnknown)
{
m_UIMode = UIModeStart;
}
break; break;
case CKY040::EventSwitchDoubleClick: case CKY040::EventSwitchDoubleClick:
if (++m_nTG == CConfig::ToneGenerators) m_Menu.EventHandler (CUIMenu::MenuEventBack);
{
m_nTG = 0;
}
break; break;
case CKY040::EventSwitchHold: case CKY040::EventSwitchHold:
@ -349,81 +202,7 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
} }
else else
{ {
m_UIMode = UIModeStart; m_Menu.EventHandler (CUIMenu::MenuEventHome);
m_nTG = 0;
}
break;
default:
return;
}
switch (m_UIMode)
{
case UIModeBankSelect:
if (m_nBank[m_nTG] + nStep < 128)
{
m_pMiniDexed->BankSelectLSB (m_nBank[m_nTG] + nStep, m_nTG);
}
break;
case UIModeVoiceSelect:
if (m_nProgram[m_nTG] + nStep < 32)
{
m_pMiniDexed->ProgramChange (m_nProgram[m_nTG] + nStep, m_nTG);
}
break;
case UIModeVolume: {
const int Increment = 128 / CConfig::LCDColumns;
int nVolume = m_nVolume[m_nTG] + nStep*Increment;
if (nVolume < 0)
{
nVolume = 0;
}
else if (nVolume > 127)
{
nVolume = 127;
}
m_pMiniDexed->SetVolume (nVolume, m_nTG);
} break;
case UIModePan: {
const int Increment = 128 / CConfig::LCDColumns;
int nPan = m_nPan[m_nTG] + nStep*Increment;
if (nPan < 0)
{
nPan = 0;
}
else if (nPan > 127)
{
nPan = 127;
}
m_pMiniDexed->SetPan (nPan, m_nTG);
} break;
case UIModeMasterTune: {
int nMasterTune = m_nMasterTune[m_nTG] + nStep;
if (nMasterTune < -99)
{
nMasterTune = -99;
}
else if (nMasterTune > 99)
{
nMasterTune = 99;
}
m_pMiniDexed->SetMasterTune (nMasterTune, m_nTG);
} break;
case UIModeMIDI:
if ((uint8_t) (m_uchMIDIChannel[m_nTG] + nStep) < CMIDIDevice::ChannelUnknown)
{
m_pMiniDexed->SetMIDIChannel (m_uchMIDIChannel[m_nTG] + nStep, m_nTG);
} }
break; break;

@ -21,11 +21,11 @@
#define _userinterface_h #define _userinterface_h
#include "config.h" #include "config.h"
#include "uimenu.h"
#include <sensor/ky040.h> #include <sensor/ky040.h>
#include <display/hd44780device.h> #include <display/hd44780device.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
#include <circle/writebuffer.h> #include <circle/writebuffer.h>
#include <stdint.h>
class CMiniDexed; class CMiniDexed;
@ -39,40 +39,22 @@ public:
void Process (void); void Process (void);
void BankSelected (unsigned nBankLSB, unsigned nTG); // 0 .. 127 void ParameterChanged (void);
void ProgramChanged (unsigned nProgram, unsigned nTG); // 0 .. 127
void VolumeChanged (unsigned nVolume, unsigned nTG); // 0 .. 127
void PanChanged (unsigned nPan, unsigned nTG); // 0 .. 127
void MasterTuneChanged (int nMasterTune, unsigned nTG); // -99 .. 99
void MIDIChannelChanged (uint8_t uchChannel, unsigned nTG);
private: // Write to display in this format:
// Print to display in this format:
// +----------------+ // +----------------+
// |INSTANCE MENU| // |PARAM MENU|
// |PARAM[=VALUE] | // |[<]VALUE [>]|
// +----------------+ // +----------------+
void DisplayWrite (const char *pInstance, const char *pMenu, void DisplayWrite (const char *pMenu, const char *pParam, const char *pValue,
const char *pParam, const char *pValue = nullptr); bool bArrowDown, bool bArrowUp);
private:
void LCDWrite (const char *pString); // Print to optional HD44780 display void LCDWrite (const char *pString); // Print to optional HD44780 display
void EncoderEventHandler (CKY040::TEvent Event); void EncoderEventHandler (CKY040::TEvent Event);
static void EncoderEventStub (CKY040::TEvent Event, void *pParam); static void EncoderEventStub (CKY040::TEvent Event, void *pParam);
private:
enum TUIMode
{
UIModeStart,
UIModeVoiceSelect = UIModeStart,
UIModeBankSelect,
UIModeVolume,
UIModePan,
UIModeMasterTune,
UIModeMIDI,
UIModeUnknown
};
private: private:
CMiniDexed *m_pMiniDexed; CMiniDexed *m_pMiniDexed;
CGPIOManager *m_pGPIOManager; CGPIOManager *m_pGPIOManager;
@ -83,15 +65,7 @@ private:
CKY040 *m_pRotaryEncoder; CKY040 *m_pRotaryEncoder;
TUIMode m_UIMode; CUIMenu m_Menu;
unsigned m_nTG;
unsigned m_nBank[CConfig::ToneGenerators];
unsigned m_nProgram[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators];
unsigned m_nPan[CConfig::ToneGenerators];
int m_nMasterTune[CConfig::ToneGenerators];
uint8_t m_uchMIDIChannel[CConfig::ToneGenerators];
}; };
#endif #endif

Loading…
Cancel
Save