diff --git a/src/Makefile b/src/Makefile index 352dfca..fd8987a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -6,7 +6,7 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib SYNTH_DEXED_DIR = ../Synth_Dexed/src 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 \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_platervbstereo.o diff --git a/src/minidexed.cpp b/src/minidexed.cpp index d3f49c9..8e0e86d 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -55,7 +55,11 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, for (unsigned i = 0; i < CConfig::ToneGenerators; i++) { m_nVoiceBankID[i] = 0; + m_nProgram[i] = 0; + m_nVolume[i] = 100; m_nPan[i] = 64; + m_nMasterTune[i] = 0; + m_nMIDIChannel[i] = CMIDIDevice::Disabled; m_nNoteLimitLow[i] = 0; m_nNoteLimitHigh[i] = 127; @@ -111,12 +115,12 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, // BEGIN setup reverb reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); - reverb->size(0.7); - reverb->hidamp(0.5); - reverb->lodamp(0.5); - reverb->lowpass(0.3); - reverb->diffusion(0.2); - reverb->send(0.8); + SetParameter (ParameterReverbSize, 70); + SetParameter (ParameterReverbHighDamp, 50); + SetParameter (ParameterReverbLowDamp, 50); + SetParameter (ParameterReverbLowPass, 30); + SetParameter (ParameterReverbDiffusion, 20); + SetParameter (ParameterReverbSend, 80); // END setup reverb }; @@ -304,7 +308,7 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) assert (nTG < CConfig::ToneGenerators); m_nVoiceBankID[nTG] = nBankLSB; - m_UI.BankSelected (nBankLSB, nTG); + m_UI.ParameterChanged (); } void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) @@ -315,13 +319,15 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) } assert (nTG < CConfig::ToneGenerators); + m_nProgram[nTG] = nProgram; + uint8_t Buffer[156]; m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer); assert (m_pTG[nTG]); m_pTG[nTG]->loadVoiceParameters (Buffer); - m_UI.ProgramChanged (nProgram, nTG); + m_UI.ParameterChanged (); } void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) @@ -332,10 +338,12 @@ void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) } assert (nTG < CConfig::ToneGenerators); + m_nVolume[nTG] = nVolume; + assert (m_pTG[nTG]); m_pTG[nTG]->setGain (nVolume / 127.0); - m_UI.VolumeChanged (nVolume, nTG); + m_UI.ParameterChanged (); } void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) @@ -348,7 +356,7 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) assert (nTG < CConfig::ToneGenerators); m_nPan[nTG] = nPan; - m_UI.PanChanged (nPan, nTG); + m_UI.ParameterChanged (); } void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) @@ -359,15 +367,18 @@ void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) } assert (nTG < CConfig::ToneGenerators); + m_nMasterTune[nTG] = nMasterTune; + assert (m_pTG[nTG]); m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune); - m_UI.MasterTuneChanged (nMasterTune, nTG); + m_UI.ParameterChanged (); } void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) { assert (nTG < CConfig::ToneGenerators); + m_nMIDIChannel[nTG] = uchChannel; for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) { @@ -386,7 +397,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) unsigned nActiveTGs = 0; for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { - if (m_PCKeyboard.GetChannel (nTG) != CMIDIDevice::Disabled) + if (m_nMIDIChannel[nTG] != CMIDIDevice::Disabled) { nActiveTGs++; } @@ -397,7 +408,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) m_nActiveTGsLog2 = Log2[nActiveTGs]; #endif - m_UI.MIDIChannelChanged (uchChannel, nTG); + m_UI.ParameterChanged (); } void CMiniDexed::keyup (int16_t pitch, unsigned nTG) @@ -473,6 +484,116 @@ void CMiniDexed::ControllersRefresh (unsigned nTG) 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) { char VoiceName[11]; @@ -596,7 +717,9 @@ void CMiniDexed::ProcessSound (void) } // BEGIN adding reverb + m_ReverbSpinLock.Acquire (); reverb->doReverb(nFrames,SampleBuffer); + m_ReverbSpinLock.Release (); // END adding reverb if (m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) != (int) sizeof SampleBuffer) diff --git a/src/minidexed.h b/src/minidexed.h index 2e9efc8..2f991f0 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -38,6 +38,7 @@ #include #include #include +#include #include "effect_platervbstereo.h" class CMiniDexed @@ -74,6 +75,39 @@ public: void setPitchbend (int16_t value, 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); private: @@ -95,9 +129,16 @@ private: private: CConfig *m_pConfig; + int m_nParameter[ParameterUnknown]; // global (non-TG) parameters + CDexedAdapter *m_pTG[CConfig::ToneGenerators]; + unsigned m_nVoiceBankID[CConfig::ToneGenerators]; + unsigned m_nProgram[CConfig::ToneGenerators]; + unsigned m_nVolume[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators]; + int m_nMasterTune[CConfig::ToneGenerators]; + unsigned m_nMIDIChannel[CConfig::ToneGenerators]; unsigned m_nNoteLimitLow[CConfig::ToneGenerators]; unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; @@ -127,6 +168,7 @@ private: bool m_bProfileEnabled; AudioEffectPlateReverb* reverb; + CSpinLock m_ReverbSpinLock; }; #endif diff --git a/src/uimenu.cpp b/src/uimenu.cpp new file mode 100644 index 0000000..dceb7b2 --- /dev/null +++ b/src/uimenu.cpp @@ -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 +// +// 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 . +// +#include "uimenu.h" +#include "minidexed.h" +#include "mididevice.h" +#include "userinterface.h" +#include "sysexfileloader.h" +#include "config.h" +#include +#include + +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; +} diff --git a/src/uimenu.h b/src/uimenu.h new file mode 100644 index 0000000..e6d9df4 --- /dev/null +++ b/src/uimenu.h @@ -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 +// +// 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 . +// +#ifndef _uimenu_h +#define _uimenu_h + +#include + +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 diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 9e768de..793c75b 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -22,9 +22,7 @@ #include #include #include -#include #include -#include #include LOGMODULE ("ui"); @@ -36,18 +34,8 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag m_pLCD (0), m_pLCDBuffered (0), m_pRotaryEncoder (0), - m_UIMode (UIModeVoiceSelect), - m_nTG (0) + m_Menu (this, pMiniDexed) { - 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) @@ -104,6 +92,8 @@ bool CUserInterface::Initialize (void) LOGDBG ("Rotary encoder initialized"); } + m_Menu.EventHandler (CUIMenu::MenuEventUpdate); + return true; } @@ -115,166 +105,24 @@ void CUserInterface::Process (void) } } -void CUserInterface::BankSelected (unsigned nBankLSB, unsigned nTG) -{ - assert (nBankLSB < 128); - 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) +void CUserInterface::ParameterChanged (void) { - 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 ()); - } + m_Menu.EventHandler (CUIMenu::MenuEventUpdate); } -void CUserInterface::VolumeChanged (unsigned nVolume, unsigned nTG) +void CUserInterface::DisplayWrite (const char *pMenu, const char *pParam, const char *pValue, + bool bArrowDown, bool bArrowUp) { - 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) -{ - 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 (pParam); - - // Do not show instance, if there is only one. - if (CConfig::ToneGenerators == 1) - { - pInstance = ""; - } + assert (pValue); CString Msg ("\x1B[H"); // cursor home // 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) { 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); // second line - CString ParamValue (pParam); - if (pValue) + CString Value (" "); + if (bArrowDown) + { + Value = "\x7F"; // arrow left character + } + + Value.Append (pValue); + + if (bArrowUp) { - ParamValue.Append ("="); - ParamValue.Append (pValue); + if (Value.GetLength () < CConfig::LCDColumns-1) + { + for (unsigned i = CConfig::LCDColumns-Value.GetLength ()-1; i > 0; i--) + { + 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 } @@ -313,31 +175,22 @@ void CUserInterface::LCDWrite (const char *pString) void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) { - int nStep = 0; - switch (Event) { case CKY040::EventClockwise: - nStep = 1; + m_Menu.EventHandler (CUIMenu::MenuEventStepUp); break; case CKY040::EventCounterclockwise: - nStep = -1; + m_Menu.EventHandler (CUIMenu::MenuEventStepDown); break; case CKY040::EventSwitchClick: - m_UIMode = static_cast (m_UIMode+1); - if (m_UIMode == UIModeUnknown) - { - m_UIMode = UIModeStart; - } + m_Menu.EventHandler (CUIMenu::MenuEventSelect); break; case CKY040::EventSwitchDoubleClick: - if (++m_nTG == CConfig::ToneGenerators) - { - m_nTG = 0; - } + m_Menu.EventHandler (CUIMenu::MenuEventBack); break; case CKY040::EventSwitchHold: @@ -349,81 +202,7 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) } else { - m_UIMode = UIModeStart; - 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); + m_Menu.EventHandler (CUIMenu::MenuEventHome); } break; diff --git a/src/userinterface.h b/src/userinterface.h index 3602d64..6f29d5c 100644 --- a/src/userinterface.h +++ b/src/userinterface.h @@ -21,11 +21,11 @@ #define _userinterface_h #include "config.h" +#include "uimenu.h" #include #include #include #include -#include class CMiniDexed; @@ -39,40 +39,22 @@ public: void Process (void); - void BankSelected (unsigned nBankLSB, unsigned nTG); // 0 .. 127 - 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); + void ParameterChanged (void); -private: - // Print to display in this format: + // Write to display in this format: // +----------------+ - // |INSTANCE MENU| - // |PARAM[=VALUE] | + // |PARAM MENU| + // |[<]VALUE [>]| // +----------------+ - void DisplayWrite (const char *pInstance, const char *pMenu, - const char *pParam, const char *pValue = nullptr); + void DisplayWrite (const char *pMenu, const char *pParam, const char *pValue, + bool bArrowDown, bool bArrowUp); +private: void LCDWrite (const char *pString); // Print to optional HD44780 display void EncoderEventHandler (CKY040::TEvent Event); static void EncoderEventStub (CKY040::TEvent Event, void *pParam); -private: - enum TUIMode - { - UIModeStart, - UIModeVoiceSelect = UIModeStart, - UIModeBankSelect, - UIModeVolume, - UIModePan, - UIModeMasterTune, - UIModeMIDI, - UIModeUnknown - }; - private: CMiniDexed *m_pMiniDexed; CGPIOManager *m_pGPIOManager; @@ -83,15 +65,7 @@ private: CKY040 *m_pRotaryEncoder; - TUIMode m_UIMode; - - 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]; + CUIMenu m_Menu; }; #endif