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.
1183 lines
26 KiB
1183 lines
26 KiB
/*
|
|
MicroDexed
|
|
|
|
MicroDexed is a port of the Dexed sound engine
|
|
(https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield.
|
|
Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android
|
|
|
|
(c)2018-2021 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 <ArduinoJson.h>
|
|
#include "synth_dexed.h"
|
|
#include "dexed_sd.h"
|
|
|
|
extern void init_MIDI_send_CC(void);
|
|
|
|
/******************************************************************************
|
|
SD BANK/VOICE LOADING
|
|
******************************************************************************/
|
|
|
|
bool load_sd_voice(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 filename[FILENAME_LEN];
|
|
char bank_name[BANK_NAME_LEN];
|
|
uint8_t data[128];
|
|
|
|
get_bank_name(b, bank_name, sizeof(bank_name));
|
|
sprintf(filename, "/%d/%s.syx", b, bank_name);
|
|
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename);
|
|
AudioInterrupts();
|
|
if (!sysex)
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
#endif
|
|
return (false);
|
|
}
|
|
|
|
if (get_sd_voice(sysex, v, data))
|
|
{
|
|
#ifdef DEBUG
|
|
char voice_name[VOICE_NAME_LEN];
|
|
get_voice_name(b, v, voice_name, sizeof(voice_name));
|
|
Serial.print(F("Loading voice from "));
|
|
Serial.print(filename);
|
|
Serial.print(F(" ["));
|
|
Serial.print(voice_name);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
uint8_t tmp_data[156];
|
|
bool ret = MicroDexed[instance_id]->decodeVoice(tmp_data, data);
|
|
MicroDexed[instance_id]->loadVoiceParameters(tmp_data);
|
|
#ifdef DEBUG
|
|
show_patch(instance_id);
|
|
#endif
|
|
|
|
configuration.dexed[instance_id].transpose = MicroDexed[instance_id]->getTranspose();
|
|
AudioNoInterrupts();
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
uint8_t data_copy[155];
|
|
MicroDexed[instance_id]->getVoiceData(data_copy);
|
|
send_sysex_voice(configuration.dexed[instance_id].midi_channel, data_copy);
|
|
init_MIDI_send_CC();
|
|
return (ret);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
Serial.println(F("E : Cannot load voice data"));
|
|
#endif
|
|
AudioNoInterrupts();
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
bool save_sd_voice(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 filename[FILENAME_LEN];
|
|
char bank_name[BANK_NAME_LEN];
|
|
uint8_t data[128];
|
|
|
|
get_bank_name(b, bank_name, sizeof(bank_name));
|
|
sprintf(filename, "/%d/%s.syx", b, bank_name);
|
|
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename, FILE_WRITE);
|
|
AudioInterrupts();
|
|
if (!sysex)
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
#endif
|
|
return (false);
|
|
}
|
|
|
|
MicroDexed[instance_id]->encodeVoice(data);
|
|
|
|
if (put_sd_voice(sysex, v, data))
|
|
{
|
|
#ifdef DEBUG
|
|
char voice_name[VOICE_NAME_LEN];
|
|
|
|
MicroDexed[instance_id]->getName(voice_name);
|
|
Serial.print(F("Saving voice to "));
|
|
Serial.print(filename);
|
|
Serial.print(F(" ["));
|
|
Serial.print(voice_name);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
AudioNoInterrupts();
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
return (true);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
Serial.println(F("E : Cannot load voice data"));
|
|
#endif
|
|
AudioNoInterrupts();
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
bool get_sd_voice(File sysex, uint8_t voice_number, uint8_t* data)
|
|
{
|
|
uint16_t n;
|
|
int32_t bulk_checksum_calc = 0;
|
|
int8_t bulk_checksum;
|
|
|
|
AudioNoInterrupts();
|
|
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;
|
|
AudioInterrupts();
|
|
|
|
#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]->resetRenderTimeMax();
|
|
|
|
return (true);
|
|
}
|
|
|
|
bool put_sd_voice(File sysex, uint8_t voice_number, uint8_t* data)
|
|
{
|
|
uint16_t n;
|
|
int32_t bulk_checksum_calc = 0;
|
|
|
|
AudioNoInterrupts();
|
|
sysex.seek(0);
|
|
|
|
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(6 + (voice_number * 128));
|
|
sysex.write(data, 128);
|
|
|
|
// checksum calculation
|
|
sysex.seek(6); // start of bulk data
|
|
for (n = 0; n < 4096; n++)
|
|
{
|
|
uint8_t d = sysex.read();
|
|
bulk_checksum_calc -= d;
|
|
}
|
|
sysex.seek(4102); // Bulk checksum
|
|
sysex.write(bulk_checksum_calc & 0x7f);
|
|
AudioInterrupts();
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Bulk checksum : 0x"));
|
|
Serial.println(bulk_checksum_calc & 0x7f, HEX);
|
|
#endif
|
|
|
|
return (true);
|
|
}
|
|
|
|
bool save_sd_bank(const char* bank_filename, uint8_t* data)
|
|
{
|
|
char tmp[FILENAME_LEN];
|
|
char tmp2[FILENAME_LEN];
|
|
int bank_number;
|
|
File root, entry;
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("Trying so store "));
|
|
Serial.print(bank_filename);
|
|
Serial.println(F("."));
|
|
#endif
|
|
|
|
// first remove old bank
|
|
sscanf(bank_filename, "/%d/%s", &bank_number, tmp);
|
|
sprintf(tmp, "/%d", bank_number);
|
|
AudioNoInterrupts();
|
|
root = SD.open(tmp);
|
|
while (42 == 42)
|
|
{
|
|
entry = root.openNextFile();
|
|
if (entry)
|
|
{
|
|
if (!entry.isDirectory())
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("Removing "));
|
|
Serial.print(tmp);
|
|
Serial.print(F("/"));
|
|
Serial.println(entry.name());
|
|
#endif
|
|
sprintf(tmp2, "%s/%s", tmp, entry.name());
|
|
entry.close();
|
|
#ifndef DEBUG
|
|
SD.remove(tmp2);
|
|
#else
|
|
bool r = SD.remove(tmp2);
|
|
if (r == false)
|
|
{
|
|
Serial.print(F("E: cannot remove "));
|
|
Serial.print(tmp2);
|
|
Serial.println(F("."));
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
root.close();
|
|
|
|
// store new bank at /<b>/<bank_name>.syx
|
|
#ifdef DEBUG
|
|
Serial.print(F("Storing bank as "));
|
|
Serial.print(bank_filename);
|
|
Serial.print(F("..."));
|
|
#endif
|
|
root = SD.open(bank_filename, FILE_WRITE);
|
|
root.write(data, 4104);
|
|
root.close();
|
|
AudioInterrupts();
|
|
#ifdef DEBUG
|
|
Serial.println(F(" done."));
|
|
#endif
|
|
}
|
|
else
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/******************************************************************************
|
|
SD VOICECONFIG
|
|
******************************************************************************/
|
|
bool load_sd_voiceconfig(int8_t vc, uint8_t instance_id)
|
|
{
|
|
if (vc < 0)
|
|
return (false);
|
|
|
|
vc = constrain(vc, 0, MAX_VOICECONFIG);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File sysex;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.syx", VOICE_CONFIG_PATH, VOICE_CONFIG_NAME, vc);
|
|
|
|
// first check if file exists...
|
|
AudioNoInterrupts();
|
|
if (SD.exists(filename))
|
|
{
|
|
// ... and if: load
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found voice configuration ["));
|
|
Serial.print(filename);
|
|
Serial.println(F("]... loading..."));
|
|
#endif
|
|
sysex = SD.open(filename);
|
|
if (sysex)
|
|
{
|
|
get_sd_data(sysex, 0x42, (uint8_t*)&configuration.dexed[instance_id]);
|
|
set_voiceconfig_params(instance_id);
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
return (true);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
AudioInterrupts();
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("No "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" available."));
|
|
#endif
|
|
}
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
bool save_sd_voiceconfig(uint8_t vc, uint8_t instance_id)
|
|
{
|
|
vc = constrain(vc, 0, MAX_VOICECONFIG);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File sysex;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.syx", VOICE_CONFIG_PATH, VOICE_CONFIG_NAME, vc);
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Saving voice config "));
|
|
Serial.print(vc);
|
|
Serial.print(F("["));
|
|
Serial.print(instance_id);
|
|
Serial.print(F("]"));
|
|
Serial.print(F(" to "));
|
|
Serial.println(filename);
|
|
#endif
|
|
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename, FILE_WRITE);
|
|
if (sysex)
|
|
{
|
|
if (write_sd_data(sysex, 0x42, (uint8_t*)&configuration.dexed[instance_id], sizeof(configuration.dexed[instance_id])))
|
|
{
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
return (true);
|
|
}
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
/******************************************************************************
|
|
SD FX
|
|
******************************************************************************/
|
|
bool load_sd_fx(int8_t fx)
|
|
{
|
|
if (fx < 0)
|
|
return (false);
|
|
|
|
fx = constrain(fx, 0, MAX_FX);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File sysex;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.syx", FX_CONFIG_PATH, FX_CONFIG_NAME, fx);
|
|
|
|
// first check if file exists...
|
|
AudioNoInterrupts();
|
|
if (SD.exists(filename))
|
|
{
|
|
// ... and if: load
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found fx configuration ["));
|
|
Serial.print(filename);
|
|
Serial.println(F("]... loading..."));
|
|
#endif
|
|
sysex = SD.open(filename);
|
|
if (sysex)
|
|
{
|
|
get_sd_data(sysex, 0x43, (uint8_t*)&configuration.fx);
|
|
set_fx_params();
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
return (true);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
AudioInterrupts();
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("No "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" available."));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
bool save_sd_fx(uint8_t fx)
|
|
{
|
|
fx = constrain(fx, 0, MAX_FX);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File sysex;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.syx", FX_CONFIG_PATH, FX_CONFIG_NAME, fx);
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Saving fx config "));
|
|
Serial.print(fx);
|
|
Serial.print(F(" to "));
|
|
Serial.println(filename);
|
|
#endif
|
|
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename, FILE_WRITE);
|
|
if (sysex)
|
|
{
|
|
if (write_sd_data(sysex, 0x43, (uint8_t*)&configuration.fx, sizeof(configuration.fx)))
|
|
{
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
return (true);
|
|
}
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
#endif
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
/******************************************************************************
|
|
SD PERFORMANCE
|
|
******************************************************************************/
|
|
bool load_sd_performance(int8_t p)
|
|
{
|
|
if (p < 0)
|
|
return (false);
|
|
|
|
p = constrain(p, 0, MAX_PERFORMANCE);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File sysex;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.syx", PERFORMANCE_CONFIG_PATH, PERFORMANCE_CONFIG_NAME, p);
|
|
|
|
// first check if file exists...
|
|
AudioNoInterrupts();
|
|
if (SD.exists(filename))
|
|
{
|
|
// ... and if: load
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found performance configuration ["));
|
|
Serial.print(filename);
|
|
Serial.println(F("]... loading..."));
|
|
#endif
|
|
sysex = SD.open(filename);
|
|
if (sysex)
|
|
{
|
|
get_sd_data(sysex, 0x44, (uint8_t*)&configuration.performance);
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++)
|
|
{
|
|
load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], instance_id);
|
|
load_sd_voiceconfig(configuration.performance.voiceconfig_number[instance_id], instance_id);
|
|
|
|
MicroDexed[instance_id]->ControllersRefresh();
|
|
MicroDexed[instance_id]->panic();
|
|
}
|
|
load_sd_fx(configuration.performance.fx_number);
|
|
|
|
return (true);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("No "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" available."));
|
|
#endif
|
|
}
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
bool save_sd_performance(uint8_t p)
|
|
{
|
|
p = constrain(p, 0, MAX_PERFORMANCE);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File sysex;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.syx", PERFORMANCE_CONFIG_PATH, PERFORMANCE_CONFIG_NAME, p);
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Saving performance config "));
|
|
Serial.print(p);
|
|
Serial.print(F(" to "));
|
|
Serial.println(filename);
|
|
#endif
|
|
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename, FILE_WRITE);
|
|
if (sysex)
|
|
{
|
|
if (write_sd_data(sysex, 0x44, (uint8_t*)&configuration.performance, sizeof(configuration.performance)))
|
|
{
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
return (true);
|
|
}
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
#endif
|
|
return (false);
|
|
}
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
bool load_sd_performance_json(int8_t p)
|
|
{
|
|
if (p < 0)
|
|
return (false);
|
|
|
|
p = constrain(p, 0, MAX_PERFORMANCE);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File json;
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%s/%s%d.json", PERFORMANCE_CONFIG_PATH, PERFORMANCE_CONFIG_NAME, p);
|
|
|
|
// first check if file exists...
|
|
AudioNoInterrupts();
|
|
if (SD.exists(filename))
|
|
{
|
|
// ... and if: load
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found performance configuration ["));
|
|
Serial.print(filename);
|
|
Serial.println(F("]... loading..."));
|
|
#endif
|
|
json = SD.open(filename);
|
|
if (json)
|
|
{
|
|
StaticJsonDocument<256> data_json;
|
|
|
|
deserializeJson(data_json, json);
|
|
|
|
#ifdef DEBUG
|
|
Serial.println(F("Read JSON data:"));
|
|
serializeJsonPretty(data_json, Serial);
|
|
Serial.println();
|
|
#endif
|
|
for (uint8_t i = 0; i < MAX_DEXED; i++)
|
|
{
|
|
configuration.performance.bank[i] = data_json["bank"][i];
|
|
configuration.performance.voice[i] = data_json["voice"][i];
|
|
configuration.performance.voiceconfig_number[i] = data_json["voiceconfig_number"][i];
|
|
}
|
|
configuration.performance.fx_number = data_json["fx_number"];
|
|
|
|
json.close();
|
|
AudioInterrupts();
|
|
|
|
for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++)
|
|
{
|
|
load_sd_voiceconfig(configuration.performance.voiceconfig_number[instance_id], instance_id);
|
|
|
|
MicroDexed[instance_id]->ControllersRefresh();
|
|
MicroDexed[instance_id]->panic();
|
|
}
|
|
load_sd_fx(configuration.performance.fx_number);
|
|
|
|
return (true);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("No "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" available."));
|
|
#endif
|
|
}
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
bool save_sd_performance_json(uint8_t p)
|
|
{
|
|
char filename[FILENAME_LEN];
|
|
|
|
p = constrain(p, 0, MAX_PERFORMANCE);
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
File json;
|
|
|
|
sprintf(filename, "/%s/%s%d.json", PERFORMANCE_CONFIG_PATH, PERFORMANCE_CONFIG_NAME, p);
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Saving performance config as JSON"));
|
|
Serial.print(p);
|
|
Serial.print(F(" to "));
|
|
Serial.println(filename);
|
|
#endif
|
|
|
|
AudioNoInterrupts();
|
|
json = SD.open(filename, FILE_WRITE);
|
|
if (json)
|
|
{
|
|
StaticJsonDocument<256> data_json;
|
|
|
|
for (uint8_t i = 0; i < MAX_DEXED; i++)
|
|
{
|
|
data_json["bank"][i] = configuration.performance.bank[i];
|
|
data_json["voice"][i] = configuration.performance.voice[i];
|
|
data_json["voiceconfig_number"][i] = configuration.performance.voiceconfig_number[i];
|
|
}
|
|
data_json["fx_number"] = configuration.performance.fx_number;
|
|
|
|
#ifdef DEBUG
|
|
Serial.println(F("Write JSON data:"));
|
|
serializeJsonPretty(data_json, Serial);
|
|
Serial.println();
|
|
#endif
|
|
serializeJsonPretty(data_json, json);
|
|
json.close();
|
|
AudioInterrupts();
|
|
return (true);
|
|
}
|
|
json.close();
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Cannot open "));
|
|
Serial.print(filename);
|
|
Serial.println(F(" on SD."));
|
|
#endif
|
|
}
|
|
|
|
AudioInterrupts();
|
|
return (false);
|
|
}
|
|
|
|
/******************************************************************************
|
|
HELPER FUNCTIONS
|
|
******************************************************************************/
|
|
bool get_sd_data(File sysex, uint8_t format, uint8_t* conf)
|
|
{
|
|
uint16_t n;
|
|
int32_t bulk_checksum_calc = 0;
|
|
int8_t bulk_checksum;
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Reading "));
|
|
Serial.print(sysex.size());
|
|
Serial.println(F(" bytes."));
|
|
#endif
|
|
|
|
AudioNoInterrupts();
|
|
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);
|
|
}
|
|
if (sysex.read() != format) // check for sysex type
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.println(F("E : SysEx type not found."));
|
|
#endif
|
|
return (false);
|
|
}
|
|
sysex.seek(sysex.size() - 1);
|
|
if (sysex.read() != 0xf7) // check sysex end-byte
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.println(F("E : SysEx end byte not found."));
|
|
#endif
|
|
return (false);
|
|
}
|
|
|
|
sysex.seek(sysex.size() - 2); // Bulk checksum
|
|
bulk_checksum = sysex.read();
|
|
|
|
sysex.seek(3); // start of bulk data
|
|
for (n = 0; n < sysex.size() - 6; n++)
|
|
{
|
|
uint8_t d = sysex.read();
|
|
bulk_checksum_calc -= d;
|
|
#ifdef DEBUG
|
|
Serial.print(F("SYSEX data read: 0x"));
|
|
Serial.println(d, HEX);
|
|
#endif
|
|
}
|
|
bulk_checksum_calc &= 0x7f;
|
|
|
|
if (int8_t(bulk_checksum_calc) != bulk_checksum)
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("E : Bulk checksum mismatch : 0x"));
|
|
Serial.print(int8_t(bulk_checksum_calc), HEX);
|
|
Serial.print(F(" != 0x"));
|
|
Serial.println(bulk_checksum, HEX);
|
|
#endif
|
|
return (false);
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
Serial.print(F("Bulk checksum : 0x"));
|
|
Serial.print(int8_t(bulk_checksum_calc), HEX);
|
|
Serial.print(F(" [0x"));
|
|
Serial.print(bulk_checksum, HEX);
|
|
Serial.println(F("]"));
|
|
}
|
|
#endif
|
|
|
|
sysex.seek(3); // start of bulk data
|
|
for (n = 0; n < sysex.size() - 6; n++)
|
|
{
|
|
uint8_t d = sysex.read();
|
|
*(conf++) = d;
|
|
}
|
|
AudioInterrupts();
|
|
|
|
#ifdef DEBUG
|
|
Serial.println(F("SD data loaded."));
|
|
#endif
|
|
|
|
return (true);
|
|
}
|
|
|
|
bool write_sd_data(File sysex, uint8_t format, uint8_t* data, uint16_t len)
|
|
{
|
|
#ifdef DEBUG
|
|
Serial.print(F("Storing SYSEX format 0x"));
|
|
Serial.print(format, HEX);
|
|
Serial.print(F(" with length of "));
|
|
Serial.print(len, DEC);
|
|
Serial.println(F(" bytes."));
|
|
#endif
|
|
|
|
// write sysex start
|
|
AudioNoInterrupts();
|
|
sysex.write(0xf0);
|
|
#ifdef DEBUG
|
|
Serial.println(F("Write SYSEX start: 0xf0"));
|
|
#endif
|
|
// write sysex vendor is unofficial SYSEX-ID for MicroDexed
|
|
sysex.write(0x67);
|
|
#ifdef DEBUG
|
|
Serial.println(F("Write SYSEX vendor: 0x67"));
|
|
#endif
|
|
// write sysex format number
|
|
sysex.write(format);
|
|
#ifdef DEBUG
|
|
Serial.print(F("Write SYSEX format: 0x"));
|
|
Serial.println(format, HEX);
|
|
#endif
|
|
// write data
|
|
sysex.write(data, len);
|
|
#ifdef DEBUG
|
|
for (uint16_t i = 0; i < len; i++)
|
|
{
|
|
Serial.print(F("Write SYSEX data: 0x"));
|
|
Serial.println(data[i], HEX);
|
|
}
|
|
#endif
|
|
// write checksum
|
|
sysex.write(calc_checksum(data, len));
|
|
#ifdef DEBUG
|
|
uint8_t checksum = calc_checksum(data, len);
|
|
sysex.write(checksum);
|
|
Serial.print(F("Write SYSEX checksum: 0x"));
|
|
Serial.println(checksum, HEX);
|
|
#endif
|
|
// write sysex end
|
|
sysex.write(0xf7);
|
|
AudioInterrupts();
|
|
|
|
#ifdef DEBUG
|
|
Serial.println(F("Write SYSEX end: 0xf7"));
|
|
#endif
|
|
|
|
return (true);
|
|
}
|
|
|
|
uint8_t calc_checksum(uint8_t* data, uint16_t len)
|
|
{
|
|
int32_t bulk_checksum_calc = 0;
|
|
|
|
for (uint16_t n = 0; n < len; n++)
|
|
bulk_checksum_calc -= data[n];
|
|
|
|
return (bulk_checksum_calc & 0x7f);
|
|
}
|
|
|
|
void strip_extension(const char* s, char* target, uint8_t len)
|
|
{
|
|
char tmp[FILENAME_LEN];
|
|
char* token;
|
|
|
|
strcpy(tmp, s);
|
|
token = strtok(tmp, ".");
|
|
if (token == NULL)
|
|
strcpy(target, "*ERROR*");
|
|
else
|
|
strcpy(target, token);
|
|
|
|
target[len] = '\0';
|
|
}
|
|
|
|
bool get_bank_name(uint8_t b, char* name, uint8_t len)
|
|
{
|
|
File sysex;
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
char bankdir[4];
|
|
File entry;
|
|
|
|
memset(name, 0, len);
|
|
|
|
sprintf(bankdir, "/%d", b);
|
|
|
|
// try to open directory
|
|
sysex = SD.open(bankdir);
|
|
if (!sysex)
|
|
return (false);
|
|
|
|
do
|
|
{
|
|
entry = sysex.openNextFile();
|
|
} while (entry.isDirectory());
|
|
|
|
if (entry.isDirectory())
|
|
{
|
|
entry.close();
|
|
sysex.close();
|
|
return (false);
|
|
}
|
|
|
|
strip_extension(entry.name(), name, len);
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found bank-name ["));
|
|
Serial.print(name);
|
|
Serial.print(F("] for bank ["));
|
|
Serial.print(b);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
|
|
entry.close();
|
|
sysex.close();
|
|
|
|
return (true);
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
bool get_voice_name(uint8_t b, uint8_t v, char* name, uint8_t len)
|
|
{
|
|
File sysex;
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
char bank_name[BANK_NAME_LEN];
|
|
char filename[FILENAME_LEN];
|
|
|
|
b = constrain(b, 0, MAX_BANKS - 1);
|
|
v = constrain(v, 0, MAX_VOICES - 1);
|
|
|
|
get_bank_name(b, bank_name, sizeof(bank_name));
|
|
sprintf(filename, "/%d/%s.syx", b, bank_name);
|
|
#ifdef DEBUG
|
|
Serial.print(F("Reading voice-name from ["));
|
|
Serial.print(filename);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
|
|
// try to open directory
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename);
|
|
if (!sysex)
|
|
return (false);
|
|
|
|
memset(name, 0, len);
|
|
sysex.seek(124 + (v * 128));
|
|
sysex.read(name, min(len, 10));
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found voice-name ["));
|
|
Serial.print(name);
|
|
Serial.print(F("] for bank ["));
|
|
Serial.print(b);
|
|
Serial.print(F("] and voice ["));
|
|
Serial.print(v);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
return (true);
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
bool get_voice_by_bank_name(uint8_t b, const char* bank_name, uint8_t v, char* voice_name, uint8_t len)
|
|
{
|
|
File sysex;
|
|
|
|
if (sd_card > 0)
|
|
{
|
|
char filename[FILENAME_LEN];
|
|
|
|
sprintf(filename, "/%d/%s.syx", b, bank_name);
|
|
#ifdef DEBUG
|
|
Serial.print(F("Reading voice-name from ["));
|
|
Serial.print(filename);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
|
|
// try to open directory
|
|
AudioNoInterrupts();
|
|
sysex = SD.open(filename);
|
|
if (!sysex)
|
|
return (false);
|
|
|
|
memset(voice_name, 0, len);
|
|
sysex.seek(124 + (v * 128));
|
|
sysex.read(voice_name, min(len, 10));
|
|
|
|
#ifdef DEBUG
|
|
Serial.print(F("Found voice-name ["));
|
|
Serial.print(voice_name);
|
|
Serial.print(F("] for bank ["));
|
|
Serial.print(b);
|
|
Serial.print(F("|"));
|
|
Serial.print(bank_name);
|
|
Serial.print(F("] and voice ["));
|
|
Serial.print(v);
|
|
Serial.println(F("]"));
|
|
#endif
|
|
|
|
sysex.close();
|
|
AudioInterrupts();
|
|
|
|
return (true);
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
void string_toupper(char* s)
|
|
{
|
|
while (*s)
|
|
{
|
|
*s = toupper((unsigned char) * s);
|
|
s++;
|
|
}
|
|
}
|
|
|