/* MicroDexed MicroDexed is a port of the Dexed sound engine (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6 with audio shield. Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android (c)2018-2020 H. Wirtz 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 "dexed.h" #include "dexed_sd.h" /****************************************************************************** SD BANK/VOICE LOADING ******************************************************************************/ bool load_sd_voice(uint8_t b, uint8_t v, uint8_t instance_id) { #if DEBUG bool found = false; #endif v = constrain(v, 0, MAX_VOICES - 1); b = constrain(b, 0, MAX_BANKS - 1); if (sd_card > 0) { File sysex; char filename[FILENAME_LEN]; uint8_t data[128]; sprintf(filename, "/%d/%s", b, bank_names[instance_id][b]); sysex = SD.open(filename); 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 Serial.print(F("Loading voice from ")); Serial.print(filename); Serial.print(F(" [")); Serial.print(voice_names[instance_id][v]); Serial.println(F("]")); #endif bool ret = MicroDexed[instance_id]->decodeVoice(data); #ifdef DEBUG show_patch(instance_id); #endif configuration.dexed[instance_id].transpose = MicroDexed[instance_id]->data[DEXED_VOICE_OFFSET + DEXED_TRANSPOSE]; sysex.close(); return (ret); } #ifdef DEBUG else Serial.println(F("E : Cannot load voice data")); #endif } #ifdef DEBUG if (found == false) Serial.println(F("E : File not found.")); #endif return (false); } bool get_sd_voice(File sysex, uint8_t voice_number, uint8_t* data) { uint16_t n; int32_t bulk_checksum_calc = 0; int8_t bulk_checksum; if (sysex.size() != 4104) // check sysex size { #ifdef DEBUG Serial.println(F("E : SysEx file size wrong.")); #endif return (false); } if (sysex.read() != 0xf0) // check sysex start-byte { #ifdef DEBUG Serial.println(F("E : SysEx start byte not found.")); #endif return (false); } if (sysex.read() != 0x43) // check sysex vendor is Yamaha { #ifdef DEBUG Serial.println(F("E : SysEx vendor not Yamaha.")); #endif return (false); } sysex.seek(4103); if (sysex.read() != 0xf7) // check sysex end-byte { #ifdef DEBUG Serial.println(F("E : SysEx end byte not found.")); #endif return (false); } sysex.seek(3); if (sysex.read() != 0x09) // check for sysex type (0x09=32 voices) { #ifdef DEBUG Serial.println(F("E : SysEx type not 32 voices.")); #endif return (false); } sysex.seek(4102); // Bulk checksum bulk_checksum = sysex.read(); sysex.seek(6); // start of bulk data for (n = 0; n < 4096; n++) { uint8_t d = sysex.read(); if (n >= voice_number * 128 && n < (voice_number + 1) * 128) data[n - (voice_number * 128)] = d; bulk_checksum_calc -= d; } bulk_checksum_calc &= 0x7f; #ifdef DEBUG Serial.print(F("Bulk checksum : 0x")); Serial.print(bulk_checksum_calc, HEX); Serial.print(F(" [0x")); Serial.print(bulk_checksum, HEX); Serial.println(F("]")); #endif if (bulk_checksum_calc != bulk_checksum) { #ifdef DEBUG Serial.print(F("E : Bulk checksum mismatch : 0x")); Serial.print(bulk_checksum_calc, HEX); Serial.print(F(" != 0x")); Serial.println(bulk_checksum, HEX); #endif return (false); } MicroDexed[0]->render_time_max = 0; return (true); } /****************************************************************************** SD VOICECONFIG ******************************************************************************/ bool load_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); // first check if file exists... 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(); 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_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 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(); return (true); } sysex.close(); } 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(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); // first check if file exists... 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(); 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_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 sysex = SD.open(filename, FILE_WRITE); if (sysex) { if (write_sd_data(sysex, 0x43, (uint8_t*)&configuration.fx, sizeof(configuration.fx))) { sysex.close(); return (true); } sysex.close(); } 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(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); // first check if file exists... 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(); 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 sysex = SD.open(filename, FILE_WRITE); if (sysex) { if (write_sd_data(sysex, 0x44, (uint8_t*)&configuration.performance, sizeof(configuration.performance))) { sysex.close(); return (true); } sysex.close(); } else { #ifdef DEBUG Serial.print(F("E : Cannot open ")); Serial.print(filename); Serial.println(F(" on SD.")); #endif return (false); } } 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 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; } #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 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); #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 create_sd_voiceconfig_filename(char* filename, uint8_t b, uint8_t instance_id) { // init and set name for actual bank memset(filename, 0, FILENAME_LEN); sprintf(filename, "/%d/%s", b, bank_names[instance_id][b]); #ifdef DEBUG Serial.print(F("Created filename from bank ")); Serial.print(b, DEC); Serial.print(F(" and name ")); Serial.print(bank_names[instance_id][b]); Serial.print(F(": [")); Serial.print(filename); Serial.println(F("]")); #endif } void strip_extension(char* s, char* target) { char tmp[BANK_NAME_LEN]; char* token; strcpy(tmp, s); token = strtok(tmp, "."); if (token == NULL) strcpy(target, "*ERROR*"); else strcpy(target, token); } bool get_voice_names_from_bank(uint8_t b, uint8_t instance_id) { File sysex; uint8_t voice_counter = 0; int32_t bulk_checksum_calc = 0; int8_t bulk_checksum; b = constrain(b, 0, MAX_BANKS - 1); // erase all data for voice names memset(voice_names[instance_id], 0, MAX_VOICES * VOICE_NAME_LEN); if (sd_card > 0) { char filename[FILENAME_LEN]; sprintf(filename, "/%d/%s", b, bank_names[instance_id][b]); #ifdef DEBUG Serial.print(F("Reading voice names for bank [")); Serial.print(filename); Serial.println(F("]")); #endif // try to open bank directory sysex = SD.open(filename); if (!sysex) return (false); if (sysex.size() != 4104) // check sysex size { #ifdef DEBUG Serial.println(F("E : SysEx file size wrong.")); #endif return (false); } if (sysex.read() != 0xf0) // check sysex start-byte { #ifdef DEBUG Serial.println(F("E : SysEx start byte not found.")); #endif return (false); } if (sysex.read() != 0x43) // check sysex vendor is Yamaha { #ifdef DEBUG Serial.println(F("E : SysEx vendor not Yamaha.")); #endif return (false); } sysex.seek(4103); if (sysex.read() != 0xf7) // check sysex end-byte { #ifdef DEBUG Serial.println(F("E : SysEx end byte not found.")); #endif return (false); } sysex.seek(3); if (sysex.read() != 0x09) // check for sysex type (0x09=32 voices) { #ifdef DEBUG Serial.println(F("E : SysEx type not 32 voices.")); #endif return (false); } sysex.seek(4102); // Bulk checksum bulk_checksum = sysex.read(); sysex.seek(6); // start of bulk data for (uint16_t n = 0; n < 4096; n++) { uint8_t d = sysex.read(); if ((n % 128) >= 118 && (n % 128) < 128) // found the start of the voicename { voice_names[instance_id][voice_counter][(n % 128) - 118] = d; } if (n % 128 == 127) voice_counter++; bulk_checksum_calc -= d; } bulk_checksum_calc &= 0x7f; #ifdef DEBUG Serial.print(F("Bulk checksum : 0x")); Serial.print(bulk_checksum_calc, HEX); Serial.print(F(" [0x")); Serial.print(bulk_checksum, HEX); Serial.println(F("]")); #endif if (bulk_checksum_calc != bulk_checksum) { #ifdef DEBUG Serial.print(F("E : Bulk checksum mismatch : 0x")); Serial.print(bulk_checksum_calc, HEX); Serial.print(F(" != 0x")); Serial.println(bulk_checksum, HEX); #endif return (false); } } return (false); } uint8_t get_bank_names(uint8_t instance_id) { File root; uint8_t bank_counter = 0; // erase all data for bank names memset(bank_names[instance_id], 0, MAX_BANKS * BANK_NAME_LEN); if (sd_card > 0) { char bankdir[4]; do { // init and set name for actual bank directory sprintf(bankdir, "/%d", bank_counter); // try to open directory root = SD.open(bankdir); if (!root) break; // read filenames File entry = root.openNextFile(); if (!entry.isDirectory()) { strcpy(bank_names[instance_id][bank_counter], entry.name()); #ifdef DEBUG Serial.print(F("Found bank [")); Serial.print(bank_names[instance_id][bank_counter]); Serial.println(F("]")); #endif bank_counter++; } } while (root); return (bank_counter); } else return (0); }