From a2343805390070e0589dbec8ee9f69682650d8fc Mon Sep 17 00:00:00 2001 From: diyelectromusic <68612569+diyelectromusic@users.noreply.github.com> Date: Sat, 27 May 2023 11:05:04 +0100 Subject: [PATCH] Add configuration option for Program Change to act on Voices within a TG or Performances. --- src/config.cpp | 6 +++ src/config.h | 2 + src/mididevice.cpp | 2 +- src/midipin.h | 1 + src/minidexed.cpp | 41 +++++++++++++++- src/minidexed.h | 2 + src/minidexed.ini | 45 ++++++++++++++---- src/uibuttons.cpp | 2 +- src/uimenu.cpp | 116 ++++++++++++++++++++++++++++----------------- 9 files changed, 161 insertions(+), 56 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index d268217..81790f8 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -139,6 +139,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_bPerformanceProgramChange = m_Properties.GetNumber ("PerformanceProgramChange", 0) != 0; } const char *CConfig::GetSoundDevice (void) const @@ -475,3 +476,8 @@ bool CConfig::GetPerformanceSelectToLoad (void) const { return m_bPerformanceSelectToLoad; } + +bool CConfig::GetPerformanceProgramChange (void) const +{ + return m_bPerformanceProgramChange; +} diff --git a/src/config.h b/src/config.h index d10808f..1010003 100644 --- a/src/config.h +++ b/src/config.h @@ -162,6 +162,7 @@ public: // Load performance mode. 0 for load just rotating encoder, 1 load just when Select is pushed bool GetPerformanceSelectToLoad (void) const; + bool GetPerformanceProgramChange (void) const; private: CPropertiesFatFsFile m_Properties; @@ -243,6 +244,7 @@ private: bool m_bMIDIDumpEnabled; bool m_bProfileEnabled; bool m_bPerformanceSelectToLoad; + bool m_bPerformanceProgramChange; }; #endif diff --git a/src/mididevice.cpp b/src/mididevice.cpp index e1f983c..f4ccf6e 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -338,7 +338,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign case MIDI_PROGRAM_CHANGE: // do program change only if enabled in config if( m_pConfig->GetMIDIRXProgramChange() ) - m_pSynthesizer->ProgramChange (pMessage[1], nTG); + m_pSynthesizer->ProgramChangePerformance (pMessage[1], nTG); break; case MIDI_PITCH_BEND: { diff --git a/src/midipin.h b/src/midipin.h index d703092..7fa3f1d 100644 --- a/src/midipin.h +++ b/src/midipin.h @@ -24,6 +24,7 @@ #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 diff --git a/src/minidexed.cpp b/src/minidexed.cpp index fafeff8..4e4ba11 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -412,6 +412,8 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) { assert (m_pConfig); + + // Program Change messages change voice on specified TG unsigned nBankOffset; bool bPCAcrossBanks = m_pConfig->GetExpandPCAcrossBanks(); @@ -458,6 +460,39 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) m_UI.ParameterChanged (); } +void CMiniDexed::ProgramChangePerformance (unsigned nProgram, unsigned nTG) +{ + assert (m_pConfig); + + if (m_pConfig-GetPerformanceProgramChange()) + { + // Program Change messages change Performances. + // + // Only pay attention to PCs received on the MIDI channel + // associated with TG1. + // + // Note: as performances store the MIDI channnel, if + // the MIDI channel for TG1 changes, then further + // PCs will only be actioned on the new channel number... + if (nTG == 0) + { + unsigned nLastPerformance = m_PerformanceConfig.GetLastPerformance(); + + // GetLastPerformance actually returns 1-indexed, number of performances + if (nProgram < nLastPerformance - 1) + { + SetNewPerformance(nProgram); + } + } + } + else + { + ProgramChange(nProgram, nTG); + } + + m_UI.ParameterChanged (); +} + void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) { nVolume=constrain((int)nVolume,0,127); @@ -1431,7 +1466,11 @@ unsigned CMiniDexed::GetLastPerformance() return m_PerformanceConfig.GetLastPerformance(); } - +bool CMiniDexed::GetPerformanceProgramChange (void) +{ + assert (m_pConfig); + return m_pConfig->GetPerformanceProgramChange(); +} unsigned CMiniDexed::GetActualPerformanceID() { diff --git a/src/minidexed.h b/src/minidexed.h index e49e7de..315cc15 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, unsigned nTG); void SetVolume (unsigned nVolume, unsigned nTG); void SetPan (unsigned nPan, unsigned nTG); // 0 .. 127 void SetMasterTune (int nMasterTune, unsigned nTG); // -99 .. 99 @@ -125,6 +126,7 @@ public: bool DoSetNewPerformance (void); bool GetPerformanceSelectToLoad(void); bool SavePerformance (bool bSaveAsDeault); + bool GetPerformanceProgramChange (void); // Must match the order in CUIMenu::TParameter enum TParameter diff --git a/src/minidexed.ini b/src/minidexed.ini index 27d622c..a423666 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -18,7 +18,20 @@ MIDIRXProgramChange=1 IgnoreAllNotesOff=0 MIDIAutoVoiceDumpOnPC=1 HeaderlessSysExVoices=0 +# Program Change mode +# 0 = Only recognise Program Change 0-31. +# 1 = Support 0-127 across four consecutive banks. +# NB: Ignored if PerformanceProgramChange=1 ExpandPCAcrossBanks=1 +# Program Change action: +# 0 = Select voices +# 1 = Select performances +# +# NB: If Perfromances, then only reacts to Program Change +# messages received on the MIDI channel for TG1. +# Warning: Make sure all perfromances use the same MIDI +# channel for TG1 and that MIDI is enabled. +PerformanceProgramChange=0 # HD44780 LCD LCDEnabled=1 @@ -59,24 +72,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 11a02c2..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); diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 65b41cd..7acce30 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -1203,57 +1203,85 @@ void CUIMenu::OPShortcutHandler (TMenuEvent Event) void CUIMenu::PgmUpDownHandler (TMenuEvent Event) { - // If we're on anything apart from the root menu, - // then find the current TG number. Otherwise assume TG1 (nTG=0). - unsigned nTG = 0; - if (m_MenuStackMenu[0] == s_MainMenu) { - nTG = m_nMenuStackSelection[0]; - } - assert (nTG < CConfig::ToneGenerators); - - int nPgm = m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterProgram, nTG); - - assert (Event == MenuEventPgmDown || Event == MenuEventPgmUp); - if (Event == MenuEventPgmDown) + if (m_pMiniDexed->GetPerformanceProgramChange()) { - //LOGNOTE("PgmDown"); - if (--nPgm < 0) + // 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) { - // 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); + if (nPerformance > 0) + { + m_pMiniDexed->SetNewPerformance(nPerformance-1); + LOGNOTE("Performance new=%d, last=%d", nPerformance-1, nLastPerformance); + } + } + else + { + if (nPerformance < nLastPerformance-1) + { + m_pMiniDexed->SetNewPerformance(nPerformance+1); + LOGNOTE("Performance new=%d, last=%d", nPerformance+1, nLastPerformance); + } } - 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); + // Program Up/Down acts on voices within a TG. + + // If we're on anything apart from the root menu, + // then find the current TG number. Otherwise assume TG1 (nTG=0). + unsigned nTG = 0; + if (m_MenuStackMenu[0] == s_MainMenu) { + nTG = m_nMenuStackSelection[0]; } - m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nPgm, nTG); - } + assert (nTG < CConfig::ToneGenerators); - // 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); + 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); } - if (Event == MenuEventPgmDown) { - PgmUpDownHandler (MenuEventPgmDown); + 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); + } } } } @@ -1268,9 +1296,9 @@ void CUIMenu::TGUpDownHandler (TMenuEvent Event) return; } - // If we're on anything apart from the root menu, + // 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) { + if (m_MenuStackMenu[0] == s_MainMenu && (m_pCurrentMenu == s_TGMenu) || (m_MenuStackMenu[1] == s_TGMenu)) { nTG = m_nMenuStackSelection[0]; }