/*
   MicroDexed

   MicroDexed is a port of the Dexed sound engine
   (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield.
   Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android

   (c)2018-2021 H. Wirtz <wirtz@parasitstudio.de>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA

*/

#include <Arduino.h>
#include "config.h"
#include <Wire.h>
#include <SD.h>
#include <ArduinoJson.h>
#include "synth_dexed.h"
#include "dexed_sd.h"
#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<JSON_BUFFER> 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 /<b>/<bank_name>.syx
#ifdef DEBUG
    Serial.print(F("Storing bank as "));
    Serial.print(bank_filename);
    Serial.print(F("..."));
#endif
    root = SD.open(bank_filename, FILE_WRITE);
    root.write(data, 4104);
    root.close();
    AudioInterrupts();
#ifdef DEBUG
    Serial.println(F(" done."));
#endif
  }
  else
    return (false);

  return (true);
}

/******************************************************************************
   SD VOICECONFIG
 ******************************************************************************/
bool load_sd_voiceconfig_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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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<JSON_BUFFER_SIZE> 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++;
  }
}