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

// =============================================================================
// playroutine
// =============================================================================

#define PLAY_C

#include "play.h"

#include "channels.h"
#include "display.h"
#include "order.h"
#include "pattern.h"
#include "settings.h"
#include "sid.h"
#include "song.h"
#include "sound.h"
#include "timer.h"
#include "tuning.h"

#include <cstdio>
#include <cstring>

unsigned char filterctrl = 0;
unsigned char filtertype = 0;
unsigned char filtercutoff = 0;
unsigned char filtertime = 0;
unsigned char filterptr = 0;
unsigned char filter2ctrl = 0;
unsigned char filter2type = 0;
unsigned char filter2cutoff = 0;
unsigned char filter2time = 0;
unsigned char filter2ptr = 0;
unsigned char masterfader = 0x0f;
int psnum = 0;
int songinit = PLAY_STOPPED;
int lastsonginit = 0;
int startpattpos = 0;

void sequencer(int c, Chn *cptr);

void resetmasterfader()
{
  masterfader = 0x0f;
}

int getlastsonginit()
{
    return lastsonginit;
}

void initsong(int num, int mode)
{
  initsongpos(num, mode, 0);
}

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

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

void rewindsong()
{
  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 (!(song.instr[ins].gatetimer & 0x40))
  {
    chn[chnnum].gate = 0xfe; // Keyoff
    if (!(song.instr[ins].gatetimer & 0x80))
    {
      if (chnnum < 3)
      {
        writereg(0x5+chnnum*7, config.adparam>>8); // Hardrestart
        writereg(0x6+chnnum*7, config.adparam&0xff);
      }
      else
      {
        writereg2(0x5+(chnnum-3)*7, config.adparam>>8); // Hardrestart
        writereg2(0x6+(chnnum-3)*7, config.adparam&0xff);
      }
    }
  }

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

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

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

bool isplaying()
{
  return (songinit != PLAY_STOPPED);
}

void playroutine()
{
  Chn *cptr = &chn[0];

  if (songinit == PLAY_STOP)
    followplay = false;

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

    filterctrl = 0;
    filterptr = 0;

    timer.reset();

    if ((songinit == PLAY_POS) || (songinit == PLAY_PATTERN))
    {
      if ((espos[0] >= song.len[psnum][0]) || (espos[1] >= song.len[psnum][1]) || (espos[2] >= song.len[psnum][2]))
         songinit = PLAY_BEGINNING;
    }

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

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

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

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

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

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

    for (int c = 0; c < MAX_CHN_MONO; c++)
    {
      Instr *iptr = &song.instr[cptr->instr];

      // Reset tempo in jammode
      if ((songinit == PLAY_STOPPED) && (cptr->tempo < 2))
      {
        if (config.multiplier)
          cptr->tempo = 6*config.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 (song.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 (song.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 (song.ltable[FTBL][filterptr-1] == 0xff)
                stopsong();
            }
          }
          writereg(0x5+7*c, iptr->ad);
          writereg(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:
        writereg(0x5+7*c, cptr->newcmddata);
        break;

        case CMD_SETSR:
        writereg(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 (song.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 (song.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 (song.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] = song.ltable[STBL][cptr->newcmddata-1]-1;
          funktable[1] = song.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 = song.ltable[WTBL][cptr->ptr[WTBL]-1];
        unsigned char note = song.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 = song.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 = (song.ltable[STBL][param-1] << 8) | song.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 >>= song.rtable[STBL][param-1];
                }
                cptr->freq += speed;
              }
              break;

              case CMD_PORTADOWN:
              {
                unsigned short speed = 0;
                if (param)
                {
                  speed = (song.ltable[STBL][param-1] << 8) | song.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 >>= song.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 = (song.ltable[STBL][param-1] << 8) | song.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 >>= song.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 = song.ltable[STBL][param-1];
                  speed = song.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 >>= song.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:
              writereg(0x5+7*c, param);
              break;

              case CMD_SETSR:
              writereg(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 (song.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 (song.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 (song.ltable[WTBL][cptr->ptr[WTBL]-1] == 0xff)
        {
          cptr->ptr[WTBL] = song.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 ((!config.optimizerealtime) || (cptr->tick))
      {
        switch(cptr->command)
        {
          case CMD_PORTAUP:
          {
            unsigned short speed = 0;
            if (cptr->cmddata)
            {
              speed = (song.ltable[STBL][cptr->cmddata-1] << 8) | song.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 >>= song.rtable[STBL][cptr->cmddata-1];
            }
            cptr->freq += speed;
          }
          break;

          case CMD_PORTADOWN:
          {
            unsigned short speed = 0;
            if (cptr->cmddata)
            {
              speed = (song.ltable[STBL][cptr->cmddata-1] << 8) | song.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 >>= song.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 = song.ltable[STBL][cptr->cmddata-1];
              speed = song.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 >>= song.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 = (song.ltable[STBL][cptr->cmddata-1] << 8) | song.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 >>= song.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 (config.optimizepulse)
      {
        if ((songinit != PLAY_STOPPED) && (cptr->tick == cptr->gatetimer)) goto GETNEWNOTES;
      }

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

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

        if (!cptr->pulsetime)
        {
          // Set pulse
          if (song.ltable[PTBL][cptr->ptr[PTBL]-1] >= 0x80)
          {
            cptr->pulse = (song.ltable[PTBL][cptr->ptr[PTBL]-1] & 0xf) << 8;
            cptr->pulse |= song.rtable[PTBL][cptr->ptr[PTBL]-1];
            cptr->ptr[PTBL]++;
          }
          else
          {
            cptr->pulsetime = song.ltable[PTBL][cptr->ptr[PTBL]-1];
          }
        }
        // Pulse modulation
        if (cptr->pulsetime)
        {
          unsigned char speed = song.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 = song.pattern[cptr->pattnum][cptr->pattptr];
        if (song.pattern[cptr->pattnum][cptr->pattptr+1])
          cptr->instr = song.pattern[cptr->pattnum][cptr->pattptr+1];
        cptr->newcommand = song.pattern[cptr->pattnum][cptr->pattptr+2];
        cptr->newcmddata = song.pattern[cptr->pattnum][cptr->pattptr+3];
        cptr->pattptr += 4;
        if (song.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 (!(song.instr[cptr->instr].gatetimer & 0x40))
            {
              cptr->gate = 0xfe;
              if (!(song.instr[cptr->instr].gatetimer & 0x80))
              {
                writereg(0x5+7*c, config.adparam>>8);
                writereg(0x6+7*c, config.adparam&0xff);
              }
            }
          }
        }
      }
NEXTCHN:
      if (cptr->mute)
        writereg(0x4+7*c, cptr->wave = 0x08);
      else
      {
        writereg(0x0+7*c, cptr->freq & 0xff);
        writereg(0x1+7*c, cptr->freq >> 8);
        writereg(0x2+7*c, cptr->pulse & 0xfe);
        writereg(0x3+7*c, cptr->pulse >> 8);
        writereg(0x4+7*c, cptr->wave & cptr->gate);
      }
      cptr++;
    }
  }
  if (songinit != PLAY_STOPPED) timer.increment();
}

/* ========================================================================== */

void playroutine_stereo()
{
    Chn *cptr = &chn[0];

    if (songinit == PLAY_STOP)
        followplay = false;

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

        filterctrl = 0;
        filterptr = 0;
        filter2ctrl = 0;
        filter2ptr = 0;

        timer.reset();

        if ((songinit == PLAY_POS) || (songinit == PLAY_PATTERN))
        {
            for (int c = 0; c < MAX_CHN; c++)
            {
                if (espos[c] >= song.len[psnum][c])
                    songinit = PLAY_BEGINNING;
            }
        }

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

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

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

            case PLAY_POS:
                cptr->songptr = espos[c];
                sequencer(c, cptr);
                break;
            }
            cptr++;
        }
        songinit = (songinit != PLAY_STOP) ? PLAY_PLAYING : PLAY_STOPPED;
        if ((!song.len[psnum][0]) || (!song.len[psnum][1]) || (!song.len[psnum][2]) ||
                (!song.len[psnum][3]) || (!song.len[psnum][4]) || (!song.len[psnum][5]))
            songinit = PLAY_STOPPED; // Zero length song

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

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

        if (filter2ptr)
        {
            // Filter jump
            if (song.ltable[FTBL][filter2ptr-1] == 0xff)
            {
                filter2ptr = song.rtable[FTBL][filter2ptr-1];
                if (!filter2ptr) goto FILTER2STOP_S;
            }

            if (!filter2time)
            {
                // Filter set
                if (song.ltable[FTBL][filter2ptr-1] >= 0x80)
                {
                    filter2type = song.ltable[FTBL][filter2ptr-1] & 0x70;
                    filter2ctrl = song.rtable[FTBL][filter2ptr-1];
                    filter2ptr++;
                    // Can be combined with cutoff set
                    if (song.ltable[FTBL][filter2ptr-1] == 0x00)
                    {
                        filter2cutoff = song.rtable[FTBL][filter2ptr-1];
                        filter2ptr++;
                    }
                }
                else
                {
                    // New modulation step
                    if (song.ltable[FTBL][filter2ptr-1])
                        filter2time = song.ltable[FTBL][filter2ptr-1];
                    else
                    {
                        // Cutoff set
                        filter2cutoff = song.rtable[FTBL][filter2ptr-1];
                        filter2ptr++;
                    }
                }
            }
            // Filter modulation
            if (filter2time)
            {
                filter2cutoff += song.rtable[FTBL][filter2ptr-1];
                filter2time--;
                if (!filter2time) filter2ptr++;
            }
        }

FILTER2STOP_S:
        writereg2(0x15, 0x00);
        writereg2(0x16, filter2cutoff);
        writereg2(0x17, filter2ctrl);
        writereg2(0x18, filter2type | masterfader);

        for (int c = 0; c < MAX_CHN; c++)
        {
            Instr *iptr = &song.instr[cptr->instr];

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

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

            // 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 (chn->gatetimer > cptr->tick)
                    stopsong();
            }
            goto WAVEEXEC_S;

            // Tick 0
TICK0_S:
            // 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 (song.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 (song.ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
                                stopsong();
                        }
                    }
                    if (iptr->ptr[FTBL])
                    {
                        if (c < 3)
                        {
                            filterptr = iptr->ptr[FTBL];
                            filtertime = 0;
                            if (filterptr)
                            {
                                // Stop the song in case of jumping into a jump
                                if (song.ltable[FTBL][filterptr-1] == 0xff)
                                    stopsong();
                            }
                        }
                        else
                        {
                            filter2ptr = iptr->ptr[FTBL];
                            filter2time = 0;
                            if (filter2ptr)
                            {
                                // Stop the song in case of jumping into a jump
                                if (song.ltable[FTBL][filter2ptr-1] == 0xff)
                                    stopsong();
                            }
                        }
                    }
                    if (c < 3)
                    {
                        writereg(0x5+7*c, iptr->ad);
                        writereg(0x6+7*c, iptr->sr);
                    }
                    else
                    {
                        writereg2(0x5+7*(c-3), iptr->ad);
                        writereg2(0x6+7*(c-3), 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:
                if (c < 3)
                    writereg(0x5+7*c, cptr->newcmddata);
                else
                    writereg2(0x5+7*(c-3), cptr->newcmddata);
                break;

            case CMD_SETSR:
                if (c < 3)
                    writereg(0x6+7*c, cptr->newcmddata);
                else
                    writereg2(0x6+7*(c-3), 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 (song.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 (song.ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
                        stopsong();
                }
                break;

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

            case CMD_SETFILTERCTRL:
                if (c < 3)
                {
                    filterctrl = cptr->newcmddata;
                    if (!filterctrl) filterptr = 0;
                }
                else
                {
                    filter2ctrl = cptr->newcmddata;
                    if (!filter2ctrl) filter2ptr = 0;
                }
                break;

            case CMD_SETFILTERCUTOFF:
                if (c < 3)
                    filtercutoff = cptr->newcmddata;
                else
                    filter2cutoff = cptr->newcmddata;
                break;

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

            case CMD_FUNKTEMPO:
                if (cptr->newcmddata)
                {
                    funktable[0] = song.ltable[STBL][cptr->newcmddata-1]-1;
                    funktable[1] = song.rtable[STBL][cptr->newcmddata-1]-1;
                }
                {
                    int d;
                    for (d = 0; d < MAX_CHN; d++)
                        chn[d].tempo = 0;
                }
                break;

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

                if (newtempo >= 3) newtempo--;
                if (cptr->newcmddata >= 0x80)
                    cptr->tempo = newtempo;
                else
                {
                    int d;
                    for (d = 0; d < MAX_CHN; d++)
                        chn[d].tempo = newtempo;
                }
            }
            break;
            }
            if (cptr->newnote)
            {
                cptr->newnote = 0;
                if (cptr->newcommand != CMD_TONEPORTA) goto NEXTCHN_S;
            }

WAVEEXEC_S:
            if (cptr->ptr[WTBL])
            {
                unsigned char wave = song.ltable[WTBL][cptr->ptr[WTBL]-1];
                unsigned char note = song.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 = song.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 = (song.ltable[STBL][param-1] << 8) | song.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 >>= song.rtable[STBL][param-1];
                            }
                            cptr->freq += speed;
                        }
                        break;

                        case CMD_PORTADOWN:
                        {
                            unsigned short speed = 0;
                            if (param)
                            {
                                speed = (song.ltable[STBL][param-1] << 8) | song.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 >>= song.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->vibtime = 0;
                            }
                            else
                            {
                                speed = (song.ltable[STBL][param-1] << 8) | song.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 >>= song.rtable[STBL][param-1];
                                }
                                if (cptr->freq < targetfreq)
                                {
                                    cptr->freq += speed;
                                    if (cptr->freq > targetfreq)
                                    {
                                        cptr->freq = targetfreq;
                                        cptr->vibtime = 0;
                                    }
                                }
                                if (cptr->freq > targetfreq)
                                {
                                    cptr->freq -= speed;
                                    if (cptr->freq < targetfreq)
                                    {
                                        cptr->freq = targetfreq;
                                        cptr->vibtime = 0;
                                    }
                                }
                            }
                        }
                        break;

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

                            if (param)
                            {
                                cmpvalue = song.ltable[STBL][param-1];
                                speed = song.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 >>= song.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:
                            if (c < 3)
                                writereg(0x5+7*c, param);
                            else
                                writereg2(0x5+7*(c-3), param);
                            break;

                        case CMD_SETSR:
                            if (c < 3)
                                writereg(0x6+7*c, param);
                            else
                                writereg2(0x6+7*(c-3), 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 (song.ltable[PTBL][cptr->ptr[PTBL]-1] == 0xff)
                                    stopsong();
                            }
                            break;

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

                        case CMD_SETFILTERCTRL:
                            if (c < 3)
                            {
                                filterctrl = param;
                                if (!filterctrl) filterptr = 0;
                            }
                            else
                            {
                                filter2ctrl = param;
                                if (!filter2ctrl) filter2ptr = 0;
                            }
                            break;

                        case CMD_SETFILTERCUTOFF:
                            if (c < 3)
                                filtercutoff = param;
                            else
                                filter2cutoff = param;
                            break;

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

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

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

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

            // Tick N command
TICKNEFFECTS_S:
            if ((!config.optimizerealtime) || (cptr->tick))
            {
                switch(cptr->command)
                {
                case CMD_PORTAUP:
                {
                    unsigned short speed = 0;
                    if (cptr->cmddata)
                    {
                        speed = (song.ltable[STBL][cptr->cmddata-1] << 8) | song.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 >>= song.rtable[STBL][cptr->cmddata-1];
                    }
                    cptr->freq += speed;
                }
                break;

                case CMD_PORTADOWN:
                {
                    unsigned short speed = 0;
                    if (cptr->cmddata)
                    {
                        speed = (song.ltable[STBL][cptr->cmddata-1] << 8) | song.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 >>= song.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 = song.ltable[STBL][cptr->cmddata-1];
                        speed = song.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 >>= song.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->vibtime = 0;
                    }
                    else
                    {
                        speed = (song.ltable[STBL][cptr->cmddata-1] << 8) | song.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 >>= song.rtable[STBL][cptr->cmddata-1];
                        }
                        if (cptr->freq < targetfreq)
                        {
                            cptr->freq += speed;
                            if (cptr->freq > targetfreq)
                            {
                                cptr->freq = targetfreq;
                                cptr->vibtime = 0;
                            }
                        }
                        if (cptr->freq > targetfreq)
                        {
                            cptr->freq -= speed;
                            if (cptr->freq < targetfreq)
                            {
                                cptr->freq = targetfreq;
                                cptr->vibtime = 0;
                            }
                        }
                    }
                }
                break;
                }
            }

PULSEEXEC_S:
            if (config.optimizepulse)
            {
                if ((songinit != PLAY_STOPPED) && (cptr->tick == cptr->gatetimer)) goto GETNEWNOTES_S;
            }

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

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

                if (!cptr->pulsetime)
                {
                    // Set pulse
                    if (song.ltable[PTBL][cptr->ptr[PTBL]-1] >= 0x80)
                    {
                        cptr->pulse = (song.ltable[PTBL][cptr->ptr[PTBL]-1] & 0xf) << 8;
                        cptr->pulse |= song.rtable[PTBL][cptr->ptr[PTBL]-1];
                        cptr->ptr[PTBL]++;
                    }
                    else
                    {
                        cptr->pulsetime = song.ltable[PTBL][cptr->ptr[PTBL]-1];
                    }
                }
                // Pulse modulation
                if (cptr->pulsetime)
                {
                    unsigned char speed = song.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_S;

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

                newnote = song.pattern[cptr->pattnum][cptr->pattptr];
                if (song.pattern[cptr->pattnum][cptr->pattptr+1])
                    cptr->instr = song.pattern[cptr->pattnum][cptr->pattptr+1];
                cptr->newcommand = song.pattern[cptr->pattnum][cptr->pattptr+2];
                cptr->newcmddata = song.pattern[cptr->pattnum][cptr->pattptr+3];
                cptr->pattptr += 4;
                if (song.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 (!(song.instr[cptr->instr].gatetimer & 0x40))
                        {
                            cptr->gate = 0xfe;
                            if (!(song.instr[cptr->instr].gatetimer & 0x80))
                            {
                                if (c < 3)
                                {
                                    writereg(0x5+7*c, config.adparam>>8);
                                    writereg(0x6+7*c, config.adparam&0xff);
                                }
                                else
                                {
                                    writereg2(0x5+7*(c-3), config.adparam>>8);
                                    writereg2(0x6+7*(c-3), config.adparam&0xff);
                                }
                            }
                        }
                    }
                }
            }
NEXTCHN_S:
            if (cptr->mute)
            {
                if (c < 3)
                    writereg(0x4+7*c, cptr->wave = 0x08);
                else
                    writereg2(0x4+7*(c-3), cptr->wave = 0x08);
            }
            else
            {
                if (c < 3)
                {
                    writereg(0x0+7*c, cptr->freq & 0xff);
                    writereg(0x1+7*c, cptr->freq >> 8);
                    writereg(0x2+7*c, cptr->pulse & 0xfe);
                    writereg(0x3+7*c, cptr->pulse >> 8);
                    writereg(0x4+7*c, cptr->wave & cptr->gate);
                }
                else
                {
                    writereg2(0x0+7*(c-3), cptr->freq & 0xff);
                    writereg2(0x1+7*(c-3), cptr->freq >> 8);
                    writereg2(0x2+7*(c-3), cptr->pulse & 0xfe);
                    writereg2(0x3+7*(c-3), cptr->pulse >> 8);
                    writereg2(0x4+7*(c-3), cptr->wave & cptr->gate);
                }
            }
            cptr++;
        }
    }
    if (songinit != PLAY_STOPPED) timer.increment();
}

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 (song.order[psnum][c][cptr->songptr] == LOOPSONG)
    {
      cptr->songptr = song.order[psnum][c][cptr->songptr+1];
      if (cptr->songptr >= song.len[psnum][c])
      {
        stopsong();
        cptr->songptr = 0;
        goto SEQDONE;
      }
    }
    // Transpose
    if ((song.order[psnum][c][cptr->songptr] >= TRANSDOWN) && (song.order[psnum][c][cptr->songptr] < LOOPSONG))
    {
      cptr->trans = song.order[psnum][c][cptr->songptr]-TRANSUP;
      cptr->songptr++;
    }
    // Repeat
    if ((song.order[psnum][c][cptr->songptr] >= REPEAT) && (song.order[psnum][c][cptr->songptr] < TRANSDOWN))
    {
      cptr->repeat = song.order[psnum][c][cptr->songptr]-REPEAT;
      cptr->songptr++;
    }
    // Pattern number
    cptr->pattnum = song.order[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 >= (getPattlen(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] < song.len[psnum][c]))
      cptr->songptr = espos[c];
  }
SEQDONE:
  {}
}
