//
// performanceconfig.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022  The MiniDexed Team
//
// Original author of this class:
//	R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#include <circle/logger.h>
#include "performanceconfig.h"
#include "mididevice.h"
#include <cstring>
#include <algorithm>
#include <sstream>

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 (DEFAULT_PERFORMANCE_FILENAME, pFileSystem)
{
	m_pFileSystem = pFileSystem; 
}

CPerformanceConfig::~CPerformanceConfig (void)
{
}

bool CPerformanceConfig::Init (unsigned nToneGenerators)
{
	// Different versions of Pi allow different TG configurations.
	// On loading, performances will load up to the number of
	// supported/active TGs.
	//
	// On saving, the active/supported number of TGs is used.
	//
	// This means that if an 8TG performance is loaded into
	// a 16 TG system and then saved, the saved performance
	// will include all 16 TG configurations.
	//
	m_nToneGenerators = nToneGenerators;

	// 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_PERFORMANCE_BANKS; i++)
	{
		if (!m_PerformanceBankName[i].empty())
		{
			SetNewPerformanceBank(i);
			SetNewPerformance(0);
		}
	}
#endif
	// Set to default initial bank
	SetNewPerformanceBank(0);
	SetNewPerformance(0);

	LOGNOTE ("Loaded Default Performance Bank - Last Performance: %d", m_nLastPerformance + 1); // Show "user facing" index

	return true;
}

bool CPerformanceConfig::Load (void)
{
	if (!m_Properties.Load ())
	{
		return false;
	}

	bool bResult = false;

	for (unsigned nTG = 0; nTG < CConfig::AllToneGenerators; nTG++)
	{
		CString PropertyName;

		PropertyName.Format ("BankNumber%u", nTG+1);
		m_nBankNumber[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("VoiceNumber%u", nTG+1);
		m_nVoiceNumber[nTG] = m_Properties.GetNumber (PropertyName, 1);
		if (m_nVoiceNumber[nTG] > 0)
		{
			m_nVoiceNumber[nTG]--;
		}

		PropertyName.Format ("MIDIChannel%u", nTG+1);
		unsigned nMIDIChannel = m_Properties.GetNumber (PropertyName, 0);
		if (nMIDIChannel == 0)
		{
			m_nMIDIChannel[nTG] = CMIDIDevice::Disabled;
		}
		else if (nMIDIChannel <= CMIDIDevice::Channels)
		{
			m_nMIDIChannel[nTG] = nMIDIChannel-1;
			bResult = true;
		}
		else
		{
			m_nMIDIChannel[nTG] = CMIDIDevice::OmniMode;
			bResult = true;
		}

		PropertyName.Format ("Volume%u", nTG+1);
		m_nVolume[nTG] = m_Properties.GetNumber (PropertyName, 100);

		PropertyName.Format ("Pan%u", nTG+1);
		m_nPan[nTG] = m_Properties.GetNumber (PropertyName, 64);

		PropertyName.Format ("InsertFX%u", nTG+1);
		m_nInsertFX[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("InsertFXParams%u", nTG+1);
		m_sInsertFXParams[nTG] = m_Properties.GetString (PropertyName, "");

		PropertyName.Format ("MidiFX%u", nTG+1);
		m_nMidiFX[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("MidiFXParams%u", nTG+1);
		m_sMidiFXParams[nTG] = m_Properties.GetString (PropertyName, "");

		PropertyName.Format ("Detune%u", nTG+1);
		m_nDetune[nTG] = m_Properties.GetSignedNumber (PropertyName, 0);

		PropertyName.Format ("Cutoff%u", nTG+1);
		m_nCutoff[nTG] = m_Properties.GetNumber (PropertyName, 99);

		PropertyName.Format ("Resonance%u", nTG+1);
		m_nResonance[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("NoteLimitLow%u", nTG+1);
		m_nNoteLimitLow[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("NoteLimitHigh%u", nTG+1);
		m_nNoteLimitHigh[nTG] = m_Properties.GetNumber (PropertyName, 127);

		PropertyName.Format ("NoteShift%u", nTG+1);
		m_nNoteShift[nTG] = m_Properties.GetSignedNumber (PropertyName, 0);

		PropertyName.Format ("ReverbSend%u", nTG+1);
		m_nReverbSend[nTG] = m_Properties.GetNumber (PropertyName, 50);
		
		PropertyName.Format ("PitchBendRange%u", nTG+1);
		m_nPitchBendRange[nTG] = m_Properties.GetNumber (PropertyName, 2);

		PropertyName.Format ("PitchBendStep%u", nTG+1);
		m_nPitchBendStep[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("PortamentoMode%u", nTG+1);
		m_nPortamentoMode[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("PortamentoGlissando%u", nTG+1);
		m_nPortamentoGlissando[nTG] = m_Properties.GetNumber (PropertyName, 0);

		PropertyName.Format ("PortamentoTime%u", nTG+1);
		m_nPortamentoTime[nTG] = m_Properties.GetNumber (PropertyName, 0);
		
		PropertyName.Format ("VoiceData%u", nTG+1); 
		m_nVoiceDataTxt[nTG] = m_Properties.GetString (PropertyName, "");
		
		PropertyName.Format ("MonoMode%u", nTG+1);
		m_bMonoMode[nTG] = m_Properties.GetNumber (PropertyName, 0) != 0;
				
		PropertyName.Format ("ModulationWheelRange%u", nTG+1);
		m_nModulationWheelRange[nTG] = m_Properties.GetNumber (PropertyName, 99); 
		
		PropertyName.Format ("ModulationWheelTarget%u", nTG+1);
		m_nModulationWheelTarget[nTG] = m_Properties.GetNumber (PropertyName, 1);
		
		PropertyName.Format ("FootControlRange%u", nTG+1);
		m_nFootControlRange[nTG] = m_Properties.GetNumber (PropertyName, 99); 
		
		PropertyName.Format ("FootControlTarget%u", nTG+1);
		m_nFootControlTarget[nTG] = m_Properties.GetNumber (PropertyName, 0);
		
		PropertyName.Format ("BreathControlRange%u", nTG+1);
		m_nBreathControlRange[nTG] = m_Properties.GetNumber (PropertyName, 99); 
		
		PropertyName.Format ("BreathControlTarget%u", nTG+1);
		m_nBreathControlTarget[nTG] = m_Properties.GetNumber (PropertyName, 0);
		
		PropertyName.Format ("AftertouchRange%u", nTG+1);
		m_nAftertouchRange[nTG] = m_Properties.GetNumber (PropertyName, 99); 
		
		PropertyName.Format ("AftertouchTarget%u", nTG+1);
		m_nAftertouchTarget[nTG] = m_Properties.GetNumber (PropertyName, 0);
		
	}

	m_bCompressorEnable = m_Properties.GetNumber ("CompressorEnable", 1) != 0;

	m_bReverbEnable = m_Properties.GetNumber ("ReverbEnable", 1) != 0;
	m_nReverbSize = m_Properties.GetNumber ("ReverbSize", 70);
	m_nReverbHighDamp = m_Properties.GetNumber ("ReverbHighDamp", 50);
	m_nReverbLowDamp = m_Properties.GetNumber ("ReverbLowDamp", 50);
	m_nReverbLowPass = m_Properties.GetNumber ("ReverbLowPass", 30);
	m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65);
	m_nReverbLevel = m_Properties.GetNumber ("ReverbLevel", 100);

	m_nTempo = m_Properties.GetNumber ("Tempo", 120);

	m_nSendFX1 = m_Properties.GetNumber ("SendFX1", 7);
	m_sSendFX1Params = m_Properties.GetString ("SendFX1Params", "");
	m_nSendFX1SendLevel = m_Properties.GetNumber ("SendFX1SendLevel", 0);
	m_nSendFX1Level = m_Properties.GetNumber ("SendFX1Level", 100);

	// Set EFFECT_REVERB as Default for backward compatibility
	// EFFECT_REVERB 7
	// Support for first FX version with only one send effect for backward compatibility
	m_nSendFX2 = m_Properties.GetNumber ("SendFX", 7);
	m_sSendFX2Params = m_Properties.GetString ("SendFXParams", "");
	m_nSendFX2Level = m_Properties.GetNumber ("SendFXLevel", m_nReverbLevel);
	// Now read the new values, but use previous values as default
	m_nSendFX2 = m_Properties.GetNumber ("SendFX2", m_nSendFX2);
	m_sSendFX2Params = m_Properties.GetString ("SendFX2Params", m_sSendFX2Params.c_str());
	m_nSendFX2Level = m_Properties.GetNumber ("SendFX2Level", m_nSendFX2Level);
	if (m_nSendFX2 == 7 && m_sSendFX2Params.empty()) {
		m_sSendFX2Params = std::to_string(m_bReverbEnable == 1 ? 0 : 1) + "," + std::to_string(m_nReverbSize) + ","
			+ std::to_string(m_nReverbHighDamp) + "," + std::to_string(m_nReverbLowDamp) + ","
			+ std::to_string(m_nReverbLowPass) + "," + std::to_string(m_nReverbDiffusion) + ","
			+ std::to_string(100);
	}

	// Set 3 Band EQ as Default Master FX
	m_nMasterFX = m_Properties.GetNumber ("MasterFX", 9);
	m_sMasterFXParams = m_Properties.GetString ("MasterFXParams", "");
	
	return bResult;
}

bool CPerformanceConfig::Save (void)
{
	m_Properties.RemoveAll ();

	for (unsigned nTG = 0; nTG < m_nToneGenerators; nTG++)
	{
		CString PropertyName;

		PropertyName.Format ("BankNumber%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nBankNumber[nTG]);

		PropertyName.Format ("VoiceNumber%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nVoiceNumber[nTG]+1);

		PropertyName.Format ("MIDIChannel%u", nTG+1);
		unsigned nMIDIChannel = m_nMIDIChannel[nTG];
		if (nMIDIChannel < CMIDIDevice::Channels)
		{
			nMIDIChannel++;
		}
		else if (nMIDIChannel == CMIDIDevice::OmniMode)
		{
			nMIDIChannel = 255;
		}
		else
		{
			nMIDIChannel = 0;
		}
		m_Properties.SetNumber (PropertyName, nMIDIChannel);

		PropertyName.Format ("Volume%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nVolume[nTG]);

		PropertyName.Format ("Pan%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nPan[nTG]);

		PropertyName.Format ("InsertFX%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nInsertFX[nTG]);

		PropertyName.Format ("InsertFXParams%u", nTG+1);
		m_Properties.SetString (PropertyName, m_sInsertFXParams[nTG].c_str());

		PropertyName.Format ("MidiFX%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nMidiFX[nTG]);

		PropertyName.Format ("MidiFXParams%u", nTG+1);
		m_Properties.SetString (PropertyName, m_sMidiFXParams[nTG].c_str());

		PropertyName.Format ("Detune%u", nTG+1);
		m_Properties.SetSignedNumber (PropertyName, m_nDetune[nTG]);

		PropertyName.Format ("Cutoff%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nCutoff[nTG]);

		PropertyName.Format ("Resonance%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nResonance[nTG]);

		PropertyName.Format ("NoteLimitLow%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nNoteLimitLow[nTG]);

		PropertyName.Format ("NoteLimitHigh%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nNoteLimitHigh[nTG]);

		PropertyName.Format ("NoteShift%u", nTG+1);
		m_Properties.SetSignedNumber (PropertyName, m_nNoteShift[nTG]);

		PropertyName.Format ("ReverbSend%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nReverbSend[nTG]);
		
		PropertyName.Format ("PitchBendRange%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nPitchBendRange[nTG]);

		PropertyName.Format ("PitchBendStep%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nPitchBendStep[nTG]);

		PropertyName.Format ("PortamentoMode%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nPortamentoMode[nTG]);

		PropertyName.Format ("PortamentoGlissando%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nPortamentoGlissando[nTG]);

		PropertyName.Format ("PortamentoTime%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nPortamentoTime[nTG]);
		
		PropertyName.Format ("VoiceData%u", nTG+1);
		char *cstr = &m_nVoiceDataTxt[nTG][0];
		m_Properties.SetString (PropertyName, cstr);
		
		PropertyName.Format ("MonoMode%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_bMonoMode[nTG] ? 1 : 0);
				
		PropertyName.Format ("ModulationWheelRange%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nModulationWheelRange[nTG]);
	
		PropertyName.Format ("ModulationWheelTarget%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nModulationWheelTarget[nTG]);	
			
		PropertyName.Format ("FootControlRange%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nFootControlRange[nTG]);	
		
		PropertyName.Format ("FootControlTarget%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nFootControlTarget[nTG]);	
		
		PropertyName.Format ("BreathControlRange%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nBreathControlRange[nTG]);	
		
		PropertyName.Format ("BreathControlTarget%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nBreathControlTarget[nTG]);	
		
		PropertyName.Format ("AftertouchRange%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nAftertouchRange[nTG]);	
		
		PropertyName.Format ("AftertouchTarget%u", nTG+1);
		m_Properties.SetNumber (PropertyName, m_nAftertouchTarget[nTG]);			

		}

	m_Properties.SetNumber ("CompressorEnable", m_bCompressorEnable ? 1 : 0);

	m_Properties.SetNumber ("SendFX1", m_nSendFX1);
	m_Properties.SetString ("SendFX1Params", m_sSendFX1Params.c_str());
	m_Properties.SetNumber ("SendFX1SendLevel", m_nSendFX1SendLevel);
	m_Properties.SetNumber ("SendFX1Level", m_nSendFX1Level);

	m_Properties.SetNumber ("SendFX2", m_nSendFX2);
	m_Properties.SetString ("SendFX2Params", m_sSendFX2Params.c_str());
	m_Properties.SetNumber ("SendFX2Level", m_nSendFX2Level);

	// For backward compatibility
	// EFFECT_REVERB 7
	if (m_nSendFX2 == 7)
	{
		std::vector<unsigned> tokens;
		std::string params = m_sSendFX2Params;
		char delimiter = ',';
		std::stringstream ss(params);
		std::string temp;
		while (getline(ss, temp, delimiter))
		{
			tokens.push_back(stoi(temp));
		}
		
		for (size_t i = 0; i < tokens.size(); i++)
		{
			switch (i)
			{
			case 0:
				m_Properties.SetNumber ("ReverbEnable", tokens[i] ? 0 : 1);
				break;
			case 1:
				m_Properties.SetNumber ("ReverbSize", tokens[i]);
				break;
			case 2:
				m_Properties.SetNumber ("ReverbHighDamp", tokens[i]);
				break;
			case 3:
				m_Properties.SetNumber ("ReverbLowDamp", tokens[i]);
				break;
			case 4:
				m_Properties.SetNumber ("ReverbLowPass", tokens[i]);
				break;
			case 5:
				m_Properties.SetNumber ("ReverbDiffusion", tokens[i]);
				break;
			default:
				break;
			}
		}
		m_Properties.SetNumber ("ReverbLevel", m_nSendFX2Level);

		tokens.clear();
		tokens.shrink_to_fit();
	}
	
	m_Properties.SetNumber ("MasterFX", m_nMasterFX);
	m_Properties.SetString ("MasterFXParams", m_sMasterFXParams.c_str());

	m_Properties.SetNumber ("Tempo", m_nTempo);

	return m_Properties.Save ();
}

unsigned CPerformanceConfig::GetBankNumber (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nBankNumber[nTG];
}

unsigned CPerformanceConfig::GetVoiceNumber (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nVoiceNumber[nTG];
}

unsigned CPerformanceConfig::GetMIDIChannel (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nMIDIChannel[nTG];
}

unsigned CPerformanceConfig::GetVolume (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nVolume[nTG];
}

unsigned CPerformanceConfig::GetPan (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nPan[nTG];
}

unsigned CPerformanceConfig::GetInsertFX (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nInsertFX[nTG];
}

std::vector<unsigned> CPerformanceConfig::GetInsertFXParams (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return StringToVector(m_sInsertFXParams[nTG]);
}

unsigned CPerformanceConfig::GetMidiFX (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nMidiFX[nTG];
}

std::vector<unsigned> CPerformanceConfig::GetMidiFXParams (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return StringToVector(m_sMidiFXParams[nTG]);
}

int CPerformanceConfig::GetDetune (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nDetune[nTG];
}

unsigned CPerformanceConfig::GetCutoff (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nCutoff[nTG];
}

unsigned CPerformanceConfig::GetResonance (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nResonance[nTG];
}

unsigned CPerformanceConfig::GetNoteLimitLow (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nNoteLimitLow[nTG];
}

unsigned CPerformanceConfig::GetNoteLimitHigh (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nNoteLimitHigh[nTG];
}

int CPerformanceConfig::GetNoteShift (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nNoteShift[nTG];
}

unsigned CPerformanceConfig::GetReverbSend (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nReverbSend[nTG];
}

void CPerformanceConfig::SetBankNumber (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nBankNumber[nTG] = nValue;
}

void CPerformanceConfig::SetVoiceNumber (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nVoiceNumber[nTG] = nValue;
}

void CPerformanceConfig::SetMIDIChannel (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nMIDIChannel[nTG] = nValue;
}

void CPerformanceConfig::SetVolume (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nVolume[nTG] = nValue;
}

void CPerformanceConfig::SetPan (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nPan[nTG] = nValue;
}

void CPerformanceConfig::SetInsertFX (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nInsertFX[nTG] = nValue;
}

void CPerformanceConfig::SetInsertFXParams (std::vector<unsigned> pParams, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_sInsertFXParams[nTG] = VectorToString(pParams);
}

void CPerformanceConfig::SetMidiFX (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nMidiFX[nTG] = nValue;
}

void CPerformanceConfig::SetMidiFXParams (std::vector<unsigned> pParams, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_sMidiFXParams[nTG] = VectorToString(pParams);
}

void CPerformanceConfig::SetDetune (int nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nDetune[nTG] = nValue;
}

void CPerformanceConfig::SetCutoff (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nCutoff[nTG] = nValue;
}

void CPerformanceConfig::SetResonance (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nResonance[nTG] = nValue;
}

void CPerformanceConfig::SetNoteLimitLow (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nNoteLimitLow[nTG] = nValue;
}

void CPerformanceConfig::SetNoteLimitHigh (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nNoteLimitHigh[nTG] = nValue;
}

void CPerformanceConfig::SetNoteShift (int nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nNoteShift[nTG] = nValue;
}

void CPerformanceConfig::SetReverbSend (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nReverbSend[nTG] = nValue;
}

bool CPerformanceConfig::GetCompressorEnable (void) const
{
	return m_bCompressorEnable;
}

unsigned CPerformanceConfig::GetSendFX1 (void) const
{
	return m_nSendFX1;
}

unsigned CPerformanceConfig::GetSendFX2 (void) const
{
	return m_nSendFX2;
}

std::vector<unsigned> CPerformanceConfig::GetSendFX1Params (void) const
{
	return StringToVector(m_sSendFX1Params);
}

std::vector<unsigned> CPerformanceConfig::GetSendFX2Params (void) const
{
	return StringToVector(m_sSendFX2Params);
}

unsigned CPerformanceConfig::GetSendFX1SendLevel (void) const
{
	return m_nSendFX1SendLevel;
}

unsigned CPerformanceConfig::GetSendFX1Level (void) const
{
	return m_nSendFX1Level;
}

unsigned CPerformanceConfig::GetSendFX2Level (void) const
{
	return m_nSendFX2Level;
}

unsigned CPerformanceConfig::GetMasterFX (void) const
{
	return m_nMasterFX;
}

std::vector<unsigned> CPerformanceConfig::GetMasterFXParams (void) const
{
	return StringToVector(m_sMasterFXParams);
}

bool CPerformanceConfig::GetReverbEnable (void) const
{
	return m_bReverbEnable;
}

unsigned CPerformanceConfig::GetReverbSize (void) const
{
	return m_nReverbSize;
}

unsigned CPerformanceConfig::GetReverbHighDamp (void) const
{
	return m_nReverbHighDamp;
}

unsigned CPerformanceConfig::GetReverbLowDamp (void) const
{
	return m_nReverbLowDamp;
}

unsigned CPerformanceConfig::GetReverbLowPass (void) const
{
	return m_nReverbLowPass;
}

unsigned CPerformanceConfig::GetReverbDiffusion (void) const
{
	return m_nReverbDiffusion;
}

unsigned CPerformanceConfig::GetReverbLevel (void) const
{
	return m_nReverbLevel;
}

unsigned CPerformanceConfig::GetTempo (void) const
{
	return m_nTempo;
}

void CPerformanceConfig::SetCompressorEnable (bool bValue)
{
	m_bCompressorEnable = bValue;
}

void CPerformanceConfig::SetSendFX1 (unsigned nValue)
{
	m_nSendFX1 = nValue;
}

void CPerformanceConfig::SetSendFX2 (unsigned nValue)
{
	m_nSendFX1 = nValue;
}

void CPerformanceConfig::SetSendFX1Params (std::vector<unsigned> pParams)
{
	m_sSendFX1Params = VectorToString(pParams);
}

void CPerformanceConfig::SetSendFX2Params (std::vector<unsigned> pParams)
{
	m_sSendFX2Params = VectorToString(pParams);
}

void CPerformanceConfig::SetSendFX1SendLevel (unsigned nValue)
{
	m_nSendFX1SendLevel = nValue;
}

void CPerformanceConfig::SetSendFX1Level (unsigned nValue)
{
	m_nSendFX1Level = nValue;
}

void CPerformanceConfig::SetSendFX2Level (unsigned nValue)
{
	m_nSendFX2Level = nValue;
}

void CPerformanceConfig::SetMasterFX (unsigned nValue)
{
	m_nMasterFX = nValue;
}

void CPerformanceConfig::SetMasterFXParams (std::vector<unsigned> pParams)
{
	m_sMasterFXParams = VectorToString(pParams);
}

void CPerformanceConfig::SetReverbEnable (bool bValue)
{
	m_bReverbEnable = bValue;
}

void CPerformanceConfig::SetReverbSize (unsigned nValue)
{
	m_nReverbSize = nValue;
}

void CPerformanceConfig::SetReverbHighDamp (unsigned nValue)
{
	m_nReverbHighDamp = nValue;
}

void CPerformanceConfig::SetReverbLowDamp (unsigned nValue)
{
	m_nReverbLowDamp = nValue;
}

void CPerformanceConfig::SetReverbLowPass (unsigned nValue)
{
	m_nReverbLowPass = nValue;
}

void CPerformanceConfig::SetReverbDiffusion (unsigned nValue)
{
	m_nReverbDiffusion = nValue;
}

void CPerformanceConfig::SetReverbLevel (unsigned nValue)
{
	m_nReverbLevel = nValue;
}

void CPerformanceConfig::SetTempo (unsigned nValue)
{
	m_nTempo = nValue;
}

// Pitch bender and portamento:
void CPerformanceConfig::SetPitchBendRange (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nPitchBendRange[nTG] = nValue;
}

unsigned CPerformanceConfig::GetPitchBendRange (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nPitchBendRange[nTG];
}


void CPerformanceConfig::SetPitchBendStep (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nPitchBendStep[nTG] = nValue;
}

unsigned CPerformanceConfig::GetPitchBendStep (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nPitchBendStep[nTG];
}


void CPerformanceConfig::SetPortamentoMode (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nPortamentoMode[nTG] = nValue;
}

unsigned CPerformanceConfig::GetPortamentoMode (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nPortamentoMode[nTG];
}


void CPerformanceConfig::SetPortamentoGlissando (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nPortamentoGlissando[nTG] = nValue;
}

unsigned CPerformanceConfig::GetPortamentoGlissando (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nPortamentoGlissando[nTG];
}


void CPerformanceConfig::SetPortamentoTime (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nPortamentoTime[nTG] = nValue;
}

unsigned CPerformanceConfig::GetPortamentoTime (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nPortamentoTime[nTG];
}

void CPerformanceConfig::SetMonoMode (bool bValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_bMonoMode[nTG] = bValue;
}

bool CPerformanceConfig::GetMonoMode (unsigned nTG) const
{
	return m_bMonoMode[nTG];
}

void CPerformanceConfig::SetModulationWheelRange (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nModulationWheelRange[nTG] = nValue;
}

unsigned CPerformanceConfig::GetModulationWheelRange (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nModulationWheelRange[nTG];
}

void CPerformanceConfig::SetModulationWheelTarget (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nModulationWheelTarget[nTG] = nValue;
}

unsigned CPerformanceConfig::GetModulationWheelTarget (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nModulationWheelTarget[nTG];
}

void CPerformanceConfig::SetFootControlRange (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nFootControlRange[nTG] = nValue;
}

unsigned CPerformanceConfig::GetFootControlRange (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nFootControlRange[nTG];
}

void CPerformanceConfig::SetFootControlTarget (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nFootControlTarget[nTG] = nValue;
}

unsigned CPerformanceConfig::GetFootControlTarget (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nFootControlTarget[nTG];
}

void CPerformanceConfig::SetBreathControlRange (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nBreathControlRange[nTG] = nValue;
}

unsigned CPerformanceConfig::GetBreathControlRange (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nBreathControlRange[nTG];
}

void CPerformanceConfig::SetBreathControlTarget (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nBreathControlTarget[nTG] = nValue;
}

unsigned CPerformanceConfig::GetBreathControlTarget (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nBreathControlTarget[nTG];
}

void CPerformanceConfig::SetAftertouchRange (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nAftertouchRange[nTG] = nValue;
}

unsigned CPerformanceConfig::GetAftertouchRange (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nAftertouchRange[nTG];
}

void CPerformanceConfig::SetAftertouchTarget (unsigned nValue, unsigned nTG)
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nAftertouchTarget[nTG] = nValue;
}

unsigned CPerformanceConfig::GetAftertouchTarget (unsigned nTG) const
{
	assert (nTG < CConfig::AllToneGenerators);
	return m_nAftertouchTarget[nTG];
}

void CPerformanceConfig::SetVoiceDataToTxt (const uint8_t *pData, unsigned nTG)  
{
	assert (nTG < CConfig::AllToneGenerators);
	m_nVoiceDataTxt[nTG] = "";
	char nDtoH[]="0123456789ABCDEF";
	for (int i = 0; i < NUM_VOICE_PARAM; i++)
	{
	m_nVoiceDataTxt[nTG]  += nDtoH[(pData[i] & 0xF0)/16];
	m_nVoiceDataTxt[nTG]  += nDtoH[pData[i] & 0x0F]  ;
	if ( i < (NUM_VOICE_PARAM-1)  ) 
    	{
     	 m_nVoiceDataTxt[nTG] += " ";
    	}
	}
}

uint8_t *CPerformanceConfig::GetVoiceDataFromTxt (unsigned nTG) 
{
	assert (nTG < CConfig::AllToneGenerators);
	static uint8_t pData[NUM_VOICE_PARAM];
	std::string nHtoD="0123456789ABCDEF";
	 
 	for (int i=0; i<NUM_VOICE_PARAM * 3; i=i+3)
	{
  	pData[i/3] = ((nHtoD.find(toupper(m_nVoiceDataTxt[nTG][i]),0) * 16 + nHtoD.find(toupper(m_nVoiceDataTxt[nTG][i+1]),0))) & 0xFF ;
	} 
  
	return pData;
}

bool CPerformanceConfig::VoiceDataFilled(unsigned nTG) 
{
	return (strcmp(m_nVoiceDataTxt[nTG].c_str(),"") != 0) ;
}

std::string CPerformanceConfig::GetPerformanceFileName(unsigned nID)
{
	assert (nID < NUM_PERFORMANCES);
	std::string FileN = "";
	if ((m_nPerformanceBank==0) && (nID == 0)) // in order to assure retrocompatibility
	{
		FileN += DEFAULT_PERFORMANCE_FILENAME;
	}
	else
	{
		// Build up from the index, "_", performance name, and ".ini"
		// NB: Index on disk = index in memory + 1
		std::string nIndex = "000000";
		nIndex += std::to_string(nID+1);
		nIndex = nIndex.substr(nIndex.length()-6,6);
		FileN += nIndex;
		FileN += "_";
		FileN += m_PerformanceFileName[nID];
		FileN += ".ini";
	}
	return FileN;
}

std::string CPerformanceConfig::GetPerformanceFullFilePath(unsigned nID)
{
	assert (nID < NUM_PERFORMANCES);
	std::string FileN = "SD:/";
	if ((m_nPerformanceBank == 0) && (nID == 0))
	{
		// Special case for the legacy Bank 1/Default performance
		FileN += DEFAULT_PERFORMANCE_FILENAME;
	}
	else
	{
		if (m_bPerformanceDirectoryExists)
		{
			FileN += PERFORMANCE_DIR;
			FileN += AddPerformanceBankDirName(m_nPerformanceBank);
			FileN += "/";
			FileN += GetPerformanceFileName(nID);
		}
	}
	return FileN;
}

std::string CPerformanceConfig::GetPerformanceName(unsigned nID)
{
	assert (nID < NUM_PERFORMANCES);
	if ((m_nPerformanceBank==0) && (nID == 0)) // in order to assure retrocompatibility
	{
		return DEFAULT_PERFORMANCE_NAME;
	}
	else
	{
		return m_PerformanceFileName[nID];
	}
}

unsigned CPerformanceConfig::GetLastPerformance()
{
	return m_nLastPerformance;
}

unsigned CPerformanceConfig::GetLastPerformanceBank()
{
	return m_nLastPerformanceBank;
}

unsigned CPerformanceConfig::GetActualPerformanceID()
{
	return m_nActualPerformance;
}

void CPerformanceConfig::SetActualPerformanceID(unsigned nID)
{
	assert (nID < NUM_PERFORMANCES);
	m_nActualPerformance = nID;
}

unsigned CPerformanceConfig::GetActualPerformanceBankID()
{
	return m_nActualPerformanceBank;
}

void CPerformanceConfig::SetActualPerformanceBankID(unsigned nBankID)
{
	assert (nBankID < NUM_PERFORMANCE_BANKS);
	m_nActualPerformanceBank = nBankID;
}

bool CPerformanceConfig::GetInternalFolderOk()
{
	return m_bPerformanceDirectoryExists;
}

bool CPerformanceConfig::IsValidPerformance(unsigned nID)
{
	if (nID < NUM_PERFORMANCES)
	{
		if (!m_PerformanceFileName[nID].empty())
		{
			return true;
		}
	}
	
	return false;
}


bool CPerformanceConfig::CheckFreePerformanceSlot(void)
{
	if (m_nLastPerformance < NUM_PERFORMANCES-1)
	{
		// There is a free slot...
		return true;
	}
	else
	{
		return false;
	}
}

bool CPerformanceConfig::CreateNewPerformanceFile(void)
{
	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=""; 
	unsigned nNewPerformance = m_nLastPerformance + 1;
	std::string nFileName;
	std::string nPath;
	std::string nIndex = "000000";
	nIndex += std::to_string(nNewPerformance+1);  // Index on disk = index in memory+1
	nIndex = nIndex.substr(nIndex.length()-6,6);
	

	nFileName = nIndex;
	nFileName += "_";
	if (strcmp(sPerformanceName.c_str(),"") == 0)
	{
		nFileName += "Perf";
		nFileName += nIndex;
	}		
	else
	{
		nFileName +=sPerformanceName.substr(0,14);
	}
	nFileName += ".ini";
	m_PerformanceFileName[nNewPerformance]= sPerformanceName;
	
	nPath = "SD:/" ;
	nPath += PERFORMANCE_DIR;
	nPath += AddPerformanceBankDirName(m_nPerformanceBank);
	nPath += "/";
	nFileName = nPath + nFileName;
	
	FIL File;
	FRESULT Result = f_open (&File, nFileName.c_str(), FA_WRITE | FA_CREATE_ALWAYS);
	if (Result != FR_OK)
	{
		m_PerformanceFileName[nNewPerformance]=nullptr;
		return false;
	}

	if (f_close (&File) != FR_OK)
	{
		m_PerformanceFileName[nNewPerformance]=nullptr;
		return false;
	}
	
	m_nLastPerformance = nNewPerformance;
	m_nActualPerformance = nNewPerformance;
	new (&m_Properties) CPropertiesFatFsFile(nFileName.c_str(), m_pFileSystem);
	
	return true;
}

bool CPerformanceConfig::ListPerformances()
{
	// Clear any existing lists of performances
	for (unsigned i=0; i<NUM_PERFORMANCES; i++)
	{
		m_PerformanceFileName[i].clear();
	}
	m_nLastPerformance=0;
	if (m_nPerformanceBank == 0)
	{
		// The first bank is the default performance directory
	   	m_PerformanceFileName[0]=DEFAULT_PERFORMANCE_NAME; // in order to assure retrocompatibility
	}
	
	if (m_bPerformanceDirectoryExists)
	{
		DIR Directory;
		FILINFO FileInfo;
		FRESULT Result;
		std::string PerfDir = "SD:/" PERFORMANCE_DIR + AddPerformanceBankDirName(m_nPerformanceBank);
#ifdef VERBOSE_DEBUG
		LOGNOTE("Listing Performances from %s", PerfDir.c_str());
#endif
		Result = f_opendir (&Directory, PerfDir.c_str());
		if (Result != FR_OK)
		{
			return false;
		}
		unsigned nPIndex;

		Result = f_findfirst (&Directory, &FileInfo, PerfDir.c_str(), "*.ini");
		for (unsigned i = 0; Result == FR_OK && FileInfo.fname[0]; i++)
		{
			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)
				{
					// 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())
						{
							if(nPIndex > m_nLastPerformance)
							{
								m_nLastPerformance=nPIndex;
							}

							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);
		}
		f_closedir (&Directory);
	}
	
	return true;
}

void CPerformanceConfig::SetNewPerformance (unsigned nID)
{
	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))
		{
			return nID;
		}
	}

	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(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;
}

void CPerformanceConfig::SetNewPerformanceName(std::string nName)
{
	int  i = nName.length();
	do
	{
		--i;
	}
	while (i>=0 && nName[i] == 32);
	nName=nName.substr(0,i+1)  ;
	
	NewPerformanceName = 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((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(), GetPerformanceFileName(nID).c_str());
	if (Result == FR_OK && FileInfo.fname[0])
	{
		FileN += "/";
		FileN += GetPerformanceFileName(nID);
		Result=f_unlink (FileN.c_str());
		if (Result == FR_OK)
		{
			SetNewPerformance(0);
			m_nActualPerformance =0;
			//nMenuSelectedPerformance=0;
			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;
}

std::string CPerformanceConfig::VectorToString (std::vector<unsigned> pParams)
{
	std::string params = "";
	for (size_t i = 0; i < pParams.size(); i++)
	{
		if (i != 0) {
			params += ",";
		}
		params += std::to_string(pParams[i]);
	}
	return params;
}

std::vector<unsigned> CPerformanceConfig::StringToVector (std::string sParams) const
{
	std::vector<unsigned> tokens;
	if (sParams.empty()) {
		return tokens; 
	}

	char delimiter = ',';
	std::stringstream ss(sParams);
	std::string temp;
	while (getline(ss, temp, delimiter))
	{
		tokens.push_back(stoi(temp));
	}
	return tokens;
}