/*
** ST3PLAY v0.75a - 5th of October 2016 - http://16-bits.org
** =========================================================
** This is the foobar2000 version, with added code by kode54
**
** Changelog from v0.71:
** - Initial pans should be 128 since we simulate GUS mode.
**   Pans are still changed if custom pans follow.
**
** Changelog from v0.70:
** - Any pan commands except Lxy/Yxy/XA4 should disable channel surround
**
** Changelog from v0.60:
** - Added S2x (non-ST3, set middle-C finetune)
** - Added S6x (non-ST3, tick delay)
** - Added S9E/S9F (non-ST3, play sample backwards/forwards)
** - Fixed a bug in setspd() in Amiga limit mode
** - Proper tracker handling for non-ST3 effects
** - Panbrello (Yxy) didn't set the panning at all
** - Decodes ADPCM samples at load time instead of play time
** - Mxx (set cannel volume) didn't work correctly
**
** C port of Scream Tracker 3's replayer, by 8bitbubsy (Olav Sørensen)
** using the original asm source codes by PSI (Sami Tammilehto) of Future Crew
**
** This is by no means a piece of beautiful code, nor is it meant to be...
** It's just an accurate Scream Tracker 3 replayer port for people to enjoy.
**
** Non-ST3 additions from other trackers (only handled for non ST3 S3Ms):
**
** - Mixing:
**   * 16-bit sample support
**   * Stereo sample support
**   * 2^32 sample length support
**   * Middle-C speeds beyond 65535
**   * Process the last 16 channels as PCM (if not AdLib)
**   * Process 8 octaves instead of 7
**   * Compile-time optional volume ramping
**   * The ability to play samples backwards
**
** - Effects:
**   * Command S2x        (set middle-C finetune)
**   * Command S5x        (panbrello type)
**   * Command S6x        (tick delay)
**   * Command S9x        (sound control - only S90/S91/S9E/S9F)
**   * Command Mxx        (set channel volume)
**   * Command Nxy        (channel volume slide)
**   * Command Pxy        (panning slide)
**   * Command Txx<0x20   (tempo slide)
**   * Command Wxy        (global volume slide)
**   * Command Xxx        (7+1-bit pan) + XA4 for 'surround'
**   * Command Yxy        (panbrello)
**   * Volume Command Pxx (set 4+1-bit panning)
**
** - Variables:
**   * Pan changed from 4-bit (0..15) to 8+1-bit (0..256)
**   * Memory variables for the new N/P/T/W/Y effects
**   * Panbrello counter
**   * Panbrello type
**   * Channel volume multiplier
**   * Channel surround flag
**
** - Other changes:
**   * Added tracker identification to make sure Scream Tracker 3.xx
**     modules are still played exactly like they should. :-)
*/

#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include <math.h>
#include <assert.h>

#if defined(_MSC_VER) && !defined(inline)
#define inline __forceinline
#endif

#include "dbopl.h"
#include "resampler.h"
#include "st3play.h"

#define USE_VOL_RAMP

// TRACKER ID
enum
{
    SCREAM_TRACKER  = 1,
    IMAGO_ORPHEUS   = 2,
    IMPULSE_TRACKER = 3,
    SCHISM_TRACKER  = 4,
    OPENMPT         = 5,
    BEROTRACKER     = 6,
    // there is also CREAMTRACKER (7), but let's ignore that weird thing
};


// STRUCTS
typedef struct chn
{
    int8_t aorgvol;
    int8_t avol;
    uint8_t channelnum;
    uint8_t achannelused;
    uint8_t aglis;
    uint8_t atremor;
    uint8_t atreon;
    uint8_t atrigcnt;
    uint8_t anotecutcnt;
    uint8_t anotedelaycnt;
    uint8_t avibtretype;
    uint8_t note;
    uint8_t ins;
    uint8_t vol;
    uint8_t cmd;
    uint8_t info;
    uint8_t lastins;
    uint8_t lastnote;
    uint8_t alastnfo;
    uint8_t alasteff;
    uint8_t alasteff1;
    int16_t apanpos;
    int16_t avibcnt;
    uint16_t astartoffset;
    uint16_t astartoffset00;
    int32_t ac2spd;
    int32_t asldspd;
    int32_t aorgspd;
    int32_t aspd;

    // NON-ST3 variables
    int8_t chanvol;
    uint8_t surround;
    uint8_t apantype;
    uint8_t nxymem;
    uint8_t pxymem;
    uint8_t txxmem;
    uint8_t wxymem;
    uint8_t yxymem;
    int16_t apancnt;
} chn_t;

typedef struct
{
    const int8_t *sampleData;
    int8_t loopEnabled;
    int8_t sixteenBit;
    int8_t stereo;
    int8_t mixing;
    int8_t interpolating;
    int8_t playBackwards;
    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t samplePosition;
    int32_t sampleLoopLength;

    float incRate;
    float volume;
    float panningL;
    float panningR;
    float orgPanR;

#ifdef USE_VOL_RAMP
    float targetVol;
    float targetPanL;
    float targetPanR;
    float volDelta;
    float panDeltaL;
    float panDeltaR;
    float fader;
    float faderDest;
    float faderDelta;
#endif
} VOICE;

// VARIABLES / STATE
typedef struct
{
    int8_t tickdelay;
    int8_t volslidetype;
    int8_t patterndelay;
    int8_t patloopcount;
    uint8_t breakpat;
    uint8_t startrow;
    uint8_t musiccount;
    int16_t np_ord;
    int16_t np_row;
    int16_t np_pat;
    int16_t np_patoff;
    int16_t patloopstart;
    int16_t jumptorow;
    uint16_t patternadd;
    uint16_t instrumentadd;
    uint16_t patmusicrand;
    int32_t aspdmax;
    int32_t aspdmin;
    uint32_t np_patseg;
    chn_t chn[32];

    uint8_t mixingVolume;
    int32_t soundBufferSize;
    uint32_t outputFreq;

#ifdef USE_VOL_RAMP
    VOICE voice[32 * 2];
    void *resampler[64 * 2];
#else
    VOICE voice[32];
    void *resampler[64];
#endif

    void *fmChip;
    void *fmResampler;

    const uint8_t *fmPatchTable[9];
    uint8_t fmLastB0[9];

    int8_t **adpcmSamples;

    float f_outputFreq;
    float f_masterVolume;

#ifdef USE_VOL_RAMP
    float f_samplesPerFrame;
    float f_samplesPerFrameSharp;
#endif

    int8_t samplingInterpolation;
#ifdef USE_VOL_RAMP
    int8_t rampStyle;
#endif
    float *masterBufferL;
    float *masterBufferR;
    int32_t samplesLeft;
    int8_t isMixing;
    uint32_t samplesPerFrame;

    // GLOBAL VARIABLES
    int8_t ModuleLoaded;
    int8_t MusicPaused;
    int8_t Playing;

    uint8_t *mseg;
    int8_t lastachannelused;
    int8_t tracker;
    int8_t oldstvib;
    int8_t fastvolslide;
    int8_t amigalimits;
    uint8_t musicmax;
    uint8_t numChannels;
    int16_t x_np_row;
    int16_t x_np_ord;
    int16_t x_np_pat;
    int16_t tempo;
    int16_t globalvol;
    int8_t stereomode;
    uint8_t mastervol;
    uint32_t mseg_len;

    uint8_t muted[4];

    uint32_t loopCount;
    uint8_t playedOrder[8192];
} PLAYER;

typedef void (*effect_routine)(PLAYER *, chn_t *ch);


enum { _soundBufferSize = 512 };


// TABLES

static const int16_t xfinetune_amiga[16] =
{
    7895, 7941, 7985, 8046, 8107, 8169, 8232, 8280,
    8363, 8413, 8463, 8529, 8581, 8651, 8723, 8757
};

static const int8_t retrigvoladd[32] =
{
    0, -1, -2, -4, -8,-16,  0,  0,
    0,  1,  2,  4,  8, 16,  0,  0,
    0,  0,  0,  0,  0,  0, 10,  8,
    0,  0,  0,  0,  0,  0, 24, 32
};

static const uint16_t notespd[12] =
{
    1712 * 16, 1616 * 16, 1524 * 16,
    1440 * 16, 1356 * 16, 1280 * 16,
    1208 * 16, 1140 * 16, 1076 * 16,
    1016 * 16,  960 * 16,  907 * 16
};

static const int16_t vibsin[64] =
{
     0x00, 0x18, 0x31, 0x4A, 0x61, 0x78, 0x8D, 0xA1,
     0xB4, 0xC5, 0xD4, 0xE0, 0xEB, 0xF4, 0xFA, 0xFD,
     0xFF, 0xFD, 0xFA, 0xF4, 0xEB, 0xE0, 0xD4, 0xC5,
     0xB4, 0xA1, 0x8D, 0x78, 0x61, 0x4A, 0x31, 0x18,
     0x00,-0x18,-0x31,-0x4A,-0x61,-0x78,-0x8D,-0xA1,
    -0xB4,-0xC5,-0xD4,-0xE0,-0xEB,-0xF4,-0xFA,-0xFD,
    -0xFF,-0xFD,-0xFA,-0xF4,-0xEB,-0xE0,-0xD4,-0xC5,
    -0xB4,-0xA1,-0x8D,-0x78,-0x61,-0x4A,-0x31,-0x18
};

static const uint8_t vibsqu[64] =
{
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

static const int16_t vibramp[64] =
{
       0, -248,-240,-232,-224,-216,-208,-200,
    -192, -184,-176,-168,-160,-152,-144,-136,
    -128, -120,-112,-104, -96, -88, -80, -72,
     -64,  -56, -48, -40, -32, -24, -16,  -8,
       0,    8,  16,  24,  32,  40,  48,  56,
      64,   72,  80,  88,  96, 104, 112, 120,
     128,  136, 144, 152, 160, 168, 176, 184,
     192,  200, 208, 216, 224, 232, 240, 248
};

static const char Adlib_PortBases[9] = {0, 1, 2, 8, 9, 10, 16, 17, 18};

// FUNCTION DECLARATIONS


static void setSamplesPerFrame(PLAYER *, uint32_t val);
static void setSamplingInterpolation(PLAYER *, int8_t value);
#ifdef USE_VOL_RAMP
static void setRampStyle(PLAYER *, int8_t value);
#endif
static void setStereoMode(PLAYER *, int8_t value);
static void setMasterVolume(PLAYER *, uint8_t value);
static void voiceSetSource(PLAYER *, uint8_t voiceNumber, const int8_t *sampleData,
    int32_t sampleLength, int32_t sampleLoopLength, int32_t sampleLoopEnd,
    int8_t loopEnabled, int8_t sixteenbit, int8_t stereo);
static void voiceSetSamplePosition(PLAYER *, uint8_t voiceNumber, uint16_t value);
static void voiceSetVolume(PLAYER *, uint8_t voiceNumber, float volume, uint8_t note_on);
static void voiceSetSurround(PLAYER *, uint8_t voiceNumber, int8_t surround);
static void voiceSetPanning(PLAYER *, uint8_t voiceNumber, uint16_t pan);
static void voiceSetSamplingFrequency(PLAYER *, uint8_t voiceNumber, float samplingFrequency);
static void voiceSetPlayBackwards(PLAYER *, uint8_t voiceNumber, int8_t playBackwards);
static void voiceSetReadPosToEnd(PLAYER *, uint8_t voiceNumber);

static void FreeSong(PLAYER *);

static void s_ret(PLAYER *, chn_t *ch);
static void s_setgliss(PLAYER *, chn_t *ch);
static void s_setfinetune(PLAYER *, chn_t *ch); // NON-ST3
static void s_setvibwave(PLAYER *, chn_t *ch);
static void s_settrewave(PLAYER *, chn_t *ch);
static void s_setpanwave(PLAYER *, chn_t *ch); // NON-ST3
static void s_tickdelay(PLAYER *, chn_t *ch); // NON-ST3
static void s_setpanpos(PLAYER *, chn_t *ch);
static void s_sndcntrl(PLAYER *, chn_t *ch); // NON-ST3
static void s_patloop(PLAYER *, chn_t *ch);
static void s_notecut(PLAYER *, chn_t *ch);
static void s_notecutb(PLAYER *, chn_t *ch);
static void s_notedelay(PLAYER *, chn_t *ch);
static void s_notedelayb(PLAYER *, chn_t *ch);
static void s_patterdelay(PLAYER *, chn_t *ch);
static void s_setspeed(PLAYER *, chn_t *ch);
static void s_jmpto(PLAYER *, chn_t *ch);
static void s_break(PLAYER *, chn_t *ch);
static void s_volslide(PLAYER *, chn_t *ch);
static void s_slidedown(PLAYER *, chn_t *ch);
static void s_slideup(PLAYER *, chn_t *ch);
static void s_toneslide(PLAYER *, chn_t *ch);
static void s_vibrato(PLAYER *, chn_t *ch);
static void s_tremor(PLAYER *, chn_t *ch);
static void s_arp(PLAYER *, chn_t *ch);
static void s_chanvol(PLAYER *, chn_t *ch); // NON-ST3
static void s_chanvolslide(PLAYER *, chn_t *ch); // NON-ST3
static void s_vibvol(PLAYER *, chn_t *ch);
static void s_tonevol(PLAYER *, chn_t *ch);
static void s_panslide(PLAYER *, chn_t *ch);
static void s_retrig(PLAYER *, chn_t *ch);
static void s_tremolo(PLAYER *, chn_t *ch);
static void s_scommand1(PLAYER *, chn_t *ch);
static void s_scommand2(PLAYER *, chn_t *ch);
static void s_settempo(PLAYER *, chn_t *ch);
static void s_finevibrato(PLAYER *, chn_t *ch);
static void s_setgvol(PLAYER *, chn_t *ch);
static void s_globvolslide(PLAYER *, chn_t *ch); // NON-ST3
static void s_setpan(PLAYER *, chn_t *ch); // NON-ST3
static void s_panbrello(PLAYER *, chn_t *ch); // NON-ST3

static effect_routine ssoncejmp[16] =
{
    s_ret,
    s_setgliss,
    s_setfinetune, // NON-ST3
    s_setvibwave,
    s_settrewave,
    s_setpanwave, // NON-ST3
    s_tickdelay, // NON-ST3
    s_ret,
    s_setpanpos,
    s_sndcntrl, // NON-ST3
    s_ret,
    s_patloop,
    s_notecut,
    s_notedelay,
    s_patterdelay,
    s_ret
};

static effect_routine ssotherjmp[16] =
{
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_notecutb,
    s_notedelayb,
    s_ret,
    s_ret
};

static effect_routine soncejmp[27] =
{
    s_ret,
    s_setspeed,
    s_jmpto,
    s_break,
    s_volslide,
    s_slidedown,
    s_slideup,
    s_ret,
    s_ret,
    s_tremor,
    s_arp,
    s_ret,
    s_ret,
    s_chanvol, // NON-ST3
    s_chanvolslide, // NON-ST3
    s_ret,
    s_panslide, // NON-ST3
    s_retrig,
    s_ret,
    s_scommand1,
    s_settempo,
    s_ret,
    s_ret,
    s_globvolslide, // NON-ST3
    s_setpan, // NON-ST3
    s_panbrello, // NON-ST3
    s_ret
};

static effect_routine sotherjmp[27] =
{
    s_ret,
    s_ret,
    s_ret,
    s_ret,
    s_volslide,
    s_slidedown,
    s_slideup,
    s_toneslide,
    s_vibrato,
    s_tremor,
    s_arp,
    s_vibvol,
    s_tonevol,
    s_ret,
    s_chanvolslide, // NON-ST3
    s_ret,
    s_panslide, // NON-ST3
    s_retrig,
    s_tremolo,
    s_scommand2,
    s_settempo, // NON-ST3 (for tempo slides)
    s_finevibrato,
    s_setgvol,
    s_globvolslide, // NON-ST3
    s_ret,
    s_panbrello, // NON-ST3
    s_ret
};


// CODE START
void * st3play_Alloc(uint32_t outputFreq, int8_t interpolation, int8_t ramp_style)
{
    int32_t i;
    PLAYER *p;

    p = (PLAYER *)(calloc(sizeof (PLAYER), 1));
    if (p == NULL)
        return (0);

    resampler_init();

#ifdef USE_VOL_RAMP
    for (i = 0; i < (64 * 2); ++i)
#else
    for (i = 0; i < 64; ++i)
#endif
    {
        p->resampler[i] = resampler_create();
        if (p->resampler[i] == NULL)
        {
            st3play_Free(p);
            return (0);
        }

        resampler_set_quality(p->resampler[i], interpolation);
    }

    p->soundBufferSize = _soundBufferSize;
    p->masterBufferL   = (float *)(malloc(p->soundBufferSize * sizeof (float)));
    p->masterBufferR   = (float *)(malloc(p->soundBufferSize * sizeof (float)));

    if ((p->masterBufferL == NULL) || (p->masterBufferR == NULL))
    {
        st3play_Free(p);
        return (0);
    }

    p->stereomode  = 1;
    p->numChannels = 32;
    p->globalvol   = 64;
    p->mastervol   = 48;

    p->outputFreq = outputFreq;
    p->f_outputFreq = (float)(outputFreq);

    setSamplingInterpolation(p, interpolation);
#ifdef USE_VOL_RAMP
    setRampStyle(p, ramp_style);
#endif
    setSamplesPerFrame(p, ((outputFreq * 5) / 2 / 125));

    return (p);
}

static int st3play_AdlibInit(PLAYER *p)
{
    p->fmChip = malloc(Chip_GetSize());
    if (p->fmChip == NULL)
        return (-1);

    Chip_Init(p->fmChip);
    Chip_Setup(p->fmChip, 3579545 * 4, 49716);
    Chip_WriteReg(p->fmChip, 0x01, 0x20); // enable wave select, but rather pointless with dbopl

    p->fmResampler = resampler_create();
    if (p->fmResampler == NULL)
        return (-1);

    resampler_set_quality(p->fmResampler, RESAMPLER_QUALITY_MAX);
    resampler_set_rate(p->fmResampler, 49716.0f / p->f_outputFreq);

    return (0);
}

void st3play_Free(void *_p)
{
    int32_t i;
    PLAYER *p;

    p = (PLAYER *)(_p);
    FreeSong(p);

    if (p->fmResampler)
        resampler_delete(p->fmResampler);

    if (p->fmChip)
        free(p->fmChip);

#ifdef USE_VOL_RAMP
    for (i = 0; i < (64 * 2); ++i)
#else
        for (i = 0; i < 64; ++i)
#endif
    {
        if (p->resampler[i])
            resampler_delete(p->resampler[i]);
    }

    if (p->masterBufferL) free(p->masterBufferL);
    if (p->masterBufferR) free(p->masterBufferR);

    free(p);
}

static inline uint16_t get_le16(const void *_p)
{
    const uint8_t *p;

    p = (const uint8_t *)(_p);
    return (p[0] | (p[1] << 8));
}

static inline void set_le16(void *_p, uint16_t v)
{
    uint8_t *p;

    p = (uint8_t *)(_p);
    p[0] = (uint8_t)(v);
    p[1] = v >> 8;
}

static inline uint32_t get_le32(const void *_p)
{
    const uint8_t *p;

    p = (const uint8_t *)(_p);
    return (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24));
}

static inline void getlastnfo(PLAYER *p, chn_t *ch)
{
    (void)(p);

    if (!ch->info)
        ch->info = ch->alastnfo;
}

static void setspeed(PLAYER *p, uint8_t val)
{
    if (val)
        p->musicmax = val;
}

static void settempo(PLAYER *p, uint16_t val)
{
    if (val > 32)
    {
        p->tempo = val;
        setSamplesPerFrame(p, ((p->outputFreq * 5) / 2) / p->tempo);
    }
}

static void st3play_AdlibHertzTouch(PLAYER *p, uint8_t ch, int32_t Hertz, uint8_t keyoff)
{
    int32_t Oct;

    for (Oct = 0; Hertz >= 512; Oct++)
        Hertz /= 2;

    Chip_WriteReg(p->fmChip, 0xA0 + ch, Hertz & 0xFF);

    p->fmLastB0[ch] = (keyoff ? 0 : 0x20) | ((Hertz >> 8) & 3) | ((Oct & 7) << 2);
    Chip_WriteReg(p->fmChip, 0xB0 + ch, p->fmLastB0[ch]);
}

static inline void setspd(PLAYER *p, uint8_t ch)
{
    uint8_t adlibChannel;
    int32_t tmpspd;

    adlibChannel = (p->mseg[0x40 + ch] & 0x7F) - 16;
    p->chn[ch].achannelused |= 0x80;

    tmpspd = p->chn[ch].aspd;

    if (p->amigalimits)
    {
        if (p->chn[ch].aorgspd > p->aspdmax) p->chn[ch].aorgspd = p->aspdmax;
        if (p->chn[ch].aorgspd < p->aspdmin) p->chn[ch].aorgspd = p->aspdmin;

        if (p->chn[ch].aspd > p->aspdmax)
            p->chn[ch].aspd = p->aspdmax;
    }

    if ((p->tracker == SCREAM_TRACKER) || p->amigalimits)
    {
        if (tmpspd > p->aspdmax)
            tmpspd = p->aspdmax;
    }
    else
    {
        // *ABSOLUTE* max!
        if (tmpspd > 14317056)
            tmpspd = 14317056;
    }

    if (tmpspd == 0)
    {
        // cut channel
        voiceSetSamplingFrequency(p, ch, 0);
        return;
    }

    if (tmpspd < p->aspdmin)
    {
        tmpspd = p->aspdmin;

        if (p->amigalimits && (p->chn[ch].aspd < p->aspdmin))
            p->chn[ch].aspd = p->aspdmin;
    }

    // ST3 actually uses 14317056 (3.579264MHz * 4) instead of 14317456 (8363*1712)
    if (tmpspd > 0)
        voiceSetSamplingFrequency(p, ch, 14317056 / tmpspd);

    if (adlibChannel < 9)
        st3play_AdlibHertzTouch(p, adlibChannel, 14317056.0f / (float)(tmpspd), 0);
}

static void st3play_AdlibTouch(PLAYER *p, uint8_t ch, const uint8_t * patch, uint8_t vol)
{
    int32_t Operator;

    if (!patch)
    {
        patch = p->fmPatchTable[ch];
        if (!patch)
            return;
    }

    p->fmPatchTable[ch] = patch;
    Operator = Adlib_PortBases[ch];

    Chip_WriteReg(p->fmChip, 0x40 + Operator, (patch[2] & 0xC0) | (((int32_t)(patch[2] & 63) - 63) * vol + 63 * 64 - 32) / 64);
    Chip_WriteReg(p->fmChip, 0x43 + Operator, (patch[3] & 0xC0) | (((int32_t)(patch[3] & 63) - 63) * vol + 63 * 64 - 32) / 64);
}

static inline void setvol(PLAYER *p, uint8_t ch, uint8_t offset, uint8_t note_on)
{
    uint8_t adlibChannel;

    adlibChannel = (p->mseg[0x40 + ch] & 0x7F) - 16;
    p->chn[ch].achannelused |= 0x80;

#ifdef USE_VOL_RAMP
    voiceSetVolume(p, ch + offset, ((float)(p->chn[ch].avol) / 63.0f) * ((float)(p->chn[ch].chanvol) / 64.0f) * ((float)(p->globalvol) / 64.0f), note_on);
#else
    voiceSetVolume(p, ch, ((float)(p->chn[ch].avol) / 63.0f) * ((float)(p->chn[ch].chanvol) / 64.0f) * ((float)(p->globalvol) / 64.0f), note_on);
#endif

    if (adlibChannel < 9)
        st3play_AdlibTouch(p, adlibChannel, NULL, p->chn[ch].avol);
}

static inline void setpan(PLAYER *p, uint8_t ch)
{
    voiceSetPanning(p, ch, p->chn[ch].apanpos);
}

static inline int16_t stnote2herz(PLAYER *p, uint8_t note)
{
    uint8_t tmpnote;
    uint8_t tmpocta;

    if (note == 254) return (0);

    tmpnote = note & 0x0F;
    tmpocta = note >> 4;

    // ST3 doesn't do this, but do it for safety
    if (tmpnote > 11) tmpnote = 11;

    // Limit octaves to 8 in ST3 mode
    if ((p->tracker == SCREAM_TRACKER) && (tmpocta > 7))
        tmpocta = 7;

    return (notespd[tmpnote] >> tmpocta);
}

static inline int32_t scalec2spd(PLAYER *p, uint8_t ch, int32_t spd)
{
    spd *= 8363;

    if (p->tracker == SCREAM_TRACKER)
    {
        if ((spd / 65536) > p->chn[ch].ac2spd)
            return (32767);
    }

    if (p->chn[ch].ac2spd)
        spd /= p->chn[ch].ac2spd;

    if (p->tracker == SCREAM_TRACKER)
    {
        if (spd > 32767)
            return (32767);
    }

    return (spd);
}

// for Gxx with semitones slide flag
static inline int32_t roundspd(PLAYER *p, uint8_t ch, int32_t spd)
{
    int8_t octa;
    int8_t lastnote;
    int8_t newnote;
    int32_t note;
    int32_t lastspd;
    int32_t newspd;

    newspd = spd * p->chn[ch].ac2spd;

    if (p->tracker == SCREAM_TRACKER)
    {
        if ((newspd / 65536) >= 8363)
            return (spd);
    }

    newspd /= 8363;

    // find octave
    octa    = 0;
    lastspd = ((1712 * 8) + (907 * 16)) / 2;;
    while (newspd < lastspd)
    {
        octa++;
        lastspd /= 2;
    }

    // find note
    lastnote = 0;
    newnote  = 0;

    if (p->tracker == SCREAM_TRACKER)
        lastspd = 32767;
    else
        lastspd = 32767 * 2;

    while (newnote < 11)
    {
        note = (notespd[newnote] >> octa) - newspd;
        if (note < 0) note *= -1; // abs()

        if (note < lastspd)
        {
            lastspd = note;
            lastnote = newnote;
        }

        newnote++;
    }

    // get new speed from new note
    newspd = (stnote2herz(p, (octa << 4) | (lastnote & 0x0F))) * 8363;

    if (p->tracker == SCREAM_TRACKER)
    {
        if ((newspd / 65536) >= p->chn[ch].ac2spd)
            return (spd);
    }

    if (p->chn[ch].ac2spd)
        newspd /= p->chn[ch].ac2spd;

    return (newspd);
}

static int16_t neworder(PLAYER *p)
{
newOrderSkip:
    p->np_ord++;

    if ((p->mseg[0x60 + (p->np_ord - 1)] == 255) || (p->np_ord > get_le16(&p->mseg[0x20]))) // end
        p->np_ord = 1;

    if (p->mseg[0x60 + (p->np_ord - 1)] == 254) // skip
        goto newOrderSkip; // avoid recursive calling

    p->np_pat       = (int16_t)(p->mseg[0x60 + (p->np_ord - 1)]);
    p->np_patoff    = -1; // force reseek
    p->np_row       = p->startrow;
    p->startrow     = 0;
    p->patmusicrand = 0;
    p->patloopstart = -1;
    p->jumptorow    = -1;

    return (p->np_row);
}

// updates np_patseg and np_patoff
static inline void seekpat(PLAYER *p)
{
    uint8_t dat;
    int16_t i;
    int16_t j;

    if (p->np_patoff == -1) // seek must be done
    {
        p->np_patseg = get_le16(&p->mseg[p->patternadd + (p->np_pat * 2)]) * 16;
        if (p->np_patseg)
        {
            j = 2; // skip packed pat len flag

            // inc np_patoff on patbreak
            if (p->np_row)
            {
                i = p->np_row;
                while (i)
                {
                    dat = p->mseg[p->np_patseg + j++];
                    if (!dat)
                    {
                        i--;
                    }
                    else
                    {
                        // skip ch data
                        if (dat & 0x20) j += 2;
                        if (dat & 0x40) j += 1;
                        if (dat & 0x80) j += 2;
                    }
                }
            }

            p->np_patoff = j;
        }
    }
}

static inline uint8_t getnote(PLAYER *p)
{
    uint8_t dat;
    uint8_t ch;
    int16_t i;

    if (!p->np_patseg || (p->np_patseg >= p->mseg_len) || (p->np_pat >= get_le16(&p->mseg[0x24])))
        return (255);

    i = p->np_patoff;
    for (;;)
    {
        dat = p->mseg[p->np_patseg + i++];
        if (!dat) // end of row
        {
            p->np_patoff = i;
            return (255);
        }

        if (!(p->mseg[0x40 + (dat & 0x1F)] & 0x80))
        {
            ch = dat & 0x1F; // channel to trigger
            break;
        }

        // channel is off, skip data
        if (dat & 0x20) i += 2;
        if (dat & 0x40) i += 1;
        if (dat & 0x80) i += 2;
    }

    if (dat & 0x20)
    {
        p->chn[ch].note = p->mseg[p->np_patseg + i++];
        p->chn[ch].ins  = p->mseg[p->np_patseg + i++];

        if (p->chn[ch].note != 255) p->chn[ch].lastnote = p->chn[ch].note;
        if (p->chn[ch].ins)         p->chn[ch].lastins  = p->chn[ch].ins;
    }

    if (dat & 0x40)
        p->chn[ch].vol = p->mseg[p->np_patseg + i++];

    if (dat & 0x80)
    {
        p->chn[ch].cmd  = p->mseg[p->np_patseg + i++];
        p->chn[ch].info = p->mseg[p->np_patseg + i++];
    }

    p->np_patoff = i;
    return (ch);
}

static void st3play_AdlibPatch(PLAYER *p, uint8_t ch, const uint8_t *patch)
{
    int32_t Operator;

    Operator = Adlib_PortBases[ch];
    p->fmPatchTable[ch] = patch;

    Chip_WriteReg(p->fmChip, 0x20 + Operator, patch[0]);
    Chip_WriteReg(p->fmChip, 0x60 + Operator, patch[4]);
    Chip_WriteReg(p->fmChip, 0x80 + Operator, patch[6]);
    Chip_WriteReg(p->fmChip, 0xE0 + Operator, patch[8] & 3);
    Chip_WriteReg(p->fmChip, 0x23 + Operator, patch[1]);
    Chip_WriteReg(p->fmChip, 0x63 + Operator, patch[5]);
    Chip_WriteReg(p->fmChip, 0x83 + Operator, patch[7]);
    Chip_WriteReg(p->fmChip, 0xE3 + Operator, patch[9] & 3);
    Chip_WriteReg(p->fmChip, 0xC0 + ch, patch[10]);
}

static void st3play_AdlibNoteOff(PLAYER *p, uint8_t ch)
{
    Chip_WriteReg(p->fmChip, 0xB0 + ch, p->fmLastB0[ch] & ~0x20);
}

static inline void doamiga(PLAYER *p, uint8_t ch)
{
    uint8_t *insdat;
    int8_t loop;
    int8_t shift;
    uint8_t adlibChannel;
    uint32_t insoffs;
    uint32_t inslen;
    uint32_t insrepbeg;
    uint32_t insrepend;

#ifdef USE_VOL_RAMP
    uint8_t volassigned;
#endif

    volassigned  = 0;
    adlibChannel = (p->mseg[0x40 + ch] & 0x7F) - 16;

    if ((p->fmChip == NULL) || (p->fmResampler == NULL)) // safety check
        adlibChannel = 255;

    if (p->chn[ch].ins)
    {
        p->chn[ch].lastins = p->chn[ch].ins;
        p->chn[ch].astartoffset = 0;

        if (p->chn[ch].ins <= get_le16(&p->mseg[0x22])) // added for safety reasons
        {
            insdat = &p->mseg[get_le16(&p->mseg[p->instrumentadd + ((p->chn[ch].ins - 1) * 2)]) * 16];
            if (insdat[0])
            {
                if ((insdat[0] == 1) && (adlibChannel >= 9))
                {
                    p->chn[ch].ac2spd = get_le32(&insdat[0x20]);

                    if ((p->tracker == OPENMPT) || (p->tracker == BEROTRACKER))
                    {
                        if ((p->chn[ch].cmd == ('S' - 64)) && ((p->chn[ch].info & 0xF0) == 0x20))
                            p->chn[ch].ac2spd = xfinetune_amiga[p->chn[ch].info & 0x0F];
                    }

                    p->chn[ch].avol = (int8_t)(insdat[0x1C]);

                         if (p->chn[ch].avol <  0) p->chn[ch].avol =  0;
                    else if (p->chn[ch].avol > 63) p->chn[ch].avol = 63;

                    p->chn[ch].aorgvol = p->chn[ch].avol;

                    insoffs = ((insdat[0x0D] << 16) | (insdat[0x0F] << 8) | insdat[0x0E]) * 16;

                    if (insoffs > p->mseg_len)
                        insoffs = p->mseg_len;

                    inslen = get_le32(&insdat[0x10]);

                    shift = 0;
                    if (insdat[0x1F] & 2) shift++;
                    if (insdat[0x1F] & 4) shift++;

                    if (insoffs + (inslen << shift) > p->mseg_len)
                        inslen = (p->mseg_len - insoffs) >> shift;

                    insrepbeg = get_le32(&insdat[0x14]);
                    insrepend = get_le32(&insdat[0x18]);

                    if (insrepbeg > inslen) insrepbeg = inslen;
                    if (insrepend > inslen) insrepend = inslen;

                    loop = 0;
                    if ((insdat[0x1F] & 1) && inslen && (insrepend > insrepbeg))
                        loop = 1;

#ifdef USE_VOL_RAMP
                    if (p->rampStyle > 0 && (p->chn[ch].cmd != 7) && p->voice[ch].mixing)
                    {
                        memcpy(p->voice + 32 + (int32_t)(ch), p->voice + (int32_t)(ch), sizeof (VOICE));
                        p->voice[ch + 32].faderDest = 0.0f;
                        p->voice[ch + 32].faderDelta = (p->voice[ch + 32].faderDest - p->voice[ch + 32].fader) * p->f_samplesPerFrameSharp;

                        setvol(p, ch, 32, 0);

                        resampler_dup_inplace(p->resampler[ch + 32], p->resampler[ch]);
                        resampler_dup_inplace(p->resampler[ch + 32 + 64], p->resampler[ch + 64]);
                        resampler_clear(p->resampler[ch]);
                        resampler_clear(p->resampler[ch + 64]);

                        if (p->chn[ch].vol != 255)
                        {
                            if (p->chn[ch].vol <= 64)
                            {
                                p->chn[ch].avol    = p->chn[ch].vol;
                                p->chn[ch].aorgvol = p->chn[ch].vol;
                            }
                            else if ((p->chn[ch].vol >= 128) && (p->chn[ch].vol <= 192))// NON-ST3
                            {
                                p->chn[ch].surround = 0;
                                voiceSetSurround(p, ch, 0);

                                p->chn[ch].apanpos = (p->chn[ch].vol - 128) * 4;
                                setpan(p, ch);
                            }
                        }

                        setvol(p, ch, 0, 1);
                        volassigned = 1;
                    }
                    else
#endif
                    setvol(p, ch, 0, 0);

                    // This specific phase differs from what sound card driver you use in ST3...
                    // GUS: Don't set new voice sample. SB: Set new voice sample.
                    // Let's use the GUS model here.
                    if ((p->chn[ch].cmd != ('G' - 64)) && (p->chn[ch].cmd != ('L' - 64)))
                    {
                        if (insdat[0x1E] == 4)
                            voiceSetSource(p, ch, p->adpcmSamples[p->chn[ch].ins - 1], inslen,
                                           insrepend - insrepbeg, insrepend, loop, 0, 0);
                        else
                            voiceSetSource(p, ch, (const int8_t *)(&p->mseg[insoffs]), inslen,
                                           insrepend - insrepbeg, insrepend, loop,
                                           insdat[0x1F] & 4, insdat[0x1F] & 2);
                    }

#ifdef USE_VOL_RAMP
                    if (p->rampStyle > 0)
                    {
                        p->voice[ch].fader      = 0.0f;
                        p->voice[ch].faderDest  = 1.0f;
                        p->voice[ch].faderDelta = (p->voice[ch].faderDest - p->voice[ch].fader) * p->f_samplesPerFrameSharp;
                    }
#endif
                }
                else if (insdat[0] == 2 && adlibChannel < 9)
                {
                    p->chn[ch].ac2spd  = (8363 * 164) / 249;
                    p->chn[ch].avol    = (int8_t)(insdat[0x1C]);
                    p->chn[ch].aorgvol = p->chn[ch].avol;

                    st3play_AdlibPatch(p, adlibChannel, &insdat[0x10]);
                    voiceSetSource(p, ch, NULL, 0, 0, 0, 0, 0, 0);
                    voiceSetSamplePosition(p, ch, 0);
                }
                else
                {
                    p->chn[ch].lastins = 0;
                }
            }
        }
    }

    // continue only if we have an active instrument on this channel
    if (!p->chn[ch].lastins) return;

    if (p->chn[ch].cmd == ('O' - 64))
    {
        if (!p->chn[ch].info)
        {
            p->chn[ch].astartoffset = p->chn[ch].astartoffset00;
        }
        else
        {
            p->chn[ch].astartoffset   = 256 * p->chn[ch].info;
            p->chn[ch].astartoffset00 = p->chn[ch].astartoffset;
        }
    }

    if (p->chn[ch].note != 255)
    {
        if (p->chn[ch].note == 254)
        {
            p->chn[ch].aspd    = 0;
            p->chn[ch].avol    = 0;
            p->chn[ch].asldspd = 65535;

            if (adlibChannel >= 9)
            {
                setspd(p, ch);
                setvol(p, ch, 0, 0);
            }

            // shutdown channel
            voiceSetSource(p, ch, NULL, 0, 0, 0, 0, 0, 0);
            voiceSetSamplePosition(p, ch, 0);

            if (adlibChannel < 9)
                st3play_AdlibNoteOff(p, adlibChannel);
        }
        else
        {
            p->chn[ch].lastnote = p->chn[ch].note;

            if (adlibChannel >= 9)
            {
                if ((p->chn[ch].cmd != ('G' - 64)) && (p->chn[ch].cmd != ('L' - 64)))
                    voiceSetSamplePosition(p, ch, p->chn[ch].astartoffset);

                if ((p->tracker == OPENMPT) || (p->tracker == BEROTRACKER))
                {
                    voiceSetPlayBackwards(p, ch, 0);
                    if ((p->chn[ch].cmd == ('S' - 64)) && (p->chn[ch].info == 0x9F))
                        voiceSetReadPosToEnd(p, ch);
                }
            }

            if (!p->chn[ch].aorgspd || ((p->chn[ch].cmd != ('G' - 64)) && (p->chn[ch].cmd != ('L' - 64))))
            {
                p->chn[ch].aspd    = scalec2spd(p, ch, stnote2herz(p, p->chn[ch].note));
                p->chn[ch].aorgspd = p->chn[ch].aspd;
                p->chn[ch].avibcnt = 0;
                p->chn[ch].apancnt = 0;

                setspd(p, ch);
            }

            p->chn[ch].asldspd = scalec2spd(p, ch, stnote2herz(p, p->chn[ch].note));

            if (adlibChannel < 9)
            {
                st3play_AdlibNoteOff(p, adlibChannel);
                setspd(p, ch);
                st3play_AdlibTouch(p, adlibChannel, NULL, p->chn[ch].avol);
            }
        }
    }

#ifdef USE_VOL_RAMP
    if (p->chn[ch].vol != 255 && (p->rampStyle < 1 || !volassigned))
#else
    if (p->chn[ch].vol != 255)
#endif
    {
        if (p->chn[ch].vol <= 64)
        {
            p->chn[ch].avol    = p->chn[ch].vol;
            p->chn[ch].aorgvol = p->chn[ch].vol;

            setvol(p, ch, 0, 0);

            return;
        }

        // NON-ST3, but let's handle it no matter what tracker
        if ((p->chn[ch].vol >= 128) && (p->chn[ch].vol <= 192))
        {
            p->chn[ch].surround = 0;
            voiceSetSurround(p, ch, 0);

            p->chn[ch].apanpos = (p->chn[ch].vol - 128) * 4;
            setpan(p, ch);
        }
    }
}

static inline void donewnote(PLAYER *p, uint8_t ch, int8_t notedelayflag)
{
    if (notedelayflag)
    {
        p->chn[ch].achannelused = 0x81;
    }
    else
    {
        if (ch > p->lastachannelused)
        {
            p->lastachannelused = ch + 1;

            // sanity fix
            if (p->lastachannelused > 31) p->lastachannelused = 31;
        }

        p->chn[ch].achannelused = 0x01;

        if (p->chn[ch].cmd == ('S' - 64))
        {
            if ((p->chn[ch].info & 0xF0) == 0xD0)
                return;
        }
    }

    doamiga(p, ch);
}

static inline void donotes(PLAYER *p)
{
    uint8_t i;
    uint8_t ch;

    for (i = 0; i < 32; ++i)
    {
        p->chn[i].note = 255;
        p->chn[i].vol  = 255;
        p->chn[i].ins  = 0;
        p->chn[i].cmd  = 0;
        p->chn[i].info = 0;
    }

    seekpat(p);

    for (;;)
    {
        ch = getnote(p);
        if (ch == 255) break; // end of row/channels

        if ((p->mseg[0x40 + ch] & 0x7F) <= (15 + 9))
            donewnote(p, ch, 0);
    }
}

// tick 0 commands
static inline void docmd1(PLAYER *p)
{
    uint8_t i;

    for (i = 0; i < (p->lastachannelused + 1); ++i)
    {
        if (p->chn[i].achannelused)
        {
            if (p->chn[i].info)
                p->chn[i].alastnfo = p->chn[i].info;

            if (p->chn[i].cmd)
            {
                p->chn[i].achannelused |= 0x80;

                if (p->chn[i].cmd == ('D' - 64))
                {
                    // fix retrig if Dxy
                    p->chn[i].atrigcnt = 0;

                    // fix speed if tone port noncomplete
                    if (p->chn[i].aspd != p->chn[i].aorgspd)
                    {
                        p->chn[i].aspd  = p->chn[i].aorgspd;
                        setspd(p, i);
                    }
                }
                else
                {
                    if (p->chn[i].cmd != ('I' - 64))
                    {
                        p->chn[i].atremor = 0;
                        p->chn[i].atreon  = 1;
                    }

                    if ((p->chn[i].cmd != ('H' - 64)) &&
                        (p->chn[i].cmd != ('U' - 64)) &&
                        (p->chn[i].cmd != ('K' - 64)) &&
                        (p->chn[i].cmd != ('R' - 64)))
                    {
                        p->chn[i].avibcnt |= 0x80;
                    }

                    // NON-ST3
                    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
                    {
                        if (p->chn[i].cmd != ('Y' - 64))
                            p->chn[i].apancnt |= 0x80;
                    }
                }

                if (p->chn[i].cmd < 27)
                {
                    p->volslidetype = 0;
                    soncejmp[p->chn[i].cmd](p, &p->chn[i]);
                }
            }
            else
            {
                // NON-ST3
                if (p->tracker != SCREAM_TRACKER)
                {
                    // recalc pans
                    setpan(p, i);
                    voiceSetSurround(p, i, p->chn[i].surround);
                }

                // fix retrig if no command
                p->chn[i].atrigcnt = 0;

                // fix speed if tone port noncomplete
                if (p->chn[i].aspd != p->chn[i].aorgspd)
                {
                    p->chn[i].aspd = p->chn[i].aorgspd;
                    setspd(p, i);
                }
            }
        }
    }
}

// tick >0 commands
static inline void docmd2(PLAYER *p)
{
    uint8_t i;

    for (i = 0; i < (p->lastachannelused + 1); ++i)
    {
        if (p->chn[i].achannelused)
        {
            if (p->chn[i].cmd)
            {
                p->chn[i].achannelused |= 0x80;

                if (p->chn[i].cmd < 27)
                {
                    p->volslidetype = 0;
                    sotherjmp[p->chn[i].cmd](p, &p->chn[i]);
                }
            }
        }
    }
}

void dorow(PLAYER *p) // periodically called from mixer
{
    int8_t offset;
    int8_t bit;

    p->patmusicrand = (((p->patmusicrand * 0xCDEF) >> 16) + 0x1727) & 0x0000FFFF;

    if (!p->musiccount)
    {
        if (p->patterndelay)
        {
            p->np_row--;
            docmd1(p);
            p->patterndelay--;
        }
        else
        {
            donotes(p);
            docmd1(p);
        }
    }
    else
    {
        docmd2(p);
    }

    p->musiccount++;
    if (p->musiccount >= (p->musicmax + p->tickdelay))
    {
        p->tickdelay = 0;
        p->np_row++;

        if (p->jumptorow != -1)
        {
            p->np_row = p->jumptorow;
            p->jumptorow = -1;
        }

        // np_row = 0..63, 64 = get new pat
        if ((p->np_row >= 64) || (!p->patloopcount && p->breakpat))
        {
            if (p->breakpat == 255)
            {
                p->breakpat = 0;
                p->Playing  = 0;

                return;
            }

            p->breakpat = 0;
            p->np_row = neworder(p); // if breakpat, np_row = break row
        }

        // x_ used for info retrieval
        p->x_np_ord = p->np_ord;
        p->x_np_row = p->np_row;
        p->x_np_pat = p->np_pat;

        if (p->np_row == 0)
        {
            offset = (p->np_ord - 1) / 8;
            bit = 1 << ((p->np_ord - 1) % 8);

            if (p->playedOrder[offset] & bit)
            {
                p->loopCount++;
                memset(p->playedOrder, 0, sizeof (p->playedOrder));
            }

            p->playedOrder[offset] |= bit;
        }

        p->musiccount = 0;
    }
}

static inline int8_t get_adpcm_sample(const int8_t *sampleDictionary, const uint8_t *sampleData, int32_t samplePosition, int8_t *lastDelta)
{
    uint8_t byte;

    byte = sampleData[samplePosition / 2];
    byte = (samplePosition & 1) ? (byte >> 4) : (byte & 0x0F);

    return (*(lastDelta) += sampleDictionary[byte]);
}

static inline void decode_adpcm(const uint8_t *sampleData, int8_t *decodedSampleData, int32_t sampleLength)
{
    int32_t i;
    int8_t lastDelta;
    int8_t sample;
    const int8_t *sampleDictionary;

    sampleDictionary = (const int8_t *)(sampleData);
    sampleData += 16;

    lastDelta = 0;
    for (i = 0; i < sampleLength; ++i)
    {
        sample = get_adpcm_sample(sampleDictionary, sampleData, i, &lastDelta);

        *decodedSampleData++ = sample;
    }
}

static void loadheaderparms(PLAYER *p)
{
    uint8_t *insdat;
    uint16_t insnum;
    uint32_t i;
    uint32_t j;
    uint32_t inslen;
    uint32_t insoff;

    // set to init defaults first
    p->oldstvib = 0;
    setspeed(p, 6);
    settempo(p, 125);
    p->aspdmin = 64;
    p->aspdmax = 32767;
    p->globalvol = 64;
    p->amigalimits = 0;
    p->fastvolslide = 0;
    setStereoMode(p, 0);
    setMasterVolume(p, 48);

    p->tracker = p->mseg[0x29] >> 4;

    if (p->mseg[0x33])
    {
        if (p->mseg[0x33] & 0x80)
            setStereoMode(p, 1);

        if (p->mseg[0x33] & 0x7F)
        {
            if ((p->mseg[0x33] & 0x7F) < 16)
                setMasterVolume(p, 16);
            else
                setMasterVolume(p, p->mseg[0x33] & 0x7F);
        }
    }

    if (p->mseg[0x32])
        settempo(p, p->mseg[0x32]);

    if (p->mseg[0x31] != 255)
        setspeed(p, p->mseg[0x31]);

    if (p->mseg[0x30] != 255)
    {
        p->globalvol = p->mseg[0x30];
        if (p->globalvol > 64)
            p->globalvol = 64;
    }

    if (p->mseg[0x26] != 255)
    {
        p->amigalimits  = p->mseg[0x26] & 0x10;
        p->fastvolslide = p->mseg[0x26] & 0x40;

        if (p->amigalimits)
        {
            p->aspdmax = 1712 * 2;
            p->aspdmin =  907 / 2;
        }
    }

    // force fastvolslide if ST3.00
    if (get_le16(&p->mseg[0x28]) == 0x1300)
        p->fastvolslide = 1;

    p->oldstvib = p->mseg[0x26] & 0x01;

    if (*((uint16_t *)(&p->mseg[0x2A])))
    {
        // we have unsigned samples, convert to signed

        insnum = get_le16(&p->mseg[0x22]);
        for (i = 0; i < insnum; ++i)
        {
            insdat = &p->mseg[get_le16(&p->mseg[p->instrumentadd + (i * 2)]) * 16];
            insoff = ((insdat[0x0D] << 16) | (insdat[0x0F] << 8) | insdat[0x0E]) * 16;

            if (insoff && (insdat[0] == 1)) // PCM
            {
                if (insoff > p->mseg_len)
                    insoff = p->mseg_len;

                inslen = get_le32(&insdat[0x10]);

                if (insdat[0x1E] == 4) // modplug packed
                {
                    if (p->adpcmSamples == NULL)
                        p->adpcmSamples = (int8_t **)(calloc(sizeof(int8_t *), insnum));

                    if (p->adpcmSamples)
                    {
                        p->adpcmSamples[i] = (int8_t *)(calloc(1, inslen));
                        if (p->adpcmSamples[i])
                        {
                            if ((insoff + (16 + (inslen + 1) / 2)) > p->mseg_len)
                                inslen = ((p->mseg_len - insoff) - 16) * 2;

                            if (inslen >= 1)
                                decode_adpcm(&p->mseg[insoff], p->adpcmSamples[i], inslen);
                        }
                    }

                    continue;
                }

                if (insdat[0x1F] & 2) inslen *= 2; // stereo

                if (insdat[0x1F] & 4)
                {
                    // 16-bit
                    if ((insoff + (inslen * 2)) > p->mseg_len)
                        inslen = (p->mseg_len - insoff) / 2;

                    for (j = 0; j < inslen; ++j)
                        set_le16(&p->mseg[insoff + (j * 2)], get_le16(&p->mseg[insoff + (j * 2)]) - 0x8000);
                }
                else
                {
                    // 8-bit
                    if ((insoff + inslen) > p->mseg_len)
                        inslen = p->mseg_len - insoff;

                    for (j = 0; j < inslen; ++j)
                        p->mseg[insoff + j] = p->mseg[insoff + j] - 0x80;
                }
            }
            else if (insdat[0] == 2) // AdLib melodic
            {
                if (!p->fmChip && st3play_AdlibInit(p) < 0)
                    break;
            }
        }
    }
}

void st3play_PlaySong(void *_p, int16_t startOrder)
{
    uint8_t i;
    int8_t offset;
    int8_t bit;
    uint8_t dat;
    int16_t pan;
    PLAYER *p;

    p = (PLAYER *)(_p);
    if (!p->ModuleLoaded) return;

    memset(p->voice, 0, sizeof (p->voice));

    loadheaderparms(p);

    p->np_ord = startOrder;
    neworder(p);
    p->x_np_ord = p->np_ord;

    // setup pans
    for (i = 0; i < 32; ++i)
    {
        pan = 128;
        if (p->mseg[0x35] == 0xFC) // non-default pannings follow
        {
            dat = p->mseg[(p->patternadd + (get_le16(&p->mseg[0x24]) * 2)) + i];
            if (dat & 0x20)
                pan = (dat & 0x0F) * 16;
        }

        p->chn[i].apanpos = p->stereomode ? pan : 128;
        voiceSetPanning(p, i, pan);
    }

    p->Playing = 1;
    setSamplesPerFrame(p, ((p->outputFreq * 5) / 2 / p->tempo));
    p->isMixing = 1;

    p->loopCount = 0;
    memset(p->playedOrder, 0, sizeof (p->playedOrder));

    offset = startOrder / 8;
    bit = 1 << (startOrder % 8);

    p->playedOrder[offset] = bit;
}

int8_t st3play_LoadModule(void *_p, const uint8_t *module, size_t size)
{
    char SCRM[4];
    uint16_t i;
    PLAYER *p;

    p = (PLAYER *)(_p);
    if (p->ModuleLoaded)
        FreeSong(p);

    p->ModuleLoaded = 0;

    if (size < 0x30)
    {
        return (0);
    }

    memcpy(SCRM, module + 0x2C, 4);
    if (memcmp(SCRM, "SCRM", 4))
    {
        return (0);
    }

    if (p->mseg)
    {
        free(p->mseg);
        p->mseg = NULL;
    }

    p->mseg = (uint8_t *)(malloc(size));
    if (p->mseg == NULL)
    {
        return (0);
    }

    memcpy(p->mseg, module, size);

    p->mseg_len = (uint32_t)(size);

    p->instrumentadd = 0x60             +  p->mseg[0x20];
    p->patternadd    = p->instrumentadd + (p->mseg[0x22] * 2);
    p->tickdelay     = 0;
    p->musiccount    = 0;
    p->patterndelay  = 0;
    p->patloopstart  = 0;
    p->patloopcount  = 0;
    p->np_row        = 0;
    p->np_pat        = 0;
    p->x_np_row      = 0;
    p->x_np_pat      = 0;
    p->x_np_ord      = 0;
    p->startrow      = 0;
    p->np_patseg     = 0;
    p->np_patoff     = 0;
    p->breakpat      = 0;
    p->patmusicrand  = 0;
    p->volslidetype  = 0;
    p->jumptorow     = 0;

    // zero all channel vars
    memset(p->chn, 0, sizeof (p->chn));

    p->numChannels = 0;
    for (i = 0; i < 32; ++i)
    {
        if (!(p->mseg[0x40 + i] & 0x80))
            p->numChannels++;

        p->chn[i].channelnum   = (int8_t)(i);
        p->chn[i].achannelused = 0x80;
        p->chn[i].chanvol      = 0x40;
    }

    p->lastachannelused = 1;

    // count *real* amounts of orders
    i = get_le16(&p->mseg[0x20]);
    while (i)
    {
        if (p->mseg[0x60 + (i - 1)] != 255)
            break;

        i--;
    }
    set_le16(&p->mseg[0x20], i);

    p->ModuleLoaded = 1;

    return (1);
}


// EFFECTS

// non-used effects
static void s_ret(PLAYER *p, chn_t *ch) { (void)(p); (void)(ch); }
// ----------------

static void s_setgliss(PLAYER *p, chn_t *ch)
{
    (void)(p);
    ch->aglis = ch->info & 0x0F;
}

static void s_setfinetune(PLAYER *p, chn_t *ch)
{
    // this function does nothing in ST3 and many other trackers
    if ((p->tracker == OPENMPT) || (p->tracker == BEROTRACKER))
        ch->ac2spd = xfinetune_amiga[ch->info & 0x0F];
}

static void s_setvibwave(PLAYER *p, chn_t *ch)
{
    (void)(p);
    ch->avibtretype = (ch->avibtretype & 0xF0) | ((ch->info << 1) & 0x0F);
}

static void s_settrewave(PLAYER *p, chn_t *ch)
{
    (void)(p);
    ch->avibtretype = ((ch->info << 5) & 0xF0) | (ch->avibtretype & 0x0F);
}

static void s_setpanwave(PLAYER *p, chn_t *ch) // NON-ST3
{
    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
        ch->apantype = ch->info & 0x0F;
}

static void s_tickdelay(PLAYER *p, chn_t *ch) // NON-ST3
{
    if (   (p->tracker == OPENMPT)
        || (p->tracker == BEROTRACKER)
        || (p->tracker == IMPULSE_TRACKER)
        || (p->tracker == SCHISM_TRACKER)
       )
    {
        p->tickdelay += (ch->info & 0x0F);
    }
}

static void s_setpanpos(PLAYER *p, chn_t *ch)
{
    ch->surround = 0;
    voiceSetSurround(p, ch->channelnum, 0);

    ch->apanpos = (ch->info & 0x0F) * 16;
    setpan(p, ch->channelnum);
}

static void s_sndcntrl(PLAYER *p, chn_t *ch) // NON-ST3
{
    if ((ch->info & 0x0F) == 0x00)
    {
        if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
        {
            ch->surround = 0;
            voiceSetSurround(p, ch->channelnum, 0);
        }
    }
    else if ((ch->info & 0x0F) == 0x01)
    {
        if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
        {
            ch->surround = 1;
            voiceSetSurround(p, ch->channelnum, 1);
        }
    }
    else if ((ch->info & 0x0F) == 0x0E)
    {
        if ((p->tracker == OPENMPT) || (p->tracker == BEROTRACKER))
            voiceSetPlayBackwards(p, ch->channelnum, 0);
    }
    else if ((ch->info & 0x0F) == 0x0F)
    {
        if ((p->tracker == OPENMPT) || (p->tracker == BEROTRACKER))
            voiceSetPlayBackwards(p, ch->channelnum, 1);
    }
}

static void s_patloop(PLAYER *p, chn_t *ch)
{
    int8_t offset;
    int8_t bit;

    if (!(ch->info & 0x0F))
    {
        p->patloopstart = p->np_row;
        return;
    }

    if (!p->patloopcount)
    {
        p->patloopcount = (ch->info & 0x0F) + 1;

        if (p->patloopstart == -1)
            p->patloopstart = 0; // default loopstart
    }

    if (p->patloopcount > 1)
    {
        p->patloopcount--;
        p->jumptorow = p->patloopstart;
        p->np_patoff = -1; // force reseek

        if (p->patloopstart == 0)
        {
            offset = (p->np_ord - 1) / 8;
            bit = 1 << ((p->np_ord - 1) % 8);

            p->playedOrder[offset] &= ~bit;
        }
    }
    else
    {
        p->patloopcount = 0;
        p->patloopstart = p->np_row + 1;
    }
}

static void s_notecut(PLAYER *p, chn_t *ch)
{
    (void)(p);
    ch->anotecutcnt = ch->info & 0x0F;
}

static void s_notecutb(PLAYER *p, chn_t *ch)
{
    uint8_t adlibChannel;

    if (ch->anotecutcnt)
    {
        ch->anotecutcnt--;
        if (!ch->anotecutcnt)
        {
            adlibChannel = (p->mseg[0x40 + ch->channelnum] & 0x7F) - 16;

            voiceSetSamplingFrequency(p, ch->channelnum, 0); // cut note

            if (adlibChannel < 9)
                st3play_AdlibNoteOff(p, adlibChannel);
        }
    }
}

static void s_notedelay(PLAYER *p, chn_t *ch)
{
    (void)(p);
    ch->anotedelaycnt = ch->info & 0x0F;
}

static void s_notedelayb(PLAYER *p, chn_t *ch)
{
    if (ch->anotedelaycnt)
    {
        ch->anotedelaycnt--;
        if (!ch->anotedelaycnt)
            donewnote(p, ch->channelnum, 1); // 1 = notedelay end
    }
}

static void s_patterdelay(PLAYER *p, chn_t *ch)
{
    if (p->patterndelay == 0)
        p->patterndelay = ch->info & 0x0F;
}

static void s_setspeed(PLAYER *p, chn_t *ch)
{
    setspeed(p, ch->info);
}

static void s_jmpto(PLAYER *p, chn_t *ch)
{
    if (ch->info != 0xFF)
    {
        p->breakpat = 1;
        p->np_ord = ch->info;
    }
    else
    {
        p->breakpat = 255;
    }
}

static void s_break(PLAYER *p, chn_t *ch)
{
    p->startrow = ((ch->info >> 4) * 10) + (ch->info & 0x0F);
    p->breakpat = 1;
}

static void s_volslide(PLAYER *p, chn_t *ch)
{
    uint8_t infohi;
    uint8_t infolo;

    getlastnfo(p, ch);

    infohi = ch->info >> 4;
    infolo = ch->info & 0x0F;

    if (infolo == 0x0F)
    {
        if (!infohi)
            ch->avol -= infolo;
        else if (!p->musiccount)
            ch->avol += infohi;
    }
    else if (infohi == 0x0F)
    {
        if (!infolo)
            ch->avol += infohi;
        else if (!p->musiccount)
            ch->avol -= infolo;
    }
    else if (p->fastvolslide || p->musiccount)
    {
        if (!infolo)
            ch->avol += infohi;
        else
            ch->avol -= infolo;
    }
    else
    {
        return; // illegal slide
    }

         if (ch->avol <  0) ch->avol =  0;
    else if (ch->avol > 63) ch->avol = 63;

    setvol(p, ch->channelnum, 0, 0);

         if (p->volslidetype == 1) s_vibrato(p, ch);
    else if (p->volslidetype == 2) s_toneslide(p, ch);
}

static void s_slidedown(PLAYER *p, chn_t *ch)
{
    if (ch->aorgspd)
    {
        getlastnfo(p, ch);

        if (p->musiccount)
        {
            if (ch->info >= 0xE0) return; // no fine slides here

            ch->aspd += (ch->info * 4);
            if (ch->aspd > 32767) ch->aspd = 32767;
        }
        else
        {
            if (ch->info <= 0xE0) return; // only fine slides here

            if (ch->info <= 0xF0)
            {
                ch->aspd += (ch->info & 0x0F);
                if (ch->aspd > 32767) ch->aspd = 32767;
            }
            else
            {
                ch->aspd += ((ch->info & 0x0F) * 4);
                if (ch->aspd > 32767) ch->aspd = 32767;
            }
        }

        ch->aorgspd = ch->aspd;
        setspd(p, ch->channelnum);
    }
}

static void s_slideup(PLAYER *p, chn_t *ch)
{
    if (ch->aorgspd)
    {
        getlastnfo(p, ch);

        if (p->musiccount)
        {
            if (ch->info >= 0xE0) return; // no fine slides here

            ch->aspd -= (ch->info * 4);
            if (ch->aspd < 0) ch->aspd = 0;
        }
        else
        {
            if (ch->info <= 0xE0) return; // only fine slides here

            if (ch->info <= 0xF0)
            {
                ch->aspd -= (ch->info & 0x0F);
                if (ch->aspd < 0) ch->aspd = 0;
            }
            else
            {
                ch->aspd -= ((ch->info & 0x0F) * 4);
                if (ch->aspd < 0) ch->aspd = 0;
            }
        }

        ch->aorgspd = ch->aspd;
        setspd(p, ch->channelnum);
    }
}

static void s_toneslide(PLAYER *p, chn_t *ch)
{
    if (p->volslidetype == 2) // we came from a Lxy (toneslide+volslide)
    {
        ch->info = ch->alasteff1;
    }
    else
    {
        if (!ch->aorgspd)
        {
            if (!ch->asldspd)
                return;

            ch->aorgspd = ch->asldspd;
            ch->aspd    = ch->asldspd;
        }

        if (!ch->info)
            ch->info = ch->alasteff1;
        else
            ch->alasteff1 = ch->info;
   }

    if (ch->aorgspd != ch->asldspd)
    {
        if (ch->aorgspd < ch->asldspd)
        {
            ch->aorgspd += (ch->info * 4);
            if (ch->aorgspd > ch->asldspd)
                ch->aorgspd = ch->asldspd;
        }
        else
        {
            ch->aorgspd -= (ch->info * 4);
            if (ch->aorgspd < ch->asldspd)
                ch->aorgspd = ch->asldspd;
        }

        if (ch->aglis)
            ch->aspd = roundspd(p, ch->channelnum, ch->aorgspd);
        else
            ch->aspd = ch->aorgspd;

        setspd(p, ch->channelnum);
    }
}

static void s_vibrato(PLAYER *p, chn_t *ch)
{
    int8_t type;
    int16_t cnt;
    int32_t dat;

    if (p->volslidetype == 1) // we came from a Kxy (vibrato+volslide)
    {
        ch->info = ch->alasteff;
    }
    else
    {
        if (!ch->info)
            ch->info = ch->alasteff;

        if (!(ch->info & 0xF0))
            ch->info = (ch->alasteff & 0xF0) | (ch->info & 0x0F);

        ch->alasteff = ch->info;
    }

    if (ch->aorgspd)
    {
        cnt  = ch->avibcnt;
        type = (ch->avibtretype & 0x0E) >> 1;

        // sine
        if ((type == 0) || (type == 4))
        {
            if (type == 4)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
        }

        // ramp
        else if ((type == 1) || (type == 5))
        {
            if (type == 5)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibramp[cnt / 2];
        }

        // square
        else if ((type == 2) || (type == 6))
        {
            if (type == 6)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsqu[cnt / 2];
        }

        // random
        else if ((type == 3) || (type == 7))
        {
            if (type == 7)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
            cnt += (p->patmusicrand & 0x1E);
        }

        // warning C4701: potentially uninitialized
        else
        {
            dat = 0;
        }

        if (p->oldstvib)
            dat = ((dat * (ch->info & 0x0F)) >> 4) + ch->aorgspd;
        else
            dat = ((dat * (ch->info & 0x0F)) >> 5) + ch->aorgspd;

        ch->aspd = dat;
        setspd(p, ch->channelnum);

        ch->avibcnt = (cnt + ((ch->info >> 4) * 2)) & 0x7E;
    }
}

static void s_tremor(PLAYER *p, chn_t *ch)
{
    getlastnfo(p, ch);

    if (ch->atremor)
    {
        ch->atremor--;
        return;
    }

    if (ch->atreon)
    {
        ch->atreon = 0;

        ch->avol = 0;
        setvol(p, ch->channelnum, 0, 0);

        ch->atremor = ch->info & 0x0F;
    }
    else
    {
        ch->atreon = 1;

        ch->avol = ch->aorgvol;
        setvol(p, ch->channelnum, 0, 0);

        ch->atremor = ch->info >> 4;
    }
}

static void s_arp(PLAYER *p, chn_t *ch)
{
    int8_t note;
    int8_t octa;
    int8_t noteadd;
    uint8_t tick;

    getlastnfo(p, ch);

    tick = p->musiccount % 3;

         if (tick == 1) noteadd = ch->info >> 4;
    else if (tick == 2) noteadd = ch->info & 0x0F;
    else                noteadd = 0;

    // check for octave overflow
    octa =  ch->lastnote & 0xF0;
    note = (ch->lastnote & 0x0F) + noteadd;

    while (note >= 12)
    {
        note -= 12;
        octa += 16;
    }

    ch->aspd = scalec2spd(p, ch->channelnum, stnote2herz(p, octa | note));
    setspd(p, ch->channelnum);
}

static void s_chanvol(PLAYER *p, chn_t *ch) // NON-ST3
{
    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
    {
        if (ch->info <= 0x40)
            ch->chanvol = ch->info;

        setvol(p, ch->channelnum, 0, 0);
    }
}

static void s_chanvolslide(PLAYER *p, chn_t *ch) // NON-ST3
{
    uint8_t infohi;
    uint8_t infolo;

    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
    {
        if (ch->info)
            ch->nxymem = ch->info;
        else
            ch->info = ch->nxymem;

        infohi = ch->nxymem >> 4;
        infolo = ch->nxymem & 0x0F;

        if (infolo == 0x0F)
        {
            if (!infohi)
                ch->chanvol -= infolo;
            else if (!p->musiccount)
                ch->chanvol += infohi;
        }
        else if (infohi == 0x0F)
        {
            if (!infolo)
                ch->chanvol += infohi;
            else if (!p->musiccount)
                ch->chanvol -= infolo;
        }
        else if (p->musiccount) // don't rely on fastvolslide flag here
        {
            if (!infolo)
                ch->chanvol += infohi;
            else
                ch->chanvol -= infolo;
        }
        else
        {
            return; // illegal slide
        }

             if (ch->chanvol <  0) ch->chanvol =  0;
        else if (ch->chanvol > 64) ch->chanvol = 64;

        setvol(p, ch->channelnum, 0, 0);
    }
}

static void s_vibvol(PLAYER *p, chn_t *ch)
{
    p->volslidetype = 1;
    s_volslide(p, ch);
}

static void s_tonevol(PLAYER *p, chn_t *ch)
{
    p->volslidetype = 2;
    s_volslide(p, ch);
}

static void s_panslide(PLAYER *p, chn_t *ch) // NON-ST3
{
    uint8_t infohi;
    uint8_t infolo;

    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
    {
        if (ch->info)
            ch->pxymem = ch->info;
        else
            ch->info = ch->pxymem;

        infohi = ch->pxymem >> 4;
        infolo = ch->pxymem & 0x0F;

        if (infolo == 0x0F)
        {
            if (!infohi)
                ch->apanpos += (infolo * 4);
            else if (!p->musiccount)
                ch->apanpos -= (infohi * 4);
        }
        else if (infohi == 0x0F)
        {
            if (!infolo)
                ch->apanpos -= (infohi * 4);
            else if (!p->musiccount)
                ch->apanpos += (infolo * 4);
        }
        else if (p->musiccount) // don't rely on fastvolslide flag here
        {
            if (!infolo)
                ch->apanpos -= (infohi * 4);
            else
                ch->apanpos += (infolo * 4);
        }
        else
        {
            return; // illegal slide
        }

             if (ch->apanpos <   0) ch->apanpos =   0;
        else if (ch->apanpos > 256) ch->apanpos = 256;

        setpan(p, ch->channelnum);
    }
}

static void s_retrig(PLAYER *p, chn_t *ch)
{
    uint8_t infohi;

    getlastnfo(p, ch);
    infohi = ch->info >> 4;

    if (!(ch->info & 0x0F) || (ch->atrigcnt < (ch->info & 0x0F)))
    {
        ch->atrigcnt++;
        return;
    }

    ch->atrigcnt = 0;

    voiceSetPlayBackwards(p, ch->channelnum, 0);
    voiceSetSamplePosition(p, ch->channelnum, 0);

    if (!retrigvoladd[16 + infohi])
        ch->avol += retrigvoladd[infohi];
    else
        ch->avol = (int8_t)((ch->avol * retrigvoladd[16 + infohi]) / 16);

         if (ch->avol > 63) ch->avol = 63;
    else if (ch->avol <  0) ch->avol =  0;

    setvol(p, ch->channelnum, 0, 0);

    ch->atrigcnt++; // probably a mistake? Keep it anyways.
}

static void s_tremolo(PLAYER *p, chn_t *ch)
{
    int8_t type;
    int16_t cnt;
    int16_t dat;

    getlastnfo(p, ch);

    if (!(ch->info & 0xF0))
        ch->info = (ch->alastnfo & 0xF0) | (ch->info & 0x0F);

    ch->alastnfo = ch->info;

    if (ch->aorgvol)
    {
        cnt  = ch->avibcnt;
        type = ch->avibtretype >> 5;

        // sine
        if ((type == 0) || (type == 4))
        {
            if (type == 4)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
        }

        // ramp
        else if ((type == 1) || (type == 5))
        {
            if (type == 5)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibramp[cnt / 2];
        }

        // square
        else if ((type == 2) || (type == 6))
        {
            if (type == 6)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsqu[cnt / 2];
        }

        // random
        else if ((type == 3) || (type == 7))
        {
            if (type == 7)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
            cnt += (p->patmusicrand & 0x1E);
        }

        // warning C4701: potentially uninitialized
        else
        {
            dat = 0;
        }

        dat = ((dat * (ch->info & 0x0F)) >> 7) + ch->aorgvol;

             if (dat > 63) dat = 63;
        else if (dat <  0) dat =  0;

        ch->avol = (int8_t)(dat);
        setvol(p, ch->channelnum, 0, 0);

        ch->avibcnt = (cnt + ((ch->info >> 4) * 2)) & 0x7E;
    }
}

static void s_scommand1(PLAYER *p, chn_t *ch)
{
    getlastnfo(p, ch);
    ssoncejmp[ch->info >> 4](p, ch);
}

static void s_scommand2(PLAYER *p, chn_t *ch)
{
    getlastnfo(p, ch);
    ssotherjmp[ch->info >> 4](p, ch);
}

static void s_settempo(PLAYER *p, chn_t *ch)
{
    if (!p->musiccount && (ch->info >= 0x20))
        p->tempo = ch->info;

    // NON-ST3 tempo slide
    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
    {
        if (!p->musiccount)
        {
            if (!ch->info)
                ch->info = ch->txxmem;
            else
                ch->txxmem = ch->info;
        }
        else if (p->musiccount)
        {
            if (ch->info <= 0x0F)
            {
                p->tempo -= ch->info;
                if (p->tempo < 32)
                    p->tempo = 32;
            }
            else if (ch->info <= 0x1F)
            {
                p->tempo += (ch->info - 0x10);
                if (p->tempo > 255)
                    p->tempo = 255;
            }
        }
    }
    // ------------------

    settempo(p, p->tempo);
}

static void s_finevibrato(PLAYER *p, chn_t *ch)
{
    int8_t type;
    int16_t cnt;
    int32_t dat;

    if (!ch->info)
        ch->info = ch->alasteff;

    if (!(ch->info & 0xF0))
        ch->info = (ch->alasteff & 0xF0) | (ch->info & 0x0F);

    ch->alasteff = ch->info;

    if (ch->aorgspd)
    {
        cnt  =  ch->avibcnt;
        type = (ch->avibtretype & 0x0E) >> 1;

        // sine
        if ((type == 0) || (type == 4))
        {
            if (type == 4)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
        }

        // ramp
        else if ((type == 1) || (type == 5))
        {
            if (type == 5)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibramp[cnt / 2];
        }

        // square
        else if ((type == 2) || (type == 6))
        {
            if (type == 6)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsqu[cnt / 2];
        }

        // random
        else if ((type == 3) || (type == 7))
        {
            if (type == 7)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
            cnt += (p->patmusicrand & 0x1E);
        }

        // warning C4701: potentially uninitialized
        else
        {
            dat = 0;
        }

        if (p->oldstvib)
            dat = ((dat * (ch->info & 0x0F)) >> 6) + ch->aorgspd;
        else
            dat = ((dat * (ch->info & 0x0F)) >> 7) + ch->aorgspd;

        ch->aspd = dat;
        setspd(p, ch->channelnum);

        ch->avibcnt = (cnt + ((ch->info >> 4) * 2)) & 0x7E;
    }
}

static void s_setgvol(PLAYER *p, chn_t *ch)
{
    if (ch->info <= 64)
        p->globalvol = ch->info;
}

static void s_globvolslide(PLAYER *p, chn_t *ch) // NON-ST3
{
    uint8_t i;
    uint8_t infohi;
    uint8_t infolo;

    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
    {
        if (ch->info)
            ch->wxymem = ch->info;
        else
            ch->info = ch->wxymem;

        infohi = ch->wxymem >> 4;
        infolo = ch->wxymem & 0x0F;

        if (infolo == 0x0F)
        {
            if (!infohi)
                p->globalvol -= infolo;
            else if (!p->musiccount)
                p->globalvol += infohi;
        }
        else if (infohi == 0x0F)
        {
            if (!infolo)
                p->globalvol += infohi;
            else if (!p->musiccount)
                p->globalvol -= infolo;
        }
        else if (p->musiccount) // don't rely on fastvolslide flag here
        {
            if (!infolo)
                p->globalvol += infohi;
            else
                p->globalvol -= infolo;
        }
        else
        {
            return; // illegal slide
        }

             if (p->globalvol <  0) p->globalvol =  0;
        else if (p->globalvol > 64) p->globalvol = 64;

        // update all channels now
        for (i = 0; i < (p->lastachannelused + 1); ++i)
            setvol(p, i, 0, 0);
    }
}

static void s_setpan(PLAYER *p, chn_t *ch) // NON-ST3
{
    // This one should work even in MONO mode
    // for newer trackers that exports as ST3

    if (ch->info <= 0x80)
    {
        ch->surround = 0;
        voiceSetSurround(p, ch->channelnum, 0);

        ch->apanpos = ch->info * 2;
        setpan(p, ch->channelnum);
    }
    else if (ch->info == 0xA4) // surround
    {
        if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
        {
            ch->surround = 1;
            voiceSetSurround(p, ch->channelnum, 1);
        }
    }
}

static void s_panbrello(PLAYER *p, chn_t *ch) // NON-ST3
{
    int8_t type;
    int16_t cnt;
    int16_t dat;

    if ((p->tracker != SCREAM_TRACKER) && (p->tracker != IMAGO_ORPHEUS))
    {
        if (!p->musiccount)
        {
            if (!ch->info)
                ch->info = ch->alasteff;
            else
                ch->yxymem = ch->info;

            if (!(ch->info & 0xF0))
                ch->info = (ch->yxymem & 0xF0) | (ch->info & 0x0F);

            if (!(ch->info & 0x0F))
                ch->info = (ch->info & 0xF0) | (ch->yxymem & 0x0F);
        }

        cnt  = ch->apancnt;
        type = ch->apantype;

        // sine
        if ((type == 0) || (type == 4))
        {
            if (type == 4)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
        }

        // ramp
        else if ((type == 1) || (type == 5))
        {
            if (type == 5)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibramp[cnt / 2];
        }

        // square
        else if ((type == 2) || (type == 6))
        {
            if (type == 6)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsqu[cnt / 2];
        }

        // random
        else if ((type == 3) || (type == 7))
        {
            if (type == 7)
            {
                cnt &= 0x7F;
            }
            else
            {
                if (cnt & 0x80) cnt = 0;
            }

            dat = vibsin[cnt / 2];
            cnt += (p->patmusicrand & 0x1E);
        }

        // warning C4701: potentially uninitialized
        else
        {
            dat = 0;
        }

        dat = ((dat * (ch->info & 0x0F)) >> 4) + ch->apanpos;

             if (dat <   0) dat =   0;
        else if (dat > 256) dat = 256;

        voiceSetPanning(p, ch->channelnum, dat);

        ch->apancnt = (cnt + ((ch->info >> 6) * 2)) & 0x7E;
    }
}

void setSamplesPerFrame(PLAYER *p, uint32_t val)
{
    p->samplesPerFrame = val;

#ifdef USE_VOL_RAMP
    p->f_samplesPerFrame      = 1.0f / ((float)(val) / 4.0f);
    p->f_samplesPerFrameSharp = 1.0f / (p->f_outputFreq / 1000.0f); // 1ms
#endif
}

void setSamplingInterpolation(PLAYER *p, int8_t value)
{
    p->samplingInterpolation = value;
}

#ifdef USE_VOL_RAMP
void setRampStyle(PLAYER *p, int8_t value)
{
    p->rampStyle = value;
}
#endif

void setStereoMode(PLAYER *p, int8_t value)
{
    p->stereomode = value;
}

void setMasterVolume(PLAYER *p, uint8_t value)
{
    p->mastervol = value;
    p->f_masterVolume = (float)(value) / 127.0f;
}

void voiceSetSource(PLAYER *p, uint8_t voiceNumber, const int8_t *sampleData,
    int32_t sampleLength, int32_t sampleLoopLength, int32_t sampleLoopEnd,
    int8_t loopEnabled, int8_t sixteenbit, int8_t stereo)
{
    p->voice[voiceNumber].sampleData       = sampleData;
    p->voice[voiceNumber].sampleLength     = sampleLength;
    p->voice[voiceNumber].sampleLoopEnd    = sampleLoopEnd;
    p->voice[voiceNumber].sampleLoopLength = sampleLoopLength;
    p->voice[voiceNumber].loopEnabled      = loopEnabled;

    p->voice[voiceNumber].sixteenBit = sixteenbit;
    p->voice[voiceNumber].stereo     = stereo;

    p->voice[voiceNumber].mixing        = 1;
    p->voice[voiceNumber].interpolating = 1;

    if (p->voice[voiceNumber].samplePosition >= p->voice[voiceNumber].sampleLength)
        p->voice[voiceNumber].samplePosition = 0;
}

void voiceSetSamplePosition(PLAYER *p, uint8_t voiceNumber, uint16_t value)
{
    p->voice[voiceNumber].samplePosition = value;
    p->voice[voiceNumber].mixing         = 1;
    p->voice[voiceNumber].interpolating  = 1;

    if (p->voice[voiceNumber].loopEnabled)
    {
        while (p->voice[voiceNumber].samplePosition >= p->voice[voiceNumber].sampleLoopEnd)
               p->voice[voiceNumber].samplePosition -= p->voice[voiceNumber].sampleLoopLength;
    }
    else if (p->voice[voiceNumber].samplePosition >= p->voice[voiceNumber].sampleLength)
    {
        p->voice[voiceNumber].mixing         = 0;
        p->voice[voiceNumber].interpolating  = 0;
        p->voice[voiceNumber].samplePosition = 0;
    }
}

void voiceSetVolume(PLAYER *p, uint8_t voiceNumber, float volume, uint8_t note_on)
{
    float rampRate;

#ifdef USE_VOL_RAMP
    rampRate = p->f_samplesPerFrame;
    if (p->rampStyle > 1 && !note_on)
    {
        p->voice[voiceNumber].targetVol = volume;
        p->voice[voiceNumber].volDelta  = (p->voice[voiceNumber].targetVol - p->voice[voiceNumber].volume) * rampRate;
    }
    else
    {
        p->voice[voiceNumber].volume = volume;
        p->voice[voiceNumber].targetVol = volume;
        p->voice[voiceNumber].volDelta = 0;
    }
#else
    p->voice[voiceNumber].volume = volume;
#endif
}

void voiceSetSurround(PLAYER *p, uint8_t voiceNumber, int8_t surround)
{
    float rampRate;

    if (surround)
    {
        p->chn[voiceNumber].apanpos = 128;
        setpan(p, voiceNumber);
    }

#ifdef USE_VOL_RAMP
    rampRate = p->f_samplesPerFrameSharp;
    if (p->rampStyle > 1)
    {
        if (surround)
            p->voice[voiceNumber].targetPanR = -p->voice[voiceNumber].orgPanR;
        else
            p->voice[voiceNumber].targetPanR =  p->voice[voiceNumber].orgPanR;

        p->voice[voiceNumber].panDeltaR = (p->voice[voiceNumber].targetPanR - p->voice[voiceNumber].panningR) * rampRate;
    }
    else
    {
        if (surround)
            p->voice[voiceNumber].panningR = -p->voice[voiceNumber].orgPanR;
        else
            p->voice[voiceNumber].panningR =  p->voice[voiceNumber].orgPanR;

        p->voice[voiceNumber].targetPanR = p->voice[voiceNumber].panningR;
        p->voice[voiceNumber].panDeltaR  = 0;
    }
#else
    if (surround)
        p->voice[voiceNumber].panningR = -p->voice[voiceNumber].orgPanR;
    else
        p->voice[voiceNumber].panningR =  p->voice[voiceNumber].orgPanR;
#endif
}

void voiceSetPanning(PLAYER *p, uint8_t voiceNumber, uint16_t pan)
{
    float pf;
    float rampRate;

    pf = (float)(pan) / 256.0f;

#ifdef USE_VOL_RAMP
    rampRate = p->f_samplesPerFrameSharp;
    if (p->rampStyle > 1)
    {
        p->voice[voiceNumber].targetPanL = 1.0f - pf;
        p->voice[voiceNumber].targetPanR = pf;
        p->voice[voiceNumber].panDeltaL = (p->voice[voiceNumber].targetPanL - p->voice[voiceNumber].panningL) * rampRate;
        p->voice[voiceNumber].panDeltaR = (p->voice[voiceNumber].targetPanR - p->voice[voiceNumber].panningR) * rampRate;
    }
    else
    {
        p->voice[voiceNumber].panningL   = 1.0f - pf;
        p->voice[voiceNumber].targetPanL = 1.0f - pf;
        p->voice[voiceNumber].panningR   = pf;
        p->voice[voiceNumber].targetPanR = pf;
        p->voice[voiceNumber].panDeltaL  = 0;
        p->voice[voiceNumber].panDeltaR  = 0;
    }
#else
    p->voice[voiceNumber].panningL = 1.0f - pf;
    p->voice[voiceNumber].panningR = pf;
#endif
    p->voice[voiceNumber].orgPanR = pf;
}

void voiceSetSamplingFrequency(PLAYER *p, uint8_t voiceNumber, float samplingFrequency)
{
    p->voice[voiceNumber].incRate = samplingFrequency / p->f_outputFreq;
}

void voiceSetPlayBackwards(PLAYER *p, uint8_t voiceNumber, int8_t playBackwards)
{
    p->voice[voiceNumber].playBackwards = playBackwards;
}

void voiceSetReadPosToEnd(PLAYER *p, uint8_t voiceNumber)
{
    p->voice[voiceNumber].samplePosition = p->voice[voiceNumber].sampleLength - 1;
}

static inline void mix8b(PLAYER *p, uint8_t ch, uint32_t samples)
{
    const int8_t *sampleData;
    int8_t loopEnabled;
    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;
    int32_t interpolating;
#ifdef USE_VOL_RAMP
    int32_t rampStyle;
#endif
    uint32_t j;
    float volume;
    float sample;
    float panningL;
    float panningR;
    void *resampler;
    VOICE *v;

    v = &p->voice[ch];

#ifdef USE_VOL_RAMP
    rampStyle = p->rampStyle;
#endif

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = sampleLoopEnd - sampleLoopLength;
    loopEnabled      = v->loopEnabled;
    volume           = v->volume;
    panningL         = v->panningL;
    panningR         = v->panningR;
    interpolating    = v->interpolating;
    sampleData       = v->sampleData;
    resampler        = p->resampler[ch];

    resampler_set_rate(resampler, v->incRate);

    for (j = 0; (j < samples) && sampleData; ++j)
    {
        samplePosition = v->samplePosition;

        while (interpolating > 0 && (resampler_get_free_count(resampler) || !resampler_get_sample_count(resampler)))
        {
            resampler_write_sample_fixed(resampler, sampleData[samplePosition], 8);

            if (v->playBackwards)
            {
                --samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition < sampleLoopBegin)
                        samplePosition = sampleLoopEnd - (sampleLoopBegin - samplePosition);
                }
                else
                {
                    if (samplePosition < 0)
                    {
                        samplePosition = 0;
                        interpolating  = -resampler_get_padding_size();
                    }
                }
            }
            else
            {
                ++samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition >= sampleLoopEnd)
                        samplePosition = sampleLoopBegin + (samplePosition - sampleLoopEnd);
                }
                else
                {
                    if (samplePosition >= sampleLength)
                        interpolating = -resampler_get_padding_size();
                }
            }
        }

        while (interpolating < 0 && (resampler_get_free_count(resampler) || !resampler_get_sample_count(resampler)))
        {
            resampler_write_sample_fixed(resampler, 0, 8);
            ++interpolating;
        }

        v->samplePosition = samplePosition;
        v->interpolating  = (int8_t)(interpolating);

        if (!resampler_get_sample_count(resampler))
        {
            resampler_clear(resampler);

            v->mixing = 0;
            break;
        }

        sample = resampler_get_sample_float(resampler);
        resampler_remove_sample(resampler, 1);

#ifdef USE_VOL_RAMP
        if (rampStyle > 0)
        {
            v->fader += v->faderDelta;

            if ((v->faderDelta > 0.0f) && (v->fader > v->faderDest))
            {
                v->fader = v->faderDest;
            }
            else if ((v->faderDelta < 0.0f) && (v->fader < v->faderDest))
            {
                v->fader = v->faderDest;
                resampler_clear(resampler);

                v->mixing  = 0;
                sampleData = 0;
            }

            sample *= v->fader;
        }
#endif

        sample *= volume;

        p->masterBufferL[j] += (sample * panningL);
        p->masterBufferR[j] += (sample * panningR);

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            volume   += v->volDelta;
            panningL += v->panDeltaL;
            panningR += v->panDeltaR;

            if ((v->volDelta > 0.0f) && (volume > v->targetVol))
                volume = v->targetVol;
            else if ((v->volDelta < 0.0f) && (volume < v->targetVol))
                volume = v->targetVol;

            if ((v->panDeltaL > 0.0f) && (panningL > v->targetPanL))
                panningL = v->targetPanL;
            else if ((v->panDeltaL < 0.0f) && (panningL < v->targetPanL))
                panningL = v->targetPanL;

            if ((v->panDeltaR > 0.0f) && (panningR > v->targetPanR))
                panningR = v->targetPanR;
            else if ((v->panDeltaR < 0.0f) && (panningR < v->targetPanR))
                panningR = v->targetPanR;
        }
#endif
    }
#ifdef USE_VOL_RAMP
    v->volume   = volume;
    v->panningL = panningL;
    v->panningR = panningR;
#endif
}

static inline void mix8bstereo(PLAYER *p, uint8_t ch, uint32_t samples)
{
    const int8_t *sampleData;
    int8_t loopEnabled;
    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;
    int32_t interpolating;
#ifdef USE_VOL_RAMP
    int32_t rampStyle;
#endif
    uint32_t j;
    float volume;
    float sampleL;
    float sampleR;
    float panningL;
    float panningR;
    void *resampler[2];
    VOICE *v;

    v = &p->voice[ch];
#ifdef USE_VOL_RAMP
    rampStyle = p->rampStyle;
#endif

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = sampleLoopEnd - sampleLoopLength;
    loopEnabled      = v->loopEnabled;
    volume           = v->volume;
    panningL         = v->panningL;
    panningR         = v->panningR;
    interpolating    = v->interpolating;
    sampleData       = v->sampleData;

    resampler[0] = p->resampler[ch];
#ifdef USE_VOL_RAMP
    resampler[1] = p->resampler[ch + 64];
#else
    resampler[1] = p->resampler[ch + 32];
#endif

    resampler_set_rate(resampler[0], v->incRate );
    resampler_set_rate(resampler[1], v->incRate );

    for (j = 0; (j < samples) && sampleData; ++j)
    {
        samplePosition = v->samplePosition;

        while (interpolating > 0 && (resampler_get_free_count(resampler[0]) ||
               (!resampler_get_sample_count(resampler[0]) &&
               !resampler_get_sample_count(resampler[1]))))
        {
            resampler_write_sample_fixed(resampler[0], sampleData[samplePosition], 8);
            resampler_write_sample_fixed(resampler[1], sampleData[sampleLength + samplePosition], 8);

            if (v->playBackwards)
            {
                --samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition < sampleLoopBegin)
                        samplePosition = sampleLoopEnd - (sampleLoopBegin - samplePosition);
                }
                else
                {
                    if (samplePosition < 0)
                    {
                        samplePosition = 0;
                        interpolating  = -resampler_get_padding_size();
                    }
                }
            }
            else
            {
                ++samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition >= sampleLoopEnd)
                        samplePosition = sampleLoopBegin + (samplePosition - sampleLoopEnd);
                }
                else
                {
                    if (samplePosition >= sampleLength)
                        interpolating = -resampler_get_padding_size();
                }
            }
        }

        while (interpolating < 0 && (resampler_get_free_count(resampler[0]) ||
               (!resampler_get_sample_count(resampler[0]) &&
               !resampler_get_sample_count(resampler[1]))))
        {
            resampler_write_sample_fixed(resampler[0], 0, 8);
            resampler_write_sample_fixed(resampler[1], 0, 8);
            ++interpolating;
        }

        v->samplePosition = samplePosition;
        v->interpolating  = (int8_t)(interpolating);

        if (!resampler_get_sample_count(resampler[0]))
        {
            resampler_clear(resampler[0]);
            resampler_clear(resampler[1]);

            v->mixing = 0;
            break;
        }

        sampleL = resampler_get_sample_float(resampler[0]);
        sampleR = resampler_get_sample_float(resampler[1]);
        resampler_remove_sample(resampler[0], 1);
        resampler_remove_sample(resampler[1], 1);

#ifdef USE_VOL_RAMP
        if (rampStyle > 0)
        {
            v->fader += v->faderDelta;

            if ((v->faderDelta > 0.0f) && (v->fader > v->faderDest))
            {
                v->fader = v->faderDest;
            }
            else if ((v->faderDelta < 0.0f) && (v->fader < v->faderDest))
            {
                v->fader = v->faderDest;
                resampler_clear(resampler);

                v->mixing  = 0;
                sampleData = 0;
            }

            sampleL *= v->fader;
            sampleR *= v->fader;
        }
#endif

        sampleL *= volume;
        sampleR *= volume;

        p->masterBufferL[j] += (sampleL * panningL);
        p->masterBufferR[j] += (sampleR * panningR);

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            volume   += v->volDelta;
            panningL += v->panDeltaL;
            panningR += v->panDeltaR;

            if ((v->volDelta > 0.0f) && (volume > v->targetVol))
                volume = v->targetVol;
            else if ((v->volDelta < 0.0f) && (volume < v->targetVol))
                volume = v->targetVol;

            if ((v->panDeltaL > 0.0f) && (panningL > v->targetPanL))
                panningL = v->targetPanL;
            else if ((v->panDeltaL < 0.0f) && (panningL < v->targetPanL))
                panningL = v->targetPanL;

            if ((v->panDeltaR > 0.0f) && (panningR > v->targetPanR))
                panningR = v->targetPanR;
            else if ((v->panDeltaR < 0.0f) && (panningR < v->targetPanR))
                panningR = v->targetPanR;
        }
#endif
    }
#ifdef USE_VOL_RAMP
    v->volume   = volume;
    v->panningL = panningL;
    v->panningR = panningR;
#endif
}

static inline void mix16b(PLAYER *p, uint8_t ch, uint32_t samples)
{
    const int16_t *sampleData;
    int8_t loopEnabled;
    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;
    int32_t interpolating;
#ifdef USE_VOL_RAMP
    int32_t rampStyle;
#endif
    uint32_t j;
    float volume;
    float sample;
    float panningL;
    float panningR;
    void *resampler;
    VOICE *v;

    v = &p->voice[ch];
#ifdef USE_VOL_RAMP
    rampStyle = p->rampStyle;
#endif

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = sampleLoopEnd - sampleLoopLength;
    loopEnabled      = v->loopEnabled;
    volume           = v->volume;
    panningL         = v->panningL;
    panningR         = v->panningR;
    interpolating    = v->interpolating;
    sampleData       = (const int16_t *)(v->sampleData);
    resampler        = p->resampler[ch];

    resampler_set_rate(resampler, v->incRate);

    for (j = 0; (j < samples) && sampleData; ++j)
    {
        samplePosition = v->samplePosition;

        while (interpolating > 0 && (resampler_get_free_count(resampler) || !resampler_get_sample_count(resampler)))
        {
            resampler_write_sample_fixed(resampler, (int16_t)(get_le16(&sampleData[samplePosition])), 16);

            if (v->playBackwards)
            {
                --samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition < sampleLoopBegin)
                        samplePosition = sampleLoopEnd - (sampleLoopBegin - samplePosition);
                }
                else
                {
                    if (samplePosition < 0)
                    {
                        samplePosition = 0;
                        interpolating  = -resampler_get_padding_size();
                    }
                }
            }
            else
            {
                ++samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition >= sampleLoopEnd)
                        samplePosition = sampleLoopBegin + (samplePosition - sampleLoopEnd);
                }
                else
                {
                    if (samplePosition >= sampleLength)
                        interpolating = -resampler_get_padding_size();
                }
            }
        }

        while (interpolating < 0 && (resampler_get_free_count(resampler) || !resampler_get_sample_count(resampler)))
        {
            resampler_write_sample_fixed(resampler, 0, 16);
            ++interpolating;
        }

        v->samplePosition = samplePosition;
        v->interpolating  = (int8_t)(interpolating);

        if (!resampler_get_sample_count(resampler))
        {
            resampler_clear(resampler);

            v->mixing = 0;
            break;
        }

        sample = resampler_get_sample_float(resampler);
        resampler_remove_sample(resampler, 1);

#ifdef USE_VOL_RAMP
        if (rampStyle > 0)
        {
            v->fader += v->faderDelta;

            if ((v->faderDelta > 0.0f) && (v->fader > v->faderDest))
            {
                v->fader = v->faderDest;
            }
            else if ((v->faderDelta < 0.0f) && (v->fader < v->faderDest))
            {
                v->fader = v->faderDest;
                resampler_clear(resampler);

                v->mixing  = 0;
                sampleData = 0;
            }

            sample *= v->fader;
        }
#endif

        sample *= volume;

        p->masterBufferL[j] += (sample * panningL);
        p->masterBufferR[j] += (sample * panningR);

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            volume   += v->volDelta;
            panningL += v->panDeltaL;
            panningR += v->panDeltaR;

            if ((v->volDelta > 0.0f) && (volume > v->targetVol))
                volume = v->targetVol;
            else if ((v->volDelta < 0.0f) && (volume < v->targetVol))
                volume = v->targetVol;

            if ((v->panDeltaL > 0.0f) && (panningL > v->targetPanL))
                panningL = v->targetPanL;
            else if ((v->panDeltaL < 0.0f) && (panningL < v->targetPanL))
                panningL = v->targetPanL;

            if ((v->panDeltaR > 0.0f) && (panningR > v->targetPanR))
                panningR = v->targetPanR;
            else if ((v->panDeltaR < 0.0f) && (panningR < v->targetPanR))
                panningR = v->targetPanR;
        }
#endif
    }
#ifdef USE_VOL_RAMP
    v->volume   = volume;
    v->panningL = panningL;
    v->panningR = panningR;
#endif
}

static inline void mix16bstereo(PLAYER *p, uint8_t ch, uint32_t samples)
{
    const int16_t *sampleData;
    int8_t loopEnabled;
    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;
    int32_t interpolating;
#ifdef USE_VOL_RAMP
    int32_t rampStyle;
#endif
    uint32_t j;
    float volume;
    float sampleL;
    float sampleR;
    float panningL;
    float panningR;
    void *resampler[2];
    VOICE *v;

    v = &p->voice[ch];
#ifdef USE_VOL_RAMP
    rampStyle = p->rampStyle;
#endif

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = sampleLoopEnd - sampleLoopLength;
    loopEnabled      = v->loopEnabled;
    volume           = v->volume;
    panningL         = v->panningL;
    panningR         = v->panningR;
    interpolating    = v->interpolating;
    sampleData       = (const int16_t *)(v->sampleData);

    resampler[0] = p->resampler[ch];
#ifdef USE_VOL_RAMP
    resampler[1] = p->resampler[ch+64];
#else
    resampler[1] = p->resampler[ch+32];
#endif

    resampler_set_rate(resampler[0], v->incRate);
    resampler_set_rate(resampler[1], v->incRate);

    for (j = 0; (j < samples) && sampleData; ++j)
    {
        samplePosition = v->samplePosition;

        while (interpolating > 0 && (resampler_get_free_count(resampler[0]) ||
               (!resampler_get_sample_count(resampler[0]) &&
               !resampler_get_sample_count(resampler[1]))))
        {
            resampler_write_sample_fixed(resampler[0], (int16_t)(get_le16(&sampleData[samplePosition])), 16);
            resampler_write_sample_fixed(resampler[1], (int16_t)(get_le16(&sampleData[sampleLength + samplePosition])), 16);

            if (v->playBackwards)
            {
                --samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition < sampleLoopBegin)
                        samplePosition = sampleLoopEnd - (sampleLoopBegin - samplePosition);
                }
                else
                {
                    if (samplePosition < 0)
                    {
                        samplePosition = 0;
                        interpolating  = -resampler_get_padding_size();
                    }
                }
            }
            else
            {
                ++samplePosition;

                if (loopEnabled)
                {
                    if (samplePosition >= sampleLoopEnd)
                        samplePosition = sampleLoopBegin + (samplePosition - sampleLoopEnd);
                }
                else
                {
                    if (samplePosition >= sampleLength)
                        interpolating = -resampler_get_padding_size();
                }
            }
        }

        while (interpolating < 0 && (resampler_get_free_count(resampler[0]) ||
               (!resampler_get_sample_count(resampler[0]) &&
               !resampler_get_sample_count(resampler[1]))))
        {
            resampler_write_sample_fixed(resampler[0], 0, 16);
            resampler_write_sample_fixed(resampler[1], 0, 16);
            ++interpolating;
        }

        v->samplePosition = samplePosition;
        v->interpolating  = (int8_t)(interpolating);

        if (!resampler_get_sample_count(resampler[0]))
        {
            resampler_clear(resampler[0]);
            resampler_clear(resampler[1]);

            v->mixing = 0;
            break;
        }

        sampleL = resampler_get_sample_float(resampler[0]);
        sampleR = resampler_get_sample_float(resampler[1]);
        resampler_remove_sample(resampler[0], 1);
        resampler_remove_sample(resampler[1], 1);

#ifdef USE_VOL_RAMP
        if (rampStyle > 0)
        {
            v->fader += v->faderDelta;

            if ((v->faderDelta > 0.0f) && (v->fader > v->faderDest))
            {
                v->fader = v->faderDest;
            }
            else if ((v->faderDelta < 0.0f) && (v->fader < v->faderDest))
            {
                v->fader = v->faderDest;
                resampler_clear(resampler);

                v->mixing  = 0;
                sampleData = 0;
            }

            sampleL *= v->fader;
            sampleR *= v->fader;
        }
#endif

        sampleL *= volume;
        sampleR *= volume;

        p->masterBufferL[j] += (sampleL * panningL);
        p->masterBufferR[j] += (sampleR * panningR);

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            volume   += v->volDelta;
            panningL += v->panDeltaL;
            panningR += v->panDeltaR;

            if ((v->volDelta > 0.0f) && (volume > v->targetVol))
                volume = v->targetVol;
            else if ((v->volDelta < 0.0f) && (volume < v->targetVol))
                volume = v->targetVol;

            if ((v->panDeltaL > 0.0f) && (panningL > v->targetPanL))
                panningL = v->targetPanL;
            else if ((v->panDeltaL < 0.0f) && (panningL < v->targetPanL))
                panningL = v->targetPanL;

            if ((v->panDeltaR > 0.0f) && (panningR > v->targetPanR))
                panningR = v->targetPanR;
            else if ((v->panDeltaR < 0.0f) && (panningR < v->targetPanR))
                panningR = v->targetPanR;
        }
#endif
    }
#ifdef USE_VOL_RAMP
    v->volume   = volume;
    v->panningL = panningL;
    v->panningR = panningR;
#endif
}

void mixChannel(PLAYER *p, uint8_t i, uint32_t sampleBlockLength)
{
    if (p->voice[i].incRate && p->voice[i].mixing)
    {
        if (p->voice[i].stereo)
        {
            if (p->voice[i].sixteenBit)
                mix16bstereo(p, i, sampleBlockLength);
            else
                mix8bstereo(p, i, sampleBlockLength);
        }
        else
        {
            if (p->voice[i].sixteenBit)
                mix16b(p, i, sampleBlockLength);
            else
                mix8b(p, i, sampleBlockLength);
        }
    }
}

void mixSampleBlock(PLAYER *p, float *outputStream, uint32_t sampleBlockLength)
{
    float *streamPointer;
    uint8_t i;
    uint32_t j;
    float outL;
    float outR;

#ifdef USE_VOL_RAMP
    int32_t rampStyle = p->rampStyle;
#endif

    streamPointer = outputStream;

    memset(p->masterBufferL, 0, sampleBlockLength * sizeof (float));
    memset(p->masterBufferR, 0, sampleBlockLength * sizeof (float));

    for (i = 0; i < 32; ++i)
    {
        if (p->muted[i / 8] & (1 << (i % 8)))
            continue;

        mixChannel(p, i, sampleBlockLength);

#ifdef USE_VOL_RAMP
        if (rampStyle > 0)
            mixChannel(p, i + 32, sampleBlockLength);
#endif
    }

    for (j = 0; j < sampleBlockLength; ++j)
    {
        outL = p->masterBufferL[j] * p->f_masterVolume;
        outR = p->masterBufferR[j] * p->f_masterVolume;

        *streamPointer++ = outL;
        *streamPointer++ = outR;
    }
}

static void st3play_AdlibMix(PLAYER *p, float *buffer, int32_t count)
{
    int32_t tempbuffer[256];
    int32_t inbuffer_free;
    int32_t outbuffer_avail;
    int32_t i;
    float sample;

    while (count)
    {
        inbuffer_free = resampler_get_free_count(p->fmResampler);
        if (inbuffer_free > 256)
            inbuffer_free = 256;

        if (inbuffer_free)
        {
            Chip_GenerateBlock_Mono(p->fmChip, inbuffer_free, tempbuffer);

            for (i = 0; i < inbuffer_free; ++i)
                resampler_write_sample_fixed( p->fmResampler, (int32_t)(tempbuffer[i]), 16);
        }

        outbuffer_avail = resampler_get_sample_count(p->fmResampler);
        if (outbuffer_avail > count)
            outbuffer_avail = count;

        for (i = 0; i < outbuffer_avail; ++i)
        {
            sample = resampler_get_sample_float(p->fmResampler);
            resampler_remove_sample(p->fmResampler, 1);

            buffer[(i * 2) + 0] += sample;
            buffer[(i * 2) + 1] += sample;
        }

        buffer += (outbuffer_avail * 2);
        count  -=  outbuffer_avail;
    }
}

void st3play_RenderFloat(void *_p, float *buffer, int32_t count)
{
    PLAYER *p;
    int32_t samplesTodo;
    float *outputStream;

    p = (PLAYER *)(_p);
    if (p->isMixing)
    {
        outputStream = buffer;

        while (count)
        {
            if (p->samplesLeft)
            {
                samplesTodo = (count < p->samplesLeft) ? count : p->samplesLeft;
                samplesTodo = (samplesTodo < p->soundBufferSize) ? samplesTodo : p->soundBufferSize;

                if (outputStream)
                {
                    mixSampleBlock(p, outputStream, samplesTodo);

                    if (p->fmChip && p->fmResampler)
                        st3play_AdlibMix(p, outputStream, samplesTodo);

                    outputStream += (samplesTodo * 2);
                }

                p->samplesLeft -= samplesTodo;
                count          -= samplesTodo;
            }
            else
            {
                if (p->Playing)
                    dorow(p);

                p->samplesLeft = p->samplesPerFrame;
            }
        }
    }
}

void st3play_RenderFixed32(void *_p, int32_t *buffer, int32_t count, int8_t depth)
{
    int32_t i;
    float *fbuffer;
    float scale;
    float sample;

    assert(sizeof (int32_t) == sizeof (float));

    fbuffer = (float *)(buffer);
    st3play_RenderFloat(_p, fbuffer, count);

    if (buffer)
    {
        scale = (float)(1 << (depth - 1));
        for (i = 0; i < (count * 2); ++i)
        {
            sample = fbuffer[i] * scale;

                 if (sample > INT_MAX) sample = INT_MAX;
            else if (sample < INT_MIN) sample = INT_MIN;

            buffer[i] = (int32_t)(sample);
        }
    }
}

void st3play_RenderFixed16(void *_p, int16_t *buffer, int32_t count, int8_t depth)
{
    int32_t i;
    int32_t SamplesTodo;
    float scale;
    float sample;
    float fbuffer[1024];

    if (buffer == NULL)
    {
        st3play_RenderFloat(_p, 0, count);
    }
    else
    {
        scale = (float)(1 << (depth - 1));

        while (count)
        {
            SamplesTodo = (count < 512) ? count : 512;

            st3play_RenderFloat(_p, fbuffer, SamplesTodo);
            for (i = 0; i < (SamplesTodo * 2); ++i)
            {
                sample = fbuffer[i] * scale;

                     if (sample >  32767) sample =  32767;
                else if (sample < -32768) sample = -32768;

                buffer[i] = (int16_t)(sample);
            }

            buffer += (SamplesTodo * 2);
            count  -=  SamplesTodo;
        }
    }
}

void FreeSong(PLAYER *p)
{
    uint16_t i;

    p->Playing = 0;

    if (p->adpcmSamples != NULL)
    {
        for (i = 0; i < get_le16(&p->mseg[0x22]); ++i)
        {
            if (p->adpcmSamples[i] != NULL)
                free(p->adpcmSamples[i]);
        }

        free(p->adpcmSamples);
        p->adpcmSamples = NULL;
    }

    memset(p->voice, 0, sizeof (p->voice));

    if (p->mseg)
    {
        free(p->mseg);
        p->mseg = NULL;
    }

    p->ModuleLoaded = 0;
}

void st3play_Mute(void *_p, int8_t channel, int8_t mute)
{
    PLAYER *p;
    int8_t offset;
    int8_t bit;
    uint8_t adlibChannel;

    if (channel > 31)
        return;

    p = (PLAYER *)(_p);

    adlibChannel = (p->mseg[0x40 + channel] & 0x7F) - 16;

    offset = channel / 8;
    bit = 1 << (channel % 8);

    if (mute)
        p->muted[offset] |= bit;
    else
        p->muted[offset] &= ~bit;

    if (adlibChannel < 9)
        Chip_Mute(p->fmChip, adlibChannel, mute);
}

int32_t st3play_GetLoopCount(void *_p)
{
    PLAYER *p = (PLAYER *)(_p);
    return (p->loopCount);
}

void st3play_GetInfo(void *_p, st3_info *info)
{
    int32_t i;
    int32_t channels_playing;
    PLAYER *p;

    p = (PLAYER *)(_p);

    info->order   = p->x_np_ord - 1;
    info->pattern = p->x_np_pat;
    info->row     = p->x_np_row;
    info->speed   = p->musicmax;
    info->tempo   = p->tempo;

    channels_playing = 0;

    if (p->isMixing)
    {
        for (i = 0; i < 32; ++i)
        {
            if (p->voice[i].mixing)
                ++channels_playing;
        }

        if (p->fmChip)
            channels_playing += Chip_GetActiveChannels(p->fmChip);
    }

    info->channels_playing = (int8_t)(channels_playing);
}

// EOF
