/*
 * 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 "globals.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>

struct Filter
{
    unsigned char ctrl;
    unsigned char type;
    unsigned char cutoff;
    unsigned char time;
    unsigned char ptr;
};

Filter filter = {};
Filter filter2 = {};

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];
    int maxChns = config.getMaxChannels();

    if (songinit == PLAY_STOP)
        followplay = false;

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

        filter.ctrl = 0;
        filter.ptr = 0;
        filter2.ctrl = 0;
        filter2.ptr = 0;

        timer.reset();

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

        for (int c = 0; c < maxChns; 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;
        for (int c = 0; c < maxChns; c++)
        {
            if (!song.len[psnum][c])
            {
                songinit = PLAY_STOPPED; // Zero length song
                break;
            }
        }

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

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

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

            if (!filter2.time)
            {
                // Filter set
                if (song.ltable[FTBL][filter2.ptr-1] >= 0x80)
                {
                    filter2.type = song.ltable[FTBL][filter2.ptr-1] & 0x70;
                    filter2.ctrl = song.rtable[FTBL][filter2.ptr-1];
                    filter2.ptr++;
                    // Can be combined with cutoff set
                    if (song.ltable[FTBL][filter2.ptr-1] == 0x00)
                    {
                        filter2.cutoff = song.rtable[FTBL][filter2.ptr-1];
                        filter2.ptr++;
                    }
                }
                else
                {
                    // New modulation step
                    if (song.ltable[FTBL][filter2.ptr-1])
                        filter2.time = song.ltable[FTBL][filter2.ptr-1];
                    else
                    {
                        // Cutoff set
                        filter2.cutoff = song.rtable[FTBL][filter2.ptr-1];
                        filter2.ptr++;
                    }
                }
            }
            // Filter modulation
            if (filter2.time)
            {
                filter2.cutoff += song.rtable[FTBL][filter2.ptr-1];
                filter2.time--;
                if (!filter2.time) filter2.ptr++;
            }
        }

FILTER2STOP:
        writereg2(0x15, 0x00);
        writereg2(0x16, filter2.cutoff);
        writereg2(0x17, filter2.ctrl);
        writereg2(0x18, filter2.type | masterfader);

        for (int c = 0; c < maxChns; 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])
                    {
                        if (c < 3)
                        {
                            filter.ptr = iptr->ptr[FTBL];
                            filter.time = 0;
                            if (filter.ptr)
                            {
                                // Stop the song in case of jumping into a jump
                                if (song.ltable[FTBL][filter.ptr-1] == 0xff)
                                    stopsong();
                            }
                        }
                        else
                        {
                            filter2.ptr = iptr->ptr[FTBL];
                            filter2.time = 0;
                            if (filter2.ptr)
                            {
                                // Stop the song in case of jumping into a jump
                                if (song.ltable[FTBL][filter2.ptr-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)
                {
                    filter.ptr = cptr->newcmddata;
                    filter.time = 0;
                    if (filter.ptr)
                    {
                        // Stop the song in case of jumping into a jump
                        if (song.ltable[FTBL][filter.ptr-1] == 0xff)
                            stopsong();
                    }
                }
                else
                {
                    filter2.ptr = cptr->newcmddata;
                    filter2.time = 0;
                    if (filter2.ptr)
                    {
                        // Stop the song in case of jumping into a jump
                        if (song.ltable[FTBL][filter2.ptr-1] == 0xff)
                            stopsong();
                    }
                }
                break;

            case CMD_SETFILTERCTRL:
                if (c < 3)
                {
                    filter.ctrl = cptr->newcmddata;
                    if (!filter.ctrl) filter.ptr = 0;
                }
                else
                {
                    filter2.ctrl = cptr->newcmddata;
                    if (!filter2.ctrl) filter2.ptr = 0;
                }
                break;

            case CMD_SETFILTERCUTOFF:
                if (c < 3)
                    filter.cutoff = cptr->newcmddata;
                else
                    filter2.cutoff = 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;
                }
                for (int d = 0; d < maxChns; 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
                {
                    for (int d = 0; d < maxChns; d++)
                        chn[d].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:
                            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)
                            {
                                filter.ptr = param;
                                filter.time = 0;
                                if (filter.ptr)
                                {
                                    // Stop the song in case of jumping into a jump
                                    if (song.ltable[FTBL][filter.ptr-1] == 0xff)
                                        stopsong();
                                }
                            }
                            else
                            {
                                filter2.ptr = param;
                                filter2.time = 0;
                                if (filter2.ptr)
                                {
                                    // Stop the song in case of jumping into a jump
                                    if (song.ltable[FTBL][filter2.ptr-1] == 0xff)
                                        stopsong();
                                }
                            }
                            break;

                        case CMD_SETFILTERCTRL:
                            if (c < 3)
                            {
                                filter.ctrl = param;
                                if (!filter.ctrl) filter.ptr = 0;
                            }
                            else
                            {
                                filter2.ctrl = param;
                                if (!filter2.ctrl) filter2.ptr = 0;
                            }
                            break;

                        case CMD_SETFILTERCUTOFF:
                            if (c < 3)
                                filter.cutoff = param;
                            else
                                filter2.cutoff = 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))
                            {
                                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:
            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) return;
    // 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;
        return;
      }
    }
    // 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];
  }
}
