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