//
// GOATTRACKER v2 playroutine
//

#define GPLAY_C

#include "goattrk2.h"

unsigned char freqtbllo[] = {
  0x17,0x27,0x39,0x4b,0x5f,0x74,0x8a,0xa1,0xba,0xd4,0xf0,0x0e,
  0x2d,0x4e,0x71,0x96,0xbe,0xe8,0x14,0x43,0x74,0xa9,0xe1,0x1c,
  0x5a,0x9c,0xe2,0x2d,0x7c,0xcf,0x28,0x85,0xe8,0x52,0xc1,0x37,
  0xb4,0x39,0xc5,0x5a,0xf7,0x9e,0x4f,0x0a,0xd1,0xa3,0x82,0x6e,
  0x68,0x71,0x8a,0xb3,0xee,0x3c,0x9e,0x15,0xa2,0x46,0x04,0xdc,
  0xd0,0xe2,0x14,0x67,0xdd,0x79,0x3c,0x29,0x44,0x8d,0x08,0xb8,
  0xa1,0xc5,0x28,0xcd,0xba,0xf1,0x78,0x53,0x87,0x1a,0x10,0x71,
  0x42,0x89,0x4f,0x9b,0x74,0xe2,0xf0,0xa6,0x0e,0x33,0x20,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,0x0d,0x0d,0x0e,0x0f,0x10,
  0x11,0x12,0x13,0x14,0x15,0x17,0x18,0x1a,0x1b,0x1d,0x1f,0x20,
  0x22,0x24,0x27,0x29,0x2b,0x2e,0x31,0x34,0x37,0x3a,0x3e,0x41,
  0x45,0x49,0x4e,0x52,0x57,0x5c,0x62,0x68,0x6e,0x75,0x7c,0x83,
  0x8b,0x93,0x9c,0xa5,0xaf,0xb9,0xc4,0xd0,0xdd,0xea,0xf8,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};

CHN chn[MAX_CHN];
unsigned char filterctrl = 0;
unsigned char filtertype = 0;
unsigned char filtercutoff = 0;
unsigned char filtertime = 0;
unsigned char filterptr = 0;
unsigned char funktable[2];
unsigned char masterfader = 0x0f;
int psnum = 0;
int songinit = PLAY_STOPPED;
int lastsonginit = 0;
int startpattpos = 0;

void sequencer(int c, CHN *cptr);

void initchannels(void)
{
  int c;
  CHN *cptr = &chn[0];

  memset(chn, 0, sizeof chn);

  for (c = 0; c < MAX_CHN; c++)
  {
    chn[c].trans = 0;
    chn[c].instr = 1;
    if (multiplier)
      cptr->tempo = 6*multiplier-1;
    else
      cptr->tempo = 6-1;
    cptr++;
  }

  if (multiplier)
  {
    funktable[0] = 9*multiplier-1;
    funktable[1] = 6*multiplier-1;
  }
  else
  {
    funktable[0] = 9-1;
    funktable[1] = 6-1;
  }
}

void initsong(int num, int mode)
{
  sound_suspend();
  songinit = PLAY_STOPPED;
  psnum = num;
  songinit = mode;
  startpattpos = 0;
  sound_flush();
}

void initsongpos(int num, int mode, int pattpos)
{
  sound_suspend();
  songinit = PLAY_STOPPED;
  psnum = num;
  songinit = mode;
  startpattpos = pattpos;
  sound_flush();
}

void stopsong(void)
{
  if (songinit != PLAY_STOPPED)
  {
    sound_suspend();
    songinit = PLAY_STOP;
    sound_flush();
  }
}

void rewindsong(void)
{
  if (lastsonginit == PLAY_BEGINNING) lastsonginit = PLAY_POS;
  initsong(psnum, lastsonginit);
}

void playtestnote(int note, int ins, int chnnum)
{
  if (note == KEYON) return;
  if ((note == REST) || (note == KEYOFF))
  {
    releasenote(chnnum);
    return;
  }

  if (!(instr[ins].gatetimer & 0x40))
  {
    chn[chnnum].gate = 0xfe; // Keyoff
    if (!(instr[ins].gatetimer & 0x80))
    {
      sidreg[0x5+chnnum*7] = adparam>>8; // Hardrestart
      sidreg[0x6+chnnum*7] = adparam&0xff;
    }
  }

  chn[chnnum].instr = ins;
  chn[chnnum].newnote = note;
  if (songinit == PLAY_STOPPED)
  {
    chn[chnnum].tick = (instr[ins].gatetimer & 0x3f)+1;
    chn[chnnum].gatetimer = instr[ins].gatetimer & 0x3f;
  }
}

void releasenote(int chnnum)
{
  chn[chnnum].gate = 0xfe;
}

void mutechannel(int chnnum)
{
  chn[chnnum].mute ^= 1;
}

int isplaying(void)
{
  return (songinit != PLAY_STOPPED);
}

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

  if (songinit == PLAY_STOP)
    followplay = 0;

  if ((songinit > 0) && (songinit < PLAY_STOPPED))
  {
    lastsonginit = songinit;

    filterctrl = 0;
    filterptr = 0;

    resettime();

    if ((songinit == 0x02) || (songinit == 0x03))
    {
      if ((espos[0] >= songlen[psnum][0]) || (espos[1] >= songlen[psnum][1]) || (espos[2] >= songlen[psnum][2]))
         songinit = 0x01;
    }

    for (c = 0; c < MAX_CHN; c++)
    {
      cptr->songptr = 0;
      cptr->command = 0;
      cptr->cmddata = 0;
      cptr->newcommand = 0;
      cptr->newcmddata = 0;
      cptr->advance = 1;
      cptr->wave = 0;
      cptr->ptr[WTBL] = 0;
      cptr->newnote = 0;
      cptr->repeat = 0;
      if (multiplier)
        cptr->tick = 6*multiplier-1;
      else
        cptr->tick = 6-1;
      cptr->gatetimer = instr[1].gatetimer & 0x3f;
      cptr->pattptr = 0x7fffffff;
      if (cptr->tempo < 2) cptr->tempo = 0;

      switch (songinit)
      {
        case PLAY_BEGINNING:
        if (multiplier)
        {
          funktable[0] = 9*multiplier-1;
          funktable[1] = 6*multiplier-1;
          cptr->tempo = 6*multiplier-1;
        }
        else
        {
          funktable[0] = 9-1;
          funktable[1] = 6-1;
          cptr->tempo = 6-1;
        }
        if ((instr[MAX_INSTR-1].ad >= 2) && (!(instr[MAX_INSTR-1].ptr[WTBL])))
          cptr->tempo = instr[MAX_INSTR-1].ad - 1;
        cptr->trans = 0;
        cptr->instr = 1;
        sequencer(c, cptr);
        break;

        case PLAY_PATTERN:
        cptr->advance = 0;
        cptr->pattptr = startpattpos * 4;
        cptr->pattnum = epnum[c];
        if (cptr->pattptr >= (pattlen[cptr->pattnum] * 4))
          cptr->pattptr = 0;
        break;

        case PLAY_POS:
        cptr->songptr = espos[c];
        sequencer(c, cptr);
        break;
      }
      cptr++;
    }
    if (songinit != PLAY_STOP)
      songinit = 0;
    else
      songinit = PLAY_STOPPED;
    if ((!songlen[psnum][0]) || (!songlen[psnum][1]) || (!songlen[psnum][2]))
      songinit = PLAY_STOPPED; // Zero length song

    startpattpos = 0;
  }
  else
  {
    if (filterptr)
    {
      // Filter jump
      if (ltable[FTBL][filterptr-1] == 0xff)
      {
        filterptr = rtable[FTBL][filterptr-1];
        if (!filterptr) goto FILTERSTOP;
      }

      if (!filtertime)
      {
        // Filter set
        if (ltable[FTBL][filterptr-1] >= 0x80)
        {
          filtertype = ltable[FTBL][filterptr-1] & 0x70;
          filterctrl = rtable[FTBL][filterptr-1];
          filterptr++;
          // Can be combined with cutoff set
          if (ltable[FTBL][filterptr-1] == 0x00)
          {
            filtercutoff = rtable[FTBL][filterptr-1];
            filterptr++;
          }
        }
        else
        {
          // New modulation step
          if (ltable[FTBL][filterptr-1])
            filtertime = ltable[FTBL][filterptr-1];
          else
          {
            // Cutoff set
            filtercutoff = rtable[FTBL][filterptr-1];
            filterptr++;
          }
        }
      }
      // Filter modulation
      if (filtertime)
      {
        filtercutoff += rtable[FTBL][filterptr-1];
        filtertime--;
        if (!filtertime) filterptr++;
      }
    }
    FILTERSTOP:
    sidreg[0x15] = 0x00;
    sidreg[0x16] = filtercutoff;
    sidreg[0x17] = filterctrl;
    sidreg[0x18] = filtertype | masterfader;

    for (c = 0; c < MAX_CHN; c++)
    {
      iptr = &instr[cptr->instr];

      // Reset tempo in jammode
      if ((songinit == PLAY_STOPPED) && (cptr->tempo < 2))
      {
        if (multiplier)
          cptr->tempo = 6*multiplier-1;
        else
          cptr->tempo = 6-1;
      }

      // Decrease tick
      cptr->tick--;
      if (!cptr->tick) goto TICK0;

      // Tick N
      // Reload counter
      if (cptr->tick >= 0x80)
      {
        if (cptr->tempo >= 2)
          cptr->tick = cptr->tempo;
        else
        {
          // Set funktempo, switch between 2 values
          cptr->tick = funktable[cptr->tempo];
          cptr->tempo ^= 1;
        }
        // Check for illegally high gatetimer and stop the song in this case
        if (cptr->gatetimer > cptr->tick)
          stopsong();
      }
      goto WAVEEXEC;

      // Tick 0
      TICK0:
      // Advance in sequencer
      sequencer(c, cptr);

      // Get gatetimer compare-value
      cptr->gatetimer = iptr->gatetimer & 0x3f;

      // New note init
      if (cptr->newnote)
      {
        cptr->note = cptr->newnote-FIRSTNOTE;
        cptr->command = 0;
        cptr->vibdelay = iptr->vibdelay;
        cptr->cmddata = iptr->ptr[STBL];
        if (cptr->newcommand != CMD_TONEPORTA)
        {
          if (iptr->firstwave)
          {
            if (iptr->firstwave >= 0xfe) cptr->gate = iptr->firstwave;
            else
            {
              cptr->wave = iptr->firstwave;
              cptr->gate = 0xff;
            }
          }


          cptr->ptr[WTBL] = iptr->ptr[WTBL];

          if (cptr->ptr[WTBL])
          {
            // Stop the song in case of jumping into a jump
            if (ltable[WTBL][cptr->ptr[WTBL]-1] == 0xff)
              stopsong();
          }
          if (iptr->ptr[PTBL])
          {
            cptr->ptr[PTBL] = iptr->ptr[PTBL];
            cptr->pulsetime = 0;
            if (cptr->ptr[PTBL])
            {
              // Stop the song in case of jumping into a jump
              if (ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
                stopsong();
            }
          }
          if (iptr->ptr[FTBL])
          {
            filterptr = iptr->ptr[FTBL];
            filtertime = 0;
            if (filterptr)
            {
              // Stop the song in case of jumping into a jump
              if (ltable[FTBL][filterptr-1] == 0xff)
                stopsong();
            }
          }
          sidreg[0x5+7*c] = iptr->ad;
          sidreg[0x6+7*c] = iptr->sr;
        }
      }

      // Tick 0 effects

      switch (cptr->newcommand)
      {
        case CMD_DONOTHING:
        cptr->command = 0;
        cptr->cmddata = iptr->ptr[STBL];
        break;

        case CMD_PORTAUP:
        case CMD_PORTADOWN:
        cptr->vibtime = 0;
        cptr->command = cptr->newcommand;
        cptr->cmddata = cptr->newcmddata;
        break;

        case CMD_TONEPORTA:
        case CMD_VIBRATO:
        cptr->command = cptr->newcommand;
        cptr->cmddata = cptr->newcmddata;
        break;

        case CMD_SETAD:
        sidreg[0x5+7*c] = cptr->newcmddata;
        break;

        case CMD_SETSR:
        sidreg[0x6+7*c] = cptr->newcmddata;
        break;

        case CMD_SETWAVE:
        cptr->wave = cptr->newcmddata;
        break;

        case CMD_SETWAVEPTR:
        cptr->ptr[WTBL] = cptr->newcmddata;
        cptr->wavetime = 0;
        if (cptr->ptr[WTBL])
        {
          // Stop the song in case of jumping into a jump
          if (ltable[WTBL][cptr->ptr[WTBL]-1] == 0xff)
            stopsong();
        }
        break;

        case CMD_SETPULSEPTR:
        cptr->ptr[PTBL] = cptr->newcmddata;
        cptr->pulsetime = 0;
        if (cptr->ptr[PTBL])
        {
          // Stop the song in case of jumping into a jump
          if (ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
            stopsong();
        }
        break;

        case CMD_SETFILTERPTR:
        filterptr = cptr->newcmddata;
        filtertime = 0;
        if (filterptr)
        {
          // Stop the song in case of jumping into a jump
          if (ltable[FTBL][filterptr-1] == 0xff)
            stopsong();
        }
        break;

        case CMD_SETFILTERCTRL:
        filterctrl = cptr->newcmddata;
        if (!filterctrl) filterptr = 0;
        break;

        case CMD_SETFILTERCUTOFF:
        filtercutoff = cptr->newcmddata;
        break;

        case CMD_SETMASTERVOL:
        if (cptr->newcmddata < 0x10)
          masterfader = cptr->newcmddata;
        break;

        case CMD_FUNKTEMPO:
        if (cptr->newcmddata)
        {
          funktable[0] = ltable[STBL][cptr->newcmddata-1]-1;
          funktable[1] = rtable[STBL][cptr->newcmddata-1]-1;
        }
        chn[0].tempo = 0;
        chn[1].tempo = 0;
        chn[2].tempo = 0;
        break;

        case CMD_SETTEMPO:
        {
          unsigned char newtempo = cptr->newcmddata & 0x7f;

          if (newtempo >= 3) newtempo--;
          if (cptr->newcmddata >= 0x80)
            cptr->tempo = newtempo;
          else
          {
            chn[0].tempo = newtempo;
            chn[1].tempo = newtempo;
            chn[2].tempo = newtempo;
          }
        }
        break;
      }
      if (cptr->newnote)
      {
        cptr->newnote = 0;
        if (cptr->newcommand != CMD_TONEPORTA) goto NEXTCHN;
      }

      WAVEEXEC:
      if (cptr->ptr[WTBL])
      {
        unsigned char wave = ltable[WTBL][cptr->ptr[WTBL]-1];
        unsigned char note = rtable[WTBL][cptr->ptr[WTBL]-1];

        if (wave > WAVELASTDELAY)
        {
          // Normal waveform values
          if (wave < WAVESILENT) cptr->wave = wave;
          // Values without waveform selected
          if ((wave >= WAVESILENT) && (wave <= WAVELASTSILENT)) cptr->wave = wave & 0xf;
          // Command execution from wavetable
          if ((wave >= WAVECMD) && (wave <= WAVELASTCMD))
          {
            unsigned char param = rtable[WTBL][cptr->ptr[WTBL]-1];
            switch (wave & 0xf)
            {
              case CMD_DONOTHING:
              case CMD_SETWAVEPTR:
              case CMD_FUNKTEMPO:
              stopsong();
              break;

              case CMD_PORTAUP:
              {
                unsigned short speed = 0;
                if (param)
                {
                  speed = (ltable[STBL][param-1] << 8) | rtable[STBL][param-1];
                }
                if (speed >= 0x8000)
                {
                  speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
                  speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
                  speed >>= rtable[STBL][param-1];
                }
                cptr->freq += speed;
              }
              break;

              case CMD_PORTADOWN:
              {
                unsigned short speed = 0;
                if (param)
                {
                  speed = (ltable[STBL][param-1] << 8) | rtable[STBL][param-1];
                }
                if (speed >= 0x8000)
                {
                  speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
                  speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
                  speed >>= rtable[STBL][param-1];
                }
                cptr->freq -= speed;
              }
              break;

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

                if (!param)
                {
                  cptr->freq = targetfreq;
                  cptr->lastnote = cptr->note;
                  cptr->vibtime = 0;
                }
                else
                {
                  speed = (ltable[STBL][param-1] << 8) | rtable[STBL][param-1];
                  if (speed >= 0x8000)
                  {
                    speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
                    speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
                    speed >>= rtable[STBL][param-1];
                  }
                  if (cptr->freq < targetfreq)
                  {
                    cptr->freq += speed;
                    if (cptr->freq > targetfreq)
                    {
                      cptr->freq = targetfreq;
                      cptr->lastnote = cptr->note;
                      cptr->vibtime = 0;
                    }
                  }
                  if (cptr->freq > targetfreq)
                  {
                    cptr->freq -= speed;
                    if (cptr->freq < targetfreq)
                    {
                      cptr->freq = targetfreq;
                      cptr->lastnote = cptr->note;
                      cptr->vibtime = 0;
                    }
                  }
                }
              }
              break;

              case CMD_VIBRATO:
              {
                unsigned short speed = 0;
                unsigned char cmpvalue = 0;

                if (param)
                {
                  cmpvalue = ltable[STBL][param-1];
                  speed = rtable[STBL][param-1];
                }
                if (cmpvalue >= 0x80)
                {
                  cmpvalue &= 0x7f;
                  speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
                  speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
                  speed >>= rtable[STBL][param-1];
                }

                if ((cptr->vibtime < 0x80) && (cptr->vibtime > cmpvalue))
                  cptr->vibtime ^= 0xff;
                cptr->vibtime += 0x02;
                if (cptr->vibtime & 0x01)
                  cptr->freq -= speed;
                else
                  cptr->freq += speed;
              }
              break;

              case CMD_SETAD:
              sidreg[0x5+7*c] = param;
              break;

              case CMD_SETSR:
              sidreg[0x6+7*c] = param;;
              break;

              case CMD_SETWAVE:
              cptr->wave = param;
              break;

              case CMD_SETPULSEPTR:
              cptr->ptr[PTBL] = param;
              cptr->pulsetime = 0;
              if (cptr->ptr[PTBL])
              {
                // Stop the song in case of jumping into a jump
                if (ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
                  stopsong();
              }
              break;

              case CMD_SETFILTERPTR:
              filterptr = param;
              filtertime = 0;
              if (filterptr)
              {
                // Stop the song in case of jumping into a jump
                if (ltable[FTBL][filterptr-1] == 0xff)
                stopsong();
              }
              break;

              case CMD_SETFILTERCTRL:
              filterctrl = param;
              if (!filterctrl) filterptr = 0;
              break;

              case CMD_SETFILTERCUTOFF:
              filtercutoff = param;
              break;

              case CMD_SETMASTERVOL:
              if (param < 0x10)
                masterfader = param;
              break;
            }
          }
        }
        else
        {
          // Wavetable delay
          if (cptr->wavetime != wave)
          {
            cptr->wavetime++;
            goto TICKNEFFECTS;
          }
        }

        cptr->wavetime = 0;
        cptr->ptr[WTBL]++;
        // Wavetable jump
        if (ltable[WTBL][cptr->ptr[WTBL]-1] == 0xff)
        {
          cptr->ptr[WTBL] = rtable[WTBL][cptr->ptr[WTBL]-1];
        }

        if ((wave >= WAVECMD) && (wave <= WAVELASTCMD))
          goto PULSEEXEC;

        if (note != 0x80)
        {
          if (note < 0x80)
            note += cptr->note;
          note &= 0x7f;
          cptr->freq = freqtbllo[note] | (freqtblhi[note]<<8);
          cptr->vibtime = 0;
          cptr->lastnote = note;
          goto PULSEEXEC;
        }
      }

      // Tick N command
      TICKNEFFECTS:
      if ((!optimizerealtime) || (cptr->tick))
      {
        switch(cptr->command)
        {
          case CMD_PORTAUP:
          {
            unsigned short speed = 0;
            if (cptr->cmddata)
            {
              speed = (ltable[STBL][cptr->cmddata-1] << 8) | rtable[STBL][cptr->cmddata-1];
            }
            if (speed >= 0x8000)
            {
              speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
              speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
              speed >>= rtable[STBL][cptr->cmddata-1];
            }
            cptr->freq += speed;
          }
          break;

          case CMD_PORTADOWN:
          {
            unsigned short speed = 0;
            if (cptr->cmddata)
            {
              speed = (ltable[STBL][cptr->cmddata-1] << 8) | rtable[STBL][cptr->cmddata-1];
            }
            if (speed >= 0x8000)
            {
              speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
              speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
              speed >>= rtable[STBL][cptr->cmddata-1];
            }
            cptr->freq -= speed;
          }
          break;

          case CMD_DONOTHING:
          if ((!cptr->cmddata) || (!cptr->vibdelay))
            break;
          if (cptr->vibdelay > 1)
          {
            cptr->vibdelay--;
            break;
          }
          /* fall through */
          case CMD_VIBRATO:
          {
            unsigned short speed = 0;
            unsigned char cmpvalue = 0;

            if (cptr->cmddata)
            {
              cmpvalue = ltable[STBL][cptr->cmddata-1];
              speed = rtable[STBL][cptr->cmddata-1];
            }
            if (cmpvalue >= 0x80)
            {
              cmpvalue &= 0x7f;
              speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
              speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
              speed >>= rtable[STBL][cptr->cmddata-1];
            }

            if ((cptr->vibtime < 0x80) && (cptr->vibtime > cmpvalue))
              cptr->vibtime ^= 0xff;
            cptr->vibtime += 0x02;
            if (cptr->vibtime & 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 = 0;

            if (!cptr->cmddata)
            {
              cptr->freq = targetfreq;
              cptr->lastnote = cptr->note;
              cptr->vibtime = 0;
            }
            else
            {
              speed = (ltable[STBL][cptr->cmddata-1] << 8) | rtable[STBL][cptr->cmddata-1];
              if (speed >= 0x8000)
              {
                speed = freqtbllo[cptr->lastnote + 1] | (freqtblhi[cptr->lastnote + 1] << 8);
                speed -= freqtbllo[cptr->lastnote] | (freqtblhi[cptr->lastnote] << 8);
                speed >>= rtable[STBL][cptr->cmddata-1];
              }
              if (cptr->freq < targetfreq)
              {
                cptr->freq += speed;
                if (cptr->freq > targetfreq)
                {
                  cptr->freq = targetfreq;
                  cptr->lastnote = cptr->note;
                  cptr->vibtime = 0;
                }
              }
              if (cptr->freq > targetfreq)
              {
                cptr->freq -= speed;
                if (cptr->freq < targetfreq)
                {
                  cptr->freq = targetfreq;
                  cptr->lastnote = cptr->note;
                  cptr->vibtime = 0;
                }
              }
            }
          }
          break;
        }
      }

      PULSEEXEC:
      if (optimizepulse)
      {
        if ((songinit != PLAY_STOPPED) && (cptr->tick == cptr->gatetimer)) goto GETNEWNOTES;
      }

      if (cptr->ptr[PTBL])
      {
        // Skip pulse when sequencer has been executed
        if (optimizepulse)
        {
          if ((!cptr->tick) && (!cptr->pattptr)) goto NEXTCHN;
        }

        // Pulsetable jump
        if (ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
        {
          cptr->ptr[PTBL] = rtable[PTBL][cptr->ptr[PTBL]-1];
          if (!cptr->ptr[PTBL]) goto PULSEEXEC;
        }

        if (!cptr->pulsetime)
        {
          // Set pulse
          if (ltable[PTBL][cptr->ptr[PTBL]-1] >= 0x80)
          {
            cptr->pulse = (ltable[PTBL][cptr->ptr[PTBL]-1] & 0xf) << 8;
            cptr->pulse |= rtable[PTBL][cptr->ptr[PTBL]-1];
            cptr->ptr[PTBL]++;
          }
          else
          {
            cptr->pulsetime = ltable[PTBL][cptr->ptr[PTBL]-1];
          }
        }
        // Pulse modulation
        if (cptr->pulsetime)
        {
          unsigned char speed = rtable[PTBL][cptr->ptr[PTBL]-1];
          if (speed < 0x80)
          {
            cptr->pulse += speed;
            cptr->pulse &= 0xfff;
          }
          else
          {
            cptr->pulse += speed;
            cptr->pulse -= 0x100;
            cptr->pulse &= 0xfff;
          }
          cptr->pulsetime--;
          if (!cptr->pulsetime) cptr->ptr[PTBL]++;
        }
      }
      if ((songinit == PLAY_STOPPED) || (cptr->tick != cptr->gatetimer)) goto NEXTCHN;

      // New notes processing
      GETNEWNOTES:
      {
        unsigned char newnote;

        newnote = pattern[cptr->pattnum][cptr->pattptr];
        if (pattern[cptr->pattnum][cptr->pattptr+1])
          cptr->instr = pattern[cptr->pattnum][cptr->pattptr+1];
        cptr->newcommand = pattern[cptr->pattnum][cptr->pattptr+2];
        cptr->newcmddata = pattern[cptr->pattnum][cptr->pattptr+3];
        cptr->pattptr += 4;
        if (pattern[cptr->pattnum][cptr->pattptr] == ENDPATT)
          cptr->pattptr = 0x7fffffff;

        if (newnote == KEYOFF)
          cptr->gate = 0xfe;
        if (newnote == KEYON)
          cptr->gate = 0xff;
        if (newnote <= LASTNOTE)
        {
          cptr->newnote = newnote+cptr->trans;
          if ((cptr->newcommand) != CMD_TONEPORTA)
          {
            if (!(instr[cptr->instr].gatetimer & 0x40))
            {
              cptr->gate = 0xfe;
              if (!(instr[cptr->instr].gatetimer & 0x80))
              {
                sidreg[0x5+7*c] = adparam>>8;
                sidreg[0x6+7*c] = adparam&0xff;
              }
            }
          }
        }
      }
      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->gate;
      }
      cptr++;
    }
  }
  if (songinit != PLAY_STOPPED) incrementtime();
}

void sequencer(int c, CHN *cptr)
{
  if ((songinit != PLAY_STOPPED) && (cptr->pattptr == 0x7fffffff))
  {
    cptr->pattptr = startpattpos * 4;
    if (!cptr->advance) goto SEQDONE;
    // Song loop
    if (songorder[psnum][c][cptr->songptr] == LOOPSONG)
    {
      cptr->songptr = songorder[psnum][c][cptr->songptr+1];
      if (cptr->songptr >= songlen[psnum][c])
      {
        stopsong();
        cptr->songptr = 0;
        goto SEQDONE;
      }
    }
    // Transpose
    if ((songorder[psnum][c][cptr->songptr] >= TRANSDOWN) && (songorder[psnum][c][cptr->songptr] < LOOPSONG))
    {
      cptr->trans = songorder[psnum][c][cptr->songptr]-TRANSUP;
      cptr->songptr++;
    }
    // Repeat
    if ((songorder[psnum][c][cptr->songptr] >= REPEAT) && (songorder[psnum][c][cptr->songptr] < TRANSDOWN))
    {
      cptr->repeat = songorder[psnum][c][cptr->songptr]-REPEAT;
      cptr->songptr++;
    }
    // Pattern number
    cptr->pattnum = songorder[psnum][c][cptr->songptr];
    if (cptr->repeat)
      cptr->repeat--;
    else
      cptr->songptr++;

    // Check for illegal pattern now
    if (cptr->pattnum >= MAX_PATT)
    {
      stopsong();
      cptr->pattnum = 0;
    }
    if (cptr->pattptr >= (pattlen[cptr->pattnum] * 4))
      cptr->pattptr = 0;
      
    // Check for playback endpos
    if ((lastsonginit != PLAY_BEGINNING) && (esend[c] > 0) && (esend[c] > espos[c]) && (cptr->songptr > esend[c]) && (espos[c] < songlen[psnum][c]))
      cptr->songptr = espos[c];
  }
  SEQDONE: {}
}
