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