Load voices from .syx files in /sysex/voice directory

https://github.com/probonopd/MiniDexed/issues/11#issuecomment-1052239623
Thanks @rsta2
pull/31/head
probonopd 3 years ago
parent 6291c234ca
commit 79fe4aabbf
  1. 4
      src/Makefile
  2. 29
      src/minidexed.cpp
  3. 2
      src/minidexed.h
  4. 237
      src/sysexfileloader.cpp
  5. 61
      src/sysexfileloader.h

@ -5,8 +5,8 @@
CIRCLE_STDLIB_DIR = ../circle-stdlib
SYNTH_DEXED_DIR = ../Synth_Dexed/src
OBJS = main.o kernel.o minidexed.o pckeyboard.o $(SYNTH_DEXED_DIR)/synth_dexed.o \
perftimer.o
OBJS = main.o kernel.o minidexed.o pckeyboard.o sysexfileloader.o perftimer.o \
$(SYNTH_DEXED_DIR)/synth_dexed.o
INCLUDE += -I $(SYNTH_DEXED_DIR)

@ -12,6 +12,9 @@
#define MIDI_NOTE_OFF 0b1000
#define MIDI_NOTE_ON 0b1001
#define MIDI_AFTERTOUCH 0xA0
#define MIDI_CONTROL_CHANGE 0xB0
#define MIDI_CC_BANK_SELECT_MSB 0 // TODO: not supported
#define MIDI_CC_BANK_SELECT_LSB 32
#define MIDI_PROGRAM_CHANGE 0xC0
#define MIDI_PITCH_BEND 0xE0
@ -25,6 +28,8 @@ CPerformanceTimer GetChunkTimer ("GetChunk", 1000000U * CHUNK_SIZE/2 / SAMPLE_RA
bool CMiniDexed::Initialize (void)
{
m_SysExFileLoader.Load ();
if (!m_Serial.Initialize(31250))
{
return false;
@ -144,15 +149,33 @@ void CMiniDexed::MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLeng
}
#endif
if (pPacket[0] == MIDI_CONTROL_CHANGE)
{
if (pPacket[1] == MIDI_CC_BANK_SELECT_LSB)
{
if (pPacket[2] < 1 || pPacket[2] > 128)
{
return;
}
printf ("Select voice bank %u\n", (unsigned) pPacket[2]);
s_pThis->m_SysExFileLoader.SelectVoiceBank (pPacket[2]-1);
}
return;
}
if (pPacket[0] == MIDI_PROGRAM_CHANGE)
{
if(pPacket[1] < 1 || pPacket[1] > 32) {
return;
}
printf ("Loading voice %d\n", (unsigned) pPacket[1]);
s_pThis->loadVoiceParameters(voices_bank[0][(unsigned) pPacket[1] - 1]);
printf ("Loading voice %u\n", (unsigned) pPacket[1]);
uint8_t Buffer[156];
s_pThis->m_SysExFileLoader.GetVoice (pPacket[1]-1, Buffer);
s_pThis->loadVoiceParameters(Buffer);
char buf_name[11];
memset(reinterpret_cast<void*>(buf_name), 0, 11); // Initialize with 0x00 chars
memset(buf_name, 0, 11); // Initialize with 0x00 chars
s_pThis->setName(buf_name);
printf ("%s\n", buf_name);
return;

@ -15,6 +15,7 @@
#include <circle/pwmsoundbasedevice.h>
#include <circle/i2ssoundbasedevice.h>
#include <circle/hdmisoundbasedevice.h>
#include "sysexfileloader.h"
#include "pckeyboard.h"
#define SAMPLE_RATE 48000
@ -51,6 +52,7 @@ class CMiniDexed : public Dexed
boolean m_bUseSerial;
unsigned m_nSerialState;
u8 m_SerialMessage[3];
CSysExFileLoader m_SysExFileLoader;
static CMiniDexed *s_pThis;
};

@ -0,0 +1,237 @@
//
// sysexfileloader.cpp
//
#include "sysexfileloader.h"
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <circle/logger.h>
extern uint8_t voices_bank[1][32][156];
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 ")
};
CSysExFileLoader::CSysExFileLoader (const char *pDirName)
: m_DirName (pDirName),
m_nBankID (0)
{
m_DirName += "/voice";
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 (void)
{
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)
{
unsigned nBank;
size_t nLen = strlen (pEntry->d_name);
if ( nLen < 5 // "[NNNN]N[_name].syx"
|| strcmp (&pEntry->d_name[nLen-4], ".syx") != 0
|| sscanf (pEntry->d_name, "%u", &nBank) != 1)
{
LOGWARN ("%s: Invalid filename format", pEntry->d_name);
continue;
}
if ( nBank < 1
|| nBank > MaxVoiceBankID+1)
{
LOGWARN ("Bank #%u is not supported", nBank);
continue;
}
nBank--; // zero-based internally
if (m_pVoiceBank[nBank])
{
LOGWARN ("Bank #%u already loaded", nBank+1);
continue;
}
m_pVoiceBank[nBank] = new TVoiceBank;
assert (m_pVoiceBank[nBank]);
std::string Filename (m_DirName);
Filename += "/";
Filename += pEntry->d_name;
FILE *pFile = fopen (Filename.c_str (), "rb");
if (pFile)
{
if ( fread (m_pVoiceBank[nBank], sizeof (TVoiceBank), 1, pFile) == 1
&& m_pVoiceBank[nBank]->StatusStart == 0xF0
&& m_pVoiceBank[nBank]->CompanyID == 0x43
&& m_pVoiceBank[nBank]->Format == 0x09
&& m_pVoiceBank[nBank]->StatusEnd == 0xF7)
{
LOGDBG ("Bank #%u successfully loaded", nBank+1);
}
else
{
LOGWARN ("%s: Invalid size or format", Filename.c_str ());
delete m_pVoiceBank[nBank];
m_pVoiceBank[nBank] = nullptr;
}
fclose (pFile);
}
else
{
delete m_pVoiceBank[nBank];
m_pVoiceBank[nBank] = nullptr;
}
}
closedir (pDirectory);
}
void CSysExFileLoader::SelectVoiceBank (unsigned nBankID)
{
if (nBankID <= MaxVoiceBankID)
{
m_nBankID = nBankID;
}
}
void CSysExFileLoader::GetVoice (unsigned nVoiceID, uint8_t *pVoiceData)
{
if (nVoiceID <= VoicesPerBank)
{
assert (m_nBankID <= MaxVoiceBankID);
if (m_pVoiceBank[m_nBankID])
{
DecodePackedVoice (m_pVoiceBank[m_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 (m_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];
}
}
}

@ -0,0 +1,61 @@
//
// sysexfileloader.h
//
// See: https://github.com/asb2m10/dexed/blob/master/Documentation/sysex-format.txt
//
#ifndef _sysexfileloader_h
#define _sysexfileloader_h
#include <stdint.h>
#include <string>
#include <circle/macros.h>
class CSysExFileLoader // Loader for DX7 .syx files
{
public:
static const unsigned MaxVoiceBankID = 127; // TODO? 16383
static const unsigned VoicesPerBank = 32;
static const size_t SizePackedVoice = 128;
static const size_t SizeSingleVoice = 156;
struct TVoiceBank
{
uint8_t StatusStart; // 0xF0
uint8_t CompanyID; // 0x43
uint8_t SubStatus; // 0x00
uint8_t Format; // 0x09
uint8_t ByteCountMS; // 0x20
uint8_t ByteCountLS; // 0x00
uint8_t Voice[VoicesPerBank][SizePackedVoice];
uint8_t Checksum;
uint8_t StatusEnd; // 0xF7
}
PACKED;
public:
CSysExFileLoader (const char *pDirName = "/sysex");
~CSysExFileLoader (void);
void Load (void);
void SelectVoiceBank (unsigned nBankID); // 0 .. 127
void GetVoice (unsigned nVoiceID, // 0 .. 31
uint8_t *pVoiceData); // returns unpacked format (156 bytes)
private:
static void DecodePackedVoice (const uint8_t *pPackedData, uint8_t *pDecodedData);
private:
std::string m_DirName;
TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1];
unsigned m_nBankID;
static uint8_t s_DefaultVoice[SizeSingleVoice];
};
#endif
Loading…
Cancel
Save