/*
 * LoadTracker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// =============================================================================
// packer/relocator
// =============================================================================

#define RELOC_C

#include "reloc.h"

#include "colors.h"
#include "console.h"
#include "display.h"
#include "play.h"
#include "settings.h"
#include "song.h"
#include "table.h"
#include "tuning.h"

#include "bme_main.h"
#include "bme_end.h"
#include "bme_win.h"
#include "bme_io.h"
#include "bme_snd.h"

#ifdef LTRELOC
#  include "tools/ltreloc.h"
#else
#  include "loadtrk.h"
#endif

#include "asm/buf.h"
//#include "asm/log.h"
#include "asm/parse.h"

#include <algorithm>
#include <array>
#include <bitset>
#include <iterator>
#include <new>
#include <string>

#include <cstring>
#include <cstdio>
#include <cstdlib>

#ifdef OVERFLOW
// might be defined in math.h
#  undef OVERFLOW
#endif

enum class Cause
{
  NONE,
  PATTERN,
  INSTRUMENT,
  WAVE_CMD
};

enum class ErrorType
{
  NONE,
  OVERFLOW,
  JUMP
};

struct TabError
{
  ErrorType type = ErrorType::NONE;
  Cause cause = Cause::NONE;
  int source1 = 0;
  int source2 = 0;
};

struct DataInf
{
    int offset;
    int size;
};

struct MapInf
{
    unsigned char map;
    bool used;
};

constexpr int MAX_OPTIONS = 7;

const char *playeroptname[MAX_OPTIONS] =
{
  "Buffered SID-writes",
  "Sound effect support",
  "Volume change support",
  "Store author-info",
  "Use zeropage ghostregs",
  "Disable optimization",
  "Full SID buffering"
};

const char *tableleftname[MAX_TABLES] =
{
  "mt_wavetbl",
  "mt_pulsetimetbl",
  "mt_filttimetbl",
  "mt_speedlefttbl"
};

const char *tablerightname[MAX_TABLES] =
{
  "mt_notetbl",
  "mt_pulsespdtbl",
  "mt_filtspdtbl",
  "mt_speedrighttbl"
};

std::bitset<MAX_CHN> chnused;
std::array<MapInf, MAX_INSTR> instr;
std::array<std::array<MapInf, MAX_TABLELEN+1>, MAX_TABLES> table;

ErrorType tableerror;

int nofilter;
int nofiltermod;
int nopulse;
int nopulsemod;
int nowavedelay;
int norepeat;
int notrans;
int noportamento;
int notoneporta;
int novib;
int noinsvib;
int nosetad;
int nosetsr;
int nosetwave;
int nosetwaveptr;
int nosetpulseptr;
int nosetfiltptr;
int nosetfiltcutoff;
int nosetfiltctrl;
int nosetmastervol;
int nofunktempo;
int noglobaltempo;
int nochanneltempo;
int nogate;
int noeffects;
int nowavecmd;
int nofirstwavecmd;
int nocalculatedspeed;
int nonormalspeed;
int nozerospeed;

buf src = STATIC_BUF_INIT;
buf dest = STATIC_BUF_INIT;

//int testoverlap(int area1start, int area1size, int area2start, int area2size);
void exectable(int num, int ptr);
int packpattern(unsigned char *dest, unsigned char *src, int rows);
void findtableduplicates(int num);
int isusedandselfcontained(int num, int start);
void calcspeedtest(unsigned char pos);
unsigned char swapnybbles(unsigned char n);

int insertfile(const char *name);
void inserttext(const char *text);
void insertdefine(const char *name, int value);
void insertlabel(const char *name);
void insertbyte(unsigned char byte);
void insertbytes(const unsigned char *bytes, int size);
void insertaddrlo(const char *name);
void insertaddrhi(const char *name);

void error(const char *msg)
{
    clearscreen();
    printtextc(MAX_ROWS/2, colors.CTITLE, msg);
    fliptoscreen();
    waitkeynoupdate();
}

void table_error(TabError terr)
{
    clearscreen();
    switch (terr.type)
    {
        case ErrorType::JUMP:
        std::snprintf(textbuffer, MAX_PATHNAME, "TABLE POINTER POINTS TO A JUMP! ");
        break;

        case ErrorType::OVERFLOW:
        std::snprintf(textbuffer, MAX_PATHNAME, "TABLE EXECUTION OVERFLOWS! ");
        break;

        case ErrorType::NONE:
        // unreachable
        break;
    }
    switch (terr.cause)
    {
        case Cause::PATTERN:
        std::snprintf(textbuffer, MAX_PATHNAME + std::strlen(textbuffer), "(PATTERN %02X, ROW %02d)", terr.source1, terr.source2);
        break;

        case Cause::WAVE_CMD:
        std::snprintf(textbuffer, MAX_PATHNAME + std::strlen(textbuffer), "WAVETABLE CMD (ROW %02X, ", terr.source1);
        goto TABLETYPE;

        case Cause::INSTRUMENT:
        std::snprintf(textbuffer, MAX_PATHNAME + std::strlen(textbuffer), "(INSTRUMENT %02X, ", terr.source1);
TABLETYPE:
        switch (terr.source2)
        {
            case WTBL:
            std::strcat(textbuffer, "WAVE");
            break;

            case PTBL:
            std::strcat(textbuffer, "PULSE");
            break;

            case FTBL:
            std::strcat(textbuffer, "FILTER");
            break;
        }
        std::strcat(textbuffer, ")");
        break;

        case Cause::NONE:
        // unreachable
        break;
    }
    printtextc(MAX_ROWS/2, colors.CTITLE, textbuffer);

    fliptoscreen();
    waitkeynoupdate();
}

void initreloc()
{
  noeffects = 1;
  nogate = 1;
  nofilter = 1;
  nofiltermod = 1;
  nopulse = 1;
  nopulsemod = 1;
  nowavedelay = 1;
  nowavecmd = 1;
  norepeat = 1;
  notrans = 1;
  noportamento = 1;
  notoneporta = 1;
  novib = 1;
  noinsvib = 1;
  nosetad = 1;
  nosetsr = 1;
  nosetwave = 1;
  nosetwaveptr = 1;
  nosetpulseptr = 1;
  nosetfiltptr = 1;
  nosetfiltcutoff = 1;
  nosetfiltctrl = 1;
  nosetmastervol = 1;
  nofunktempo = 1;
  noglobaltempo = 1;
  nochanneltempo = 1;
  nofirstwavecmd = 1;
  nocalculatedspeed = 1;
  nonormalspeed = 1;
  nozerospeed = 1;

  instr.fill({0,false});
  table.fill({0,false});
  chnused.reset();
  pattused.reset();

  tableerror = ErrorType::NONE;
}

bool checklen(int c, bool dual)
{
  bool res = (song.len[c][0]) &&
             (song.len[c][1]) &&
             (song.len[c][2]);
  if (dual)
  {
      res &= (song.len[c][3]) &&
             (song.len[c][4]) &&
             (song.len[c][5]);
  }
  return res;
}

void relocator(const char* filename)
{
  bool dual = config.numsids == 2;
  unsigned char *packeddata = nullptr;
  std::string playername = (config.adparam < 0xf000) ? "player" : "altplayer";
  if (dual)
    playername.append("_s");
  playername.append(".s");

  unsigned char pattmap[MAX_PATT];
  DataInf patt[MAX_PATT];
  DataInf sng[MAX_SONGS][MAX_CHN];

  TabError taberr;
  int patterns = 0;
  int songs = 0;
  int instruments = 0;
  int numlegato = 0;
  int numnohr = 0;
  int numnormal = 0;
  int freenormal;
  int freenohr;
  int freelegato;
  int transuprange = 0;
  int transdownrange = 0;
  int pattdatasize = 0;
  int patttblsize = 0;
  int songdatasize = 0;
  int songtblsize = 0;
  int instrsize = 0;
  int wavetblsize = 0;
  int pulsetblsize = 0;
  int filttblsize = 0;
  int speedtblsize = 0;
  int playersize = 0;
  int packedsize = 0;

  FILE *songhandle = nullptr;
#ifndef LTRELOC
  int selectdone;
  int opt = 0;
#endif
  unsigned char speedcode[] = {0xa2,0x00,0x8e,0x04,0xdc,0xa2,0x00,0x8e,0x05,0xdc};

  unsigned char patttemp[512];
  unsigned char *songwork = nullptr;
  unsigned char *pattwork = nullptr;
  unsigned char *instrwork = nullptr;

  int ciaval = 0;

  int channels = config.getMaxChannels();
  int fixedparams = 1;
  int simplepulse = 1;
  int firstnote = MAX_NOTES-1;
  int lastnote = 0;
  int patternlastnote = 0;

  initreloc();

  stopsong();

  // this errors out, hopefully we don't need to debug players
  // asm/log.h:124:59: error: invalid conversion from ‘int’ to ‘log_level’ [-fpermissive]
  //LOG_INIT_CONSOLE(LOG_NORMAL);
  parse_init();
  buf_free(&src);
  buf_free(&dest);

  int maxChns = config.getMaxChannels();
#ifndef LTRELOC
  int ypos;
#endif
  // Process song-orderlists
  countpatternlengths();

  // Calculate amount of songs with nonzero length
  for (int c = 0; c < MAX_SONGS; c++)
  {
    if (checklen(c, dual))
    {
      // See which patterns are used in this song
      for (int d = 0; d < maxChns; d++)
      {
        songdatasize += song.len[c][d]+2;
        for (int e = 0; e < song.len[c][d]; e++)
        {
          if (song.order[c][d][e] < REPEAT)
          {
            int num = song.order[c][d][e];

            pattused.set(num);
            for (int f = 0; f < getPattlen(num); f++)
            {
              if ((song.pattern[num][f*4] != REST) || (song.pattern[num][f*4+1]) || (song.pattern[num][f*4+2]))
                chnused.set(d);
            }
          }
          else
          {
            if (song.order[c][d][e] >= TRANSDOWN)
            {
              notrans = 0;
              if (song.order[c][d][e] < TRANSUP)
              {
                int newtransdownrange = -(song.order[c][d][e] - TRANSUP);
                if (newtransdownrange > transdownrange) transdownrange = newtransdownrange;
              }
              else
              {
                int newtransuprange = song.order[c][d][e] - TRANSUP;
                if (newtransuprange > transuprange) transuprange = newtransuprange;
              }
            }
            else norepeat = 0;
          }
        }
        if (song.order[c][d][song.len[c][d]+1] >= song.len[c][d])
        {
          std::snprintf(textbuffer, MAX_PATHNAME, "ILLEGAL SONG RESTART POSITION! (SUBTUNE %02X, CHANNEL %d)", c, d+1);
          error(textbuffer);
          goto PRCLEANUP;
        }
      }
      songs++;
    }
  }

  if (!songs)
  {
    error("NO SONGS, NO DATA TO SAVE!");
    goto PRCLEANUP;
  }

  // Optimize amount of used channels
  for (int c = maxChns-1; c; c--)
  {
    if (chnused.test(c))
      break;
    channels = c;
  }

  // Build the pattern-mapping
  // Instrument 1 is always used
  instr[1].used = true;
  for (int c = 0; c < MAX_PATT; c++)
  {
    if (pattused.test(c))
    {
      pattmap[c] = patterns;
      patterns++;

      // See which instruments/tablecommands are used
      for (int d = 0; d < getPattlen(c); d++)
      {
        tableerror = ErrorType::NONE;

        if ((song.pattern[c][d*4] == KEYOFF) || (song.pattern[c][d*4] == KEYON))
          nogate = 0;
        if (song.pattern[c][d*4+1])
          instr[song.pattern[c][d*4+1]].used = true;
        if (song.pattern[c][d*4+2])
          noeffects = 0;
        if ((song.pattern[c][d*4+2] >= CMD_SETWAVEPTR) && (song.pattern[c][d*4+2] <= CMD_SETFILTERPTR))
          exectable(song.pattern[c][d*4+2] - CMD_SETWAVEPTR, song.pattern[c][d*4+3]);
        if ((song.pattern[c][d*4+2] >= CMD_PORTAUP) && (song.pattern[c][d*4+2] <= CMD_VIBRATO))
        {
          exectable(STBL, song.pattern[c][d*4+3]);
          calcspeedtest(song.pattern[c][d*4+3]);
        }
        if (song.pattern[c][d*4+2] == CMD_FUNKTEMPO)
          exectable(STBL, song.pattern[c][d*4+3]);
        if (song.pattern[c][d*4+2] == CMD_FUNKTEMPO)
        {
          nofunktempo = 0;
          noglobaltempo = 0;
        }
        if ((song.pattern[c][d*4+2] == CMD_SETTEMPO) && ((song.pattern[c][d*4+3] & 0x7f) < 3)) nofunktempo = 0;

        // See, which are the highest/lowest notes used
        if ((song.pattern[c][d*4] >= FIRSTNOTE) && (song.pattern[c][d*4] <= LASTNOTE))
        {
          int newfirstnote = song.pattern[c][d*4] - FIRSTNOTE - transdownrange;
          int newlastnote = song.pattern[c][d*4] - FIRSTNOTE + transuprange;
          if (newfirstnote < 0) newfirstnote = 0;
          if (newlastnote > MAX_NOTES-1) newlastnote = MAX_NOTES-1;

          if (newfirstnote < firstnote) firstnote = newfirstnote;
          if (newlastnote > lastnote)
          {
            patternlastnote = newlastnote;
            lastnote = newlastnote;
          }
          if (newfirstnote > lastnote)
          {
            patternlastnote = newfirstnote;
            lastnote = newfirstnote;
          }
        }
        if ((tableerror != ErrorType::NONE) && (taberr.type == ErrorType::NONE))
        {
          taberr.type = tableerror;
          taberr.cause = Cause::PATTERN;
          taberr.source1 = c;
          taberr.source2 = d;
        }
      }
    }
  }

  // Count amount of normal, nohr, and legato instruments
  // Also see if special first wave parameters are used
  for (int c = 0; c < MAX_INSTR; c++)
  {
    if (instr[c].used)
    {
      if (song.instr[c].gatetimer & 0x40) numlegato++;
      else
      {
        if (song.instr[c].gatetimer & 0x80) numnohr++;
        else numnormal++;
      }
      if ((!song.instr[c].firstwave) || (song.instr[c].firstwave >= 0xfe))
        nofirstwavecmd = 0;
    }
  }
  freenormal = 1;
  freenohr = freenormal + numnormal;
  freelegato = freenohr + numnohr;

  // Build the instrument-mapping
  for (int c = 0; c < MAX_INSTR; c++)
  {
    if (instr[c].used)
    {
      if (song.instr[c].gatetimer & 0x40) instr[c].map = freelegato++;
      else
      {
        if (song.instr[c].gatetimer & 0x80) instr[c].map = freenohr++;
        else instr[c].map = freenormal++;
      }
      instruments++;
      for (int d = 0; d < MAX_TABLES; d++)
      {
        tableerror = ErrorType::NONE;
        exectable(d, song.instr[c].ptr[d]);
        if (d == STBL) calcspeedtest(song.instr[c].ptr[d]);
        if ((tableerror != ErrorType::NONE) && (taberr.type == ErrorType::NONE))
        {
          taberr.type = tableerror;
          taberr.cause = Cause::INSTRUMENT;
          taberr.source1 = c;
          taberr.source2 = d;
        }
      }
    }
  }

  // Execute tableprograms invoked from wavetable commands
  for (int c = 0; c < MAX_TABLELEN; c++)
  {
    if (table[WTBL][c+1].used)
    {
      if ((song.ltable[WTBL][c] >= WAVECMD) && (song.ltable[WTBL][c] <= WAVELASTCMD))
      {
        int d = -1;
        tableerror = ErrorType::NONE;

        switch(song.ltable[WTBL][c] - WAVECMD)
        {
          case CMD_PORTAUP:
          case CMD_PORTADOWN:
          case CMD_TONEPORTA:
          case CMD_VIBRATO:
          d = STBL;
          calcspeedtest(song.rtable[WTBL][c]);
          break;

          case CMD_SETPULSEPTR:
          d = PTBL;
          nopulse = 0;
           break;

           case CMD_SETFILTERPTR:
           d = FTBL;
          nofilter = 0;
          break;

          case CMD_DONOTHING:
          case CMD_SETWAVEPTR:
          case CMD_FUNKTEMPO:
          std::snprintf(textbuffer, MAX_PATHNAME, "ILLEGAL WAVETABLE COMMAND (ROW %02X, COMMAND %X)", c+1, song.ltable[WTBL][c] - WAVECMD);
          error(textbuffer);
          goto PRCLEANUP;
        }

        if (d != -1) exectable(d, song.rtable[WTBL][c]);

        if ((tableerror != ErrorType::NONE) && (taberr.type == ErrorType::NONE))
        {
          taberr.type = tableerror;
          taberr.cause = Cause::WAVE_CMD;
          taberr.source1 = c+1;
          taberr.source2 = d;
        }
      }
    }
  }

  // Build the table-mapping
  for (int c = 0; c < MAX_TABLES; c++)
  {
    unsigned char e = 1;
    for (int d = 0; d < MAX_TABLELEN; d++)
    {
      if (table[c][d+1].used)
      {
        table[c][d+1].map = e;
        e++;
      }
    }
  }

  // Check for table errors
  if (taberr.cause != Cause::NONE)
  {
    table_error(taberr);
    goto PRCLEANUP;
  }

  // Find duplicate ranges in tables
  for (int c = 0; c < MAX_TABLES; c++)
    findtableduplicates(c);

  if (dual)
  {
    // Force options for dual sid tunes
    config.playerversion |= PLAYER_BUFFERED;
    config.playerversion &= ~PLAYER_ZPGHOSTREGS;
  }

  // Select playroutine options
#ifndef LTRELOC
  clearscreen();
  printblankc(0, 0, colors.CHEADER, MAX_COLUMNS);
  if (!std::strlen(filename))
    std::snprintf(textbuffer, MAX_PATHNAME, "%s Packer/Relocator", programname);
  else
    std::snprintf(textbuffer, MAX_PATHNAME, "%s Packer/Relocator - %s", programname, filename);
  textbuffer[MAX_COLUMNS] = 0;
  printtext(1, 0, colors.CHEADER, textbuffer);
  printtext(1, 2, colors.CTITLE, "SELECT PLAYROUTINE OPTIONS: (CURSORS=MOVE/CHANGE, ENTER=ACCEPT, ESC=CANCEL)");

  if (!dual)
  {
    if (config.multiplier == 1 && snd_bpmtempo != 125)
    {
      ciaval = 19566 - ((19655 / 125) * (snd_bpmtempo - 125));

      std::snprintf(textbuffer, MAX_PATHNAME, "[INFO] CIA timer value for %03d BPM: $%04X", snd_bpmtempo, ciaval);
      printtext(1, 3+MAX_OPTIONS+8, colors.CEDIT, textbuffer);
    }
  }

  selectdone = 0;
  while (!selectdone)
  {
    int opts = MAX_OPTIONS;
    if (dual) opts--;
    for (int c = 0; c < opts; c++)
    {
      int color = (opt == c) ? colors.CEDIT : colors.CNORMAL;

      printtext(1, 3+c, color, playeroptname[c]);
      if (config.playerversion & (PLAYER_BUFFERED << c))
        printtext(24, 3+c, color, "Yes");
      else
        printtext(24, 3+c, color, "No ");
    }
    fliptoscreen();
    waitkeynoupdate();

    if (win_quitted)
    {
      exitprogram = true;
      goto PRCLEANUP;
    }

    switch(input.rawkey)
    {
      case KEY_LEFT:
      case KEY_RIGHT:
      case KEY_SPACE:
      config.playerversion ^= (PLAYER_BUFFERED << opt);
      if (opt)
      {
        if ((config.playerversion & PLAYER_SOUNDEFFECTS) ||
            (config.playerversion & PLAYER_ZPGHOSTREGS) ||
            (!dual && (config.playerversion & PLAYER_FULLBUFFERED)))
          config.playerversion |= PLAYER_BUFFERED;
      }
      else
      {
        if (!(config.playerversion & PLAYER_BUFFERED))
        {
          config.playerversion &= ~PLAYER_SOUNDEFFECTS;
          config.playerversion &= ~PLAYER_ZPGHOSTREGS;
          if (!dual) config.playerversion &= ~PLAYER_FULLBUFFERED;
        }
      }
      break;

      case KEY_UP:
      opt--;
      if (opt < 0) opt = MAX_OPTIONS-1;
      break;

      case KEY_DOWN:
      opt++;
      if (opt >= MAX_OPTIONS) opt = 0;
      break;

      case KEY_ESC:
      selectdone = -1;
      break;

      case KEY_ENTER:
      selectdone = 1;
      break;
    }

    if (dual)
    {
      config.playerversion |= PLAYER_BUFFERED;
      config.playerversion &= ~PLAYER_ZPGHOSTREGS;
    }
  }
  if (selectdone == -1) goto PRCLEANUP;
#endif

  // Disable optimizations if necessary
  if (config.playerversion & PLAYER_NOOPTIMIZATION)
  {
    fixedparams = 0;
    if (!numlegato) numlegato++;

    simplepulse = 0;
    firstnote = 0;
    lastnote = MAX_NOTES-1;
    nogate = 0;
    noeffects = 0;
    nofilter = 0;
    nofiltermod = 0;
    nopulse = 0;
    nopulsemod = 0;
    nowavedelay = 0;
    nowavecmd = 0;
    norepeat = 0;
    notrans = 0;
    noportamento = 0;
    notoneporta = 0;
    novib = 0;
    noinsvib = 0;
    nosetad = 0;
    nosetsr = 0;
    nosetwave = 0;
    nosetwaveptr = 0;
    nosetpulseptr = 0;
    nosetfiltptr = 0;
    nosetfiltcutoff = 0;
    nosetfiltctrl = 0;
    nosetmastervol = 0;
    nofunktempo = 0;
    noglobaltempo = 0;
    nochanneltempo = 0;
    nofirstwavecmd = 0;
    nocalculatedspeed = 0;
    nonormalspeed = 0;
    nozerospeed = 0;
  }

  // Make sure buffering is used if it is needed
  if ((config.playerversion & PLAYER_SOUNDEFFECTS) ||
      (config.playerversion & PLAYER_ZPGHOSTREGS) ||
      (!dual && (config.playerversion & PLAYER_FULLBUFFERED)))
    config.playerversion |= PLAYER_BUFFERED;

  if (!dual)
  {
    // Sound effect or ghostreg players always use full 3 channels
    if ((config.playerversion & PLAYER_SOUNDEFFECTS) ||
        (config.playerversion & PLAYER_FULLBUFFERED) ||
        (config.playerversion & PLAYER_ZPGHOSTREGS))
      channels = maxChns;
  }

  // Allocate memory for song-orderlists
  songtblsize = songs*6;
  songwork = new (std::nothrow) unsigned char[songdatasize];
  if (!songwork)
  {
    error("OUT OF MEMORY IN PACKER/RELOCATOR!");
    goto PRCLEANUP;
  }

  // Generate song.orderlists & songtable
  songdatasize = 0;
  for (int c = 0; c < songs; c++)
  {
    if (checklen(c, dual))
    {
      for (int d = 0; d < maxChns; d++)
      {
        sng[c][d].offset = songdatasize;
        sng[c][d].size = song.len[c][d] + 2;

        int e;
        for (e = 0; e < song.len[c][d]; e++)
        {
          // Pattern
          if (song.order[c][d][e] < REPEAT)
            songwork[songdatasize++] = pattmap[song.order[c][d][e]];
          else
          {
            // Transpose
            if (song.order[c][d][e] >= TRANSDOWN)
            {
              songwork[songdatasize++] = song.order[c][d][e];
            }
            // Repeat sequence: must be swapped
            else
            {
              // See that repeat amount is more than 1
              if (song.order[c][d][e] > REPEAT)
              {
                // Insanity check that a pattern indeed follows
                if (song.order[c][d][e+1] < REPEAT)
                {
                  songwork[songdatasize++] = pattmap[song.order[c][d][e+1]];
                  songwork[songdatasize++] = song.order[c][d][e];
                  e++;
                }
                else
                  songwork[songdatasize++] = song.order[c][d][e];
              }
            }
          }
        }
        // Endmark & repeat position
        songwork[songdatasize++] = song.order[c][d][e++];
        songwork[songdatasize++] = song.order[c][d][e++];
      }
    }
    else
    {
      for (int d = 0; d < maxChns; d++)
      {
        sng[c][d].offset = songdatasize;
        sng[c][d].size = 0;
      }
    }
  }

  // Calculate total size of patterns
  for (int c = 0; c < MAX_PATT; c++)
  {
    if (pattused.test(c))
    {
      int result = packpattern(patttemp, song.pattern[c], getPattlen(c));

      if (result < 0)
      {
        std::snprintf(textbuffer, MAX_PATHNAME, "PATTERN %02X IS TOO COMPLEX (OVER 256 BYTES PACKED)!", c);
        error(textbuffer);
        goto PRCLEANUP;
      }
      pattdatasize += result;
    }
  }

  patttblsize = patterns*2;
  pattwork = new (std::nothrow) unsigned char[pattdatasize];
  if (!pattwork)
  {
    error("OUT OF MEMORY IN PACKER/RELOCATOR!");
    goto PRCLEANUP;
  }

  // This time pack the patterns for real
  pattdatasize = 0;
  for (int c = 0, d = 0; c < MAX_PATT; c++)
  {
    if (pattused.test(c))
    {
      patt[d].offset = pattdatasize;
      patt[d].size = packpattern(&pattwork[pattdatasize], song.pattern[c], getPattlen(c));
      pattdatasize += patt[d].size;
      d++;
    }
  }

  // Then process instruments
  instrsize = instruments*9;
  instrwork = new (std::nothrow) unsigned char[instrsize];
  if (!instrwork)
  {
    error("OUT OF MEMORY IN PACKER/RELOCATOR!");
    goto PRCLEANUP;
  }

  for (int c = 1; c < MAX_INSTR; c++)
  {
    if (instr[c].used)
    {
      int d = instr[c].map - 1;
      instrwork[d] = song.instr[c].ad;
      instrwork[d+instruments] = song.instr[c].sr;
      instrwork[d+instruments*2] = table[WTBL][song.instr[c].ptr[WTBL]].map;
      instrwork[d+instruments*3] = table[PTBL][song.instr[c].ptr[PTBL]].map;
      instrwork[d+instruments*4] = table[FTBL][song.instr[c].ptr[FTBL]].map;
      if (song.instr[c].vibdelay)
      {
        instrwork[d+instruments*5] = table[STBL][song.instr[c].ptr[STBL]].map;
        instrwork[d+instruments*6] = song.instr[c].vibdelay - 1;
      }
      else
      {
        instrwork[d+instruments*5] = 0;
        instrwork[d+instruments*6] = 0;
      }
      instrwork[d+instruments*7] = song.instr[c].gatetimer & 0x3f;
      instrwork[d+instruments*8] = song.instr[c].firstwave;

      if (song.instr[c].ptr[STBL])
      {
        novib = 0;
        noinsvib = 0;
      }
      if (song.instr[c].ptr[PTBL])
        nopulse = 0;
      if (song.instr[c].ptr[FTBL])
        nofilter = 0;

      // See if all instruments use same gatetimer & firstwave parameters
      if ((song.instr[c].gatetimer != song.instr[1].gatetimer) ||
          (song.instr[c].firstwave != song.instr[1].firstwave))
        fixedparams = 0;
      // or if special firstwave commands are in use
      if ((!song.instr[c].firstwave) || (song.instr[c].firstwave >= 0xfe))
        fixedparams = 0;
    }
  }

  // Disable sameparam optimization for multispeed stability
  if (config.multiplier > 1)
  {
    fixedparams = 0;
    numlegato++;
    numnohr++;
  }

  if (fixedparams) instrsize -= instruments*2;
  if (noinsvib) instrsize -= instruments*2;
  if (nopulse) instrsize -= instruments;
  if (nofilter) instrsize -= instruments;

  // Process tables
  for (int c = 0; c < MAX_TABLELEN; c++)
  {
    if (table[WTBL][c+1].used)
    {
      wavetblsize += 2;
      if ((song.ltable[WTBL][c] >= WAVEDELAY) && (song.ltable[WTBL][c] <= WAVELASTDELAY)) nowavedelay = 0;
      if ((song.ltable[WTBL][c] >= WAVECMD) && (song.ltable[WTBL][c] <= WAVELASTCMD))
      {
        nowavecmd = 0;
        noeffects = 0;
        switch (song.ltable[WTBL][c] - WAVECMD)
        {
          case CMD_PORTAUP:
          case CMD_PORTADOWN:
          noportamento = 0;
          break;

          case CMD_TONEPORTA:
          notoneporta = 0;
          break;

          case CMD_VIBRATO:
          novib = 0;
          break;

          case CMD_SETAD:
          nosetad = 0;
          break;

          case CMD_SETSR:
          nosetsr = 0;
          break;

          case CMD_SETWAVE:
          nosetwave = 0;
          break;

          case CMD_SETPULSEPTR:
          nosetpulseptr = 0;
          break;

          case CMD_SETFILTERPTR:
          nosetfiltptr = 0;
          break;

          case CMD_SETFILTERCUTOFF:
          nosetfiltcutoff = 0;
          break;

          case CMD_SETFILTERCTRL:
          nosetfiltctrl = 0;
          break;

          case CMD_SETMASTERVOL:
          nosetmastervol = 0;
          break;
        }
      }
      if (song.ltable[WTBL][c] < WAVECMD)
      {
        if (song.rtable[WTBL][c] <= 0x80)
        {
          int newlastnote = song.rtable[WTBL][c] + patternlastnote;
          if (newlastnote > MAX_NOTES - 1) newlastnote = MAX_NOTES - 1;
          if (song.rtable[WTBL][c] >= 0x20) firstnote = 0;
           if (newlastnote > lastnote) lastnote = newlastnote;
        }
        else
        {
          int newfirstnote = song.rtable[WTBL][c] & 0x7f;
          int newlastnote = song.rtable[WTBL][c] & 0x7f;
          if (newlastnote > MAX_NOTES - 1) newlastnote = MAX_NOTES - 1;
          if (newfirstnote < firstnote) firstnote = newfirstnote;
          if (newlastnote > lastnote) lastnote = newlastnote;
        }
      }
    }
  }
  for (int c = 0; c < MAX_TABLELEN; c++)
  {
    if (table[PTBL][c+1].used)
    {
      pulsetblsize += 2;
      if ((song.ltable[PTBL][c] >= 0x80) && (song.ltable[PTBL][c] != 0xff))
      {
        if (song.rtable[PTBL][c] & 0xf) simplepulse = 0;
      }
      if (song.ltable[PTBL][c] < 0x80)
      {
        nopulsemod = 0;
        if (song.rtable[PTBL][c] & 0xf) simplepulse = 0;
      }
    }
  }
  for (int c = 0; c < MAX_TABLELEN; c++)
  {
    if (table[FTBL][c+1].used)
    {
      filttblsize += 2;
      if (song.ltable[FTBL][c] < 0x80) nofiltermod = 0;
    }
  }
  for (int c = 0; c < MAX_TABLELEN; c++)
  {
    if (table[STBL][c+1].used) speedtblsize += 2;
  }
  // Zero entry of speedtable
  if ((!novib) || (!nofunktempo) || (!noportamento) || (!notoneporta))
    speedtblsize += 2;

  if (nopulse) pulsetblsize = 0;
  if (nofilter) filttblsize = 0;

#ifdef LTRELOC
  std::fprintf(STDOUT, "Player address:   $%04X\n", config.playeradr);
  std::fprintf(STDOUT, "Zeropage address: $%04X\n", config.zeropageadr);
#else
  ypos = dual ? 10 : 11;
  std::snprintf(textbuffer, MAX_PATHNAME, "SELECT START ADDRESS: (CURSORS=MOVE, ENTER=ACCEPT, ESC=CANCEL)");
  printtext(1, ypos, colors.CTITLE, textbuffer);

  selectdone = 0;
  while (!selectdone)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "$%04X", config.playeradr);
    printtext(1, ypos+1, colors.CEDIT, textbuffer);

    fliptoscreen();
    waitkeynoupdate();

    if (win_quitted)
    {
      exitprogram = true;
      goto PRCLEANUP;
    }

    switch(input.rawkey)
    {
      case KEY_LEFT:
      config.playeradr -= 0x0400;
      config.playeradr &= 0xff00;
      break;

      case KEY_UP:
      config.playeradr += 0x0100;
      config.playeradr &= 0xff00;
      break;

      case KEY_RIGHT:
      config.playeradr += 0x0400;
      config.playeradr &= 0xff00;
      break;

      case KEY_DOWN:
      config.playeradr -= 0x0100;
      config.playeradr &= 0xff00;
      break;

      case KEY_ESC:
      selectdone = -1;
      break;

      case KEY_ENTER:
      selectdone = 1;
      break;
    }
  }

  if (selectdone == -1) goto PRCLEANUP;

  std::snprintf(textbuffer, MAX_PATHNAME, "SELECT ZEROPAGE ADDRESS: (CURSORS=MOVE, ENTER=ACCEPT, ESC=CANCEL)");
  printtext(1, ypos+3, colors.CTITLE, textbuffer);

  selectdone = 0;
  while (!selectdone)
  {
    if (config.playerversion & PLAYER_ZPGHOSTREGS)
    {
      if (config.zeropageadr < 0x02) config.zeropageadr = 0xe5;
      if (config.zeropageadr > 0xe5) config.zeropageadr = 0x02;
    }
    else
    {
      if (config.zeropageadr < 0x02) config.zeropageadr = 0xfe;
      if (config.zeropageadr > 0xfe) config.zeropageadr = 0x02;
    }

    if (!(config.playerversion & PLAYER_ZPGHOSTREGS))
    {
      if (config.zeropageadr < 0x90)
        std::snprintf(textbuffer, MAX_PATHNAME, "$%02X-$%02X (Used by BASIC interpreter)    ", config.zeropageadr, config.zeropageadr+1);
      if ((config.zeropageadr >= 0x90) && (config.zeropageadr < 0xfb))
        std::snprintf(textbuffer, MAX_PATHNAME, "$%02X-$%02X (Used by KERNAL routines)      ", config.zeropageadr, config.zeropageadr+1);
      if ((config.zeropageadr >= 0xfb) && (config.zeropageadr < 0xfe))
        std::snprintf(textbuffer, MAX_PATHNAME, "$%02X-$%02X (Unused)                       ", config.zeropageadr, config.zeropageadr+1);
      if (config.zeropageadr >= 0xfe)
        std::snprintf(textbuffer, MAX_PATHNAME, "$%02X-$%02X ($FF used by BASIC interpreter)", config.zeropageadr, config.zeropageadr+1);
    }
    else
    {
      std::snprintf(textbuffer, MAX_PATHNAME, "$%02X-$%02X (ghostregs start at %02X)", config.zeropageadr, config.zeropageadr+26, config.zeropageadr);
    }

    printtext(1, ypos+4, colors.CEDIT, textbuffer);

    fliptoscreen();
    waitkeynoupdate();

    if (win_quitted)
    {
      exitprogram = true;
      goto PRCLEANUP;
    }

    switch(input.rawkey)
    {
      case KEY_LEFT:
      config.zeropageadr -= 0x10;
      break;

      case KEY_UP:
      config.zeropageadr++;
      break;

      case KEY_RIGHT:
      config.zeropageadr += 0x10;
      break;

      case KEY_DOWN:
      config.zeropageadr--;
      break;

      case KEY_ESC:
      selectdone = -1;
      break;

      case KEY_ENTER:
      selectdone = 1;
      break;
    }
  }

  if (selectdone == -1) goto PRCLEANUP;
#endif

  // Validate frequencytable parameters
  if (lastnote < firstnote)
    lastnote = firstnote;
  if (firstnote < 0) firstnote = 0;
  if (!nocalculatedspeed)
    lastnote++; // Calculated speeds need the next frequency value
  if (lastnote > MAX_NOTES-1) lastnote = MAX_NOTES-1;
  // For sound effect support, always use the full table
  if (config.playerversion & PLAYER_SOUNDEFFECTS)
  {
    firstnote = 0;
    lastnote = MAX_NOTES-1;
  }

  // Insert baseaddresses
  insertdefine("base", config.playeradr);
  insertdefine("zpbase", config.zeropageadr);
  insertdefine("SIDBASE", config.sidaddress);
  if (dual)
    insertdefine("SID2BASE", config.sid2address);

  // Insert conditionals
  insertdefine("SOUNDSUPPORT", (config.playerversion & PLAYER_SOUNDEFFECTS) ? 1 : 0);
  insertdefine("VOLSUPPORT", (config.playerversion & PLAYER_VOLUME) ? 1 : 0);
  insertdefine("BUFFEREDWRITES", (config.playerversion & PLAYER_BUFFERED) ? 1 : 0);
  if (!dual)
    insertdefine("GHOSTREGS", (config.playerversion & (PLAYER_ZPGHOSTREGS|PLAYER_FULLBUFFERED)) ? 1 : 0);
  insertdefine("ZPGHOSTREGS", (config.playerversion & PLAYER_ZPGHOSTREGS) ? 1 : 0);
  insertdefine("FIXEDPARAMS", fixedparams);
  insertdefine("SIMPLEPULSE", simplepulse);
  insertdefine("PULSEOPTIMIZATION", config.optimizepulse);
  insertdefine("REALTIMEOPTIMIZATION", config.optimizerealtime);
  insertdefine("NOAUTHORINFO", (config.playerversion & PLAYER_AUTHORINFO) ? 0 : 1);
  insertdefine("NOEFFECTS", noeffects);
  insertdefine("NOGATE", nogate);
  insertdefine("NOFILTER", nofilter);
  insertdefine("NOFILTERMOD", nofiltermod);
  insertdefine("NOPULSE", nopulse);
  insertdefine("NOPULSEMOD", nopulsemod);
  insertdefine("NOWAVEDELAY", nowavedelay);
  insertdefine("NOWAVECMD", nowavecmd);
  insertdefine("NOREPEAT", norepeat);
  insertdefine("NOTRANS", notrans);
  insertdefine("NOPORTAMENTO", noportamento);
  insertdefine("NOTONEPORTA", notoneporta);
  insertdefine("NOVIB", novib);
  insertdefine("NOINSTRVIB", noinsvib);
  insertdefine("NOSETAD", nosetad);
  insertdefine("NOSETSR", nosetsr);
  insertdefine("NOSETWAVE", nosetwave);
  insertdefine("NOSETWAVEPTR", nosetwaveptr);
  insertdefine("NOSETPULSEPTR", nosetpulseptr);
  insertdefine("NOSETFILTPTR", nosetfiltptr);
  insertdefine("NOSETFILTCTRL", nosetfiltctrl);
  insertdefine("NOSETFILTCUTOFF", nosetfiltcutoff);
  insertdefine("NOSETMASTERVOL", nosetmastervol);
  insertdefine("NOFUNKTEMPO", nofunktempo);
  insertdefine("NOGLOBALTEMPO", noglobaltempo);
  insertdefine("NOCHANNELTEMPO", nochanneltempo);
  insertdefine("NOFIRSTWAVECMD", nofirstwavecmd);
  insertdefine("NOCALCULATEDSPEED", nocalculatedspeed);
  insertdefine("NONORMALSPEED", nonormalspeed);
  insertdefine("NOZEROSPEED", nozerospeed);

  // Insert parameters
  insertdefine("NUMCHANNELS", channels);
  insertdefine("NUMSONGS", songs);
  insertdefine("FIRSTNOTE", firstnote);
  insertdefine("FIRSTNOHRINSTR", numnormal + 1);
  insertdefine("FIRSTLEGATOINSTR", numnormal + numnohr + 1);
  insertdefine("NUMHRINSTR", numnormal);
  insertdefine("NUMNOHRINSTR", numnohr);
  insertdefine("NUMLEGATOINSTR", numlegato);
  insertdefine("ADPARAM", config.adparam >> 8);
  insertdefine("SRPARAM", config.adparam & 0xff);
  insertdefine("CIAVALLO", ciaval & 0xff);
  insertdefine("CIAVALHI", ciaval >> 8);
  if ((song.instr[MAX_INSTR-1].ad >= 2) && (!(song.instr[MAX_INSTR-1].ptr[WTBL])))
    insertdefine("DEFAULTTEMPO", song.instr[MAX_INSTR-1].ad - 1);
  else
    insertdefine("DEFAULTTEMPO", config.multiplier ? (config.multiplier*6-1) : 5);

  // Fixed firstwave & gatetimer
  if (fixedparams)
  {
    insertdefine("FIRSTWAVEPARAM", song.instr[1].firstwave);
    insertdefine("GATETIMERPARAM", song.instr[1].gatetimer & 0x3f);
  }

  // Insert source code of player
  if (!insertfile(playername.c_str()))
  {
    error("COULD NOT OPEN PLAYROUTINE!");
    goto PRCLEANUP;
  }

  if (!dual)
  {
    // Modify ghostregs to not be zeropage if needed
    if ((config.playerversion & PLAYER_FULLBUFFERED) && (config.playerversion & PLAYER_ZPGHOSTREGS) == 0)
    {
      int bufsize = buf_size(&src);
      char* bufdata = (char*)buf_data(&src);
      for (int c = 0; c < bufsize; c++)
      {
        if (bufdata[c] == '<')
        {
          if (std::memcmp(bufdata + c + 1, "ghost", 5) == 0)
            bufdata[c] = ' ';
        }
      }
    }
  }

  // Insert frequencytable
  insertlabel("mt_freqtbllo");
  insertbytes(&freqtbllo[firstnote], lastnote-firstnote+1);
  insertlabel("mt_freqtblhi");
  insertbytes(&freqtblhi[firstnote], lastnote-firstnote+1);

  // Insert songtable
  insertlabel("mt_songtbllo");
  for (int c = 0; c < songs*maxChns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "mt_song%d", c);
    insertaddrlo(textbuffer);
  }
  insertlabel("mt_songtblhi");
  for (int c = 0; c < songs*maxChns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "mt_song%d", c);
    insertaddrhi(textbuffer);
  }

  // Insert patterntable
  insertlabel("mt_patttbllo");
  for (int c = 0; c < patterns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "mt_patt%d", c);
    insertaddrlo(textbuffer);
  }
  insertlabel("mt_patttblhi");
  for (int c = 0; c < patterns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "mt_patt%d", c);
    insertaddrhi(textbuffer);
  }

  // Insert instruments
  insertlabel("mt_insad");
  insertbytes(&instrwork[0], instruments);
  insertlabel("mt_inssr");
  insertbytes(&instrwork[instruments], instruments);
  insertlabel("mt_inswaveptr");
  insertbytes(&instrwork[instruments*2], instruments);
  if (!nopulse)
  {
    insertlabel("mt_inspulseptr");
    insertbytes(&instrwork[instruments*3], instruments);
  }
  if (!nofilter)
  {
    insertlabel("mt_insfiltptr");
    insertbytes(&instrwork[instruments*4], instruments);
  }
  if (!noinsvib)
  {
    insertlabel("mt_insvibparam");
    insertbytes(&instrwork[instruments*5], instruments);
    insertlabel("mt_insvibdelay");
    insertbytes(&instrwork[instruments*6], instruments);
  }
  if (!fixedparams)
  {
    insertlabel("mt_insgatetimer");
    insertbytes(&instrwork[instruments*7], instruments);
    insertlabel("mt_insfirstwave");
    insertbytes(&instrwork[instruments*8], instruments);
  }

  // Insert tables
  for (int c = 0; c < MAX_TABLES; c++)
  {
    if ((c == PTBL) && (nopulse)) goto SKIPTABLE;
    if ((c == FTBL) && (nofilter)) goto SKIPTABLE;

    // Write table left side
    // Extra zero for speedtable
    if ((c == STBL) && ((!novib) || (!nofunktempo) || (!noportamento) || (!notoneporta))) insertbyte(0);
    // Table label
    insertlabel(tableleftname[c]);

    // Table data
    for (int d = 0; d < MAX_TABLELEN; d++)
    {
      if (table[c][d+1].used)
      {
        switch (c)
        {
          // In wavetable, convert waveform values for the playroutine
          case WTBL:
          {
            unsigned char wave = song.ltable[c][d];
            if ((song.ltable[c][d] >= WAVESILENT) && (song.ltable[c][d] <= WAVELASTSILENT)) wave &= 0xf;
            if ((song.ltable[c][d] > WAVELASTDELAY) && (song.ltable[c][d] <= WAVELASTSILENT) && (!nowavedelay)) wave += 0x10;
            insertbyte(wave);
          }
          break;

          case PTBL:
          if ((simplepulse) && (song.ltable[c][d] != 0xff) && (song.ltable[c][d] > 0x80))
            insertbyte(0x80);
          else
            insertbyte(song.ltable[c][d]);
          break;

          // In filtesong.rtable, modify passband bits
          case FTBL:
          if ((song.ltable[c][d] != 0xff) && (song.ltable[c][d] > 0x80))
            insertbyte(((song.ltable[c][d] & 0x70) >> 1) | 0x80);
          else
            insertbyte(song.ltable[c][d]);
          break;

          default:
          insertbyte(song.ltable[c][d]);
          break;
        }
      }
    }

    // Write table right side, remapping jumps as necessary
    // Extra zero for speedtable
    if ((c == STBL) && ((!novib) || (!nofunktempo) || (!noportamento) || (!notoneporta))) insertbyte(0);
    // Table label
    insertlabel(tablerightname[c]);

    for (int d = 0; d < MAX_TABLELEN; d++)
    {
      if (table[c][d+1].used)
      {
        if ((song.ltable[c][d] != 0xff) || (c == STBL))
        {
          switch(c)
          {
            case WTBL:
            if ((song.ltable[c][d] >= WAVECMD) && (song.ltable[c][d] <= WAVELASTCMD))
            {
              // Remap table-referencing commands
              switch (song.ltable[c][d] - WAVECMD)
              {
                case CMD_PORTAUP:
                case CMD_PORTADOWN:
                case CMD_TONEPORTA:
                case CMD_VIBRATO:
                insertbyte(table[STBL][song.rtable[c][d]].map);
                break;

                case CMD_SETPULSEPTR:
                insertbyte(table[PTBL][song.rtable[c][d]].map);
                break;

                case CMD_SETFILTERPTR:
                insertbyte(table[FTBL][song.rtable[c][d]].map);
                break;

                default:
                insertbyte(song.rtable[c][d]);
                break;
              }
            }
            else
            {
              // For normal notes, reverse all right side high bits
              insertbyte(song.rtable[c][d] ^ 0x80);
            }
            break;

            case PTBL:
            if (simplepulse)
            {
              if (song.ltable[c][d] >= 0x80)
                insertbyte((song.ltable[c][d] & 0x0f) | (song.rtable[c][d] & 0xf0));
              else
              {
                int pulsespeed = song.rtable[c][d] >> 4;
                if (song.rtable[c][d] & 0x80)
                {
                  pulsespeed |= 0xf0;
                  pulsespeed--;
                }
                pulsespeed = swapnybbles(pulsespeed);
                insertbyte(pulsespeed);
              }
            }
            else
              insertbyte(song.rtable[c][d]);
            break;

            default:
            insertbyte(song.rtable[c][d]);
            break;
          }
        }
        else
          insertbyte(table[c][song.rtable[c][d]].map);
      }
    }

SKIPTABLE:
    {}
  }

  // Insert orderlists
  for (int c = 0; c < songs; c++)
  {
    for (int d = 0; d < maxChns; d++)
    {
      std::snprintf(textbuffer, MAX_PATHNAME, "mt_song%d", c*maxChns+d);
      insertlabel(textbuffer);
      insertbytes(&songwork[sng[c][d].offset], sng[c][d].size);
    }
  }

  // Insert patterns
  for (int c = 0; c < patterns; c++)
  {
    std::snprintf(textbuffer, MAX_PATHNAME, "mt_patt%d", c);
    insertlabel(textbuffer);
    insertbytes(&pattwork[patt[c].offset], patt[c].size);
  }
#ifndef NDEBUG
  {
    FILE *handle = std::fopen("debug.s", "wb");
    std::fwrite(buf_data(&src), buf_size(&src), 1, handle);
    std::fclose(handle);
  }
#endif
  // Assemble; on error fail in a rude way (the parser does so too)
  if (assemble(&src, &dest)) std::exit(EXIT_FAILURE);

  packeddata = (unsigned char*)buf_data(&dest);
  packedsize = buf_size(&dest);
  playersize = packedsize - songtblsize - songdatasize - patttblsize - pattdatasize - instrsize - wavetblsize - pulsetblsize - filttblsize - speedtblsize;

  // Copy author info
  if (config.playerversion & PLAYER_AUTHORINFO)
  {
    for (int c = 0; c < 32; c++)
    {
      packeddata[32+c] = song.author[c];
      // Convert 0 to space
      if (packeddata[32+c] == 0) packeddata[32+c] = 0x20;
    }
  }

  // Print results
#ifdef LTRELOC
  std::fprintf(STDOUT, "packing results:\n");
  std::fprintf(STDOUT, "Playroutine:     %d bytes\n", playersize);
  std::fprintf(STDOUT, "Songtable:       %d bytes\n", songtblsize);
  std::fprintf(STDOUT, "Song-orderlists: %d bytes\n", songdatasize);
  std::fprintf(STDOUT, "Patterntable:    %d bytes\n", patttblsize);
  std::fprintf(STDOUT, "Patterns:        %d bytes\n", pattdatasize);
  std::fprintf(STDOUT, "Instruments:     %d bytes\n", instrsize);
  std::fprintf(STDOUT, "Tables:          %d bytes\n", wavetblsize+pulsetblsize+filttblsize+speedtblsize);
  std::fprintf(STDOUT, "Total size:      %d bytes\n", packedsize);

  songhandle = std::fopen(filename, "wb");
  if (!songhandle) 
  {
      std::fprintf(STDERR, "error: could not open output file '%s'.\n", filename);
      goto PRCLEANUP;
  }

#else
  clearscreen();
  printblankc(0, 0, colors.CHEADER, MAX_COLUMNS);
  if (!std::strlen(filename))
    std::snprintf(textbuffer, MAX_PATHNAME, "%s Packer/Relocator", programname);
  else
    std::snprintf(textbuffer, MAX_PATHNAME, "%s Packer/Relocator - %s", programname, filename);
  textbuffer[80] = 0;
  printtext(0, 0, colors.CHEADER, textbuffer);

  std::snprintf(textbuffer, MAX_PATHNAME, "PACKING RESULTS:");
  printtext(1, 2, colors.CMESSAGE, textbuffer);

  std::snprintf(textbuffer, MAX_PATHNAME, "Playroutine:     %d bytes", playersize);
  printtext(1, 3, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Songtable:       %d bytes", songtblsize);
  printtext(1, 4, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Song-orderlists: %d bytes", songdatasize);
  printtext(1, 5, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Patterntable:    %d bytes", patttblsize);
  printtext(1, 6, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Patterns:        %d bytes", pattdatasize);
  printtext(1, 7, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Instruments:     %d bytes", instrsize);
  printtext(1, 8, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Tables:          %d bytes", wavetblsize+pulsetblsize+filttblsize+speedtblsize);
  printtext(1, 9, colors.CHDRFG, textbuffer);
  std::snprintf(textbuffer, MAX_PATHNAME, "Total size:      %d bytes", packedsize);
  printtext(1, 11, colors.CHDRFG, textbuffer);
  fliptoscreen();


  // Now ask for config.fileformat
  printtext(1, 13, colors.CTITLE, "SELECT FORMAT TO SAVE IN: (CURSORS=MOVE, ENTER=ACCEPT, ESC=CANCEL)");

  selectdone = 0;

  char packedfilter[MAX_FILENAME];
  while (!selectdone)
  {
    switch(config.fileformat)
    {
      case FORMAT_SID:
      printtext(1, 14, colors.CEDIT, "SID - SIDPlay music file format          ");
      std::strcpy(packedfilter, "*.sid");
      break;

      case FORMAT_PRG:
      printtext(1, 14, colors.CEDIT, "PRG - C64 native format                  ");
      std::strcpy(packedfilter, "*.prg");
      break;

      case FORMAT_BIN:
      printtext(1, 14, colors.CEDIT, "BIN - Raw binary format (no startaddress)");
      std::strcpy(packedfilter, "*.bin");
      break;
    }

    fliptoscreen();
    waitkeynoupdate();

    if (win_quitted)
    {
      exitprogram = true;
      goto PRCLEANUP;
    }

    switch(input.rawkey)
    {
      case KEY_LEFT:
      case KEY_DOWN:
      config.fileformat--;
      if (config.fileformat < FORMAT_SID) config.fileformat = FORMAT_BIN;
      break;

      case KEY_RIGHT:
      case KEY_UP:
      config.fileformat++;
      if (config.fileformat > FORMAT_BIN) config.fileformat = FORMAT_SID;
      break;

      case KEY_ESC:
      selectdone = -1;
      break;

      case KEY_ENTER:
      selectdone = 1;
      break;
    }
  }
  if (selectdone == -1) goto PRCLEANUP;

  // By default, copy loaded song name up to the extension
  char packedsongname[MAX_FILENAME];
  std::memset(packedsongname, 0, sizeof packedsongname);
  for (size_t i = 0; i < std::strlen(filename); i++)
  {
    if (filename[i] == '.') break;
    packedsongname[i] = filename[i];
  }
  switch (config.fileformat)
  {
    case FORMAT_PRG:
    std::strcat(packedsongname, ".prg");
    break;

    case FORMAT_BIN:
    std::strcat(packedsongname, ".bin");
    break;

    case FORMAT_SID:
    std::strcat(packedsongname, ".sid");
    break;
  }

  // Now ask for filename, retry if unsuccessful
  while (!songhandle)
  {
    if (!fileselector(packedsongname, packedpath, packedfilter, "Save Music+Playroutine", 3))
      goto PRCLEANUP;

    if (std::strlen(packedsongname) < MAX_FILENAME-4)
    {
      int extfound = 0;
      for (int c = (int)std::strlen(packedsongname)-1; c >= 0; c--)
      {
        if (packedsongname[c] == '.') extfound = 1;
      }
      if (!extfound)
      {
        switch (config.fileformat)
        {
          case FORMAT_PRG:
          std::strcat(packedsongname, ".prg");
          break;

          case FORMAT_BIN:
          std::strcat(packedsongname, ".bin");
          break;

          case FORMAT_SID:
          std::strcat(packedsongname, ".sid");
          break;
        }
      }
    }
    songhandle = std::fopen(packedsongname, "wb");
  }
#endif

  if (config.fileformat == FORMAT_PRG)
  {
    fwritele16(songhandle, config.playeradr);
  }
  if (config.fileformat == FORMAT_SID)
  {
    unsigned char ident[] = {'P', 'S', 'I', 'D', 0x00, 0x02, 0x00, 0x7c};
    if (dual) ident[5] = 0x03;
    unsigned char byte;
    // Identification
    std::fwrite(ident, sizeof ident, 1, songhandle);

    // Load address
    byte = 0x00;
    fwrite8(songhandle, byte);
    fwrite8(songhandle, byte);

    // Init address
    if ((config.multiplier > 1) || (!config.multiplier))
    {
      unsigned speedvalue;
      byte = (config.playeradr-10) >> 8;
      fwrite8(songhandle, byte);
      byte = (config.playeradr-10) & 0xff;
      fwrite8(songhandle, byte);

      if (config.multiplier)
      {
        if (config.ntsc) speedvalue = 0x42c6/config.multiplier;
        else speedvalue = 0x4cc7/config.multiplier;
      }
      else
      {
        if (config.ntsc) speedvalue = 0x42c6*2;
        else speedvalue = 0x4cc7*2;
      }
      speedcode[1] = speedvalue & 0xff;
      speedcode[6] = speedvalue >> 8;
    }
    else
    {
      byte = (config.playeradr) >> 8;
      fwrite8(songhandle, byte);
      byte = (config.playeradr) & 0xff;
      fwrite8(songhandle, byte);
    }

    // Play address
    byte = (config.playeradr+3) >> 8;
    fwrite8(songhandle, byte);
    byte = (config.playeradr+3) & 0xff;
    fwrite8(songhandle, byte);

    // Number of subtunes
    byte = 0x00;
    fwrite8(songhandle, byte);
    byte = songs;
    fwrite8(songhandle, byte);

    // Default subtune
    byte = 0x00;
    fwrite8(songhandle, byte);
    byte = 0x01;
    fwrite8(songhandle, byte);

    // Song speed bits
    byte = 0x00;
    if ((config.ntsc) ||
        (config.multiplier > 1) ||
        (!config.multiplier) ||
        (!dual && ciaval)) byte = 0xff;
    fwrite8(songhandle, byte);
    fwrite8(songhandle, byte);
    fwrite8(songhandle, byte);
    fwrite8(songhandle, byte);

    // Songname etc.
    std::fwrite(song.title, sizeof song.title, 1, songhandle);
    std::fwrite(song.author, sizeof song.author, 1, songhandle);
    std::fwrite(song.released, sizeof song.released, 1, songhandle);

    // Flags
    byte = 0x00;
    fwrite8(songhandle, byte);
    if (config.ntsc) byte = 8;
      else byte = 4;
    if (config.sidmodel)
        byte |= dual ? 32 + 128 : 32;
    else
        byte |= dual ? 16 + 64 : 16;
    fwrite8(songhandle, byte);

    // reserved bytes and second SID address if any
    byte = 0x00;
    fwrite8(songhandle, byte);
    fwrite8(songhandle, byte);
    if (dual)
        fwrite8(songhandle, (config.sid2address & 0x0ff0) >> 4);
    else
        fwrite8(songhandle, byte);
    fwrite8(songhandle, byte);

    // Load address
    if ((config.multiplier > 1) || (!config.multiplier))
    {
      byte = (config.playeradr-10) & 0xff;
      fwrite8(songhandle, byte);
      byte = (config.playeradr-10) >> 8;
      fwrite8(songhandle, byte);
    }
    else
    {
      byte = (config.playeradr) & 0xff;
      fwrite8(songhandle, byte);
      byte = (config.playeradr) >> 8;
      fwrite8(songhandle, byte);
    }
    if ((config.multiplier > 1) || (!config.multiplier)) std::fwrite(speedcode, 10, 1, songhandle);
  }

  std::fwrite(packeddata, packedsize, 1, songhandle);
  std::fclose(songhandle);

PRCLEANUP:
  buf_free(&src);
  buf_free(&dest);
  parse_free();
  //LOG_FREE;

  if (pattwork) delete [] pattwork;
  if (songwork) delete [] songwork;
  if (instrwork) delete [] instrwork;
  printmainscreen();
  input.clearkeys();
}

int packpattern(unsigned char *dest, unsigned char *src, int rows)
{
  unsigned char temp1[MAX_PATTROWS*4];
  unsigned char ninstr = 0;

  // First optimize instrument changes
  for (int c = 0; c < rows; c++)
  {
    if ((c) && (src[c*4+1]) && (src[c*4+1] == ninstr))
    {
      temp1[c*4] = src[c*4];
      temp1[c*4+1] = 0;
      temp1[c*4+2] = src[c*4+2];
      temp1[c*4+3] = src[c*4+3];
    }
    else
    {
      temp1[c*4] = src[c*4];
      temp1[c*4+1] = src[c*4+1];
      temp1[c*4+2] = src[c*4+2];
      temp1[c*4+3] = src[c*4+3];
      if (src[c*4+1])
        ninstr = src[c*4+1];
    }

    switch(temp1[c*4+2])
    {
      // Remap speedtable commands
      case CMD_PORTAUP:
      case CMD_PORTADOWN:
      noportamento = 0;
      temp1[c*4+3] = table[STBL][temp1[c*4+3]].map;
      break;

      case CMD_TONEPORTA:
      notoneporta = 0;
      temp1[c*4+3] = table[STBL][temp1[c*4+3]].map;
      break;

      case CMD_VIBRATO:
      novib = 0;
      temp1[c*4+3] = table[STBL][temp1[c*4+3]].map;
      break;

      case CMD_SETAD:
      nosetad = 0;
      break;

      case CMD_SETSR:
      nosetsr = 0;
      break;

      case CMD_SETWAVE:
      nosetwave = 0;
      break;

      // Remap table commands
      case CMD_SETWAVEPTR:
      nosetwaveptr = 0;
      temp1[c*4+3] = table[WTBL][temp1[c*4+3]].map;
      break;

      case CMD_SETPULSEPTR:
      nosetpulseptr = 0;
      nopulse = 0;
      temp1[c*4+3] = table[PTBL][temp1[c*4+3]].map;
      break;

      case CMD_SETFILTERPTR:
      nosetfiltptr = 0;
      nofilter = 0;
      temp1[c*4+3] = table[FTBL][temp1[c*4+3]].map;
      break;

      case CMD_SETFILTERCTRL:
      nosetfiltctrl = 0;
      nofilter = 0;
      break;

      case CMD_SETFILTERCUTOFF:
      nosetfiltcutoff = 0;
      nofilter = 0;
      break;

      case CMD_SETMASTERVOL:
      nosetmastervol = 0;
      // If no authorinfo being saved, erase timingmarks (not supported)
      if (!(config.playerversion & PLAYER_AUTHORINFO))
      {
        if (temp1[c*4+3] > 0x0f)
        {
          temp1[c*4+2] = 0;
          temp1[c*4+3] = 0;
        }
      }
      break;

      case CMD_FUNKTEMPO:
      nofunktempo = 0;
      temp1[c*4+3] = table[STBL][temp1[c*4+3]].map;
      break;

      case CMD_SETTEMPO:
      if (temp1[c*4+3] >= 0x80) nochanneltempo = 0;
      else noglobaltempo = 0;
      // Decrease databyte of all tempo commands for the playroutine
      // Do not touch funktempo
      if ((temp1[c*4+3] & 0x7f) >= 3)
        temp1[c*4+3]--;
      break;

    }
  }

  int command = -1;
  int databyte = -1;
  if (noeffects)
  {
    command = 0;
    databyte = 0;
  }

  unsigned char temp2[512];
  int destsizeim = 0;
  // Write in playroutine format
  for (int c = 0; c < rows; c++)
  {
    // Instrument change with mapping
    if (temp1[c*4+1])
    {
      temp2[destsizeim++] = instr[INSTRCHG+temp1[c*4+1]].map;
    }
    // Rest+FX
    if (temp1[c*4] == REST)
    {
      if ((temp1[c*4+2] != command) || (temp1[c*4+3] != databyte))
      {
        command = temp1[c*4+2];
        databyte = temp1[c*4+3];
        temp2[destsizeim++] = FXONLY+command;
        if (command)
          temp2[destsizeim++] = databyte;
      }
      else
        temp2[destsizeim++] = REST;
    }
    else
    {
      // Normal note
      if ((temp1[c*4+2] != command) || (temp1[c*4+3] != databyte))
      {
        command = temp1[c*4+2];
        databyte = temp1[c*4+3];
        temp2[destsizeim++] = FX+command;
        if (command)
          temp2[destsizeim++] = databyte;
      }
      temp2[destsizeim++] = temp1[c*4];
    }
  }

  int destsize = 0;
  // Final step: optimize long singlebyte rests with "packed rest"
  for (int c = 0; c < destsizeim;)
  {
    bool packok = true;

    // Never pack first row or sequencer goes crazy
    if (!c) packok = false;

    // There must be no instrument or command changes on the row to be packed
    if (temp2[c] < FX)
    {
      dest[destsize++] = temp2[c++];
      packok = false;
    }
    if ((temp2[c] >= FXONLY) && (temp2[c] < FIRSTNOTE))
    {
      int fxnum = temp2[c] - FXONLY;
      dest[destsize++] = temp2[c++];
      if (fxnum) dest[destsize++] = temp2[c++];
      packok = false;
      goto NEXTROW;
    }
    if (temp2[c] < FXONLY)
     {
      int fxnum = temp2[c] - FX;
      dest[destsize++] = temp2[c++];
      if (fxnum) dest[destsize++] = temp2[c++];
      packok = false;
    }

    if (temp2[c] != REST) packok = false;

    if (!packok)
      dest[destsize++] = temp2[c++];
    else
    {
      int d;
      for (d = c; d < destsizeim; )
      {
        if (temp2[d] == REST)
        {
          d++;
          if (d-c == 64) break;
        }
        else break;
      }
      d -= c;
      if (d > 1)
      {
        dest[destsize++] = -d;
        c += d;
      }
      else
        dest[destsize++] = temp2[c++];
    }
NEXTROW:
    {}
  }
  // See if pattern too big
  if (destsize > 256) return -1;

  // If less than 256 bytes, insert endmark
  if (destsize < 256)
  dest[destsize++] = 0x00;

  return destsize;
}
/*
int testoverlap(int area1start, int area1size, int area2start, int area2size)
{
  int area1last = area1start+area1size-1;
  int area2last = area2start+area2size-1;

  if (area1start == area2start) return 1;

  if (area1start < area2start)
  {
    if (area1last < area2start) return 0;
    else return 1;
  }
  else
  {
    if (area2last < area1start) return 0;
    else return 1;
  }
}
*/
unsigned char swapnybbles(unsigned char n)
{
  unsigned char highnybble = n >> 4;
  unsigned char lownybble = n & 0xf;

  return (lownybble << 4) | highnybble;
}

int insertfile(const char *name)
{
  int handle = io_open(name);
  if (handle == -1) return 0;

  int size = io_lseek(handle, 0, SEEK_END);
  io_lseek(handle, 0, SEEK_SET);
  while (size--)
  {
    buf_append_char(&src, io_read8(handle));
  }
  io_close(handle);
  return 1;
}

void inserttext(const char *text)
{
  int l = (int)std::strlen(text);
  buf_append(&src, text, l);
}

void insertdefine(const char *name, int value)
{
  char insertbuffer[80];

  std::snprintf(insertbuffer, 80, "%-16s = %d\n", name, value);
  inserttext(insertbuffer);
}

void insertlabel(const char *name)
{
  char insertbuffer[80];

  std::snprintf(insertbuffer, 80, "%s:\n", name);
  inserttext(insertbuffer);
}

void insertbytes(const unsigned char *bytes, int size)
{
  constexpr int MAX_BYTES_PER_ROW = 16;
  char insertbuffer[80];
  int row = 0;

  while (size--)
  {
    if (!row)
    {
      inserttext("                .BYTE (");
      std::snprintf(insertbuffer, 80, "$%02x", *bytes);
      inserttext(insertbuffer);
      bytes++;
      row++;
    }
    else
    {
      std::snprintf(insertbuffer, 80, ",$%02x", *bytes);
      inserttext(insertbuffer);
      bytes++;
      row++;
      if (row == MAX_BYTES_PER_ROW)
      {
        inserttext(")\n");
        row = 0;
      }
    }
  }
  if (row) inserttext(")\n");
}

void insertbyte(unsigned char byte)
{
  char insertbuffer[80];

  std::snprintf(insertbuffer, 80, "                .BYTE ($%02x)\n", byte);
  inserttext(insertbuffer);
}

void insertaddrlo(const char *name)
{
  char insertbuffer[80];

  std::snprintf(insertbuffer, 80, "                .BYTE (%s %% 256)\n", name);
  inserttext(insertbuffer);
}

void insertaddrhi(const char *name)
{
  char insertbuffer[80];

  std::snprintf(insertbuffer, 80, "                .BYTE (%s / 256)\n", name);
  inserttext(insertbuffer);
}

void findtableduplicates(int num)
{
  if (num == STBL)
  {
    for (int c = 1; c <= MAX_TABLELEN; c++)
    {
      if (table[num][c].used)
      {
        for (int d = c+1; d <= MAX_TABLELEN; d++)
        {
          if (table[num][d].used)
          {
            if ((song.ltable[num][d-1] == song.ltable[num][c-1]) && (song.rtable[num][d-1] == song.rtable[num][c-1]))
            {
              // Duplicate found, remove and map to the original
              table[num][d].used = false;
              for (int e = d; e <= MAX_TABLELEN; e++)
                if (table[num][e].used) table[num][e].map--;
              table[num][d].map = table[num][c].map;
            }
          }
        }
      }
    }
  }
  else
  {
    for (int c = 1; c <= MAX_TABLELEN; c++)
    {
      if (isusedandselfcontained(num, c))
      {
        for (int d = c + song.gettablepartlen(num, c - 1); d <= MAX_TABLELEN; )
        {
          int len = song.gettablepartlen(num, d - 1);

          if (isusedandselfcontained(num, d))
          {
            int e = 0;
            for (; e < len; e++)
            {
              if (e < len-1)
              {
                // Is table data the same?
                if ((song.ltable[num][d+e-1] != song.ltable[num][c+e-1]) || (song.rtable[num][d+e-1] != song.rtable[num][c+e-1]))
                  break;
              }
              else
              {
                // Do both parts have a jump in the end?
                if (song.ltable[num][d+e-1] != song.ltable[num][c+e-1])
                  break;
                // Do both parts end?
                if (song.rtable[num][d+e-1] == 0)
                {
                  if (song.rtable[num][c+e-1] != 0)
                    break;
                }
                else
                {
                  // Do both parts loop in the same way?
                  if ((song.rtable[num][d+e-1] - d) != (song.rtable[num][c+e-1] - c))
                    break;
                }
              }
            }
            if (e == len)
            {
              // Duplicate found, remove and map to the original
              for (e = 0; e < len; e++)
                table[num][d+e].used = 0;
              for (e = d; e < MAX_TABLELEN; e++)
                if (table[num][e].used) table[num][e].map -= len;
              for (e = 0; e < len; e++)
                table[num][d+e].map = table[num][c+e].map;
            }
          }
          d += len;
        }
      }
    }
  }
}

int isusedandselfcontained(int num, int start)
{
  int len = song.gettablepartlen(num, start - 1);
  int end = start + len - 1;

  // Don't use jumps only
  if (len == 1) return 0;

  // Check that whole table is used
  for (int c = start; c <= end; c++)
  {
    if (!table[num][c].used) return 0;
  }
  // Check for jump to outside
  if (song.rtable[num][end-1] != 0)
  {
    if ((song.rtable[num][end-1] < start) || (song.rtable[num][end-1] > end)) return 0;
  }
  // Check for jump from outside
  for (int c = 1; c < start; c++)
    if ((table[num][c].used) && (song.ltable[num][c-1] == 0xff) && (song.rtable[num][c-1] >= start) && (song.rtable[num][c-1] <= end)) return 0;
  for (int c = end+1; c <= MAX_TABLELEN; c++)
    if ((table[num][c].used) && (song.ltable[num][c-1] == 0xff) && (song.rtable[num][c-1] >= start) && (song.rtable[num][c-1] <= end)) return 0;

  // OK!
  return 1;
}

void calcspeedtest(unsigned char pos)
{
  if (!pos) 
  {
    nozerospeed = 0;
    return;
  }

  if (song.ltable[STBL][pos-1] >= 0x80) nocalculatedspeed = 0;
  else nonormalspeed = 0;
}

void exectable(int num, int ptr)
{
  // Jump error check
  if ((num != STBL) && (ptr) && (ptr <= MAX_TABLELEN))
  {
    if (song.ltable[num][ptr-1] == 0xff)
    {
      tableerror = ErrorType::JUMP;
      return;
    }
  }

  for (;;)
  {
    // Exit when table stopped
    if (!ptr) break;
    // Overflow check
    if ((num != STBL) && (ptr > MAX_TABLELEN))
    {
      tableerror = ErrorType::OVERFLOW;
      break;
    }
    // If were already here, exit
    if (table[num][ptr].used) break;
    // Mark current position used
    table[num][ptr].used = true;
    // Go to next ptr.
    if (num != STBL)
    {
      if (song.ltable[num][ptr-1] == 0xff)
      {
        ptr = song.rtable[num][ptr-1];
      }
      else ptr++;
    }
    else break;
  }
}

void optimizetable(int num)
{
  table.fill({0, false});

  for (int c = 0; c < MAX_PATT; c++)
  {
    for (int d = 0; d < getPattlen(c); d++)
    {
      if ((song.pattern[c][d*4+2] >= CMD_SETWAVEPTR) && (song.pattern[c][d*4+2] <= CMD_SETFILTERPTR))
        exectable(song.pattern[c][d*4+2] - CMD_SETWAVEPTR, song.pattern[c][d*4+3]);
      if ((song.pattern[c][d*4+2] >= CMD_PORTAUP) && (song.pattern[c][d*4+2] <= CMD_VIBRATO))
        exectable(STBL, song.pattern[c][d*4+3]);
      if (song.pattern[c][d*4+2] == CMD_FUNKTEMPO)
        exectable(STBL, song.pattern[c][d*4+3]);
    }
  }

  for (int c = 0; c < MAX_INSTR; c++)
  {
    for (int d = 0; d < MAX_TABLES; d++)
    {
      exectable(d, song.instr[c].ptr[d]);
    }
  }

  for (int c = 0; c < MAX_TABLELEN; c++)
  {
    if (table[WTBL][c+1].used)
    {
      if ((song.ltable[WTBL][c] >= WAVECMD) && (song.ltable[WTBL][c] <= WAVELASTCMD))
      {
        int d = -1;

        switch(song.ltable[WTBL][c] - WAVECMD)
        {
          case CMD_PORTAUP:
          case CMD_PORTADOWN:
          case CMD_TONEPORTA:
          case CMD_VIBRATO:
          d = STBL;
          break;

          case CMD_SETPULSEPTR:
          d = PTBL;
           break;

           case CMD_SETFILTERPTR:
           d = FTBL;
          break;
        }

        if (d != -1) exectable(d, song.rtable[WTBL][c]);
      }
    }
  }

  int c;
  for (c = MAX_TABLELEN-1; c >= 0; c--)
  {
    if ((song.ltable[num][c]) || (song.rtable[num][c])) break;
  }
  for (; c >= 0; c--)
  {
    if (!table[num][c+1].used) song.deletetable(num, c);
  }
}
