From b2c95bda78ba7625b7d290813f3db4e4fb64c110 Mon Sep 17 00:00:00 2001 From: Kevin <68612569+diyelectromusic@users.noreply.github.com> Date: Sun, 13 Aug 2023 17:15:04 +0100 Subject: [PATCH] Add Program/TG Select buttons and choice for Program Change to act on Voices or Performances (#500) * Initial implementation of Program Up/Down and Tone Generator Up/Down selection and short cuts as buttons and MIDI buttons. * Add configuration option for Program Change to act on Voices within a TG or Performances. * Added PC option to Performance menu. Various bug fixes. Allow menu updating if on Performance/Load display. Swapped TG1 and TG3 in Performance 000008 so that TG1 is set to MIDI CH 1 in all Performances. * Minor fixups in formatting and so on after review. * Formatting fixups * Changed config setting to match description in original manuals: Performance Select Channel. Also now accepts 1-16 or Omni as values. * Additional fix to ensure GPIO buttons are working correctly on Voices or Performances. * Changed initialisation order for PerformanceSelectChannel to avoid asset problem on a Pi 4 and updated menu text to fit nicely on a HD44780 display. --- performance/000008_AmpUpright.ini | 20 ++-- src/config.cpp | 81 ++++++++++++++- src/config.h | 35 ++++++- src/mididevice.cpp | 23 ++++- src/midipin.h | 5 +- src/minidexed.cpp | 62 +++++++++++- src/minidexed.h | 6 +- src/minidexed.ini | 47 +++++++-- src/uibuttons.cpp | 40 ++++++-- src/uibuttons.h | 32 +++++- src/uimenu.cpp | 162 +++++++++++++++++++++++++++++- src/uimenu.h | 7 ++ src/userinterface.cpp | 30 +++++- 13 files changed, 505 insertions(+), 45 deletions(-) diff --git a/performance/000008_AmpUpright.ini b/performance/000008_AmpUpright.ini index 17016a1..3b10a92 100644 --- a/performance/000008_AmpUpright.ini +++ b/performance/000008_AmpUpright.ini @@ -1,9 +1,9 @@ BankNumber1=2 -VoiceNumber1=6 -MIDIChannel1=0 -Volume1=47 +VoiceNumber1=2 +MIDIChannel1=1 +Volume1=87 Pan1=0 -Detune1=0 +Detune1=2 Cutoff1=99 Resonance1=0 NoteLimitLow1=0 @@ -15,7 +15,7 @@ PitchBendStep1=0 PortamentoMode1=0 PortamentoGlissando1=0 PortamentoTime1=0 -VoiceData1=5F 15 00 26 5C 3E 60 00 30 05 0A 03 00 04 00 01 55 00 01 00 07 4D 48 48 22 32 62 00 00 27 00 14 00 01 00 00 04 5F 01 02 00 0A 59 3C 17 23 63 5E 00 00 2E 03 02 03 03 03 00 01 5E 01 00 00 00 5F 1C 06 23 63 59 4B 00 05 00 63 00 01 02 00 06 4F 00 05 00 09 58 5C 47 3F 63 43 5B 5A 27 0C 14 03 00 02 00 01 5A 00 01 00 05 58 1C 1B 32 63 5A 00 00 11 63 05 03 00 02 00 03 63 00 01 00 07 00 00 00 00 32 32 32 32 0A 05 01 0E 05 00 00 00 00 01 18 41 31 31 50 6E 34 4D 46 78 64 74 +VoiceData1=43 0B 0B 2A 63 58 00 00 02 00 4E 03 01 06 00 00 50 00 05 00 0E 45 0A 0D 2A 63 58 00 00 22 08 09 03 00 01 00 04 51 00 01 00 0B 4E 49 33 37 63 3A 00 00 33 0C 0B 03 03 01 00 02 5C 01 06 09 06 40 15 07 2C 63 4F 00 00 1E 00 00 00 00 07 00 03 63 00 01 00 08 42 0B 00 27 63 4F 00 00 20 00 09 00 00 03 00 01 4C 00 01 01 00 40 15 07 2C 63 4F 00 00 1E 00 00 00 00 07 00 02 63 00 01 00 03 63 63 63 63 32 32 32 32 08 07 01 23 00 00 00 01 00 02 18 50 69 61 6E 6F 42 6F 20 31 5C 55 MonoMode1=0 ModulationWheelRange1=99 ModulationWheelTarget1=1 @@ -53,11 +53,11 @@ BreathControlTarget2=0 AftertouchRange2=99 AftertouchTarget2=0 BankNumber3=2 -VoiceNumber3=2 -MIDIChannel3=1 -Volume3=87 +VoiceNumber3=6 +MIDIChannel3=0 +Volume3=47 Pan3=0 -Detune3=2 +Detune3=0 Cutoff3=99 Resonance3=0 NoteLimitLow3=0 @@ -69,7 +69,7 @@ PitchBendStep3=0 PortamentoMode3=0 PortamentoGlissando3=0 PortamentoTime3=0 -VoiceData3=43 0B 0B 2A 63 58 00 00 02 00 4E 03 01 06 00 00 50 00 05 00 0E 45 0A 0D 2A 63 58 00 00 22 08 09 03 00 01 00 04 51 00 01 00 0B 4E 49 33 37 63 3A 00 00 33 0C 0B 03 03 01 00 02 5C 01 06 09 06 40 15 07 2C 63 4F 00 00 1E 00 00 00 00 07 00 03 63 00 01 00 08 42 0B 00 27 63 4F 00 00 20 00 09 00 00 03 00 01 4C 00 01 01 00 40 15 07 2C 63 4F 00 00 1E 00 00 00 00 07 00 02 63 00 01 00 03 63 63 63 63 32 32 32 32 08 07 01 23 00 00 00 01 00 02 18 50 69 61 6E 6F 42 6F 20 31 5C 55 +VoiceData3=5F 15 00 26 5C 3E 60 00 30 05 0A 03 00 04 00 01 55 00 01 00 07 4D 48 48 22 32 62 00 00 27 00 14 00 01 00 00 04 5F 01 02 00 0A 59 3C 17 23 63 5E 00 00 2E 03 02 03 03 03 00 01 5E 01 00 00 00 5F 1C 06 23 63 59 4B 00 05 00 63 00 01 02 00 06 4F 00 05 00 09 58 5C 47 3F 63 43 5B 5A 27 0C 14 03 00 02 00 01 5A 00 01 00 05 58 1C 1B 32 63 5A 00 00 11 63 05 03 00 02 00 03 63 00 01 00 07 00 00 00 00 32 32 32 32 0A 05 01 0E 05 00 00 00 00 01 18 41 31 31 50 6E 34 4D 46 78 64 74 MonoMode3=0 ModulationWheelRange3=99 ModulationWheelTarget3=1 diff --git a/src/config.cpp b/src/config.cpp index 9a64f50..d06a84a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -119,6 +119,16 @@ void CConfig::Load (void) m_nDoubleClickTimeout = m_Properties.GetNumber ("DoubleClickTimeout", 400); m_nLongPressTimeout = m_Properties.GetNumber ("LongPressTimeout", 600); + m_nButtonPinPgmUp = m_Properties.GetNumber ("ButtonPinPgmUp", 0); + m_nButtonPinPgmDown = m_Properties.GetNumber ("ButtonPinPgmDown", 0); + m_nButtonPinTGUp = m_Properties.GetNumber ("ButtonPinTGUp", 0); + m_nButtonPinTGDown = m_Properties.GetNumber ("ButtonPinTGDown", 0); + + m_ButtonActionPgmUp = m_Properties.GetString ("ButtonActionPgmUp", ""); + m_ButtonActionPgmDown = m_Properties.GetString ("ButtonActionPgmDown", ""); + m_ButtonActionTGUp = m_Properties.GetString ("ButtonActionTGUp", ""); + m_ButtonActionTGDown = m_Properties.GetString ("ButtonActionTGDown", ""); + m_nMIDIButtonCh = m_Properties.GetNumber ("MIDIButtonCh", 0); m_nMIDIButtonNotes = m_Properties.GetNumber ("MIDIButtonNotes", 0); m_nMIDIButtonPrev = m_Properties.GetNumber ("MIDIButtonPrev", 0); @@ -127,6 +137,11 @@ void CConfig::Load (void) m_nMIDIButtonSelect = m_Properties.GetNumber ("MIDIButtonSelect", 0); m_nMIDIButtonHome = m_Properties.GetNumber ("MIDIButtonHome", 0); + m_nMIDIButtonPgmUp = m_Properties.GetNumber ("MIDIButtonPgmUp", 0); + m_nMIDIButtonPgmDown = m_Properties.GetNumber ("MIDIButtonPgmDown", 0); + m_nMIDIButtonTGUp = m_Properties.GetNumber ("MIDIButtonTGUp", 0); + m_nMIDIButtonTGDown = m_Properties.GetNumber ("MIDIButtonTGDown", 0); + m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0; m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 10); m_nEncoderPinData = m_Properties.GetNumber ("EncoderPinData", 9); @@ -134,6 +149,7 @@ void CConfig::Load (void) m_bMIDIDumpEnabled = m_Properties.GetNumber ("MIDIDumpEnabled", 0) != 0; m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; m_bPerformanceSelectToLoad = m_Properties.GetNumber ("PerformanceSelectToLoad", 1) != 0; + m_bPerformanceSelectChannel = m_Properties.GetNumber ("PerformanceSelectChannel", 0); } const char *CConfig::GetSoundDevice (void) const @@ -351,6 +367,46 @@ unsigned CConfig::GetLongPressTimeout (void) const return m_nLongPressTimeout; } +unsigned CConfig::GetButtonPinPgmUp (void) const +{ + return m_nButtonPinPgmUp; +} + +unsigned CConfig::GetButtonPinPgmDown (void) const +{ + return m_nButtonPinPgmDown; +} + +unsigned CConfig::GetButtonPinTGUp (void) const +{ + return m_nButtonPinTGUp; +} + +unsigned CConfig::GetButtonPinTGDown (void) const +{ + return m_nButtonPinTGDown; +} + +const char *CConfig::GetButtonActionPgmUp (void) const +{ + return m_ButtonActionPgmUp.c_str(); +} + +const char *CConfig::GetButtonActionPgmDown (void) const +{ + return m_ButtonActionPgmDown.c_str(); +} + +const char *CConfig::GetButtonActionTGUp (void) const +{ + return m_ButtonActionTGUp.c_str(); +} + +const char *CConfig::GetButtonActionTGDown (void) const +{ + return m_ButtonActionTGDown.c_str(); +} + unsigned CConfig::GetMIDIButtonCh (void) const { return m_nMIDIButtonCh; @@ -386,6 +442,26 @@ unsigned CConfig::GetMIDIButtonHome (void) const return m_nMIDIButtonHome; } +unsigned CConfig::GetMIDIButtonPgmUp (void) const +{ + return m_nMIDIButtonPgmUp; +} + +unsigned CConfig::GetMIDIButtonPgmDown (void) const +{ + return m_nMIDIButtonPgmDown; +} + +unsigned CConfig::GetMIDIButtonTGUp (void) const +{ + return m_nMIDIButtonTGUp; +} + +unsigned CConfig::GetMIDIButtonTGDown (void) const +{ + return m_nMIDIButtonTGDown; +} + bool CConfig::GetEncoderEnabled (void) const { return m_bEncoderEnabled; @@ -416,4 +492,7 @@ bool CConfig::GetPerformanceSelectToLoad (void) const return m_bPerformanceSelectToLoad; } - +unsigned CConfig::GetPerformanceSelectChannel (void) const +{ + return m_bPerformanceSelectChannel; +} diff --git a/src/config.h b/src/config.h index c34cdf5..c088461 100644 --- a/src/config.h +++ b/src/config.h @@ -123,6 +123,19 @@ public: unsigned GetDoubleClickTimeout (void) const; unsigned GetLongPressTimeout (void) const; + // GPIO Button Program and TG Selection + // GPIO pin numbers are chip numbers, not header positions + unsigned GetButtonPinPgmUp (void) const; + unsigned GetButtonPinPgmDown (void) const; + unsigned GetButtonPinTGUp (void) const; + unsigned GetButtonPinTGDown (void) const; + + // Action type for buttons: "click", "doubleclick", "longpress", "" + const char *GetButtonActionPgmUp (void) const; + const char *GetButtonActionPgmDown (void) const; + const char *GetButtonActionTGUp (void) const; + const char *GetButtonActionTGDown (void) const; + // MIDI Button Navigation unsigned GetMIDIButtonCh (void) const; unsigned GetMIDIButtonNotes (void) const; @@ -131,6 +144,12 @@ public: unsigned GetMIDIButtonBack (void) const; unsigned GetMIDIButtonSelect (void) const; unsigned GetMIDIButtonHome (void) const; + + // MIDI Button Program and TG Selection + unsigned GetMIDIButtonPgmUp (void) const; + unsigned GetMIDIButtonPgmDown (void) const; + unsigned GetMIDIButtonTGUp (void) const; + unsigned GetMIDIButtonTGDown (void) const; // KY-040 Rotary Encoder // GPIO pin numbers are chip numbers, not header positions @@ -144,6 +163,7 @@ public: // Load performance mode. 0 for load just rotating encoder, 1 load just when Select is pushed bool GetPerformanceSelectToLoad (void) const; + unsigned GetPerformanceSelectChannel (void) const; private: CPropertiesFatFsFile m_Properties; @@ -189,12 +209,20 @@ private: unsigned m_nButtonPinSelect; unsigned m_nButtonPinHome; unsigned m_nButtonPinShortcut; + unsigned m_nButtonPinPgmUp; + unsigned m_nButtonPinPgmDown; + unsigned m_nButtonPinTGUp; + unsigned m_nButtonPinTGDown; std::string m_ButtonActionPrev; std::string m_ButtonActionNext; std::string m_ButtonActionBack; std::string m_ButtonActionSelect; std::string m_ButtonActionHome; + std::string m_ButtonActionPgmUp; + std::string m_ButtonActionPgmDown; + std::string m_ButtonActionTGUp; + std::string m_ButtonActionTGDown; unsigned m_nDoubleClickTimeout; unsigned m_nLongPressTimeout; @@ -206,6 +234,10 @@ private: unsigned m_nMIDIButtonBack; unsigned m_nMIDIButtonSelect; unsigned m_nMIDIButtonHome; + unsigned m_nMIDIButtonPgmUp; + unsigned m_nMIDIButtonPgmDown; + unsigned m_nMIDIButtonTGUp; + unsigned m_nMIDIButtonTGDown; bool m_bEncoderEnabled; unsigned m_nEncoderPinClock; @@ -214,8 +246,7 @@ private: bool m_bMIDIDumpEnabled; bool m_bProfileEnabled; bool m_bPerformanceSelectToLoad; - - + unsigned m_bPerformanceSelectChannel; }; #endif diff --git a/src/mididevice.cpp b/src/mididevice.cpp index e1f983c..5f231d6 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -191,10 +191,25 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign case MIDI_NOTE_ON: if (nLength < 3) { - break; + break; } m_pUI->UIMIDICmdHandler (ucChannel, ucStatus & 0xF0, pMessage[1], pMessage[2]); break; + case MIDI_PROGRAM_CHANGE: + // Check for performance PC messages + if( m_pConfig->GetMIDIRXProgramChange() ) + { + unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel(); + if( nPerfCh != Disabled) + { + if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode)) + { + //printf("Performance Select Channel %d\n", nPerfCh); + m_pSynthesizer->ProgramChangePerformance (pMessage[1]); + } + } + } + break; } // Process MIDI for each Tone Generator @@ -336,9 +351,11 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign break; case MIDI_PROGRAM_CHANGE: - // do program change only if enabled in config - if( m_pConfig->GetMIDIRXProgramChange() ) + // do program change only if enabled in config and not in "Performance Select Channel" mode + if( m_pConfig->GetMIDIRXProgramChange() && ( m_pSynthesizer->GetPerformanceSelectChannel() == Disabled) ) { + //printf("Program Change to %d (%d)\n", ucChannel, m_pSynthesizer->GetPerformanceSelectChannel()); m_pSynthesizer->ProgramChange (pMessage[1], nTG); + } break; case MIDI_PITCH_BEND: { diff --git a/src/midipin.h b/src/midipin.h index deb2ca8..7fa3f1d 100644 --- a/src/midipin.h +++ b/src/midipin.h @@ -24,11 +24,12 @@ #include // MIDI CC numbers go 0 to 127. +// NB: 0 is treated as "unused" so CC=0 won't work // Normal GPIO pins are below 100. // So use a "pin number" of 128 + MIDI CC message for a "MIDI Pin" #define MIDI_PINS 128 -#define ccToMidiPin(c) ((c)+MIDI_PINS) -#define MidiPinToCC(p) ((p)-MIDI_PINS) +#define ccToMidiPin(c) (((c)==0)?0:((c)+MIDI_PINS)) +#define MidiPinToCC(p) (((p)>=MIDI_PINS)?((p)-MIDI_PINS):0) #define isMidiPin(p) (((p)>=MIDI_PINS)?1:0) class CMIDIPin diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 83de52d..1ddf250 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -159,6 +159,8 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, // END setup reverb SetParameter (ParameterCompressorEnable, 1); + + SetPerformanceSelectChannel(m_pConfig->GetPerformanceSelectChannel()); }; bool CMiniDexed::Initialize (void) @@ -179,6 +181,20 @@ bool CMiniDexed::Initialize (void) m_bUseSerial = true; } + + if (m_pConfig->GetMIDIRXProgramChange()) + { + int nPerfCh = GetParameter(ParameterPerformanceSelectChannel); + if (nPerfCh == CMIDIDevice::Disabled) { + LOGNOTE("Program Change: Enabled for Voices"); + } else if (nPerfCh == CMIDIDevice::OmniMode) { + LOGNOTE("Program Change: Enabled for Performances (Omni)"); + } else { + LOGNOTE("Program Change: Enabled for Performances (CH %d)", nPerfCh+1); + } + } else { + LOGNOTE("Program Change: Disabled"); + } for (unsigned i = 0; i < CConfig::ToneGenerators; i++) { @@ -459,6 +475,22 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) m_UI.ParameterChanged (); } +void CMiniDexed::ProgramChangePerformance (unsigned nProgram) +{ + if (m_nParameter[ParameterPerformanceSelectChannel] != CMIDIDevice::Disabled) + { + // Program Change messages change Performances. + unsigned nLastPerformance = m_PerformanceConfig.GetLastPerformance(); + + // GetLastPerformance actually returns 1-indexed, number of performances + if (nProgram < nLastPerformance - 1) + { + SetNewPerformance(nProgram); + } + m_UI.ParameterChanged (); + } +} + void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) { nVolume=constrain((int)nVolume,0,127); @@ -755,6 +787,10 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) m_ReverbSpinLock.Release (); break; + case ParameterPerformanceSelectChannel: + // Nothing more to do + break; + default: assert (0); break; @@ -1110,6 +1146,30 @@ void CMiniDexed::ProcessSound (void) #endif +unsigned CMiniDexed::GetPerformanceSelectChannel (void) +{ + // Stores and returns Select Channel using MIDI Device Channel definitions + return (unsigned) GetParameter (ParameterPerformanceSelectChannel); +} + +void CMiniDexed::SetPerformanceSelectChannel (unsigned uCh) +{ + // Turns a configuration setting to MIDI Device Channel definitions + // Mirrors the logic in Performance Config for handling MIDI channel configuration + if (uCh == 0) + { + SetParameter (ParameterPerformanceSelectChannel, CMIDIDevice::Disabled); + } + else if (uCh < CMIDIDevice::Channels) + { + SetParameter (ParameterPerformanceSelectChannel, uCh - 1); + } + else + { + SetParameter (ParameterPerformanceSelectChannel, CMIDIDevice::OmniMode); + } +} + bool CMiniDexed::SavePerformance (bool bSaveAsDeault) { m_bSavePerformance = true; @@ -1432,8 +1492,6 @@ unsigned CMiniDexed::GetLastPerformance() return m_PerformanceConfig.GetLastPerformance(); } - - unsigned CMiniDexed::GetActualPerformanceID() { return m_PerformanceConfig.GetActualPerformanceID(); diff --git a/src/minidexed.h b/src/minidexed.h index e49e7de..1aa3096 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -67,6 +67,7 @@ public: void BankSelectMSB (unsigned nBankMSB, unsigned nTG); void BankSelectLSB (unsigned nBankLSB, unsigned nTG); void ProgramChange (unsigned nProgram, unsigned nTG); + void ProgramChangePerformance (unsigned nProgram); void SetVolume (unsigned nVolume, unsigned nTG); void SetPan (unsigned nPan, unsigned nTG); // 0 .. 127 void SetMasterTune (int nMasterTune, unsigned nTG); // -99 .. 99 @@ -125,7 +126,9 @@ public: bool DoSetNewPerformance (void); bool GetPerformanceSelectToLoad(void); bool SavePerformance (bool bSaveAsDeault); - + unsigned GetPerformanceSelectChannel (void); + void SetPerformanceSelectChannel (unsigned uCh); + // Must match the order in CUIMenu::TParameter enum TParameter { @@ -137,6 +140,7 @@ public: ParameterReverbLowPass, ParameterReverbDiffusion, ParameterReverbLevel, + ParameterPerformanceSelectChannel, ParameterUnknown }; diff --git a/src/minidexed.ini b/src/minidexed.ini index f58b44c..cd5ea1a 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -16,11 +16,24 @@ EngineType=1 # MIDI MIDIBaudRate=31250 #MIDIThru=umidi1,ttyS1 -MIDIRXProgramChange=1 IgnoreAllNotesOff=0 MIDIAutoVoiceDumpOnPC=1 HeaderlessSysExVoices=0 +# Program Change enable +# 0 = Ignore all Program Change messages. +# 1 = Respond to Program Change messages. +MIDIRXProgramChange=1 +# Program Change mode +# 0 = Only recognise Program Change 0-31. +# 1 = Support 0-127 across four consecutive banks. +# NB: Only relevant if PerformanceSelectChannel=0 ExpandPCAcrossBanks=1 +# Program Change action: +# 0 = Program Change messages select voices on the channel associated with each TG. +# 1-16 = Program Change messages on this channel select performances. +# >16 = Program Change messages on ANY channel select performances. +# NB: In performance mode, all Program Change messages on other channels are ignored. +PerformanceSelectChannel=0 # HD44780 LCD LCDEnabled=1 @@ -61,24 +74,38 @@ ButtonActionHome=doubleclick ButtonPinShortcut=11 # (Shortcut doesn't have an action) +# GPIO Program/TG Selection +# Any buttons set to 0 will be ignored +ButtonPinPgmUp=0 +ButtonActionPgmUp= +ButtonPinPgmDown=0 +ButtonActionPgmDown= +ButtonPinTGUp=0 +ButtonActionTGUp= +ButtonPinTGDown=0 +ButtonActionTGDown= + # Timeouts in milliseconds for double click and long press DoubleClickTimeout=400 LongPressTimeout=400 # MIDI Button Navigation -# Specify MIDI CC to act as a button +# Specify MIDI CC to act as a button (0 = ununsed, so don't use CC 0) # NB: Off < 64 < ON # CC channel: 0=OFF; 1-16 MIDI Ch; >16 Omni -# If MIDIButtonNotes>0 then treat MIDIButton -# numbers as MIDI Note numbers note CC numbers. +# If MIDIButtonNotes>0 then treat MIDIButton numbers as MIDI +# Note numbers, triggered with NoteOn/NoteOff, not CC numbers. MIDIButtonCh=0 MIDIButtonNotes=0 -MIDIButtonNotes=1 -MIDIButtonPrev=00 -MIDIButtonNext=02 -MIDIButtonBack=03 -MIDIButtonSelect=04 -MIDIButtonHome=06 +MIDIButtonPrev=0 +MIDIButtonNext=0 +MIDIButtonBack=0 +MIDIButtonSelect=0 +MIDIButtonHome=0 +MIDIButtonPgmUp=0 +MIDIButtonPgmDown=0 +MIDIButtonTGUp=0 +MIDIButtonTGDown=0 # KY-040 Rotary Encoder EncoderEnabled=1 diff --git a/src/uibuttons.cpp b/src/uibuttons.cpp index e3e64f8..0e361ae 100644 --- a/src/uibuttons.cpp +++ b/src/uibuttons.cpp @@ -76,7 +76,7 @@ boolean CUIButton::Initialize (unsigned pinNumber, unsigned doubleClickTimeout, { if (isMidiPin(m_pinNumber)) { - LOGDBG("MIDI Button on pin: %d (%x)", m_pinNumber, m_pinNumber); + LOGDBG("MIDI Button on msg: %d (%x)", MidiPinToCC(m_pinNumber), MidiPinToCC(m_pinNumber)); m_midipin = new CMIDIPin (m_pinNumber); } else { LOGDBG("GPIO Button on pin: %d (%x)", m_pinNumber, m_pinNumber); @@ -263,8 +263,13 @@ CUIButtons::CUIButtons ( unsigned backPin, const char *backAction, unsigned selectPin, const char *selectAction, unsigned homePin, const char *homeAction, + unsigned pgmUpPin, const char *pgmUpAction, + unsigned pgmDownPin, const char *pgmDownAction, + unsigned TGUpPin, const char *TGUpAction, + unsigned TGDownPin, const char *TGDownAction, unsigned doubleClickTimeout, unsigned longPressTimeout, - unsigned notesMidi, unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi + unsigned notesMidi, unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi, + unsigned pgmUpMidi, unsigned pgmDownMidi, unsigned TGUpMidi, unsigned TGDownMidi ) : m_doubleClickTimeout(doubleClickTimeout), m_longPressTimeout(longPressTimeout), @@ -278,12 +283,24 @@ CUIButtons::CUIButtons ( m_selectAction(CUIButton::triggerTypeFromString(selectAction)), m_homePin(homePin), m_homeAction(CUIButton::triggerTypeFromString(homeAction)), + m_pgmUpPin(pgmUpPin), + m_pgmUpAction(CUIButton::triggerTypeFromString(pgmUpAction)), + m_pgmDownPin(pgmDownPin), + m_pgmDownAction(CUIButton::triggerTypeFromString(pgmDownAction)), + m_TGUpPin(TGUpPin), + m_TGUpAction(CUIButton::triggerTypeFromString(TGUpAction)), + m_TGDownPin(TGDownPin), + m_TGDownAction(CUIButton::triggerTypeFromString(TGDownAction)), m_notesMidi(notesMidi), m_prevMidi(ccToMidiPin(prevMidi)), m_nextMidi(ccToMidiPin(nextMidi)), m_backMidi(ccToMidiPin(backMidi)), m_selectMidi(ccToMidiPin(selectMidi)), m_homeMidi(ccToMidiPin(homeMidi)), + m_pgmUpMidi(ccToMidiPin(pgmUpMidi)), + m_pgmDownMidi(ccToMidiPin(pgmDownMidi)), + m_TGUpMidi(ccToMidiPin(TGUpMidi)), + m_TGDownMidi(ccToMidiPin(TGDownMidi)), m_eventHandler (0), m_lastTick (0) { @@ -315,14 +332,16 @@ boolean CUIButtons::Initialize (void) // longpress. We may not initialise all of the buttons. // MIDI buttons only support a single click. unsigned pins[MAX_BUTTONS] = { - m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin, - m_prevMidi, m_nextMidi, m_backMidi, m_selectMidi, m_homeMidi + m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin, m_pgmUpPin, m_pgmDownPin, m_TGUpPin, m_TGDownPin, + m_prevMidi, m_nextMidi, m_backMidi, m_selectMidi, m_homeMidi, m_pgmUpMidi, m_pgmDownMidi, m_TGUpMidi, m_TGDownMidi }; CUIButton::BtnTrigger triggers[MAX_BUTTONS] = { // Normal buttons m_prevAction, m_nextAction, m_backAction, m_selectAction, m_homeAction, + m_pgmUpAction, m_pgmDownAction, m_TGUpAction, m_TGDownAction, // MIDI Buttons only support a single click (at present) - CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick + CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, + CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick }; CUIButton::BtnEvent events[MAX_BUTTONS] = { // Normal buttons @@ -331,12 +350,20 @@ boolean CUIButtons::Initialize (void) CUIButton::BtnEventBack, CUIButton::BtnEventSelect, CUIButton::BtnEventHome, + CUIButton::BtnEventPgmUp, + CUIButton::BtnEventPgmDown, + CUIButton::BtnEventTGUp, + CUIButton::BtnEventTGDown, // MIDI buttons CUIButton::BtnEventPrev, CUIButton::BtnEventNext, CUIButton::BtnEventBack, CUIButton::BtnEventSelect, - CUIButton::BtnEventHome + CUIButton::BtnEventHome, + CUIButton::BtnEventPgmUp, + CUIButton::BtnEventPgmDown, + CUIButton::BtnEventTGUp, + CUIButton::BtnEventTGDown }; // Setup normal GPIO buttons first @@ -352,6 +379,7 @@ boolean CUIButtons::Initialize (void) for (unsigned j=0; j using namespace std; +LOGMODULE ("uimenu"); const CUIMenu::TMenuItem CUIMenu::s_MenuRoot[] = { @@ -212,7 +213,8 @@ const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknow {0, 99, 1}, // ParameterReverbLowDamp {0, 99, 1}, // ParameterReverbLowPass {0, 99, 1}, // ParameterReverbDiffusion - {0, 99, 1} // ParameterReverbLevel + {0, 99, 1}, // ParameterReverbLevel + {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel} // ParameterPerformanceSelectChannel }; // must match CMiniDexed::TTGParameter @@ -323,7 +325,8 @@ const CUIMenu::TMenuItem CUIMenu::s_PerformanceMenu[] = { {"Load", PerformanceMenu, 0, 0}, {"Save", MenuHandler, s_SaveMenu}, - {"Delete", PerformanceMenu, 0, 1}, + {"Delete", PerformanceMenu, 0, 1}, + {"PCCH", EditGlobalParameter, 0, CMiniDexed::ParameterPerformanceSelectChannel}, {0} }; @@ -402,6 +405,16 @@ void CUIMenu::EventHandler (TMenuEvent Event) EventHandler (MenuEventUpdate); break; + case MenuEventPgmUp: + case MenuEventPgmDown: + PgmUpDownHandler(Event); + break; + + case MenuEventTGUp: + case MenuEventTGDown: + TGUpDownHandler(Event); + break; + default: (*m_pParentMenu[m_nCurrentMenuItem].Handler) (this, Event); break; @@ -604,7 +617,9 @@ void CUIMenu::EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event) return; } - string voiceName = pUIMenu->m_pMiniDexed->GetVoiceName (nTG); // Skip empty voices + // Skip empty voices. + // Use same criteria in PgmUpDownHandler() too. + string voiceName = pUIMenu->m_pMiniDexed->GetVoiceName (nTG); if (voiceName == "EMPTY " || voiceName == " " || voiceName == "----------" @@ -1188,6 +1203,147 @@ void CUIMenu::OPShortcutHandler (TMenuEvent Event) } } +void CUIMenu::PgmUpDownHandler (TMenuEvent Event) +{ + if (m_pMiniDexed->GetParameter (CMiniDexed::ParameterPerformanceSelectChannel) != CMIDIDevice::Disabled) + { + // Program Up/Down acts on performances + unsigned nLastPerformance = m_pMiniDexed->GetLastPerformance(); + unsigned nPerformance = m_pMiniDexed->GetActualPerformanceID(); + //LOGNOTE("Performance actual=%d, last=%d", nPerformance, nLastPerformance); + if (Event == MenuEventPgmDown) + { + if (nPerformance > 0) + { + m_nSelectedPerformanceID = nPerformance-1; + m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID); + //LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance); + } + } + else + { + if (nPerformance < nLastPerformance-1) + { + m_nSelectedPerformanceID = nPerformance+1; + m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID); + //LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance); + } + } + } + else + { + // Program Up/Down acts on voices within a TG. + + // If we're not in the root menu, then see if we are already in a TG menu, + // then find the current TG number. Otherwise assume TG1 (nTG=0). + unsigned nTG = 0; + if (m_MenuStackMenu[0] == s_MainMenu && (m_pCurrentMenu == s_TGMenu) || (m_MenuStackMenu[1] == s_TGMenu)) { + nTG = m_nMenuStackSelection[0]; + } + assert (nTG < CConfig::ToneGenerators); + + int nPgm = m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterProgram, nTG); + + assert (Event == MenuEventPgmDown || Event == MenuEventPgmUp); + if (Event == MenuEventPgmDown) + { + //LOGNOTE("PgmDown"); + if (--nPgm < 0) + { + // Switch down a voice bank and set to the last voice + nPgm = CSysExFileLoader::VoicesPerBank-1; + int nVB = m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG); + nVB = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankDown(nVB); + m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG); + } + m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG); + } + else + { + //LOGNOTE("PgmUp"); + if (++nPgm > (int) CSysExFileLoader::VoicesPerBank-1) + { + // Switch up a voice bank and reset to voice 0 + nPgm = 0; + int nVB = m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG); + nVB = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankUp(nVB); + m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG); + } + m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG); + } + + // Skip empty voices. + // Use same criteria in EditProgramNumber () too. + string voiceName = m_pMiniDexed->GetVoiceName (nTG); + if (voiceName == "EMPTY " + || voiceName == " " + || voiceName == "----------" + || voiceName == "~~~~~~~~~~" ) + { + if (Event == MenuEventPgmUp) { + PgmUpDownHandler (MenuEventPgmUp); + } + if (Event == MenuEventPgmDown) { + PgmUpDownHandler (MenuEventPgmDown); + } + } + } +} + +void CUIMenu::TGUpDownHandler (TMenuEvent Event) +{ + // This will update the menus to position it for the next TG up or down + unsigned nTG = 0; + + if (CConfig::ToneGenerators <= 1) { + // Nothing to do if only a single TG + return; + } + + // If we're not in the root menu, then see if we are already in a TG menu, + // then find the current TG number. Otherwise assume TG1 (nTG=0). + if (m_MenuStackMenu[0] == s_MainMenu && (m_pCurrentMenu == s_TGMenu) || (m_MenuStackMenu[1] == s_TGMenu)) { + nTG = m_nMenuStackSelection[0]; + } + + assert (nTG < CConfig::ToneGenerators); + assert (Event == MenuEventTGDown || Event == MenuEventTGUp); + if (Event == MenuEventTGDown) + { + //LOGNOTE("TGDown"); + if (nTG > 0) { + nTG--; + } + } + else + { + //LOGNOTE("TGUp"); + if (nTG < CConfig::ToneGenerators - 1) { + nTG++; + } + } + + // Set menu to the appropriate TG menu as follows: + // Top = Root + // Menu [0] = Main + // Menu [1] = TG Menu + m_pParentMenu = s_MainMenu; + m_pCurrentMenu = s_TGMenu; + m_nCurrentMenuItem = nTG; + m_nCurrentSelection = 0; + m_nCurrentParameter = nTG; + m_nCurrentMenuDepth = 1; + + // Place the main menu on the stack with Root as the parent + m_MenuStackParent[0] = s_MenuRoot; + m_MenuStackMenu[0] = s_MainMenu; + m_nMenuStackItem[0] = 0; + m_nMenuStackSelection[0] = nTG; + m_nMenuStackParameter[0] = 0; + + EventHandler (MenuEventUpdate); +} + void CUIMenu::TimerHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext) { CUIMenu *pThis = static_cast (pContext); diff --git a/src/uimenu.h b/src/uimenu.h index 78384be..2b70cfe 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -45,6 +45,10 @@ public: MenuEventStepUp, MenuEventPressAndStepDown, MenuEventPressAndStepUp, + MenuEventPgmUp, + MenuEventPgmDown, + MenuEventTGUp, + MenuEventTGDown, MenuEventUnknown }; @@ -112,6 +116,9 @@ private: void TGShortcutHandler (TMenuEvent Event); void OPShortcutHandler (TMenuEvent Event); + void PgmUpDownHandler (TMenuEvent Event); + void TGUpDownHandler (TMenuEvent Event); + static void TimerHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext); static void InputTxt (CUIMenu *pUIMenu, TMenuEvent Event); diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 0bcd931..890232f 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -115,6 +115,14 @@ bool CUserInterface::Initialize (void) m_pConfig->GetButtonActionSelect (), m_pConfig->GetButtonPinHome (), m_pConfig->GetButtonActionHome (), + m_pConfig->GetButtonPinPgmUp (), + m_pConfig->GetButtonActionPgmUp (), + m_pConfig->GetButtonPinPgmDown (), + m_pConfig->GetButtonActionPgmDown (), + m_pConfig->GetButtonPinTGUp (), + m_pConfig->GetButtonActionTGUp (), + m_pConfig->GetButtonPinTGDown (), + m_pConfig->GetButtonActionTGDown (), m_pConfig->GetDoubleClickTimeout (), m_pConfig->GetLongPressTimeout (), m_pConfig->GetMIDIButtonNotes (), @@ -122,7 +130,11 @@ bool CUserInterface::Initialize (void) m_pConfig->GetMIDIButtonNext (), m_pConfig->GetMIDIButtonBack (), m_pConfig->GetMIDIButtonSelect (), - m_pConfig->GetMIDIButtonHome () + m_pConfig->GetMIDIButtonHome (), + m_pConfig->GetMIDIButtonPgmUp (), + m_pConfig->GetMIDIButtonPgmDown (), + m_pConfig->GetMIDIButtonTGUp (), + m_pConfig->GetMIDIButtonTGDown () ); assert (m_pUIButtons); @@ -320,6 +332,22 @@ void CUserInterface::UIButtonsEventHandler (CUIButton::BtnEvent Event) m_Menu.EventHandler (CUIMenu::MenuEventHome); break; + case CUIButton::BtnEventPgmUp: + m_Menu.EventHandler (CUIMenu::MenuEventPgmUp); + break; + + case CUIButton::BtnEventPgmDown: + m_Menu.EventHandler (CUIMenu::MenuEventPgmDown); + break; + + case CUIButton::BtnEventTGUp: + m_Menu.EventHandler (CUIMenu::MenuEventTGUp); + break; + + case CUIButton::BtnEventTGDown: + m_Menu.EventHandler (CUIMenu::MenuEventTGDown); + break; + default: break; }