//
// sysexfileloader.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// 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 .
//
#include "sysexfileloader.h"
#include
#include
#include
#include
#include
#include
#include
#include "voices.c"
LOGMODULE ("syxfile");
/*
uint8_t CSysExFileLoader::s_DefaultVoice[SizeSingleVoice] = // FM-Piano
{
95, 29, 20, 50, 99, 95, 00, 00, 41, 00, 19, 00, 00, 03, 00, 06, 79, 00, 01, 00, 14, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune
95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 00, 99, 00, 01, 00, 00, // OP5
95, 29, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 06, 89, 00, 01, 00, 07, // OP4
95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 07, // OP3
95, 50, 35, 78, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 07, 58, 00, 14, 00, 07, // OP2
96, 25, 25, 67, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 10, // OP1
94, 67, 95, 60, 50, 50, 50, 50, // 4 * pitch EG rates, 4 * pitch EG level
04, 06, 00, // algorithm, feedback, osc sync
34, 33, 00, 00, 00, 04, // lfo speed, lfo delay, lfo pitch_mod_depth, lfo_amp_mod_depth, lfo_sync, lfo_waveform
03, 24, // pitch_mod_sensitivity, transpose
70, 77, 45, 80, 73, 65, 78, 79, 00, 00 // 10 * char for name ("DEFAULT ")
};
*/
uint8_t CSysExFileLoader::s_DefaultVoice[SizeSingleVoice] = // INIT VOICE
{
99, 99, 99, 99, 99, 99, 99, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 07, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune
99, 99, 99, 99, 99, 99, 99, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 07, // OP5
99, 99, 99, 99, 99, 99, 99, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 07, // OP4
99, 99, 99, 99, 99, 99, 99, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 07, // OP3
99, 99, 99, 99, 99, 99, 99, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 07, // OP2
99, 99, 99, 99, 99, 99, 99, 00, 00, 00, 00, 00, 00, 00, 00, 00, 99, 00, 01, 00, 07, // OP1
99, 99, 99, 99, 50, 50, 50, 50, // 4 * pitch EG rates, 4 * pitch EG level
00, 00, 01, // algorithm, feedback, osc sync
35, 00, 00, 00, 01, 00, // lfo speed, lfo delay, lfo pitch_mod_depth, lfo_amp_mod_depth, lfo_sync, lfo_waveform
03, 24, // pitch_mod_sensitivity, transpose
73, 78, 73, 84, 32, 86, 79, 73, 67, 69 // 10 * char for name
};
CSysExFileLoader::CSysExFileLoader (const char *pDirName)
: m_DirName (pDirName)
{
m_DirName += "/voice";
m_nNumHighestBank = 0;
for (unsigned i = 0; i <= MaxVoiceBankID; i++)
{
m_pVoiceBank[i] = nullptr;
}
}
CSysExFileLoader::~CSysExFileLoader (void)
{
for (unsigned i = 0; i <= MaxVoiceBankID; i++)
{
delete m_pVoiceBank[i];
}
}
void CSysExFileLoader::Load (bool bHeaderlessSysExVoices)
{
m_nNumHighestBank = 0;
m_nBanksLoaded = 0;
DIR *pDirectory = opendir (m_DirName.c_str ());
if (!pDirectory)
{
LOGWARN ("Directory %s not found", m_DirName.c_str ());
return;
}
dirent *pEntry;
while ((pEntry = readdir (pDirectory)) != nullptr)
{
LoadBank(m_DirName.c_str (), pEntry->d_name, bHeaderlessSysExVoices, 0);
}
LOGDBG ("%u Banks loaded. Highest Bank loaded: #%u", m_nBanksLoaded, m_nNumHighestBank+1);
closedir (pDirectory);
}
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;
DIR *pDirectory = opendir (Dirname.c_str ());
if (pDirectory)
{
if (nSubDirCount >= MaxSubDirs)
{
LOGWARN ("Too many nested subdirectories: %s", sBankName);
return;
}
LOGDBG ("Processing subdirectory %s", sBankName);
dirent *pEntry;
while ((pEntry = readdir (pDirectory)) != nullptr)
{
LoadBank(Dirname.c_str (), pEntry->d_name, bHeaderlessSysExVoices, nSubDirCount+1);
}
closedir (pDirectory);
}
else
{
LOGWARN ("%s: Invalid filename format", sBankName);
}
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;
}
if (m_pVoiceBank[nBankIdx])
{
LOGWARN ("Bank #%u already loaded", nBank);
return;
}
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)
{
if (m_nBanksLoaded % 100 == 0)
{
LOGDBG ("Banks successfully loaded #%u", m_nBanksLoaded);
}
//LOGDBG ("Bank #%u successfully loaded", nBank);
m_BankFileName[nBankIdx] = sBankName;
if (nBankIdx > m_nNumHighestBank)
{
// This is the bank ID of the highest loaded bank
m_nNumHighestBank = nBankIdx;
}
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 (nBankIdx > m_nNumHighestBank)
{
// This is the bank ID of the highest loaded bank
m_nNumHighestBank = nBankIdx;
}
bBankLoaded = true;
m_nBanksLoaded++;
}
}
if (!bBankLoaded)
{
LOGWARN ("%s: Invalid size or format", Filename.c_str ());
delete m_pVoiceBank[nBankIdx];
m_pVoiceBank[nBankIdx] = nullptr;
}
fclose (pFile);
}
else
{
delete m_pVoiceBank[nBankIdx];
m_pVoiceBank[nBankIdx] = nullptr;
}
}
std::string CSysExFileLoader::GetBankName (unsigned nBankID)
{
if (nBankID <= MaxVoiceBankID)
{
std::string Result = m_BankFileName[nBankID];
size_t nLen = Result.length ();
if (nLen > 4)
{
Result.resize (nLen-4); // remove file extension
unsigned nBank;
char BankName[30+1];
if (sscanf (Result.c_str (), "%u_%30s", &nBank, BankName) == 2)
{
Result = BankName;
return Result;
}
}
}
return "NO NAME";
}
std::string CSysExFileLoader::GetVoiceName (unsigned nBankID, unsigned nVoiceID)
{
if ((nBankID <= MaxVoiceBankID) && (nVoiceID < VoicesPerBank))
{
if (IsValidBank(nBankID))
{
// The name is the last 10 characters of the voice data
char sVoiceName[11];
strncpy (sVoiceName, (char *)((char *)&(m_pVoiceBank[nBankID]->Voice[nVoiceID]) + SizePackedVoice - 10), 10);
sVoiceName[10] = 0;
std::string result(sVoiceName);
return result;
}
}
return "INIT VOICE";
}
unsigned CSysExFileLoader::GetNextBankUp (unsigned nBankID)
{
// Find the next loaded bank "up" from the provided bank ID
for (unsigned id=nBankID+1; id <= m_nNumHighestBank; id++)
{
if (IsValidBank(id))
{
return id;
}
}
// Handle wrap-around
for (unsigned id=0; id= 0; id--)
{
if (IsValidBank((unsigned)id))
{
return id;
}
}
// Handle wrap-around
for (unsigned id=m_nNumHighestBank; id>nBankID; id--)
{
if (IsValidBank(id))
{
return id;
}
}
// If we get here there are no other banks!
return nBankID;
}
bool CSysExFileLoader::IsValidBank (unsigned nBankID)
{
if (m_pVoiceBank[nBankID])
{
// Use a valid "status start/end" as an indicator of a valid loaded bank
if ((m_pVoiceBank[nBankID]->StatusStart == 0xF0) &&
(m_pVoiceBank[nBankID]->StatusEnd == 0xF7))
{
return true;
}
}
return false;
}
unsigned CSysExFileLoader::GetNumHighestBank (void)
{
return m_nNumHighestBank;
}
void CSysExFileLoader::GetVoice (unsigned nBankID, unsigned nVoiceID, uint8_t *pVoiceData)
{
if ( nBankID <= MaxVoiceBankID
&& nVoiceID <= VoicesPerBank)
{
if (IsValidBank(nBankID))
{
DecodePackedVoice (m_pVoiceBank[nBankID]->Voice[nVoiceID], pVoiceData);
return;
}
else
{
// Use default voices_bank instead of s_DefaultVoice for bank 0,
// if the bank was not successfully loaded from disk.
if (nBankID == 0)
{
memcpy (pVoiceData, voices_bank[0][nVoiceID], SizeSingleVoice);
return;
}
}
}
memcpy (pVoiceData, s_DefaultVoice, SizeSingleVoice);
}
// See: https://github.com/bwhitman/learnfm/blob/master/dx7db.py
void CSysExFileLoader::DecodePackedVoice (const uint8_t *pPackedData, uint8_t *pDecodedData)
{
const uint8_t *p = pPackedData;
uint8_t *o = pDecodedData;
memset (o, 0, 156);
for (unsigned op = 0; op < 6; op++)
{
memcpy(&o[op*21], &p[op*17], 11);
uint8_t leftrightcurves = p[op*17+11];
o[op * 21 + 11] = leftrightcurves & 3;
o[op * 21 + 12] = (leftrightcurves >> 2) & 3;
uint8_t detune_rs = p[op * 17 + 12];
o[op * 21 + 13] = detune_rs & 7;
o[op * 21 + 20] = detune_rs >> 3;
uint8_t kvs_ams = p[op * 17 + 13];
o[op * 21 + 14] = kvs_ams & 3;
o[op * 21 + 15] = kvs_ams >> 2;
o[op * 21 + 16] = p[op * 17 + 14];
uint8_t fcoarse_mode = p[op * 17 + 15];
o[op * 21 + 17] = fcoarse_mode & 1;
o[op * 21 + 18] = fcoarse_mode >> 1;
o[op * 21 + 19] = p[op * 17 + 16];
}
memcpy (&o[126], &p[102], 9);
uint8_t oks_fb = p[111];
o[135] = oks_fb & 7;
o[136] = oks_fb >> 3;
memcpy (&o[137], &p[112], 4);
uint8_t lpms_lfw_lks = p[116];
o[141] = lpms_lfw_lks & 1;
o[142] = (lpms_lfw_lks >> 1) & 7;
o[143] = lpms_lfw_lks >> 4;
memcpy (&o[144], &p[117], 11);
o[155] = 0x3f;
// Clamp the unpacked patches to a known max.
static uint8_t maxes[] =
{
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, // osc6
3, 3, 7, 3, 7, 99, 1, 31, 99, 14,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, // osc5
3, 3, 7, 3, 7, 99, 1, 31, 99, 14,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, // osc4
3, 3, 7, 3, 7, 99, 1, 31, 99, 14,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, // osc3
3, 3, 7, 3, 7, 99, 1, 31, 99, 14,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, // osc2
3, 3, 7, 3, 7, 99, 1, 31, 99, 14,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, // osc1
3, 3, 7, 3, 7, 99, 1, 31, 99, 14,
99, 99, 99, 99, 99, 99, 99, 99, // pitch eg rate & level
31, 7, 1, 99, 99, 99, 99, 1, 5, 7, 48, // algorithm etc
126, 126, 126, 126, 126, 126, 126, 126, 126, 126, // name
127 // operator on/off
};
for (unsigned i = 0; i < 156; i++)
{
if (o[i] > maxes[i])
{
o[i] = maxes[i];
}
}
}