Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for subdirectories in the SysEx voices directory #473

Merged
merged 6 commits into from
Apr 7, 2023
Merged
201 changes: 125 additions & 76 deletions src/sysexfileloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ CSysExFileLoader::~CSysExFileLoader (void)
void CSysExFileLoader::Load (bool bHeaderlessSysExVoices)
{
m_nNumHighestBank = 0;
m_nBanksLoaded = 0;

DIR *pDirectory = opendir (m_DirName.c_str ());
if (!pDirectory)
Expand All @@ -96,107 +97,155 @@ void CSysExFileLoader::Load (bool bHeaderlessSysExVoices)
dirent *pEntry;
while ((pEntry = readdir (pDirectory)) != nullptr)
{
unsigned nBank;
size_t nLen = strlen (pEntry->d_name);
LoadBank(m_DirName.c_str (), pEntry->d_name, bHeaderlessSysExVoices, 0);
}
LOGDBG ("%u Banks loaded. Highest Bank loaded: #%u", m_nBanksLoaded, m_nNumHighestBank);

if ( nLen < 5 // "[NNNN]N[_name].syx"
|| strcasecmp (&pEntry->d_name[nLen-4], ".syx") != 0
|| sscanf (pEntry->d_name, "%u", &nBank) != 1)
{
LOGWARN ("%s: Invalid filename format", pEntry->d_name);
closedir (pDirectory);
}

continue;
}
void CSysExFileLoader::LoadBank (const char * sDirName, const char * sBankName, bool bHeaderlessSysExVoices, unsigned nSubDirCount)
{
unsigned nBank;
size_t nLen = strlen (sBankName);

if ( nLen < 5 // "[NNNN]N[_name].syx"
|| strcasecmp (&sBankName[nLen-4], ".syx") != 0
|| sscanf (sBankName, "%u", &nBank) != 1)
{
// See if this is a subdirectory...
std::string Dirname (sDirName);
Dirname += "/";
Dirname += sBankName;

if (nBank > MaxVoiceBankID)
DIR *pDirectory = opendir (Dirname.c_str ());
if (pDirectory)
{
LOGWARN ("Bank #%u is not supported", nBank);
if (nSubDirCount >= MaxSubDirs)
{
LOGWARN ("Too many nested subdirectories: %s", sBankName);
return;
}

LOGDBG ("Processing subdirectory %s", sBankName);

continue;
dirent *pEntry;
while ((pEntry = readdir (pDirectory)) != nullptr)
{
LoadBank(Dirname.c_str (), pEntry->d_name, bHeaderlessSysExVoices, nSubDirCount+1);
}
closedir (pDirectory);
}

if (m_pVoiceBank[nBank])
else
{
LOGWARN ("Bank #%u already loaded", nBank);

continue;
LOGWARN ("%s: Invalid filename format", sBankName);
}

m_pVoiceBank[nBank] = new TVoiceBank;
assert (m_pVoiceBank[nBank]);
assert (sizeof(TVoiceBank) == VoiceSysExHdrSize + VoiceSysExSize);
return;
}

// File and UI handling requires banks to be 1..indexed.
// Internally (and via MIDI) we need 0..indexed.
// Any mention of a BankID internally is assumed to be 0..indexed.
unsigned nBankIdx = nBank - 1;

// BankIdx goes from 0 to MaxVoiceBankID inclusive
if (nBankIdx > MaxVoiceBankID)
{
LOGWARN ("Bank #%u is not supported", nBank);

return;
}

std::string Filename (m_DirName);
Filename += "/";
Filename += pEntry->d_name;
if (m_pVoiceBank[nBankIdx])
{
LOGWARN ("Bank #%u already loaded", nBank);

return;
}

FILE *pFile = fopen (Filename.c_str (), "rb");
if (pFile)
m_pVoiceBank[nBankIdx] = new TVoiceBank;
assert (m_pVoiceBank[nBankIdx]);
assert (sizeof(TVoiceBank) == VoiceSysExHdrSize + VoiceSysExSize);

std::string Filename (sDirName);
Filename += "/";
Filename += sBankName;

FILE *pFile = fopen (Filename.c_str (), "rb");
if (pFile)
{
bool bBankLoaded = false;
if ( fread (m_pVoiceBank[nBankIdx], VoiceSysExHdrSize+VoiceSysExSize, 1, pFile) == 1
&& m_pVoiceBank[nBankIdx]->StatusStart == 0xF0
&& m_pVoiceBank[nBankIdx]->CompanyID == 0x43
&& m_pVoiceBank[nBankIdx]->Format == 0x09
&& m_pVoiceBank[nBankIdx]->StatusEnd == 0xF7)
{
bool bBankLoaded = false;
if ( fread (m_pVoiceBank[nBank], VoiceSysExHdrSize+VoiceSysExSize, 1, pFile) == 1
&& m_pVoiceBank[nBank]->StatusStart == 0xF0
&& m_pVoiceBank[nBank]->CompanyID == 0x43
&& m_pVoiceBank[nBank]->Format == 0x09
&& m_pVoiceBank[nBank]->StatusEnd == 0xF7)
if (m_nBanksLoaded % 100 == 0)
{
LOGDBG ("Bank #%u successfully loaded", nBank);
LOGDBG ("Banks successfully loaded #%u", m_nBanksLoaded);
}
//LOGDBG ("Bank #%u successfully loaded", nBank);

m_BankFileName[nBank] = pEntry->d_name;
m_BankFileName[nBankIdx] = sBankName;
if (nBank > m_nNumHighestBank)
{
// This is the bank ID of the highest loaded bank
m_nNumHighestBank = nBank;
}
m_nBanksLoaded++;
bBankLoaded = true;
}
else if (bHeaderlessSysExVoices)
{
// Config says to accept headerless SysEx Voice Banks
// so reset file pointer and try again.
fseek (pFile, 0, SEEK_SET);
if (fread (m_pVoiceBank[nBankIdx]->Voice, VoiceSysExSize, 1, pFile) == 1)
{
if (m_nBanksLoaded % 100 == 0)
{
LOGDBG ("Banks successfully loaded #%u", m_nBanksLoaded);
}
//LOGDBG ("Bank #%u successfully loaded (headerless)", nBank);

// Add in the missing header items.
// Naturally it isn't possible to validate these!
m_pVoiceBank[nBankIdx]->StatusStart = 0xF0;
m_pVoiceBank[nBankIdx]->CompanyID = 0x43;
m_pVoiceBank[nBankIdx]->Format = 0x09;
m_pVoiceBank[nBankIdx]->ByteCountMS = 0x20;
m_pVoiceBank[nBankIdx]->ByteCountLS = 0x00;
m_pVoiceBank[nBankIdx]->Checksum = 0x00;
m_pVoiceBank[nBankIdx]->StatusEnd = 0xF7;

m_BankFileName[nBankIdx] = sBankName;
if (nBank > m_nNumHighestBank)
{
// This is the bank ID of the highest loaded bank
m_nNumHighestBank = nBank;
}
bBankLoaded = true;
m_nBanksLoaded++;
}
else if (bHeaderlessSysExVoices)
{
// Config says to accept headerless SysEx Voice Banks
// so reset file pointer and try again.
fseek (pFile, 0, SEEK_SET);
if (fread (m_pVoiceBank[nBank]->Voice, VoiceSysExSize, 1, pFile) == 1)
{
LOGDBG ("Bank #%u successfully loaded (headerless)", nBank);

// Add in the missing header items.
// Naturally it isn't possible to validate these!
m_pVoiceBank[nBank]->StatusStart = 0xF0;
m_pVoiceBank[nBank]->CompanyID = 0x43;
m_pVoiceBank[nBank]->Format = 0x09;
m_pVoiceBank[nBank]->ByteCountMS = 0x20;
m_pVoiceBank[nBank]->ByteCountLS = 0x00;
m_pVoiceBank[nBank]->Checksum = 0x00;
m_pVoiceBank[nBank]->StatusEnd = 0xF7;

m_BankFileName[nBank] = pEntry->d_name;
if (nBank > m_nNumHighestBank)
{
// This is the bank ID of the highest loaded bank
m_nNumHighestBank = nBank;
}
bBankLoaded = true;
}
}

if (!bBankLoaded)
{
LOGWARN ("%s: Invalid size or format", Filename.c_str ());

delete m_pVoiceBank[nBank];
m_pVoiceBank[nBank] = nullptr;
}

fclose (pFile);
}
else

if (!bBankLoaded)
{
delete m_pVoiceBank[nBank];
m_pVoiceBank[nBank] = nullptr;
LOGWARN ("%s: Invalid size or format", Filename.c_str ());

delete m_pVoiceBank[nBankIdx];
m_pVoiceBank[nBankIdx] = nullptr;
}
}

closedir (pDirectory);
fclose (pFile);
}
else
{
delete m_pVoiceBank[nBankIdx];
m_pVoiceBank[nBankIdx] = nullptr;
}
}

std::string CSysExFileLoader::GetBankName (unsigned nBankID)
Expand Down
4 changes: 4 additions & 0 deletions src/sysexfileloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class CSysExFileLoader // Loader for DX7 .syx files
static const size_t SizeSingleVoice = 156;
static const unsigned VoiceSysExHdrSize = 8; // Additional (optional) Header/Footer bytes for bank of 32 voices
static const unsigned VoiceSysExSize = 4096; // Bank of 32 voices as per DX7 MIDI Spec
static const unsigned MaxSubDirs = 3; // Number of nested subdirectories supported.

struct TVoiceBank
{
Expand Down Expand Up @@ -75,11 +76,14 @@ class CSysExFileLoader // Loader for DX7 .syx files
std::string m_DirName;

unsigned m_nNumHighestBank;
unsigned m_nBanksLoaded;

TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1];
std::string m_BankFileName[MaxVoiceBankID+1];

static uint8_t s_DefaultVoice[SizeSingleVoice];

void LoadBank (const char * sDirName, const char * sBankName, bool bHeaderlessSysExVoices, unsigned nSubDirCount);
};

#endif
13 changes: 13 additions & 0 deletions submod.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -ex
git submodule update --init --recursive
cd circle-stdlib/
git checkout e318f89 # Needed to support Circle develop?
cd -
cd circle-stdlib/libs/circle
git checkout ec09d7e # develop
cd -
cd circle-stdlib/libs/circle-newlib
git checkout 48bf91d # needed for circle ec09d7e
cd -