//
// GOATTRACKER v2 song data model, loading/saving/conversion
//

#define GSONG_C

#include "goattrk2.h"

INSTR instr[MAX_INSTR];
unsigned char ltable[MAX_TABLES][MAX_TABLELEN];
unsigned char rtable[MAX_TABLES][MAX_TABLELEN];
unsigned char songorder[MAX_SONGS][MAX_CHN][MAX_SONGLEN+2];
unsigned char pattern[MAX_PATT][MAX_PATTROWS*4+4];
char songname[MAX_STR];
char authorname[MAX_STR];
char copyrightname[MAX_STR];
int pattlen[MAX_PATT];
int songlen[MAX_SONGS][MAX_CHN];
int highestusedpattern;
int highestusedinstr;

int savesong(void)
{
  int c;
  char ident[] = {'G', 'T', 'S', '5'};
  FILE *handle;

  if (strlen(songfilename) < MAX_FILENAME-4)
  {
    int extfound = 0;
    for (c = strlen(songfilename)-1; c >= 0; c--)
    {
      if (songfilename[c] == '.') extfound = 1;
    }
    if (!extfound) strcat(songfilename, ".sng");
  }
  handle = fopen(songfilename, "wb");
  if (handle)
  {
    int d;
    int length;
    int amount;
    int writebytes;
    fwrite(ident, 4, 1, handle);

    // Determine amount of patterns & instruments
    countpatternlengths();
    for (c = 1; c < MAX_INSTR; c++)
    {
      if ((instr[c].ad) || (instr[c].sr) || (instr[c].ptr[0]) || (instr[c].ptr[1]) ||
          (instr[c].ptr[2]) || (instr[c].vibdelay) || (instr[c].ptr[3]))
      {
        if (c > highestusedinstr) highestusedinstr = c;
      }
    }

    // Write infotexts
    fwrite(songname, sizeof songname, 1, handle);
    fwrite(authorname, sizeof authorname, 1, handle);
    fwrite(copyrightname, sizeof copyrightname, 1, handle);

    // Determine amount of songs to be saved
    c = MAX_SONGS - 1;
    for (;;)
    {
      if ((songlen[c][0])&&
         (songlen[c][1])&&
         (songlen[c][2])) break;
      if (c == 0) break;
      c--;
    }
    amount = c + 1;

    fwrite8(handle, amount);
    // Write songorderlists
    for (d = 0; d < amount; d++)
    {
      for (c = 0; c < MAX_CHN; c++)
      {
        length = songlen[d][c]+1;
        fwrite8(handle, length);
        writebytes = length;
        writebytes++;
        fwrite(songorder[d][c], writebytes, 1, handle);
      }
    }
    // Write amount of instruments
    fwrite8(handle, highestusedinstr);
    // Write instruments
    for (c = 1; c <= highestusedinstr; c++)
    {
      fwrite8(handle, instr[c].ad);
      fwrite8(handle, instr[c].sr);
      fwrite8(handle, instr[c].ptr[WTBL]);
      fwrite8(handle, instr[c].ptr[PTBL]);
      fwrite8(handle, instr[c].ptr[FTBL]);
      fwrite8(handle, instr[c].ptr[STBL]);
      fwrite8(handle, instr[c].vibdelay);
      fwrite8(handle, instr[c].gatetimer);
      fwrite8(handle, instr[c].firstwave);
      fwrite(&instr[c].name, MAX_INSTRNAMELEN, 1, handle);
    }
    // Write tables
    for (c = 0; c < MAX_TABLES; c++)
    {
      writebytes = gettablelen(c);
      fwrite8(handle, writebytes);
      fwrite(ltable[c], writebytes, 1, handle);
      fwrite(rtable[c], writebytes, 1, handle);
    }
    // Write patterns
    amount = highestusedpattern + 1;
    fwrite8(handle, amount);
    for (c = 0; c < amount; c++)
    {
      length = pattlen[c]+1;
      fwrite8(handle, length);
      fwrite(pattern[c], length * 4, 1, handle);
    }
    fclose(handle);
    strcpy(loadedsongfilename, songfilename);
    return 1;
  }
  return 0;
}

int saveinstrument(void)
{
  int c;
  char ident[] = {'G', 'T', 'I', '5'};
  FILE *handle;

  if (strlen(instrfilename) < MAX_FILENAME-4)
  {
    int extfound = 0;
    for (c = strlen(instrfilename)-1; c >= 0; c--)
    {
      if (instrfilename[c] == '.') extfound = 1;
    }
    if (!extfound) strcat(instrfilename, ".ins");
  }

  handle = fopen(instrfilename, "wb");
  if (handle)
  {
    fwrite(ident, 4, 1, handle);

    // Write instrument
    fwrite8(handle, instr[einum].ad);
    fwrite8(handle, instr[einum].sr);
    fwrite8(handle, instr[einum].ptr[WTBL]);
    fwrite8(handle, instr[einum].ptr[PTBL]);
    fwrite8(handle, instr[einum].ptr[FTBL]);
    fwrite8(handle, instr[einum].ptr[STBL]);
    fwrite8(handle, instr[einum].vibdelay);
    fwrite8(handle, instr[einum].gatetimer);
    fwrite8(handle, instr[einum].firstwave);
    fwrite(&instr[einum].name, MAX_INSTRNAMELEN, 1, handle);
    for (c = 0; c < MAX_TABLES; c++)
    {
      if (instr[einum].ptr[c])
      {
        int pos = instr[einum].ptr[c] - 1;
        int len = gettablepartlen(c, pos);
        fwrite8(handle, len);
        fwrite(&ltable[c][pos], len, 1, handle);
        fwrite(&rtable[c][pos], len, 1, handle);
      }
      else fwrite8(handle, 0);
    }
    fclose(handle);
    return 1;
  }
  return 0;
}

void loadsong(void)
{
  int c;
  int ok = 0;
  char ident[4];
  FILE *handle;

  handle = fopen(songfilename, "rb");

  if (handle)
  {
    fread(ident, 4, 1, handle);
    if ((!memcmp(ident, "GTS3", 4)) || (!memcmp(ident, "GTS4", 4)) || (!memcmp(ident, "GTS5", 4)))
    {
      int d;
      int length;
      int amount;
      int loadsize;
      clearsong(1,1,1,1,1);
      ok = 1;

      // Read infotexts
      fread(songname, sizeof songname, 1, handle);
      fread(authorname, sizeof authorname, 1, handle);
      fread(copyrightname, sizeof copyrightname, 1, handle);

      // Read songorderlists
      amount = fread8(handle);
      for (d = 0; d < amount; d++)
      {
        for (c = 0; c < MAX_CHN; c++)
        {
          length = fread8(handle);
          loadsize = length;
          loadsize++;
          fread(songorder[d][c], loadsize, 1, handle);
        }
      }
      // Read instruments
      amount = fread8(handle);
      for (c = 1; c <= amount; c++)
      {
        instr[c].ad = fread8(handle);
        instr[c].sr = fread8(handle);
        instr[c].ptr[WTBL] = fread8(handle);
        instr[c].ptr[PTBL] = fread8(handle);
        instr[c].ptr[FTBL] = fread8(handle);
        instr[c].ptr[STBL] = fread8(handle);
        instr[c].vibdelay = fread8(handle);
        instr[c].gatetimer = fread8(handle);
        instr[c].firstwave = fread8(handle);
        fread(&instr[c].name, MAX_INSTRNAMELEN, 1, handle);
      }
      // Read tables
      for (c = 0; c < MAX_TABLES; c++)
      {
        loadsize = fread8(handle);
        fread(ltable[c], loadsize, 1, handle);
        fread(rtable[c], loadsize, 1, handle);
      }
      // Read patterns
      amount = fread8(handle);
      for (c = 0; c < amount; c++)
      {
        length = fread8(handle) * 4;
        fread(pattern[c], length, 1, handle);
      }
      countpatternlengths();
      songchange();
    }

    // Goattracker v2.xx (3-table) import
    if (!memcmp(ident, "GTS2", 4))
    {
      int d;
      int length;
      int amount;
      int loadsize;
      clearsong(1,1,1,1,1);
      ok = 1;

      // Read infotexts
      fread(songname, sizeof songname, 1, handle);
      fread(authorname, sizeof authorname, 1, handle);
      fread(copyrightname, sizeof copyrightname, 1, handle);

      // Read songorderlists
      amount = fread8(handle);
      for (d = 0; d < amount; d++)
      {
        for (c = 0; c < MAX_CHN; c++)
        {
          length = fread8(handle);
          loadsize = length;
          loadsize++;
          fread(songorder[d][c], loadsize, 1, handle);
        }
      }
      // Read instruments
      amount = fread8(handle);
      for (c = 1; c <= amount; c++)
      {
        instr[c].ad = fread8(handle);
        instr[c].sr = fread8(handle);
        instr[c].ptr[WTBL] = fread8(handle);
        instr[c].ptr[PTBL] = fread8(handle);
        instr[c].ptr[FTBL] = fread8(handle);
        instr[c].vibdelay = fread8(handle);
        instr[c].ptr[STBL] = makespeedtable(fread8(handle), finevibrato, 0) + 1;
        instr[c].gatetimer = fread8(handle);
        instr[c].firstwave = fread8(handle);
        fread(&instr[c].name, MAX_INSTRNAMELEN, 1, handle);
      }
      // Read tables
      for (c = 0; c < MAX_TABLES-1; c++)
      {
        loadsize = fread8(handle);
        fread(ltable[c], loadsize, 1, handle);
        fread(rtable[c], loadsize, 1, handle);
      }
      // Read patterns
      amount = fread8(handle);
      for (c = 0; c < amount; c++)
      {
        int d;
        length = fread8(handle) * 4;
        fread(pattern[c], length, 1, handle);

        // Convert speedtable-requiring commands
        for (d = 0; d < length; d++)
        {
          switch (pattern[c][d*4+2])
          {
            case CMD_FUNKTEMPO:
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], MST_FUNKTEMPO, 0) + 1;
            break;

            case CMD_PORTAUP:
            case CMD_PORTADOWN:
            case CMD_TONEPORTA:
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], MST_PORTAMENTO, 0) + 1;
            break;

            case CMD_VIBRATO:
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], finevibrato, 0) + 1;
            break;
          }
        }
      }
      countpatternlengths();
      songchange();
    }
    // Goattracker 1.xx import
    if (!memcmp(ident, "GTS!", 4))
    {
      int d;
      int length;
      int amount;
      int loadsize;
      int fw = 0;
      int fp = 0;
      int ff = 0;
      int fi = 0;
      int numfilter = 0;
      unsigned char filtertable[256];
      unsigned char filtermap[64];
      int arpmap[32][256];
      unsigned char pulse[32], pulseadd[32], pulselimitlow[32], pulselimithigh[32];
      int filterjumppos[64];

      clearsong(1,1,1,1,1);
      ok = 1;

      // Read infotexts
      fread(songname, sizeof songname, 1, handle);
      fread(authorname, sizeof authorname, 1, handle);
      fread(copyrightname, sizeof copyrightname, 1, handle);

      // Read songorderlists
      amount = fread8(handle);
      for (d = 0; d < amount; d++)
      {
        for (c = 0; c < MAX_CHN; c++)
        {
          length = fread8(handle);
          loadsize = length;
          loadsize++;
          fread(songorder[d][c], loadsize, 1, handle);
        }
      }

      // Convert instruments
      for (c = 1; c < 32; c++)
      {
        unsigned char wavelen;

        instr[c].ad = fread8(handle);
        instr[c].sr = fread8(handle);
        pulse[c] = fread8(handle);
        pulseadd[c] = fread8(handle);
        pulselimitlow[c] = fread8(handle);
        pulselimithigh[c] = fread8(handle);
        instr[c].ptr[FTBL] = fread8(handle); // Will be converted later
        if (instr[c].ptr[FTBL] > numfilter) numfilter = instr[c].ptr[FTBL];
        if (pulse[c] & 1) instr[c].gatetimer |= 0x80; // "No hardrestart" flag
        pulse[c] &= 0xfe;
        wavelen = fread8(handle)/2;
        fread(&instr[c].name, MAX_INSTRNAMELEN, 1, handle);
        instr[c].ptr[WTBL] = fw+1;

        // Convert wavetable
        for (d = 0; d < wavelen; d++)
        {
          if (fw < MAX_TABLELEN)
          {
            ltable[WTBL][fw] = fread8(handle);
            rtable[WTBL][fw] = fread8(handle);
            if (ltable[WTBL][fw] == 0xff)
              if (rtable[WTBL][fw]) rtable[WTBL][fw] += instr[c].ptr[WTBL]-1;
            if ((ltable[WTBL][fw] >= 0x8) && (ltable[WTBL][fw] <= 0xf))
              ltable[WTBL][fw] |= 0xe0;
            fw++;
          }
          else
          {
            fread8(handle);
            fread8(handle);
          }
        }

        // Remove empty wavetable afterwards
        if ((wavelen == 2) && (!ltable[WTBL][fw-2]) && (!rtable[WTBL][fw-2]))
        {
          instr[c].ptr[WTBL] = 0;
          fw -= 2;
          ltable[WTBL][fw] = 0;
          rtable[WTBL][fw] = 0;
          ltable[WTBL][fw+1] = 0;
          rtable[WTBL][fw+1] = 0;
        }

        // Convert pulsetable
        if (pulse[c])
        {
          int pulsetime, pulsedist, hlpos;

          // Check for duplicate pulse settings
          for (d = 1; d < c; d++)
          {
            if ((pulse[d] == pulse[c]) && (pulseadd[d] == pulseadd[c]) && (pulselimitlow[d] == pulselimitlow[c]) &&
                (pulselimithigh[d] == pulselimithigh[c]))
            {
              instr[c].ptr[PTBL] = instr[d].ptr[PTBL];
              goto PULSEDONE;
            }
          }

          // Initial pulse setting
          if (fp >= MAX_TABLELEN) goto PULSEDONE;
          instr[c].ptr[PTBL] = fp+1;
          ltable[PTBL][fp] = 0x80 | (pulse[c] >> 4);
          rtable[PTBL][fp] = pulse[c] << 4;
          fp++;

          // Pulse modulation
          if (pulseadd[c])
          {
            int startpulse = pulse[c]*16;
            int currentpulse = pulse[c]*16;
            // Phase 1: From startpos to high limit
            pulsedist = pulselimithigh[c]*16 - currentpulse;
            if (pulsedist > 0)
            {
              pulsetime = pulsedist/pulseadd[c];
              currentpulse += pulsetime*pulseadd[c];
              while (pulsetime)
              {
                int acttime = pulsetime;
                if (acttime > 127) acttime = 127;
                if (fp >= MAX_TABLELEN) goto PULSEDONE;
                ltable[PTBL][fp] = acttime;
                rtable[PTBL][fp] = pulseadd[c] / 2;
                fp++;
                pulsetime -= acttime;
              }
            }

            hlpos = fp;
            // Phase 2: from high limit to low limit
            pulsedist = currentpulse - pulselimitlow[c]*16;
            if (pulsedist > 0)
            {
              pulsetime = pulsedist/pulseadd[c];
              currentpulse -= pulsetime*pulseadd[c];
              while (pulsetime)
              {
                int acttime = pulsetime;
                if (acttime > 127) acttime = 127;
                if (fp >= MAX_TABLELEN) goto PULSEDONE;
                ltable[PTBL][fp] = acttime;
                rtable[PTBL][fp] = -(pulseadd[c] / 2);
                fp++;
                pulsetime -= acttime;
              }
            }

            // Phase 3: from low limit back to startpos/high limit
            if ((startpulse < pulselimithigh[c]*16) && (startpulse > currentpulse))
            {
              pulsedist = startpulse - currentpulse;
              if (pulsedist > 0)
              {
                pulsetime = pulsedist/pulseadd[c];
                while (pulsetime)
                {
                  int acttime = pulsetime;
                  if (acttime > 127) acttime = 127;
                  if (fp >= MAX_TABLELEN) goto PULSEDONE;
                  ltable[PTBL][fp] = acttime;
                  rtable[PTBL][fp] = pulseadd[c] / 2;
                  fp++;
                  pulsetime -= acttime;
                }
              }
              // Pulse jump back to beginning
              if (fp >= MAX_TABLELEN) goto PULSEDONE;
              ltable[PTBL][fp] = 0xff;
              rtable[PTBL][fp] = instr[c].ptr[PTBL] + 1;
              fp++;
            }
            else
            {
              pulsedist = pulselimithigh[c]*16 - currentpulse;
              if (pulsedist > 0)
              {
                pulsetime = pulsedist/pulseadd[c];
                while (pulsetime)
                {
                  int acttime = pulsetime;
                  if (acttime > 127) acttime = 127;
                  if (fp >= MAX_TABLELEN) goto PULSEDONE;
                  ltable[PTBL][fp] = acttime;
                  rtable[PTBL][fp] = pulseadd[c] / 2;
                  fp++;
                  pulsetime -= acttime;
                }
              }
              // Pulse jump back to beginning
              if (fp >= MAX_TABLELEN) goto PULSEDONE;
              ltable[PTBL][fp] = 0xff;
              rtable[PTBL][fp] = hlpos + 1;
              fp++;
            }
          }
          else
          {
            // Pulse stopped
            if (fp >= MAX_TABLELEN) goto PULSEDONE;
            ltable[PTBL][fp] = 0xff;
            rtable[PTBL][fp] = 0;
            fp++;
          }
          PULSEDONE: {}
        }
      }
      // Convert patterns
      amount = fread8(handle);
      for (c = 0; c < amount; c++)
      {
        length = fread8(handle);
        for (d = 0; d < length/3; d++)
        {
          unsigned char note, cmd, data, instr;
          note = fread8(handle);
          cmd = fread8(handle);
          data = fread8(handle);
          instr = cmd >> 3;
          cmd &= 7;

          switch(note)
          {
            default:
            note += FIRSTNOTE;
            if (note > LASTNOTE) note = REST;
            break;

            case OLDKEYOFF:
            note = KEYOFF;
            break;

            case OLDREST:
            note = REST;
            break;

            case ENDPATT:
            break;
          }
          switch(cmd)
          {
            case 5:
            cmd = CMD_SETFILTERPTR;
            if (data > numfilter) numfilter = data;
            break;

            case 7:
            if (data < 0xf0)
              cmd = CMD_SETTEMPO;
            else
            {
              cmd = CMD_SETMASTERVOL;
              data &= 0x0f;
            }
            break;
          }
          pattern[c][d*4] = note;
          pattern[c][d*4+1] = instr;
          pattern[c][d*4+2] = cmd;
          pattern[c][d*4+3] = data;
        }
      }
      countpatternlengths();
      fi = highestusedinstr + 1;
      songchange();

      // Read filtertable
      fread(filtertable, 256, 1, handle);

      // Convert filtertable
      for (c = 0; c < 64; c++)
      {
        filterjumppos[c] = -1;
        filtermap[c] = 0;
        if (filtertable[c*4+3] > numfilter) numfilter = filtertable[c*4+3];
      }

      if (numfilter > 63) numfilter = 63;

      for (c = 1; c <= numfilter; c++)
      {
        filtermap[c] = ff+1;

        if (filtertable[c*4]|filtertable[c*4+1]|filtertable[c*4+2]|filtertable[c*4+3])
        {
          // Filter set
          if (filtertable[c*4])
          {
            ltable[FTBL][ff] = 0x80 + (filtertable[c*4+1] & 0x70);
            rtable[FTBL][ff] = filtertable[c*4];
            ff++;
            if (filtertable[c*4+2])
            {
              ltable[FTBL][ff] = 0x00;
              rtable[FTBL][ff] = filtertable[c*4+2];
              ff++;
            }
          }
          else
          {
            // Filter modulation
            int time = filtertable[c*4+1];

            while (time)
            {
              int acttime = time;
              if (acttime > 127) acttime = 127;
              ltable[FTBL][ff] = acttime;
              rtable[FTBL][ff] = filtertable[c*4+2];
              ff++;
              time -= acttime;
            }
          }

          // Jump to next step: unnecessary if follows directly
          if (filtertable[c*4+3] != c+1)
          {
            filterjumppos[c] = ff;
            ltable[FTBL][ff] = 0xff;
            rtable[FTBL][ff] = filtertable[c*4+3]; // Fix the jump later
            ff++;
          }
        }
      }

      // Now fix jumps as the filterstep mapping is known
      for (c = 1; c <= numfilter; c++)
      {
        if (filterjumppos[c] != -1)
          rtable[FTBL][filterjumppos[c]] = filtermap[rtable[FTBL][filterjumppos[c]]];
      }

      // Fix filterpointers in instruments
      for (c = 1; c < 32; c++)
        instr[c].ptr[FTBL] = filtermap[instr[c].ptr[FTBL]];

      // Now fix pattern commands
      memset(arpmap, 0, sizeof arpmap);
      for (c = 0; c < MAX_PATT; c++)
      {
        unsigned char i = 0;
        for (d = 0; d <= MAX_PATTROWS; d++)
        {
          if (pattern[c][d*4+1]) i = pattern[c][d*4+1];

          // Convert portamento & vibrato
          if (pattern[c][d*4+2] == CMD_PORTAUP)
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], MST_PORTAMENTO, 0) + 1;
          if (pattern[c][d*4+2] == CMD_PORTADOWN)
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], MST_PORTAMENTO, 0) + 1;
          if (pattern[c][d*4+2] == CMD_TONEPORTA)
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], MST_PORTAMENTO, 0) + 1;
          if (pattern[c][d*4+2] == CMD_VIBRATO)
            pattern[c][d*4+3] = makespeedtable(pattern[c][d*4+3], MST_NOFINEVIB, 0) + 1;

          // Convert filterjump
          if (pattern[c][d*4+2] == CMD_SETFILTERPTR)
            pattern[c][d*4+3] = filtermap[pattern[c][d*4+3]];

          // Convert funktempo
          if ((pattern[c][d*4+2] == CMD_SETTEMPO) && (!pattern[c][d*4+3]))
          {
            pattern[c][d*4+2] = CMD_FUNKTEMPO;
            pattern[c][d*4+3] = makespeedtable((filtertable[2] << 4) | (filtertable[3] & 0x0f), MST_FUNKTEMPO, 0) + 1;
          }
          // Convert arpeggio
          if ((pattern[c][d*4+2] == CMD_DONOTHING) && (pattern[c][d*4+3]))
          {
            // Must be in conjunction with a note
            if ((pattern[c][d*4] >= FIRSTNOTE) && (pattern[c][d*4] <= LASTNOTE))
            {
              unsigned char param = pattern[c][d*4+3];
              if (i)
              {
                // Old arpeggio
                if (arpmap[i][param])
                {
                  // As command, or as instrument?
                  if (arpmap[i][param] < 256)
                  {
                    pattern[c][d*4+2] = CMD_SETWAVEPTR;
                    pattern[c][d*4+3] = arpmap[i][param];
                  }
                  else
                  {
                    pattern[c][d*4+1] = arpmap[i][param] - 256;
                    pattern[c][d*4+3] = 0;
                  }
                }
                else
                {
                  int e;
                  unsigned char arpstart;
                  unsigned char arploop;

                  // New arpeggio
                  // Copy first the instrument's wavetable up to loop/end point
                  arpstart = fw + 1;
                  if (instr[i].ptr[WTBL])
                  {
                    for (e = instr[i].ptr[WTBL]-1;; e++)
                    {
                      if (ltable[WTBL][e] == 0xff) break;
                      if (fw < MAX_TABLELEN)
                      {
                        ltable[WTBL][fw] = ltable[WTBL][e];
                        fw++;
                      }
                    }
                  }
                  // Then make the arpeggio
                  arploop = fw + 1;
                  if (fw < MAX_TABLELEN-3)
                  {
                    ltable[WTBL][fw] = (param & 0x80) >> 7;
                    rtable[WTBL][fw] = (param  & 0x70) >> 4;
                    fw++;
                    ltable[WTBL][fw] = (param & 0x80) >> 7;
                    rtable[WTBL][fw] = (param & 0xf);
                    fw++;
                    ltable[WTBL][fw] = (param & 0x80) >> 7;
                    rtable[WTBL][fw] = 0;
                    fw++;
                    ltable[WTBL][fw] = 0xff;
                    rtable[WTBL][fw] = arploop;
                    fw++;

                    // Create new instrument if possible
                    if (fi < MAX_INSTR)
                    {
                      arpmap[i][param] = fi + 256;
                      instr[fi] = instr[i];
                      instr[fi].ptr[WTBL] = arpstart;
                      // Add arpeggio parameter to new instrument name
                      if (strlen(instr[fi].name) < MAX_INSTRNAMELEN-3)
                      {
                        char arpname[8];
                        sprintf(arpname, "0%02X", param&0x7f);
                        strcat(instr[fi].name, arpname);
                      }
                      fi++;
                    }
                    else
                    {
                      arpmap[i][param] = arpstart;
                    }
                  }

                  if (arpmap[i][param])
                  {
                    // As command, or as instrument?
                    if (arpmap[i][param] < 256)
                    {
                      pattern[c][d*4+2] = CMD_SETWAVEPTR;
                      pattern[c][d*4+3] = arpmap[i][param];
                    }
                    else
                    {
                      pattern[c][d*4+1] = arpmap[i][param] - 256;
                      pattern[c][d*4+3] = 0;
                    }
                  }
                }
              }
            }
            // If arpeggio could not be converted, databyte zero
            if (!pattern[c][d*4+2])
              pattern[c][d*4+3] = 0;
          }
        }
      }
    }
    fclose(handle);
  }
  if (ok)
  {
    strcpy(loadedsongfilename, songfilename);

    // Reset table views
    for (c = 0; c < MAX_TABLES; c++) settableview(c, 0);

    // Convert pulsemodulation speed of < v2.4 songs
    if (ident[3] < '4')
    {
      for (c = 0; c < MAX_TABLELEN; c++)
      {
        if ((ltable[PTBL][c] < 0x80) && (rtable[PTBL][c]))
        {
          int speed = ((signed char)rtable[PTBL][c]);
          speed <<= 1;
          if (speed > 127) speed = 127;
          if (speed < -128) speed = -128;
          rtable[PTBL][c] = speed;
        }
      }
    }

    // Convert old legato/nohr parameters
    if (ident[3] < '5')
    {
        for (c = 1; c < MAX_INSTR; c++)
        {
            if (instr[c].firstwave >= 0x80)
            {
                instr[c].gatetimer |= 0x80;
                instr[c].firstwave &= 0x7f;
            }
            if (!instr[c].firstwave) instr[c].gatetimer |= 0x40;
        }
    }
  }
}

void loadinstrument(void)
{
  char ident[4];
  FILE *handle;
  int c,d;
  int pulsestart = -1;
  int pulseend = -1;

  handle = fopen(instrfilename, "rb");
  if (handle)
  {
    stopsong();
    fread(ident, 4, 1, handle);

    if ((!memcmp(ident, "GTI3", 4)) || (!memcmp(ident, "GTI4", 4)) || (!memcmp(ident, "GTI5", 4)))
    {
      unsigned char optr[4];

      instr[einum].ad = fread8(handle);
      instr[einum].sr = fread8(handle);
      optr[0] = fread8(handle);
      optr[1] = fread8(handle);
      optr[2] = fread8(handle);
      optr[3] = fread8(handle);
      instr[einum].vibdelay = fread8(handle);
      instr[einum].gatetimer = fread8(handle);
      instr[einum].firstwave = fread8(handle);
      fread(&instr[einum].name, MAX_INSTRNAMELEN, 1, handle);

      // Erase old tabledata
      deleteinstrtable(einum);

      // Load new tabledata
      for (c = 0; c < MAX_TABLES; c++)
      {
        int start = gettablelen(c);
        int len = fread8(handle);

        if (len)
        {
          for (d = start; (d < start+len) && (d < MAX_TABLELEN); d++)
            ltable[c][d] = fread8(handle);
          while (d < start+len)
          {
            fread8(handle);
            d++;
          }
          for (d = start; (d < start+len) && (d < MAX_TABLELEN); d++)
            rtable[c][d] = fread8(handle);
          while (d < start+len)
          {
            fread8(handle);
            d++;
          }
          if (c != STBL)
          {
            for (d = start; (d < start+len) && (d < MAX_TABLELEN); d++)
            {
              if (ltable[c][d] == 0xff)
              {
                if (rtable[c][d])
                  rtable[c][d] = rtable[c][d] - optr[c] + start + 1;
              }
            }
          }
          if (c == PTBL)
          {
            pulsestart = start;
            pulseend = start + len;
          }
          instr[einum].ptr[c] = start + 1;
        }
        else instr[einum].ptr[c] = 0;
      }
    }

    // Goattracker v2.xx (3-table) import
    if (!memcmp(ident, "GTI2", 4))
    {
      unsigned char optr[3];

      instr[einum].ad = fread8(handle);
      instr[einum].sr = fread8(handle);
      optr[0] = fread8(handle);
      optr[1] = fread8(handle);
      optr[2] = fread8(handle);
      instr[einum].vibdelay = fread8(handle);
      instr[einum].ptr[STBL] = makespeedtable(fread8(handle), finevibrato, 0) + 1;
      instr[einum].gatetimer = fread8(handle);
      instr[einum].firstwave = fread8(handle);
      fread(&instr[einum].name, MAX_INSTRNAMELEN, 1, handle);

      // Erase old tabledata
      deleteinstrtable(einum);

      // Load new tabledata
      for (c = 0; c < MAX_TABLES-1; c++)
      {
        int start = gettablelen(c);
        int len = fread8(handle);

        if (len)
        {
          for (d = start; (d < start+len) && (d < MAX_TABLELEN); d++)
            ltable[c][d] = fread8(handle);
          while (d < start+len)
          {
            fread8(handle);
            d++;
          }
          for (d = start; (d < start+len) && (d < MAX_TABLELEN); d++)
            rtable[c][d] = fread8(handle);
          while (d < start+len)
          {
            fread8(handle);
            d++;
          }
          for (d = start; (d < start+len) && (d < MAX_TABLELEN); d++)
          {
            if (ltable[c][d] == 0xff)
            {
              if (rtable[c][d])
                rtable[c][d] = rtable[c][d] - optr[c] + start + 1;
            }
          }
          if (c == PTBL)
          {
            pulsestart = start;
            pulseend = start + len;
          }
          instr[einum].ptr[c] = start + 1;
        }
        else instr[einum].ptr[c] = 0;
      }
    }
    // Goattracker 1.xx import
    if (!memcmp(ident, "GTI!", 4))
    {

      unsigned char pulse, pulseadd, pulselimitlow, pulselimithigh, wavelen;
      unsigned char filtertemp[4];
      int fw, fp, ff;

      // Erase old tabledata
      deleteinstrtable(einum);

      fw = gettablelen(WTBL);
      fp = gettablelen(PTBL);
      ff = gettablelen(FTBL);

      instr[einum].ad = fread8(handle);
      instr[einum].sr = fread8(handle);
      if (multiplier)
        instr[einum].gatetimer = 2 * multiplier;
      else
        instr[einum].gatetimer = 1;
      instr[einum].firstwave = 0x9;
      pulse = fread8(handle);
      pulseadd = fread8(handle);
      pulselimitlow = fread8(handle);
      pulselimithigh = fread8(handle);
      instr[einum].ptr[FTBL] = fread8(handle) ? ff+1 : 0;
      if (pulse & 1) instr[einum].gatetimer |= 0x80; // "No hardrestart" flag
        wavelen = fread8(handle)/2;
      fread(&instr[einum].name, MAX_INSTRNAMELEN, 1, handle);
      instr[einum].ptr[WTBL] = fw+1;

      // Convert wavetable
      for (d = 0; d < wavelen; d++)
      {
        if (fw < MAX_TABLELEN)
        {
          ltable[WTBL][fw] = fread8(handle);
          rtable[WTBL][fw] = fread8(handle);
          if (ltable[WTBL][fw] == 0xff)
            if (rtable[WTBL][fw]) rtable[WTBL][fw] += instr[einum].ptr[WTBL]-1;
          fw++;
        }
        else
        {
          fread8(handle);
          fread8(handle);
        }
      }

      // Remove empty wavetable afterwards
      if ((wavelen == 2) && (!ltable[WTBL][fw-2]) && (!rtable[WTBL][fw-2]))
      {
        instr[einum].ptr[WTBL] = 0;
        fw -= 2;
        ltable[WTBL][fw] = 0;
        rtable[WTBL][fw] = 0;
        ltable[WTBL][fw+1] = 0;
        rtable[WTBL][fw+1] = 0;
      }

      // Convert pulsetable
      pulse &= 0xfe;
      if (pulse)
      {
        int pulsetime, pulsedist, hlpos;

        // Initial pulse setting
        if (fp >= MAX_TABLELEN) goto PULSEDONE;
        pulsestart = fp;
        instr[einum].ptr[PTBL] = fp+1;
        ltable[PTBL][fp] = 0x80 | (pulse >> 4);
        rtable[PTBL][fp] = pulse << 4;
        fp++;

        // Pulse modulation
        if (pulseadd)
        {
          int startpulse = pulse*16;
          int currentpulse = pulse*16;
          // Phase 1: From startpos to high limit
          pulsedist = pulselimithigh*16 - currentpulse;
          if (pulsedist > 0)
          {
            pulsetime = pulsedist/pulseadd;
            currentpulse += pulsetime*pulseadd;
            while (pulsetime)
            {
              int acttime = pulsetime;
              if (acttime > 127) acttime = 127;
              if (fp >= MAX_TABLELEN) goto PULSEDONE;
              ltable[PTBL][fp] = acttime;
              rtable[PTBL][fp] = pulseadd / 2;
              fp++;
              pulsetime -= acttime;
            }
          }

          hlpos = fp;
          // Phase 2: from high limit to low limit
          pulsedist = currentpulse - pulselimitlow*16;
          if (pulsedist > 0)
          {
            pulsetime = pulsedist/pulseadd;
            currentpulse -= pulsetime*pulseadd;
            while (pulsetime)
            {
              int acttime = pulsetime;
              if (acttime > 127) acttime = 127;
              if (fp >= MAX_TABLELEN) goto PULSEDONE;
              ltable[PTBL][fp] = acttime;
              rtable[PTBL][fp] = -(pulseadd / 2);
              fp++;
              pulsetime -= acttime;
            }
          }

          // Phase 3: from low limit back to startpos/high limit
          if ((startpulse < pulselimithigh*16) && (startpulse > currentpulse))
          {
            pulsedist = startpulse - currentpulse;
            if (pulsedist > 0)
            {
              pulsetime = pulsedist/pulseadd;
              while (pulsetime)
              {
                int acttime = pulsetime;
                if (acttime > 127) acttime = 127;
                if (fp >= MAX_TABLELEN) goto PULSEDONE;
                ltable[PTBL][fp] = acttime;
                rtable[PTBL][fp] = pulseadd / 2;
                fp++;
                pulsetime -= acttime;
              }
            }
            // Pulse jump back to beginning
            if (fp >= MAX_TABLELEN) goto PULSEDONE;
            ltable[PTBL][fp] = 0xff;
            rtable[PTBL][fp] = instr[einum].ptr[PTBL] + 1;
            fp++;
          }
          else
          {
            pulsedist = pulselimithigh*16 - currentpulse;
            if (pulsedist > 0)
            {
              pulsetime = pulsedist/pulseadd;
              while (pulsetime)
              {
                int acttime = pulsetime;
                if (acttime > 127) acttime = 127;
                if (fp >= MAX_TABLELEN) goto PULSEDONE;
                ltable[PTBL][fp] = acttime;
                rtable[PTBL][fp] = pulseadd / 2;
                fp++;
                pulsetime -= acttime;
              }
            }
            // Pulse jump back to beginning
            if (fp >= MAX_TABLELEN) goto PULSEDONE;
            ltable[PTBL][fp] = 0xff;
            rtable[PTBL][fp] = hlpos + 1;
            fp++;
          }
        }
        else
        {
          // Pulse stopped
          if (fp >= MAX_TABLELEN) goto PULSEDONE;
          ltable[PTBL][fp] = 0xff;
          rtable[PTBL][fp] = 0;
          fp++;
        }
        PULSEDONE:
        pulseend = fp;
      }

      // Convert filter (if any)
      if ((instr[einum].ptr[FTBL]) && (ff < MAX_TABLELEN-2))
      {
        fread(filtertemp, sizeof filtertemp, 1, handle);
        // Filter set
        if (filtertemp[0])
        {
          ltable[FTBL][ff] = 0x80 + (filtertemp[1] & 0x70);
          rtable[FTBL][ff] = filtertemp[0];
          ff++;
          if (filtertemp[2])
          {
            ltable[FTBL][ff] = 0x00;
            rtable[FTBL][ff] = filtertemp[2];
            ff++;
          }
        }
        else
        {
          // Filter modulation
          int time = filtertemp[1];

          while (time)
          {
            int acttime = time;
            if (acttime > 127) acttime = 127;
            ltable[FTBL][ff] = acttime;
            rtable[FTBL][ff] = filtertemp[2];
            ff++;
            time -= acttime;
          }
        }

        // Jump to next step: always end the filter
        ltable[FTBL][ff] = 0xff;
        rtable[FTBL][ff] = 0;
        ff++;
      }
    }
    
    fclose(handle);

    // Convert pulsemodulation speed of < v2.4 instruments
    if ((ident[3] < '4') && (pulsestart != -1))
    {
      for (c = pulsestart; (c < pulseend) && (c < MAX_TABLELEN); c++)
      {
        if ((ltable[PTBL][c] < 0x80) && (rtable[PTBL][c]))
        {
          int speed = ((signed char)rtable[PTBL][c]);
          speed <<= 1;
          if (speed > 127) speed = 127;
          if (speed < -128) speed = -128;
          rtable[PTBL][c] = speed;
        }
      }
    }
    // Convert old legato/nohr parameters
    if (ident[3] < '5')
    {
      if (instr[einum].firstwave >= 0x80)
      {
        instr[einum].firstwave &= 0x7f;
        instr[einum].gatetimer |= 0x80;
      }
      if (!instr[einum].firstwave) instr[einum].gatetimer |= 0x40;
    }
  }
}

void clearsong(int cs, int cp, int ci, int ct, int cn)
{
  int c;

  if (!(cs | cp | ci | ct | cn)) return;

  stopsong();

  masterfader = 0x0f;
  epmarkchn = -1;
  etmarknum = -1;
  esmarkchn = -1;
  followplay = 0;

  for (c = 0; c < MAX_CHN; c++)
  {
    int d;
    chn[c].mute = 0;
    if (multiplier)
      chn[c].tempo = multiplier*6-1;
    else
      chn[c].tempo = 6-1;
    chn[c].pattptr = 0;
    if (cs)
    {
      memset(loadedsongfilename, 0, sizeof loadedsongfilename);
      for (d = 0; d < MAX_SONGS; d++)
      {
        memset(&songorder[d][c][0], 0, MAX_SONGLEN+2);
        if (!d)
        {
          songorder[d][c][0] = c;
          songorder[d][c][1] = LOOPSONG;
        }
        else
        {
          songorder[d][c][0] = LOOPSONG;
        }
      }
      epnum[c] = songorder[0][c][0];
      espos[c] = 0;
      esend[c] = 0;
    }
  }
  if (cs)
  {
    esview = 0;
    eseditpos = 0;
    escolumn = 0;
    eschn = 0;
    esnum = 0;
    eppos = 0;
    epview =-VISIBLEPATTROWS/2;
    epcolumn = 0;
    epchn = 0;
  }
  if (cn)
  {
    memset(songname, 0, sizeof songname);
    memset(authorname, 0, sizeof authorname);
    memset(copyrightname, 0, sizeof copyrightname);
    enpos = 0;
  }
  if (cp)
  {
    memset(loadedsongfilename, 0, sizeof loadedsongfilename);
    for (c = 0; c < MAX_PATT; c++)
      clearpattern(c);
  }
  if (ci)
  {
    for (c = 0; c < MAX_INSTR; c++)
      clearinstr(c);
    memset(&instrcopybuffer, 0, sizeof(INSTR));
    eipos = 0;
    eicolumn = 0;
    einum = 1;
  }
  if (ct == 1)
  {
    for (c = MAX_TABLES-1; c >= 0; c--)
    {
      memset(ltable[c], 0, MAX_TABLELEN);
      memset(rtable[c], 0, MAX_TABLELEN);
      settableview(c, 0);
    }
  }
  countpatternlengths();
}

void countpatternlengths(void)
{
  int c, d, e;

  highestusedpattern = 0;
  highestusedinstr = 0;
  for (c = 0; c < MAX_PATT; c++)
  {
    for (d = 0; d <= MAX_PATTROWS; d++)
    {
      if (pattern[c][d*4] == ENDPATT) break;
      if ((pattern[c][d*4] != REST) || (pattern[c][d*4+1]) || (pattern[c][d*4+2]) || (pattern[c][d*4+3]))
        highestusedpattern = c;
      if (pattern[c][d*4+1] > highestusedinstr) highestusedinstr = pattern[c][d*4+1];
    }
    pattlen[c] = d;
  }

  for (e = 0; e < MAX_SONGS; e++)
  {
    for (c = 0; c < MAX_CHN; c++)
    {
      for (d = 0; d < MAX_SONGLEN; d++)
      {
        if (songorder[e][c][d] >= LOOPSONG) break;
        if ((songorder[e][c][d] < REPEAT) && (songorder[e][c][d] > highestusedpattern))
          highestusedpattern = songorder[e][c][d];
      }
      songlen[e][c] = d;
    }
  }
}

void countthispattern(void)
{
  int c, d, e;

  c = epnum[epchn];
  for (d = 0; d <= MAX_PATTROWS; d++)
  {
    if (pattern[c][d*4] == ENDPATT) break;
  }
  pattlen[c] = d;

  e = esnum;
  c = eschn;
  for (d = 0; d < MAX_SONGLEN; d++)
  {
    if (songorder[e][c][d] >= LOOPSONG) break;
    if (songorder[e][c][d] > highestusedpattern)
      highestusedpattern = songorder[e][c][d];
  }
  songlen[e][c] = d;
}

int insertpattern(int p)
{
  int c, d, e;

  findusedpatterns();
  if (p >= MAX_PATT-2) return 0;
  if (pattused[MAX_PATT-1]) return 0;
  memmove(pattern[p+2], pattern[p+1], (MAX_PATT-p-2)*(MAX_PATTROWS*4+4));  
  countpatternlengths();

  for (c = 0; c < MAX_SONGS; c++)
  {
    if ((songlen[c][0]) &&
        (songlen[c][1]) &&
        (songlen[c][2]))
    {
      for (d = 0; d < MAX_CHN; d++)
      {
        for (e = 0; e < songlen[c][d]; e++)
        {
          if ((songorder[c][d][e] < REPEAT) && (songorder[c][d][e] > p) && (songorder[c][d][e] != MAX_PATT-1))
            songorder[c][d][e]++;
        }
      }
    }
  }
  
  for (c = 0; c < MAX_CHN; c++)
  {
    if ((epnum[c] > p) && (epnum[c] != MAX_PATT-1)) epnum[c]++;
  }

  return 1;
}

void deletepattern(int p)
{
  int c, d, e;

  if (p == MAX_PATT-1) return;

  memmove(pattern[p], pattern[p+1], (MAX_PATT-p-1)*(MAX_PATTROWS*4+4));
  clearpattern(MAX_PATT-1);
  countpatternlengths();

  for (c = 0; c < MAX_SONGS; c++)
  {
    if ((songlen[c][0]) &&
        (songlen[c][1]) &&
        (songlen[c][2]))
    {
      for (d = 0; d < MAX_CHN; d++)
      {
        for (e = 0; e < songlen[c][d]; e++)
        {
          if ((songorder[c][d][e] < REPEAT) && (songorder[c][d][e] > p))
            songorder[c][d][e]--;
        }
      }
    }
  }
  
  for (c = 0; c < MAX_CHN; c++)
  {
    if (epnum[c] > p) epnum[c]--;
  }
}

void clearpattern(int p)
{
  int c;

  memset(pattern[p], 0, MAX_PATTROWS*4);
  for (c = 0; c < defaultpatternlength; c++) pattern[p][c*4] = REST;
  for (c = defaultpatternlength; c <= MAX_PATTROWS; c++) pattern[p][c*4] = ENDPATT;
}

void findusedpatterns(void)
{
  int c, d, e;

  countpatternlengths();
  memset(pattused, 0, sizeof pattused);
  for (c = 0; c < MAX_SONGS; c++)
  {
    if ((songlen[c][0]) &&
        (songlen[c][1]) &&
        (songlen[c][2]))
    {
      for (d = 0; d < MAX_CHN; d++)
      {
        for (e = 0; e < songlen[c][d]; e++)
        {
          if (songorder[c][d][e] < REPEAT)
            pattused[songorder[c][d][e]] = 1;
        }
      }
    }
  }
}

void findduplicatepatterns(void)
{
  int c, d;

  findusedpatterns();

  for (c = 0; c < MAX_PATT; c++)
  {
    if (pattused[c])
    {
      for (d = c+1; d < MAX_PATT; d++)
      {
        if (pattlen[d] == pattlen[c])
        {
          if (!memcmp(pattern[c], pattern[d], pattlen[c]*4))
          {
            int f, g, h;

            for (f = 0; f < MAX_SONGS; f++)
            {
              if ((songlen[f][0]) &&
                  (songlen[f][1]) &&
                  (songlen[f][2]))
              {
                for (g = 0; g < MAX_CHN; g++)
                {
                  for (h = 0; h < songlen[f][g]; h++)
                  {
                    if (songorder[f][g][h] == d)
                      songorder[f][g][h] = c;
                  }
                }
              }
            }
            for (f = 0; f < MAX_CHN; f++)
              if (epnum[f] == d) epnum[f] = c;
          }
        }
      }
    }
  }

  findusedpatterns();
}

void optimizeeverything(int oi, int ot)
{
  int c, d, e;

  stopsong();

  findduplicatepatterns();

  memset(instrused, 0, sizeof instrused);

  for (c = MAX_PATT-1; c >= 0; c--)
  {
    if (pattused[c])
    {
      for (d = 0; d < MAX_PATTROWS; d++)
      {
        if (pattern[c][d*4] == ENDPATT) break;
        if (pattern[c][d*4+1])
          instrused[pattern[c][d*4+1]] = 1;
      }
    }
    else deletepattern(c);
  }

  countpatternlengths();

  if (oi)
  {
    for (c = MAX_INSTR-2; c >= 1; c--)
    {
      if (!instrused[c])
      {
        clearinstr(c);

        if (c < MAX_INSTR-2)
        {
          memmove(&instr[c], &instr[c+1], (MAX_INSTR-2-c) * sizeof(INSTR));
          clearinstr(MAX_INSTR-2);
          for (d = 0; d < MAX_PATT; d++)
          {
            for (e = 0; e < pattlen[d]; e++)
            {
              if ((pattern[d][e*4+1] > c) && (pattern[d][e*4+1] != MAX_INSTR-1))
                pattern[d][e*4+1]--;
            }
          }
        }
      }
    }
  }

  if (ot)
  {
    for (c = 0; c < MAX_TABLES; c++) optimizetable(c);
  }
}

