From 4793816d223637a58efaac9ebbd4295a61ddf60c Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Wed, 30 Mar 2022 21:45:29 +0200 Subject: [PATCH] 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. --- src/Makefile | 2 +- src/minidexed.cpp | 67 +++++++- src/minidexed.h | 19 +++ src/uimenu.cpp | 379 ++++++++++++++++++++++++++++++++++++++++++ src/uimenu.h | 110 ++++++++++++ src/userinterface.cpp | 291 ++++---------------------------- src/userinterface.h | 44 +---- 7 files changed, 613 insertions(+), 299 deletions(-) create mode 100644 src/uimenu.cpp create mode 100644 src/uimenu.h diff --git a/src/Makefile b/src/Makefile index b9f2195..15ec5cf 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 diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 4279444..f2de781 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; @@ -294,7 +298,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) @@ -305,13 +309,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) @@ -322,10 +328,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) @@ -338,7 +346,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) @@ -349,15 +357,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++) { @@ -376,7 +387,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++; } @@ -387,7 +398,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) @@ -463,6 +474,48 @@ void CMiniDexed::ControllersRefresh (unsigned nTG) m_pTG[nTG]->ControllersRefresh (); } +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; + } +} + std::string CMiniDexed::GetVoiceName (unsigned nTG) { char VoiceName[11]; diff --git a/src/minidexed.h b/src/minidexed.h index 7632544..64c06fc 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -73,6 +73,20 @@ public: void setPitchbend (int16_t value, unsigned nTG); void ControllersRefresh (unsigned nTG); + enum TTGParameter + { + TGParameterVoiceBank, + TGParameterProgram, + TGParameterVolume, + TGParameterPan, + TGParameterMasterTune, + TGParameterMIDIChannel, + TGParameterUnknown + }; + + void SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG); + int GetTGParameter (TTGParameter Parameter, unsigned nTG); + std::string GetVoiceName (unsigned nTG); private: @@ -95,8 +109,13 @@ private: CConfig *m_pConfig; 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]; diff --git a/src/uimenu.cpp b/src/uimenu.cpp new file mode 100644 index 0000000..f54fa76 --- /dev/null +++ b/src/uimenu.cpp @@ -0,0 +1,379 @@ +// +// 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; + +CUIMenu::TMenuItem CUIMenu::s_MenuRoot[] = +{ + {"MiniDexed", MenuHandler, s_MainMenu}, + {0} +}; + +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}, +#endif + {0} +}; + +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}, + {0} +}; + +// must match CMiniDexed::TTGParameter +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 +}; + +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::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]; + + int nValue = pUIMenu->m_pMiniDexed->GetTGParameter ( + (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter, nTG); + + switch (Event) + { + case MenuEventUpdate: + break; + + case MenuEventStepDown: + nValue -= s_TGParameter[pUIMenu->m_nCurrentParameter].Increment; + if (nValue < s_TGParameter[pUIMenu->m_nCurrentParameter].Minimum) + { + nValue = s_TGParameter[pUIMenu->m_nCurrentParameter].Minimum; + } + pUIMenu->m_pMiniDexed->SetTGParameter ( + (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter, nValue, nTG); + break; + + case MenuEventStepUp: + nValue += s_TGParameter[pUIMenu->m_nCurrentParameter].Increment; + if (nValue > s_TGParameter[pUIMenu->m_nCurrentParameter].Maximum) + { + nValue = s_TGParameter[pUIMenu->m_nCurrentParameter].Maximum; + } + pUIMenu->m_pMiniDexed->SetTGParameter ( + (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter, nValue, nTG); + break; + + default: + return; + } + + string TG ("TG"); + TG += to_string (nTG+1); + + string Value = GetTGValueString ( + pUIMenu->m_nCurrentParameter, + pUIMenu->m_pMiniDexed->GetTGParameter ( + (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter, nTG)); + + pUIMenu->m_pUI->DisplayWrite (TG.c_str (), + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + Value.c_str (), + nValue > s_TGParameter[pUIMenu->m_nCurrentParameter].Minimum, nValue < s_TGParameter[pUIMenu->m_nCurrentParameter].Maximum); +} + +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::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); + } +} diff --git a/src/uimenu.h b/src/uimenu.h new file mode 100644 index 0000000..d113f20 --- /dev/null +++ b/src/uimenu.h @@ -0,0 +1,110 @@ +// +// 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; + 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 EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event); + + static std::string GetTGValueString (unsigned nTGParameter, int nValue); + + static std::string ToVolume (int nValue); + static std::string ToPan (int nValue); + static std::string ToMIDIChannel (int nValue); + +private: + CUserInterface *m_pUI; + CMiniDexed *m_pMiniDexed; + + TMenuItem *m_pParentMenu; + TMenuItem *m_pCurrentMenu; + unsigned m_nCurrentMenuItem; + unsigned m_nCurrentSelection; + unsigned m_nCurrentParameter; + + TMenuItem *m_MenuStackParent[MaxMenuDepth]; + TMenuItem *m_MenuStackMenu[MaxMenuDepth]; + unsigned m_nMenuStackItem[MaxMenuDepth]; + unsigned m_nMenuStackSelection[MaxMenuDepth]; + unsigned m_nMenuStackParameter[MaxMenuDepth]; + unsigned m_nCurrentMenuDepth; + + static TMenuItem s_MenuRoot[]; + static TMenuItem s_MainMenu[]; + static TMenuItem s_TGMenu[]; + + static TParameter s_TGParameter[]; +}; + +#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