You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MicroDexed/dexed_sysex.cpp

548 lines
13 KiB

/*
MicroDexed
MicroDexed is a port of the Dexed sound engine
(https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6 with audio shield.
Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android
(c)2018-2020 H. Wirtz <wirtz@parasitstudio.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, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "config.h"
#include <Wire.h>
#include <SD.h>
#include "dexed.h"
#include "dexed_sysex.h"
extern AudioSourceMicroDexed * MicroDexed[NUM_DEXED];
extern void show_patch(uint8_t instance_id);
/*
void create_sysex_filename(uint8_t b, char* sysex_file_name, uint8_t instance_id)
{
// init and set name for actual bank
memset(sysex_file_name, 0, 4 + VOICE_NAME_LEN);
sysex_file_name[0] = '/';
itoa(b, &sysex_file_name[1], 10);
strcat(sysex_file_name, "/");
strcat(sysex_file_name, bank_names[instance_id][b]);
#ifdef DEBUG
Serial.print(F("Created sysex_filename from bank "));
Serial.print(b, DEC);
Serial.print(F(" and name "));
Serial.print(bank_names[instance_id][b]);
Serial.print(F(": ["));
Serial.print(sysex_file_name);
Serial.println(F("]"));
#endif
}
*/
void create_sysex_bankfilename(char* filename, uint8_t b, uint8_t instance_id)
{
// init and set name for actual bank
memset(filename, 0, FILENAME_LEN);
sprintf(filename, "/%d/%s", b, bank_names[instance_id][b]);
#ifdef DEBUG
Serial.print(F("Created sysex_filename from bank "));
Serial.print(b, DEC);
Serial.print(F(" and name "));
Serial.print(bank_names[instance_id][b]);
Serial.print(F(": ["));
Serial.print(filename);
Serial.println(F("]"));
#endif
}
void strip_extension(char* s, char* target)
{
char tmp[BANK_NAME_LEN];
char* token;
strcpy(tmp, s);
token = strtok(tmp, ".");
if (token == NULL)
strcpy(target, "*ERROR*");
else
strcpy(target, token);
}
bool get_voice_names_from_bank(uint8_t b, uint8_t instance_id)
{
File sysex;
uint8_t voice_counter = 0;
int32_t bulk_checksum_calc = 0;
int8_t bulk_checksum;
b = constrain(b, 0, MAX_BANKS - 1);
// erase all data for voice names
memset(voice_names[instance_id], 0, MAX_VOICES * VOICE_NAME_LEN);
if (sd_card > 0)
{
char sysex_file_name[FILENAME_LEN];
// init and set name for actual bank
create_sysex_bankfilename(sysex_file_name, b, instance_id);
#ifdef DEBUG
Serial.print(F("Reading voice names for bank ["));
Serial.print(sysex_file_name);
Serial.println(F("]"));
#endif
// try to open bank directory
sysex = SD.open(sysex_file_name);
if (!sysex)
return (false);
if (sysex.size() != 4104) // check sysex size
{
#ifdef DEBUG
Serial.println(F("E : SysEx file size wrong."));
#endif
return (false);
}
if (sysex.read() != 0xf0) // check sysex start-byte
{
#ifdef DEBUG
Serial.println(F("E : SysEx start byte not found."));
#endif
return (false);
}
if (sysex.read() != 0x43) // check sysex vendor is Yamaha
{
#ifdef DEBUG
Serial.println(F("E : SysEx vendor not Yamaha."));
#endif
return (false);
}
sysex.seek(4103);
if (sysex.read() != 0xf7) // check sysex end-byte
{
#ifdef DEBUG
Serial.println(F("E : SysEx end byte not found."));
#endif
return (false);
}
sysex.seek(3);
if (sysex.read() != 0x09) // check for sysex type (0x09=32 voices)
{
#ifdef DEBUG
Serial.println(F("E : SysEx type not 32 voices."));
#endif
return (false);
}
sysex.seek(4102); // Bulk checksum
bulk_checksum = sysex.read();
sysex.seek(6); // start of bulk data
for (uint16_t n = 0; n < 4096; n++)
{
uint8_t d = sysex.read();
if ((n % 128) >= 118 && (n % 128) < 128) // found the start of the voicename
{
voice_names[instance_id][voice_counter][(n % 128) - 118] = d;
}
if (n % 128 == 127)
voice_counter++;
bulk_checksum_calc -= d;
}
bulk_checksum_calc &= 0x7f;
#ifdef DEBUG
Serial.print(F("Bulk checksum : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" [0x"));
Serial.print(bulk_checksum, HEX);
Serial.println(F("]"));
#endif
if (bulk_checksum_calc != bulk_checksum)
{
#ifdef DEBUG
Serial.print(F("E : Bulk checksum mismatch : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" != 0x"));
Serial.println(bulk_checksum, HEX);
#endif
return (false);
}
}
return (false);
}
uint8_t get_bank_names(uint8_t instance_id)
{
File root;
uint8_t bank_counter = 0;
// erase all data for bank names
memset(bank_names[instance_id], 0, MAX_BANKS * BANK_NAME_LEN);
if (sd_card > 0)
{
char bankdir[4];
do
{
// init and set name for actual bank directory
memset(bankdir, 0, sizeof(bankdir));
bankdir[0] = '/';
itoa(bank_counter, &bankdir[1], 10);
// try to open directory
root = SD.open(bankdir);
if (!root)
break;
// read filenames
File entry = root.openNextFile();
if (!entry.isDirectory())
{
while (strncmp(entry.name(), "CONFIG", 6) == 0)
{
entry = root.openNextFile();
}
strcpy(bank_names[instance_id][bank_counter], entry.name());
#ifdef DEBUG
Serial.print(F("Found bank ["));
Serial.print(bank_names[instance_id][bank_counter]);
Serial.println(F("]"));
#endif
bank_counter++;
}
} while (root);
return (bank_counter);
}
else
return (0);
}
bool load_sysex(uint8_t b, uint8_t v, uint8_t instance_id)
{
#if DEBUG
bool found = false;
#endif
v = constrain(v, 0, MAX_VOICES - 1);
b = constrain(b, 0, MAX_BANKS - 1);
if (sd_card > 0)
{
File sysex;
char sysex_file_name[FILENAME_LEN];
uint8_t data[128];
create_sysex_bankfilename(sysex_file_name, b, instance_id);
sysex = SD.open(sysex_file_name);
if (!sysex)
{
#ifdef DEBUG
Serial.print(F("E : Cannot open "));
Serial.print(sysex_file_name);
Serial.println(F("from SD."));
#endif
return (false);
}
if (get_sysex_voice(sysex, v, data))
{
#ifdef DEBUG
Serial.print(F("Loading sysex "));
Serial.print(sysex_file_name);
Serial.print(F(" ["));
Serial.print(voice_names[instance_id][v]);
Serial.println(F("]"));
#endif
bool ret = MicroDexed[instance_id]->decodeVoice(data);
#ifdef DEBUG
show_patch(instance_id);
#endif
configuration.dexed[instance_id].transpose = MicroDexed[instance_id]->data[DEXED_VOICE_OFFSET + DEXED_TRANSPOSE];
return (ret);
}
#ifdef DEBUG
else
Serial.println(F("E : Cannot load voice data"));
#endif
}
#ifdef DEBUG
if (found == false)
Serial.println(F("E : File not found."));
#endif
return (false);
}
bool get_sysex_voice(File sysex, uint8_t voice_number, uint8_t* data)
{
uint16_t n;
int32_t bulk_checksum_calc = 0;
int8_t bulk_checksum;
if (sysex.size() != 4104) // check sysex size
{
#ifdef DEBUG
Serial.println(F("E : SysEx file size wrong."));
#endif
return (false);
}
if (sysex.read() != 0xf0) // check sysex start-byte
{
#ifdef DEBUG
Serial.println(F("E : SysEx start byte not found."));
#endif
return (false);
}
if (sysex.read() != 0x43) // check sysex vendor is Yamaha
{
#ifdef DEBUG
Serial.println(F("E : SysEx vendor not Yamaha."));
#endif
return (false);
}
sysex.seek(4103);
if (sysex.read() != 0xf7) // check sysex end-byte
{
#ifdef DEBUG
Serial.println(F("E : SysEx end byte not found."));
#endif
return (false);
}
sysex.seek(3);
if (sysex.read() != 0x09) // check for sysex type (0x09=32 voices)
{
#ifdef DEBUG
Serial.println(F("E : SysEx type not 32 voices."));
#endif
return (false);
}
sysex.seek(4102); // Bulk checksum
bulk_checksum = sysex.read();
sysex.seek(6); // start of bulk data
for (n = 0; n < 4096; n++)
{
uint8_t d = sysex.read();
if (n >= voice_number * 128 && n < (voice_number + 1) * 128)
{
data[n - (voice_number * 128)] = d;
}
bulk_checksum_calc -= d;
}
bulk_checksum_calc &= 0x7f;
#ifdef DEBUG
Serial.print(F("Bulk checksum : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" [0x"));
Serial.print(bulk_checksum, HEX);
Serial.println(F("]"));
#endif
if (bulk_checksum_calc != bulk_checksum)
{
#ifdef DEBUG
Serial.print(F("E : Bulk checksum mismatch : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" != 0x"));
Serial.println(bulk_checksum, HEX);
#endif
return (false);
}
MicroDexed[0]->render_time_max = 0;
return (true);
}
bool write_sysex_config(File sysex, config_t configuration)
{
uint16_t n;
char* c = (char*)&configuration;
// write sysex start
sysex.write(0xf0);
// write sysex vendor is unofficial SYSEX-ID for MicroDexed
sysex.write(0x67);
// write sysex format number (f=66..69; 1..4 MicroDexed setup(s))
sysex.write(0x66);
// write configuration data
for (n = 0; n < sizeof(configuration); n++)
sysex.write(c + n);
// write sysex end
sysex.write(0xf7);
return (true);
}
bool read_sysex_config(File sysex, config_t configuration)
{
uint16_t n;
int32_t bulk_checksum_calc = 0;
int8_t bulk_checksum;
if (sysex.size() != 47 || sysex.size() != 78) // check sysex size
{
#ifdef DEBUG
Serial.println(F("E : SysEx file size wrong."));
#endif
return (false);
}
if (sysex.read() != 0xf0) // check sysex start-byte
{
#ifdef DEBUG
Serial.println(F("E : SysEx start byte not found."));
#endif
return (false);
}
if (sysex.read() != 0x67) // check sysex vendor is unofficial SYSEX-ID for MicroDexed
{
#ifdef DEBUG
Serial.println(F("E : SysEx vendor not unofficial SYSEX-ID for MicroDexed."));
#endif
return (false);
}
sysex.seek(sysex.size());
if (sysex.read() != 0xf7) // check sysex end-byte
{
#ifdef DEBUG
Serial.println(F("E : SysEx end byte not found."));
#endif
return (false);
}
sysex.seek(3);
uint8_t dexed_sysex_setup_type = sysex.read();
if (dexed_sysex_setup_type >= 0x42 && dexed_sysex_setup_type < 0x45) // check for sysex type (0x75=MicroDexed setup for one to four instances)
{
#ifdef DEBUG
Serial.println(F("E : SysEx type not MicroDexed setup."));
#endif
return (false);
}
sysex.seek(4);
uint8_t sysex_config_size = sysex.read();
sysex.seek(sysex.size() - 1); // Bulk checksum
bulk_checksum = sysex.read();
sysex.seek(6); // start of bulk data
for (n = 0; n < sysex.size() - 2; n++)
{
uint8_t d = sysex.read();
bulk_checksum_calc -= d;
}
bulk_checksum_calc &= 0x7f;
#ifdef DEBUG
Serial.print(F("Bulk checksum : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" [0x"));
Serial.print(bulk_checksum, HEX);
Serial.println(F("]"));
#endif
if (bulk_checksum_calc != bulk_checksum)
{
#ifdef DEBUG
Serial.print(F("E : Bulk checksum mismatch : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" != 0x"));
Serial.println(bulk_checksum, HEX);
#endif
return (false);
}
// Now everything is ok and we can read the data
sysex.seek(6); // start of bulk data
uint8_t* conf = (uint8_t*)&configuration + 4;
for (n = 0; n < sysex_config_size; n++)
{
uint8_t d = sysex.read();
#ifdef DEBUG
Serial.print(n + 6, DEC);
Serial.print(F("="));
Serial.println(d);
#endif
*(conf + n) = d;
}
configuration.checksum = crc32((uint8_t*)&configuration + 4, sizeof(configuration) - 4);
return (true);
}
bool save_sysex_config(uint8_t b, uint8_t v, uint8_t instance_id)
{
v = constrain(v, 0, MAX_VOICES - 1);
b = constrain(b, 0, MAX_BANKS - 1);
if (sd_card > 0)
{
File sysex;
char sysex_config_filename[FILENAME_LEN];
sprintf(sysex_config_filename, "/%d/config%d.syx", b, v);
#ifdef DEBUG
Serial.print(F("saving config "));
Serial.print(b);
Serial.print(F("/"));
Serial.print(v);
Serial.print(F("["));
Serial.print(instance_id);
Serial.print(F("]"));
Serial.print(F(" to "));
Serial.println(sysex_config_filename);
#endif
sysex = SD.open(sysex_config_filename, FILE_WRITE);
if (!sysex)
{
#ifdef DEBUG
Serial.print(F("E : Cannot open "));
Serial.print(sysex_config_filename);
Serial.println(F(" on SD."));
#endif
return (false);
}
if (write_sysex_config(sysex, configuration))
{
sysex.close();
return (true);
}
#ifdef DEBUG
else
Serial.println(F("E : Cannot load setup data"));
#endif
}
return (false);
}