/*
  MidiSynth, MIDI synthesiser driver module for the MidiSupport system

  store.c - controls the storage of instrument and bank definitions

  created  01/02/2021
*/

#include <time.h>
#include "main.h"
#include "kbd.h"
#include "kernel.h"
#include "swis.h"
#include "store.h"

// -----  Default Sound Set  -----
// SoundSet.c
extern const char def_name[];                          // name of sound set
extern const unsigned int def_date;                    // creation date/time
extern const ins_t def_instrument[];                   // array of instruments
extern const int num_def_instruments;                  // number of entries in def_instrument[]
extern const bank_t def_bank[];                        // instrument and drum kit banks, array of array's of indexes to instruments[]
extern const int num_def_banks;                        // number of entries in def_bank[]
extern const unsigned char def_banks[MIDI_DATA_RANGE]; // bank selector, indexes to bank[]
extern const unsigned char def_kits[MIDI_DATA_RANGE];  // drum kit selector, indexes to bank[]
extern const harm_t def_wave[];                        // array of waveform harmonic data
extern const int num_def_waves;                        // number of entries in def_wave[]
// -------------------------------

#define NPT (1<<SIN_BITS)
typedef struct fft_data_s // fft data
{
  int xr[NPT]; // real component
  int xi[NPT]; // imaginary component
} fft_data_t;

static fft_data_t fd;

// fft.s
#define INVERSE 0
#define FORWARD 1
void fft(fft_data_t *fd, int dir);

#define TIDY_HARMS // Remove unecessary data from the harmonics when creating the source listing

/*
 Sound Set file format
 ---------------------

 A binary file consisting of the following.

 1. File Header, 48 byte
      24 byte: ascii name null padded
      4 byte: (unused)
      4 byte: timestamp
      2 byte: number of instruments
      2 byte: size of each instrument (128)
      2 byte: number of banks
      2 byte: size of each bank       (280)
      2 byte: number of waveforms
      2 byte: size of each waveform   (152)
      4 byte: crc of following data

 2. Instrument data, an array of instruments
    n x 128 byte instrument definitions, of 24 byte name then 104 byte instrument data

 3. Bank data, arrays of banks each, containting indexes to instruments
    n x 280 byte arrays, of 24 byte name then 128 x 2 byte entries

 4. Bank selector data, arrays of indexes to banks,
    2 arrays of 128 bytes, one for melodic instruments, one for percussion drum kits

 5. Waveform's harmonic data array, (optional)
    n x 152 byte waveform definitions, of 24 byte name then 32 x 4 byte harmonic data (2 byte real, 2 byte imaginary)

 All names are ascii null padded strings
*/

//file header
typedef struct InsHdr_s
{
  char name[NAME_LEN];  // ascii string, null padded
  int spare;            // (currently not used)
  unsigned int stamp;   // timestamp, seconds since 1/1/2001
  short int num_instr;  // number of instruments
  short int instr_size; // size of a single instrument
  short int num_banks;  // number of banks
  short int bank_size;  // size of a single bank
  short int num_waves;  // number of waveforms
  short int wave_size;  // total size of the waveforms harmonic data
  unsigned int crc;     // 32 bit crc of the rest of the following data
} InsHdr_t;

extern const char * const note[]; // midi.c

// OS_file bits
#define OS_FILE         8
#define READ_CATINFO    17
#define WRITE_CATINFO   18
#define IS_FILE         1
// file types
#define TEXT            0xfff
#define DATA            0xffd
#define CSV             0xdfe


/*
 * report_error
 * ------------
 * Returns 1 if OK selected, 2 if Cancel selected
 */
#define FLG_OK        1
#define FLG_CANCEL    2
#define FLG_HIGHLIGHT 4
#define FLG_NO_ERROR 16
#define FLG_QUIET   128
int report_error(char *errmess, int flags)
{
  _kernel_oserror blk;
  _kernel_swi_regs regs;

  blk.errnum = 1;
  strcpy(blk.errmess, errmess);
  regs.r[0] = (int)&blk;
  regs.r[1] = flags;
  regs.r[2] = (int)"Midi Synth";
  _kernel_swi(Wimp_ReportError, &regs, &regs);

  return regs.r[1];
}


/*
 * ins_pntr
 * --------
 * instr = patch number
 * returns a pointer to the instrument, or NULL if the instrument doesn't exist.
 */
static ins_t *ins_pntr(ins_data_t* idat, int instr)
{
  patch_t p = syn.kbd_patch.list[instr];
  int b, i;

  if(p.hi > PERCUSSION_BANK) // percussion
  {
    if((b = idat->kits[p.prg]) != 0) // kit
      if((i = idat->bank[b-1].ins[p.lo]) != 0) // key
        return &idat->instrument[i-1];
  }
  else if(p.hi < PERCUSSION_BANK) // melodic
  {
    if((b = idat->banks[p.lo]) != 0) // bank
      if((i = idat->bank[b-1].ins[p.prg]) != 0) // program
        return &idat->instrument[i-1];
  }
  return NULL;
}


/*
 * compile_list
 * ------------
 * Counts the number of patches.
 * if 'update' is true, updates the patch list and assumes it is large enough.
 * Returns the number of patches.
 *
 * note. The patch list is only used by the editor as a means of selecting instruments.
 *       Synth operation does not need it.
 */
#define COUNT  0
#define UPDATE 1

static int add_patch(patch_t *p, int hi, int lo, int prg, int update)
{
  if(update)
  {
    p->hi = hi;
    p->lo = lo;
    p->prg = prg;
  }
  return 1;
}

static int compile_list(ins_data_t *idat, patch_t *patch, int update)
{
  int b, lo, prg, k = 0;

  // melodic
  if(syn.switches & (1<<PATCH_BY_BANK)) // (by bank)
  {
    for(lo=0; lo<MIDI_DATA_RANGE; lo++)
      if((b = idat->banks[lo]) != 0)
        for(prg=0; prg<MIDI_DATA_RANGE; prg++)
          if(idat->bank[b-1].ins[prg])
            k += add_patch(&patch[k], 0, lo, prg, update);
  }
  else // (by program)
  {
    for(prg=0; prg<MIDI_DATA_RANGE; prg++)
      for(lo=0; lo<MIDI_DATA_RANGE; lo++)
        if((b = idat->banks[lo]) != 0)
          if(idat->bank[b-1].ins[prg])
            k += add_patch(&patch[k], 0, lo, prg, update);
  }

  // drum kits
  if(!(syn.switches & (1<<PATCH_NO_KITS)))
    for(prg=0; prg<MIDI_DATA_RANGE; prg++)
      if(idat->kits[prg])
        k += add_patch(&patch[k], PERCUSSION_BANK, 0, prg, update);

  // percussion
  if(syn.switches & (1<<PATCH_BY_BANK)) // (by program)
  {
    for(prg=0; prg<MIDI_DATA_RANGE; prg++)
      if((b = idat->kits[prg]) != 0)
        for(lo=0; lo<MIDI_DATA_RANGE; lo++) // note number
          if(idat->bank[b-1].ins[lo])
            k += add_patch(&patch[k], PERCUSSION_INST, lo, prg, update);
  }
  else // (by note)
  {
    for(lo=0; lo<MIDI_DATA_RANGE; lo++) // note number
      for(prg=0; prg<MIDI_DATA_RANGE; prg++)
        if((b = idat->kits[prg]) != 0)
          if(idat->bank[b-1].ins[lo])
            k += add_patch(&patch[k], PERCUSSION_INST, lo, prg, update);
  }

  return k;
}


/*
 * update_list
 * -----------
 * Updates the instrument patch list. Ensures the list array is large enough to
 * hold all the entries.
 * Returns any error.
 */
int update_list(ins_data_t* idat)
{
  int num = compile_list(idat, syn.kbd_patch.list, COUNT); // count the number of patches

  // if the current list is not large enough, re-allocate
  if(num > syn.kbd_patch.max)
  {
    if(syn.kbd_patch.list)
      free(syn.kbd_patch.list);
    if((syn.kbd_patch.list = malloc(num * sizeof(kbd_patch_t))) == 0)
      return -CANNOT_ALLOCATE_MEMORY;
    syn.kbd_patch.max = num;
  }
  // note. num can be less than max if instruments have been deleted or a smaller
  //       sound set is loaded.

  syn.kbd_patch.num = compile_list(idat, syn.kbd_patch.list, UPDATE); // update the list

  return NO_ERROR_;
}


/*
 * save_new
 * --------
 * Saves the user instrument as a new variation in the first empty entry.
 * 'instr' is the source of the edited instrument which is held in 'user'. It tells
 * us what we were editing.
 */
int save_new(ins_data_t* idat, int instr)
{
  patch_t p = syn.kbd_patch.list[instr];
  int i,j,b;
  unsigned char *sel; // banks selector
  int ins, bnk; // index's
  char *name;

  if(p.hi == PERCUSSION_BANK)
    return -NOT_EDITABLE; // cannot save a whole drum kit
  if(!syn.user.name[0])
    return -NO_INSTRUMENT_NAME; // instrument must have a name or the array slot is assumed empty

  // find the first empty instrument slot
  for(i=0; i<idat->max_instruments; i++)
    if(idat->instrument[i].name[0] == 0) // slot is empty if there is no name
      break;

  if(i >= idat->max_instruments)
    return -TOO_MANY_INSTRUMENTS;

  // i = instrument number

  if(p.hi > PERCUSSION_BANK) // percussion
  {
    sel = idat->kits;
    ins = p.lo;
    bnk = p.prg;
    name = "New Kit";
  }
  else // melodic
  {
    sel = idat->banks;
    ins = p.prg;
    bnk = p.lo;
    name = "New Bank";
  }

  // locate the new instrument in the next free bank/kit, ignore banks lower than the current one.
  for(j=bnk+1; j<MIDI_DATA_RANGE; j++)
  {
    if(sel[j] > 0)
    {
      b = sel[j] - 1;
      if(idat->bank[b].ins[ins] > 0)
        continue;
      else
      { // empty slot in existing bank/kit
        idat->bank[b].ins[ins] = i + 1; // locate new instrument
        break;
      }
    }
    // create a new bank, search for an unused bank before creating an additional one.
    for(b=0; b<idat->num_banks; b++)
      if(idat->bank[b].name[0] == 0) // bank is unused if there is no name
        break;
    if(b >= idat->num_banks)
    { // no unused banks, check if there's room to allocate an additional bank
      if(idat->num_banks >= idat->max_banks)
        return -TOO_MANY_BANKS;
      idat->num_banks++;
    }
    sel[j] = b + 1;      // create new bank/kit
    idat->bank[b].ins[ins] = i + 1;  // locate new instrument
    strcpy(idat->bank[b].name, name);
      break;
  }
  if(j >= MIDI_DATA_RANGE)
    return -TOO_MANY_BANKS; // should never happen here

  idat->instrument[i] = syn.user; // create new instrument
  if(i >= idat->num_instruments)
    idat->num_instruments = i + 1;

  return update_list(idat);
}


/*
 * save_instrument
 * ---------------
 *   patch (destination) = bits: 23-16 Program, 15-8 Bank Lo, 7-0 Bank Hi
 *   If hi < 127, user/instrument is a melodic instrument
 *   If hi > 127, user/instrument is a drum kit part.
 *
 * action = 0: Saves the user instrument as a new instrument referenced in the given bank/prog
 * action = 1: "instr" is given a new reference in the given bank/prog
 * action = 2: cur_patch is moved to the given bank/prog
 */
int save_instrument(ins_data_t* idat, int patch, int action, int instr)
{
  int hi = patch & 0xff;
  int lo = (patch >> 8) & 0xff;
  int prg = (patch >> 16) & 0xff;
  int i=0, b;
  unsigned char *sel; // banks selector
  int ins, bnk; // index's
  char *name;
  unsigned short *src = NULL;

  if(hi == PERCUSSION_BANK)
    return -NOT_EDITABLE; // cannot save a whole drum kit
  if(!syn.user.name[0])
    return -NO_INSTRUMENT_NAME; // instrument must have a name or the array slot is assumed empty

  if(action == LOCATE_INSTR) // just locating (referencing) the given intrument number
    i = instr;

  else if(action == CREATE_NEW) // creating a new instrument from user and locating it
  {
    // find the first empty instrument slot
    for(i=0; i<idat->max_instruments; i++)
      if(idat->instrument[i].name[0] == 0) // slot is empty if there is no name
        break;

    if(i >= idat->max_instruments)
      return -TOO_MANY_INSTRUMENTS;
  }

  else if(action == MOVE_PATCH) // move the current patch
  {
    patch_t p = syn.kbd_patch.list[syn.kbd_patch.cur];
    i = -1;

    if(p.hi > PERCUSSION_BANK) // percussion
    {
      if((b = idat->kits[p.prg]) != 0) // kit
        i = *(src = &idat->bank[b-1].ins[p.lo]) - 1; // key
    }
    else if(p.hi < PERCUSSION_BANK) // melodic
    {
      if((b = idat->banks[p.lo]) != 0) // bank
        i = *(src = &idat->bank[b-1].ins[p.prg]) - 1; // program
    }

    if(i < 0)
      return -NO_INSTRUMENT;
  }

  // i = instrument number to store in the required bank

  if(hi > PERCUSSION_BANK) // percussion
  {
    sel = idat->kits;
    ins = lo;
    bnk = prg;
    name = "New Kit";
  }
  else // melodic
  {
    sel = idat->banks;
    ins = prg;
    bnk = lo;
    name = "New Bank";
  }

  if((b = sel[bnk]) != 0) // bank exists
  {
    if(idat->bank[b-1].ins[ins] != 0)
      if(report_error("Location references an instrument, Overwrite ?",
                         FLG_OK | FLG_CANCEL | FLG_NO_ERROR | FLG_QUIET) != 1)
        return NO_ERROR_; // operation aborted
    idat->bank[b-1].ins[ins] = i + 1; // locate instrument, overwrite any existing instrument reference
  }
  else // create new bank, search for an unused bank before creating an additional one.
  {
    for(b=0; b<idat->num_banks; b++)
      if(idat->bank[b].name[0] == 0) // bank is unused if there is no name
        break;
    if(b >= idat->num_banks)
    { // no unused banks, check if there's room to allocate an additional bank
      if(idat->num_banks >= idat->max_banks)
        return -TOO_MANY_BANKS;
      idat->num_banks++;
    }
    sel[bnk] = b + 1;      // create new bank/kit
    idat->bank[b].ins[ins] = i + 1;  // locate instrument
    strcpy(idat->bank[b].name, name);
  }

  if(action == CREATE_NEW)
  {
    idat->instrument[i] = syn.user; // create new instrument
    if(i >= idat->num_instruments)
      idat->num_instruments = i + 1;
  }
  else if(action == MOVE_PATCH)
  {
    if(src)
      *src = 0; // clear source reference
  }

  return update_list(idat);
}


/*
 * clear_instrument
 * ----------------
 * action = 0: Delete instrument and all bank references
 * action = 1: Just clear the current bank reference
 */
int clear_instrument(ins_data_t* idat, int instr, int action)
{
  if(instr < 0)
    return NO_ERROR_;

  patch_t p = syn.kbd_patch.list[instr];

  if(p.hi == PERCUSSION_BANK)
    return -NOT_EDITABLE; // cannot clear a whole drum kit

  unsigned char *sel; // bank selector
  int ins, i, j, b, r;
  if(p.hi > PERCUSSION_BANK)
  {
    sel = idat->kits;
    b = p.prg;
    r = p.lo;
  }
  else
  {
    sel = idat->banks;
    b = p.lo;
    r = p.prg;
  }
  ins = idat->bank[sel[b]-1].ins[r];

  if(ins == 0)
    return -NO_INSTRUMENT;

  if(action == 1) // Just clear the current bank reference
  {
    idat->bank[sel[b]-1].ins[r] = 0;
  }
  else if(action == 0) // Delete instrument and all bank references
  {
    if(report_error("This will permanently delete the Instrument. Are you sure ?",
                       FLG_OK | FLG_CANCEL | FLG_NO_ERROR | FLG_QUIET) != 1)
      return NO_ERROR_; // operation aborted

    memset(&idat->instrument[ins-1], 0, sizeof(ins_t)); // clear instrument

    // remove all references to the instrument in all banks
    for(i=0; i<idat->num_banks; i++)
    {
      unsigned short *ir = idat->bank[i].ins;
      for(j=0; j<MIDI_DATA_RANGE; j++)
        if(ir[j] == ins)
          ir[j] = 0;
    }

    // if any bank is now empty, delete it and any references to it in the bank selector
    for(i=0; i<idat->num_banks; i++)
    {
      unsigned short *ir = idat->bank[i].ins;
      for(j=0; j<MIDI_DATA_RANGE; j++)
        if(ir[j] > 0)
          break;
      if(j >= MIDI_DATA_RANGE)
      {
        // bank empty
        memset(&idat->bank[i], 0, NAME_LEN); // clear the name to mark the bank as unused
        for (j=0; j<MIDI_DATA_RANGE; j++)
          if(sel[j] == i + 1)
            sel[j] = 0; // remove references to it in the selector
      }
    }
  }

  return update_list(idat);
}


/*
 * update_instrument
 * -----------------
 * Updates the instrument with the edited version. This won't affect the default set
 * which is programmed in the code and loaded on startup.
 */
int update_instrument(ins_data_t* idat, int instr)
{
  patch_t p = syn.kbd_patch.list[instr];

  if(p.hi == PERCUSSION_BANK)
    return -NOT_EDITABLE; // cannot edit a whole drum kit
  if(!syn.user.name[0])
    return -NO_INSTRUMENT_NAME; // instrument must have a name or the array slot is assumed empty

  ins_t *ins = ins_pntr(idat, instr);

  if(!ins)
    return -NO_INSTRUMENT;

  *ins = syn.user;

  return NO_ERROR_;
}


/*
 * save_harm_wave
 * --------------
 * Save / delete, wave harmonics data, and the associated waveform.
 *         h = pointer to harmonic data, if 0 then command is delete
 *         w = array index to store / delete. if -1, then command is
 *              "save new" and the first empty slot will be used.
 * Returns any error.
 */
int save_harm_wave(ins_data_t* idat, harm_t *h, int w)
{
  int i, j;

  //----------------------------- delete
  if(h == NULL)
  {
    if((w < 0) || (w >= idat->num_waves))
      return -WAVE_OUT_OF_RANGE;

    // check if the waveform is referenced by any instruments
    j = idat->num_instruments;
    for(i=0; i<j; i++)
    {
      ins_t *ins = &idat->instrument[i];
      if((ins->wave[0].number == w) ||
         (ins->wave[1].number == w) ||
         (ins->mod_wave == w))
      break;
    }
    if(i < j)
      return -WAVE_IN_USE;

    // ok to delete
    memset(&idat->wtbl[w], 0, sizeof(wave_t));
    memset(&idat->harm[w], 0, sizeof(harm_t));
    // check upper used limit of store
    for(i = idat->max_waves-1; i>= 0; i--)
      if((idat->harm[i].name[0]) != 0)
        break;
    idat->num_waves = i + 1;
  }
  //----------------------------- save new
  else if(w == -1)
  {
    if(h == NULL)
      return -NO_WAVEFORM;

    // search through the store for the first empty slot
    j = idat->max_waves;
    for(i=0; i<j; i++)
      if(idat->harm[i].name[0] == 0)
        break;

    if(i >= j)
      return -TOO_MANY_WAVES;

    idat->harm[i] = *h; // update harmonics
    harm_to_wave(h, idat->wtbl[i].wave); // update waveform
    idat->num_waves = i + 1;
  }
  //----------------------------- save to [w]
  else
  {
    if((w < 0) || (w >= idat->num_waves))
      return -WAVE_OUT_OF_RANGE;

    if(h == NULL)
      return -NO_WAVEFORM;

    idat->harm[w] = *h; // update harmonics
    harm_to_wave(h, idat->wtbl[w].wave); // update waveform
  }

  return NO_ERROR_;
}


// date time constants
#define UNIX_TIME_OFFSET  978307200 // number of seconds from 1/1/1970 to 1/1/2001
#define DAY_ZERO_OFFSET      730792 // number of days from 1/3/0000 to 1/1/2001

/*
 * TimestampToDate
 * ---------------
 * Converts timestamp to date/time structure.
 * We use a timestamp epoch of 1/1/2001 but these converters can use any
 * start date just by changing the DAY_ZERO_OFFSET.
 * The maths is correct for the whole 400 year Gregorian cycle and
 * hence forever, but a 32 bit timestamp overflows in the year 2137.
 */
void TimestampToDate(unsigned int t, datetime_t *dt)
{
  unsigned int t1;
  t1 = t / 60;
  dt->second = t - (t1 * 60);
  t = t1 / 60;
  dt->minute = t1 - (t * 60);
  t1 = t / 24;
  dt->hour = t - (t1 * 24);
  dt->weekday = t1 % 7;

  t = t1 + DAY_ZERO_OFFSET; // relocate from 1/1/2001 to 1/3/0000
  unsigned int a = 4 * t / 146097;
  unsigned int b = t + a - (a / 4);
  unsigned int y = (4 * b - 1) / 1461;
  unsigned int d = b - 365 * y - (y / 4) + 122;
  unsigned int m = d * 100 / 3061;
  dt->day = d - m * 3061 / 100;
  if(--m > 12) // 1/3/xxx0 to 1/1/xxx1 correction
  {
    m -= 12;
    y++;
  }
  dt->month = m;
  dt->year = y;
}


/*
 * DateToTimestamp
 * ---------------
 * Converts date/time structure to timestamp.
 */
unsigned int DateToTimestamp(datetime_t *dt)
{
  unsigned int t;
  unsigned int y = dt->year;
  unsigned int m = dt->month;
  if(m < 3) // 1/1/xxx1 to 1/3/xxx0 correction
  {
     m += 12;
     y--;
  }

  t = (365 * y) + (y / 4) - (y / 100) + (y / 400);
  t += (153 * m - 457) / 5 + dt->day - DAY_ZERO_OFFSET; // - relocation from 1/3/0000 to 1/1/2001
//  u->weekday = t % 7;
  t = (t * 24) + dt->hour;
  t = (t * 60) + dt->minute;
  t = (t * 60) + dt->second;

  return t;
}


/*
 * crc32
 * -----
 * crc of a number of blocks. For first block, pass 0 for crc.
 *  The polynomial is 0x04C11DB7 or
 *  x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x1 + 1
 */
static const unsigned int crc32tab[256] =
{
  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
  0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
  0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
  0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
  0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
  0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
  0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
  0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
  0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
  0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
  0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
  0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
  0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
  0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
  0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
  0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
  0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
  0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
  0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
  0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
  0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
  0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
static unsigned int crc32(const void *buf, int len, unsigned int crc)
{
  while(len--)
    crc = (crc>>8) ^ crc32tab[(crc ^ *(unsigned char *)buf++) & 0xff];

  return crc;
}


#ifdef TIDY_HARMS
/*
 * Integer square root
 * -------------------
 * Calculates the integer square root and remainder.
 * Returns the square root.
 */
static unsigned int isqrt(unsigned int n)
{
  unsigned int q, r, s;
  int i;
  q = r = 0;
  for(i=30; i>=0; i-=2)
  {
    r = (r << 2) + ((n >> i) & 3); // shift 2 bits in
    s = (q << 2) + 1; // 4*q + 1
    q <<= 1;
    if(r >= s)
    {
      r -= s;
      q++;
    }
  }
  // q = square root of n, r = remainder
  return q;
}
#endif


/*
 * harm_to_wave
 * ------------
 * converts a harmonic structure to a waveform
 */
void harm_to_wave(const harm_t *h, short int *w)
{
  int i, j;

  // convert harmonic array to waveform
  memset(&fd, 0, sizeof(fd));
  for(i=1; i<=NUM_HARMS; i++)
  {
    fd.xr[i] = h->hrm[i-1].re;
    fd.xi[i] = h->hrm[i-1].im;
    j = (NPT - i) & (NPT - 1);
    fd.xr[j] = fd.xr[i];
    fd.xi[j] = -fd.xi[i];
  }
  fft(&fd, INVERSE);

  // limit data to signed 16 bits and copy to waveform array
  for(i=0; i<NPT; i++)
   if(fd.xr[i] > 0x7fff)
     w[i] = 0x7fff;
   else if(fd.xr[i] < -0x8000)
     w[i] = -0x8000;
   else
     w[i] = fd.xr[i];
}


/*
 * load_defaults
 * -------------
 * Loads the instrument and bank data from the default set programmed in the code.
 */
int load_defaults(ins_data_t* idat)
{
  int max_instruments = num_def_instruments + 100; // allocate extra to allow for editing
  int max_banks = num_def_banks + 10;
  int max_waves = num_def_waves + 5;

  // instruments
  if(idat->instrument)
    free(idat->instrument);
  if((idat->instrument = calloc(max_instruments, sizeof(ins_t))) == NULL)
    return -CANNOT_ALLOCATE_MEMORY;
  memcpy(idat->instrument, &def_instrument, num_def_instruments * sizeof(ins_t));
  idat->num_instruments = num_def_instruments;
  idat->max_instruments = max_instruments;

  // banks
  if(idat->bank)
    free(idat->bank);
  if((idat->bank = calloc(max_banks, sizeof(bank_t))) == NULL)
    return -CANNOT_ALLOCATE_MEMORY;
  memcpy(idat->bank, &def_bank, num_def_banks * sizeof(bank_t));
  idat->num_banks = num_def_banks;
  idat->max_banks = max_banks;

  // selectors
  memcpy(&idat->banks, &def_banks, MIDI_DATA_RANGE);
  memcpy(&idat->kits, &def_kits, MIDI_DATA_RANGE);

  // waveforms
  if(idat->wtbl)
    free(idat->wtbl);
  if((idat->wtbl = calloc(max_waves, sizeof(wave_t))) == NULL)
    return -CANNOT_ALLOCATE_MEMORY;
  if(idat->harm)
    free(idat->harm);
  if((idat->harm = calloc(max_waves, sizeof(harm_t))) == NULL)
    return -CANNOT_ALLOCATE_MEMORY;

  memcpy(idat->harm, &def_wave, num_def_waves * sizeof(harm_t));
  idat->num_waves = num_def_waves;
  idat->max_waves = max_waves;

  int w;
  for(w=0; w<num_def_waves; w++)
    harm_to_wave(&def_wave[w], idat->wtbl[w].wave);

  strncpy(idat->name, def_name, NAME_LEN);
  idat->date = def_date;

  return update_list(idat);
}


/*
 * check_filename
 * --------------
 * Sets the top bit of spaces and replaces the first control character with a null.
 */
#define SPC 32
static void check_filename(char *str)
{
  while(*str >= SPC)
  {
    if(*str == SPC)
      *str = SPC | 0x80;
    str++;
  }
  *str = 0;
}


/*
 * load_file
 * ---------
 * Loads the instrument and bank data from the given file
 */
int load_file(ins_data_t* idat, char *filename)
{
  #define BUFF_SIZE 32
  unsigned char buff[BUFF_SIZE];
  int err = NO_ERROR_, len, crc = 0, n;
  FILE *f;
  InsHdr_t hdr;

  check_filename(filename);
  if((f = fopen(filename, "rb")) == NULL)
    return -PROBLEM_OPENING_FILE;

  if(fread(&hdr, sizeof(InsHdr_t), 1, f) != 1) // read header
    err = -INVALID_DATA;

  else if((hdr.instr_size != sizeof(ins_t)) ||
          (hdr.bank_size != sizeof(bank_t)) ||
          (hdr.wave_size != sizeof(harm_t)))    // check that data sizes agree with ours
    err = -INVALID_SIZE;

  else
  {
    // length of remainder of file
    len = hdr.num_instr * sizeof(ins_t) +
          hdr.num_banks * sizeof(bank_t) +
          MIDI_DATA_RANGE + MIDI_DATA_RANGE;
    if(hdr.num_waves > 0)
      len += hdr.num_waves * sizeof(harm_t);

    // check validity of data
    while((len > 0) && (err >= NO_ERROR_))
    {
      n = (len > BUFF_SIZE) ? BUFF_SIZE : len;
      if(fread(buff, 1, n, f) != n)
        err = -PROBLEM_READING_FILE;
      else
        crc = crc32(buff, n, crc);
      len -= n;
    }
    if(err >= NO_ERROR_)
    {
      if(crc != hdr.crc)
        err = -INVALID_CRC;
      else // data valid
      {
        fseek(f, sizeof(hdr), SEEK_SET);

        int max_instruments = hdr.num_instr + 100; // allocate extra to allow for editing
        int max_banks = hdr.num_banks + 10;
        int max_waves = hdr.num_waves + 5;

        // instruments
        if(idat->instrument)
          free(idat->instrument);
        if((idat->instrument = calloc(max_instruments, sizeof(ins_t))) == NULL)
          err = -CANNOT_ALLOCATE_MEMORY;
        else
        {
          fread(idat->instrument, sizeof(ins_t), hdr.num_instr, f);
          idat->num_instruments = hdr.num_instr;
          idat->max_instruments = max_instruments;

          // banks
          if(idat->bank)
            free(idat->bank);
          if((idat->bank = calloc(max_banks, sizeof(bank_t))) == NULL)
            err = -CANNOT_ALLOCATE_MEMORY;
          else
          {
            fread(idat->bank, sizeof(bank_t), hdr.num_banks, f);
            idat->num_banks = hdr.num_banks;
            idat->max_banks = max_banks;

            // selectors
            fread(&idat->banks, MIDI_DATA_RANGE, 1, f);
            fread(&idat->kits, MIDI_DATA_RANGE, 1, f);

            // waveforms
            if(hdr.num_waves > 0)
            {
              if(idat->wtbl)
                free(idat->wtbl);
              if((idat->wtbl = calloc(max_waves, sizeof(wave_t))) == NULL)
                err = -CANNOT_ALLOCATE_MEMORY;
              else
              {
                if(idat->harm)
                  free(idat->harm);
                if((idat->harm = calloc(max_waves, sizeof(harm_t))) == NULL)
                  err = -CANNOT_ALLOCATE_MEMORY;
                else
                {
                  fread(idat->harm, sizeof(harm_t), hdr.num_waves, f);
                  idat->num_waves = hdr.num_waves;
                  idat->max_waves = max_waves;

                  // create waveforms from the harmonic data
                  int w;
                  for(w=0; w<hdr.num_waves; w++)
                    harm_to_wave(&idat->harm[w], idat->wtbl[w].wave);
                }
              }
            }
          }
        }
      }
    }
  }
  fclose(f);

  if(err >= NO_ERROR_)
  {
    strncpy(idat->name, hdr.name, NAME_LEN);
    idat->date = hdr.stamp;
    err = update_list(idat); // create patch list
  }

  return err;
}


/*
 * set_filetype
 * ------------
 */
void set_filetype(char *filename, int filetype)
{
  _kernel_swi_regs regs;

  regs.r[0] = WRITE_CATINFO;
  regs.r[1] = (int)filename;
  regs.r[2] = filetype;
  _kernel_swi(OS_File, &regs, &regs);
}


/*
 * save_data
 * ---------
 * Saves the instrument and bank store to a data file with the
 * given filename.
 * action can be ALL or NO_WAVES
 * Filename should include path but not extension.
 */
int save_data(ins_data_t* idat, char *filename, int action)
{
  FILE *f;
  check_filename(filename);
  if((f = fopen(filename, "wb")) == NULL)
    return -PROBLEM_OPENING_FILE;

  unsigned int crc;
  crc = crc32(idat->instrument, sizeof(ins_t) * idat->num_instruments, 0);
  crc = crc32(idat->bank, sizeof(bank_t) * idat->num_banks, crc);
  crc = crc32(idat->banks, MIDI_DATA_RANGE, crc);
  crc = crc32(idat->kits, MIDI_DATA_RANGE, crc);
  if(action != NO_WAVES)
    crc = crc32(idat->harm, sizeof(harm_t) * idat->num_waves, crc);

  time_t t = time(0) - UNIX_TIME_OFFSET; // 1/1/2001 - 1/1/1970
  InsHdr_t hdr = (InsHdr_t){{0},
                            0, // (currently not used)
                            t, // use current time when saving the data type file
                            idat->num_instruments, sizeof(ins_t),
                            idat->num_banks, sizeof(bank_t),
                            (action != NO_WAVES) ? idat->num_waves : 0, sizeof(harm_t),
                            crc};
  strncpy(hdr.name, idat->name, NAME_LEN);

  fwrite(&hdr, sizeof(InsHdr_t), 1, f);
  fwrite(idat->instrument, sizeof(ins_t), idat->num_instruments, f);
  fwrite(idat->bank, sizeof(bank_t), idat->num_banks, f);
  fwrite(idat->banks, MIDI_DATA_RANGE, 1, f);
  fwrite(idat->kits, MIDI_DATA_RANGE, 1, f);
  if(action != NO_WAVES)
    fwrite(idat->harm, sizeof(harm_t), idat->num_waves, f);

  fclose(f);
  set_filetype(filename, DATA);
  idat->date = t; // update store date

  return NO_ERROR_;
}


/*
 * save_source
 * -----------
 * Saves the instrument store as 'c' source to a file with the
 * given filename. Creates definitions for instruments, banks, and waves.
 * The resulting file can replace the file in the source code to update the
 * defaults.
 * Also saves a text patch list, a csv bank map, and a text instrument list.
 * Filename should include path but no extension.
 */
// save envelope as c source
static void save_env(FILE *f, const env_t *e)
{
  fprintf(f, "{%d,%d,%d,%d,%d,%d,%d,%d}",
             e->delay, e->attack_step, e->attack_target, e->hold,
             e->decay_step, e->decay_target, e->sustain_step, e->release_step);
}
// save wave as c source
static void save_wave(FILE *f, const tone_t *t)
{
  fprintf(f, "{%d,%d,%d,%d,",
             t->number,
             t->pitch,
             t->initial_pitch,
             t->glide);
  save_env(f, &t->env);
  fprintf(f, "}");
}
// save instrument as c source
static void save_instr(FILE *f, ins_t *ins)
{
  fprintf(f, "{\"%s\",{", ins->name);
  save_wave(f, &ins->wave[0]);
  fprintf(f, ",");
  save_wave(f, &ins->wave[1]);
  fprintf(f, "},");
  save_env(f, &ins->noise_env);
  fprintf(f, ",");
  save_env(f, &ins->filter_env);
  fprintf(f, ",%d,", ins->switches);
  fprintf(f, "%d,", ins->filter_fc);
  fprintf(f, "%d,", ins->filter_q);
  fprintf(f, "%d,", ins->retrig);
  fprintf(f, "%d,", ins->mod_wave);
  fprintf(f, "%d,", ins->mod_rate);
  fprintf(f, "%d,", ins->mod_depth);
  fprintf(f, "%d,", ins->fm_depth);
  fprintf(f, "%d,", ins->detune);
  fprintf(f, "%d,", ins->gain);
  fprintf(f, "%d}", ins->master_env);
}
// save data set as c source, text patch list, csv bank map, text instrument list
// flags: bit 0: c source, bit 1: text patch list, bit 2: csv bank map, bit 3: text instrument list
int save_source(ins_data_t* idat, char *filename, int flags)
{
  static const char
    wd[7][4] = {"Mon","Tue","Wed","Thu","Fri","Sat","Sun"},
    ms[12][4] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
  datetime_t dt;
  TimestampToDate(idat->date, &dt);
  char date[28];
  sprintf(date, "%s %d %s %d %02d:%02d:%02d", wd[(int)dt.weekday], dt.day, ms[dt.month-1], dt.year, dt.hour, dt.minute, dt.second);

  static char s[256];
  FILE *f;
  int b, i, k, w, ins;

  check_filename(filename);

  // c source
  // --------
  if(flags & (1<<C_SRC))
  {
    sprintf(s, "%s/c", filename);
    if((f = fopen(s, "wb")) == NULL)
      return -PROBLEM_OPENING_FILE;

    fprintf(f, "/*\n"
               "  Default Instruments and Banks\n"
               "  -----------------------------\n"
               "  file: %s\n"
               "  created: %s\n"
               "  AUTOMATICALLY GENERATED FILE\n"
               "*/\n"
               "#include \"main.h\"\n"
               "#include \"kbd.h\"\n"
               "#include \"store.h\"\n\n", filename, date);

    fprintf(f, "const char def_name[] = \"Default Sound Set\";\n");
    fprintf(f, "const unsigned int def_date = %u;\n\n", idat->date);

    // instruments
    fprintf(f, "// Instruments\n"
               "const ins_t def_instrument[] =\n{\n");
    for(i=0; i<idat->num_instruments; i++)
    {
      fprintf(f, "/*%4d */", i+1);
      save_instr(f, &idat->instrument[i]);
      fprintf(f, "%s", (i == idat->num_instruments-1) ? "\n};\n" : ",\n");
    }
    fprintf(f, "const int num_def_instruments = %d;\n\n", idat->num_instruments);

    // banks
    fprintf(f, "// Banks\n"
               "const bank_t def_bank[] =\n{\n");
    for(b=0; b<idat->num_banks; b++)
    {
      fprintf(f, "/*%4d */{\"%s\",\n  {\n", b + 1, idat->bank[b].name);
      for(i=0; i<MIDI_DATA_RANGE; i++)
      {
        if((i & 15) == 0)
          fprintf(f, "    ");
        fprintf(f, "%3d", idat->bank[b].ins[i]);

        if(i < (MIDI_DATA_RANGE - 1))
          fprintf(f, ",");
        if((i & 15) == 15)
          fprintf(f, "\n");
      }
      fprintf(f, "  }}%s\n", (b == idat->num_banks-1) ? "" : ",");
    }
    fprintf(f, "};\nconst int num_def_banks = %d;\n\n", idat->num_banks);

    // selectors
    for(b=0; b<2; b++)
    {
      fprintf(f, "// %s selector\n", (b == 0) ? "Bank" : "Drum Kit");
      fprintf(f, "const unsigned char def_%s[MIDI_DATA_RANGE] = \n{\n", (b == 0) ? "banks" : "kits");
      for(i=0; i<MIDI_DATA_RANGE; i++)
      {
        if((i & 15) == 0)
          fprintf(f, "  ");
        fprintf(f, "%2d", (b==0) ? idat->banks[i] : idat->kits[i]);

        if(i < (MIDI_DATA_RANGE - 1))
          fprintf(f, ",");
        if((i & 15) == 15)
          fprintf(f, "\n");
      }
      fprintf(f, "};\n\n");
    }

    // waveform harmonics
    fprintf(f, "// Waveforms harmonic data\n"
               "const harm_t def_wave[] =\n"
               "{\n");
    for(w=0; w<idat->num_waves; w++)
    {
      harm_t *h = &idat->harm[w];
#ifdef TIDY_HARMS
      // Remove unecessary data from the source listing.
      // Search back from the highest harmonic, setting any that are below -60dB to
      // zero until one is found that is greater than -60dB.
      for(k=NUM_HARMS-1; k>=0; k--)
      {
        int re, im, mag;
        re = h->hrm[k].re;
        im = h->hrm[k].im;
        mag = isqrt(re * re + im * im);
        if(mag < 16) // -60dB
          h->hrm[k].re = h->hrm[k].im = 0;
        else
          break;
      }
#else
      k = NUM_HARMS-1;
#endif
      fprintf(f, "/* %2d */{\"%s\",{", w, h->name);
      for(i=0; i<=k; i++)
        fprintf(f, "{%d,%d}%s", h->hrm[i].re, h->hrm[i].im, (i < (NUM_HARMS-1)) ? "," : "");
      fprintf(f, "}}%s", (w < (idat->num_waves-1)) ? ",\n" : "\n};\n\n");
    }
    fprintf(f, "const int num_def_waves = %d;\n", idat->num_waves);

    fclose(f);
    set_filetype(s, TEXT);
  }


  // text patch list
  // ---------------
  if(flags & (1<<TXT_PATCH))
  {
    sprintf(s, "%s/txt", filename);
    if((f = fopen(s, "wb")) == NULL)
      return -PROBLEM_OPENING_FILE;

    fprintf(f, "Patch List\n"
               "---------------\n"
               "%s\n"
               "%s\n", idat->name, date);
    i = -1;

    for(k=0; k < syn.kbd_patch.num; k++)
    {
      patch_t p = syn.kbd_patch.list[k];
      if(p.hi < PERCUSSION_BANK)
      {
        if(i != p.hi)
          fprintf(f, "\nMelodic Instruments\n"
                     " [---Patch---]\n"
                     "   Hi  Lo Prg  Num Name\n"
                     "  --- --- ---  --- ----\n");
        fprintf(f, " [  0,%3d,%3d] %3d %s\n", p.lo, p.prg, k,
                idat->instrument[idat->bank[idat->banks[p.lo]-1].ins[p.prg]-1].name);
      }
      else if(p.hi == PERCUSSION_BANK)
      {
        if(i != p.hi)
          fprintf(f, "\nDrum Kits\n");
        fprintf(f, " [%3d,  0,%3d] %3d %s\n", PERCUSSION_BANK, p.prg, k, idat->bank[idat->kits[p.prg]-1].name);
      }
      else if(p.hi > PERCUSSION_BANK)
      {
        if(i != p.hi)
          fprintf(f, "\nPercussive Instruments,\n"
                     " not directly accessible via MIDI, only as part of a Drum Kit.\n"
                     "   Hi Key Prg  Num Name\n"
                     "  --- --- ---  --- ----\n");
        fprintf(f, " [%3d,%3d,%3d] %3d %-23s %s%d\n", PERCUSSION_INST, p.lo, p.prg, k,
                idat->instrument[idat->bank[idat->kits[p.prg]-1].ins[p.lo]-1].name, note[p.lo % 12], p.lo / 12);
      }
      i = p.hi;
    }

    fclose(f);
    set_filetype(s, TEXT);
  }


  // csv Bank Map
  // ------------
  if(flags & (1<<CSV_MAP))
  {
    sprintf(s, "%s/csv", filename);
    if((f = fopen(s, "wb")) == NULL)
      return -PROBLEM_OPENING_FILE;

    // Title
    fprintf(f, "\"%s\",\"%s\"\n", idat->name, date);

    // Instrument Banks
    fprintf(f, "\n\"Melodic Instruments\"\n\"Bank Hi = 0\"\n\"Bank Lo\"");
    for(b=0; b<MIDI_DATA_RANGE; b++)
      if((k=idat->banks[b]) != 0)
        fprintf(f, ",\"%d: %s\"", b, idat->bank[k-1].name);
    fprintf(f, "\n\"Program\"\n");
    for(i=0; i<MIDI_DATA_RANGE; i++)
    {
      for(b=0; b<MIDI_DATA_RANGE; b++)
        if((k=idat->banks[b]) != 0)
          if(idat->bank[k-1].ins[i] != 0)
            break;
      if(b < MIDI_DATA_RANGE)
      {
        fprintf(f, "\"%d\"", i);
        for(b=0; b<MIDI_DATA_RANGE; b++)
          if((k=idat->banks[b]) != 0)
          {
            fprintf(f, ",");
            if((ins = idat->bank[k-1].ins[i]) != 0)
            {
              if(syn.switches & (1<<CSV_NO_NUMBER))
                fprintf(f, "\"%s\"", idat->instrument[ins-1].name);
              else
                fprintf(f, "\"%d: %s\"", ins-1, idat->instrument[ins-1].name);
            }
          }
        fprintf(f, "\n");
      }
    }

    // Drum Kits
    fprintf(f, "\n\"Drum Kits\"\n\"Bank Hi = 127\"\n\"Bank Lo = 0\"\n\"Program\"");
    for(b=0; b<MIDI_DATA_RANGE; b++)
      if((k=idat->kits[b]) != 0)
        fprintf(f, ",\"%d: %s\"", b, idat->bank[k-1].name);
    fprintf(f, "\n\"Key\"\n");
    for(i=0; i<MIDI_DATA_RANGE; i++)
    {
      for(b=0; b<MIDI_DATA_RANGE; b++)
        if((k=idat->kits[b]) != 0)
          if(idat->bank[k-1].ins[i] != 0)
            break;
      if(b < MIDI_DATA_RANGE)
      {
        fprintf(f, "\"%d: %s%d\"", i, note[i % 12], i / 12);
        for(b=0; b<MIDI_DATA_RANGE; b++)
          if((k=idat->kits[b]) != 0)
          {
            fprintf(f, ",");
            if((ins = idat->bank[k-1].ins[i]) != 0)
            {
              if(syn.switches & (1<<CSV_NO_NUMBER))
                fprintf(f, "\"%s\"", idat->instrument[ins-1].name);
              else
                fprintf(f, "\"%d: %s\"", ins-1, idat->instrument[ins-1].name);
            }
          }
        fprintf(f, "\n");
      }
    }

    fclose(f);
    set_filetype(s, CSV);
  }


  // Instrument list (for use with the editor)
  // -----------------------------------------
  if(flags & (1<<TXT_LST))
  {
    sprintf(s, "%s/lst", filename);
    if((f = fopen(s, "wb")) == NULL)
      return -PROBLEM_OPENING_FILE;

    // Title
    fprintf(f, "Instrument List\n"
               "---------------\n"
               "%s\n"
               "%s\n\n"
               " %4d Instruments\n\n"
               "      name                     references\n"
               "      ------------------------ ----------\n", idat->name, date, idat->num_instruments);

    for(i=0; i<idat->num_instruments; i++)
    {
      int j;
      fprintf(f, " %4d %-24s", i, idat->instrument[i].name);
      if(idat->instrument[i].name[0] != 0)
      {
        // list all the banks/kits that this instrument is referenced in
        for(k=0; k<MIDI_DATA_RANGE; k++)
          if((b = idat->banks[k]) != 0)
            for(j=0; j<MIDI_DATA_RANGE; j++)
              if(idat->bank[b-1].ins[j] == i+1)
                fprintf(f, " b%d:%d", k, j);
        for(k=0; k<MIDI_DATA_RANGE; k++)
          if((b = idat->kits[k]) != 0)
            for(j=0; j<MIDI_DATA_RANGE; j++)
              if(idat->bank[b-1].ins[j] == i+1)
                fprintf(f, " k%d:%d(%s%d)", k, j, note[j % 12], j / 12);
      }
      fprintf(f, "\n");
    }

    fclose(f);
    set_filetype(s, TEXT);
  }

  return NO_ERROR_;
}


/*
 * load_csv
 * --------
 * Loads a CSV bank map file and creates a sound set from it using the
 * existing instrument array. If an error is encountered, nothing is
 * changed and the error is returned. If all is ok, the banks and bank
 * selectors are replaced. The instrument and waveform arrays are not
 * changed. This means that the sound set name and bank names in the
 * file are used but not instrument names, the instrument number is all
 * that is needed. All instruments refered to in the CSV file MUST be
 * defined in the existing sound set. This method of saving, editing, and
 * loading back in of CSV files can only change the bank mapping.
 * The new sound set that is created will need to be saved (if required).
 */
#define LINE_END    1
#define LINE_LEN  256
#define FIELD_LEN  32

// read_field: Reads the next field in the given line and line is updated,
// pointing to the next field and field is updated with the field contents.
// Quotes are removed and a maximum of len characters are copied including
// the terminating null.
static int read_field(char *field, char **line, int len)
{
  while((**line != ',') && (**line >= ' '))
  {
    if(**line != '"')
      if(--len > 0)
        *field++ = **line;
    (*line)++;
  }
  *field = 0;
  if(**line == ',')
    (*line)++;
  else
    return LINE_END;
  return 0;
}

// read_num_name: Reads a number then a name which can contain spaces.
// Returns number of items read. (like sscanf). Only NAME_LEN characters
// including the trailing null are copied into name.
static int read_num_name(char *field, int *num, char *name)
{
  int len = NAME_LEN;

  if((*field < '0') || (*field > '9'))
    return 0;
  *num = atoi(field);
  while((*field != ' ') && (*field != 0)) field++;
    if(*field == 0) return 1;
  while((*field == ' ') && (*field != 0)) field++;
    if(*field == 0) return 1;
  while((*field != ',') && (*field != 0))
  {
    if(--len > 0)
       *name++ = *field;
    field++;
  }
  *name = 0;
  return 2;
}

// load_csv: ----
int load_csv(ins_data_t *idat, char *filename)
{
  static char name[2*MIDI_DATA_RANGE][NAME_LEN];
  static int num[MIDI_DATA_RANGE];
  FILE *f;
  char *s, line[LINE_LEN], field[FIELD_LEN];
  ins_data_t new;
  int prg, i, j = 0, n, b = 0, state, err = 0;

  check_filename(filename);
  if((f = fopen(filename, "r")) == NULL)
    return -PROBLEM_OPENING_FILE;

  // initialise new sound store, copy items that aren't being changed
  memset(&new, 0, sizeof(new));
  new.date = (unsigned int)time(0) - UNIX_TIME_OFFSET; // 1/1/2001 - 1/1/1970
  new.instrument = idat->instrument;
  new.wtbl = idat->wtbl;
  new.harm = idat->harm;
  new.num_instruments = idat->num_instruments;
  new.num_waves = idat->num_waves;
  new.max_instruments = idat->max_instruments;
  new.max_waves = idat->max_waves;

  // create bank array and populate selectors with bank numbers
  state = 0;
  while(fgets(line, LINE_LEN, f) != 0)
  {
    s = line;
    switch(state)
    {
      // sound set name
      case 0:
        read_field(new.name, &s, NAME_LEN);
        state = 1;
        break;

      case 1:
        read_field(field, &s, FIELD_LEN);
        if(strncmp(field, "Melodic", 7) == 0) // skip lines until this one
          state = 2;
        break;

      // count melodic banks
      case 2:
        read_field(field, &s, FIELD_LEN);
        if(strncmp(field, "Bank Lo", 7) != 0) // skip lines until this one
          break;
        j = 0;
        do
        {
          i = read_field(field, &s, FIELD_LEN);
          if(read_num_name(field, &num[j], name[j]) == 2)
          {
            if((num[j] < 0) || (num[j] > 127))
              err = -INVALID_BANK_NUMBER;
            else
              j++;
          }
        }
        while((i != LINE_END) && (j < MIDI_DATA_RANGE));
        b = j;
        state = 3;
        break;

      case 3:
        read_field(field, &s, FIELD_LEN);
        if(strncmp(field, "Drum Kits", 9) == 0) // skip lines until this one
          state = 4;
        break;

      // count percussive banks, create bank array, populate selectors
      case 4:
        read_field(field, &s, FIELD_LEN);
        if(strncmp(field, "Program", 7) != 0) // skip lines until this one
          break;
        do
        {
          i = read_field(field, &s, FIELD_LEN);
          if(read_num_name(field, &num[j], name[j]) == 2)
          {
            if((num[j] < 0) || (num[j] > 127))
              err = -INVALID_KIT_NUMBER;
            else
            j++;
          }
        }
        while((i != LINE_END) && ((j-b) < MIDI_DATA_RANGE));

        if(!err)
        {
          new.num_banks = j;
          new.max_banks = j + 10;
          if((new.bank = calloc(new.max_banks, sizeof(bank_t))) == NULL)
            err = -CANNOT_ALLOCATE_MEMORY;
          else
          {
            for(i=0; i<b; i++)
            {
              strncpy(new.bank[i].name, name[i], NAME_LEN);
              new.banks[num[i]] = i + 1;
            }
            for(; i<j; i++)
            {
              strncpy(new.bank[i].name, name[i], NAME_LEN);
              new.kits[num[i]] = i + 1;
            }
          }
          state = 5;
        }
        break;
    }
    if(err || (state >= 5))
      break;
  }
  if(!err)
  {
    if(state == 1)
      err = -NO_MELODICS_LINE;
    else if(state == 2)
      err = -NO_BANK_LINE;
    else if(state == 3)
      err = -NO_DRUM_LINE;
    else if(state == 4)
      err = -NO_KIT_LINE;
  }

  if(!err && (state == 5))
  {
    // populate banks with instrument numbers
    fseek(f, 0, SEEK_SET);
    while(fgets(line, LINE_LEN, f) != 0)
    {
      s = line;
      switch(state)
      {
        case 5:
          read_field(field, &s, FIELD_LEN);
          if(strncmp(field, "Program", 7) == 0) // skip lines until this one
            state = 6;
          break;

        // read program lines
        case 6:
          read_field(field, &s, FIELD_LEN);
          if(sscanf(field, "%d", &prg) == 1)
          {
            if((prg < 0) || (prg > 127))
              err = -INVALID_PROGRAM_NUMBER;
            else
            {
              j = 0;
              do
              {
                i = read_field(field, &s, FIELD_LEN);
                if(sscanf(field, "%d", &n) == 1)
                {
                  if((n < 0) || (n >= new.num_instruments))
                    err = -INVALID_INSTRUMENT_REF;
                  else
                    new.bank[new.banks[num[j]]-1].ins[prg] = n + 1;
                }
                j++;
              }
              while((i != LINE_END) && (j < b));
            }
          }
          else
            state = 7;
          break;

        case 7:
          read_field(field, &s, FIELD_LEN);
          if(strncmp(field, "Key", 3) == 0) // skip lines until this one
            state = 8;
          break;

        // read key lines
        case 8:
          read_field(field, &s, FIELD_LEN);
          if(sscanf(field, "%d", &prg) == 1)
          {
            if((prg < 0) || (prg > 127))
              err = -INVALID_KEY_NUMBER;
            else
            {
              j = b;
              do
              {
                i = read_field(field, &s, FIELD_LEN);
                if(sscanf(field, "%d", &n) == 1)
                {
                  if((n < 0) || (n >= new.num_instruments))
                    err = -INVALID_INSTRUMENT_REF;
                  else
                  new.bank[new.kits[num[j]]-1].ins[prg] = n + 1;
                }
                j++;
              }
              while((i != LINE_END) && (j < new.num_banks));
            }
          }
          else
            state = 9;
          break;
      }
      if(err || (state >= 9))
        break;
    }
  }

  fclose(f);

  if(!err)
  {
    if(state == 5)
      err = -NO_PROGRAM_LINE;
    if(state == 7)
      err = -NO_KEY_LINE;
  }

  // update sound set
  if(!err)
  {
    if(idat->bank)
      free(idat->bank);
    *idat = new;
    err = update_list(idat);
  }
  else if(new.bank)
    free(new.bank);

  return err;
}

