/*
 * GOATTRACKER v1.4f
 *
 * 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
 */

#ifdef __WIN32__
#include <windows.h>
#include <io.h>
#else
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>

#include "endian.h"
#include "bme.h"
#include "goattrk.h"
#include "gconsole.h"
#include "gsound.h"
#include "gsid.h"

CHN chn[MAX_CHN];
INSTR instr[MAX_INSTR];
unsigned char wavetable[MAX_INSTR][MAX_WAVELEN*2];
unsigned char songorder[MAX_SONGS][MAX_CHN][MAX_SONGLEN+2];
unsigned char pattern[MAX_PATT][MAX_PATTROWS*3+3];
unsigned char patterncopybuffer[MAX_PATTROWS*3+3];
unsigned char filtercopybuffer[4];
INSTR instrcopybuffer;
unsigned char wavecopybuffer[MAX_WAVELEN*2];
int pattlen[MAX_PATT];
int songlen[MAX_SONGS][MAX_CHN];

unsigned char filtertable[256];
unsigned char songfilename[MAX_FILENAME];
unsigned char instrfilename[MAX_FILENAME];
unsigned char songfilter[MAX_FILENAME];
unsigned char instrfilter[MAX_FILENAME];
DIRENTRY direntry[MAX_DIRFILES];

unsigned char songname[MAX_STR];
unsigned char authorname[MAX_STR];
unsigned char copyrightname[MAX_STR];

unsigned char cmdcopybuffer = 0;
unsigned char cmddatacopybuffer = 0;

unsigned adparam = 0;

int espos[MAX_CHN];
int eseditpos;
int esview;
int escolumn;
int eschn;
int esnum;
int epnum[MAX_CHN];
int eppos;
int epview;
int epcolumn;
int epchn;
int epoctave = 2;
int einum;
int efnum;
int eipos;
int eicolumn;
int ewpos;
int ewview;
int enpos;
int editmode = EDIT_PATTERN;
int recordmode = 0;
int followplay = 0;
int hexnybble = -1;
int highestusedpattern;
int highestusedinstr;
int scrrep;
int stepsize = 4;
int epmarkchn = -1;
int epmarkstart;
int epmarkend;
int patterncopyrows = 0;
int autoadvance = 0;
int defaultpatternlength = 64;
int keypreset = KEY_TRACKER;
int playerversion = 0;
unsigned zeropageadr = 0xfc;
unsigned playeradr = 0x1000;
int fileformat = FORMAT_PRG;
int hardrestart = 2;

extern unsigned char datafile[];

unsigned char freqtbllo[] = {
  0x15,0x25,0x36,0x49,0x5c,0x71,0x87,0x9f,0xb7,0xd1,0xed,0x0a,
  0x2a,0x4a,0x6d,0x92,0xb9,0xe3,0x0f,0x3e,0x6f,0xa3,0xdb,0x15,
  0x54,0x95,0xdb,0x25,0x73,0xc7,0x1e,0x7c,0xde,0x47,0xb6,0x2b,
  0xa8,0x2b,0xb7,0x4b,0xe7,0x8e,0x3d,0xf8,0xbd,0x8e,0x6c,0x57,
  0x50,0x57,0x6e,0x96,0xcf,0x1c,0x7b,0xf0,0x7b,0x1d,0xd8,0xae,
  0xa0,0xaf,0xdd,0x2d,0x9f,0x38,0xf7,0xe0,0xf6,0x3b,0xb1,0x5d,
  0x40,0x5e,0xbb,0x5a,0x3f,0x70,0xef,0xc1,0xed,0x76,0x63,0xba,
  0x80,0xbc,0x76,0xb4,0x7f,0xe0,0xde,0x83,0xda,0xed,0xc7,0xff,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

unsigned char freqtblhi[] = {
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x03,0x03,0x03,0x03,0x03,0x04,
  0x04,0x04,0x04,0x05,0x05,0x05,0x06,0x06,0x06,0x07,0x07,0x08,
  0x08,0x09,0x09,0x0a,0x0a,0x0b,0x0c,0x0c,0x0d,0x0e,0x0f,0x10,
  0x11,0x12,0x13,0x14,0x15,0x17,0x18,0x19,0x1b,0x1d,0x1e,0x20,
  0x22,0x24,0x26,0x29,0x2b,0x2e,0x30,0x33,0x36,0x3a,0x3d,0x41,
  0x45,0x49,0x4d,0x52,0x57,0x5c,0x61,0x67,0x6d,0x74,0x7b,0x82,
  0x8a,0x92,0x9b,0xa4,0xae,0xb8,0xc3,0xcf,0xdb,0xe8,0xf6,0xff,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

INSTRUCTION asmtable[] = {{0x69,2}, //ADC immediate
                {0x65,4}, //ADC zeropage
                {0x75,4}, //ADC zeropage,X
                {0x6d,3}, //ADC absolute
                {0x7d,3}, //ADC absolute,X
                {0x79,3}, //ADC absolute,Y
                {0x61,4}, //ADC indirect,X
                {0x71,4}, //ADC indirect,Y

                {0x29,2}, //AND immediate
                {0x25,4}, //AND zeropage
                {0x35,4}, //AND zeropage,X
                {0x2d,3}, //AND absolute
                {0x3d,3}, //AND absolute,X
                {0x39,3}, //AND absolute,Y
                {0x21,4}, //AND indirect,X
                {0x31,4}, //AND indirect,Y

                {0x0a,1}, //ASL accumulator
                {0x06,4}, //ASL zeropage
                {0x16,4}, //ASL zeropage,X
                {0x0e,3}, //ASL absolute
                {0x1e,3}, //ASL absolute,X
                {0x90,2}, //BCC
                {0xb0,2}, //BCS
                {0xf0,2}, //BEQ

                {0x24,4}, //BIT zeropage
                {0x2c,3}, //BIT absolute
                {0x30,2}, //BMI
                {0xd0,2}, //BNE
                {0x10,2}, //BPL
                {0x00,1}, //BRK
                {0x50,2}, //BVC
                {0x70,2}, //BVS

                {0x18,1}, //CLC
                {0xd8,1}, //CLD
                {0x58,1}, //CLI
                {0xb8,1}, //CLV
                {0xc9,2}, //CMP immediate
                {0xc5,4}, //CMP zeropage
                {0xd5,4}, //CMP zeropage,X
                {0xcd,3}, //CMP absolute

                {0xdd,3}, //CMP absolute,X
                {0xd9,3}, //CMP absolute,Y
                {0xc1,4}, //CMP indirect,X
                {0xd1,4}, //CMP indirect,Y
                {0xe0,2}, //CPX immediate
                {0xe4,4}, //CPX zeropage
                {0xec,3}, //CPX absolute
                {0xc0,2}, //CPY immediate

                {0xc4,4}, //CPY zeropage
                {0xcc,3}, //CPY absolute
                {0xc6,4}, //DEC zeropage
                {0xd6,4}, //DEC zeropage,X
                {0xce,3}, //DEC absolute
                {0xde,3}, //DEC absolute,X
                {0xca,1}, //DEX
                {0x88,1}, //DEY

                {0x49,2}, //EOR immediate
                {0x45,4}, //EOR zeropage
                {0x55,4}, //EOR zeropage,X
                {0x4d,3}, //EOR absolute
                {0x5d,3}, //EOR absolute,X
                {0x59,3}, //EOR absolute,Y
                {0x41,4}, //EOR indirect,X
                {0x51,4}, //EOR indirect,Y

                {0xe6,4}, //INC zeropage
                {0xf6,4}, //INC zeropage,X
                {0xee,3}, //INC absolute
                {0xfe,3}, //INC absolute,X
                {0xe8,1}, //INX
                {0xc8,1}, //INY
                {0x4c,3}, //JMP absolute
                {0x6c,3}, //JMP indirect

                {0x20,3}, //JSR absolute
                {0xa9,2}, //LDA immediate
                {0xa5,4}, //LDA zeropage
                {0xb5,4}, //LDA zeropage,X
                {0xad,3}, //LDA absolute
                {0xbd,3}, //LDA absolute,X
                {0xb9,3}, //LDA absolute,Y
                {0xa1,4}, //LDA indirect,X

                {0xb1,4}, //LDA indirect,Y
                {0xa2,2}, //LDX immediate
                {0xa6,4}, //LDX zeropage
                {0xb6,4}, //LDX zeropage,Y
                {0xae,3}, //LDX absolute
                {0xbe,3}, //LDX absolute,Y
                {0xa0,2}, //LDY immediate
                {0xa4,4}, //LDY zeropage

                {0xb4,4}, //LDY zeropage,Y
                {0xac,3}, //LDY absolute
                {0xbc,3}, //LDY absolute,Y
                {0x4a,1}, //LSR accumulator
                {0x46,4}, //LSR zeropage
                {0x56,4}, //LSR zeropage,X
                {0x4e,3}, //LSR absolute
                {0x5e,3}, //LSR absolute,X

                {0xea,1}, //NOP
                {0x09,2}, //ORA immediate
                {0x05,4}, //ORA zeropage
                {0x15,4}, //ORA zeropage,X
                {0x0d,3}, //ORA absolute
                {0x1d,3}, //ORA absolute,X
                {0x19,3}, //ORA absolute,Y

                {0x11,4}, //ORA indirect,Y
                {0x48,1}, //PHA
                {0x08,1}, //PHP
                {0x68,1}, //PLA
                {0x28,1}, //PLP
                {0x2a,1}, //ROL accumulator
                {0x26,4}, //ROL zeropage

                {0x36,4}, //ROL zeropage,X
                {0x2e,3}, //ROL absolute
                {0x3e,3}, //ROL absolute,X
                {0x6a,1}, //ROR accumulator
                {0x66,4}, //ROR zeropage
                {0x76,4}, //ROR zeropage,X
                {0x6e,3}, //ROR absolute
                {0x7e,3}, //ROR absolute,X

                {0x40,1}, //RTI
                {0x60,1}, //RTS
                {0xe9,2}, //SBC immediate
                {0xe5,4}, //SBC zeropage
                {0xf5,4}, //SBC zeropage,X
                {0xed,3}, //SBC absolute
                {0xfd,3}, //SBC absolute,X
                {0xf9,3}, //SBC absolute,Y

                {0xe1,4}, //SBC indirect,X
                {0xf1,4}, //SBC indirect,Y
                {0x38,1}, //SEC
                {0xf8,1}, //SED
                {0x78,1}, //SEI
                {0x85,4}, //STA zeropage
                {0x95,4}, //STA zeropage,X
                {0x8d,3}, //STA absolute

                {0x9d,3}, //STA absolute,X
                {0x99,3}, //STA absolute,Y
                {0x81,4}, //STA indirect,X
                {0x91,4}, //STA indirect,Y
                {0x86,4}, //STX zeropage
                {0x96,4}, //STX zeropage,Y
                {0x8e,3}, //STX absolute
                {0x84,4}, //STY zeropage

                {0x94,4}, //STY zeropage,X
                {0x8c,3}, //STY absolute
                {0xaa,1}, //TAX
                {0xa8,1}, //TAY
                {0xba,1}, //TSX
                {0x8a,1}, //TXA
                {0x9a,1}, //TXS
                {0x98,1}, //TYA
                {0xff,2}, //Hardrestart param.
                {0x100,0}};


unsigned char filterctrl = 0;
unsigned char filtertype = 0;
unsigned char filtercutoff = 0;
unsigned char filtercutoffadd = 0;
unsigned char filtertime = 0;
unsigned char filterstep = 0;
unsigned char songinit;
int playsongnum;
unsigned multiplier = 1;

int timemin = 0;
int timesec = 0;
int timeframe = 0;
int cursorflash = 0;
int cursorcolortable[] = {1,2,7,2};
unsigned ntsc = 0;

unsigned char notekeytbl1[] = {KEY_Z, KEY_S, KEY_X, KEY_D, KEY_C, KEY_V,
  KEY_G, KEY_B, KEY_H, KEY_N, KEY_J, KEY_M, KEY_COMMA, KEY_L, KEY_COLON};

unsigned char notekeytbl2[] = {KEY_Q, KEY_2, KEY_W, KEY_3, KEY_E, KEY_R,
  KEY_5, KEY_T, KEY_6, KEY_Y, KEY_7, KEY_U, KEY_I, KEY_9, KEY_O, KEY_0, KEY_P};

unsigned char dmckeytbl[] = {KEY_A, KEY_W, KEY_S, KEY_E, KEY_D, KEY_F,
  KEY_T, KEY_G, KEY_Y, KEY_H, KEY_U, KEY_J, KEY_K, KEY_O, KEY_L, KEY_P};

unsigned char hexkeytbl[] = {'0', '1', '2', '3', '4', '5', '6', '7',
  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

char timechar[] = {':', ' '};

char *notename[] =
 {"C-0", "C#0", "D-0", "D#0", "E-0", "F-0", "F#0", "G-0", "G#0", "A-0", "A#0", "B-0",
  "C-1", "C#1", "D-1", "D#1", "E-1", "F-1", "F#1", "G-1", "G#1", "A-1", "A#1", "B-1",
  "C-2", "C#2", "D-2", "D#2", "E-2", "F-2", "F#2", "G-2", "G#2", "A-2", "A#2", "B-2",
  "C-3", "C#3", "D-3", "D#3", "E-3", "F-3", "F#3", "G-3", "G#3", "A-3", "A#3", "B-3",
  "C-4", "C#4", "D-4", "D#4", "E-4", "F-4", "F#4", "G-4", "G#4", "A-4", "A#4", "B-4",
  "C-5", "C#5", "D-5", "D#5", "E-5", "F-5", "F#5", "G-5", "G#5", "A-5", "A#5", "B-5",
  "C-6", "C#6", "D-6", "D#6", "E-6", "F-6", "F#6", "G-6", "G#6", "A-6", "A#6", "B-6",
  "C-7", "C#7", "D-7", "D#7", "E-7", "F-7", "F#7", "G-7", "G#7", "A-7", "===", "---"};

char textbuffer[80];
char exitprogram = 0;
char *programname = "GOATTRACKER v1.4f (reSID + SDL)";

int main(int argc, char **argv);
void waitkey(void);
void waitkey2(void);
void relocator(void);
int testoverlap(int area1start, int area1size, int area2start, int area2size);
int packpattern(unsigned char *dest, unsigned char *src, int rows);
void printmainscreen(void);
void docommand(void);
void onlinehelp(void);
void orderlistcommands(void);
void patterncommands(void);
void instrumentcommands(void);
void playtestnote(int note);
void namecommands(void);
void display_update(void);
void editstring(char *buffer, int maxlength);
int fileselector(char *name, char *filter, char *title, int initialmode);
void clearsong(int cs, int cp, int ci, int cf, int cn);
void countpatternlengths(void);
void countthispattern(void);
void printpatterns(void);
void playroutine(void);
void setfilter(unsigned char filternum);
int getfilteramount(void);
unsigned char swapnybbles(unsigned char n);
void sixteenbitadd(unsigned char *adr, int value);
void sixteenbitsub(unsigned char *adr, int value);

int main(int argc, char **argv)
{
  unsigned b = DEFAULTBUFFER;
  unsigned mr = DEFAULTMIXRATE;
  unsigned m = 0;
  unsigned writer = 0;
  unsigned hardsid = 0;
  unsigned catweasel = 0;
  char filename[256];
  FILE *configfile;
  unsigned c;
  int info = 0;

  io_openlinkeddatafile(datafile);

  #ifdef __WIN32__
  GetModuleFileName(NULL, filename, 256);
  filename[strlen(filename)-3] = 'c';
  filename[strlen(filename)-2] = 'f';
  filename[strlen(filename)-1] = 'g';
  #else
  strcpy(filename, getenv("HOME"));
  strcat(filename, "/.goattrk/goattrk.cfg");
  #endif

  configfile = fopen(filename, "rt");
  if (configfile)
  {
        fscanf(configfile, "%d", &b);
        fscanf(configfile, "%d", &mr);
        fscanf(configfile, "%d", &hardsid);
        fscanf(configfile, "%d", &m);
        fscanf(configfile, "%d", &ntsc);
        fscanf(configfile, "%d", &fileformat);
        fscanf(configfile, "%d", &playeradr);
        fscanf(configfile, "%d", &zeropageadr);
        fscanf(configfile, "%d", &playerversion);
        fscanf(configfile, "%d", &keypreset);
        fscanf(configfile, "%d", &stepsize);
        fscanf(configfile, "%d", &multiplier);
        fscanf(configfile, "%d", &hardrestart);
        fscanf(configfile, "%d", &catweasel);
        fscanf(configfile, "%d", &adparam);
        fclose(configfile);
  }
  if (!stepsize) stepsize = 4;

  if (!initscreen())
  {
    return 1;
  }

  /* Scan command line */
  for (c = 1; c < (unsigned)argc; c++)
  {
    if ((argv[c][0] == '-') || (argv[c][0] == '/'))
    {
      int y=0;
      switch(toupper(argv[c][1]))
      {
        case '?':
        printtext(0,y++,15,"Usage: GOATTRK [options]");
        printtext(0,y++,15,"Options:");
        printtext(0,y++,15,"/Axx Set AD parameter for hardrestart in hex. DEFAULT=00");
        printtext(0,y++,15,"/Bxx Set sound buffer length in milliseconds DEFAULT=200");
        printtext(0,y++,15,"/CXX Use CatWeasel MK3 PCI SID (0 = off, 1 = on)");
        printtext(0,y++,15,"/Exx Set emulated SID model (0 = 6581 1 = 8580) DEFAULT=6581");
        printtext(0,y++,15,"/Hxx Use HardSID (0 = off, 1 = HardSID ID0 2 = HardSID ID1 etc.)");
        printtext(0,y++,15,"/Kxx Note-entry mode (0 = PROTRK. 1 = DMC) DEFAULT=PROTRK.");
        printtext(0,y++,15,"/Mxx Set sound mixing rate DEFAULT=44100");
        printtext(0,y++,15,"/Rxx Set hardrestart length (1 or 2) DEFAULT=2");
        printtext(0,y++,15,"/Sxx Set speed multiplier (2 for 2x tunes, 4 for 4x tunes etc.)");
        printtext(0,y++,15,"/N   Use NTSC timing");
        printtext(0,y++,15,"/P   Use PAL timing (DEFAULT)");
        printtext(0,y++,15,"/W   Write sound output to a file SIDAUDIO.RAW");
        printtext(0,y++,15,"/?   Show this info again");
        fliptoscreen();
        kbd_waitkey();
        info = 1;
        break;

        case 'A':
        sscanf(&argv[c][2], "%x", &adparam);
        adparam &= 0xff;
        break;

        case 'S':
        sscanf(&argv[c][2], "%u", &multiplier);
        if (multiplier == 0) multiplier = 1;
        if (multiplier > 32) multiplier = 32;
        break;


        case 'B':
        sscanf(&argv[c][2], "%u", &b);
        break;

        case 'E':
        sscanf(&argv[c][2], "%u", &m);
        break;

        case 'R':
        sscanf(&argv[c][2], "%u", &hardrestart);
        break;

        case 'K':
        sscanf(&argv[c][2], "%u", &keypreset);
        if (keypreset > 2) keypreset = 0;
        break;

        case 'N':
        ntsc = 1;
        break;

        case 'P':
        ntsc = 0;
        break;

        case 'M':
        sscanf(&argv[c][2], "%u", &mr);
        break;

        case 'H':
        sscanf(&argv[c][2], "%u", &hardsid);
        break;

        case 'W':
        writer = 1;
        break;

        case 'C':
        sscanf(&argv[c][2], "%u", &catweasel);
	break;
      }
    }
    if (info) return 0;
  }

  if (hardrestart < 1) hardrestart = 1;
  if (hardrestart > 2) hardrestart = 2;

  if (!sound_init(b, mr, writer, hardsid, m, ntsc, multiplier, catweasel))
  {
    printtextc(11,15,"Sound init failed.");
    printtextc(12,15,"Check that soundcard drivers are installed.");
    fliptoscreen();
    kbd_waitkey();
    return 1;
  }
  atexit(sound_uninit);

  memset(songfilename, 0, sizeof songfilename);
  memset(instrfilename, 0, sizeof instrfilename);
  strcpy(songfilter, "*.sng");
  strcpy(instrfilter, "*.ins");
  memset(chn, 0, sizeof chn);
  clearsong(1,1,1,1,1);
  printmainscreen();

  while (!exitprogram)
  {
    waitkey();
    docommand();
  }

  #ifndef __WIN32__
  strcpy(filename, getenv("HOME"));
  strcat(filename, "/.goattrk");
  mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR);
  strcat(filename, "/goattrk.cfg");
  #endif

  /* Store configuration to config file */
  configfile = fopen(filename, "wt");
  if (configfile)
  {
    fprintf(configfile, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
    b,
    mr,
    hardsid,
    m,
    ntsc,
    fileformat,
    playeradr,
    zeropageadr,
    playerversion,
    keypreset,
    stepsize,
    multiplier,
    hardrestart,
    catweasel,
    adparam);
    fclose(configfile);
  }

  return 0;
}

void waitkey(void)
{
  for (;;)
  {
    display_update();
    getkey();
    if ((rawkey) || (key)) break;
    if (win_quitted) break;
  }
}

void waitkey2(void)
{
  for (;;)
  {
    getkey();
    if ((rawkey) || (key)) break;
    if (win_quitted) break;
  }
}

void display_update(void)
{
  if (cursorflashdelay >= 5)
  {
    cursorflashdelay %= 5;
    cursorflash++;
    cursorflash &= 3;
    printpatterns();
    fliptoscreen();
  }
  else
  {
    if (rawkey)
    {
      printpatterns();
      fliptoscreen();
    }
  }
}

void docommand(void)
{
  int c;

  hexnybble = -1;
  for (c = 0; c < 16; c++)
  {
    if (tolower(key) == hexkeytbl[c])
    {
      if (c >= 10)
      {
        if (!shiftpressed) hexnybble = c;
      }
      else
      {
        hexnybble = c;
      }
    }
  }

  /* Mode-specific commands */
  switch(editmode)
  {
    case EDIT_ORDERLIST:
    orderlistcommands();
    break;

    case EDIT_INSTRUMENT:
    instrumentcommands();
    break;

    case EDIT_PATTERN:
    patterncommands();
    break;

    case EDIT_NAMES:
    namecommands();
    break;
  }

  /* General commands */
  switch(key)
  {
    case '-':
    if ((eipos < 7) || (editmode != EDIT_INSTRUMENT))
    {
      if ((editmode != EDIT_NAMES) && (editmode != EDIT_ORDERLIST))
      {
        einum--;
        if (einum < 0) einum = 0;
        ewpos = 0;
        ewview = 0;
      }
    }
    if ((eipos > 7) && (editmode == EDIT_INSTRUMENT))
    {
      efnum--;
      efnum &= 0x3f;
    }
    break;

    case '+':
    if ((eipos < 7) || (editmode != EDIT_INSTRUMENT))
    {
      if ((editmode != EDIT_NAMES) && (editmode != EDIT_ORDERLIST))
      {
        einum++;
        if (einum >= MAX_INSTR) einum = MAX_INSTR - 1;
        ewpos = 0;
        ewview = 0;
      }
    }
    if ((eipos > 7) && (editmode == EDIT_INSTRUMENT))
    {
      efnum++;
      efnum &= 0x3f;
    }
    break;
  }
  if (win_quitted) exitprogram = 1;
  switch(rawkey)
  {
    case KEY_KPMULTIPLY:
    if ((eipos != 7) || (editmode != EDIT_INSTRUMENT))
    {
      if (editmode != EDIT_NAMES)
      {
        if (epoctave < 7) epoctave++;
      }
    }
    break;

    case KEY_KPDIVIDE:
    if ((eipos != 7) || (editmode != EDIT_INSTRUMENT))
    {
      if (editmode != EDIT_NAMES)
      {
        if (epoctave > 0) epoctave--;
      }
    }
    break;

    case KEY_ESC:
    if (!shiftpressed)
    {
      printtextc(24, 15, "Really Quit (y/n)?");
      waitkey();
      printblank(20, 24, 40);
      if ((key == 'y') || (key == 'Y')) exitprogram = 1;
      break;
    }
    else
    {
      int cs = 0;
      int cp = 0;
      int ci = 0;
      int cf = 0;
      int cn = 0;

      printtextc(24, 15, "Clear orderlists (y/n)?");
      waitkey();
      printblank(20, 24, 40);
      if ((key == 'y') || (key == 'Y')) cs = 1;

      printtextc(24, 15, "Clear patterns (y/n)?");
      waitkey();
      printblank(20, 24, 40);
      if ((key == 'y') || (key == 'Y')) cp = 1;

      printtextc(24, 15, "Clear instruments (y/n)?");
      waitkey();
      printblank(20, 24, 40);
      if ((key == 'y') || (key == 'Y')) ci = 1;

      printtextc(24, 15, "Clear filtertable (y/n)?");
      waitkey();
      printblank(20, 24, 40);
      if ((key == 'y') || (key == 'Y')) cf = 1;

      printtextc(24, 15, "Clear songname (y/n)?");
      waitkey();
      printblank(20, 24, 40);
      if ((key == 'y') || (key == 'Y')) cn = 1;

      if (cp == 1)
      {
        int selectdone = 0;
        int olddpl = defaultpatternlength;

        printtext(30, 24, 15,"Pattern length:");
        while (!selectdone)
        {
          sprintf(textbuffer, "%02d", defaultpatternlength);
          printtext(45, 24, 15, textbuffer);
          waitkey();
          switch(rawkey)
          {
            case KEY_LEFT:
            defaultpatternlength -= 3;
            case KEY_DOWN:
            defaultpatternlength--;
            if (defaultpatternlength < 1) defaultpatternlength = 1;
            break;

            case KEY_RIGHT:
            defaultpatternlength += 3;
            case KEY_UP:
            defaultpatternlength++;
            if (defaultpatternlength > 80) defaultpatternlength = 80;
            break;

            case KEY_ESC:
            defaultpatternlength = olddpl;
            selectdone = 1;
            break;

            case KEY_ENTER:
            selectdone = 1;
            break;
          }
        }
        printblank(20, 24, 40);
      }

      clearsong(cs, cp, ci, cf, cn);
      break;
    }


    case KEY_F12:
    onlinehelp();
    printmainscreen();
    break;

    case KEY_F1:
    songinit = 0x01;
    if (shiftpressed)
      followplay = 1;
    else
      followplay = 0;
    break;

    case KEY_F2:
    songinit = 0x02;
    if (shiftpressed)
      followplay = 1;
    else
      followplay = 0;
    break;

    case KEY_F3:
    songinit = 0x03;
    if (shiftpressed)
      followplay = 1;
    else
      followplay = 0;
    break;

    case KEY_F4:
    if (shiftpressed)
      chn[epchn].mute ^= 1;
    else
    songinit = 0x04;
    break;

    case KEY_F5:
    editmode = EDIT_PATTERN;
    break;

    case KEY_F6:
    editmode = EDIT_ORDERLIST;
    break;

    case KEY_F7:
    editmode = EDIT_INSTRUMENT;
    break;

    case KEY_F8:
    editmode = EDIT_NAMES;
    break;

    case KEY_F9:
    relocator();
    break;

    case KEY_F10:
    if (editmode != EDIT_INSTRUMENT)
    {
      if (fileselector(songfilename, songfilter, "LOAD SONG", 0))
      {
        char ident[4];
        FILE *handle;

        handle = fopen(songfilename, "rb");
        if (handle)
        {
          fread(ident, 4, 1, handle);
          if (!memcmp(ident, "GTS!", 4))
          {
            int d;
            unsigned char length;
            unsigned char amount;
            int loadbytes;
            clearsong(1,1,1,1,1);

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

            /* Read songorderlists */
            fread(&amount, sizeof amount, 1, handle);
            for (d = 0; d < amount; d++)
            {
              for (c = 0; c < MAX_CHN; c++)
              {
                fread(&length, sizeof length, 1, handle);
                loadbytes = length;
                loadbytes++;
                fread(songorder[d][c], loadbytes, 1, handle);
                /* Convert the old endmark */
                if (songorder[d][c][length] == 0xfe)
                  songorder[d][c][length] = LOOPSONG;
              }
            }
            /* Read instruments */
            for (c = 1; c < MAX_INSTR; c++)
            {
  	      instr[c].ad = fread8(handle);
  	      instr[c].sr = fread8(handle);
	      instr[c].pulse = fread8(handle);
	      instr[c].pulseadd = fread8(handle);
	      instr[c].pulselimitlow = fread8(handle);
              instr[c].pulselimithigh = fread8(handle);
              instr[c].filtertype = fread8(handle);
              instr[c].wavetableindex = fread8(handle);
              fread(&instr[c].name, MAX_INSTRNAMELEN, 1, handle);
              loadbytes = instr[c].wavetableindex;
              instr[c].wavetableindex = 1;
              fread(wavetable[c], loadbytes, 1, handle);
            }
            /* Read patterns */
            fread(&amount, sizeof amount, 1, handle);
            for (c = 0; c < amount; c++)
            {
              fread(&length, sizeof length, 1, handle);
              fread(pattern[c], length, 1, handle);
            }
            countpatternlengths();
            for (c = 0; c < MAX_CHN; c++)
            {
              espos[c] = 0;
              if (songlen[esnum][c])
              {
                eppos = 0;
                epview = - VISIBLEPATTROWS / 2;
                if (songorder[esnum][c][0] < MAX_PATT)
                  epnum[c] = songorder[esnum][c][0];
                else
                {
                  if (songorder[esnum][c][1] < MAX_PATT)
                    epnum[c] = songorder[esnum][c][1];
                  else
                    epnum[c] = c;
                }
              }
            }
            /* Read filtertable */
            fread(filtertable, 256, 1, handle);
          }
          fclose(handle);
        }
      }
    }
    else
    {
      if (einum)
      {
        if (fileselector(instrfilename, instrfilter, "LOAD INSTRUMENT", 0))
        {
          char ident[4];
          FILE *handle;

          handle = fopen(instrfilename, "rb");
          if (handle)
          {
            fread(ident, 4, 1, handle);
            if (!memcmp(ident, "GTI!", 4))
            {
              int loadbytes;

  	      instr[einum].ad = fread8(handle);
  	      instr[einum].sr = fread8(handle);
	      instr[einum].pulse = fread8(handle);
	      instr[einum].pulseadd = fread8(handle);
	      instr[einum].pulselimitlow = fread8(handle);
              instr[einum].pulselimithigh = fread8(handle);
              instr[einum].filtertype = fread8(handle);
              instr[einum].wavetableindex = fread8(handle);
              fread(&instr[einum].name, MAX_INSTRNAMELEN, 1, handle);
              loadbytes = instr[einum].wavetableindex;
              instr[einum].wavetableindex = 1;
              memset(wavetable[einum], 0, MAX_WAVELEN*2);
              fread(wavetable[einum], loadbytes, 1, handle);
              if (instr[einum].filtertype)
              {
                char filtertemp[4];
                int filternum;
                instr[einum].filtertype = 0;
                fread(filtertemp, 4, 1, handle);
                /* Search for same filter-entry */
                for (c = 1; c < MAX_FILT; c++)
                {
                  if (!memcmp(filtertemp, &filtertable[c*4], 4))
                  {
                    instr[einum].filtertype = c;
                    efnum = c;
                    goto FILTERDONE;
                  }
                }
                filternum = getfilteramount();
                if (filternum < MAX_FILT)
                {
                  memcpy(&filtertable[filternum*4], filtertemp, 4);
                  efnum = filternum;
                  instr[einum].filtertype = filternum;
                }
	      FILTERDONE: ;
              }
            }
            fclose(handle);
          }
        }
      }
    }
    break;

    case KEY_F11:
    if (editmode != EDIT_INSTRUMENT)
    {
      if (fileselector(songfilename, songfilter, "SAVE SONG", 2))
      {
        char ident[] = {'G', 'T', 'S', '!'};
        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;
          unsigned char length;
          unsigned char amount;
          int writebytes;
          fwrite(ident, 4, 1, handle);

          countpatternlengths();

          /* 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;

          fwrite(&amount, sizeof amount, 1, handle);
          /* Write songorderlists */
          for (d = 0; d < amount; d++)
          {
            for (c = 0; c < MAX_CHN; c++)
            {
              length = songlen[d][c]+1;
              fwrite(&length, sizeof length, 1, handle);
              writebytes = length;
              writebytes++;
              fwrite(songorder[d][c], writebytes, 1, handle);
            }
          }
          /* Write instruments */
          for (c = 1; c < MAX_INSTR; c++)
          {
            instr[c].wavetableindex = 2;
            for (d = 0; d < MAX_WAVELEN*2; d+= 2)
            {
              if (wavetable[c][d] == 0xff) break;
              instr[c].wavetableindex += 2;
            }
  	    fwrite8(handle, instr[c].ad);
  	    fwrite8(handle, instr[c].sr);
	    fwrite8(handle, instr[c].pulse);
	    fwrite8(handle, instr[c].pulseadd);
	    fwrite8(handle, instr[c].pulselimitlow);
            fwrite8(handle, instr[c].pulselimithigh);
            fwrite8(handle, instr[c].filtertype);
            fwrite8(handle, instr[c].wavetableindex);
            fwrite(&instr[c].name, MAX_INSTRNAMELEN, 1, handle);
            fwrite(wavetable[c], instr[c].wavetableindex, 1, handle);
            instr[c].wavetableindex = 1;
          }
          /* Write patterns */
          amount = highestusedpattern + 1;
          fwrite(&amount, sizeof amount, 1, handle);
          for (c = 0; c < amount; c++)
          {
            length = pattlen[c]*3+3;
            fwrite(&length, sizeof length, 1, handle);
            fwrite(pattern[c], length, 1, handle);
          }
          /* Write filtertable */
          fwrite(filtertable, 256, 1, handle);
          fclose(handle);
        }
      }
    }
    else
    {
      if (einum)
      {
        if (fileselector(instrfilename, instrfilter, "SAVE INSTRUMENT", 2))
        {
          char ident[] = {'G', 'T', 'I', '!'};
          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)
          {
            int d;
            fwrite(ident, 4, 1, handle);

            /* Write instrument */
            instr[einum].wavetableindex = 2;
            for (d = 0; d < MAX_WAVELEN*2; d+= 2)
            {
              if (wavetable[einum][d] == 0xff) break;
              instr[einum].wavetableindex += 2;
            }
  	    fwrite8(handle, instr[einum].ad);
  	    fwrite8(handle, instr[einum].sr);
	    fwrite8(handle, instr[einum].pulse);
	    fwrite8(handle, instr[einum].pulseadd);
	    fwrite8(handle, instr[einum].pulselimitlow);
            fwrite8(handle, instr[einum].pulselimithigh);
            fwrite8(handle, instr[einum].filtertype);
            fwrite8(handle, instr[einum].wavetableindex);
            fwrite(&instr[einum].name, MAX_INSTRNAMELEN, 1, handle);
            fwrite(wavetable[einum], instr[einum].wavetableindex, 1, handle);
            instr[einum].wavetableindex = 1;
            if (instr[einum].filtertype)
            {
              fwrite(&filtertable[instr[einum].filtertype*4], 4, 1, handle);
            }
            fclose(handle);
          }
        }
      }
    }
    break;
  }
}

void onlinehelp(void)
{
  int hv = -1;
  int c;

  for (;;)
  {
    clearscreen();
    printtext(0, hv+2, 15, "GENERAL KEYS");
    printtext(0, hv+3, 7,  "TAB Cycle between editing modes");
    printtext(0, hv+4, 7,  "F1  Play from beginning    (w/ SHIFT = ");
    printtext(0, hv+5, 7,  "F2  Play from current pos. follow play)");
    printtext(0, hv+6, 7,  "F3  Play current pattern               ");
    printtext(0, hv+7, 7,  "F4  Stop playing (w/ SHIFT = mute chn.)");
    printtext(0, hv+8, 7, "F5  Go to pattern editor");
    printtext(0, hv+9, 7, "F6  Go to orderlist editor");
    printtext(0, hv+10, 7, "F7  Go to instrument editor");
    printtext(0, hv+11, 7, "F8  Go to songname editor");
    printtext(0, hv+12, 7, "F9  Pack, relocate & save PRG,SID etc.");
    printtext(0, hv+13, 7, "F10 Load song/instrument");
    printtext(0, hv+14, 7, "F11 Save song/instrument");
    printtext(0, hv+15, 7, "F12 This screen");
    printtext(0, hv+16,  7, "INS Insert row (Press on endmark");
    printtext(0, hv+17,  7, "DEL Delete row to change pat.len)");
    printtext(0, hv+18, 7, "SHIFT+ESC Clear music/Set pat.length");
    printtext(0, hv+19, 7, "ESC Exit program");
    printtext(40, hv+2, 15, "PATTERN EDIT MODE");
    printtext(40, hv+3,  7, "Enter notes like on piano (PT or DMC)");
    printtext(40, hv+4,  7, "0-9 & A-F to enter commands");
    printtext(40, hv+5,  7, "SPC Switch between jam/editmode");
    printtext(40, hv+6,  7, "RET (or CAPSLOCK) Insert keyoff");
    printtext(40, hv+7,  7, "BKS Insert rest");
    printtext(40, hv+8,  7, "- + Select instrument");
    printtext(40, hv+9,  7, "/ * Select octave");
    printtext(40, hv+10,  7, "< > Select pattern");
    printtext(40, hv+11,  7, "SHIFT+Q Transpose halfstep up");
    printtext(40, hv+12,  7, "SHIFT+A Transpose halfstep down");
    printtext(40, hv+13,  7, "SHIFT+CRSR Mark pattern");
    printtext(40, hv+14,  7, "SHIFT+L Mark/unmark whole pattern");
    printtext(40, hv+15,  7, "SHIFT+M,N Choose highlighting step");
    printtext(40, hv+16,  7, "SHIFT+X,C,V Cut,copy,paste pattern");
    printtext(40, hv+17,  7, "SHIFT+E,R Copy,paste effects");
    printtext(40, hv+18,  7, "SHIFT+Z Cycle autoadvance-mode");
    printtext(40, hv+20, 15, "SONG EDIT MODE");
    printtext(40, hv+21,  7, "0-9 & A-F to enter pattern numbers");
    printtext(40, hv+22,  7, "SPC Set start position for F2 key");
    printtext(40, hv+23,  7, "RET Go to pattern (With SHIFT=all chns.)");
    printtext(40, hv+24,  7, "< > Select subtune");
    printtext(40, hv+25,  7, "- + Insert transpose down/up command");
    printtext(40, hv+26,  7, "SHIFT+R Insert repeat command");
    printtext(0, hv+21, 15, "INSTRUMENT EDIT MODE");
    printtext(0, hv+22, 7, "0-9 & A-F to enter parameters");
    printtext(0, hv+23, 7, "SPC Play test note");
    printtext(0, hv+24, 7, "RET Silence test note");
    printtext(0, hv+25, 7, "- + Select instrument/filter");
    printtext(0, hv+26, 7, "/ * Select octave");
    printtext(0, hv+27, 7, "SHIFT+H Toggle hardrestart");
    printtext(0, hv+28, 7, "SHIFT+N Edit instrument name");
    printtext(0, hv+29, 7, "SHIFT+X,C,V Cut,copy,paste inst./filt.");
    printtext(40, hv+28, 15,"SONGNAME EDIT MODE");
    printtext(40, hv+29, 7,"Use cursor UP/DOWN to move between");
    printtext(40, hv+30, 7,"song, author & copyright strings");


    printtext(0, hv+32, 15,"PATTERN COMMANDS");

    printtext(0, hv+34, 7,"Command 0XY: Arpeggio. Arpeggiates with the root note, then root note + X      ");
    printtext(0, hv+35, 7,"             halftones and then root note + Y halftones. If the X value        ");
    printtext(0, hv+36, 7,"             is 8 or higher, the arpeggio will be at half speed (8 will        ");
    printtext(0, hv+37, 7,"             be subtracted from the value of X to get the amount of halftones) ");
    printtext(0, hv+38, 7,"                                                                               ");
    printtext(0, hv+39, 7,"Command 1XY: Portamento up. Raises the pitch with (XY) * 4 each tick.          ");
    printtext(0, hv+40, 7,"                                                                               ");
    printtext(0, hv+41, 7,"Command 2XY: Portamento down. Lowers the pitch with (XY) * 4 each tick.        ");
    printtext(0, hv+42, 7,"                                                                               ");
    printtext(0, hv+43, 7,"Command 3XY: Toneportamento. Raises or lowers pitch until target note has been ");
    printtext(0, hv+44, 7,"             reached. Speed 00 achieves a ""tie note"" effect. From version 1.3  ");
    printtext(0, hv+45, 7,"             onwards direction is determined automatically and the amount of   ");
    printtext(0, hv+46, 7,"             pitch change on each tick is XY * 4.                              ");
    printtext(0, hv+47, 7,"                                                                               ");
    printtext(0, hv+48, 7,"Command 4XY: Vibrato. X determines how fast the direction will change and      ");
    printtext(0, hv+49, 7,"             Y*16+X is the amount of pitch change on each tick. (That weird    ");
    printtext(0, hv+50, 7,"             formula is just caused by playroutine optimizations)              ");
    printtext(0, hv+51, 7,"                                                                               ");
    printtext(0, hv+52, 7,"Command 5XY: Set filter parameters. XY is the filter number (00-3F) to use.    ");
    printtext(0, hv+53, 7,"             Note that upon song start, filter number 00 is always activated.  ");
    printtext(0, hv+54, 7,"                                                                               ");
    printtext(0, hv+55, 7,"Command 6XY: Set sustain/release. Sets the channel's sustain/release register  ");
    printtext(0, hv+56, 7,"             to XY.                                                            ");
    printtext(0, hv+57, 7,"                                                                               ");
    printtext(0, hv+58, 7,"Command 7XY: Set tempo. If highest bit is 1 (values 80-FF) the tempo           ");
    printtext(0, hv+59, 7,"             (XY and 7F) will be set to current channel only, otherwise on     ");
    printtext(0, hv+60, 7,"             all channels. Tempo 0 will mark funktempo (see manual)            ");
    printtext(0, hv+61, 7,"                                                                               ");
    printtext(0, hv+62, 7,"             If there's both a ""current channel"" and ""all channels"" tempo      ");
    printtext(0, hv+63, 7,"             change on a same row, the ""all channels"" tempo change must be     ");
    printtext(0, hv+64, 7,"             on the lesser channel number or else the ""current channel"" tempo  ");
    printtext(0, hv+65, 7,"             setting will be overrided.                                        ");
    printtext(0, hv+66, 7,"                                                                               ");
    printtext(0, hv+67, 7,"             Playroutines 1 & 3 use values C0-FF for timing mark purposes,     ");
    printtext(0, hv+68, 7,"             so they won't affect the tempo. When packing a song for           ");
    printtext(0, hv+69, 7,"             playroutine 2 (game-playroutine) the timing marks will be removed ");
    printtext(0, hv+70, 7,"             from the output data, because that playroutine wouldn't understand");
    printtext(0, hv+71, 7,"             them.                                                             ");

    printtext(0, hv+73, 15,"INSTRUMENT PARAMETERS");

    printtext(0, hv+75, 7,"Attack/Decay          0 is fastest attack or decay, F is slowest               ");
    printtext(0, hv+76, 7,"                                                                               ");
    printtext(0, hv+77, 7,"Sustain/Release       Sustain level 0 is silent and F is the loudest. Release  ");
    printtext(0, hv+78, 7,"                      is a speed like attack & decay.                          ");
    printtext(0, hv+79, 7,"                                                                               ");
    printtext(0, hv+80, 7,"Pulse                 The starting value of pulsewidth (00x-FEx, where x = 0)  ");
    printtext(0, hv+81, 7,"                      If this is 0 the current pulsewidth and pulsewidth       ");
    printtext(0, hv+82, 7,"                      direction are left untouched. Note that bit 0 can't      ");
    printtext(0, hv+83, 7,"                      be used because it is reserved for the hard-restart      ");
    printtext(0, hv+84, 7,"                      indicator.                                               ");
    printtext(0, hv+85, 7,"                                                                               ");
    printtext(0, hv+86, 7,"Pulse Speed           The value that will be added to pulsewidth each tick     ");
    printtext(0, hv+87, 7,"                      (x00-xFE, where x = 0)                                   ");
    printtext(0, hv+88, 7,"                                                                               ");
    printtext(0, hv+89, 7,"Pulse Limit Low       The pulsewidths where pulsewidth modulation will change  ");
    printtext(0, hv+90, 7,"Pulse Limit High      its direction. The low nybble will always be 0 because of");
    printtext(0, hv+91, 7,"                      optimizations.                                           ");
    printtext(0, hv+92, 7,"                                                                               ");
    printtext(0, hv+93, 7,"                      If both limits are 0 the pulse will always be going in   ");
    printtext(0, hv+94, 7,"                      decreasing direction.                                    ");
    printtext(0, hv+95, 7,"                                                                               ");
    printtext(0, hv+96, 7,"Filter To Use         The filter number which this instrument will use. 00     ");
    printtext(0, hv+97, 7,"                      means ""leave filter settings unchanged"" so instruments   ");
    printtext(0, hv+98, 7,"                      can use only filter numbers 01-3F.                       ");
    printtext(0, hv+99, 7,"                                                                               ");
    printtext(0, hv+100, 7,"                      Note: because there's only one filter, it's a good idea  ");
    printtext(0, hv+101, 7,"                      to have filter-controlling instruments only on one       ");
    printtext(0, hv+102, 7,"                      channel at a time.                                       ");

    printtext(0, hv+104, 15,"WAVE/NOTE TABLES");

    printtext(0, hv+106, 7,"Waveform bytes in the wavetable consists of these bits:                        ");
    printtext(0, hv+107, 7,"                                                                               ");
    printtext(0, hv+108, 7,"  Bitvalue 01 = Gate bit. THIS IS IMPORTANT TO GET AN AUDIBLE SOUND! Gate bit  ");
    printtext(0, hv+109, 7,"                initiates the attack/decay/sustain phase of a sound. When it   ");
    printtext(0, hv+110, 7,"                goes zero, the release phase begins.                           ");
    printtext(0, hv+111, 7,"  Bitvalue 02 = Synchronize bit. Will synchronize with an another channel      ");
    printtext(0, hv+112, 7,"                (Consult C64 Programmer's Reference Guide for details)         ");
    printtext(0, hv+113, 7,"  Bitvalue 04 = Ring-modulation bit. Will ring-modulate with an another channel");
    printtext(0, hv+114, 7,"                (Consult C64 Programmer's Reference Guide for details)         ");
    printtext(0, hv+115, 7,"  Bitvalue 08 = Test bit. Will silence the channel and reset the random-seed   ");
    printtext(0, hv+116, 7,"                of a channel if the noise random generator has ""locked up""     ");
    printtext(0, hv+117, 7,"                because of using noise waveform in combination with another    ");
    printtext(0, hv+118, 7,"                waveform. This value is automatically used when a song starts  ");
    printtext(0, hv+119, 7,"                playing!                                                       ");
    printtext(0, hv+120, 7,"  Bitvalue 10 = Triangle waveform                                              ");
    printtext(0, hv+121, 7,"  Bitvalue 20 = Sawtooth waveform                                              ");
    printtext(0, hv+122, 7,"  Bitvalue 40 = Pulse waveform. There must be nonzero pulsewidth on the        ");
    printtext(0, hv+123, 7,"                instrument or else this waveform will be silent!               ");
    printtext(0, hv+124, 7,"  Bitvalue 80 = Noise waveform. Don't combine with other waveforms!            ");
    printtext(0, hv+125, 7,"                                                                               ");
    printtext(0, hv+126, 7,"Note bytes consist of these values:                                            ");
    printtext(0, hv+127, 7,"                                                                               ");
    printtext(0, hv+128, 7,"  00-5F       = Relative notes. Will be added to the current note playing      ");
    printtext(0, hv+129, 7,"                on that channel to get the final pitch                         ");
    printtext(0, hv+130, 7,"  80-DF       = Absolute notes C-0 - B-7.                                      ");

    printtext(0, hv+132, 15,"FILTER PARAMETERS");
    printtext(0, hv+134, 7,"Filter Control        The leftmost nybble is the resonance (0-F) of the filter ");
    printtext(0, hv+135, 7,"                      and the rightmost nybble is the channel bitmask:         ");
    printtext(0, hv+136, 7,"                                                                               ");
    printtext(0, hv+137, 7,"                        Bitvalue 01 = Filter channel 1                         ");
    printtext(0, hv+138, 7,"                        Bitvalue 02 = Filter channel 2                         ");
    printtext(0, hv+139, 7,"                        Bitvalue 04 = Filter channel 3                         ");
    printtext(0, hv+140, 7,"                        Bitvalue 08 = Filter external input                    ");
    printtext(0, hv+141, 7,"                                                                               ");
    printtext(0, hv+142, 7,"                      The byte must be nonzero whenever you want to set new    ");
    printtext(0, hv+143, 7,"                      filter parameters and cutoff. Zero value marks cutoff    ");
    printtext(0, hv+144, 7,"                      modulation instead.                                      ");
    printtext(0, hv+145, 7,"                                                                               ");
    printtext(0, hv+146, 7,"Filter Type/Time      The leftmost nybble is a bitmask determining what        ");
    printtext(0, hv+147, 7,"                      filter(s) will be used, and rightmost is the SID master  ");
    printtext(0, hv+148, 7,"                      volume (0-F, default F)                                  ");
    printtext(0, hv+149, 7,"                                                                               ");
    printtext(0, hv+150, 7,"                        Bitvalue 01 = Low-pass filter                          ");
    printtext(0, hv+151, 7,"                        Bitvalue 02 = Band-pass filter                         ");
    printtext(0, hv+152, 7,"                        Bitvalue 04 = High-pass filter                         ");
    printtext(0, hv+153, 7,"                        Bitvalue 08 = Silence channel 3                        ");
    printtext(0, hv+154, 7,"                                                                               ");
    printtext(0, hv+155, 7,"                      If Control is zero, this marks the duration of filter    ");
    printtext(0, hv+156, 7,"                      cutoff modulation in frames instead.                     ");
    printtext(0, hv+157, 7,"                                                                               ");
    printtext(0, hv+158, 7,"Filter Freq/Spd       New cutoff frequency (00 keeps unchanged.) If Control    ");
    printtext(0, hv+159, 7,"                      is zero, marks cutoff modulation speed instead.          ");
    printtext(0, hv+160, 7,"                                                                               ");
    printtext(0, hv+161, 7,"Filter Next Step      The number of next filter step that will be executed.    ");
    printtext(0, hv+162, 7,"                      Filter loops can be constructed by pointing into a       ");
    printtext(0, hv+163, 7,"                      previous step. Use step 00 to stop filter execution.     ");
    printtext(0, hv+164, 7,"                                                                               ");
    printblank(0, 0, MAX_COLUMNS);
    printtextc(0, 15, "GOATTRACKER online help: Arrows scroll, other keys exit");
    printbg(0, 0, 1, MAX_COLUMNS);
    fliptoscreen();
    waitkey2();

    switch(rawkey)
    {
      case KEY_LEFT:
      case KEY_UP:
      hv++;
      if (hv > -1) hv = -1;
      break;

      case KEY_RIGHT:
      case KEY_DOWN:
      hv--;
      if (hv < -(164-MAX_ROWS+1)) hv = -(164-MAX_ROWS+1);
      break;

      case KEY_PGUP:
      for (c = 0; c < PGUPDNREPEAT; c++)
      {
        hv++;
        if (hv > -1) hv = -1;
      }
      break;

      case KEY_PGDN:
      for (c = 0; c < PGUPDNREPEAT; c++)
      {
        hv--;
        if (hv < -(164-MAX_ROWS+1)) hv = -(164-MAX_ROWS+1);
      }
      break;

      case KEY_HOME:
      hv = -1;
      break;

      case KEY_END:
      hv = -(164-MAX_ROWS+1);
      break;

      default:
      if (key) goto EXITHELP;
      break;
    }
  }
 EXITHELP: ;
}

void orderlistcommands(void)
{
  int c;

  if (followplay)
  {
    if (rawkey == KEY_TAB)
    {
      if (!shiftpressed) editmode++;
      else editmode--;
      editmode &= 3;
    }
    return;
  }

  if (hexnybble >= 0)
  {
    if (eseditpos != songlen[esnum][eschn])
    {
      switch(escolumn)
      {
        case 0:
        songorder[esnum][eschn][eseditpos] &= 0x0f;
        songorder[esnum][eschn][eseditpos] |= hexnybble << 4;
        if (eseditpos < songlen[esnum][eschn])
        {
          if (songorder[esnum][eschn][eseditpos] >= MAX_PATT)
            songorder[esnum][eschn][eseditpos] = MAX_PATT - 1;
        }
        else
        {
          if (songorder[esnum][eschn][eseditpos] >= MAX_SONGLEN)
            songorder[esnum][eschn][eseditpos] = MAX_SONGLEN - 1;
        }
        break;

        case 1:
        songorder[esnum][eschn][eseditpos] &= 0xf0;
        if ((songorder[esnum][eschn][eseditpos] & 0xf0) == 0xd0)
        {
          hexnybble--;
          if (hexnybble < 0) hexnybble = 0xf;
        }
        if ((songorder[esnum][eschn][eseditpos] & 0xf0) == 0xe0)
        {
          hexnybble = 16 - hexnybble;
          hexnybble &= 0xf;
        }
        songorder[esnum][eschn][eseditpos] |= hexnybble;

        if (eseditpos < songlen[esnum][eschn])
        {
          if (songorder[esnum][eschn][eseditpos] == LOOPSONG)
            songorder[esnum][eschn][eseditpos] = LOOPSONG-1;
          if (songorder[esnum][eschn][eseditpos] == TRANSDOWN)
            songorder[esnum][eschn][eseditpos] = TRANSDOWN+0x0f;
        }
        else
        {
          if (songorder[esnum][eschn][eseditpos] >= MAX_SONGLEN)
            songorder[esnum][eschn][eseditpos] = MAX_SONGLEN - 1;
        }
        break;
      }
      escolumn++;
      if (escolumn > 1)
      {
        escolumn = 0;
        if (eseditpos < (songlen[esnum][eschn]+1))
        {
          eseditpos++;
          if (eseditpos == songlen[esnum][eschn]) eseditpos++;
        }
      }
    }
  }

  switch(key)
  {
    case 'R':
    if (eseditpos < songlen[esnum][eschn])
    {
      songorder[esnum][eschn][eseditpos] = REPEAT + 0x01;
      escolumn = 1;
    }
    break;

    case '+':
    if (eseditpos < songlen[esnum][eschn])
    {
      songorder[esnum][eschn][eseditpos] = TRANSUP;
      escolumn = 1;
    }
    break;

    case '-':
    if (eseditpos < songlen[esnum][eschn])
    {
      songorder[esnum][eschn][eseditpos] = TRANSDOWN + 0x0F;
      escolumn = 1;
    }
    break;

    case '>':
    case ')':
    case ']':
    esnum++;
    if (esnum >= MAX_SONGS) esnum = MAX_SONGS - 1;
    for (c = 0; c < MAX_CHN; c++)
    {
      espos[c] = 0;
      if (songlen[esnum][c])
      {
        eppos = 0;
        epview = - VISIBLEPATTROWS / 2;
        if (songorder[esnum][c][0] < MAX_PATT)
          epnum[c] = songorder[esnum][c][0];
        else
        {
          if (songorder[esnum][c][1] < MAX_PATT)
            epnum[c] = songorder[esnum][c][1];
          else epnum[c] = c;
        }
      }
    }
    eseditpos = 0;
    if (eseditpos == songlen[esnum][eschn]) eseditpos++;
    esview = 0;
    epmarkchn = -1;
    if (!songinit) songinit = 0x04;
    break;

    case '<':
    case '(':
    case '[':
    esnum--;
    if (esnum < 0) esnum = 0;
    for (c = 0; c < MAX_CHN; c++)
    {
      espos[c] = 0;
      if (songlen[esnum][c])
      {
        eppos = 0;
        epview = - VISIBLEPATTROWS / 2;
        if (songorder[esnum][c][0] < MAX_PATT)
          epnum[c] = songorder[esnum][c][0];
        else
        {
          if (songorder[esnum][c][1] < MAX_PATT)
            epnum[c] = songorder[esnum][c][1];
          else epnum[c] = c;
        }
      }
    }
    eseditpos = 0;
    if (eseditpos == songlen[esnum][eschn]) eseditpos++;
    esview = 0;
    epmarkchn = -1;
    if (!songinit) songinit = 0x04;
    break;
  }
  switch(rawkey)
  {
    case KEY_TAB:
    if (!shiftpressed) editmode++;
    else editmode--;
    editmode &= 3;
    break;

    case KEY_SPACE:
    if (!shiftpressed)
    {
      if (eseditpos < songlen[esnum][eschn]) espos[eschn] = eseditpos;
    }
    else
    {
      if (eseditpos < songlen[esnum][0]) espos[0] = eseditpos;
      if (eseditpos < songlen[esnum][1]) espos[1] = eseditpos;
      if (eseditpos < songlen[esnum][2]) espos[2] = eseditpos;
    }
    break;

    case KEY_ENTER:
    if (eseditpos < songlen[esnum][eschn])
    {
      if (!shiftpressed)
      {
        if (songorder[esnum][eschn][eseditpos] < MAX_PATT)
          epnum[eschn] = songorder[esnum][eschn][eseditpos];
      }
      else
      {
        if (eseditpos != espos[eschn])
        {
          if ((eseditpos < songlen[esnum][0]) && (songorder[esnum][0][eseditpos] < MAX_PATT)) epnum[0] = songorder[esnum][0][eseditpos];
          if ((eseditpos < songlen[esnum][1]) && (songorder[esnum][1][eseditpos] < MAX_PATT)) epnum[1] = songorder[esnum][1][eseditpos];
          if ((eseditpos < songlen[esnum][2]) && (songorder[esnum][2][eseditpos] < MAX_PATT)) epnum[2] = songorder[esnum][2][eseditpos];
        }
        else
        {
          if ((espos[0] < songlen[esnum][0]) && (songorder[esnum][0][espos[0]] < MAX_PATT)) epnum[0] = songorder[esnum][0][espos[0]];
          if ((espos[1] < songlen[esnum][1]) && (songorder[esnum][1][espos[1]] < MAX_PATT))epnum[1] = songorder[esnum][1][espos[1]];
          if ((espos[2] < songlen[esnum][2]) && (songorder[esnum][2][espos[2]] < MAX_PATT))epnum[2] = songorder[esnum][2][espos[2]];
        }
      }
      epmarkchn = -1;
    }
    epchn = eschn;
    epcolumn = 0;
    eppos = 0;
    epview = - VISIBLEPATTROWS / 2;
    editmode = EDIT_PATTERN;
    if (epchn == epmarkchn) epmarkchn = -1;
    break;

    case KEY_DEL:
    if ((songlen[esnum][eschn] - eseditpos)-1 >= 0)
    {
      int len;
      memmove(&songorder[esnum][eschn][eseditpos],
        &songorder[esnum][eschn][eseditpos+1],
        (songlen[esnum][eschn] - eseditpos)-1);
      songorder[esnum][eschn][songlen[esnum][eschn]-1] = 0x00;
      if (songlen[esnum][eschn] > 0)
      {
        songorder[esnum][eschn][songlen[esnum][eschn]-1] =
          songorder[esnum][eschn][songlen[esnum][eschn]];
        songorder[esnum][eschn][songlen[esnum][eschn]] =
          songorder[esnum][eschn][songlen[esnum][eschn]+1];
        countthispattern();
      }
      if (eseditpos == songlen[esnum][eschn]) eseditpos++;
      len = songlen[esnum][eschn]+1;
      if ((songorder[esnum][eschn][len] > eseditpos) &&
         (songorder[esnum][eschn][len] > 0))
         songorder[esnum][eschn][len]--;
    }
    else
    {
      if (eseditpos > songlen[esnum][eschn])
      {
        if (songlen[esnum][eschn] > 0)
        {
          songorder[esnum][eschn][songlen[esnum][eschn]-1] =
            songorder[esnum][eschn][songlen[esnum][eschn]];
          songorder[esnum][eschn][songlen[esnum][eschn]] =
            songorder[esnum][eschn][songlen[esnum][eschn]+1];
          countthispattern();
          eseditpos = songlen[esnum][eschn]+1;
        }
      }
    }
    break;

    case KEY_INS:
    if ((songlen[esnum][eschn] - eseditpos)-1 >= 0)
    {
      int len;
      if (songlen[esnum][eschn] < MAX_SONGLEN)
      {
        len = songlen[esnum][eschn]+1;
        songorder[esnum][eschn][len+1] =
          songorder[esnum][eschn][len];
        songorder[esnum][eschn][len] = LOOPSONG;
        if (len) songorder[esnum][eschn][len-1] = 0x00;
        countthispattern();
      }
      memmove(&songorder[esnum][eschn][eseditpos+1],
        &songorder[esnum][eschn][eseditpos],
        (songlen[esnum][eschn] - eseditpos)-1);
      songorder[esnum][eschn][eseditpos] = 0x00;
      len = songlen[esnum][eschn]+1;
      if ((songorder[esnum][eschn][len] > eseditpos) &&
         (songorder[esnum][eschn][len] < (len-2)))
         songorder[esnum][eschn][len]++;
    }
    else
    {
      if (eseditpos > songlen[esnum][eschn])
      {
        if (songlen[esnum][eschn] < MAX_SONGLEN)
        {
          songorder[esnum][eschn][eseditpos+1] =
            songorder[esnum][eschn][eseditpos];
          songorder[esnum][eschn][eseditpos] = LOOPSONG;
          if (eseditpos) songorder[esnum][eschn][eseditpos-1] = 0x00;
          countthispattern();
          eseditpos = songlen[esnum][eschn]+1;
        }
      }
    }
    break;

    case KEY_RIGHT:
    escolumn++;
    if (escolumn > 1)
    {
      escolumn = 0;
      if (eseditpos < (songlen[esnum][eschn]+1))
      {
        eseditpos++;
        if (eseditpos == songlen[esnum][eschn]) eseditpos++;
      }
      else escolumn = 1;
    }
    break;

    case KEY_PGDN:
    for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
    {
      escolumn++;
      if (escolumn > 1)
      {
        escolumn = 0;
        if (eseditpos < (songlen[esnum][eschn]+1))
        {
          eseditpos++;
          if (eseditpos == songlen[esnum][eschn]) eseditpos++;
        }
        else escolumn = 1;
      }
    }
    break;

    case KEY_HOME:
    eseditpos = 0;
    escolumn = 0;
    if (eseditpos == songlen[esnum][eschn]) eseditpos++;
    break;

    case KEY_END:
    eseditpos = songlen[esnum][eschn]+1;
    escolumn = 0;
    break;

    case KEY_PGUP:
    for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
    {
      escolumn--;
      if (escolumn < 0)
      {
        if (eseditpos > 0)
        {
          eseditpos--;
          if (eseditpos == songlen[esnum][eschn]) eseditpos--;
          escolumn = 1;
          if (eseditpos < 0)
          {
            eseditpos = 1;
            escolumn = 0;
          }
        }
        else escolumn = 0;
      }
    }
    break;

    case KEY_LEFT:
    escolumn--;
    if (escolumn < 0)
    {
      if (eseditpos > 0)
      {
        eseditpos--;
        if (eseditpos == songlen[esnum][eschn]) eseditpos--;
        escolumn = 1;
        if (eseditpos < 0)
        {
          eseditpos = 1;
          escolumn = 0;
        }
      }
      else escolumn = 0;
    }
    break;

    case KEY_UP:
    eschn--;
    if (eschn < 0) eschn = MAX_CHN - 1;
    if ((eseditpos == songlen[esnum][eschn]) || (eseditpos > songlen[esnum][eschn]+1))
    {
      eseditpos = songlen[esnum][eschn]+1;
      escolumn = 0;
    }
    break;

    case KEY_DOWN:
    eschn++;
    if (eschn >= MAX_CHN) eschn = 0;
    if ((eseditpos == songlen[esnum][eschn]) || (eseditpos > songlen[esnum][eschn]+1))
    {
      eseditpos = songlen[esnum][eschn]+1;
      escolumn = 0;
    }
    break;
  }
  if (eseditpos - esview < 0)
  {
    esview = eseditpos;
  }
  if (eseditpos - esview >= VISIBLEORDERLIST)
  {
    esview = eseditpos - VISIBLEORDERLIST + 1;
  }
}

void instrumentcommands(void)
{
  switch(rawkey)
  {
    case KEY_TAB:
    if (!shiftpressed) editmode++;
    else editmode--;
    editmode &= 3;
    break;

    case KEY_H:
    if ((einum) && (eipos != 7))
    {
      instr[einum].pulse ^= 0x01;
    }
    break;

    case KEY_X:
    if ((einum) && (shiftpressed) && (eipos < 7))
    {
      memcpy(&instrcopybuffer, &instr[einum], sizeof(INSTR));
      memcpy(wavecopybuffer, wavetable[einum], MAX_WAVELEN*2);
      memset(&instr[einum], 0, sizeof(INSTR));
      memset(wavetable[einum], 0, MAX_WAVELEN*2);
      wavetable[einum][2] = 0xff;
    }
    if (eipos > 7)
    {
      memcpy(filtercopybuffer, &filtertable[efnum*4], 4);
      memset(&filtertable[efnum*4], 0, 4);
      filtertable[efnum*4+0] = 0x80;
      filtertable[efnum*4+1] = 0xf;
    }
    break;

    case KEY_C:
    if ((einum) && (shiftpressed) && (eipos < 7))
    {
      memcpy(&instrcopybuffer, &instr[einum], sizeof(INSTR));
      memcpy(wavecopybuffer, wavetable[einum], MAX_WAVELEN*2);
    }
    if (eipos > 7)
    {
      memcpy(filtercopybuffer, &filtertable[efnum*4], 4);
    }
    break;

    case KEY_V:
    if ((einum) && (shiftpressed) && (eipos < 7))
    {
      memcpy(&instr[einum], &instrcopybuffer, sizeof(INSTR));
      memcpy(wavetable[einum], wavecopybuffer, MAX_WAVELEN*2);
    }
    if (eipos > 7)
    {
      memcpy(&filtertable[efnum*4], filtercopybuffer, 4);
    }
    break;

    case KEY_INS:
    if ((eicolumn >= 2) && (ewpos < (MAX_WAVELEN-1)))
    {
      memmove(&wavetable[einum][ewpos*2+2],
        &wavetable[einum][ewpos*2],
        (MAX_WAVELEN-1 - ewpos)*2-2);
      wavetable[einum][ewpos*2] = 0x00;
      wavetable[einum][ewpos*2 + 1] = 0x00;
    }
    break;

    case KEY_DEL:
    if ((eicolumn >= 2) && (ewpos < (MAX_WAVELEN-1)))
    {
      memmove(&wavetable[einum][ewpos*2],
        &wavetable[einum][ewpos*2+2],
        (MAX_WAVELEN-1 - ewpos)*2-2);
      wavetable[einum][(MAX_WAVELEN-2)*2] = 0x00;
      wavetable[einum][(MAX_WAVELEN-2)*2 + 1] = 0x00;
    }
    break;

    case KEY_RIGHT:
    if (eipos < 7)
    {
      eicolumn++;
      if (eicolumn > 5) eicolumn = 0;
    }
    if (eipos > 7)
    {
      eicolumn++;
      if (eicolumn > 1)
      {
        eicolumn = 0;
        eipos ^= 2;
      }
    }
    break;

    case KEY_LEFT:
    if (eipos < 7)
    {
      eicolumn--;
      if (eicolumn < 0) eicolumn = 5;
    }
    if (eipos > 7)
    {
      eicolumn--;
      if (eicolumn < 0)
      {
        eicolumn = 1;
        eipos ^= 2;
      }
    }
    break;

    case KEY_PGDN:
    if (eipos != 7)
    {
      for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
      {
        if (eicolumn < 2)
        {
          eipos++;
          if (eipos == 7) eipos = 8;
          if (eipos == 12) eipos = 0;
        }
        else
        {
          if (ewpos < MAX_WAVELEN-1)
          {
            ewpos++;
          }
        }
      }
    }
    break;

    case KEY_DOWN:
    if (eipos != 7)
    {
      if (eicolumn < 2)
      {
        eipos++;
        if (eipos == 7) eipos = 8;
        if (eipos == 12) eipos = 0;
      }
      else
      {
        if (ewpos < MAX_WAVELEN-1)
        {
          ewpos++;
        }
      }
    }
    break;

    case KEY_PGUP:
    if (eipos != 7)
    {
      for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
      {
        if (eicolumn < 2)
        {
          eipos--;
          if (eipos < 0) eipos = 11;
          if (eipos == 7) eipos = 6;
        }
        else
        {
          if (ewpos > 0)
          {
            ewpos--;
          }
        }
      }
    }
    break;

    case KEY_UP:
    if (eipos != 7)
    {
      if (eicolumn < 2)
      {
        eipos--;
        if (eipos < 0) eipos = 11;
        if (eipos == 7) eipos = 6;
      }
      else
      {
        if (ewpos > 0)
        {
          ewpos--;
        }
      }
    }
    break;

    case KEY_N:
    if ((eipos != 7) && (shiftpressed))
    {
      eipos = 7;
      goto INSTREDIT_END;
    }
    break;

    case KEY_SPACE:
    if (eipos != 7)
    {
      playtestnote(epoctave * 12);
    }
    break;

    case KEY_ENTER:
    if ((eipos != 7) && (eipos != 6))
    {
      chn[epchn].wave &= 0xfe;
    }
    if (eipos == 7)
    {
      eipos = 0;
    }
    if (eipos == 6)
    {
      if (instr[einum].filtertype)
      {
        efnum = instr[einum].filtertype;
        eipos = 8;
      }
    }
    break;
  }
  if ((eipos == 7) && (einum)) editstring(instr[einum].name, MAX_INSTRNAMELEN);
  if ((hexnybble >= 0) && (eipos < 7) && (einum))
  {
    unsigned char *ptr = &instr[einum].ad;
    ptr += eipos;

    switch(eicolumn)
    {
      case 0:
      *ptr &= 0x0f;
      *ptr |= hexnybble << 4;
      if (eipos == 6) *ptr &= 0x3f;
      if (eipos == 3) *ptr &= 0xfe;
      if (eipos == 4) *ptr &= 0xf0;
      if (eipos == 5) *ptr &= 0xf0;
      eicolumn++;
      if (eicolumn > 1)
      {
        eicolumn = 0;
        eipos++;
        if (eipos > 6) eipos = 6;
      }
      break;

      case 1:
      if (eipos == 2)
      {
        unsigned char hr = *ptr & 0x01;
        *ptr &= 0xf0;
        *ptr |= hexnybble & 0x0e;
        *ptr |= hr;
      }
      else
      {
        *ptr &= 0xf0;
        *ptr |= hexnybble;
        if (eipos == 3) *ptr &= 0xfe;
        if (eipos == 4) *ptr &= 0xf0;
        if (eipos == 5) *ptr &= 0xf0;
        if (eipos == 6) *ptr &= 0x3f;
      }
      eicolumn++;
      if (eicolumn > 1)
      {
        eicolumn = 0;
        eipos++;
        if (eipos > 6) eipos = 6;
      }
      break;

      case 2:
      if (ewpos < MAX_WAVELEN-1)
      {
        wavetable[einum][ewpos*2] &= 0x0f;
        wavetable[einum][ewpos*2] |= hexnybble << 4;
        eicolumn++;
        if (eicolumn > 5)
        {
          eicolumn = 2;
          ewpos++;
        }
      }
      break;

      case 3:
      if (ewpos < MAX_WAVELEN-1)
      {
        wavetable[einum][ewpos*2] &= 0xf0;
        wavetable[einum][ewpos*2] |= hexnybble;
        eicolumn++;
        if (eicolumn > 5)
        {
          eicolumn = 2;
          ewpos++;
        }
      }
      break;

      case 4:
      if (ewpos < MAX_WAVELEN-1)
      {
        wavetable[einum][ewpos*2+1] &= 0x0f;
        wavetable[einum][ewpos*2+1] |= hexnybble << 4;
        eicolumn++;
        if (eicolumn > 5)
        {
          eicolumn = 2;
          ewpos++;
        }
      }
      break;

      case 5:
      if (ewpos < MAX_WAVELEN-1)
      {
        wavetable[einum][ewpos*2+1] &= 0xf0;
        wavetable[einum][ewpos*2+1] |= hexnybble;
        eicolumn++;
        if (eicolumn > 5)
        {
          eicolumn = 2;
          ewpos++;
        }
      }
      break;
    }
  }
  if ((hexnybble >= 0) && (eipos > 7))
  {
    unsigned char *ptr = &filtertable[efnum*4+(eipos-8)];

    switch(eicolumn)
    {
      case 0:
      *ptr &= 0x0f;
      *ptr |= hexnybble << 4;
      eicolumn++;
      if (eicolumn > 1)
      {
        eicolumn = 0;
        eipos++;
        if (eipos > 11) eipos = 8;
      }
      break;

      case 1:
      *ptr &= 0xf0;
      *ptr |= hexnybble;
      eicolumn++;
      if (eicolumn > 1)
      {
        eicolumn = 0;
        eipos++;
        if (eipos > 11) eipos = 8;
      }
      break;
    }
    filtertable[efnum*4+3] &= 0x3f; /* Keep Next Step within limits */
  }

  INSTREDIT_END:
  if (ewpos - ewview < 0)
  {
    ewview = ewpos;
  }
  if (ewpos - ewview >= VISIBLEWAVEROWS)
  {
    ewview = ewpos - VISIBLEWAVEROWS + 1;
  }

}

void playtestnote(int note)
{
  chn[epchn].wave &= 0xfe; /* Hard restart */
  if (!(instr[einum].pulse & 0x01))
  {
    sidreg[0x5 + epchn * 7] = adparam;
    sidreg[0x6 + epchn * 7] = 0;
  }
  if ((note == REST) || (note == KEYOFF)) return;

  chn[epchn].instrnum = einum;
  chn[epchn].newnote = 0 - multiplier*2; /* Trigger note delayed by two updates */
  chn[epchn].note = note;
}

void namecommands(void)
{
  switch(rawkey)
  {
    case KEY_TAB:
    if (!shiftpressed) editmode++;
    else editmode--;
    editmode &= 3;
    break;

    case KEY_DOWN:
    case KEY_ENTER:
    enpos++;
    if (enpos > 2) enpos = 0;
    break;

    case KEY_UP:
    enpos--;
    if (enpos < 0) enpos = 2;
    break;
  }
  switch(enpos)
  {
    case 0:
    editstring(songname, MAX_STR);
    break;

    case 1:
    editstring(authorname, MAX_STR);
    break;

    case 2:
    editstring(copyrightname, MAX_STR);
    break;
  }
}

void patterncommands(void)
{
  int c;

  if (followplay)
  {
    if (rawkey == KEY_TAB)
    {
      if (!shiftpressed) editmode++;
      else editmode--;
      editmode &= 3;
    }
    return;
  }

  switch(key)
  {
    case '<':
    case '(':
    case '[':
    if (epnum[epchn] > 0)
    {
      epnum[epchn]--;
      if (eppos > pattlen[epnum[epchn]]) eppos = pattlen[epnum[epchn]];
    }
    if (epchn == epmarkchn) epmarkchn = -1;
    break;

    case '>':
    case ')':
    case ']':
    if (epnum[epchn] < MAX_PATT - 1)
    {
      epnum[epchn]++;
      if (eppos > pattlen[epnum[epchn]]) eppos = pattlen[epnum[epchn]];
    }
    if (epchn == epmarkchn) epmarkchn = -1;
    break;
  }
  {
    int newnote = -1;
    switch (keypreset)
    {
      case KEY_TRACKER:
      for (c = 0; c < sizeof(notekeytbl1); c++)
      {
        if ((rawkey == notekeytbl1[c]) && (!epcolumn) && (!shiftpressed))
        {
          newnote = c + epoctave * 12;
        }
      }
      for (c = 0; c < sizeof(notekeytbl2); c++)
      {
        if ((rawkey == notekeytbl2[c]) && (!epcolumn) && (!shiftpressed))
        {
          newnote = c + (epoctave+1) * 12;
        }
      }
      break;

      case KEY_DMC:
      for (c = 0; c < sizeof(dmckeytbl); c++)
      {
        if ((rawkey == dmckeytbl[c]) && (!epcolumn) && (!shiftpressed))
        {
          newnote = c + epoctave * 12;
        }
      }
      break;

    }

    if (newnote >= KEYOFF) newnote = -1;
    if (rawkey == 0x08) newnote = REST;
    if (rawkey == 0x14) newnote = KEYOFF;
    if (rawkey == KEY_ENTER) newnote = KEYOFF;
    if (newnote >= 0)
    {
      if ((recordmode) && (eppos < pattlen[epnum[epchn]]))
      {
        pattern[epnum[epchn]][eppos * 3] = newnote;
        if (newnote < KEYOFF)
        {
          pattern[epnum[epchn]][eppos * 3 + 1] &= 0x07;
          pattern[epnum[epchn]][eppos * 3 + 1] |= einum << 3;
        }
        if (newnote == REST)
        {
          pattern[epnum[epchn]][eppos * 3 + 1] &= 0x07;
        }
      }
      if (recordmode)
      {
        if (autoadvance < 2)
        {
          eppos++;
          if (eppos > pattlen[epnum[epchn]])
          {
            eppos = 0;
          }
        }
      }
      playtestnote(newnote);
    }
  }
  switch(rawkey)
  {
    case KEY_TAB:
    if (!shiftpressed) editmode++;
    else editmode--;
    editmode &= 3;
    break;

    case KEY_Z:
    if (shiftpressed)
    {
      autoadvance++;
      if (autoadvance > 2) autoadvance = 0;
      if (keypreset == KEY_TRACKER)
      {
        if (autoadvance == 1) autoadvance = 2;
      }
    }
    break;

    case KEY_A:
    if (shiftpressed)
    {
      if (epmarkchn != -1)
      {
        if (epmarkstart <= epmarkend)
        {
          for (c = epmarkstart; c <= epmarkend; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            if ((pattern[epnum[epmarkchn]][c*3] < KEYOFF) &&
                (pattern[epnum[epmarkchn]][c*3] > 0))
              pattern[epnum[epmarkchn]][c*3]--;
          }
        }
        else
        {
          for (c = epmarkend; c <= epmarkstart; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            if ((pattern[epnum[epmarkchn]][c*3] < KEYOFF) &&
                (pattern[epnum[epmarkchn]][c*3] > 0))
              pattern[epnum[epmarkchn]][c*3]--;
          }
        }
      }
      else
      {
        for (c = 0; c < pattlen[epnum[epchn]]; c++)
        {
          if ((pattern[epnum[epchn]][c*3] < KEYOFF) &&
              (pattern[epnum[epchn]][c*3] > 0))
            pattern[epnum[epchn]][c*3]--;
        }
      }
    }
    break;

    case KEY_E:
    if ((shiftpressed) && (eppos < pattlen[epnum[epchn]]))
    {
      cmdcopybuffer = pattern[epnum[epchn]][eppos*3+1] & 7;
      cmddatacopybuffer = pattern[epnum[epchn]][eppos*3+2];
    }
    break;

    case KEY_R:
    if ((shiftpressed) && (eppos < pattlen[epnum[epchn]]))
    {
      pattern[epnum[epchn]][eppos*3+1] &= 0xf8;
      pattern[epnum[epchn]][eppos*3+1] |= cmdcopybuffer;
      pattern[epnum[epchn]][eppos*3+2] = cmddatacopybuffer;
      eppos++;
    }
    break;

    case KEY_Q:
    if (shiftpressed)
    {
      if (epmarkchn != -1)
      {
        if (epmarkstart <= epmarkend)
        {
          for (c = epmarkstart; c <= epmarkend; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            if (pattern[epnum[epmarkchn]][c*3] < KEYOFF-1)
              pattern[epnum[epmarkchn]][c*3]++;
          }
        }
        else
        {
          for (c = epmarkend; c <= epmarkstart; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            if (pattern[epnum[epmarkchn]][c*3] < KEYOFF-1)
              pattern[epnum[epmarkchn]][c*3]++;
          }
        }
      }
      else
      {
        for (c = 0; c < pattlen[epnum[epchn]]; c++)
        {
          if (pattern[epnum[epchn]][c*3] < KEYOFF-1)
            pattern[epnum[epchn]][c*3]++;
        }
      }
    }
    break;

    case KEY_M:
    if (shiftpressed)
    {
      stepsize++;
      if (stepsize > MAX_PATTROWS) stepsize = MAX_PATTROWS;
    }
    break;

    case KEY_N:
    if (shiftpressed)
    {
      stepsize--;
      if (stepsize < 2) stepsize = 2;
    }
    break;

    case KEY_L:
    if (shiftpressed)
    {
      if (epmarkchn == -1)
      {
        epmarkchn = epchn;
        epmarkstart = 0;
        epmarkend = pattlen[epnum[epchn]];
      }
      else epmarkchn = -1;
    }
    break;

    case KEY_C:
    if (shiftpressed)
    {
      if (epmarkchn != -1)
      {
        if (epmarkstart <= epmarkend)
        {
          int d = 0;
          for (c = epmarkstart; c <= epmarkend; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            patterncopybuffer[d*3] = pattern[epnum[epmarkchn]][c*3];
            patterncopybuffer[d*3+1] = pattern[epnum[epmarkchn]][c*3+1];
            patterncopybuffer[d*3+2] = pattern[epnum[epmarkchn]][c*3+2];
            d++;
          }
          patterncopyrows = d;
        }
        else
        {
          int d = 0;
          for (c = epmarkend; c <= epmarkstart; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            patterncopybuffer[d*3] = pattern[epnum[epmarkchn]][c*3];
            patterncopybuffer[d*3+1] = pattern[epnum[epmarkchn]][c*3+1];
            patterncopybuffer[d*3+2] = pattern[epnum[epmarkchn]][c*3+2];
            d++;
          }
          patterncopyrows = d;
        }
        epmarkchn = -1;
      }
      else
      {
        int d = 0;
        for (c = 0; c < pattlen[epnum[epchn]]; c++)
        {
          patterncopybuffer[d*3] = pattern[epnum[epchn]][c*3];
          patterncopybuffer[d*3+1] = pattern[epnum[epchn]][c*3+1];
          patterncopybuffer[d*3+2] = pattern[epnum[epchn]][c*3+2];
          d++;
        }
        patterncopyrows = d;
      }
    }
    break;

    case KEY_X:
    if (shiftpressed)
    {
      if (epmarkchn != -1)
      {
        if (epmarkstart <= epmarkend)
        {
          int d = 0;
          for (c = epmarkstart; c <= epmarkend; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            patterncopybuffer[d*3] = pattern[epnum[epmarkchn]][c*3];
            patterncopybuffer[d*3+1] = pattern[epnum[epmarkchn]][c*3+1];
            patterncopybuffer[d*3+2] = pattern[epnum[epmarkchn]][c*3+2];
            pattern[epnum[epmarkchn]][c*3] = REST;
            pattern[epnum[epmarkchn]][c*3+1] = 0;
            pattern[epnum[epmarkchn]][c*3+2] = 0;
            d++;
          }
          patterncopyrows = d;
        }
        else
        {
          int d = 0;
          for (c = epmarkend; c <= epmarkstart; c++)
          {
            if (c >= pattlen[epnum[epmarkchn]]) break;
            patterncopybuffer[d*3] = pattern[epnum[epmarkchn]][c*3];
            patterncopybuffer[d*3+1] = pattern[epnum[epmarkchn]][c*3+1];
            patterncopybuffer[d*3+2] = pattern[epnum[epmarkchn]][c*3+2];
            pattern[epnum[epmarkchn]][c*3] = REST;
            pattern[epnum[epmarkchn]][c*3+1] = 0;
            pattern[epnum[epmarkchn]][c*3+2] = 0;
            d++;
          }
          patterncopyrows = d;
        }
        epmarkchn = -1;
      }
      else
      {
        int d = 0;
        for (c = 0; c < pattlen[epnum[epchn]]; c++)
        {
          patterncopybuffer[d*3] = pattern[epnum[epchn]][c*3];
          patterncopybuffer[d*3+1] = pattern[epnum[epchn]][c*3+1];
          patterncopybuffer[d*3+2] = pattern[epnum[epchn]][c*3+2];
          pattern[epnum[epchn]][c*3] = REST;
          pattern[epnum[epchn]][c*3+1] = 0;
          pattern[epnum[epchn]][c*3+2] = 0;
          d++;
        }
        patterncopyrows = d;
      }
    }
    break;

    case KEY_V:
    if ((shiftpressed) && (patterncopyrows))
    {
      for (c = 0; c < patterncopyrows; c++)
      {
        if (eppos >= pattlen[epnum[epchn]]) break;
        pattern[epnum[epchn]][eppos*3] = patterncopybuffer[c*3];
        pattern[epnum[epchn]][eppos*3+1] = patterncopybuffer[c*3+1];
        pattern[epnum[epchn]][eppos*3+2] = patterncopybuffer[c*3+2];
        eppos++;
      }
    }
    break;

    case KEY_DEL:
    if ((pattlen[epnum[epchn]] - eppos)*3-3 >= 0)
    {
      memmove(&pattern[epnum[epchn]][eppos*3],
        &pattern[epnum[epchn]][eppos*3+3],
        (pattlen[epnum[epchn]] - eppos)*3-3);
      pattern[epnum[epchn]][pattlen[epnum[epchn]]*3-3] = REST;
      pattern[epnum[epchn]][pattlen[epnum[epchn]]*3-2] = 0x00;
      pattern[epnum[epchn]][pattlen[epnum[epchn]]*3-1] = 0x00;
    }
    else
    {
      if (eppos == pattlen[epnum[epchn]])
      {
        if (pattlen[epnum[epchn]] > 1)
        {
          pattern[epnum[epchn]][pattlen[epnum[epchn]]*3-3] = ENDPATT;
          pattern[epnum[epchn]][pattlen[epnum[epchn]]*3-2] = 0x00;
          pattern[epnum[epchn]][pattlen[epnum[epchn]]*3-1] = 0x00;
          countthispattern();
          eppos = pattlen[epnum[epchn]];
        }
      }
    }
    break;

    case KEY_INS:
    if ((pattlen[epnum[epchn]] - eppos)*3-3 >= 0)
    {
      memmove(&pattern[epnum[epchn]][eppos*3+3],
        &pattern[epnum[epchn]][eppos*3],
        (pattlen[epnum[epchn]] - eppos)*3-3);
      pattern[epnum[epchn]][eppos*3] = REST;
      pattern[epnum[epchn]][eppos*3+1] = 0x00;
      pattern[epnum[epchn]][eppos*3+2] = 0x00;
    }
    else
    {
      if (eppos == pattlen[epnum[epchn]])
      {
        if (pattlen[epnum[epchn]] < MAX_PATTROWS)
        {
          pattern[epnum[epchn]][eppos*3] = REST;
          pattern[epnum[epchn]][eppos*3+1] = 0x00;
          pattern[epnum[epchn]][eppos*3+2] = 0x00;
          pattern[epnum[epchn]][eppos*3+3] = ENDPATT;
          pattern[epnum[epchn]][eppos*3+4] = 0x00;
          pattern[epnum[epchn]][eppos*3+5] = 0x00;
          countthispattern();
          eppos = pattlen[epnum[epchn]];
        }
      }
    }
    break;

    case KEY_SPACE:
    recordmode ^= 1;
    break;

    case KEY_RIGHT:
    if (!shiftpressed)
    {
      epcolumn++;
      if (epcolumn >= 6)
      {
        epcolumn = 0;
        epchn++;
        if (epchn >= MAX_CHN) epchn = 0;
        if (eppos > pattlen[epnum[epchn]]) eppos = pattlen[epnum[epchn]];
      }
    }
    else
    {
      if (epnum[epchn] < MAX_PATT - 1)
      {
        epnum[epchn]++;
        if (eppos > pattlen[epnum[epchn]]) eppos = pattlen[epnum[epchn]];
      }
      if (epchn == epmarkchn) epmarkchn = -1;
    }
    break;

    case KEY_LEFT:
    if (!shiftpressed)
    {
      epcolumn--;
      if (epcolumn < 0)
      {
        epcolumn = 5;
        epchn--;
        if (epchn < 0) epchn = MAX_CHN - 1;
        if (eppos > pattlen[epnum[epchn]]) eppos = pattlen[epnum[epchn]];
      }
    }
    else
    {
      if (epnum[epchn] > 0)
      {
        epnum[epchn]--;
        if (eppos > pattlen[epnum[epchn]]) eppos = pattlen[epnum[epchn]];
      }
      if (epchn == epmarkchn) epmarkchn = -1;
    }
    break;

    case KEY_PGDN:
    if (shiftpressed)
    {
      if ((epmarkchn != epchn) || (eppos != epmarkend))
      {
        epmarkchn = epchn;
        epmarkstart = epmarkend = eppos;
      }
    }
    for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
    {
      eppos++;
      if (eppos > pattlen[epnum[epchn]])
      {
        eppos = 0;
      }
    }
    if (shiftpressed) epmarkend = eppos;
    break;

    case KEY_DOWN:
    if (shiftpressed)
    {
      if ((epmarkchn != epchn) || (eppos != epmarkend))
      {
        epmarkchn = epchn;
        epmarkstart = epmarkend = eppos;
      }
    }
    eppos++;
    if (eppos > pattlen[epnum[epchn]])
    {
      eppos = 0;
    }
    if (shiftpressed) epmarkend = eppos;
    break;

    case KEY_HOME:
    if (shiftpressed)
    {
      if ((epmarkchn != epchn) || (eppos != epmarkend))
      {
        epmarkchn = epchn;
        epmarkstart = epmarkend = eppos;
      }
    }
    eppos = 0;
    if (shiftpressed) epmarkend = eppos;
    break;

    case KEY_END:
    if (shiftpressed)
    {
      if ((epmarkchn != epchn) || (eppos != epmarkend))
      {
        epmarkchn = epchn;
        epmarkstart = epmarkend = eppos;
      }
    }
    eppos = pattlen[epnum[epchn]];
    if (shiftpressed) epmarkend = eppos;
    break;

    case KEY_PGUP:
    if (shiftpressed)
    {
      if ((epmarkchn != epchn) || (eppos != epmarkend))
      {
        epmarkchn = epchn;
        epmarkstart = epmarkend = eppos;
      }
    }
    for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
    {
      eppos--;
      if (eppos < 0)
      {
        eppos = pattlen[epnum[epchn]];
      }
    }
    if (shiftpressed) epmarkend = eppos;
    break;

    case KEY_UP:
    if (shiftpressed)
    {
      if ((epmarkchn != epchn) || (eppos != epmarkend))
      {
        epmarkchn = epchn;
        epmarkstart = epmarkend = eppos;
      }
    }
    eppos--;
    if (eppos < 0)
    {
      eppos = pattlen[epnum[epchn]];
    }
    if (shiftpressed) epmarkend = eppos;
    break;
  }
  if ((keypreset != KEY_TRACKER) && (hexnybble >= 0) && (hexnybble <= 7) && (!epcolumn))
  {
    int oldbyte = pattern[epnum[epchn]][eppos * 3];
    int newbyte;
    int oldnote = oldbyte % 12;
    epoctave = hexnybble;
    if ((oldbyte < KEYOFF) && (recordmode))
    {
      newbyte = oldnote + epoctave * 12;
      if (newbyte < KEYOFF)
      {
        pattern[epnum[epchn]][eppos * 3] = newbyte;
      }
    }
    if ((recordmode) && (autoadvance < 1))
    {
      eppos++;
      if (eppos > pattlen[epnum[epchn]])
      {
        eppos = 0;
      }
    }
  }

  if ((hexnybble >= 0) && (epcolumn) && (recordmode))
  {
    if (eppos < pattlen[epnum[epchn]])
    {
      switch(epcolumn)
      {
        case 1:
        if (hexnybble < 2)
        {
          pattern[epnum[epchn]][eppos * 3 + 1] &= 0x7f;
          pattern[epnum[epchn]][eppos * 3 + 1] |= hexnybble << 7;
        }
        break;

        case 2:
        pattern[epnum[epchn]][eppos * 3 + 1] &= 0x87;
        pattern[epnum[epchn]][eppos * 3 + 1] |= hexnybble << 3;
        break;

        case 3:
        if (hexnybble < 8)
        {
          pattern[epnum[epchn]][eppos * 3 + 1] &= 0xf8;
          pattern[epnum[epchn]][eppos * 3 + 1] |= hexnybble;
          /* Check for illegal command data */
          if ((pattern[epnum[epchn]][eppos * 3 + 1] & 0x7) == CMD_SETFILTER)
          {
            pattern[epnum[epchn]][eppos * 3 + 2] &= 0x3f;
          }
        }
        break;

        case 4:
        pattern[epnum[epchn]][eppos * 3 + 2] &= 0x0f;
        pattern[epnum[epchn]][eppos * 3 + 2] |= hexnybble << 4;
        /* Check for illegal command data */
        if ((pattern[epnum[epchn]][eppos * 3 + 1] & 0x7) == CMD_SETFILTER)
        {
          pattern[epnum[epchn]][eppos * 3 + 2] &= 0x3f;
        }
        break;

        case 5:
        pattern[epnum[epchn]][eppos * 3 + 2] &= 0xf0;
        pattern[epnum[epchn]][eppos * 3 + 2] |= hexnybble;
        /* Check for illegal command data */
        break;
      }
    }
    if (autoadvance < 2)
    {
      eppos++;
      if (eppos > pattlen[epnum[epchn]])
      {
        eppos = 0;
      }
    }
  }
  epview = eppos - VISIBLEPATTROWS / 2;
}

void printmainscreen(void)
{
  clearscreen();
  printblankc(0, 0, 15+16, 80);
  printtext(0, 0, 15+16, programname);
  if (multiplier != 1)
  {
    sprintf(textbuffer, "%dX-mode", multiplier);
    printtext(60, 0, 15+16, textbuffer);
  }
  printtext(70, 0, 15+16, "F12 = HELP");
  printpatterns();
  fliptoscreen();
}

void editstring(char *buffer, int maxlength)
{
  int len = strlen(buffer);

  if (key)
  {
    if ((key >= 32) && (key < 256))
    {
      if (len < maxlength-1)
      {
        buffer[len] = key;
        buffer[len+1] = 0;
      }
    }
    if ((key == 8) && (len > 0))
    {
      buffer[len-1] = 0;
    }
  }
}

void playroutine(void)
{
  INSTR *iptr;
  CHN *cptr = &chn[0];
  int c;
  int maxrepeats;

  if (songinit == 0x04) followplay = 0;

  if ((songinit > 0) && (songinit < 0x80))
  {
    setfilter(0);
    timeframe = 0;
    timemin = 0;
    timesec = 0;
    playsongnum = esnum;
    if ((songinit == 0x02) || (songinit == 0x03))
    {
      if ((espos[0] >= songlen[playsongnum][0]) ||
         (espos[1] >= songlen[playsongnum][1]) ||
         (espos[2] >= songlen[playsongnum][2]))
         songinit = 0x01;
    }

    for (c = 0; c < MAX_CHN; c++)
    {
      cptr->command = 0;
      cptr->cmddata = 0;
      cptr->songptr = 0;
      cptr->advance = 2;
      if (songinit == 0x02) cptr->songptr = espos[c];
      if (songinit == 0x03)
      {
        cptr->songptr = espos[c];
        cptr->advance = 1;
      }
      cptr->wavetable = 0;
      cptr->tick = 5;
      cptr->newnote = 5;
      cptr->trans = 0;
      cptr->repeat = 0;
      if (songinit == 0x01) cptr->tempo = 5;
      cptr->wave = 0x08; /* Testbit */
      cptr->pattptr = ENDPATT;
      cptr++;
    }
    if (songinit != 0x04) songinit = 0;
    else songinit = 0x80;
    if ((!songlen[playsongnum][0]) ||
       (!songlen[playsongnum][1]) ||
       (!songlen[playsongnum][2])) songinit = 0x80; /* Zero length song */
  }
  else
  {
    maxrepeats = 32;
    for (c = 0; c < MAX_CHN; c++)
    {
      iptr = &instr[cptr->instrnum];
      if (cptr->newnote >= 0x80)
      {
        cptr->wavetable = 0;
        cptr->pulsedir = 0;
        cptr->newnote++;
      }
      /* In jammode, don't advance song */
      if (songinit == 0x80)
      {
        cptr->command = 0;
        cptr->cmddata = 0;
        goto EFFECTS;
      }

      /* Decrease tick */
      if (cptr->tick >= 0x80)
      {
        if (cptr->tempo >= 2)
        {
          cptr->tick = cptr->tempo;
        }
        else
        {
          /* Set funktempo, switch between 2 values */
          switch(cptr->tempo)
          {
            case 0:
            cptr->tick = (filtertable[2] >> 4)-1;
            break;

            case 1:
            cptr->tick = (filtertable[2] & 0xf)-1;
            break;
          }
          cptr->tempo ^= 1;
        }
      }
      cptr->tick--;
      if (cptr->tick >= 0x80) goto NEWNOTES;

      /* Check for end of pattern */
      if (cptr->pattptr != ENDPATT) goto EFFECTS;

      if (cptr->repeat)
      {
        cptr->repeat--;
        goto NEWPATTDONE;
      }
      GETNEWPATT:
      maxrepeats--;
      if (maxrepeats < 0) goto NEWPATTDONE;
      if (!cptr->advance) goto NEWPATTDONE;
      if (cptr->advance == 1) cptr->advance = 0;

      if (cptr->songptr > songlen[playsongnum][c])
      {
        songinit = 0x04;
        cptr->songptr = 0;
        goto NEWPATTDONE;
      }
      if (songorder[playsongnum][c][cptr->songptr] < LOOPSONG)
      {
        if (songorder[playsongnum][c][cptr->songptr] < TRANSDOWN)
        {
          if (songorder[playsongnum][c][cptr->songptr] < REPEAT)
          {
            cptr->pattnum = songorder[playsongnum][c][cptr->songptr];
            cptr->songptr++;
          }
          else
          {
            cptr->repeat = songorder[playsongnum][c][cptr->songptr] - REPEAT;
            cptr->songptr++;
            goto GETNEWPATT;
          }
        }
        else
        {
          cptr->trans = songorder[playsongnum][c][cptr->songptr] - TRANSUP;
          cptr->songptr++;
          goto GETNEWPATT;
        }
      }
      else
      {
        cptr->songptr = songorder[playsongnum][c][cptr->songptr+1];
        if (cptr->songptr >= songlen[playsongnum][c])
        {
          songinit = 0x04;
          cptr->songptr = 0;
          goto NEWPATTDONE;
        }
        goto GETNEWPATT;
      }
      NEWPATTDONE:
      cptr->pattptr = 0;
      if (!cptr->newnote) goto EFFECTS;
      else goto DOWAVEARP;

      /* New notes processing */
      NEWNOTES:
      {
        unsigned char newinstrnum;
        unsigned char newnote;

        newnote = pattern[cptr->pattnum][cptr->pattptr];
        newinstrnum = pattern[cptr->pattnum][cptr->pattptr+1] >> 3;
        if (newinstrnum) cptr->instrnum = newinstrnum;
        cptr->command = pattern[cptr->pattnum][cptr->pattptr+1] & 0x7;
        cptr->cmddata = pattern[cptr->pattnum][cptr->pattptr+2];
        cptr->pattptr += 3;
        if (pattern[cptr->pattnum][cptr->pattptr] == ENDPATT)
          cptr->pattptr = ENDPATT;

        if (newnote < REST)
        {
          if (newnote == KEYOFF)
            cptr->wave &= 0xfe;
          else
          {
            if (cptr->command != CMD_TONEPORTA)
            {
              cptr->newnote = 1 - multiplier*2;

              /* Execute wavetable also in conjunction with newnote init
               * (better sounding HR)
               */
              if (cptr->wavetable)
              {
                unsigned char note;

                if (wavetable[cptr->waveinstr][cptr->wavetable - 1])
                {
                  cptr->wave = wavetable[cptr->waveinstr][cptr->wavetable - 1];
                }
                note = wavetable[cptr->waveinstr][cptr->wavetable];
                if (note < 0x80)
                  note += cptr->note;
                note &= 0x7f;


                switch (wavetable[cptr->waveinstr][cptr->wavetable+1])
                {
                  case 0xff:
                  if (wavetable[cptr->waveinstr][cptr->wavetable+2])
                  {
                    cptr->waveinstr = cptr->instrnum;
                    cptr->wavetable = (wavetable[cptr->waveinstr][cptr->wavetable+2] - 1) * 2 + 1;
                  }
                  else
                  {
                    cptr->wavetable = 0;
                  }
                  break;

                  default:
                  cptr->wavetable += 2;
                  break;
                }
                cptr->freq = freqtbllo[note] | (freqtblhi[note]<<8);
              }

              cptr->wave &= 0xfe;
              if (!(instr[cptr->instrnum].pulse & 0x01))
              {
                sidreg[0x5 + 7*c] = adparam; /* Hard restart */
                sidreg[0x6 + 7*c] = 0;
              }
            }
            cptr->note = (newnote + cptr->trans) & 0x7f;
          }
        }
      }
      /* Tick 0 effects */
      switch (cptr->command)
      {
        case CMD_TONEPORTA:
        cptr->arpcount = 0;
        break;

        case CMD_SETFILTER:
        setfilter(cptr->cmddata);
        break;

        case CMD_SETSUSTAIN:
        if ((cptr->newnote) && (cptr->newnote < 0x80) && (cptr->pulsedir < 0x80))
          sidreg[0x6 + 7*c] = cptr->cmddata;
        break;

        case CMD_SETTEMPO:
        if (cptr->cmddata >= 0x80)
        {
          /* Timing mark */
          if (cptr->cmddata < 0xc0)
          {
            if ((cptr->cmddata & 0x7f) < 2)
            {
              cptr->tempo = cptr->cmddata & 0x7f;
            }
            if ((cptr->cmddata & 0x7f) >= 3)
            {
              cptr->tempo = (cptr->cmddata & 0x7f) - 1;
            }
          }
        }
        else
        {
          if (cptr->cmddata < 2)
          {
            chn[0].tempo = cptr->cmddata;
            chn[1].tempo = cptr->cmddata;
            chn[2].tempo = cptr->cmddata;
          }

          if (cptr->cmddata >= 3)
          {
            chn[0].tempo = cptr->cmddata - 1;
            chn[1].tempo = cptr->cmddata - 1;
            chn[2].tempo = cptr->cmddata - 1;
          }
        }
        break;
      }
      if (cptr->newnote != 0x3f) goto NEXTCHN;
      DOWAVEARP:
      if (cptr->wavetable) goto WAVETABLE;
      if (!cptr->command) goto ARPEGGIO;
      goto NEXTCHN;

      /* Continuous effects */
      EFFECTS:
      if (!cptr->newnote)
      {
        cptr->newnote = 0x3f;
        cptr->arpcount = 0;
        cptr->wavetable = 1;
        cptr->waveinstr = cptr->instrnum;
        if (iptr->pulse & 0xfe)
        {
          cptr->pulsedir = 0x80;
          cptr->pulse = ((iptr->pulse & 0xfe) << 4) | (iptr->pulse >> 4);
        } else cptr->pulsedir |= 0x80;
        if (iptr->filtertype)
        {
          setfilter(iptr->filtertype);
        }
        if (hardrestart == 2) goto NEXTCHN;
      }
      {
        int newpulse;

        if (cptr->pulsedir >= 0x80)
        {
          sidreg[0x5 + 7*c] = iptr->ad;
          if (cptr->command != CMD_SETSUSTAIN)
            sidreg[0x6 + 7*c] = iptr->sr;
          else
            sidreg[0x6 + 7*c] = cptr->cmddata;
          cptr->pulsedir &= 0x7f;
        }
        else
        {
          if (!cptr->pulsedir)
          {
            newpulse = cptr->pulse;
            newpulse += iptr->pulseadd;
            cptr->pulse = newpulse & 0xfff;
            if ((cptr->pulse >> 4) >= iptr->pulselimithigh)
              cptr->pulsedir = 0x01;
          }
          else
          {
            newpulse = cptr->pulse;
            newpulse -= iptr->pulseadd;
            cptr->pulse = newpulse & 0xfff;
            if ((cptr->pulse >> 4) < iptr->pulselimitlow)
              cptr->pulsedir = 0x00;
          }
        }
      }
      if (cptr->wavetable) goto WAVETABLE;
      if (!cptr->command) goto ARPEGGIO;
      switch(cptr->command)
      {
        case CMD_PORTAUP:
        {
          unsigned short speed = cptr->cmddata << 2;
          cptr->freq += speed;
        }
        break;

        case CMD_PORTADOWN:
        {
          unsigned short speed = cptr->cmddata << 2;
          cptr->freq -= speed;
        }
        break;

        case CMD_VIBRATO:
        {
          unsigned char speed = swapnybbles(cptr->cmddata);

          if ((cptr->arpcount < 0x80) && (cptr->arpcount > ((cptr->cmddata >> 4) & 0x0f)))
          {
            cptr->arpcount ^= 0xff;
          }
          cptr->arpcount += 0x02;
          if (cptr->arpcount & 0x01) cptr->freq += speed;
          else cptr->freq -= speed;
        }
        break;

        case CMD_TONEPORTA:
        {
          unsigned short targetfreq = freqtbllo[cptr->note] | (freqtblhi[cptr->note] << 8);
          unsigned short speed = cptr->cmddata << 2;

          if (!cptr->cmddata)
          {
            cptr->freq = targetfreq;
          }
          else
          {
            if (cptr->freq < targetfreq)
            {
              cptr->freq += speed;
              if (cptr->freq > targetfreq)
              {
                cptr->freq = targetfreq;
              }
            }
            if (cptr->freq > targetfreq)
            {
              cptr->freq -= speed;
              if (cptr->freq < targetfreq)
              {
                cptr->freq = targetfreq;
              }
            }
          }
        }
        break;
      }
      goto NEXTCHN;

      WAVETABLE:
      {
        unsigned char note;

        if (wavetable[cptr->waveinstr][cptr->wavetable - 1])
        {
          cptr->wave = wavetable[cptr->waveinstr][cptr->wavetable - 1];
        }
        note = wavetable[cptr->waveinstr][cptr->wavetable];
        if (note < 0x80)
          note += cptr->note;
        note &= 0x7f;

        switch (wavetable[cptr->waveinstr][cptr->wavetable+1])
        {
          case 0xff:
          if (wavetable[cptr->waveinstr][cptr->wavetable+2])
          {
            cptr->waveinstr = cptr->instrnum;
            cptr->wavetable = (wavetable[cptr->waveinstr][cptr->wavetable+2] - 1) * 2 + 1;
          }
          else
          {
            cptr->wavetable = 0;
          }
          break;

          default:
          cptr->wavetable += 2;
          break;
        }
        cptr->freq = freqtbllo[note] | (freqtblhi[note]<<8);
      }
      goto NEXTCHN;

      ARPEGGIO:
      if (cptr->cmddata)
      {
        unsigned char note = cptr->note;
        unsigned char choice;

        choice = cptr->arpcount / 2;
        /* Slow arpeggio */
        if (cptr->cmddata >= 0x80)
        {
          cptr->arpcount++;
        }
        else cptr->arpcount += 2;
        if (cptr->arpcount > 5) cptr->arpcount = 0;

        switch(choice)
        {
          case 0:
          note += (cptr->cmddata & 0x70) >> 4;
          break;

          case 1:
          note += (cptr->cmddata & 0xf);
          break;

          case 2:
          break;
        }

        note &= 0x7f;
        cptr->freq = freqtbllo[note] | (freqtblhi[note]<<8);
      }
      NEXTCHN:
      if (cptr->mute) sidreg[0x4 + 7*c] = cptr->wave = 0x08;
      else
      {
        sidreg[0x0 + 7*c] = cptr->freq & 0xff;
        sidreg[0x1 + 7*c] = cptr->freq >> 8;
        sidreg[0x2 + 7*c] = cptr->pulse & 0xfe;
        sidreg[0x3 + 7*c] = cptr->pulse >> 8;
        sidreg[0x4 + 7*c] = cptr->wave;
      }
      cptr++;
    }

    if (!filterstep) goto NOFILTER;
    if (!filtertime) setfilter(filterstep);
    else
    {
      filtertime--;
      filtercutoff += filtercutoffadd;
    }

    NOFILTER:
    sidreg[0x15] = 0x00;
    sidreg[0x16] = filtercutoff;
    sidreg[0x17] = filterctrl;
    sidreg[0x18] = filtertype;
  }
  if (songinit != 0x80)
  {
    timeframe++;
    if (!ntsc)
    {
      if (timeframe == 50*multiplier)
      {
        timeframe = 0;
        timesec++;
      }
    }
    else
    {
      if (timeframe == 60*multiplier)
      {
        timeframe = 0;
        timesec++;
      }
    }
    if (timesec == 60)
    {
      timesec = 0;
      timemin++;
      timemin %= 60;
    }
  }
}

unsigned char swapnybbles(unsigned char n)
{
  unsigned char highnybble = n >> 4;
  unsigned char lownybble = n & 0xf;

  return (lownybble << 4) | highnybble;
}

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

  songinit = 0x04;

  epmarkchn = -1;

  for (c = 0; c < MAX_CHN; c++)
  {
    int d;
    chn[c].mute = 0;
    chn[c].tempo = 5;
    if (cs)
    {
      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;
    }
  }
  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)
  {
    for (c = 0; c < MAX_PATT; c++)
    {
      int d;
      memset(&pattern[c][0], 0, MAX_PATTROWS*3);
      for (d = 0; d < defaultpatternlength; d++) pattern[c][d*3] = REST;
      for (d = defaultpatternlength; d <= MAX_PATTROWS; d++) pattern[c][d*3] = ENDPATT;
    }
    patterncopyrows = 0;
  }
  if (ci)
  {
    for (c = 0; c < MAX_INSTR; c++)
    {
      memset(&instr[c], 0, sizeof(INSTR));
      memset(&wavetable[c][0], 0, MAX_WAVELEN*2);
      wavetable[c][2] = 0xff;
      wavetable[c][MAX_WAVELEN*2-2] = 0xff;
    }
    memset(&instrcopybuffer, 0, sizeof(INSTR));
    memset(wavecopybuffer, 0, MAX_WAVELEN*2);
    eipos = 0;
    eicolumn = 0;
    einum = 1;
    ewpos = 0;
  }
  if (cf == 1)
  {
    for (c = 0; c < MAX_FILT; c++)
    {
      filtertable[c*4] = 0x80; /* Resonance 8 */
      filtertable[c*4+1] = 0xf; /* Volume */
      filtertable[c*4+2] = 0;
      filtertable[c*4+3] = 0;
    }
    efnum = 0;
  }
  countpatternlengths();
  playsongnum = 0;
}

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

  highestusedpattern = 0;
  for (c = 0; c < MAX_PATT; c++)
  {
    for (d = 0; d <= MAX_PATTROWS; d++)
    {
      if (pattern[c][d*3] == ENDPATT) break;
    }
    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] < MAX_PATT)
        {
          if (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*3] == 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;
}


void printpatterns(void)
{
  int c, d, color;
  int cc = cursorcolortable[cursorflash];

  for (c = 0; c < MAX_CHN; c++)
  {
    if (!followplay)
    {
      sprintf(textbuffer, "CHN%d PATT%02X", c+1, epnum[c]);
      printtext(1+c*13, 2, 15, textbuffer);
      for (d = 0; d < VISIBLEPATTROWS; d++)
      {
        int rownum = epview + d;
        color = 7;
        if ((epnum[c] == chn[c].pattnum) && (!songinit))
        {
          int chnrow = chn[c].pattptr;
          if (chnrow != ENDPATT)
          {
            chnrow /= 3;
            if (chnrow == rownum) color = 12;
          }
        }

        if (chn[c].mute) color = 8;
        if (rownum == eppos) color = 10;
        if ((rownum < 0) || (rownum > pattlen[epnum[c]]))
        {
          sprintf(textbuffer, "           ");
        }
        else
        {
          if (pattern[epnum[c]][rownum*3] == ENDPATT)
          {
            sprintf(textbuffer, "%02d PATT.END",
              rownum);
          }
          else
          {
            sprintf(textbuffer, "%02d %s%02X%01X%02X",
              rownum,
              notename[pattern[epnum[c]][rownum*3]],
              pattern[epnum[c]][rownum*3+1] >> 3,
              pattern[epnum[c]][rownum*3+1] & 0x7,
              pattern[epnum[c]][rownum*3+2]);
          }
        }
        textbuffer[2] = 0;
        if (rownum % stepsize)
        {
          printtext(1+c*13, 3+d, 7, textbuffer);
        }
        else
        {
          printtext(1+c*13, 3+d, 10, textbuffer);
        }
        printtext(4+c*13, 3+d, color, &textbuffer[3]);
        if (c == epmarkchn)
        {
          if (epmarkstart <= epmarkend)
          {
            if ((rownum >= epmarkstart) && (rownum <= epmarkend))
              printbg(1+c*13+3, 3+d, 1, 8);
          }
          else
          {
            if ((rownum <= epmarkstart) && (rownum >= epmarkend))
              printbg(1+c*13+3, 3+d, 1, 8);
          }
        }
        if ((color == 10) && (editmode == EDIT_PATTERN) && (epchn == c))
        {
          switch(epcolumn)
          {
            case 0:
            printbg(1+c*13+3, 3+d, cc, 3);
            break;

            default:
            printbg(1+c*13+5+epcolumn, 3+d, cc, 1);
            break;
          }
        }
      }
    }
    else
    {
      int pos = chn[c].pattptr;
      int view;
      if (pos == ENDPATT) pos = pattlen[chn[c].pattnum];
        else pos /= 3;
      view = pos - VISIBLEPATTROWS / 2;
      epnum[c] = chn[c].pattnum;
      if (epchn == c)
      {
        eppos = pos;
        epview = view;
      }

      sprintf(textbuffer, "CHN%d PATT%02X", c+1, chn[c].pattnum);
      printtext(1+c*13, 2, 15, textbuffer);
      for (d = 0; d < VISIBLEPATTROWS; d++)
      {
        int rownum;

        rownum = view + d;
        color = 7;
        if (chn[c].mute) color = 8;
        if (rownum == pos) color = 12;
        if ((rownum < 0) || (rownum > pattlen[chn[c].pattnum]))
        {
          sprintf(textbuffer, "           ");
        }
        else
        {
          if (pattern[chn[c].pattnum][rownum*3] == ENDPATT)
          {
            sprintf(textbuffer, "%02d PATT.END",
              rownum);
          }
          else
          {
            sprintf(textbuffer, "%02d %s%02X%01X%02X",
              rownum,
              notename[pattern[chn[c].pattnum][rownum*3]],
              pattern[chn[c].pattnum][rownum*3+1] >> 3,
              pattern[chn[c].pattnum][rownum*3+1] & 0x7,
              pattern[chn[c].pattnum][rownum*3+2]);
          }
        }
        textbuffer[2] = 0;
        if (rownum % stepsize)
        {
          printtext(1+c*13, 3+d, 7, textbuffer);
        }
        else
        {
          printtext(1+c*13, 3+d, 10, textbuffer);
        }
        printtext(4+c*13, 3+d, color, &textbuffer[3]);
      }
    }
  }
  if (!followplay)
  {
    sprintf(textbuffer, "CHN ORDERLIST (SUBTUNE %02X, POS %02X)", esnum, eseditpos);
    printtext(40, 2, 15, textbuffer);
    for (c = 0; c < MAX_CHN; c++)
    {
      sprintf(textbuffer, " %d ", c+1);
      printtext(40, 3+c, 15, textbuffer);
      for (d = 0; d < VISIBLEORDERLIST; d++)
      {
        int rownum = esview + d;
        color = 7;
        if (!songinit)
        {
          int chnpos = chn[c].songptr;
          chnpos--;
          if (chnpos < 0) chnpos = 0;
          if (rownum == chnpos) color = 12;
        }
        if (rownum == espos[c]) color = 10;

        if ((rownum < 0) || (rownum > (songlen[esnum][c]+1)) || (rownum > MAX_SONGLEN + 1))
        {
          sprintf(textbuffer, "   ");
        }
        else
        {
          if (songorder[esnum][c][rownum] < LOOPSONG)
          {
            if ((songorder[esnum][c][rownum] < REPEAT) || (rownum >= songlen[esnum][c]))
            {
              sprintf(textbuffer, "%02X ", songorder[esnum][c][rownum]);
            }
            else
            {
              if (songorder[esnum][c][rownum] >= TRANSUP)
              {
                sprintf(textbuffer, "+%01X ", songorder[esnum][c][rownum]&0xf);
              }
              else
              {
                if (songorder[esnum][c][rownum] >= TRANSDOWN)
                {
                  sprintf(textbuffer, "-%01X ", 16-(songorder[esnum][c][rownum] & 0x0f));
                }
                else
                {
                  sprintf(textbuffer, "R%01X ", (songorder[esnum][c][rownum]+1) & 0x0f);
                }
              }
            }
          }
          if (songorder[esnum][c][rownum] == LOOPSONG)
          {
            sprintf(textbuffer, "RST");
          }
        }
        printtext(44+d*3, 3+c, color, textbuffer);
        if ((rownum == eseditpos) && (editmode == EDIT_ORDERLIST) && (eschn == c))
        {
          printbg(44+d*3+escolumn, 3+c, cc, 1);
        }
      }
    }
  }
  else
  {
    int chnpos = chn[eschn].songptr;
    int chnview;
    chnpos--;
    if (chnpos < 0) chnpos = 0;
    sprintf(textbuffer, "CHN ORDERLIST (SUBTUNE %02X, POS %02X)", esnum, chnpos);
    printtext(40, 2, 15, textbuffer);
    for (c = 0; c < MAX_CHN; c++)
    {
      chnpos = chn[c].songptr;
      chnpos--;
      if (chnpos < 0) chnpos = 0;
      chnview = 0;
      if (chnpos >= VISIBLEORDERLIST) chnview = chnpos - VISIBLEORDERLIST + 1;
      if (eschn == c)
      {
        if (chnpos <= songlen[esnum][c]) eseditpos = chnpos;
        esview = chnview;
      }

      sprintf(textbuffer, " %d ", c+1);
      printtext(40, 3+c, 15, textbuffer);
      for (d = 0; d < VISIBLEORDERLIST; d++)
      {
        int rownum = chnview + d;
        color = 7;
        if (rownum == chnpos) color = 12;
        if (rownum == espos[c]) color = 10;
        if ((rownum < 0) || (rownum > (songlen[esnum][c]+1)) || (rownum > MAX_SONGLEN + 1))
        {
          sprintf(textbuffer, "   ");
        }
        else
        {
          if (songorder[esnum][c][rownum] < LOOPSONG)
          {
            if ((songorder[esnum][c][rownum] < REPEAT) || (rownum >= songlen[esnum][c]))
            {
              sprintf(textbuffer, "%02X ", songorder[esnum][c][rownum]);
            }
            else
            {
              if (songorder[esnum][c][rownum] >= TRANSUP)
              {
                sprintf(textbuffer, "+%01X ", songorder[esnum][c][rownum]&0xf);
              }
              else
              {
                if (songorder[esnum][c][rownum] >= TRANSDOWN)
                {
                  sprintf(textbuffer, "-%01X ", 16-(songorder[esnum][c][rownum] & 0x0f));
                }
                else
                {
                  sprintf(textbuffer, "R%01X ", (songorder[esnum][c][rownum]+1) & 0x0f);
                }
              }
            }
          }
          if (songorder[esnum][c][rownum] == LOOPSONG)
          {
            sprintf(textbuffer, "RST");
          }
        }
        printtext(44+d*3, 3+c, color, textbuffer);
      }
    }
  }
  sprintf(textbuffer, "INSTRUMENT NUM. %02X  %-16s", einum, instr[einum].name);
  printtext(40, 7, 15, textbuffer);

  sprintf(textbuffer, "Attack/Decay    %02X", instr[einum].ad);
  if (eipos == 0) color = 10; else color = 7;
  printtext(40, 8, color, textbuffer);

  sprintf(textbuffer, "Sustain/Release %02X", instr[einum].sr);
  if (eipos == 1) color = 10; else color = 7;
  printtext(40, 9, color, textbuffer);

  sprintf(textbuffer, "Pulse Width     %02X", instr[einum].pulse & 0xfe);
  if (eipos == 2) color = 10; else color = 7;
  printtext(40, 10, color, textbuffer);
  printtext(70, 13, 7, "Hardres.");
  if (instr[einum].pulse & 0x01)
  {
    printtext(70, 14, 7, "OFF");
  }
  else
  {
    printtext(70, 14, 7, "ON ");
  }

  sprintf(textbuffer, "Pulse Speed     %02X", instr[einum].pulseadd);
  if (eipos == 3) color = 10; else color = 7;
  printtext(40, 11, color, textbuffer);

  sprintf(textbuffer, "Pulse Limit Min %02X", instr[einum].pulselimitlow);
  if (eipos == 4) color = 10; else color = 7;
  printtext(40, 12, color, textbuffer);

  sprintf(textbuffer, "Pulse Limit Max %02X", instr[einum].pulselimithigh);
  if (eipos == 5) color = 10; else color = 7;
  printtext(40, 13, color, textbuffer);

  sprintf(textbuffer, "Filter To Use   %02X", instr[einum].filtertype);
  if (eipos == 6) color = 10; else color = 7;
  printtext(40, 14, color, textbuffer);

  sprintf(textbuffer, "FILTER NUM.     %02X", efnum);
  printtext(40, 16, 15, textbuffer);

  sprintf(textbuffer, "Filt Control    %02X", filtertable[efnum*4]);
  if (eipos == 8) color = 10; else color = 7;
  printtext(40, 17, color, textbuffer);

  sprintf(textbuffer, "Filt Type/Time  %02X", filtertable[efnum*4+1]);
  if (eipos == 9) color = 10; else color = 7;
  printtext(40, 18, color, textbuffer);

  sprintf(textbuffer, "Filt Freq/Spd   %02X", filtertable[efnum*4+2]);
  if (eipos == 10) color = 10; else color = 7;
  printtext(60, 17, color, textbuffer);

  sprintf(textbuffer, "Filt Next Step  %02X", filtertable[efnum*4+3]);
  if (eipos == 11) color = 10; else color = 7;
  printtext(60, 18, color, textbuffer);

  printtext(60, 8, 15, "W       N POS");
  sprintf(textbuffer,"A       O %02X", ewpos+1);
  printtext(60, 9,  15, textbuffer);
  printtext(60, 10, 15, "V       T");
  printtext(60, 11, 15, "E       E");
  printtext(60, 12, 15, "T       T");
  printtext(60, 13, 15, "B       B");
  printtext(60, 14, 15, "L       L");
  for (d = 0; d < VISIBLEWAVEROWS; d++)
  {
    int rownum = ewview + d;
    color = 7;
    if (rownum == ewpos) color = 10;
    if ((rownum < 0) || (rownum >= MAX_WAVELEN))
    {
      sprintf(textbuffer, "     ");
    }
    else
    {
      sprintf(textbuffer, "%02X %02X",
      wavetable[einum][rownum*2],
      wavetable[einum][rownum*2+1]);
    }
    printtext(62, 8+d, color, textbuffer);
  }
  if (editmode == EDIT_INSTRUMENT)
  {
    if (eipos < 7)
    {
      switch (eicolumn)
      {
        case 0:
        case 1:
        printbg(56+eicolumn, 8+eipos, cc, 1);
        break;

        case 2:
        case 3:
        printbg(60+eicolumn, 8+ewpos-ewview, cc, 1);
        break;

        case 4:
        case 5:
        printbg(61+eicolumn, 8+ewpos-ewview, cc, 1);
        break;
      }
    }
    if (eipos > 7)
    {
      switch (eicolumn)
      {
        case 0:
        case 1:
        printbg(56+eicolumn+20*((eipos-8)/2), 17+(eipos&1), cc, 1);
        break;
      }
    }
    if (eipos == 7)
    {
      printbg(60+strlen(instr[einum].name), 7, cc, 1);
    }
  }

  if (enpos == 0)
  {
    printtext(40, 20, 15, "NAME:     ");
    sprintf(textbuffer, "%-32s", songname);
    printtext(40, 21, 10, textbuffer);
  }

  if (enpos == 1)
  {
    printtext(40, 20, 15, "AUTHOR:   ");
    sprintf(textbuffer, "%-32s", authorname);
    printtext(40, 21, 10, textbuffer);
  }

  if (enpos == 2)
  {
    printtext(40, 20, 15, "COPYRIGHT:");
    sprintf(textbuffer, "%-32s", copyrightname);
    printtext(40, 21, 10, textbuffer);
  }

  if (editmode == EDIT_NAMES)
  {
    switch(enpos)
    {
      case 0:
      printbg(40+strlen(songname), 21, cc, 1);
      break;
      case 1:
      printbg(40+strlen(authorname), 21, cc, 1);
      break;
      case 2:
      printbg(40+strlen(copyrightname), 21, cc, 1);
      break;
    }
  }
  sprintf(textbuffer, "OCTAVE %d", epoctave);
  printtext(0, 23, 15, textbuffer);

  switch(autoadvance)
  {
    case 0:
    color = 10;
    break;

    case 1:
    color = 14;
    break;

    case 2:
    color = 12;
    break;
  }

  if (recordmode) printtext(0, 24, color, "EDITMODE");
  else printtext(0, 24, color, "JAM MODE");

  if (!songinit) printtext(10, 23, 15, "PLAYING");
  else printtext(10, 23, 15, "STOPPED");
  if (!ntsc)
    sprintf(textbuffer, " %02d%c%02d ", timemin, timechar[timeframe / (25*multiplier)], timesec);
  else
    sprintf(textbuffer, " %02d%c%02d ", timemin, timechar[timeframe / (30*multiplier)], timesec);

  printtext(10, 24, 10, textbuffer);

  printtext(60, 23, 15, " CHN1   CHN2   CHN3 ");
  for (c = 0; c < MAX_CHN; c++)
  {
    int chnpos = chn[c].songptr;
    int chnrow = chn[c].pattptr;
    chnpos--;
    if (chnpos < 0) chnpos = 0;
    if (chnrow == ENDPATT) chnrow = 0;
    chnrow /= 3;

    sprintf(textbuffer, "%03d/%02d",
      chnpos,chnrow);
    printtext(60+7*c, 24, 10, textbuffer);
  }
}

int fileselector(char *name, char *filter, char *title, int initialmode)
{
  DIR *dir;
  struct dirent *de;
  struct stat st;

  int color;
  int c, d;
  int files;
  int filepos;
  int fileview;
  int lowest;
  int filemode = initialmode;
  int exitfilesel;
  #ifdef __WIN32__
  char drivestr[] = "A:\\";
  char driveexists[26];
  #endif
  char cmpbuf[256];

  for (c = 0; c < VISIBLEFILES+6; c++)
  {
    printblank(40 - (MAX_FILENAME+10)/2, 2+c, MAX_FILENAME+10);
  }
  drawbox(40-(MAX_FILENAME+10)/2, 2, 15, MAX_FILENAME+10, VISIBLEFILES+6);
  printblankc(40 - (MAX_FILENAME+10)/2+1, 3, 15+16,MAX_FILENAME+8);
  printtext(40-(MAX_FILENAME+10)/2+1, 3, 15+16, title);

  /* Scan for all existing drives */
  #ifdef __WIN32__
  for (c = 0; c < 26; c++)
  {
    drivestr[0] = 'A' + c;
    if (GetDriveType(drivestr) > 1) driveexists[c] = 1;
    else driveexists[c] = 0;
  }
  #endif

  NEWPATH:
  files = 0;
  #ifdef __WIN32__
  for (c = 0; c < 26; c++)
  {
    if (driveexists[c])
    {
      drivestr[0] = 'A' + c;
      strcpy(direntry[files].name, drivestr);
      direntry[files].attribute = 2;
      files++;
    }
  }
  #endif

  dir = opendir(".");

  if (dir)
  {
    while ((de = readdir(dir)))
    {
      if ((files < MAX_DIRFILES) && (strlen(de->d_name) < MAX_FILENAME))
      {
        strcpy(direntry[files].name, de->d_name);
        direntry[files].attribute = 0;
        stat(de->d_name, &st);
        if (st.st_mode & S_IFDIR)
        {
          direntry[files].attribute = 1;
          files++;
        }
        else
        {
          int c;
	  /* If a file, must match filter */
          char *filtptr = strstr(filter, "*");
          strcpy(cmpbuf, de->d_name);
          for (c = 0; c < strlen(cmpbuf); c++)
	    cmpbuf[c] = tolower(cmpbuf[c]);
          if (filtptr)
          {
            filtptr++;
            if (strstr(cmpbuf, filtptr))
            {
              files++;
            }
          }
          else
          {
            if (strstr(cmpbuf, filter))
            {
              files++;
            }
          }
        }
      }
    }
    closedir(dir);
  }
  /* Sort the filelist in a most horrible fashion */
  for (c = 0; c < files; c++)
  {
    lowest = c;
    for (d = c+1; d < files; d++)
    {
      if (direntry[d].attribute < direntry[lowest].attribute)
      {
        lowest = d;
      }
      else
      {
        if (direntry[d].attribute == direntry[lowest].attribute)
        {
          if (strcmp(direntry[d].name, direntry[lowest].name) < 0)
          {
            lowest = d;
          }
        }
      }
    }
    if (lowest != c)
    {
      DIRENTRY swaptemp = direntry[c];
      direntry[c] = direntry[lowest];
      direntry[lowest] = swaptemp;
    }
  }

  /* Search for the current filename */
  filepos = 0;
  for (c = 0; c < files; c++)
  {
    if ((!direntry[c].attribute) && (!strcmp(name, direntry[c].name)))
    {
      filepos = c;
    }
  }

  exitfilesel = -1;
  while (exitfilesel < 0)
  {
    int cc = cursorcolortable[cursorflash];

    if (cursorflashdelay >= 5)
    {
      cursorflashdelay %= 5;
      cursorflash++;
      cursorflash &= 3;
      fliptoscreen();
    }
    else
    {
      if (rawkey) fliptoscreen();
    }
    getkey();

    switch(rawkey)
    {
      case KEY_ESC:
      exitfilesel = 0;
      break;

      case KEY_PGUP:
      for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
      {
        if ((!filemode) && (filepos > 0))
        {
          filepos--;
          if (!direntry[filepos].attribute) strcpy(name, direntry[filepos].name);
        }
      }
      break;

      case KEY_UP:
      if ((!filemode) && (filepos > 0))
      {
        filepos--;
        if (!direntry[filepos].attribute) strcpy(name, direntry[filepos].name);
      }
      break;

      case KEY_PGDN:
      for (scrrep = PGUPDNREPEAT; scrrep; scrrep--)
      {
        if ((!filemode) && (filepos < files-1))
        {
          filepos++;
          if (!direntry[filepos].attribute) strcpy(name, direntry[filepos].name);
        }
      }
      break;

      case KEY_DOWN:
      if ((!filemode) && (filepos < files-1))
      {
        filepos++;
        if (!direntry[filepos].attribute) strcpy(name, direntry[filepos].name);
      }
      break;

      case KEY_TAB:
      filemode++;
      if (filemode > 2) filemode = 0;
      break;

      case KEY_ENTER:
      switch(filemode)
      {
        case 0:
        switch (direntry[filepos].attribute)
        {
          case 0:
          strcpy(name, direntry[filepos].name);
          exitfilesel = 1;
          break;

          case 1:
          chdir(direntry[filepos].name);
          exitfilesel = 2;
          break;

          case 2:
          if (strlen(direntry[filepos].name))
          {
            if (direntry[filepos].name[strlen(direntry[filepos].name)-1] != '\\')
              strcat(direntry[filepos].name, "\\");
          }
          chdir(direntry[filepos].name);
          exitfilesel = 2;
          break;
        }
        break;

        case 1:
        filemode = 0;
        exitfilesel = 2;
        break;

        case 2:
        exitfilesel = 1;
        break;
      }
      break;
    }
    if (filemode == 1)
    {
      editstring(filter, MAX_FILENAME);
    }
    if (filemode == 2)
    {
      editstring(name, MAX_FILENAME);
    }
    fileview = - VISIBLEFILES / 2 + filepos;


    for (c = 0; c < VISIBLEFILES; c++)
    {
      if ((fileview + c >= 0) && (fileview + c < files))
      {
        switch (direntry[fileview+c].attribute)
        {
          case 0:
          sprintf(textbuffer, "%-60s        ", direntry[fileview+c].name);
          break;

          case 1:
          sprintf(textbuffer, "%-60s   <DIR>", direntry[fileview+c].name);
          break;

          case 2:
          sprintf(textbuffer, "%-60s   <DRV>", direntry[fileview+c].name);
          break;
        }
      }
      else
      {
        sprintf(textbuffer, "                                                                    ");
      }
      color = 7;
      if ((fileview + c) == filepos) color = 10;
      printtext(40-(MAX_FILENAME+10)/2+1, 4+c, color, textbuffer);
      if ((!filemode) && ((fileview + c) == filepos)) printbg(40-(MAX_FILENAME+10)/2+1, 4+c, cc, 68);
    }

    printtext(40-(MAX_FILENAME+10)/2+1, 5+VISIBLEFILES, 15, "FILTER: ");
    sprintf(textbuffer, "%-60s", filter);
    color = 7;
    if (filemode == 1) color = 10;
    printtext(40-(MAX_FILENAME+10)/2+9, 5+VISIBLEFILES, color, textbuffer);
    if (filemode == 1) printbg(40-(MAX_FILENAME+10)/2+9+strlen(filter), 5+VISIBLEFILES, cc, 1);

    printtext(40-(MAX_FILENAME+10)/2+1, 6+VISIBLEFILES, 15, "NAME:   ");
    sprintf(textbuffer, "%-60s", name);
    color = 7;
    if (filemode == 2) color = 10;
    printtext(40-(MAX_FILENAME+10)/2+9, 6+VISIBLEFILES, color, textbuffer);
    if (filemode == 2) printbg(40-(MAX_FILENAME+10)/2+9+strlen(name), 6+VISIBLEFILES, cc, 1);

    if (win_quitted) exitfilesel = 0;
  }
  if (exitfilesel == 2) goto NEWPATH;

  printmainscreen();
  return exitfilesel;
}

void relocator(void)
{
  unsigned char packedsongname[MAX_FILENAME];
  unsigned char packedfilter[MAX_FILENAME];
  unsigned char wavetemp[MAX_WAVELEN * 2];

  int patterns = 0;
  int songs = 0;
  int pattsize = 0;
  int patttblsize = 0;
  int songsize = 0;
  int songtblsize = 0;
  int instrsize = 0;
  int wavetblsize = 0;
  int filttblsize = 0;
  int playersize = 0;
  int totalsize;
  int playerhandle = -1;
  FILE *songhandle;
  int selectdone;

  int c;

  unsigned instradr;
  unsigned wavetbladr;
  unsigned songtbladr;
  unsigned patttbladr;
  unsigned filttbladr;
  unsigned songadr;
  unsigned pattadr;

  unsigned char patttemp[256];

  unsigned char *playerwork = NULL;
  unsigned char *songwork = NULL;
  unsigned char *songtblwork = NULL;
  unsigned char *pattwork = NULL;
  unsigned char *patttblwork = NULL;
  unsigned char *instrwork = NULL;
  unsigned char *wavetblwork = NULL;
  unsigned char *filttblwork = NULL;

  songinit = 0x04;

  clearscreen();
  printblankc(0, 0, 15+16, 80);
  printtext(0, 0, 15+16, "GoatTracker PACKER/RELOCATOR");

  printtext(1, 2, 15, "SELECT PLAYROUTINE: (CURSORS=MOVE, ENTER=SELECT, ESC=CANCEL)");

  selectdone = 0;
  if (multiplier > 1) playerversion = 2;
  if ((multiplier == 1) && (playerversion > 1)) playerversion = 0;

  while (!selectdone)
  {
    switch(playerversion)
    {
      case 0:
      printtext(1, 3, 10, "Standard (author-info, timing marks, no sfx-support)                 ");
      break;

      case 1:
      printtext(1, 3, 10, "Gamemusic (save musicdata only, no author-info/timing marks)         ");
      break;

      case 2:
      printtext(1, 3, 10, "Multispeed (author-info, timing marks, no sfx-support)               ");
      break;
    }

    fliptoscreen();
    waitkey2();

    switch(rawkey)
    {
      case KEY_LEFT:
      case KEY_DOWN:
      playerversion--;
      if (playerversion < 0) playerversion = 1;
      break;

      case KEY_RIGHT:
      case KEY_UP:
      playerversion++;
      if (playerversion > 1) playerversion = 0;
      break;

      case KEY_ESC:
      selectdone = -1;
      break;

      case KEY_ENTER:
      selectdone = 1;
      break;
    }
    if (multiplier > 1) playerversion = 2;
  }
  if (selectdone == -1) goto PRCLEANUP;

  clearscreen();
  printtextc(12, 7, "Packing song...");
  fliptoscreen();

  /* Process song-orderlists */

  /* Calculate amount of songs with nonzero length */
  countpatternlengths();
  for (c = 0; c < MAX_SONGS; c++)
  {
    if ((songlen[c][0]) &&
        (songlen[c][1]) &&
        (songlen[c][2]))
    {
      songs++;
      songsize += songlen[c][0]+2;
      songsize += songlen[c][1]+2;
      songsize += songlen[c][2]+2;
    }
  }

  /* Allocate memory for songtable & song-orderlists */
  songtblsize = songs * 6;
  songtblwork = malloc(songtblsize);
  songwork = malloc(songsize);
  if ((!songtblwork) || (!songwork))
  {
    clearscreen();
    printtextc(12, 15, "OUT OF MEMORY IN PACKER/RELOCATOR!");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }

  /* Generate songorderlists & songtable */
  songsize = 0;
  for (c = 0; c < songs; c++)
  {
    if ((songlen[c][0]) &&
        (songlen[c][1]) &&
        (songlen[c][2]))
    {
      songtblwork[c*3] = songsize & 0xff;
      songtblwork[(c+songs)*3] = songsize >> 8;
      memcpy(&songwork[songsize], songorder[c][0], songlen[c][0]+2);
      songsize += songlen[c][0]+2;

      songtblwork[c*3 + 1] = songsize & 0xff;
      songtblwork[(c+songs)*3 + 1] = songsize >> 8;
      memcpy(&songwork[songsize], songorder[c][1], songlen[c][1]+2);
      songsize += songlen[c][1]+2;

      songtblwork[c*3 + 2] = songsize & 0xff;
      songtblwork[(c+songs)*3 + 2] = songsize >> 8;
      memcpy(&songwork[songsize], songorder[c][2], songlen[c][2]+2);
      songsize += songlen[c][2]+2;
    }
  }

  /* Calculate total size of patterns */
  for (c = 0; c <= highestusedpattern; c++)
  {
    pattsize += packpattern(patttemp, pattern[c], pattlen[c]);
    patterns++;
  }

  patttblsize = patterns * 2;

  patttblwork = malloc(patttblsize);
  pattwork = malloc(pattsize);
  if ((!patttblwork) || (!pattwork))
  {
    clearscreen();
    printtextc(12, 15, "OUT OF MEMORY IN PACKER/RELOCATOR!");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }

  /* This time pack the patterns for real */
  highestusedinstr = 0;
  pattsize = 0;
  for (c = 0; c < patterns; c++)
  {
    patttblwork[c] = pattsize & 0xff;
    patttblwork[c+patterns] = pattsize >> 8;
    pattsize += packpattern(&pattwork[pattsize], pattern[c], pattlen[c]);
  }
  highestusedinstr >>= 3;

  /* Then process instruments */
  if (!highestusedinstr) highestusedinstr = 1;

  instrsize = highestusedinstr * 8;
  instrwork = malloc(instrsize);
  wavetblwork = malloc((MAX_WAVELEN*2) * highestusedinstr);
  if ((!instrwork) || (!wavetblwork))
  {
    clearscreen();
    printtextc(12, 15, "OUT OF MEMORY IN PACKER/RELOCATOR!");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }
  for (c = 1; c <= highestusedinstr; c++)
  {
    int d;
    int instrwavesize;

    instrwork[(c-1)*8+0] = instr[c].ad;
    instrwork[(c-1)*8+1] = instr[c].sr;
    instrwork[(c-1)*8+2] = swapnybbles(instr[c].pulse);
    instrwork[(c-1)*8+3] = (instr[c].pulseadd&0xfe)|(instr[c].pulse&1);
    instrwork[(c-1)*8+4] = instr[c].pulselimitlow >> 4;
    instrwork[(c-1)*8+5] = instr[c].pulselimithigh >> 4;
    instrwork[(c-1)*8+6] = instr[c].filtertype * 4;

    memcpy(wavetemp, wavetable[c], MAX_WAVELEN*2);

    for (d = 0; d < MAX_WAVELEN; d++)
    {
      if (wavetemp[d*2] == 0xff)
      {
        break;
      }
    }
    instrwavesize = d*2 + 2;

    if (wavetblsize < instrwavesize)
    {
      memcpy(&wavetblwork[wavetblsize], wavetemp, instrwavesize);
      instrwork[(c-1)*8+7] = wavetblsize/2 + 1;
      wavetblsize += instrwavesize;
    }
    else
    {
      int duplicate = 0;
      /* Search for duplicate from the bytes already in the wavetable */
      for (d = 0; d <= wavetblsize-instrwavesize; d++)
      {
        if (!memcmp(&wavetblwork[d], wavetemp, instrwavesize))
        {
          duplicate = 1;
          instrwork[(c-1)*8+7] = d/2 + 1;
          break;
        }
      }
      if (!duplicate)
      {
        memcpy(&wavetblwork[wavetblsize], wavetemp, instrwavesize);
        instrwork[(c-1)*8+7] = wavetblsize/2 + 1;
        wavetblsize += instrwavesize;
      }
    }
  }
  if (wavetblsize > 512)
  {
    clearscreen();
    printtextc(12, 15, "WAVETABLE EXCEEDS 512 BYTES - SONG WOULDN'T PLAY PROPERLY ON C64");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }

  /* Process filtertable */
  filttblsize = 4 * getfilteramount();
  filttblwork = malloc(filttblsize);
  if (!filttblwork)
  {
    clearscreen();
    printtextc(12, 15, "OUT OF MEMORY IN PACKER/RELOCATOR!");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }
  {
    int c;
    memcpy(filttblwork, filtertable, filttblsize);
    for (c = 0; c < filttblsize; c += 4)
    {
      if (!filttblwork[c])
        filttblwork[c+2] = -filttblwork[c+2]; /* Negate all cutoff speeds */
      filttblwork[c+3] *= 4; /* Multiply all Next Step values by 4 */
    }
  }

  switch (playerversion)
  {
    case 0:
    if (hardrestart == 2)
      playerhandle = io_open("player1.bin");
    else
      playerhandle = io_open("player4.bin");
    break;

    case 1:
    playerhandle = io_open("player2.bin");
    break;

    case 2:
    playerhandle = io_open("player3.bin");
    break;
  }

  if (playerhandle == -1)
  {
    clearscreen();
    printtextc(12, 15, "CANNOT READ PLAYER.BIN FROM DATAFILE");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }

  playersize = io_lseek(playerhandle, 0, SEEK_END);
  playerwork = malloc(playersize);
  if (!playerwork)
  {
    io_close(playerhandle);
    clearscreen();
    printtextc(12, 15, "OUT OF MEMORY IN PACKER/RELOCATOR!");
    fliptoscreen();
    waitkey2();
    goto PRCLEANUP;
  }
  io_lseek(playerhandle, 0, SEEK_SET);
  io_read(playerhandle, playerwork, playersize);
  io_close(playerhandle);

  totalsize = playersize+songtblsize+songsize+patttblsize+pattsize+instrsize+wavetblsize+filttblsize;

  clearscreen();
  printblankc(0, 0, 15+16, 80);
  printtext(0, 0, 15+16, "GoatTracker PACKER/RELOCATOR");

  sprintf(textbuffer, "PACKING RESULTS:");
  printtext(1, 2, 15, textbuffer);
  sprintf(textbuffer, "Playroutine:     %d bytes", playersize);
  printtext(1, 4, 7, textbuffer);
  sprintf(textbuffer, "Songtable:       %d bytes", songtblsize);
  printtext(1, 5, 7, textbuffer);
  sprintf(textbuffer, "Song-orderlists: %d bytes", songsize);
  printtext(1, 6, 7, textbuffer);
  sprintf(textbuffer, "Patterntable:    %d bytes", patttblsize);
  printtext(1, 7, 7, textbuffer);
  sprintf(textbuffer, "Patterns:        %d bytes", pattsize);
  printtext(1, 8, 7, textbuffer);
  sprintf(textbuffer, "Instruments:     %d bytes", instrsize);
  printtext(1, 9, 7, textbuffer);
  sprintf(textbuffer, "Wavetables:      %d bytes", wavetblsize);
  printtext(1, 10, 7, textbuffer);
  sprintf(textbuffer, "Filtertable:     %d bytes", filttblsize);
  printtext(1, 11, 7, textbuffer);
  sprintf(textbuffer, "Total size:      %d bytes", totalsize);
  printtext(1, 13, 7, textbuffer);
  fliptoscreen();

  sprintf(textbuffer, "SELECT START ADDRESS: (CURSORS=MOVE, ENTER=SELECT, ESC=CANCEL)");
  printtext(1, 15, 15, textbuffer);

  selectdone = 0;
  while (!selectdone)
  {
    int badaddr = 0;

    if (playeradr + totalsize > 0x10000)
    {
      sprintf(textbuffer, "$%04X Goes past end of memory!   ", playeradr);
      printtext(1, 16, 14, textbuffer);
      badaddr = 1;
    }
    if (!badaddr)
    {
      sprintf(textbuffer, "$%04X Ok address.                ", playeradr);
      printtext(1, 16, 10, textbuffer);
    }
    fliptoscreen();
    waitkey2();

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

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

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

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

      case KEY_ESC:
      selectdone = -1;
      break;

      case KEY_ENTER:
      if (!badaddr) selectdone = 1;
      break;
    }
  }

  if (selectdone == -1) goto PRCLEANUP;

  if (playerversion != 1)
  {
    sprintf(textbuffer, "SELECT ZEROPAGE BASEADDRESS: (CURSORS=MOVE, ENTER=SELECT, ESC=CANCEL)");
    printtext(1, 18, 15, textbuffer);

    selectdone = 0;
    while (!selectdone)
    {
      sprintf(textbuffer, "$%02X", zeropageadr);
      printtext(1, 19, 10, textbuffer);
      fliptoscreen();
      waitkey2();

      switch(rawkey)
      {
        case KEY_LEFT:
        zeropageadr -= 3;
        case KEY_DOWN:
        zeropageadr -= 1;
        if (zeropageadr < 0x02) zeropageadr = 0xfc;
        break;

        case KEY_RIGHT:
        zeropageadr += 3;
        case KEY_UP:
        zeropageadr += 1;
        if (zeropageadr > 0xfc) zeropageadr = 0x02;
        break;

        case KEY_ESC:
        selectdone = -1;
        break;

        case KEY_ENTER:
        selectdone = 1;
        break;
      }
    }

    if (selectdone == -1) goto PRCLEANUP;
  }

  instradr = playeradr + playersize;
  wavetbladr = instradr + instrsize;
  songtbladr = wavetbladr + wavetblsize;
  patttbladr = songtbladr + songtblsize;
  filttbladr = patttbladr + patttblsize;
  songadr = filttbladr + filttblsize;
  pattadr = songadr + songsize;

  /* Relocate song & patterntables now that all addresses are known */
  for (c = 0; c < songs; c++)
  {
    int adr;

    adr = songtblwork[c*3] | (songtblwork[(c+songs)*3] << 8);
    adr += songadr;
    songtblwork[c*3] = adr & 0xff;
    songtblwork[(c+songs)*3] = adr >> 8;

    adr = songtblwork[c*3+1] | (songtblwork[(c+songs)*3+1] << 8);
    adr += songadr;
    songtblwork[c*3+1] = adr & 0xff;
    songtblwork[(c+songs)*3+1] = adr >> 8;

    adr = songtblwork[c*3+2] | (songtblwork[(c+songs)*3+2] << 8);
    adr += songadr;
    songtblwork[c*3+2] = adr & 0xff;
    songtblwork[(c+songs)*3+2] = adr >> 8;
  }
  for (c = 0; c < patterns; c++)
  {
    int adr;

    adr = patttblwork[c] | (patttblwork[c+patterns] << 8);
    adr += pattadr;
    patttblwork[c] = adr & 0xff;
    patttblwork[c+patterns] = adr >> 8;
  }

  /* Copy author info to players 0 & 2 */
  if ((playerversion == 0) || (playerversion == 2))
  {
    for (c = 0; c < 32; c++)
    {
      playerwork[32+c] = authorname[c];
      /* Convert 0 to space */
      if (playerwork[32+c] == 0) playerwork[32+c] = 0x20;
    }
  }

  /* Now relocate player */

  /* Gamemusic mode: only save offsets */
  if (playerversion == 1)
  {
    playerwork[0] = instrsize;
    playerwork[1] = wavetblsize/2;
    playerwork[2] = songtblsize/2;
    playerwork[3] = patttblsize/2;

    /* Make gamemusic funktable */
    playerwork[4] = (filtertable[2] >> 4)-1;
    playerwork[5] = (filtertable[2] & 0xf)-1;
    goto RELOCDONE;
  }

  c = 0;
  for (;;)
  {
    INSTRUCTION *opptr;
    unsigned char opcode;

    if ((playerversion == 0) && (c == 16)) c = 64; /* Hop over author-info */
    if ((playerversion == 2) && (c == 16))
    {
      if (hardrestart == 2)
        playerwork[31] = multiplier * 2; /* Store hard restart-counter */
      else
        playerwork[31] = multiplier * 2 - 1;
      c = 64; /* Hop over author-info */
    }

    opcode = playerwork[c];
    if (opcode == 0x00) goto MAKEFUNKTABLE; /* Reached data, relocation complete */
    if (opcode == 0x01) goto MAKEFUNKTABLE; /* Reached data, relocation complete */
    opptr = &asmtable[0];

    for (;;)
    {
      if (opptr->opcode == 0x100)
      {
        clearscreen();
        printblankc(0, 0, 15+16, 80);
        printtext(0, 0, 15+16, "GoatTracker PACKER/RELOCATOR");
        printtextc(12, 15, "ILLEGAL OPCODE FOUND IN PLAYROUTINE!");
        fliptoscreen();
        waitkey2();
        goto PRCLEANUP;
      }

      if (opptr->opcode == opcode) break;
      opptr++;
    }

    /* 4 is the special code for zeropage relocation */
    if (opptr->length == 4)
    {
      playerwork[c+1] -= 0xfc;
      playerwork[c+1] += zeropageadr;
      c -= 2;
    }

    /* Hardrestart param. */
    if (opcode == 0xff)
    {
      playerwork[c] = 0xa9;
      playerwork[c+1] = adparam;
    }

    /* 3 bytes long instructions need relocation */
    if (opptr->length == 3)

    {
      unsigned char *adr = &playerwork[c+1];

      switch(playerwork[c+2])
      {
        /* Player code */
        default:
        sixteenbitsub(adr, 0x1000);
        sixteenbitadd(adr, playeradr);
        break;

        /* Reference to instrument data */
        case 0x40:
        sixteenbitsub(adr, 0x4000);
        sixteenbitadd(adr, instradr - 8);
        break;

        /* Reference to wavetable data */
        case 0x41:
        sixteenbitsub(adr, 0x4100);
        sixteenbitadd(adr, wavetbladr - 1);
        break;

        /* Reference to notetable data */
        case 0x42:
        sixteenbitsub(adr, 0x4200);
        sixteenbitadd(adr, wavetbladr+wavetblsize/2 - 1);
        break;

        /* Reference to songtable, lowbytes */
        case 0x43:
        sixteenbitsub(adr, 0x4300);
        sixteenbitadd(adr, songtbladr);
        break;

        /* Reference to songtable, highbytes */
        case 0x44:
        sixteenbitsub(adr, 0x4400);
        sixteenbitadd(adr, songtbladr + songs*3);
        break;

        /* Reference to patterntable, lowbytes */
        case 0x45:
        sixteenbitsub(adr, 0x4500);
        sixteenbitadd(adr, patttbladr);
        break;

        /* Reference to patterntable, highbytes */
        case 0x46:
        sixteenbitsub(adr, 0x4600);
        sixteenbitadd(adr, patttbladr + patterns);
        break;

        /* Reference to filtertable */
        case 0x47:
        sixteenbitsub(adr, 0x4700);
        sixteenbitadd(adr, filttbladr);
        break;

        /* Reference to SID registers */
        case 0xd4:
        break;
      }
    }
    c += opptr->length;
  }
  MAKEFUNKTABLE:
  playerwork[c] = (filtertable[2] >> 4)-1;
  playerwork[c+1] = (filtertable[2] & 0xf)-1;

  RELOCDONE:

  /* Now ask for fileformat */

  printtext(1, 21, 15, "SELECT FORMAT TO SAVE IN: (CURSORS=MOVE, ENTER=SELECT, ESC=CANCEL)");

  selectdone = 0;

  while (!selectdone)
  {
    switch(fileformat)
    {
      case FORMAT_SID:
      printtext(1, 22, 10, "SID - SIDPlay music file format          ");
      strcpy(packedfilter, "*.sid");
      break;

      case FORMAT_PRG:
      printtext(1, 22, 10, "PRG - C64 native format                  ");
      strcpy(packedfilter, "*.prg");
      break;

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

    fliptoscreen();
    waitkey2();

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

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

      case KEY_ESC:
      selectdone = -1;
      break;

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

  memset(packedsongname, 0, sizeof packedsongname);

  /* Now ask for filename */
  if (!fileselector(packedsongname, packedfilter, "Save Music+Playroutine", 2))
    goto PRCLEANUP;

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

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

        case FORMAT_SID:
        strcat(packedsongname, ".sid");
        break;
      }
    }
  }
  songhandle = fopen(packedsongname, "wb");
  if (songhandle)
  {
    unsigned char speedcode[] = {0xa2,0x00,0x8e,0x04,0xdc,0xa2,0x00,0x8e,0x05,0xdc};

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

      /* Load address */
      byte = 0x00;
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Init address */
      if (multiplier > 1)
      {
        unsigned speedvalue;
        byte = (playeradr-10) >> 8;
        fwrite(&byte, sizeof byte, 1, songhandle);
        byte = (playeradr-10) & 0xff;
        fwrite(&byte, sizeof byte, 1, songhandle);

        if (ntsc) speedvalue = 0x4025 / multiplier;
        else speedvalue = 0x4cc7 / multiplier;
        speedcode[1] = speedvalue & 0xff;
        speedcode[6] = speedvalue >> 8;
      }
      else
      {
        byte = (playeradr) >> 8;
        fwrite(&byte, sizeof byte, 1, songhandle);
        byte = (playeradr) & 0xff;
        fwrite(&byte, sizeof byte, 1, songhandle);
      }

      /* Play address */
      byte = (playeradr+3) >> 8;
      fwrite(&byte, sizeof byte, 1, songhandle);
      byte = (playeradr+3) & 0xff;
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Number of subtunes */
      byte = 0x00;
      fwrite(&byte, sizeof byte, 1, songhandle);
      byte = songs;
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Default subtune */
      byte = 0x00;
      fwrite(&byte, sizeof byte, 1, songhandle);
      byte = 0x01;
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Song speed bits */
      byte = 0x00;
      if ((ntsc) || (multiplier > 1)) byte = 0xff;
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Songname etc. */
      fwrite(songname, sizeof songname, 1, songhandle);
      fwrite(authorname, sizeof authorname, 1, songhandle);
      fwrite(copyrightname, sizeof copyrightname, 1, songhandle);

      /* Flags */
      byte = 0x00;
      fwrite(&byte, sizeof byte, 1, songhandle);
      if (ntsc) byte = 0x02;
        else byte = 0x00;
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Reserved longword */
      byte = 0x00;
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);
      fwrite(&byte, sizeof byte, 1, songhandle);

      /* Load address */
      if (multiplier > 1)
      {
        byte = (playeradr - 10) & 0xff;
        fwrite(&byte, sizeof byte, 1, songhandle);
        byte = (playeradr - 10) >> 8;
        fwrite(&byte, sizeof byte, 1, songhandle);
      }
      else
      {
        byte = (playeradr) & 0xff;
        fwrite(&byte, sizeof byte, 1, songhandle);
        byte = (playeradr) >> 8;
        fwrite(&byte, sizeof byte, 1, songhandle);
      }
      if (multiplier > 1) fwrite(speedcode, 10, 1, songhandle);
    }

    fwrite(playerwork, playersize, 1, songhandle);
    fwrite(instrwork, instrsize, 1, songhandle);
    for (c = 0; c < wavetblsize/2; c++)
    {
      fwrite(&wavetblwork[c*2], 1, 1, songhandle);
    }
    for (c = 0; c < wavetblsize/2; c++)
    {
      fwrite(&wavetblwork[c*2+1], 1, 1, songhandle);
    }
    fwrite(songtblwork, songtblsize, 1, songhandle);
    fwrite(patttblwork, patttblsize, 1, songhandle);
    fwrite(filttblwork, filttblsize, 1, songhandle);
    fwrite(songwork, songsize, 1, songhandle);
    fwrite(pattwork, pattsize, 1, songhandle);
    fclose(songhandle);
  }

  PRCLEANUP:
  if (pattwork) free(pattwork);
  if (patttblwork) free(patttblwork);
  if (songwork) free(songwork);
  if (songtblwork) free(songtblwork);
  if (instrwork) free(instrwork);
  if (wavetblwork) free(wavetblwork);
  if (playerwork) free(playerwork);
  printmainscreen();
}

void sixteenbitadd(unsigned char *adr, int value)
{
  int orglowbyte = adr[0];
  adr[0] += value;
  adr[1] += value >> 8;
  if (adr[0] < orglowbyte) adr[1] += 1;
}

void sixteenbitsub(unsigned char *adr, int value)
{
  int orglowbyte = adr[0];
  adr[0] -= value;
  adr[1] -= value >> 8;
  if (adr[0] > orglowbyte) adr[1] -= 1;
}



int packpattern(unsigned char *dest, unsigned char *src, int rows)
{
  unsigned char temp1[256];
  unsigned char temp2[256];
  int count;
  unsigned char instr = 0;
  int command;
  int databyte;
  int destsizeim;
  int destsize;

  /*
   * First write the pattern in such format that zero instruments are
   * eliminated
   */
  for (count = 0; count < rows; count++)
  {
    if (!count)
    {
      temp1[count*3] = src[count*3];
      temp1[count*3+1] = src[count*3+1];
      temp1[count*3+2] = src[count*3+2];
      instr = src[count*3+1] & 0xf8;
      if (instr > highestusedinstr) highestusedinstr = instr;
    }
    else
    {
      if (src[count*3+1] & 0xf8)
      {
        temp1[count*3] = src[count*3];
        temp1[count*3+1] = src[count*3+1];
        temp1[count*3+2] = src[count*3+2];
        instr = src[count*3+1] & 0xf8;
        if (instr > highestusedinstr) highestusedinstr = instr;
      }
      else
      {
        temp1[count*3] = src[count*3];
        temp1[count*3+1] = src[count*3+1] | instr;
        temp1[count*3+2] = src[count*3+2];
      }
    }
    /* Decrease databyte of all tempo commands for the playroutine */
    if ((temp1[count*3+1] & 0x07) == CMD_SETTEMPO)
    {
      /* Do not decrease timing mark */
      if (temp1[count*3+2] < 0xc0)
      {
        /* Do not touch funktempo */
        if (temp1[count*3+2] >= 3)
          temp1[count*3+2]--;
      }
      else
      {
        /* Game playroutine doesn't understand timing marks so remove them */
        if (playerversion == 1)
        {
          temp1[count*3+1] &= 0xf8;
          temp1[count*3+2] = 0;
        }
      }
    }

    /* Swap nybbles of vibrato command for the playroutine */
    if ((temp1[count*3+1] & 0x07) == CMD_VIBRATO)
    {
      temp1[count*3+2] = swapnybbles(temp1[count*3+2]);
    }

    /* Multiply set filter command's parameter by 4 for the playroutine */
    if ((temp1[count*3+1] & 0x07) == CMD_SETFILTER)
    {
      temp1[count*3+2] *= 4;
    }

    /* Toggle high bit of arpeggio command for the playroutine */
    if ((temp1[count*3+1] & 0x07) == CMD_ARPEGGIO)
    {
      if (temp1[count*3+2]) temp1[count*3+2] ^= 0x80;
    }
  }

  /*
   * Then optimize rows where command & databyte don't change
   */
  command = -1;
  databyte = -1;

  destsizeim = 0;
  for (count = 0; count < rows; count++)
  {
    if ((temp1[count*3+1] != command) ||
        (temp1[count*3+2] != databyte))
    {
      command = temp1[count*3+1];
      databyte = temp1[count*3+2];
      temp2[destsizeim++] = temp1[count*3];
      temp2[destsizeim++] = command;
      temp2[destsizeim++] = databyte;
    }
    else
    {
      temp2[destsizeim++] = temp1[count*3] + 0x60;
    }
  }

  /*
   * Then optimize long singlebyte rests with "packed rest" (final phase)
   */
  destsize = 0;
  for (count = 0; count < destsizeim;)
  {
    if (temp2[count] < 0x60)
    {
      dest[destsize++] = temp2[count++];
      dest[destsize++] = temp2[count++];
      dest[destsize++] = temp2[count++];
    }
    else
    {
      /* Never pack the last row with a packed rest */
      if ((temp2[count] != 0x60+REST) || (count >= destsizeim - 1))
      {
        dest[destsize++] = temp2[count++];
      }
      else
      {
        int d;
        for (d = count; d < destsizeim-1; )
        {
          if (temp2[d] == 0x60+REST)
          {
            d++;
            if (d - count == 64) break;
          }
          else break;
        }
        d -= count;

        if (d > 1)
        {
          dest[destsize++] = -d;
          count += d;
        }
        else
        {
          dest[destsize++] = temp2[count++];
        }
      }
    }
  }
  dest[destsize++] = ENDPATT;

  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;
  }
}

void setfilter(unsigned char filternum)
{
  unsigned char *filtptr;
  filternum &= 0x3f;

  filtptr = &filtertable[filternum*4];
  filtertime = 0;

  if (filtptr[0])
  {
    filterctrl = filtptr[0];
    filtertype = filtptr[1];
    if (filtptr[2]) filtercutoff = filtptr[2];
  }
  else
  {
    filtertime = filtptr[1];
    filtercutoffadd = filtptr[2];
  }
  filterstep = filtptr[3];
}

int getfilteramount(void)
{
  int amount = 0, c, d;
  for (c = 0; c < MAX_PATT; c++)
  {
    for (d = 0; d <= MAX_PATTROWS; d++)
    {
      if (pattern[c][d*3] == ENDPATT) break;
      if ((pattern[c][d*3 + 1] & 0x07) == CMD_SETFILTER)
      {
        if (amount < pattern[c][d*3+2]) amount = pattern[c][d*3+2];
      }
    }
  }
  for (c = 0; c < MAX_INSTR; c++)
  {
    if (amount < instr[c].filtertype) amount = instr[c].filtertype;
  }
  for (c = 0; c < MAX_FILT; c++)
  {
    if (filtertable[c*4+3] > amount) amount = filtertable[c*4+3];
  }
  amount++;

  return amount;
}
