/* 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 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 #include "config.h" #include #include #include #include "synth_dexed.h" #include "dexed_sd.h" extern void init_MIDI_send_CC(void); extern void check_configuration_dexed(uint8_t instance_id); extern void check_configuration_performance(void); extern void check_configuration_fx(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 //.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_json(int8_t vc, uint8_t instance_id) { char filename[FILENAME_LEN]; vc = constrain(vc, 0, MAX_VOICECONFIG); if (sd_card > 0) { File json; sprintf(filename, "/%s/%s%d.json", 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 json = SD.open(filename); if (json) { StaticJsonDocument data_json; deserializeJson(data_json, json); json.close(); AudioInterrupts(); check_configuration_dexed(instance_id); #ifdef DEBUG Serial.println(F("Read JSON data:")); serializeJsonPretty(data_json, Serial); Serial.println(); #endif configuration.dexed[instance_id].lowest_note = data_json["lowest_note"][instance_id]; configuration.dexed[instance_id].highest_note = data_json["highest_note"][instance_id]; configuration.dexed[instance_id].transpose = data_json["transpose"][instance_id]; configuration.dexed[instance_id].tune = data_json["tune"][instance_id]; configuration.dexed[instance_id].sound_intensity = data_json["sound_intensity"][instance_id]; configuration.dexed[instance_id].pan = data_json["pan"][instance_id]; configuration.dexed[instance_id].polyphony = data_json["polyphony"][instance_id]; configuration.dexed[instance_id].velocity_level = data_json["velocity_level"][instance_id]; configuration.dexed[instance_id].monopoly = data_json["monopoly"][instance_id]; configuration.dexed[instance_id].note_refresh = data_json["note_refresh"][instance_id]; configuration.dexed[instance_id].pb_range = data_json["pb_range"][instance_id]; configuration.dexed[instance_id].pb_step = data_json["pb_step"][instance_id]; configuration.dexed[instance_id].mw_range = data_json["mw_range"][instance_id]; configuration.dexed[instance_id].mw_assign = data_json["mw_assign"][instance_id]; configuration.dexed[instance_id].mw_mode = data_json["mw_mode"][instance_id]; configuration.dexed[instance_id].fc_range = data_json["fc_range"][instance_id]; configuration.dexed[instance_id].fc_assign = data_json["fc_assign"][instance_id]; configuration.dexed[instance_id].fc_mode = data_json["fc_mode"][instance_id]; configuration.dexed[instance_id].bc_range = data_json["bc_range"][instance_id]; configuration.dexed[instance_id].bc_assign = data_json["bc_assign"][instance_id]; configuration.dexed[instance_id].bc_mode = data_json["bc_mode"][instance_id]; configuration.dexed[instance_id].at_range = data_json["at_range"][instance_id]; configuration.dexed[instance_id].at_assign = data_json["at_assign"][instance_id]; configuration.dexed[instance_id].at_mode = data_json["at_mode"][instance_id]; configuration.dexed[instance_id].portamento_mode = data_json["portamento_mode"][instance_id]; configuration.dexed[instance_id].portamento_glissando = data_json["portamento_glissando"][instance_id]; configuration.dexed[instance_id].portamento_time = data_json["portamento_time"][instance_id]; configuration.dexed[instance_id].op_enabled = data_json["op_enabled"][instance_id]; configuration.dexed[instance_id].midi_channel = data_json["midi_channel"][instance_id]; set_voiceconfig_params(instance_id); return (true); } #ifdef DEBUG else { Serial.print(F("E : Cannot open ")); Serial.print(filename); Serial.println(F(" on SD.")); } } else { Serial.print(F("No ")); Serial.print(filename); Serial.println(F(" available.")); #endif } } AudioInterrupts(); return (false); } bool save_sd_voiceconfig_json(uint8_t vc, uint8_t instance_id) { char filename[FILENAME_LEN]; vc = constrain(vc, 0, MAX_VOICECONFIG); if (sd_card > 0) { File json; sprintf(filename, "/%s/%s%d.json", 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(); json = SD.open(filename, FILE_WRITE); if (json) { StaticJsonDocument data_json; data_json["lowest_note"][instance_id] = configuration.dexed[instance_id].lowest_note; data_json["highest_note"][instance_id] = configuration.dexed[instance_id].highest_note; data_json["transpose"][instance_id] = configuration.dexed[instance_id].transpose; data_json["tune"][instance_id] = configuration.dexed[instance_id].tune; data_json["sound_intensity"][instance_id] = configuration.dexed[instance_id].sound_intensity; data_json["pan"][instance_id] = configuration.dexed[instance_id].pan; data_json["polyphony"][instance_id] = configuration.dexed[instance_id].polyphony; data_json["velocity_level"][instance_id] = configuration.dexed[instance_id].velocity_level; data_json["monopoly"][instance_id] = configuration.dexed[instance_id].monopoly; data_json["monopoly"][instance_id] = configuration.dexed[instance_id].monopoly; data_json["note_refresh"][instance_id] = configuration.dexed[instance_id].note_refresh; data_json["pb_range"][instance_id] = configuration.dexed[instance_id].pb_range; data_json["pb_step"][instance_id] = configuration.dexed[instance_id].pb_step; data_json["mw_range"][instance_id] = configuration.dexed[instance_id].mw_range; data_json["mw_assign"][instance_id] = configuration.dexed[instance_id].mw_assign; data_json["mw_mode"][instance_id] = configuration.dexed[instance_id].mw_mode; data_json["fc_range"][instance_id] = configuration.dexed[instance_id].fc_range; data_json["fc_assign"][instance_id] = configuration.dexed[instance_id].fc_assign; data_json["fc_mode"][instance_id] = configuration.dexed[instance_id].fc_mode; data_json["bc_range"][instance_id] = configuration.dexed[instance_id].bc_range; data_json["bc_assign"][instance_id] = configuration.dexed[instance_id].bc_assign; data_json["bc_mode"][instance_id] = configuration.dexed[instance_id].bc_mode; data_json["at_range"][instance_id] = configuration.dexed[instance_id].at_range; data_json["at_assign"][instance_id] = configuration.dexed[instance_id].at_assign; data_json["at_mode"][instance_id] = configuration.dexed[instance_id].at_mode; data_json["portamento_mode"][instance_id] = configuration.dexed[instance_id].portamento_mode; data_json["portamento_glissando"][instance_id] = configuration.dexed[instance_id].portamento_glissando; data_json["portamento_time"][instance_id] = configuration.dexed[instance_id].portamento_time; data_json["op_enabled"][instance_id] = configuration.dexed[instance_id].op_enabled; data_json["midi_channel"][instance_id] = configuration.dexed[instance_id].midi_channel; #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); } /****************************************************************************** SD FX ******************************************************************************/ bool load_sd_fx_json(int8_t fx) { if (fx < 0) return (false); fx = constrain(fx, 0, MAX_FX); if (sd_card > 0) { File json; char filename[FILENAME_LEN]; sprintf(filename, "/%s/%s%d.json", 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 json = SD.open(filename); if (json) { StaticJsonDocument data_json; deserializeJson(data_json, json); json.close(); AudioInterrupts(); check_configuration_fx(); #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.fx.filter_cutoff[i] = data_json["filter_cutoff"][i]; configuration.fx.filter_resonance[i] = data_json["filter_resonance"][i]; configuration.fx.chorus_frequency[i] = data_json["chorus_frequency"][i]; configuration.fx.chorus_waveform[i] = data_json["chorus_waveform"][i]; configuration.fx.chorus_depth[i] = data_json["chorus_depth"][i]; configuration.fx.chorus_level[i] = data_json["chorus_level"][i]; configuration.fx.delay_time[i] = data_json["delay_time"][i]; configuration.fx.delay_feedback[i] = data_json["delay_feedback"][i]; configuration.fx.delay_level[i] = data_json["delay_level"][i]; configuration.fx.delay_sync[i] = data_json["delay_sync"][i]; configuration.fx.reverb_send[i] = data_json["reverb_send"][i]; } configuration.fx.reverb_roomsize = data_json["reverb_roomsize"]; configuration.fx.reverb_damping = data_json["reverb_damping"]; configuration.fx.reverb_lowpass = data_json["reverb_lowpass"]; configuration.fx.reverb_lodamp = data_json["reverb_lodamp"]; configuration.fx.reverb_hidamp = data_json["reverb_hidamp"]; configuration.fx.reverb_diffusion = data_json["reverb_diffusion"]; configuration.fx.reverb_level = data_json["reverb_level"]; configuration.fx.eq_bass = data_json["eq_bass"]; configuration.fx.eq_treble = data_json["eq_treble"]; set_fx_params(); return (true); } #ifdef DEBUG else { Serial.print(F("E : Cannot open ")); Serial.print(filename); Serial.println(F(" on SD.")); } } else { Serial.print(F("No ")); Serial.print(filename); Serial.println(F(" available.")); #endif } } AudioInterrupts(); return (false); } bool save_sd_fx_json(uint8_t fx) { char filename[FILENAME_LEN]; fx = constrain(fx, 0, MAX_FX); if (sd_card > 0) { File json; sprintf(filename, "/%s/%s%d.json", 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(); json = SD.open(filename, FILE_WRITE); if (json) { StaticJsonDocument data_json; for (uint8_t i = 0; i < MAX_DEXED; i++) { data_json["filter_cutoff"][i] = configuration.fx.filter_cutoff[i]; data_json["filter_resonance"][i] = configuration.fx.filter_resonance[i]; data_json["chorus_frequency"][i] = configuration.fx.chorus_frequency[i]; data_json["chorus_waveform"][i] = configuration.fx.chorus_waveform[i]; data_json["chorus_depth"][i] = configuration.fx.chorus_depth[i]; data_json["chorus_level"][i] = configuration.fx.chorus_level[i]; data_json["delay_time"][i] = configuration.fx.delay_time[i]; data_json["delay_feedback"][i] = configuration.fx.delay_feedback[i]; data_json["delay_level"][i] = configuration.fx.delay_level[i]; data_json["delay_sync"][i] = configuration.fx.delay_sync[i]; data_json["reverb_send"][i] = configuration.fx.reverb_send[i]; } data_json["reverb_roomsize"] = configuration.fx.reverb_roomsize; data_json["reverb_damping"] = configuration.fx.reverb_damping; data_json["reverb_lowpass"] = configuration.fx.reverb_lowpass; data_json["reverb_lodamp"] = configuration.fx.reverb_lodamp; data_json["reverb_hidamp"] = configuration.fx.reverb_hidamp; data_json["reverb_diffusion"] = configuration.fx.reverb_diffusion; data_json["reverb_level"] = configuration.fx.reverb_level; data_json["eq_bass"] = configuration.fx.eq_bass; data_json["eq_treble"] = configuration.fx.eq_treble; #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); } /****************************************************************************** SD PERFORMANCE ******************************************************************************/ 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 data_json; deserializeJson(data_json, json); json.close(); AudioInterrupts(); check_configuration_performance(); #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"]; 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_json(configuration.performance.voiceconfig_number[instance_id], instance_id); MicroDexed[instance_id]->ControllersRefresh(); MicroDexed[instance_id]->panic(); } load_sd_fx_json(configuration.performance.fx_number); return (true); } #ifdef DEBUG else { Serial.print(F("E : Cannot open ")); Serial.print(filename); Serial.println(F(" on SD.")); } } else { Serial.print(F("No ")); Serial.print(filename); Serial.println(F(" available.")); #endif } } AudioInterrupts(); 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 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++; } }