/* 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" #include "TeensyTimerTool.h" using namespace TeensyTimerTool; 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); extern void sequencer(); //extern StaticJsonDocument data_json; extern uint8_t seq_chain_lenght; extern uint8_t seq_data[10][16]; extern uint8_t seq_vel[10][16]; extern uint8_t seq_patternchain[4][4]; extern uint8_t seq_content_type[10]; extern uint8_t seq_track_type[4]; extern uint8_t seq_chord_key_ammount; extern uint8_t seq_element_shift; extern int seq_oct_shift; extern int seq_transpose; extern int seq_tempo_ms ; extern int seq_bpm; extern bool arp_play_basenote; extern bool seq_running; extern uint8_t arp_speed; extern uint8_t arp_lenght; extern uint8_t arp_style; extern uint8_t seq_chord_velocity; extern uint8_t seq_chord_dexed_inst; extern uint8_t seq_inst_dexed[4]; extern PeriodicTimer timer1; /****************************************************************************** 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; StaticJsonDocument data_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) { 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"]; configuration.dexed[instance_id].highest_note = data_json["highest_note"]; configuration.dexed[instance_id].transpose = data_json["transpose"]; configuration.dexed[instance_id].tune = data_json["tune"]; configuration.dexed[instance_id].sound_intensity = data_json["sound_intensity"]; configuration.dexed[instance_id].pan = data_json["pan"]; configuration.dexed[instance_id].polyphony = data_json["polyphony"]; configuration.dexed[instance_id].velocity_level = data_json["velocity_level"]; configuration.dexed[instance_id].monopoly = data_json["monopoly"]; configuration.dexed[instance_id].note_refresh = data_json["note_refresh"]; configuration.dexed[instance_id].pb_range = data_json["pb_range"]; configuration.dexed[instance_id].pb_step = data_json["pb_step"]; configuration.dexed[instance_id].mw_range = data_json["mw_range"]; configuration.dexed[instance_id].mw_assign = data_json["mw_assign"]; configuration.dexed[instance_id].mw_mode = data_json["mw_mode"]; configuration.dexed[instance_id].fc_range = data_json["fc_range"]; configuration.dexed[instance_id].fc_assign = data_json["fc_assign"]; configuration.dexed[instance_id].fc_mode = data_json["fc_mode"]; configuration.dexed[instance_id].bc_range = data_json["bc_range"]; configuration.dexed[instance_id].bc_assign = data_json["bc_assign"]; configuration.dexed[instance_id].bc_mode = data_json["bc_mode"]; configuration.dexed[instance_id].at_range = data_json["at_range"]; configuration.dexed[instance_id].at_assign = data_json["at_assign"]; configuration.dexed[instance_id].at_mode = data_json["at_mode"]; configuration.dexed[instance_id].portamento_mode = data_json["portamento_mode"]; configuration.dexed[instance_id].portamento_glissando = data_json["portamento_glissando"]; configuration.dexed[instance_id].portamento_time = data_json["portamento_time"]; configuration.dexed[instance_id].op_enabled = data_json["op_enabled"]; configuration.dexed[instance_id].midi_channel = data_json["midi_channel"]; 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; StaticJsonDocument data_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) { data_json["lowest_note"] = configuration.dexed[instance_id].lowest_note; data_json["highest_note"] = configuration.dexed[instance_id].highest_note; data_json["transpose"] = configuration.dexed[instance_id].transpose; data_json["tune"] = configuration.dexed[instance_id].tune; data_json["sound_intensity"] = configuration.dexed[instance_id].sound_intensity; data_json["pan"] = configuration.dexed[instance_id].pan; data_json["polyphony"] = configuration.dexed[instance_id].polyphony; data_json["velocity_level"] = configuration.dexed[instance_id].velocity_level; data_json["monopoly"] = configuration.dexed[instance_id].monopoly; data_json["monopoly"] = configuration.dexed[instance_id].monopoly; data_json["note_refresh"] = configuration.dexed[instance_id].note_refresh; data_json["pb_range"] = configuration.dexed[instance_id].pb_range; data_json["pb_step"] = configuration.dexed[instance_id].pb_step; data_json["mw_range"] = configuration.dexed[instance_id].mw_range; data_json["mw_assign"] = configuration.dexed[instance_id].mw_assign; data_json["mw_mode"] = configuration.dexed[instance_id].mw_mode; data_json["fc_range"] = configuration.dexed[instance_id].fc_range; data_json["fc_assign"] = configuration.dexed[instance_id].fc_assign; data_json["fc_mode"] = configuration.dexed[instance_id].fc_mode; data_json["bc_range"] = configuration.dexed[instance_id].bc_range; data_json["bc_assign"] = configuration.dexed[instance_id].bc_assign; data_json["bc_mode"] = configuration.dexed[instance_id].bc_mode; data_json["at_range"] = configuration.dexed[instance_id].at_range; data_json["at_assign"] = configuration.dexed[instance_id].at_assign; data_json["at_mode"] = configuration.dexed[instance_id].at_mode; data_json["portamento_mode"] = configuration.dexed[instance_id].portamento_mode; data_json["portamento_glissando"] = configuration.dexed[instance_id].portamento_glissando; data_json["portamento_time"] = configuration.dexed[instance_id].portamento_time; data_json["op_enabled"] = configuration.dexed[instance_id].op_enabled; data_json["midi_channel"] = 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; StaticJsonDocument data_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) { 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_1 = data_json["eq_1"]; configuration.fx.eq_2 = data_json["eq_2"]; configuration.fx.eq_3 = data_json["eq_3"]; configuration.fx.eq_4 = data_json["eq_4"]; configuration.fx.eq_5 = data_json["eq_5"]; configuration.fx.eq_6 = data_json["eq_6"]; configuration.fx.eq_7 = data_json["eq_7"]; 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; StaticJsonDocument data_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) { 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_1"] = configuration.fx.eq_1; data_json["eq_2"] = configuration.fx.eq_2; data_json["eq_3"] = configuration.fx.eq_3; data_json["eq_4"] = configuration.fx.eq_4; data_json["eq_5"] = configuration.fx.eq_5; data_json["eq_6"] = configuration.fx.eq_6; data_json["eq_7"] = configuration.fx.eq_7; #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); } bool load_sd_seq_drumsettings_json(uint8_t number) { if (number < 0) return (false); number = constrain(number, 0, 99); if (sd_card > 0) { File json; StaticJsonDocument data_json; char filename[FILENAME_LEN]; sprintf(filename, "/%s/%s%d-d.json", SEQ_CONFIG_PATH, SEQ_CONFIG_NAME, number); // first check if file exists... AudioNoInterrupts(); if (SD.exists(filename)) { // ... and if: load #ifdef DEBUG Serial.print(F("Found drums configuration [")); Serial.print(filename); Serial.println(F("]... loading...")); #endif json = SD.open(filename); if (json) { deserializeJson(data_json, json); json.close(); AudioInterrupts(); #ifdef DEBUG Serial.println(F("Read JSON data:")); serializeJsonPretty(data_json, Serial); Serial.println(); #endif // drums_volume = data_json["drums_volume"]; for (uint8_t i = 0; i < 20; i++) // needs to replaced by NUM_DRUMSET_CONFIG but does not work { // drum_config[i].pan = data_json["pan"][i] ; // drum_config[i].vol_max = data_json["vol_max"][i] ; // drum_config[i].vol_min = data_json["vol_min"][i] ; // drum_config[i].reverb_send = data_json["reverb_send"][i]; } //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 } } return (false); } bool save_sd_seq_drumsettings_json(uint8_t number) { char filename[FILENAME_LEN]; number = constrain(number, 0, 99); if (sd_card > 0) { File json; StaticJsonDocument data_json; sprintf(filename, "/%s/%s%d-d.json", SEQ_CONFIG_PATH, SEQ_CONFIG_NAME, number); #ifdef DEBUG Serial.print(F("Saving drums config ")); Serial.print(number); Serial.print(F(" to ")); Serial.println(filename); #endif AudioNoInterrupts(); json = SD.open(filename, FILE_WRITE); if (json) { // data_json["drums_volume"] = drums_volume; for (uint8_t i = 0; i < 20; i++) // needs to be replaced by NUM_DRUMSET_CONFIG { // data_json["pan"][i] = drum_config[i].pan; // data_json["vol_max"][i] = drum_config[i].vol_max; // data_json["vol_min"][i] = drum_config[i].vol_min; // data_json["reverb_send"][i] = drum_config[i].reverb_send; } #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(); AudioInterrupts(); } else { #ifdef DEBUG Serial.print(F("E : Cannot open ")); Serial.print(filename); Serial.println(F(" on SD.")); #endif } return (false); } bool save_sd_seq_json(uint8_t seq_number) { char filename[FILENAME_LEN]; int count = 0; seq_number = constrain(seq_number, 0, 99); if (sd_card > 0) { File json; StaticJsonDocument data_json; sprintf(filename, "/%s/%s%d.json", SEQ_CONFIG_PATH, SEQ_CONFIG_NAME, seq_number); #ifdef DEBUG Serial.print(F("Saving sequencer config ")); Serial.print(seq_number); Serial.print(F(" to ")); Serial.println(filename); #endif int total = sizeof(seq_data); int columns = sizeof(seq_data[0]); int rows = total / columns; Serial.print(F("Rows: ")); Serial.print(rows); Serial.print(" Columns: "); Serial.print(columns); Serial.print(F(" ")); AudioNoInterrupts(); json = SD.open(filename, FILE_WRITE); if (json) { for (uint8_t i = 0; i < rows; i++) { for (uint8_t j = 0; j < columns; j++) { data_json["seq_data"][count] = seq_data[i][j]; count++; } } count = 0; for (uint8_t i = 0; i < rows; i++) { for (uint8_t j = 0; j < columns; j++) { data_json["seq_velocity"][count] = seq_vel[i][j]; count++; } } total = sizeof(seq_patternchain); columns = sizeof(seq_patternchain[0]); rows = total / columns; Serial.print(F("Chain Rows: ")); Serial.print(rows); Serial.print(" Chain Columns: "); Serial.print(columns); Serial.print(F(" ")); count = 0; for (uint8_t i = 0; i < rows; i++) { for (uint8_t j = 0; j < columns; j++) { data_json["seq_patternchain"][count] = seq_patternchain[i][j]; count++; } } count = 0; data_json["seq_tempo_ms"] = seq_tempo_ms ; data_json["seq_bpm"] = seq_bpm; data_json["arp_play_basenote"] = arp_play_basenote; data_json["arp_speed"] = arp_speed; data_json["arp_lenght"] = arp_lenght; data_json["arp_style"] = arp_style; data_json["seq_chord_velocity"] = seq_chord_velocity; data_json["seq_chord_dexed_inst"] = seq_chord_dexed_inst; data_json["seq_chain_lenght"] = seq_chain_lenght; data_json["seq_transpose"] = seq_transpose; data_json["chord_key_ammount"] = seq_chord_key_ammount; data_json["seq_oct_shift"] = seq_oct_shift; data_json["seq_element_shift"] = seq_element_shift; data_json["performance"] = configuration.sys.performance_number; for (uint8_t i = 0; i < sizeof(seq_track_type); i++) { data_json["track_type"][i] = seq_track_type[i]; } for (uint8_t i = 0; i < sizeof(seq_content_type); i++) { data_json["content_type"][i] = seq_content_type[i]; } for (uint8_t i = 0; i < sizeof(seq_inst_dexed); i++) { data_json["seq_inst_dexed"][i] = seq_inst_dexed[i]; } #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 } return (false); } bool load_sd_seq_json(uint8_t seq_number) { if (seq_number < 0) return (false); seq_number = constrain(seq_number, 0, 99); if (sd_card > 0) { File json; StaticJsonDocument data_json; char filename[FILENAME_LEN]; sprintf(filename, "/%s/%s%d.json", SEQ_CONFIG_PATH, SEQ_CONFIG_NAME, seq_number); // first check if file exists... AudioNoInterrupts(); if (SD.exists(filename)) { // ... and if: load #ifdef DEBUG Serial.print(F("Found Sequencer configuration [")); Serial.print(filename); Serial.println(F("]... loading...")); #endif json = SD.open(filename); if (json) { deserializeJson(data_json, json); json.close(); AudioInterrupts(); #ifdef DEBUG Serial.println(F("Read JSON data:")); serializeJsonPretty(data_json, Serial); Serial.println(); #endif int total = sizeof(seq_data); int columns = sizeof(seq_data[0]); int rows = total / columns; int count = 0; for (uint8_t i = 0; i < rows; i++) { for (uint8_t j = 0; j < columns; j++) { seq_data[i][j] = data_json["seq_data"][count]; count++; } } count = 0; for (uint8_t i = 0; i < rows; i++) { for (uint8_t j = 0; j < columns; j++) { seq_vel[i][j] = data_json["seq_velocity"][count]; count++; } } total = sizeof(seq_patternchain); columns = sizeof(seq_patternchain[0]); rows = total / columns; count = 0; for (uint8_t i = 0; i < rows; i++) { for (uint8_t j = 0; j < columns; j++) { seq_patternchain[i][j] = data_json["seq_patternchain"][count]; count++; } } for (uint8_t i = 0; i < sizeof(seq_track_type); i++) { seq_track_type[i] = data_json["track_type"][i]; } for (uint8_t i = 0; i < sizeof(seq_content_type); i++) { seq_content_type[i] = data_json["content_type"][i]; } for (uint8_t i = 0; i < sizeof(seq_inst_dexed); i++) { seq_inst_dexed[i] = data_json["seq_inst_dexed"][i]; } count = 0; seq_tempo_ms = data_json["seq_tempo_ms"] ; seq_bpm = data_json["seq_bpm"]; arp_play_basenote = data_json["arp_play_basenote"]; arp_speed = data_json["arp_speed"] ; arp_lenght = data_json["arp_lenght"]; arp_style = data_json["arp_style"]; seq_chord_velocity = data_json["seq_chord_velocity"]; seq_chord_dexed_inst = data_json["seq_chord_dexed_inst"] ; seq_chain_lenght = data_json["seq_chain_lenght"]; seq_transpose = data_json["seq_transpose"]; seq_chord_key_ammount = data_json["chord_key_ammount"]; seq_oct_shift = data_json["seq_oct_shift"]; seq_element_shift = data_json["seq_element_shift"]; configuration.sys.performance_number = data_json["performance"]; load_sd_performance_json(configuration.sys.performance_number); if (seq_running) timer1.begin(sequencer, seq_tempo_ms / 2); else timer1.begin(sequencer, seq_tempo_ms / 2, false); 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 } } return (false); } bool check_sd_seq_exists(uint8_t number) { if (number < 0) return (false); number = constrain(number, 0, 99); AudioNoInterrupts(); if (sd_card > 0) { char filename[FILENAME_LEN]; sprintf(filename, "/%s/%s%d.json", SEQ_CONFIG_PATH, SEQ_CONFIG_NAME, number); // check if file exists... if (SD.exists(filename)) { AudioInterrupts(); return (true); } else { AudioInterrupts(); return (false); } } else { 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; StaticJsonDocument data_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) { 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); sprintf(filename, "/%s/%s%d.json", PERFORMANCE_CONFIG_PATH, PERFORMANCE_CONFIG_NAME, p); if (sd_card > 0) { File json; StaticJsonDocument data_json; #ifdef DEBUG Serial.print(F("Saving performance config as JSON")); Serial.print(p); Serial.print(F(" to ")); Serial.println(filename); #endif AudioNoInterrupts(); // Check if voice- and fx-config exist. If not, save the actual state sprintf(filename, "/%s/%s%d.json", FX_CONFIG_PATH, FX_CONFIG_NAME, configuration.performance.fx_number); if (!SD.exists(filename)) { #ifdef DEBUG Serial.print(F("FX-Config ")); Serial.print(configuration.performance.fx_number); Serial.println(F(" does not exists, creating one.")); #endif //save_sd_performance_json(configuration.performance.fx_number); save_sd_fx_json(configuration.performance.fx_number); } for (uint8_t i = 0; i < MAX_DEXED; i++) { sprintf(filename, "/%s/%s%d.json", VOICE_CONFIG_PATH, VOICE_CONFIG_NAME, configuration.performance.voiceconfig_number[i]); if (!SD.exists(filename)) { #ifdef DEBUG Serial.print(F("Voice-Config ")); Serial.print(configuration.performance.voiceconfig_number[i]); Serial.println(F(" does not exists, creating one.")); #endif save_sd_voiceconfig_json(configuration.performance.voiceconfig_number[i], i); } } sprintf(filename, "/%s/%s%d.json", PERFORMANCE_CONFIG_PATH, PERFORMANCE_CONFIG_NAME, p); json = SD.open(filename, FILE_WRITE); if (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++; } }