From 4755c5d861d837c68866f98efa46a36fa1dc28a3 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 27 Jan 2024 17:52:22 +0000 Subject: [PATCH] Performance file handling (#581) Implements #580 * Initial update in performance file handling. This change makes the 6-digit number in the filename indicate a performance "voice number" in MiniDexed. The external filename numbers will now match any Program Change messages using the common MIDI concept of user selecting 1..128 whilst internally they are treated as 0..127. Note: in the case of performances, performance 1 (index 0) is the Default "performance.ini" file for backwards compatibility. Also note that in this version, new performances, when saved, cannot occupy free slots between other performances - they are added to the end. Even though the filename standard gives 6 digit numbers, the actual number of performances is still limited to 256. * Start of subdirectory implementation for performance banks. * Initial version with performance banks, selectable over MIDI only. * Initial implementation of performance bank switching in the UI menu. * Remove debug information, fix few bugs, including PgmUpDown handling and performance numbers out of range. * Bugfixes for legacy cases when no performance directory exists plus some extra checks for saving and deleting performances. * Remove verbose debug options (doh!) * Fix a minor off-by-one error found in review. * Bugfix - removed redundant legacy check that results in out of order performance files being skipped on load. * Fix bug in MIDI button handling commands. * Fix for issue where wrong performance is selected [L] on new save. * Suggested update to UI to show bank/performance numbers. * Make performance bank select asynchronous to MIDI and UI to stop corruptions on loading performances. * Fix an assert that should be a run-time test. * Ensure bank selection works when PCCH is not enabled, and that UI remains consistent when changing banks. --------- Co-authored-by: Kevin <68612569+diyelectromusic@users.noreply.github.com> --- src/mididevice.cpp | 28 +- src/minidexed.cpp | 169 ++++++++++-- src/minidexed.h | 21 ++ src/performanceconfig.cpp | 533 ++++++++++++++++++++++++++++++++------ src/performanceconfig.h | 33 ++- src/uimenu.cpp | 186 +++++++++++-- src/uimenu.h | 2 + src/userinterface.cpp | 3 + 8 files changed, 842 insertions(+), 133 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 5f231d6..81314ba 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -184,9 +184,35 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign else { // Perform any MiniDexed level MIDI handling before specific Tone Generators + unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel(); switch (ucType) { case MIDI_CONTROL_CHANGE: + // Check for performance PC messages + if (nPerfCh != Disabled) + { + if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode)) + { + if (pMessage[1] == MIDI_CC_BANK_SELECT_MSB) + { + m_pSynthesizer->BankSelectMSBPerformance (pMessage[2]); + } + else if (pMessage[1] == MIDI_CC_BANK_SELECT_LSB) + { + m_pSynthesizer->BankSelectLSBPerformance (pMessage[2]); + } + else + { + // Ignore any other CC messages at this time + } + } + } + if (nLength == 3) + { + m_pUI->UIMIDICmdHandler (ucChannel, ucStatus & 0xF0, pMessage[1], pMessage[2]); + } + break; + case MIDI_NOTE_OFF: case MIDI_NOTE_ON: if (nLength < 3) @@ -195,11 +221,11 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } 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)) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 71b7247..968320a 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -53,8 +53,11 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSavePerformance (false), m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), + m_bSetNewPerformanceBank (false), + m_bSetFirstPerformance (false), m_bDeletePerformance (false), - m_bLoadPerformanceBusy(false) + m_bLoadPerformanceBusy(false), + m_bLoadPerformanceBankBusy(false) { assert (m_pConfig); @@ -170,6 +173,8 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, SetParameter (ParameterCompressorEnable, 1); SetPerformanceSelectChannel(m_pConfig->GetPerformanceSelectChannel()); + + SetParameter (ParameterPerformanceBank, 0); }; bool CMiniDexed::Initialize (void) @@ -227,6 +232,7 @@ bool CMiniDexed::Initialize (void) reverb_send_mixer->gain(i,mapfloat(m_nReverbSend[i],0,99,0.0f,1.0f)); } + m_PerformanceConfig.Init(); if (m_PerformanceConfig.Load ()) { LoadPerformanceParameters(); @@ -236,12 +242,6 @@ bool CMiniDexed::Initialize (void) SetMIDIChannel (CMIDIDevice::OmniMode, 0); } - // load performances file list, and attempt to create the performance folder - if (!m_PerformanceConfig.ListPerformances()) - { - LOGERR ("Cannot create internal Performance folder, new performances can't be created"); - } - // setup and start the sound device if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) { @@ -305,14 +305,30 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) m_bSavePerformanceNewFile = false; } - if (m_bSetNewPerformance && !m_bLoadPerformanceBusy) + if (m_bSetNewPerformanceBank && !m_bLoadPerformanceBusy && !m_bLoadPerformanceBankBusy) + { + DoSetNewPerformanceBank (); + if (m_nSetNewPerformanceBankID == GetActualPerformanceBankID()) + { + m_bSetNewPerformanceBank = false; + } + + // If there is no pending SetNewPerformance already, then see if we need to find the first performance to load + // NB: If called from the UI, then there will not be a SetNewPerformance, so load the first existing one. + // If called from MIDI, there will probably be a SetNewPerformance alongside the Bank select. + if (!m_bSetNewPerformance && m_bSetFirstPerformance) + { + DoSetFirstPerformance(); + } + } + + if (m_bSetNewPerformance && !m_bSetNewPerformanceBank && !m_bLoadPerformanceBusy && !m_bLoadPerformanceBankBusy) { DoSetNewPerformance (); if (m_nSetNewPerformanceID == GetActualPerformanceID()) { m_bSetNewPerformance = false; } - } if(m_bDeletePerformance) @@ -392,6 +408,11 @@ CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) return &m_SysExFileLoader; } +CPerformanceConfig *CMiniDexed::GetPerformanceConfig (void) +{ + return &m_PerformanceConfig; +} + void CMiniDexed::BankSelect (unsigned nBank, unsigned nTG) { nBank=constrain((int)nBank,0,16383); @@ -407,6 +428,20 @@ void CMiniDexed::BankSelect (unsigned nBank, unsigned nTG) } } +void CMiniDexed::BankSelectPerformance (unsigned nBank) +{ + nBank=constrain((int)nBank,0,16383); + + if (GetPerformanceConfig ()->IsValidPerformanceBank(nBank)) + { + // Only change if we have the bank loaded + m_nVoiceBankIDPerformance = nBank; + SetNewPerformanceBank (nBank); + + m_UI.ParameterChanged (); + } +} + void CMiniDexed::BankSelectMSB (unsigned nBankMSB, unsigned nTG) { nBankMSB=constrain((int)nBankMSB,0,127); @@ -422,6 +457,12 @@ void CMiniDexed::BankSelectMSB (unsigned nBankMSB, unsigned nTG) m_nVoiceBankIDMSB[nTG] = nBankMSB; } +void CMiniDexed::BankSelectMSBPerformance (unsigned nBankMSB) +{ + nBankMSB=constrain((int)nBankMSB,0,127); + m_nVoiceBankIDMSBPerformance = nBankMSB; +} + void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) { nBankLSB=constrain((int)nBankLSB,0,127); @@ -435,6 +476,18 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) BankSelect(nBank, nTG); } +void CMiniDexed::BankSelectLSBPerformance (unsigned nBankLSB) +{ + nBankLSB=constrain((int)nBankLSB,0,127); + + unsigned nBank = m_nVoiceBankIDPerformance; + unsigned nBankMSB = m_nVoiceBankIDMSBPerformance; + nBank = (nBankMSB << 7) + nBankLSB; + + // Now should have both MSB and LSB so enable the BankSelect + BankSelectPerformance(nBank); +} + void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) { assert (m_pConfig); @@ -489,10 +542,7 @@ 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) + if (m_PerformanceConfig.IsValidPerformance(nProgram)) { SetNewPerformance(nProgram); } @@ -800,6 +850,10 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) // Nothing more to do break; + case ParameterPerformanceBank: + BankSelectPerformance(nValue); + break; + default: assert (0); break; @@ -1181,10 +1235,17 @@ void CMiniDexed::SetPerformanceSelectChannel (unsigned uCh) bool CMiniDexed::SavePerformance (bool bSaveAsDeault) { - m_bSavePerformance = true; - m_bSaveAsDeault=bSaveAsDeault; + if (m_PerformanceConfig.GetInternalFolderOk()) + { + m_bSavePerformance = true; + m_bSaveAsDeault=bSaveAsDeault; - return true; + return true; + } + else + { + return false; + } } bool CMiniDexed::DoSavePerformance (void) @@ -1501,6 +1562,16 @@ unsigned CMiniDexed::GetLastPerformance() return m_PerformanceConfig.GetLastPerformance(); } +unsigned CMiniDexed::GetPerformanceBank() +{ + return m_PerformanceConfig.GetPerformanceBank(); +} + +unsigned CMiniDexed::GetLastPerformanceBank() +{ + return m_PerformanceConfig.GetLastPerformanceBank(); +} + unsigned CMiniDexed::GetActualPerformanceID() { return m_PerformanceConfig.GetActualPerformanceID(); @@ -1511,6 +1582,16 @@ void CMiniDexed::SetActualPerformanceID(unsigned nID) m_PerformanceConfig.SetActualPerformanceID(nID); } +unsigned CMiniDexed::GetActualPerformanceBankID() +{ + return m_PerformanceConfig.GetActualPerformanceBankID(); +} + +void CMiniDexed::SetActualPerformanceBankID(unsigned nBankID) +{ + m_PerformanceConfig.SetActualPerformanceBankID(nBankID); +} + bool CMiniDexed::SetNewPerformance(unsigned nID) { m_bSetNewPerformance = true; @@ -1519,6 +1600,20 @@ bool CMiniDexed::SetNewPerformance(unsigned nID) return true; } +bool CMiniDexed::SetNewPerformanceBank(unsigned nBankID) +{ + m_bSetNewPerformanceBank = true; + m_nSetNewPerformanceBankID = nBankID; + + return true; +} + +void CMiniDexed::SetFirstPerformance(void) +{ + m_bSetFirstPerformance = true; + return; +} + bool CMiniDexed::DoSetNewPerformance (void) { m_bLoadPerformanceBusy = true; @@ -1540,6 +1635,25 @@ bool CMiniDexed::DoSetNewPerformance (void) } } +bool CMiniDexed::DoSetNewPerformanceBank (void) +{ + m_bLoadPerformanceBankBusy = true; + + unsigned nBankID = m_nSetNewPerformanceBankID; + m_PerformanceConfig.SetNewPerformanceBank(nBankID); + + m_bLoadPerformanceBankBusy = false; + return true; +} + +void CMiniDexed::DoSetFirstPerformance(void) +{ + unsigned nID = m_PerformanceConfig.FindFirstPerformance(); + SetNewPerformance(nID); + m_bSetFirstPerformance = false; + return; +} + bool CMiniDexed::SavePerformanceNewFile () { m_bSavePerformanceNewFile = m_PerformanceConfig.GetInternalFolderOk() && m_PerformanceConfig.CheckFreePerformanceSlot(); @@ -1631,6 +1745,16 @@ void CMiniDexed::SetNewPerformanceName(std::string nName) m_PerformanceConfig.SetNewPerformanceName(nName); } +bool CMiniDexed::IsValidPerformance(unsigned nID) +{ + return m_PerformanceConfig.IsValidPerformance(nID); +} + +bool CMiniDexed::IsValidPerformanceBank(unsigned nBankID) +{ + return m_PerformanceConfig.IsValidPerformanceBank(nBankID); +} + void CMiniDexed::SetVoiceName (std::string VoiceName, unsigned nTG) { assert (nTG < CConfig::ToneGenerators); @@ -1642,10 +1766,17 @@ void CMiniDexed::SetVoiceName (std::string VoiceName, unsigned nTG) bool CMiniDexed::DeletePerformance(unsigned nID) { - m_bDeletePerformance = true; - m_nDeletePerformanceID = nID; + if (m_PerformanceConfig.IsValidPerformance(nID) && m_PerformanceConfig.GetInternalFolderOk()) + { + m_bDeletePerformance = true; + m_nDeletePerformanceID = nID; - return true; + return true; + } + else + { + return false; + } } bool CMiniDexed::DoDeletePerformance(void) diff --git a/src/minidexed.h b/src/minidexed.h index 1aa3096..407d5db 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -62,10 +62,14 @@ public: #endif CSysExFileLoader *GetSysExFileLoader (void); + CPerformanceConfig *GetPerformanceConfig (void); void BankSelect (unsigned nBank, unsigned nTG); + void BankSelectPerformance (unsigned nBank); void BankSelectMSB (unsigned nBankMSB, unsigned nTG); + void BankSelectMSBPerformance (unsigned nBankMSB); void BankSelectLSB (unsigned nBankLSB, unsigned nTG); + void BankSelectLSBPerformance (unsigned nBankLSB); void ProgramChange (unsigned nProgram, unsigned nTG); void ProgramChangePerformance (unsigned nProgram); void SetVolume (unsigned nVolume, unsigned nTG); @@ -117,17 +121,27 @@ public: std::string GetPerformanceFileName(unsigned nID); std::string GetPerformanceName(unsigned nID); unsigned GetLastPerformance(); + unsigned GetPerformanceBank(); + unsigned GetLastPerformanceBank(); unsigned GetActualPerformanceID(); void SetActualPerformanceID(unsigned nID); + unsigned GetActualPerformanceBankID(); + void SetActualPerformanceBankID(unsigned nBankID); bool SetNewPerformance(unsigned nID); + bool SetNewPerformanceBank(unsigned nBankID); + void SetFirstPerformance(void); + void DoSetFirstPerformance(void); bool SavePerformanceNewFile (); bool DoSavePerformanceNewFile (void); bool DoSetNewPerformance (void); + bool DoSetNewPerformanceBank (void); bool GetPerformanceSelectToLoad(void); bool SavePerformance (bool bSaveAsDeault); unsigned GetPerformanceSelectChannel (void); void SetPerformanceSelectChannel (unsigned uCh); + bool IsValidPerformance(unsigned nID); + bool IsValidPerformanceBank(unsigned nBankID); // Must match the order in CUIMenu::TParameter enum TParameter @@ -141,6 +155,7 @@ public: ParameterReverbDiffusion, ParameterReverbLevel, ParameterPerformanceSelectChannel, + ParameterPerformanceBank, ParameterUnknown }; @@ -238,6 +253,8 @@ private: unsigned m_nVoiceBankID[CConfig::ToneGenerators]; unsigned m_nVoiceBankIDMSB[CConfig::ToneGenerators]; + unsigned m_nVoiceBankIDPerformance; + unsigned m_nVoiceBankIDMSBPerformance; unsigned m_nProgram[CConfig::ToneGenerators]; unsigned m_nVolume[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators]; @@ -305,9 +322,13 @@ private: bool m_bSavePerformanceNewFile; bool m_bSetNewPerformance; unsigned m_nSetNewPerformanceID; + bool m_bSetNewPerformanceBank; + unsigned m_nSetNewPerformanceBankID; + bool m_bSetFirstPerformance; bool m_bDeletePerformance; unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; + bool m_bLoadPerformanceBankBusy; bool m_bSaveAsDeault; }; diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index 853ccf5..8cd6275 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -28,8 +28,15 @@ LOGMODULE ("Performance"); +//#define VERBOSE_DEBUG + +#define PERFORMANCE_DIR "performance" +#define DEFAULT_PERFORMANCE_FILENAME "performance.ini" +#define DEFAULT_PERFORMANCE_NAME "Default" +#define DEFAULT_PERFORMANCE_BANK_NAME "Default" + CPerformanceConfig::CPerformanceConfig (FATFS *pFileSystem) -: m_Properties ("performance.ini", pFileSystem) +: m_Properties (DEFAULT_PERFORMANCE_FILENAME, pFileSystem) { m_pFileSystem = pFileSystem; } @@ -38,6 +45,47 @@ CPerformanceConfig::~CPerformanceConfig (void) { } +bool CPerformanceConfig::Init (void) +{ + // Check intermal performance directory exists + DIR Directory; + FRESULT Result; + //Check if internal "performance" directory exists + Result = f_opendir (&Directory, "SD:/" PERFORMANCE_DIR); + if (Result == FR_OK) + { + m_bPerformanceDirectoryExists=true; + Result = f_closedir (&Directory); + } + else + { + m_bPerformanceDirectoryExists = false; + } + + // List banks if present + ListPerformanceBanks(); + +#ifdef VERBOSE_DEBUG +#warning "PerformanceConfig in verbose debug printing mode" + LOGNOTE("Testing loading of banks"); + for (unsigned i=0; i= NUM_PERFORMANCES) { + if (!m_bPerformanceDirectoryExists) + { + // Nothing can be done if there is no performance directory + LOGNOTE("Performance directory does not exist"); + return false; + } + if (m_nLastPerformance >= NUM_PERFORMANCES) { // No space left for new performances LOGWARN ("No space left for new performance"); return false; } + + // Note: New performances are created at the end of the currently selected bank. + // Sould we default to a new bank just for user-performances? + // + // There is the possibility of MIDI changing the Bank Number and the user + // not spotting the bank has changed... + // + // Another option would be to find empty slots in the current bank + // rather than always add at the end. + // + // Sorting this out is left for a future update. std::string sPerformanceName = NewPerformanceName; NewPerformanceName=""; - nActualPerformance=nLastPerformance; + unsigned nNewPerformance = m_nLastPerformance + 1; std::string nFileName; std::string nPath; std::string nIndex = "000000"; - nIndex += std::to_string(++nLastFileIndex); + nIndex += std::to_string(nNewPerformance+1); // Index on disk = index in memory+1 nIndex = nIndex.substr(nIndex.length()-6,6); @@ -793,10 +930,11 @@ bool CPerformanceConfig::CreateNewPerformanceFile(void) nFileName +=sPerformanceName.substr(0,14); } nFileName += ".ini"; - m_nPerformanceFileName[nLastPerformance]= nFileName; + m_PerformanceFileName[nNewPerformance]= sPerformanceName; nPath = "SD:/" ; nPath += PERFORMANCE_DIR; + nPath += AddPerformanceBankDirName(m_nPerformanceBank); nPath += "/"; nFileName = nPath + nFileName; @@ -804,17 +942,18 @@ bool CPerformanceConfig::CreateNewPerformanceFile(void) FRESULT Result = f_open (&File, nFileName.c_str(), FA_WRITE | FA_CREATE_ALWAYS); if (Result != FR_OK) { - m_nPerformanceFileName[nLastPerformance]=nullptr; + m_PerformanceFileName[nNewPerformance]=nullptr; return false; } if (f_close (&File) != FR_OK) { - m_nPerformanceFileName[nLastPerformance]=nullptr; + m_PerformanceFileName[nNewPerformance]=nullptr; return false; } - nLastPerformance++; + m_nLastPerformance = nNewPerformance; + m_nActualPerformance = nNewPerformance; new (&m_Properties) CPropertiesFatFsFile(nFileName.c_str(), m_pFileSystem); return true; @@ -822,88 +961,124 @@ bool CPerformanceConfig::CreateNewPerformanceFile(void) bool CPerformanceConfig::ListPerformances() { - nInternalFolderOk=false; - nExternalFolderOk=false; // for future USB implementation - nLastPerformance=0; - nLastFileIndex=0; - m_nPerformanceFileName[nLastPerformance++]="performance.ini"; // in order to assure retrocompatibility - - unsigned nPIndex; - DIR Directory; - FILINFO FileInfo; - FRESULT Result; - //Check if internal "performance" directory exists - Result = f_opendir (&Directory, "SD:/" PERFORMANCE_DIR); - if (Result == FR_OK) + // Clear any existing lists of performances + for (unsigned i=0; i= NUM_PERFORMANCES) { - LOGNOTE ("Skipping performance %s", FileInfo.fname); - } else { - if (!(FileInfo.fattrib & (AM_HID | AM_SYS))) + if (!(FileInfo.fattrib & (AM_HID | AM_SYS))) + { + std::string OriFileName = FileInfo.fname; + size_t nLen = OriFileName.length(); + if ( nLen > 8 && nLen <26 && strcmp(OriFileName.substr(6,1).c_str(), "_")==0) { - std::string FileName = FileInfo.fname; - size_t nLen = FileName.length(); - if ( nLen > 8 && nLen <26 && strcmp(FileName.substr(6,1).c_str(), "_")==0) - { - nPIndex=stoi(FileName.substr(0,6)); - if(nPIndex > nLastFileIndex) + // Note: m_nLastPerformance - refers to the number (index) of the last performance in memory, + // which includes a default performance. + // + // Filenames on the disk start from 1 to match what the user might see in MIDI. + // So this means that actually file 000001_ will correspond to index position [0]. + // For the default bank though, ID 1 is the default performance, so will already exist. + // m_PerformanceFileName[0] = default performance (file 000001) + // m_PerformanceFileName[1] = first available on-disk performance (file 000002) + // + // Note2: filenames assume 6 digits, underscore, name, finally ".ini" + // i.e. 123456_Performance Name.ini + // + nPIndex=stoi(OriFileName.substr(0,6)); + if ((nPIndex < 1) || (nPIndex >= (NUM_PERFORMANCES+1))) + { + // Index is out of range - skip to next file + LOGNOTE ("Performance number out of range: %s (%d to %d)", FileInfo.fname, 1, NUM_PERFORMANCES); + } + else + { + // Convert from "user facing" 1..indexed number to internal 0..indexed + nPIndex = nPIndex-1; + if (m_PerformanceFileName[nPIndex].empty()) { - nLastFileIndex=nPIndex; - } + if(nPIndex > m_nLastPerformance) + { + m_nLastPerformance=nPIndex; + } - m_nPerformanceFileName[nLastPerformance++]= FileName; - } + std::string FileName = OriFileName.substr(0,OriFileName.length()-4).substr(7,14); + + m_PerformanceFileName[nPIndex] = FileName; +#ifdef VERBOSE_DEBUG + LOGNOTE ("Loading performance %s (%d, %s)", OriFileName.c_str(), nPIndex, FileName.c_str()); +#endif + } + else + { + LOGNOTE ("Duplicate performance %s", OriFileName.c_str()); + } + } } } Result = f_findnext (&Directory, &FileInfo); } - // sort by performance number-name - if (nLastPerformance > 2) - { - sort (m_nPerformanceFileName+1, m_nPerformanceFileName + nLastPerformance); // default is always on first place. %%%%%%%%%%%%%%%% - } + f_closedir (&Directory); } - LOGNOTE ("Number of Performances: %d", nLastPerformance); - - return nInternalFolderOk; -} - + return true; +} void CPerformanceConfig::SetNewPerformance (unsigned nID) { - nActualPerformance=nID; - std::string FileN = ""; - if (nID != 0) // in order to assure retrocompatibility + assert (nID < NUM_PERFORMANCES); + m_nActualPerformance=nID; + std::string FileN = GetPerformanceFullFilePath(nID); + + new (&m_Properties) CPropertiesFatFsFile(FileN.c_str(), m_pFileSystem); +#ifdef VERBOSE_DEBUG + LOGNOTE("Selecting Performance: %d (%s)", nID+1, FileN.c_str()); +#endif +} + +unsigned CPerformanceConfig::FindFirstPerformance (void) +{ + for (int nID=0; nID < NUM_PERFORMANCES; nID++) + { + if (IsValidPerformance(nID)) { - FileN += PERFORMANCE_DIR; - FileN += "/"; + return nID; } - FileN += m_nPerformanceFileName[nID]; - new (&m_Properties) CPropertiesFatFsFile(FileN.c_str(), m_pFileSystem); - + } + + return 0; // Even though 0 is a valid performance, not much else to do } std::string CPerformanceConfig::GetNewPerformanceDefaultName(void) { std::string nIndex = "000000"; - nIndex += std::to_string(nLastFileIndex+1); + nIndex += std::to_string(m_nLastPerformance+1+1); // Convert from internal 0.. index to a file-based 1.. index to show the user nIndex = nIndex.substr(nIndex.length()-6,6); return "Perf" + nIndex; } @@ -923,31 +1098,229 @@ void CPerformanceConfig::SetNewPerformanceName(std::string nName) bool CPerformanceConfig::DeletePerformance(unsigned nID) { + if (!m_bPerformanceDirectoryExists) + { + // Nothing can be done if there is no performance directory + LOGNOTE("Performance directory does not exist"); + return false; + } bool bOK = false; - if(nID == 0){return bOK;} // default (performance.ini at root directory) can't be deleted + if((m_nPerformanceBank == 0) && (nID == 0)){return bOK;} // default (performance.ini at root directory) can't be deleted DIR Directory; FILINFO FileInfo; std::string FileN = "SD:/"; FileN += PERFORMANCE_DIR; - + FileN += AddPerformanceBankDirName(m_nPerformanceBank); - FRESULT Result = f_findfirst (&Directory, &FileInfo, FileN.c_str(), m_nPerformanceFileName[nID].c_str()); + FRESULT Result = f_findfirst (&Directory, &FileInfo, FileN.c_str(), GetPerformanceFileName(nID).c_str()); if (Result == FR_OK && FileInfo.fname[0]) { FileN += "/"; - FileN += m_nPerformanceFileName[nID]; + FileN += GetPerformanceFileName(nID); Result=f_unlink (FileN.c_str()); if (Result == FR_OK) { SetNewPerformance(0); - nActualPerformance =0; + m_nActualPerformance =0; //nMenuSelectedPerformance=0; - m_nPerformanceFileName[nID]="ZZZZZZ"; - sort (m_nPerformanceFileName+1, m_nPerformanceFileName + nLastPerformance); // test si va con -1 o no - --nLastPerformance; - m_nPerformanceFileName[nLastPerformance]=nullptr; + m_PerformanceFileName[nID].clear(); + // If this was the last performance in the bank... + if (nID == m_nLastPerformance) + { + do + { + // Find the new last performance + m_nLastPerformance--; + } while (!IsValidPerformance(m_nLastPerformance) && (m_nLastPerformance > 0)); + } bOK=true; } + else + { + LOGNOTE ("Failed to delete %s", FileN.c_str()); + } } return bOK; } + +bool CPerformanceConfig::ListPerformanceBanks() +{ + m_nPerformanceBank = 0; + m_nLastPerformance = 0; + m_nLastPerformanceBank = 0; + + // Open performance directory + DIR Directory; + FILINFO FileInfo; + FRESULT Result; + Result = f_opendir (&Directory, "SD:/" PERFORMANCE_DIR); + if (Result != FR_OK) + { + // No performance directory, so no performance banks. + // So nothing else to do here + LOGNOTE ("No performance banks detected"); + m_bPerformanceDirectoryExists = false; + return false; + } + + unsigned nNumBanks = 0; + + // Add in the default performance directory as the first bank + m_PerformanceBankName[0] = DEFAULT_PERFORMANCE_BANK_NAME; + nNumBanks = 1; + m_nLastPerformanceBank = 0; + + // List directories with names in format 01_Perf Bank Name + Result = f_findfirst (&Directory, &FileInfo, "SD:/" PERFORMANCE_DIR, "*"); + for (unsigned i = 0; Result == FR_OK && FileInfo.fname[0]; i++) + { + // Check to see if it is a directory + if ((FileInfo.fattrib & AM_DIR) != 0) + { + // Looking for Performance banks of the form: 01_Perf Bank Name + // So positions 0,1,2 = decimal digit + // position 3 = "_" + // positions 4.. = actual name + // + std::string OriFileName = FileInfo.fname; + size_t nLen = OriFileName.length(); + if ( nLen > 4 && nLen <26 && strcmp(OriFileName.substr(3,1).c_str(), "_")==0) + { + unsigned nBankIndex = stoi(OriFileName.substr(0,3)); + // Recall user index numbered 002..NUM_PERFORMANCE_BANKS + // NB: Bank 001 is reserved for the default performance directory + if ((nBankIndex > 0) && (nBankIndex <= NUM_PERFORMANCE_BANKS)) + { + // Convert from "user facing" 1..indexed number to internal 0..indexed + nBankIndex = nBankIndex-1; + if (m_PerformanceBankName[nBankIndex].empty()) + { + std::string BankName = OriFileName.substr(4,nLen); + + m_PerformanceBankName[nBankIndex] = BankName; +#ifdef VERBOSE_DEBUG + LOGNOTE ("Found performance bank %s (%d, %s)", OriFileName.c_str(), nBankIndex, BankName.c_str()); +#endif + nNumBanks++; + if (nBankIndex > m_nLastPerformanceBank) + { + m_nLastPerformanceBank = nBankIndex; + } + } + else + { + LOGNOTE ("Duplicate Performance Bank: %s", FileInfo.fname); + if (nBankIndex==0) + { + LOGNOTE ("(Bank 001 is the default performance directory)"); + } + } + } + else + { + LOGNOTE ("Performance Bank number out of range: %s (%d to %d)", FileInfo.fname, 1, NUM_PERFORMANCE_BANKS); + } + } + else + { +#ifdef VERBOSE_DEBUG + LOGNOTE ("Skipping: %s", FileInfo.fname); +#endif + } + } + + Result = f_findnext (&Directory, &FileInfo); + } + + if (nNumBanks > 0) + { + LOGNOTE ("Number of Performance Banks: %d (last = %d)", nNumBanks, m_nLastPerformanceBank+1); + } + + f_closedir (&Directory); + return true; +} + +void CPerformanceConfig::SetNewPerformanceBank(unsigned nBankID) +{ + assert (nBankID < NUM_PERFORMANCE_BANKS); + if (IsValidPerformanceBank(nBankID)) + { +#ifdef VERBOSE_DEBUG + LOGNOTE("Selecting Performance Bank: %d", nBankID+1); +#endif + m_nPerformanceBank = nBankID; + m_nActualPerformanceBank = nBankID; + ListPerformances(); + } + else + { +#ifdef VERBOSE_DEBUG + LOGNOTE("Not selecting invalid Performance Bank: %d", nBankID+1); +#endif + } +} + +unsigned CPerformanceConfig::GetPerformanceBank(void) +{ + return m_nPerformanceBank; +} + +std::string CPerformanceConfig::GetPerformanceBankName(unsigned nBankID) +{ + assert (nBankID < NUM_PERFORMANCE_BANKS); + if (IsValidPerformanceBank(nBankID)) + { + return m_PerformanceBankName[nBankID]; + } + else + { + return DEFAULT_PERFORMANCE_BANK_NAME; + } +} + +std::string CPerformanceConfig::AddPerformanceBankDirName(unsigned nBankID) +{ + assert (nBankID < NUM_PERFORMANCE_BANKS); + if (IsValidPerformanceBank(nBankID)) + { + // Performance Banks directories in format "001_Bank Name" + std::string Index; + if (nBankID == 0) + { + // Legacy: Bank 1 is the default performance directory + return ""; + } + + if (nBankID < 9) + { + Index = "00" + std::to_string(nBankID+1); + } + else if (nBankID < 99) + { + Index = "0" + std::to_string(nBankID+1); + } + else + { + Index = std::to_string(nBankID+1); + } + + return "/" + Index + "_" + m_PerformanceBankName[nBankID]; + } + else + { + return ""; + } +} + +bool CPerformanceConfig::IsValidPerformanceBank(unsigned nBankID) +{ + if (nBankID >= NUM_PERFORMANCE_BANKS) { + return false; + } + if (m_PerformanceBankName[nBankID].empty()) + { + return false; + } + return true; +} diff --git a/src/performanceconfig.h b/src/performanceconfig.h index c151c7a..0d50daa 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -27,14 +27,16 @@ #include #include #define NUM_VOICE_PARAM 156 -#define PERFORMANCE_DIR "performance" -#define NUM_PERFORMANCES 256 +#define NUM_PERFORMANCES 128 +#define NUM_PERFORMANCE_BANKS 128 class CPerformanceConfig // Performance configuration { public: CPerformanceConfig (FATFS *pFileSystem); ~CPerformanceConfig (void); + + bool Init (void); bool Load (void); @@ -122,17 +124,30 @@ public: bool ListPerformances(); //std::string m_DirName; void SetNewPerformance (unsigned nID); + unsigned FindFirstPerformance (void); std::string GetPerformanceFileName(unsigned nID); + std::string GetPerformanceFullFilePath(unsigned nID); std::string GetPerformanceName(unsigned nID); unsigned GetLastPerformance(); + unsigned GetLastPerformanceBank(); void SetActualPerformanceID(unsigned nID); unsigned GetActualPerformanceID(); + void SetActualPerformanceBankID(unsigned nBankID); + unsigned GetActualPerformanceBankID(); bool CreateNewPerformanceFile(void); bool GetInternalFolderOk(); std::string GetNewPerformanceDefaultName(void); void SetNewPerformanceName(std::string nName); bool DeletePerformance(unsigned nID); bool CheckFreePerformanceSlot(void); + std::string AddPerformanceBankDirName(unsigned nBankID); + bool IsValidPerformance(unsigned nID); + + bool ListPerformanceBanks(void); + void SetNewPerformanceBank(unsigned nBankID); + unsigned GetPerformanceBank(void); + std::string GetPerformanceBankName(unsigned nBankID); + bool IsValidPerformanceBank(unsigned nBankID); private: CPropertiesFatFsFile m_Properties; @@ -166,15 +181,17 @@ private: unsigned m_nAftertouchRange[CConfig::ToneGenerators]; unsigned m_nAftertouchTarget[CConfig::ToneGenerators]; - unsigned nLastPerformance; - unsigned nLastFileIndex; - unsigned nActualPerformance = 0; + unsigned m_nLastPerformance; + unsigned m_nActualPerformance = 0; + unsigned m_nActualPerformanceBank = 0; + unsigned m_nPerformanceBank; + unsigned m_nLastPerformanceBank; + bool m_bPerformanceDirectoryExists; //unsigned nMenuSelectedPerformance = 0; - std::string m_nPerformanceFileName[NUM_PERFORMANCES]; + std::string m_PerformanceFileName[NUM_PERFORMANCES]; + std::string m_PerformanceBankName[NUM_PERFORMANCE_BANKS]; FATFS *m_pFileSystem; - bool nInternalFolderOk=false; - bool nExternalFolderOk=false; // for future USB implementation std::string NewPerformanceName=""; bool m_bCompressorEnable; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 93e6b0e..82a426a 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -214,7 +214,8 @@ const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknow {0, 99, 1}, // ParameterReverbLowPass {0, 99, 1}, // ParameterReverbDiffusion {0, 99, 1}, // ParameterReverbLevel - {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel} // ParameterPerformanceSelectChannel + {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // ParameterPerformanceSelectChannel + {0, NUM_PERFORMANCE_BANKS, 1} // ParameterPerformanceBank }; // must match CMiniDexed::TTGParameter @@ -327,6 +328,7 @@ const CUIMenu::TMenuItem CUIMenu::s_PerformanceMenu[] = {"Load", PerformanceMenu, 0, 0}, {"Save", MenuHandler, s_SaveMenu}, {"Delete", PerformanceMenu, 0, 1}, + {"Bank", EditPerformanceBankNumber, 0, 0}, {"PCCH", EditGlobalParameter, 0, CMiniDexed::ParameterPerformanceSelectChannel}, {0} }; @@ -1211,24 +1213,43 @@ void CUIMenu::PgmUpDownHandler (TMenuEvent Event) // Program Up/Down acts on performances unsigned nLastPerformance = m_pMiniDexed->GetLastPerformance(); unsigned nPerformance = m_pMiniDexed->GetActualPerformanceID(); + unsigned nStart = nPerformance; //LOGNOTE("Performance actual=%d, last=%d", nPerformance, nLastPerformance); if (Event == MenuEventPgmDown) { - if (nPerformance > 0) + do { - m_nSelectedPerformanceID = nPerformance-1; - m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID); - //LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance); - } + if (nPerformance == 0) + { + // Wrap around + nPerformance = nLastPerformance; + } + else if (nPerformance > 0) + { + --nPerformance; + } + } while ((m_pMiniDexed->IsValidPerformance(nPerformance) != true) && (nPerformance != nStart)); + m_nSelectedPerformanceID = nPerformance; + m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID); + //LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance); } - else + else // MenuEventPgmUp { - if (nPerformance < nLastPerformance-1) + do { - m_nSelectedPerformanceID = nPerformance+1; - m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID); - //LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance); - } + if (nPerformance == nLastPerformance) + { + // Wrap around + nPerformance = 0; + } + else if (nPerformance < nLastPerformance) + { + ++nPerformance; + } + } while ((m_pMiniDexed->IsValidPerformance(nPerformance) != true) && (nPerformance != nStart)); + m_nSelectedPerformanceID = nPerformance; + m_pMiniDexed->SetNewPerformance(m_nSelectedPerformanceID); + //LOGNOTE("Performance new=%d, last=%d", m_nSelectedPerformanceID, nLastPerformance); } } else @@ -1366,7 +1387,15 @@ void CUIMenu::TimerHandlerNoBack (TKernelTimerHandle hTimer, void *pParam, void void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) { bool bPerformanceSelectToLoad = pUIMenu->m_pMiniDexed->GetPerformanceSelectToLoad(); + unsigned nLastPerformance = pUIMenu->m_pMiniDexed->GetLastPerformance(); unsigned nValue = pUIMenu->m_nSelectedPerformanceID; + unsigned nStart = nValue; + if (pUIMenu->m_pMiniDexed->IsValidPerformance(nValue) != true) + { + // A bank change has left the selected performance out of sync + nValue = pUIMenu->m_pMiniDexed->GetActualPerformanceID(); + pUIMenu->m_nSelectedPerformanceID = nValue; + } std::string Value; if (Event == MenuEventUpdate) @@ -1380,17 +1409,25 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) } if(!pUIMenu->m_bPerformanceDeleteMode) - { + { switch (Event) { case MenuEventUpdate: break; case MenuEventStepDown: - if (nValue > 0) + do { - --nValue; - } + if (nValue == 0) + { + // Wrap around + nValue = nLastPerformance; + } + else if (nValue > 0) + { + --nValue; + } + } while ((pUIMenu->m_pMiniDexed->IsValidPerformance(nValue) != true) && (nValue != nStart)); pUIMenu->m_nSelectedPerformanceID = nValue; if (!bPerformanceSelectToLoad && pUIMenu->m_nCurrentParameter==0) { @@ -1399,10 +1436,18 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) break; case MenuEventStepUp: - if (++nValue > (unsigned) pUIMenu->m_pMiniDexed->GetLastPerformance()-1) + do { - nValue = pUIMenu->m_pMiniDexed->GetLastPerformance()-1; - } + if (nValue == nLastPerformance) + { + // Wrap around + nValue = 0; + } + else if (nValue < nLastPerformance) + { + ++nValue; + } + } while ((pUIMenu->m_pMiniDexed->IsValidPerformance(nValue) != true) && (nValue != nStart)); pUIMenu->m_nSelectedPerformanceID = nValue; if (!bPerformanceSelectToLoad && pUIMenu->m_nCurrentParameter==0) { @@ -1421,7 +1466,7 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) break; case 1: - if (pUIMenu->m_nSelectedPerformanceID != 0) + if (pUIMenu->m_pMiniDexed->IsValidPerformance(pUIMenu->m_nSelectedPerformanceID)) { pUIMenu->m_bPerformanceDeleteMode=true; pUIMenu->m_bConfirmDeletePerformance=false; @@ -1474,17 +1519,24 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) if(!pUIMenu->m_bPerformanceDeleteMode) { Value = pUIMenu->m_pMiniDexed->GetPerformanceName(nValue); + unsigned nBankNum = pUIMenu->m_pMiniDexed->GetPerformanceBank(); - - std::string nPSelected = ""; + std::string nPSelected = "000"; + nPSelected += std::to_string(nBankNum+1); // Convert to user-facing bank number rather than index + nPSelected = nPSelected.substr(nPSelected.length()-3,3); + std::string nPPerf = "000"; + nPPerf += std::to_string(nValue+1); // Convert to user-facing performance number rather than index + nPPerf = nPPerf.substr(nPPerf.length()-3,3); + + nPSelected += ":"+nPPerf; if(nValue == pUIMenu->m_pMiniDexed->GetActualPerformanceID()) { - nPSelected= "[L]"; + nPSelected += " [L]"; } pUIMenu->m_pUI->DisplayWrite (pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, nPSelected.c_str(), - Value.c_str (), - (int) nValue > 0, (int) nValue < (int) pUIMenu->m_pMiniDexed->GetLastPerformance()-1); + Value.c_str (), true, true); +// (int) nValue > 0, (int) nValue < (int) pUIMenu->m_pMiniDexed->GetLastPerformance()); } else { @@ -1492,6 +1544,90 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) } } +void CUIMenu::EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event) +{ + bool bPerformanceSelectToLoad = pUIMenu->m_pMiniDexed->GetPerformanceSelectToLoad(); + unsigned nLastPerformanceBank = pUIMenu->m_pMiniDexed->GetLastPerformanceBank(); + unsigned nValue = pUIMenu->m_nSelectedPerformanceBankID; + unsigned nStart = nValue; + std::string Value; + + switch (Event) + { + case MenuEventUpdate: + break; + + case MenuEventStepDown: + do + { + if (nValue == 0) + { + // Wrap around + nValue = nLastPerformanceBank; + } + else if (nValue > 0) + { + --nValue; + } + } while ((pUIMenu->m_pMiniDexed->IsValidPerformanceBank(nValue) != true) && (nValue != nStart)); + pUIMenu->m_nSelectedPerformanceBankID = nValue; + if (!bPerformanceSelectToLoad) + { + // Switch to the new bank and select the first performance voice + pUIMenu->m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nValue); + pUIMenu->m_pMiniDexed->SetFirstPerformance(); + } + break; + + case MenuEventStepUp: + do + { + if (nValue == nLastPerformanceBank) + { + // Wrap around + nValue = 0; + } + else if (nValue < nLastPerformanceBank) + { + ++nValue; + } + } while ((pUIMenu->m_pMiniDexed->IsValidPerformanceBank(nValue) != true) && (nValue != nStart)); + pUIMenu->m_nSelectedPerformanceBankID = nValue; + if (!bPerformanceSelectToLoad) + { + pUIMenu->m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nValue); + pUIMenu->m_pMiniDexed->SetFirstPerformance(); + } + break; + + case MenuEventSelect: + if (bPerformanceSelectToLoad) + { + pUIMenu->m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nValue); + pUIMenu->m_pMiniDexed->SetFirstPerformance(); + } + break; + + default: + return; + } + + Value = pUIMenu->m_pMiniDexed->GetPerformanceConfig ()->GetPerformanceBankName(nValue); + std::string nPSelected = "000"; + nPSelected += std::to_string(nValue+1); // Convert to user-facing number rather than index + nPSelected = nPSelected.substr(nPSelected.length()-3,3); + + if(nValue == (unsigned)pUIMenu->m_pMiniDexed->GetParameter (CMiniDexed::ParameterPerformanceBank)) + { + nPSelected += " [L]"; + } + + pUIMenu->m_pUI->DisplayWrite (pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, nPSelected.c_str(), + Value.c_str (), + nValue > 0, + nValue < pUIMenu->m_pMiniDexed->GetLastPerformanceBank()-1); +} + void CUIMenu::InputTxt (CUIMenu *pUIMenu, TMenuEvent Event) { unsigned nTG=0; diff --git a/src/uimenu.h b/src/uimenu.h index b66d65c..d5b48dc 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -91,6 +91,7 @@ private: static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event); static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event); static void SavePerformanceNewFile (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); static std::string GetGlobalValueString (unsigned nParameter, int nValue); static std::string GetTGValueString (unsigned nTGParameter, int nValue); @@ -169,6 +170,7 @@ private: bool m_bPerformanceDeleteMode=false; bool m_bConfirmDeletePerformance=false; unsigned m_nSelectedPerformanceID =0; + unsigned m_nSelectedPerformanceBankID =0; bool m_bSplashShow=false; }; diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 1c0716e..a2d78f1 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -388,13 +388,16 @@ void CUserInterface::UISetMIDIButtonChannel (unsigned uCh) if (uCh == 0) { m_nMIDIButtonCh = CMIDIDevice::Disabled; + LOGNOTE("MIDI Button channel not set"); } else if (uCh < CMIDIDevice::Channels) { m_nMIDIButtonCh = uCh - 1; + LOGNOTE("MIDI Button channel set to: %d", m_nMIDIButtonCh); } else { m_nMIDIButtonCh = CMIDIDevice::OmniMode; + LOGNOTE("MIDI Button channel set to: OMNI"); } } \ No newline at end of file