/*
** FT2PLAY v0.79 - 14th of September 2017
** =====================================
**
** modplay version for foo_dumb.dll (foobar2000)
**
** Changelog from v0.78:
** - RelocateTon() had unneeded logic in it
** - Fixed behavior of quirky vol/pan envelopes (like FT2 now)
** - Volume ramp is now changed to behave like in FT2
** - Added a quirk simulation on <0 (pan slide left 0)
** - Fixed Pxy (panning slide) bug
**
** Changelog from v0.77
** - Instr. mute flag should be tested as !=1, not !mute (fixed malicious xm)
**
** Changelog from v0.68 (later changelogs missing):
** - Bug fix for Lxx (Set Envelope Position)
** 
** Changelog from v0.67:
** - Bug in GetNewNote() (cmd 390 would fail - "unreal2 scirreal mix.xm" fix)
**
** Changelog from v0.66:
** - Auto-vibrato was wrong on every type except for sine
**
** Changelog from v0.65:
** - RelocateTon() was less accurate, changed back to the older one
**    and made it a little bit safer. This one is slower tho' :o(
** - Forgot to zero out the internal Stm channels in StopVoices().
**
** Changelog from v0.64:
** - Fixed a critical bug in the finetune calculation.
**
** C port of FastTracker II's replayer, by 8bitbubsy (Olav Sorensen)
** using the original pascal+asm source codes by Mr.H (Fredrik Huss)
** of Triton.
**
** Thanks to aciddose (and kode54) for coding the vol/sample ramp.
** The volume ramp is tune to that of FT2.
**
** (extreme) non-FT2 extensions:
** - Max 127 channels (was 32)
** - Non-even amount-of-channels number (FT2 supports *even* numbers only)
** - Max 256 instruments (was 128)
** - Max 32 samples per instrument (was 16)
** - Max 1024 rows per pattern (was 256)
** - Stereo samples
**
** These additions shouldn't break FT2 accuracy unless I'm wrong.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include <assert.h>
#include <limits.h>
#include "resampler.h"
#include "barray.h"
#include "ft2play.h"

#if defined(_MSC_VER) && !defined(inline)
#define inline __forceinline
typedef signed long ssize_t;
#endif

#ifdef min
#undef min
#endif
#define min(a,b) (((a) < (b)) ? (a) : (b))

#define USE_VOL_RAMP
#define _soundBufferSize 512

enum
{
    IS_Vol            = 1,
    IS_Period         = 2,
    IS_NyTon          = 4,
    IS_Pan            = 8,
    IS_QuickVol       = 16
};


/* *** STRUCTS *** (remember 1-byte alignment for header/loader structs) */

#ifdef _MSC_VER
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct SongHeaderTyp_t
{
    char Sig[17];
    char Name[21];
    char ProggName[20];
    uint16_t Ver;
    int32_t HeaderSize;
    uint16_t Len;
    uint16_t RepS;
    uint16_t AntChn;
    uint16_t AntPtn;
    uint16_t AntInstrs;
    uint16_t Flags;
    uint16_t DefTempo;
    uint16_t DefSpeed;
    uint8_t SongTab[256];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
SongHeaderTyp;

typedef struct SampleHeaderTyp_t
{
    int32_t Len;
    int32_t RepS;
    int32_t RepL;
    uint8_t vol;
    int8_t Fine;
    uint8_t Typ;
    uint8_t Pan;
    int8_t RelTon;
    uint8_t skrap;
    char Name[22];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
SampleHeaderTyp;

typedef struct InstrHeaderTyp_t
{
    int32_t InstrSize;
    char Name[22];
    uint8_t Typ;
    uint16_t AntSamp;
    int32_t SampleSize;
    uint8_t TA[96];
    int16_t EnvVP[12][2];
    int16_t EnvPP[12][2];
    uint8_t EnvVPAnt;
    uint8_t EnvPPAnt;
    uint8_t EnvVSust;
    uint8_t EnvVRepS;
    uint8_t EnvVRepE;
    uint8_t EnvPSust;
    uint8_t EnvPRepS;
    uint8_t EnvPRepE;
    uint8_t EnvVTyp;
    uint8_t EnvPTyp;
    uint8_t VibTyp;
    uint8_t VibSweep;
    uint8_t VibDepth;
    uint8_t VibRate;
    uint16_t FadeOut;
    uint8_t MIDIOn;
    uint8_t MIDIChannel;
    int16_t MIDIProgram;
    int16_t MIDIBend;
    int8_t Mute;
    uint8_t Reserved[15];
    SampleHeaderTyp Samp[32];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
InstrHeaderTyp;

typedef struct PatternHeaderTyp_t
{
    int32_t PatternHeaderSize;
    uint8_t Typ;
    uint16_t PattLen;
    uint16_t DataLen;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
PatternHeaderTyp;
#ifdef _MSC_VER
#pragma pack(pop)
#endif

typedef struct SongTyp_t
{
    uint16_t Len;
    uint16_t RepS;
    uint8_t AntChn;
    uint16_t AntPtn;
    uint16_t AntInstrs;
    int16_t SongPos;
    int16_t PattNr;
    int16_t PattPos;
    int16_t PattLen;
    uint16_t Speed;
    uint16_t Tempo;
    uint16_t InitSpeed;
    uint16_t InitTempo;
    int16_t GlobVol; /* must be signed */
    uint16_t Timer;
    uint8_t PattDelTime;
    uint8_t PattDelTime2;
    uint8_t PBreakFlag;
    uint8_t PBreakPos;
    uint8_t PosJumpFlag;
    uint8_t SongTab[256];
    uint16_t Ver;
    char Name[21];
    char ProgName[21];
    char InstrName[256][23];
    uint16_t startOrder;
} SongTyp;

typedef struct SampleTyp_t
{
    int32_t Len;
    int32_t RepS;
    int32_t RepL;
    uint8_t Vol;
    int8_t Fine;
    uint8_t Typ;
    uint8_t Pan;
    int8_t RelTon;
    uint8_t skrap;
    char Name[22];
    int8_t *Pek;
} SampleTyp;

typedef struct InstrTyp_t
{
    uint32_t SampleSize;
    uint8_t TA[96];
    int16_t EnvVP[12][2];
    int16_t EnvPP[12][2];
    uint8_t EnvVPAnt;
    uint8_t EnvPPAnt;
    uint8_t EnvVSust;
    uint8_t EnvVRepS;
    uint8_t EnvVRepE;
    uint8_t EnvPSust;
    uint8_t EnvPRepS;
    uint8_t EnvPRepE;
    uint8_t EnvVTyp;
    uint8_t EnvPTyp;
    uint8_t VibTyp;
    uint8_t VibSweep;
    uint8_t VibDepth;
    uint8_t VibRate;
    uint16_t FadeOut;
    uint8_t MIDIOn;
    uint8_t MIDIChannel;
    uint16_t MIDIProgram;
    uint16_t MIDIBend;
    uint8_t Mute;
    uint8_t Reserved[15];
    uint16_t AntSamp;
    SampleTyp Samp[32];
} InstrTyp;

typedef struct StmTyp_t
{
    SampleTyp InstrOfs; /* read only */
    InstrTyp InstrSeg;  /* read only */
    float FinalVol;
    int8_t OutVol;       /* must be signed */
    int8_t RealVol;      /* must be signed */
    int8_t RelTonNr;     /* must be signed */
    int8_t FineTune;     /* must be signed */
    int16_t RealPeriod;  /* must be signed */
    int32_t FadeOutAmp;  /* must be signed */
    int16_t EnvVIPValue; /* must be signed */
    int16_t EnvPIPValue; /* must be signed */
    uint8_t OldVol;
    uint8_t OldPan;
    uint8_t OutPan;
    uint16_t OutPeriod;
    uint8_t FinalPan;
    uint16_t FinalPeriod;
    uint8_t EnvSustainActive;
    uint16_t SmpStartPos;
    uint16_t InstrNr;
    uint16_t TonTyp;
    uint8_t EffTyp;
    uint8_t Eff;
    uint8_t SmpOffset;
    uint16_t WantPeriod;
    uint8_t WaveCtrl;
    uint8_t Status;
    uint8_t PortaDir;
    uint8_t GlissFunk;
    uint16_t PortaSpeed;
    uint8_t VibPos;
    uint8_t TremPos;
    uint8_t VibSpeed;
    uint8_t VibDepth;
    uint8_t TremSpeed;
    uint8_t TremDepth;
    uint8_t PattPos;
    uint8_t LoopCnt;
    uint8_t VolSlideSpeed;
    uint8_t FVolSlideUpSpeed;
    uint8_t FVolSlideDownSpeed;
    uint8_t FPortaUpSpeed;
    uint8_t FPortaDownSpeed;
    uint8_t EPortaUpSpeed;
    uint8_t EPortaDownSpeed;
    uint8_t PortaUpSpeed;
    uint8_t PortaDownSpeed;
    uint8_t RetrigSpeed;
    uint8_t RetrigCnt;
    uint8_t RetrigVol;
    uint8_t VolKolVol;
    uint8_t TonNr;
    uint16_t FadeOutSpeed;
    uint16_t EnvVCnt;
    uint8_t EnvVPos;
    uint16_t EnvVAmp;
    uint16_t EnvPCnt;
    uint8_t EnvPPos;
    uint16_t EnvPAmp;
    uint8_t EVibPos;
    uint16_t EVibAmp;
    uint16_t EVibSweep;
    uint8_t TremorSave;
    uint8_t TremorPos;
    uint8_t GlobVolSlideSpeed;
    uint8_t PanningSlideSpeed;
    uint8_t Mute;
    uint8_t Nr;
} StmTyp;

typedef struct TonTyp_t
{
    uint8_t Ton;
    uint8_t Instr;
    uint8_t Vol;
    uint8_t EffTyp;
    uint8_t Eff;
} TonTyp;

typedef struct
{
    const int8_t *sampleData;
    int8_t loopEnabled;
    int8_t sixteenBit;
    int8_t stereo;
    int8_t loopBidi;
    int8_t loopingForward;
    int32_t sampleLength;
    int32_t sampleLoopBegin;
    int32_t sampleLoopEnd;
    int32_t samplePosition;
    int32_t sampleLoopLength;
    int8_t interpolating;

    float incRate;
    float volumeL;
    float volumeR;
#ifdef USE_VOL_RAMP
    float targetVolL;
    float targetVolR;
    float volDeltaL;
    float volDeltaR;
    float fader;
    float faderDelta;
    float faderDest;
#endif
} VOICE;

#define InstrHeaderSize (sizeof (InstrHeaderTyp) - (32 * sizeof (SampleHeaderTyp)))

#ifdef USE_VOL_RAMP
enum
{
    MAX_VOICES   = 127,
    TOTAL_VOICES = 254,
    SPARE_OFFSET = 127
};
#else
enum
{
    MAX_VOICES   = 127,
    TOTAL_VOICES = 127
};
#endif

typedef struct
{
    uint8_t *_ptr;
    size_t _cnt;
    uint8_t *_base;
    size_t _bufsiz;
    int32_t _eof;
} MEM;


typedef struct
{
    int8_t  *VibSineTab;
    uint16_t PattLens[256];
    int16_t *Note2Period;
    int16_t *linearPeriods;
    int16_t *amigaPeriods;
    uint32_t *LogTab;
    int8_t   LinearFrqTab;
    uint32_t soundBufferSize;
    uint32_t outputFreq;

    TonTyp   *NilPatternLine;
    TonTyp   *Patt[256];
    StmTyp    Stm[MAX_VOICES];
    SongTyp   Song;
    InstrTyp *Instr[255 + 1];
    VOICE voice[TOTAL_VOICES];

    void *resampler[TOTAL_VOICES*2];

    float *PanningTab;
    float f_outputFreq;

#ifdef USE_VOL_RAMP
    float quickVolRampMul_f;
    float tickVolRampMul_f;
#endif

    int8_t samplingInterpolation;
    int8_t rampStyle;
    float *masterBufferL;
    float *masterBufferR;
    int32_t samplesLeft;
    uint32_t samplesPerFrame;

    /* globally accessed */
    int8_t ModuleLoaded;/*  = 0; */
    int8_t Playing;/*       = 0; */
    uint16_t numChannels;/* = 127; */

    uint8_t muted[16];

    uint32_t loopCount;
    void * playedRows;
    uint16_t playedRowsPatLoop[1024];
} PLAYER;

/* FUNCTION DECLARATIONS */


static MEM *mopen(const uint8_t *src, size_t length);
static void mclose(MEM **buf);
static size_t mread(void *buffer, size_t size, size_t count, MEM *buf);
static size_t mread_swap(void *buffer, size_t size, size_t count, MEM *buf, uint8_t le_xor, uint8_t be_xor);
static int32_t meof(MEM *buf);
static void mseek(MEM *buf, ssize_t offset, int32_t whence);
static void setSamplesPerFrame(PLAYER *, uint32_t val);
static void voiceSetSource(PLAYER *, uint8_t i, 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 i, uint16_t value);
static void voiceSetVolume(PLAYER *, uint8_t i, float vol, uint8_t pan, uint8_t status);
static void voiceSetSamplingFrequency(PLAYER *, uint8_t i, uint32_t samplingFrequency);
static void ft2play_FreeSong(PLAYER *);


/* TABLES AND VARIABLES */


static uint16_t AmigaFinePeriod[12 * 8] =
{
    907,900,894,887,881,875,868,862,856,850,844,838,
    832,826,820,814,808,802,796,791,785,779,774,768,
    762,757,752,746,741,736,730,725,720,715,709,704,
    699,694,689,684,678,675,670,665,660,655,651,646,
    640,636,632,628,623,619,614,610,604,601,597,592,
    588,584,580,575,570,567,563,559,555,551,547,543,
    538,535,532,528,524,520,516,513,508,505,502,498,
    494,491,487,484,480,477,474,470,467,463,460,457
};

/* This table is so small that generating it is almost as big */
static uint8_t VibTab[32] =
{
    0,  24,  49, 74, 97,120,141,161,
    180,197,212,224,235,244,250,253,
    255,253,250,244,235,224,212,197,
    180,161,141,120, 97, 74, 49, 24
};


/* CODE START */


static inline int8_t voiceIsActive(PLAYER *p, uint8_t i)
{
    return (p->voice[i].sampleData != NULL);
}

static inline void RetrigVolume(StmTyp *ch)
{
    ch->RealVol = ch->OldVol;
    ch->OutVol  = ch->OldVol;
    ch->OutPan  = ch->OldPan;
    ch->Status |= (IS_Vol + IS_Pan + IS_QuickVol);
}

static void RetrigEnvelopeVibrato(StmTyp *ch)
{
    if (!(ch->WaveCtrl & 0x04)) ch->VibPos  = 0;
    if (!(ch->WaveCtrl & 0x40)) ch->TremPos = 0;

    ch->RetrigCnt = 0;
    ch->TremorPos = 0;

    ch->EnvSustainActive = 1;

    if (ch->InstrSeg.EnvVTyp & 1)
    {
        ch->EnvVCnt = 65535;
        ch->EnvVPos = 0;
    }

    if (ch->InstrSeg.EnvPTyp & 1)
    {
        ch->EnvPCnt = 65535;
        ch->EnvPPos = 0;
    }

    ch->FadeOutSpeed = ch->InstrSeg.FadeOut; /* FT2 doesn't check if fadeout is more than 4095 */
    ch->FadeOutAmp   = 32768;

    if (ch->InstrSeg.VibDepth != 0)
    {
        ch->EVibPos = 0;

        if (ch->InstrSeg.VibSweep != 0)
        {
            ch->EVibAmp   = 0;
            ch->EVibSweep = ((uint16_t)(ch->InstrSeg.VibDepth) << 8) / ch->InstrSeg.VibSweep;
        }
        else
        {
            ch->EVibAmp   = (uint16_t)(ch->InstrSeg.VibDepth) << 8;
            ch->EVibSweep = 0;
        }
    }
}

static void KeyOff(StmTyp *ch)
{
    ch->EnvSustainActive = 0;

    if (!(ch->InstrSeg.EnvPTyp & 1)) /* yes, FT2 does this (!) */
    {
        if (ch->EnvPCnt >= ch->InstrSeg.EnvPP[ch->EnvPPos][0])
            ch->EnvPCnt  = ch->InstrSeg.EnvPP[ch->EnvPPos][0] - 1;
    }

    if (ch->InstrSeg.EnvVTyp & 1)
    {
        if (ch->EnvVCnt >= ch->InstrSeg.EnvVP[ch->EnvVPos][0])
            ch->EnvVCnt  = ch->InstrSeg.EnvVP[ch->EnvVPos][0] - 1;
    }
    else
    {
        ch->RealVol = 0;
        ch->OutVol  = 0;
        ch->Status |= (IS_Vol + IS_QuickVol);
    }
}

static inline uint32_t GetFrequenceValue(PLAYER *p, uint16_t period)
{
    uint16_t index;

    if (!period) return (0);

    if (p->LinearFrqTab)
    {
        index = (12 * 192 * 4) - period;
        return (p->LogTab[index % 768] >> ((14 - (index / 768)) & 0x1F));
    }
    else
    {
        return ((1712 * 8363) / period);
    }
}

/* don't inline this */
static void StartTone(PLAYER *p, uint8_t Ton, uint8_t EffTyp, uint8_t Eff, StmTyp *ch)
{
    SampleTyp *s;

    uint16_t tmpTon;
    uint8_t samp;
    uint8_t tonLookUp;
    uint8_t tmpFTune;

    /* if we came from Rxy (retrig), we didn't check note (Ton) yet */
    if (Ton == 97)
    {
        KeyOff(ch);
        return;
    }

    if (!Ton)
    {
        Ton = ch->TonNr;
        if (!Ton) return; /* if still no note, return. */
    }
    /* ------------------------------------------------------------ */

    ch->TonNr = Ton;

    if (p->Instr[ch->InstrNr] != NULL)
        ch->InstrSeg = *p->Instr[ch->InstrNr];
    else
        ch->InstrSeg = *p->Instr[0]; /* placeholder for invalid samples */

    /* non-FT2 security fix */
    tonLookUp = Ton - 1;
    if (tonLookUp > 95) tonLookUp = 95;
    /*----------------------------------*/

    ch->Mute = ch->InstrSeg.Mute;

    samp         = ch->InstrSeg.TA[tonLookUp] & 0x1F;
    s            = &ch->InstrSeg.Samp[samp];
    ch->InstrOfs = *s;
    ch->RelTonNr = s->RelTon;

    Ton += ch->RelTonNr;
    if (Ton >= (12 * 10)) return;

    ch->OldVol = s->Vol;

    /*
    ** FT2 doesn't do this, but we don't want to blow our eardrums
    ** on malicious XMs...
    */
    if (ch->OldVol > 64) ch->OldVol = 64;

    ch->OldPan = s->Pan;

    if ((EffTyp == 0x0E) && ((Eff & 0xF0) == 0x50))
        ch->FineTune = (int8_t)((Eff & 0x0F) << 4) - 128;
    else
        ch->FineTune = s->Fine;

    if (Ton)
    {
        // signed 8-bit >>3 without rounding or undefined behavior (FT2 exact)
        tmpFTune = (ch->FineTune >= 0) ? (ch->FineTune >> 3) : (0xE0 | ((uint8_t)(ch->FineTune) >> 3));
        tmpTon = (((Ton - 1) & 0x00FF) << 4) + ((tmpFTune + 16) & 0x00FF);

        if (tmpTon < ((12 * 10 * 16) + 16)) /* should never happen, but FT2 does this check */
        {
            ch->RealPeriod = p->Note2Period[tmpTon];
            ch->OutPeriod  = ch->RealPeriod;
        }
    }

    ch->Status |= (IS_Period + IS_Vol + IS_Pan + IS_NyTon + IS_QuickVol);

    if (EffTyp == 9)
    {
        if (Eff)
            ch->SmpOffset = ch->Eff;

        ch->SmpStartPos = (uint16_t)(ch->SmpOffset) << 8;
    }
    else
    {
        ch->SmpStartPos = 0;
    }
}

/* don't inline this */
static void MultiRetrig(PLAYER *p, StmTyp *ch)
{
    uint8_t cnt;
    int16_t vol;
    int8_t cmd;

    cnt = ch->RetrigCnt + 1;
    if (cnt < ch->RetrigSpeed)
    {
        ch->RetrigCnt = cnt;
        return;
    }

    ch->RetrigCnt = 0;

    vol = ch->RealVol;
    cmd = ch->RetrigVol;

    /* 0x00 and 0x08 are not handled, ignore them */

    if      (cmd == 0x01) vol  -= 1;
    else if (cmd == 0x02) vol  -= 2;
    else if (cmd == 0x03) vol  -= 4;
    else if (cmd == 0x04) vol  -= 8;
    else if (cmd == 0x05) vol  -= 16;
    else if (cmd == 0x06) vol   = (vol >> 1) + (vol >> 3) + (vol >> 4);
    else if (cmd == 0x07) vol >>= 1;
    else if (cmd == 0x09) vol  += 1;
    else if (cmd == 0x0A) vol  += 2;
    else if (cmd == 0x0B) vol  += 4;
    else if (cmd == 0x0C) vol  += 8;
    else if (cmd == 0x0D) vol  += 16;
    else if (cmd == 0x0E) vol   = (vol >> 1) + vol;
    else if (cmd == 0x0F) vol  += vol; /* signed *2 */

    if      (vol <  0) vol =  0;
    else if (vol > 64) vol = 64;

    ch->RealVol = (int8_t)(vol);
    ch->OutVol  = (int8_t)(vol);

    if ((ch->VolKolVol >= 0x10) && (ch->VolKolVol <= 0x50))
    {
        ch->OutVol  = ch->VolKolVol - 0x10;
        ch->RealVol = ch->OutVol;
    }
    else if ((ch->VolKolVol >= 0xC0) && (ch->VolKolVol <= 0xCF))
    {
        ch->OutPan = (ch->VolKolVol & 0x0F) << 4;
    }

    StartTone(p, 0, 0, 0, ch);
}

/* don't inline this */
static void CheckEffects(PLAYER *p, StmTyp *ch)
{
    int8_t envUpdate;
    uint8_t tmpEff;
    uint8_t tmpEffHi;
    int16_t newEnvPos;
    int8_t envPos;
    uint16_t i;

    /* *** VOLUME COLUMN EFFECTS (TICK 0) *** */

    /* set volume */
    if ((ch->VolKolVol >= 0x10) && (ch->VolKolVol <= 0x50))
    {
        ch->OutVol  = ch->VolKolVol - 0x10;
        ch->RealVol = ch->OutVol;

        ch->Status |= (IS_Vol + IS_QuickVol);
    }

    /* fine volume slide down */
    else if ((ch->VolKolVol & 0xF0) == 0x80)
    {
        ch->RealVol -= (ch->VolKolVol & 0x0F);
        if (ch->RealVol < 0) ch->RealVol = 0;

        ch->OutVol  = ch->RealVol;
        ch->Status |= IS_Vol;
    }

    /* fine volume slide up */
    else if ((ch->VolKolVol & 0xF0) == 0x90)
    {
        ch->RealVol += (ch->VolKolVol & 0x0F);
        if (ch->RealVol > 64) ch->RealVol = 64;

        ch->OutVol  = ch->RealVol;
        ch->Status |= IS_Vol;
    }

    /* set vibrato speed */
    else if ((ch->VolKolVol & 0xF0) == 0xA0)
        ch->VibSpeed = (ch->VolKolVol & 0x0F) << 2;

    /* set panning */
    else if ((ch->VolKolVol & 0xF0) == 0xC0)
    {
        ch->OutPan  = (ch->VolKolVol & 0x0F) << 4;
        ch->Status |= IS_Pan;
    }


    // *** MAIN EFFECTS (TICK 0) ***


    if ((ch->EffTyp == 0) && (ch->Eff == 0)) return;

    // 8xx - set panning
    if (ch->EffTyp == 8)
    {
        ch->OutPan  = ch->Eff;
        ch->Status |= IS_Pan;
    }

    // Bxx - position jump
    else if (ch->EffTyp == 11)
    {
        p->Song.SongPos     = ch->Eff;
        p->Song.SongPos    -= 1;
        p->Song.PBreakPos   = 0;
        p->Song.PosJumpFlag = 1;
    }

    // Cxx - set volume
    else if (ch->EffTyp == 12)
    {
        ch->RealVol = ch->Eff;
        if (ch->RealVol > 64) ch->RealVol = 64;

        ch->OutVol  = ch->RealVol;
        ch->Status |= (IS_Vol + IS_QuickVol);
    }

    // Dxx - pattern break
    else if (ch->EffTyp == 13)
    {
        p->Song.PosJumpFlag = 1;

        tmpEff = ((ch->Eff >> 4) * 10) + (ch->Eff & 0x0F);
        if (tmpEff <= 63)
            p->Song.PBreakPos = tmpEff;
        else
            p->Song.PBreakPos = 0;
    }

    // Exx - E effects
    else if (ch->EffTyp == 14)
    {
        // E1x - fine period slide up
        if ((ch->Eff & 0xF0) == 0x10)
        {
            tmpEff = ch->Eff & 0x0F;
            if (!tmpEff)
                tmpEff = ch->FPortaUpSpeed;

            ch->FPortaUpSpeed = tmpEff;

            ch->RealPeriod -= ((int16_t)(tmpEff) << 2);
            if (ch->RealPeriod < 1) ch->RealPeriod = 1;

            ch->OutPeriod = ch->RealPeriod;
            ch->Status   |= IS_Period;
        }

        // E2x - fine period slide down
        else if ((ch->Eff & 0xF0) == 0x20)
        {
            tmpEff = ch->Eff & 0x0F;
            if (!tmpEff)
                tmpEff = ch->FPortaDownSpeed;

            ch->FPortaDownSpeed = tmpEff;

            ch->RealPeriod += ((int16_t)(tmpEff) << 2);
            if (ch->RealPeriod > (32000 - 1)) ch->RealPeriod = 32000 - 1;

            ch->OutPeriod = ch->RealPeriod;
            ch->Status   |= IS_Period;
        }

        // E3x - set glissando type
        else if ((ch->Eff & 0xF0) == 0x30) ch->GlissFunk = ch->Eff & 0x0F;

        // E4x - set vibrato waveform
        else if ((ch->Eff & 0xF0) == 0x40) ch->WaveCtrl = (ch->WaveCtrl & 0xF0) | (ch->Eff & 0x0F);

        // E5x (set finetune) is handled in StartTone();

        // E6x - pattern loop
        else if ((ch->Eff & 0xF0) == 0x60)
        {
            if (ch->Eff == 0x60) // E60, empty param
            {
                ch->PattPos = p->Song.PattPos & 0x00FF;
            }
            else
            {
                if (!ch->LoopCnt)
                {
                    ch->LoopCnt     = ch->Eff & 0x0F;
                    p->Song.PBreakPos  = ch->PattPos;
                    p->Song.PBreakFlag = 1;
                }
                else
                {
                    ch->LoopCnt--;
                    if (ch->LoopCnt)
                    {
                        p->Song.PBreakPos  = ch->PattPos;
                        p->Song.PBreakFlag = 1;
                    }
                }
                if (p->Song.PBreakFlag == 1)
                {
                    size_t i, j;
                    for (i = 0; i < 1024 && p->playedRowsPatLoop[i] != 0xFFFF && p->playedRowsPatLoop[i] != ch->PattPos; ++i);
                    for (j = i; i < 1024 && p->playedRowsPatLoop[i] != 0xFFFF; ++i)
                        bit_array_clear(p->playedRows, p->Song.SongPos * 1024 + p->playedRowsPatLoop[i]);
                    memset(p->playedRowsPatLoop + j, 0xFF, (i - j) * 2);
                }
            }
        }

        // E7x - set tremolo waveform
        else if ((ch->Eff & 0xF0) == 0x70) ch->WaveCtrl = ((ch->Eff & 0x0F) << 4) | (ch->WaveCtrl & 0x0F);

        // E8x - set 4-bit panning (NON-FT2)
        else if ((ch->Eff & 0xF0) == 0x80)
        {
            ch->OutPan  = (ch->Eff & 0x0F) << 4;
            ch->Status |= IS_Pan;
        }

        // EAx - fine volume slide up
        else if ((ch->Eff & 0xF0) == 0xA0)
        {
            tmpEff = ch->Eff & 0x0F;
            if (!tmpEff)
                tmpEff = ch->FVolSlideUpSpeed;

            ch->FVolSlideUpSpeed = tmpEff;

            ch->RealVol += tmpEff;
            if (ch->RealVol > 64) ch->RealVol = 64;

            ch->OutVol  = ch->RealVol;
            ch->Status |= IS_Vol;
        }

        // EBx - fine volume slide down
        else if ((ch->Eff & 0xF0) == 0xB0)
        {
            tmpEff = ch->Eff & 0x0F;
            if (!tmpEff)
                tmpEff = ch->FVolSlideDownSpeed;

            ch->FVolSlideDownSpeed = tmpEff;

            ch->RealVol -= tmpEff;
            if (ch->RealVol < 0) ch->RealVol = 0;

            ch->OutVol = ch->RealVol;
            ch->Status |= IS_Vol;
        }

        // ECx - note cut
        else if ((ch->Eff & 0xF0) == 0xC0)
        {
            if (ch->Eff == 0xC0) // empty param
            {
                ch->RealVol = 0;
                ch->OutVol  = 0;
                ch->Status |= IS_Vol;
            }
        }

        // EEx - pattern delay
        else if ((ch->Eff & 0xF0) == 0xE0)
        {
            if (!p->Song.PattDelTime2)
                p->Song.PattDelTime = (ch->Eff & 0x0F) + 1;
        }
    }

    // Fxx - set speed/tempo
    else if (ch->EffTyp == 15)
    {
        if (ch->Eff >= 32)
        {
            p->Song.Speed = ch->Eff;
            setSamplesPerFrame(p, ((p->outputFreq * 5) / 2) / p->Song.Speed);
        }
        else
        {
            // F00 makes sense for stopping the song in tracker,
            // but in a replayer let's make the song start over instead.
            // We make this a pattern jump now, so the player doesn't start
            // pulling effects from the wrong row, and so the first row of
            // the first order isn't skipped.
            if (ch->Eff == 0)
            {
                memset(p->voice, 0, sizeof (p->voice));

                p->Song.PBreakPos   = 0;
                p->Song.PosJumpFlag = 1;
                p->Song.SongPos     = p->Song.startOrder - 1;
                p->Song.Timer       = 1;
                p->Song.Speed       = p->Song.InitSpeed;
                p->Song.Tempo       = p->Song.InitTempo;
                p->Song.GlobVol     = 64;
            }
            else
            {
                p->Song.Tempo = ch->Eff;
                p->Song.Timer = ch->Eff;
            }
        }
    }

    // Gxx - set global volume
    else if (ch->EffTyp == 16)
    {
        p->Song.GlobVol = ch->Eff;
        if (p->Song.GlobVol > 64) p->Song.GlobVol = 64;

        for (i = 0; i < p->Song.AntChn; ++i) p->Stm[i].Status |= IS_Vol;
    }

    // Lxx - set vol and pan envelope position
    else if (ch->EffTyp == 21)
    {
        /* *** VOLUME ENVELOPE *** */
        if (ch->InstrSeg.EnvVTyp & 1)
        {
            ch->EnvVCnt = ch->Eff - 1;

            envPos = 0;
            envUpdate = 1;
            newEnvPos = ch->Eff;

            if (ch->InstrSeg.EnvVPAnt > 1)
            {
                envPos++;
                for (i = 0; i < ch->InstrSeg.EnvVPAnt - 1; ++i)
                {
                    if (newEnvPos < ch->InstrSeg.EnvVP[envPos][0])
                    {
                        envPos--;

                        newEnvPos -= ch->InstrSeg.EnvVP[envPos][0];
                        if (newEnvPos == 0)
                        {
                            envUpdate = 0;
                            break;
                        }

                        if (ch->InstrSeg.EnvVP[envPos + 1][0] <= ch->InstrSeg.EnvVP[envPos][0])
                        {
                            envUpdate = 1;
                            break;
                        }

                        ch->EnvVIPValue = ((ch->InstrSeg.EnvVP[envPos + 1][1] - ch->InstrSeg.EnvVP[envPos][1]) & 0x00FF) << 8;
                        ch->EnvVIPValue /= (ch->InstrSeg.EnvVP[envPos + 1][0] - ch->InstrSeg.EnvVP[envPos][0]);

                        ch->EnvVAmp = (ch->EnvVIPValue * (newEnvPos - 1)) + ((ch->InstrSeg.EnvVP[envPos][1] & 0x00FF) << 8);

                        envPos++;

                        envUpdate = 0;
                        break;
                    }

                    envPos++;
                }

                if (envUpdate) envPos--;
            }

            if (envUpdate)
            {
                ch->EnvVIPValue = 0;
                ch->EnvVAmp = (ch->InstrSeg.EnvVP[envPos][1] & 0x00FF) << 8;
            }

            if (envPos >= ch->InstrSeg.EnvVPAnt)
            {
                envPos = ch->InstrSeg.EnvVPAnt - 1;
                if (envPos < 0)
                    envPos = 0;
            }

            ch->EnvVPos = envPos;
        }

        /* *** PANNING ENVELOPE *** */
        if (ch->InstrSeg.EnvVTyp & 2) /* probably an FT2 bug */
        {
            ch->EnvPCnt = ch->Eff - 1;

            envPos = 0;
            envUpdate = 1;
            newEnvPos = ch->Eff;

            if (ch->InstrSeg.EnvPPAnt > 1)
            {
                envPos++;
                for (i = 0; i < ch->InstrSeg.EnvPPAnt - 1; ++i)
                {
                    if (newEnvPos < ch->InstrSeg.EnvPP[envPos][0])
                    {
                        envPos--;

                        newEnvPos -= ch->InstrSeg.EnvPP[envPos][0];
                        if (newEnvPos == 0)
                        {
                            envUpdate = 0;
                            break;
                        }

                        if (ch->InstrSeg.EnvPP[envPos + 1][0] <= ch->InstrSeg.EnvPP[envPos][0])
                        {
                            envUpdate = 1;
                            break;
                        }

                        ch->EnvPIPValue = ((ch->InstrSeg.EnvPP[envPos + 1][1] - ch->InstrSeg.EnvPP[envPos][1]) & 0x00FF) << 8;
                        ch->EnvPIPValue /= (ch->InstrSeg.EnvPP[envPos + 1][0] - ch->InstrSeg.EnvPP[envPos][0]);

                        ch->EnvPAmp = (ch->EnvPIPValue * (newEnvPos - 1)) + ((ch->InstrSeg.EnvPP[envPos][1] & 0x00FF) << 8);

                        envPos++;

                        envUpdate = 0;
                        break;
                    }

                    envPos++;
                }

                if (envUpdate) envPos--;
            }

            if (envUpdate)
            {
                ch->EnvPIPValue = 0;
                ch->EnvPAmp = (ch->InstrSeg.EnvPP[envPos][1] & 0x00FF) << 8;
            }

            if (envPos >= ch->InstrSeg.EnvPPAnt)
            {
                envPos = ch->InstrSeg.EnvPPAnt - 1;
                if (envPos < 0)
                    envPos = 0;
            }

            ch->EnvPPos = envPos;
        }
    }

    // Rxy - note multi retrigger
    else if (ch->EffTyp == 27)
    {
        tmpEff = ch->Eff & 0x0F;
        if (!tmpEff)
            tmpEff = ch->RetrigSpeed;

        ch->RetrigSpeed = tmpEff;

        tmpEffHi = ch->Eff >> 4;
        if (!tmpEffHi)
            tmpEffHi = ch->RetrigVol;

        ch->RetrigVol = tmpEffHi;

        if (!ch->VolKolVol) MultiRetrig(p, ch);
    }

    // X1x - extra fine period slide up
    else if ((ch->EffTyp == 33) && ((ch->Eff & 0xF0) == 0x10))
    {
        tmpEff = ch->Eff & 0x0F;
        if (!tmpEff)
            tmpEff = ch->EPortaUpSpeed;

        ch->EPortaUpSpeed = tmpEff;

        ch->RealPeriod -= tmpEff;
        if (ch->RealPeriod < 1) ch->RealPeriod = 1;

        ch->OutPeriod = ch->RealPeriod;
        ch->Status   |= IS_Period;
    }

    // X2x - extra fine period slide down
    else if ((ch->EffTyp == 33) && ((ch->Eff & 0xF0) == 0x20))
    {
        tmpEff = ch->Eff & 0x0F;
        if (!tmpEff)
            tmpEff = ch->EPortaDownSpeed;

        ch->EPortaDownSpeed = tmpEff;

        ch->RealPeriod += tmpEff;
        if (ch->RealPeriod > (32000 - 1)) ch->RealPeriod = 32000 - 1;

        ch->OutPeriod = ch->RealPeriod;
        ch->Status   |= IS_Period;
    }
}

static void fixTonePorta(PLAYER *pl, StmTyp *ch, TonTyp *p, uint8_t inst)
{
    uint16_t portaTmp;
    uint8_t tmpFTune;

    if (p->Ton)
    {
        if (p->Ton == 97)
        {
            KeyOff(ch);
        }
        else
        {
            // signed 8-bit >>3 without rounding or undefined behavior (FT2 exact)
            tmpFTune = (ch->FineTune >= 0) ? (ch->FineTune >> 3) : (0xE0 | ((uint8_t)(ch->FineTune) >> 3));
            portaTmp = ((((p->Ton - 1) + ch->RelTonNr) & 0x00FF) << 4) + ((tmpFTune + 16) & 0x00FF);

            if (portaTmp < ((12 * 10 * 16) + 16))
            {
                ch->WantPeriod = pl->Note2Period[portaTmp];

                if (ch->WantPeriod == ch->RealPeriod)
                    ch->PortaDir = 0;
                else if (ch->WantPeriod > ch->RealPeriod)
                    ch->PortaDir = 1;
                else
                    ch->PortaDir = 2;
            }
        }
    }

    if (inst)
    {
        RetrigVolume(ch);

        if (p->Ton != 97)
            RetrigEnvelopeVibrato(ch);
    }
}

static inline void GetNewNote(PLAYER *pl, StmTyp *ch, TonTyp *p)
{
    uint8_t inst;

    ch->VolKolVol = p->Vol;

    if (!ch->EffTyp)
    {
        if (ch->Eff)
        {
            /* we have an arpeggio running, set period back */
            ch->OutPeriod = ch->RealPeriod;
            ch->Status   |= IS_Period;
        }
    }
    else
    {
        if ((ch->EffTyp == 4) || (ch->EffTyp == 6))
        {
            /* we have a vibrato running */
            if ((p->EffTyp != 4) && (p->EffTyp != 6))
            {
                /* but it's ending at the next (this) row, so set period back */
                ch->OutPeriod = ch->RealPeriod;
                ch->Status   |= IS_Period;
            }
        }
    }

    ch->EffTyp = p->EffTyp;
    ch->Eff    = p->Eff;
    ch->TonTyp = (p->Instr << 8) | p->Ton;

    /* 'inst' var is used for later if checks... */
    inst = p->Instr;
    if (inst)
    {
        if ((pl->Song.AntInstrs > 128) || (inst <= 128)) /* >128 insnum hack */
            ch->InstrNr = inst;
        else
            inst = 0;
    }

    if (p->EffTyp == 0x0E)
    {
        if ((p->Eff >= 0xD1) && (p->Eff <= 0xDF))
            return; /* we have a note delay (ED1..EDF) */
    }

    if (!((p->EffTyp == 0x0E) && (p->Eff == 0x90))) /* E90 is 'retrig' speed 0 */
    {
        if ((ch->VolKolVol & 0xF0) == 0xF0) /* gxx */
        {
            if (ch->VolKolVol & 0x0F)
                ch->PortaSpeed = (int16_t)(ch->VolKolVol & 0x0F) << 6;

            fixTonePorta(pl, ch, p, inst);

            CheckEffects(pl, ch);
            return;
        }

        if ((p->EffTyp == 3) || (p->EffTyp == 5)) /* 3xx or 5xx */
        {
            if ((p->EffTyp != 5) && p->Eff)
                ch->PortaSpeed = (int16_t)(p->Eff) << 2;

            fixTonePorta(pl, ch, p, inst);

            CheckEffects(pl, ch);
            return;
        }

        if ((p->EffTyp == 0x14) && !p->Eff) /* K00 (KeyOff) */
        {
            KeyOff(ch);

            if (inst)
                RetrigVolume(ch);

            CheckEffects(pl, ch);
            return;
        }

        if (!p->Ton)
        {
            if (inst)
            {
                RetrigVolume(ch);
                RetrigEnvelopeVibrato(ch);
            }

            CheckEffects(pl, ch);
            return;
        }
    }

    if (p->Ton == 97)
        KeyOff(ch);
    else
        StartTone(pl, p->Ton, p->EffTyp, p->Eff, ch);

    if (inst)
    {
        RetrigVolume(ch);

        if (p->Ton != 97)
            RetrigEnvelopeVibrato(ch);
    }

    CheckEffects(pl, ch);
}

static void FixaEnvelopeVibrato(PLAYER *p, StmTyp *ch)
{  
    int8_t envInterpolateFlag, envDidInterpolate;
    uint8_t envPos;
    int16_t autoVibVal, autoVibAmp, tmpPeriod, panTmp;
    uint16_t envVal;

    /* *** FADEOUT *** */
    if (!ch->EnvSustainActive)
    {
        ch->Status |= IS_Vol;

        /* unsigned clamp + reset */
        if (ch->FadeOutAmp >= ch->FadeOutSpeed)
        {
            ch->FadeOutAmp -= ch->FadeOutSpeed;
        }
        else
        {
            ch->FadeOutAmp   = 0;
            ch->FadeOutSpeed = 0;
        }
    }

    if (ch->Mute != 1)
    {
        /* *** VOLUME ENVELOPE *** */
        envVal = 0;
        if (ch->InstrSeg.EnvVTyp & 1)
        {
            envDidInterpolate = 0;
            envPos = ch->EnvVPos;

            ch->EnvVCnt++;
            if (ch->EnvVCnt == ch->InstrSeg.EnvVP[envPos][0])
            {
                ch->EnvVAmp = (ch->InstrSeg.EnvVP[envPos][1] & 0x00FF) << 8;

                envPos++;
                if (ch->InstrSeg.EnvVTyp & 4)
                {
                    envPos--;

                    if (envPos == ch->InstrSeg.EnvVRepE)
                    {
                        if (!(ch->InstrSeg.EnvVTyp & 2) || (envPos != ch->InstrSeg.EnvVSust) || ch->EnvSustainActive)
                        {
                            envPos = ch->InstrSeg.EnvVRepS;

                            ch->EnvVCnt =  ch->InstrSeg.EnvVP[envPos][0];
                            ch->EnvVAmp = (ch->InstrSeg.EnvVP[envPos][1] & 0x00FF) << 8;
                        }
                    }

                    envPos++;
                }

                if (envPos < ch->InstrSeg.EnvVPAnt)
                {
                    envInterpolateFlag = 1;
                    if ((ch->InstrSeg.EnvVTyp & 2) && ch->EnvSustainActive)
                    {
                        if ((envPos - 1) == ch->InstrSeg.EnvVSust)
                        {
                            envPos--;
                            ch->EnvVIPValue = 0;
                            envInterpolateFlag = 0;
                        }
                    }

                    if (envInterpolateFlag)
                    {
                        ch->EnvVPos = envPos;

                        ch->EnvVIPValue = 0;
                        if (ch->InstrSeg.EnvVP[envPos][0] > ch->InstrSeg.EnvVP[envPos - 1][0])
                        {
                            ch->EnvVIPValue = ((ch->InstrSeg.EnvVP[envPos][1] - ch->InstrSeg.EnvVP[envPos - 1][1]) & 0x00FF) << 8;
                            ch->EnvVIPValue /= (ch->InstrSeg.EnvVP[envPos][0] - ch->InstrSeg.EnvVP[envPos - 1][0]);

                            envVal = ch->EnvVAmp;
                            envDidInterpolate = 1;
                        }
                    }
                }
                else
                {
                    ch->EnvVIPValue = 0;
                }
            }

            if (!envDidInterpolate)
            {
                ch->EnvVAmp += ch->EnvVIPValue;

                envVal = ch->EnvVAmp;
                if ((envVal >> 8) > 0x40)
                {
                    if ((envVal >> 8) > ((0x40 + 0xC0) / 2))
                        envVal = 16384;
                    else
                        envVal = 0;

                    ch->EnvVIPValue = 0;
                }
            }
            
            envVal >>= 8;
            
            ch->FinalVol = (p->Song.GlobVol * (1.0f / 64.0f)) * (envVal * (1.0f / 64.0f)) * (ch->OutVol * (1.0f / 64.0f)) * (ch->FadeOutAmp * (1.0f / 32768.0f));
            ch->Status   |= IS_Vol;
        }
        else
        {
            ch->FinalVol = (p->Song.GlobVol * (1.0f / 64.0f)) * (ch->OutVol * (1.0f / 64.0f)) * (ch->FadeOutAmp * (1.0f / 32768.0f));
        }
    }
    else
    {
        ch->FinalVol = 0.0f;
    }

    /* *** PANNING ENVELOPE *** */
    envVal = 0;
    if (ch->InstrSeg.EnvPTyp & 1)
    {
        envDidInterpolate = 0;
        envPos = ch->EnvPPos;

        ch->EnvPCnt++;
        if (ch->EnvPCnt == ch->InstrSeg.EnvPP[envPos][0])
        {
            ch->EnvPAmp = (ch->InstrSeg.EnvPP[envPos][1] & 0x00FF) << 8;

            envPos++;
            if (ch->InstrSeg.EnvPTyp & 4)
            {
                envPos--;

                if (envPos == ch->InstrSeg.EnvPRepE)
                {
                    if (!(ch->InstrSeg.EnvPTyp & 2) || (envPos != ch->InstrSeg.EnvPSust) || ch->EnvSustainActive)
                    {
                        envPos = ch->InstrSeg.EnvPRepS;

                        ch->EnvPCnt =  ch->InstrSeg.EnvPP[envPos][0];
                        ch->EnvPAmp = (ch->InstrSeg.EnvPP[envPos][1] & 0x00FF) << 8;
                    }
                }

                envPos++;
            }

            if (envPos < ch->InstrSeg.EnvPPAnt)
            {
                envInterpolateFlag = 1;
                if ((ch->InstrSeg.EnvPTyp & 2) && ch->EnvSustainActive)
                {
                    if ((envPos - 1) == ch->InstrSeg.EnvPSust)
                    {
                        envPos--;
                        ch->EnvPIPValue = 0;
                        envInterpolateFlag = 0;
                    }
                }

                if (envInterpolateFlag)
                {
                    ch->EnvPPos = envPos;
                    
                    ch->EnvPIPValue = 0;
                    if (ch->InstrSeg.EnvPP[envPos][0] > ch->InstrSeg.EnvPP[envPos - 1][0])
                    {
                        ch->EnvPIPValue = ((ch->InstrSeg.EnvPP[envPos][1] - ch->InstrSeg.EnvPP[envPos - 1][1]) & 0x00FF) << 8;
                        ch->EnvPIPValue /= (ch->InstrSeg.EnvPP[envPos][0] - ch->InstrSeg.EnvPP[envPos - 1][0]);

                        envVal = ch->EnvPAmp;
                        envDidInterpolate = 1;
                    }
                }
            }
            else
            {
                ch->EnvPIPValue = 0;
            }
        }

        if (!envDidInterpolate)
        {
            ch->EnvPAmp += ch->EnvPIPValue;

            envVal = ch->EnvPAmp;
            if ((envVal >> 8) > 0x40)
            {
                if ((envVal >> 8) > ((0x40 + 0xC0) / 2))
                    envVal = 16384;
                else
                    envVal = 0;

                ch->EnvPIPValue = 0;
            }
        }

        /* panning calculated exactly like FT2 */
        panTmp = ch->OutPan - 128;
        if (panTmp > 0)
            panTmp = 0 - panTmp;
        panTmp += 128;

        envVal -= (32 * 256);
        
        ch->FinalPan = ch->OutPan + (int8_t)((((int16_t)(envVal) * (panTmp << 3)) >> 16) & 0xFF);
        
        ch->Status  |= IS_Pan;
    }
    else
    {
        ch->FinalPan = ch->OutPan;
    }

    /* *** AUTO VIBRATO *** */
    if (ch->InstrSeg.VibDepth != 0)
    {
        if (ch->EVibSweep != 0)
        {
            autoVibAmp = ch->EVibSweep;
            if (ch->EnvSustainActive)
            {
                autoVibAmp += ch->EVibAmp;
                if ((autoVibAmp >> 8) > ch->InstrSeg.VibDepth)
                {
                    ch->EVibAmp   = ch->InstrSeg.VibDepth << 8;
                    ch->EVibSweep = 0;
                }

                ch->EVibAmp = autoVibAmp;
            }
        }
        else
        {
            autoVibAmp = ch->EVibAmp;
        }

        ch->EVibPos += ch->InstrSeg.VibRate;

        /* square */
        if (ch->InstrSeg.VibTyp == 1)
            autoVibVal = (ch->EVibPos > 127) ? 64 : -64;

        /* ramp up */
        else if (ch->InstrSeg.VibTyp == 2)
            autoVibVal = (((ch->EVibPos >> 1) + 64) & 127) - 64;

        /* ramp down */
        else if (ch->InstrSeg.VibTyp == 3)
            autoVibVal = (((0 - (ch->EVibPos >> 1)) + 64) & 127) - 64;

        /* sine */
        else
            autoVibVal = p->VibSineTab[ch->EVibPos];

        autoVibVal <<= 2;

        tmpPeriod = ch->OutPeriod + ((autoVibVal * autoVibAmp) >> 16);
        if (tmpPeriod > (32000 - 1)) tmpPeriod = 0; /* yes, FT2 zeroes it out */

        ch->FinalPeriod = tmpPeriod;
        ch->Status  |= IS_Period;
    }
    else
    {
        ch->FinalPeriod = ch->OutPeriod;
    }
}

static int16_t RelocateTon(PLAYER *p, int16_t period, int8_t relativeNote, StmTyp *ch)
{
    int8_t i, fineTune;
    int32_t loPeriod, hiPeriod, tmpPeriod, tableIndex;

    fineTune = (ch->FineTune >> 3) + 16; /* MUST use >> 3 here (sar cl,3) - safe for x86/x86_64 */
    hiPeriod = 8 * 12 * 16;
    loPeriod = 0;

    for (i = 0; i < 8; ++i)
    {
        tmpPeriod = (((loPeriod + hiPeriod) / 2) & ~15) + fineTune;

        tableIndex = tmpPeriod - 8;
        if (tableIndex < 0) /* added security check */
            tableIndex = 0;

        if (period >= p->Note2Period[tableIndex])
            hiPeriod = tmpPeriod - fineTune;
        else
            loPeriod = tmpPeriod - fineTune;
    }

    tmpPeriod = loPeriod + fineTune + (relativeNote * 16);
    if (tmpPeriod < 0) /* added security check */
        tmpPeriod = 0;

    if (tmpPeriod >= ((8 * 12 * 16) + 15) - 1) /* FT2 bug: stupid off-by-one edge case */
        tmpPeriod  =  (8 * 12 * 16) + 15;

    return (p->Note2Period[tmpPeriod]);
}

static void TonePorta(PLAYER *p, StmTyp *ch)
{
    if (ch->PortaDir)
    {
        if (ch->PortaDir > 1)
        {
            ch->RealPeriod -= ch->PortaSpeed;
            if (ch->RealPeriod <= (int16_t)ch->WantPeriod)
            {
                ch->PortaDir   = 1;
                ch->RealPeriod = ch->WantPeriod;
            }
        }
        else
        {
            ch->RealPeriod += ch->PortaSpeed;
            if ((uint16_t)ch->RealPeriod >= ch->WantPeriod)
            {
                ch->PortaDir   = 1;
                ch->RealPeriod = ch->WantPeriod;
            }
        }

        if (ch->GlissFunk) /* semi-tone slide flag */
            ch->OutPeriod = RelocateTon(p, ch->RealPeriod, 0, ch);
        else
            ch->OutPeriod = ch->RealPeriod;

        ch->Status |= IS_Period;
    }
}

static void Volume(StmTyp *ch) /* actually volume slide */
{
    uint8_t tmpEff;

    tmpEff = ch->Eff;
    if (!tmpEff)
        tmpEff = ch->VolSlideSpeed;

    ch->VolSlideSpeed = tmpEff;

    if (!(tmpEff & 0xF0))
    {
        ch->RealVol -= tmpEff;
        if (ch->RealVol < 0) ch->RealVol = 0;
    }
    else
    {
        ch->RealVol += (tmpEff >> 4);
        if (ch->RealVol > 64) ch->RealVol = 64;
    }

    ch->OutVol  = ch->RealVol;
    ch->Status |= IS_Vol;
}

static void Vibrato2(StmTyp *ch)
{
    uint8_t tmpVibPos;
    int8_t tmpVibTyp;

    tmpVibPos = (ch->VibPos >> 2) & 0x1F;
    tmpVibTyp =  ch->WaveCtrl     & 0x03;

    if (tmpVibTyp == 0)
    {
        tmpVibPos = VibTab[tmpVibPos];
    }
    else if (tmpVibTyp == 1)
    {
        tmpVibPos <<= 3; /* (0..31) * 8 */
        if (ch->VibPos >= 128) tmpVibPos ^= -1;
    }
    else
    {
        tmpVibPos = 255;
    }

    tmpVibPos = ((uint16_t)(tmpVibPos) * ch->VibDepth) >> 5;

    if (ch->VibPos >= 128) ch->OutPeriod = ch->RealPeriod - tmpVibPos;
    else                   ch->OutPeriod = ch->RealPeriod + tmpVibPos;

    ch->Status |= IS_Period;
    ch->VibPos += ch->VibSpeed;
}

static inline void Vibrato(StmTyp *ch)
{
    if (ch->Eff)
    {
        if (ch->Eff & 0x0F) ch->VibDepth =  ch->Eff & 0x0F;
        if (ch->Eff & 0xF0) ch->VibSpeed = (ch->Eff & 0xF0) >> 2; /* speed*4 */
    }

    Vibrato2(ch);
}

static inline void DoEffects(PLAYER *p, StmTyp *ch)
{
    int8_t note;
    uint8_t tmpEff;

    uint8_t tremorData;
    uint8_t tremorSign;
    uint8_t tmpTremPos;
    uint8_t tmpTremTyp;
    uint16_t i;
    uint16_t tick;

    /* *** VOLUME COLUMN EFFECTS (TICKS >0) *** */

    /* volume slide down */
    if ((ch->VolKolVol & 0xF0) == 0x60)
    {
        ch->RealVol -= (ch->VolKolVol & 0x0F);
        if (ch->RealVol < 0) ch->RealVol = 0;

        ch->OutVol  = ch->RealVol;
        ch->Status |= IS_Vol;
    }

    /* volume slide up */
    else if ((ch->VolKolVol & 0xF0) == 0x70)
    {
        ch->RealVol += (ch->VolKolVol & 0x0F);
        if (ch->RealVol > 64) ch->RealVol = 64;

        ch->OutVol  = ch->RealVol;
        ch->Status |= IS_Vol;
    }

    /* vibrato (+ set vibrato depth) */
    else if ((ch->VolKolVol & 0xF0) == 0xB0)
    {
        if (ch->VolKolVol != 0xB0)
            ch->VibDepth = ch->VolKolVol & 0x0F;

        Vibrato2(ch);
    }

    /* pan slide left */
    else if ((ch->VolKolVol & 0xF0) == 0xD0)
    {
        /* unsigned clamp + a bug when the parameter is 0  */
        if (((ch->VolKolVol & 0x0F) == 0) || (ch->OutPan < (ch->VolKolVol & 0x0F)))
            ch->OutPan = 0;
        else
            ch->OutPan -= (ch->VolKolVol & 0x0F);
 
        ch->Status |= IS_Pan;
    }

    /* pan slide right */
    else if ((ch->VolKolVol & 0xF0) == 0xE0)
    {
        ch->OutPan += (ch->VolKolVol & 0x0F);
        if (ch->OutPan > 255) ch->OutPan = 255;

        ch->Status |= IS_Pan;
    }

    /* tone porta */
    else if ((ch->VolKolVol & 0xF0) == 0xF0) TonePorta(p, ch);

    /* *** MAIN EFFECTS (TICKS >0) *** */

    if (((ch->Eff == 0) && (ch->EffTyp == 0)) || (ch->EffTyp >= 36)) return;

    /* 0xy - Arpeggio */
    if (ch->EffTyp == 0)
    {
        tick = p->Song.Timer;
        note = 0;

        /* FT2 "out of boundary" arp LUT simulation */
        if      (tick  > 16) tick  = 2;
        else if (tick == 16) tick  = 0;
        else                 tick %= 3;

        /*
        ** this simulation doesn't work properly for >=128 tick arps.
        ** but you'd need to hexedit the initial speed to get >31
        */
        if (tick == 0)
        {
            ch->OutPeriod = ch->RealPeriod;
        }
        else
        {
            if      (tick == 1) note = ch->Eff >> 4;
            else if (tick == 2) note = ch->Eff & 0x0F;

            ch->OutPeriod = RelocateTon(p, ch->RealPeriod, note, ch);
        }

        ch->Status |= IS_Period;
    }

    /* 1xx - period slide up */
    else if (ch->EffTyp == 1)
    {
        tmpEff = ch->Eff;
        if (!tmpEff)
            tmpEff = ch->PortaUpSpeed;

        ch->PortaUpSpeed = tmpEff;

        ch->RealPeriod -= ((int16_t)(tmpEff) << 2);
        if (ch->RealPeriod < 1) ch->RealPeriod = 1;

        ch->OutPeriod = ch->RealPeriod;
        ch->Status   |= IS_Period;
    }

    /* 2xx - period slide down */
    else if (ch->EffTyp == 2)
    {
        tmpEff = ch->Eff;
        if (!tmpEff)
            tmpEff = ch->PortaUpSpeed;

        ch->PortaUpSpeed = tmpEff;

        ch->RealPeriod += ((int16_t)(tmpEff) << 2);
        if (ch->RealPeriod > (32000 - 1)) ch->RealPeriod = 32000 - 1;

        ch->OutPeriod = ch->RealPeriod;
        ch->Status   |= IS_Period;
    }

    /* 3xx - tone portamento */
    else if (ch->EffTyp == 3) TonePorta(p, ch);

    /* 4xy - vibrato */
    else if (ch->EffTyp == 4) Vibrato(ch);

    /* 5xy - tone portamento + volume slide */
    else if (ch->EffTyp == 5)
    {
        TonePorta(p, ch);
        Volume(ch);
    }

    /* 6xy - vibrato + volume slide */
    else if (ch->EffTyp == 6)
    {
        Vibrato2(ch);
        Volume(ch);
    }

    /* 7xy - tremolo */
    else if (ch->EffTyp == 7)
    {
        tmpEff = ch->Eff;
        if (tmpEff)
        {
            if (tmpEff & 0x0F) ch->TremDepth =  tmpEff & 0x0F;
            if (tmpEff & 0xF0) ch->TremSpeed = (tmpEff & 0xF0) >> 2; /* speed*4 */
        }

        tmpTremPos = (ch->TremPos  >> 2) & 0x1F;
        tmpTremTyp = (ch->WaveCtrl >> 4) & 0x03;

        if (tmpTremTyp == 0)
        {
            tmpTremPos = VibTab[tmpTremPos];
        }
        else if (tmpTremTyp == 1)
        {
            tmpTremPos <<= 3; /* (0..31) * 8 */
            if (ch->VibPos >= 128) tmpTremPos ^= -1; /* VibPos indeed, FT2 bug */
        }
        else
        {
            tmpTremPos = 255;
        }

        tmpTremPos = (uint8_t)(((uint16_t)(tmpTremPos) * ch->TremDepth) >> 6);

        if (ch->TremPos >= 128)
        {
            ch->OutVol = ch->RealVol - tmpTremPos;
            if (ch->OutVol < 0) ch->OutVol = 0;
        }
        else
        {
            ch->OutVol = ch->RealVol + tmpTremPos;
            if (ch->OutVol > 64) ch->OutVol = 64;
        }

        ch->TremPos += ch->TremSpeed;

        ch->Status |= IS_Vol;
    }

    /* Axy - volume slide */
    else if (ch->EffTyp == 10) Volume(ch); /* actually volume slide */

    /* Exy - E effects */
    else if (ch->EffTyp == 14)
    {
        /* E9x - note retrigger */
        if ((ch->Eff & 0xF0) == 0x90)
        {
            if (ch->Eff != 0x90) /* E90 is handled in GetNewNote(); */
            {
                if (!((p->Song.Tempo - p->Song.Timer) % (ch->Eff & 0x0F)))
                {
                    StartTone(p, 0, 0, 0, ch);
                    RetrigEnvelopeVibrato(ch);
                }
            }
        }

        /* ECx - note cut */
        else if ((ch->Eff & 0xF0) == 0xC0)
        {
            if (((p->Song.Tempo - p->Song.Timer) & 0x00FF) == (ch->Eff & 0x0F))
            {
                ch->OutVol  = 0;
                ch->RealVol = 0;
                ch->Status |= (IS_Vol + IS_QuickVol);
            }
        }

        /* EDx - note delay */
        else if ((ch->Eff & 0xF0) == 0xD0)
        {
            if (((p->Song.Tempo - p->Song.Timer) & 0x00FF) == (ch->Eff & 0x0F))
            {
                StartTone(p, ch->TonTyp & 0x00FF, 0, 0, ch);

                if (ch->TonTyp & 0xFF00)
                    RetrigVolume(ch);

                RetrigEnvelopeVibrato(ch);

                if ((ch->VolKolVol >= 0x10) && (ch->VolKolVol <= 0x50))
                {
                    ch->OutVol  = ch->VolKolVol - 16;
                    ch->RealVol = ch->OutVol;
                }
                else if ((ch->VolKolVol >= 0xC0) && (ch->VolKolVol <= 0xCF))
                {
                    ch->OutPan = (ch->VolKolVol & 0x0F) << 4;
                }
            }
        }
    }

    /* Hxy - global volume slide */
    else if (ch->EffTyp == 17)
    {
        tmpEff = ch->Eff;
        if (!tmpEff)
            tmpEff = ch->GlobVolSlideSpeed;
        ch->GlobVolSlideSpeed = tmpEff;

        if (!(tmpEff & 0xF0))
        {
            p->Song.GlobVol -= tmpEff;
            if (p->Song.GlobVol < 0) p->Song.GlobVol = 0;
        }
        else
        {
            p->Song.GlobVol += (tmpEff >> 4);
            if (p->Song.GlobVol > 64) p->Song.GlobVol = 64;
        }

        for (i = 0; i < p->Song.AntChn; ++i) p->Stm[i].Status |= IS_Vol;
    }

    /* Kxx - key off */
    else if (ch->EffTyp == 20)
    {
        if (((p->Song.Tempo - p->Song.Timer) & 31) == (ch->Eff & 0x0F))
            KeyOff(ch);
    }

    /* Pxy - panning slide */
    else if (ch->EffTyp == 25)
    {
        tmpEff = ch->Eff;
        if (!tmpEff)
            tmpEff = ch->PanningSlideSpeed;

        ch->PanningSlideSpeed = tmpEff;

        if ((tmpEff & 0xF0) == 0)
        {
            /* unsigned clamp */
            if (ch->OutPan >= tmpEff)
                ch->OutPan -= tmpEff;
            else
                ch->OutPan = 0;
        }
        else
        {
            tmpEff >>= 4;

            /* unsigned clamp */
            if (ch->OutPan <= (255 - tmpEff))
                ch->OutPan += tmpEff;
            else
                ch->OutPan = 255;
        }

        ch->Status |= IS_Pan;
    }

    /* Rxy - multi note retrig */
    else if (ch->EffTyp == 27) MultiRetrig(p, ch);

    /* Txy - tremor */
    else if (ch->EffTyp == 29)
    {
        tmpEff = ch->Eff;
        if (!tmpEff)
            tmpEff = ch->TremorSave;

        ch->TremorSave = tmpEff;

        tremorSign = ch->TremorPos & 0x80;
        tremorData = ch->TremorPos & 0x7F;

        if (--tremorData & 0x80)
        {
            if (tremorSign == 0x80)
            {
                tremorSign = 0x00;
                tremorData = tmpEff & 0x0F;
            }
            else
            {
                tremorSign = 0x80;
                tremorData = tmpEff >> 4;
            }
        }

        ch->TremorPos = tremorData | tremorSign;

        ch->OutVol  = tremorSign ? ch->RealVol : 0;
        ch->Status |= (IS_Vol + IS_QuickVol);
    }
}

static void MainPlayer(PLAYER *p) /* periodically called from mixer */
{
    StmTyp *ch;
    SampleTyp s;

    uint8_t i;
    int8_t tickzero;

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

    if (p->Playing)
    {
        tickzero = 0;

        if (--(p->Song.Timer) == 0)
        {
            p->Song.Timer = p->Song.Tempo;
            tickzero   = 1;
        }

        if (tickzero)
        {
            if (!p->Song.PattDelTime2)
            {
                for (i = 0; i < p->Song.AntChn; ++i)
                {
                    if (p->Patt[p->Song.PattNr] != NULL)
                        GetNewNote(p, &p->Stm[i], &p->Patt[p->Song.PattNr][(p->Song.PattPos * MAX_VOICES) + i]);
                    else
                        GetNewNote(p, &p->Stm[i], &p->NilPatternLine[(p->Song.PattPos * MAX_VOICES) + i]);

                    FixaEnvelopeVibrato(p, &p->Stm[i]);
                }
            }
            else
            {
                for (i = 0; i < p->Song.AntChn; ++i)
                {
                    DoEffects(p, &p->Stm[i]);
                    FixaEnvelopeVibrato(p, &p->Stm[i]);
                }
            }
        }
        else
        {
            for (i = 0; i < p->Song.AntChn; ++i)
            {
                DoEffects(p, &p->Stm[i]);
                FixaEnvelopeVibrato(p, &p->Stm[i]);
            }
        }

        if (p->Song.Timer == 1)
        {
            size_t playedRowsCount;

            p->Song.PattPos++;

            if (p->Song.PattDelTime)
            {
                p->Song.PattDelTime2 = p->Song.PattDelTime;
                p->Song.PattDelTime  = 0;
            }

            if (p->Song.PattDelTime2)
            {
                p->Song.PattDelTime2--;
                if (p->Song.PattDelTime2)
                {
                    p->Song.PattPos--;
                    bit_array_clear(p->playedRows, p->Song.SongPos * 1024 + p->Song.PattPos);
                }
            }

            if (p->Song.PBreakFlag)
            {
                p->Song.PBreakFlag = 0;
                p->Song.PattPos    = p->Song.PBreakPos;
            }

            for (playedRowsCount = 0; playedRowsCount < 1024 && p->playedRowsPatLoop[playedRowsCount] != 0xFFFF; ++playedRowsCount);

            if ((p->Song.PattPos >= p->Song.PattLen) || p->Song.PosJumpFlag)
            {
                int16_t oldSongPos = p->Song.SongPos;

                p->Song.PattPos     = p->Song.PBreakPos;
                p->Song.PBreakPos   = 0;
                p->Song.PosJumpFlag = 0;

                p->Song.SongPos++;
                if (p->Song.SongPos >= p->Song.Len) p->Song.SongPos = p->Song.RepS;

                p->Song.PattNr  = p->Song.SongTab[p->Song.SongPos];
                p->Song.PattLen = p->PattLens[p->Song.PattNr];

                if (oldSongPos != p->Song.SongPos)
                {
                    size_t i;
                    for (i = 0; i < playedRowsCount; ++i)
                        bit_array_set(p->playedRows, oldSongPos * 1024 + p->playedRowsPatLoop[i]);
                    memset(p->playedRowsPatLoop, 0xFF, playedRowsCount * 2);
                    playedRowsCount = 0;
                }
            }

            if (bit_array_test(p->playedRows, p->Song.SongPos * 1024 + p->Song.PattPos))
            {
                p->loopCount++;
                bit_array_reset(p->playedRows);
            }
            bit_array_set(p->playedRows, p->Song.SongPos * 1024 + p->Song.PattPos);
            p->playedRowsPatLoop[playedRowsCount] = p->Song.PattPos;
        }
    }
    else
    {
        for (i = 0; i < p->Song.AntChn; ++i)
            FixaEnvelopeVibrato(p, &p->Stm[i]);
    }

    /* update mixer */
    for (i = 0; i < p->Song.AntChn; ++i)
    {
        ch = &p->Stm[i];

        if (ch->Status & IS_NyTon)
        {
            s = ch->InstrOfs;

#ifdef USE_VOL_RAMP
            if (rampStyle > 0 && voiceIsActive(p, ch->Nr))
            {
                int32_t ChNr = ch->Nr;
                memcpy(p->voice + SPARE_OFFSET + ChNr, p->voice + ChNr, sizeof (VOICE));

                p->voice[SPARE_OFFSET + ChNr].faderDest  = 0.0f;
                p->voice[SPARE_OFFSET + ChNr].faderDelta =
                (p->voice[SPARE_OFFSET + ChNr].faderDest - p->voice[SPARE_OFFSET + ChNr].fader) * p->quickVolRampMul_f;

                resampler_dup_inplace(p->resampler[SPARE_OFFSET + ChNr], p->resampler[ChNr]);
                resampler_dup_inplace(p->resampler[TOTAL_VOICES + SPARE_OFFSET + ChNr], p->resampler[TOTAL_VOICES + ChNr]);
                resampler_clear(p->resampler[ChNr]);
                resampler_clear(p->resampler[TOTAL_VOICES + ChNr]);
            }
#endif

            voiceSetSource(p, ch->Nr, s.Pek, s.Len, s.RepL, s.RepS + s.RepL, s.Typ & 3, s.Typ & 16, s.Typ & 32);
            voiceSetSamplePosition(p, ch->Nr, ch->SmpStartPos);

#ifdef USE_VOL_RAMP
            if (rampStyle > 0)
            {
                p->voice[ch->Nr].fader      = 0.0f;
                p->voice[ch->Nr].faderDest  = 1.0f;
                p->voice[ch->Nr].faderDelta = (p->voice[ch->Nr].faderDest - p->voice[ch->Nr].fader) * p->quickVolRampMul_f;
            }
#endif
        }

        if (ch->Status & (IS_Vol | IS_Pan))
            voiceSetVolume(p, ch->Nr, ch->FinalVol, ch->FinalPan, ch->Status);

        if (ch->Status & IS_Period)
            voiceSetSamplingFrequency(p, ch->Nr, GetFrequenceValue(p, ch->FinalPeriod));

        ch->Status = 0;
    }
}

static void StopVoices(PLAYER *p)
{
    uint8_t a;

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

    for (a = 0; a < MAX_VOICES; ++a)
    {
        StmTyp *ch = &p->Stm[a];
        ch->Nr       = a;
        ch->TonTyp   = 0;
        ch->RelTonNr = 0;
        ch->InstrNr  = 0;
        ch->InstrSeg = *p->Instr[0];
        ch->Status   = IS_Vol;
        ch->RealVol  = 0;
        ch->OutVol   = 0;
        ch->OldVol   = 0;
        ch->FinalVol = 0.0f;
        ch->OldPan   = 128;
        ch->OutPan   = 128;
        ch->FinalPan = 128;
        ch->VibDepth = 0;

        voiceSetVolume(p, a, ch->FinalVol, ch->FinalPan, 1);
    }
}

static void SetPos(PLAYER *p, int16_t SongPos, int16_t PattPos)
{
    if (SongPos > -1)
    {
        p->Song.SongPos = SongPos;
        if ((p->Song.Len > 0) && (p->Song.SongPos >= p->Song.Len))
            p->Song.SongPos = p->Song.Len - 1;

        p->Song.PattNr  = p->Song.SongTab[SongPos];
        p->Song.PattLen = p->PattLens[p->Song.PattNr];
    }

    if (PattPos > -1)
    {
        p->Song.PattPos = PattPos;
        if (p->Song.PattPos >= p->Song.PattLen)
            p->Song.PattPos = p->Song.PattLen - 1;
    }

    p->Song.Timer = 1;
}

static void FreeInstr(PLAYER *pl, uint16_t ins)
{
    uint8_t i;
    InstrTyp *p;

    p = pl->Instr[ins];
    if (p == NULL) return;

    for (i = 0; i < 32; ++i)
    {
        if (p->Samp[i].Pek) free(p->Samp[i].Pek);
        p->Samp[i].Pek = NULL;
    }

    free(pl->Instr[ins]);
    pl->Instr[ins] = NULL;
}

static void FreeMusic(PLAYER *p)
{
    uint16_t a;

    for (a = 1; a < (255 + 1); ++a)
        FreeInstr(p, a);

    for (a = 0; a < 256; ++a)
    {
        if (p->Patt[a]) free(p->Patt[a]);

        p->Patt[a]     = NULL;
        p->PattLens[a] = 64;
    }

    memset(&p->Song, 0, sizeof (p->Song));

    p->Song.Len     = 1;
    p->Song.Tempo   = 6;
    p->Song.Speed   = 125;
    p->Song.Timer   = 1;
    p->Song.AntChn  = MAX_VOICES;
    p->LinearFrqTab = 1;

    StopVoices(p);
    SetPos(p, 0, 0);
}

static void Delta2Samp(int8_t *p, uint32_t len, uint8_t typ)
{
    uint32_t i;

    int16_t *p16;
    int16_t news16;
    int16_t olds16L;
    int16_t olds16R;

    int8_t *p8;
    int8_t news8;
    int8_t olds8L;
    int8_t olds8R;

    if (typ & 16) len >>= 1; /* 16-bit */
    if (typ & 32) len >>= 1; /* stereo */

    if (typ & 32)
    {
        if (typ & 16)
        {
            p16     = (int16_t *)(p);
            olds16L = 0;
            olds16R = 0;

            for (i = 0; i < len; ++i)
            {
                news16  = p16[i] + olds16L;
                p16[i]  = news16;
                olds16L = news16;

                news16       = p16[len + i] + olds16R;
                p16[len + i] = news16;
                olds16R      = news16;
            }
        }
        else
        {
            p8     = (int8_t *)(p);
            olds8L = 0;
            olds8R = 0;

            for (i = 0; i < len; ++i)
            {
                news8  = p8[i] + olds8L;
                p8[i]  = news8;
                olds8L = news8;

                news8       = p8[len + i] + olds8R;
                p8[len + i] = news8;
                olds8R      = news8;
            }
        }
    }
    else
    {
        if (typ & 16)
        {
            p16     = (int16_t *)(p);
            olds16L = 0;

            for (i = 0; i < len; ++i)
            {
                news16  = p16[i] + olds16L;
                p16[i]  = news16;
                olds16L = news16;
            }
        }
        else
        {
            p8     = (int8_t *)(p);
            olds8L = 0;

            for (i = 0; i < len; ++i)
            {
                news8  = p8[i] + olds8L;
                p8[i]  = news8;
                olds8L = news8;
            }
        }
    }
}

static inline int8_t GetAdpcmSample(const int8_t *sampleDictionary, const uint8_t *sampleData, int32_t samplePosition, int8_t *lastDelta)
{
    uint8_t byte = sampleData[samplePosition / 2];
    byte = (samplePosition & 1) ? byte >> 4 : byte & 15;
    return *lastDelta += sampleDictionary[byte];
}

static void Adpcm2Samp(uint8_t * sample, uint32_t length)
{
    const int8_t *sampleDictionary;
    const uint8_t *sampleData;

    uint32_t samplePosition;
    int8_t lastDelta;

    uint8_t * sampleDataOut = (uint8_t *) malloc(length);
    if (!sampleDataOut)
        return;

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

    samplePosition = 0;
    lastDelta = 0;

    while (samplePosition < length)
    {
        sampleDataOut[samplePosition] = GetAdpcmSample(sampleDictionary, sampleData, samplePosition, &lastDelta);
        samplePosition++;
    }

    memcpy(sample, sampleDataOut, length);
}

static void FreeAllInstr(PLAYER *p)
{
    uint16_t i;
    for (i = 1; i < (255 + 1); ++i) FreeInstr(p, i);
}

static int8_t AllocateInstr(PLAYER *pl, uint16_t i)
{
    uint8_t j;

    InstrTyp *p;

    if (pl->Instr[i] == NULL)
    {
        p = (InstrTyp *)(calloc(1, sizeof (InstrTyp)));
        if (p == NULL) return (0);

        for (j = 0; j < 32; ++j)
        {
            p->Samp[j].Pan = 128;
            p->Samp[j].Vol = 64;
        }

        pl->Instr[i] = p;

        return (1);
    }

    return (0);
}

static int8_t LoadInstrHeader(PLAYER *p, MEM *f, uint16_t i)
{
    uint8_t j;
    uint16_t k;

    InstrHeaderTyp ih;
    size_t size;

    memset(&ih, 0, InstrHeaderSize);

    mread_swap(&ih.InstrSize, 4, 1, f, 0, 3);

    if ((ih.InstrSize <= 0) || (ih.InstrSize > InstrHeaderSize))
        ih.InstrSize = InstrHeaderSize;

    size = ih.InstrSize - 4;

    mread(ih.Name, min(size, 22), 1, f);
    if (size > 22)  mread(&ih.Typ, min(size - 22, 1), 1, f);
    if (size > 23)  mread_swap(&ih.AntSamp, min(size - 23, 2), 1, f, 0, 1);
    if (size > 25)  mread_swap(&ih.SampleSize, min(size - 25, 4), 1, f, 0, 3);
    if (size > 29)  mread(&ih.TA, min(size - 29, 96), 1, f);
    if (size > 125) mread_swap(&ih.EnvVP, min(size - 125, 96), 1, f, 0, 1);
    if (size > 221) mread(&ih.EnvVPAnt, min(size - 221, 14), 1, f);
    if (size > 235) mread_swap(&ih.FadeOut, min(size - 235, 2), 1, f, 0, 1);
    if (size > 237) mread(&ih.MIDIOn, min(size - 237, 2), 1, f);
    if (size > 239) mread_swap(&ih.MIDIProgram, min(size - 239, 4), 1, f, 0, 1);
    if (size > 243) mread(&ih.Mute, min(size - 243, 16), 1, f);

    if (meof(f) || (ih.AntSamp > 32)) return (0);

    if (ih.AntSamp > 0)
    {
        if (AllocateInstr(p, i) == 0) return (0);

        memcpy(p->Instr[i]->TA, ih.TA, ih.InstrSize);
        p->Instr[i]->AntSamp = ih.AntSamp;

        for (k = 0; k < ih.AntSamp; k++)
        {
            mread_swap(&ih.Samp[k].Len, 4, 3, f, 0, 3);
            mread(&ih.Samp[k].vol, 28, 1, f);
        }
        if (meof(f)) return (0);

        for (j = 0; j < ih.AntSamp; ++j)
            memcpy(&p->Instr[i]->Samp[j].Len, &ih.Samp[j].Len, 12 + 4 + 24);
    }

    return (1);
}

static int8_t LoadInstrSample(PLAYER *p, MEM *f, uint16_t i)
{
    uint16_t j;
    int32_t l;

    InstrTyp *Instr;
    SampleTyp *s;

    if (p->Instr[i] != NULL)
    {
        Instr = p->Instr[i];
        for (j = 1; j <= Instr->AntSamp; ++j)
        {
            int adpcm = 0;
            s = &Instr->Samp[j - 1];
            s->Pek = NULL;

            l = s->Len;
            if (s->skrap == 0xAD &&
                !(s->Typ & (16|32)))
                adpcm = ((l + 1) / 2) + 16;
            if (l > 0)
            {
                s->Pek = (int8_t *)(malloc(l));
                if (s->Pek == NULL)
                {
                    for (j = i; j <= p->Song.AntInstrs; ++j) FreeInstr(p, j);
                    return (0);
                }

                if (s->Typ & 16)
                    mread_swap(s->Pek, l, 1, f, 0, 1); /* byte swap 16 bit sample on big endian system, which by definition cannot be an adpcm sample */
                else
                    mread(s->Pek, adpcm ? adpcm : l, 1, f);
                if (!adpcm)
                    Delta2Samp(s->Pek, l, s->Typ);
                else
                    Adpcm2Samp((uint8_t *)s->Pek, l);
            }

            if (s->Pek == NULL)
            {
                s->Len  = 0;
                s->RepS = 0;
                s->RepL = 0;
            }
            else
            {
                if (s->RepS < 0)                  s->RepS = 0;
                if (s->RepL < 0)                  s->RepL = 0;
                if (s->RepS > s->Len)             s->RepS = s->Len;
                if ((s->RepS + s->RepL) > s->Len) s->RepL = s->Len - s->RepS;
            }

            if (s->RepL == 0) s->Typ &= 0xFC; /* non-FT2 fix: force loop off if looplen is 0 */
        }
    }

    return (1);
}

static void UnpackPatt(PLAYER *p, TonTyp *patdata, uint16_t length, uint16_t packlen, uint8_t *packdata)
{
    uint32_t patofs;

    uint16_t i;
    uint16_t packindex;

    uint8_t j;
    uint8_t packnote;

    packindex = 0;
    for (i = 0; i < length; ++i)
    {
        for (j = 0; j < p->Song.AntChn; ++j)
        {
            if (packindex >= packlen) return;

            patofs   = (i * MAX_VOICES) + j;
            packnote = packdata[packindex++];

            if (packnote & 0x80)
            {
                if (packnote & 0x01) patdata[patofs].Ton    = packdata[packindex++];
                if (packnote & 0x02) patdata[patofs].Instr  = packdata[packindex++];
                if (packnote & 0x04) patdata[patofs].Vol    = packdata[packindex++];
                if (packnote & 0x08) patdata[patofs].EffTyp = packdata[packindex++];
                if (packnote & 0x10) patdata[patofs].Eff    = packdata[packindex++];
            }
            else
            {
                patdata[patofs].Ton    = packnote;
                patdata[patofs].Instr  = packdata[packindex++];
                patdata[patofs].Vol    = packdata[packindex++];
                patdata[patofs].EffTyp = packdata[packindex++];
                patdata[patofs].Eff    = packdata[packindex++];
            }
        }
    }
}

static inline int8_t PatternEmpty(PLAYER *p, uint16_t nr)
{
    uint32_t patofs;
    uint16_t i;
    uint8_t j;

    if (p->Patt[nr] == NULL)
    {
        return (1);
    }
    else
    {
        for (i = 0; i < p->PattLens[nr]; ++i)
        {
            for (j = 0; j < p->Song.AntChn; ++j)
            {
                patofs = (i * MAX_VOICES) + j;

                if (p->Patt[nr][patofs].Ton)    return (0);
                if (p->Patt[nr][patofs].Instr)  return (0);
                if (p->Patt[nr][patofs].Vol)    return (0);
                if (p->Patt[nr][patofs].EffTyp) return (0);
                if (p->Patt[nr][patofs].Eff)    return (0);
            }
        }
    }

    return (1);
}

static int8_t LoadPatterns(PLAYER *p, MEM *f)
{
    uint8_t *patttmp;
    uint16_t i;
    uint8_t tmpLen;

    PatternHeaderTyp ph;

    for (i = 0; i < p->Song.AntPtn; ++i)
    {
        mread_swap(&ph.PatternHeaderSize, 4, 1, f, 0, 3);
        mread(&ph.Typ, 1, 1, f);

        ph.PattLen = 0;
        if (p->Song.Ver == 0x0102)
        {
            mread(&tmpLen, 1, 1, f);
            ph.PattLen = (uint16_t)(tmpLen) + 1; /* +1 in v1.02 */
        }
        else
        {
            mread_swap(&ph.PattLen, 2, 1, f, 0, 1);
        }

        mread_swap(&ph.DataLen, 2, 1, f, 0, 1);

        if (p->Song.Ver == 0x0102)
            mseek(f, ph.PatternHeaderSize - 8, SEEK_CUR);
        else
            mseek(f, ph.PatternHeaderSize - 9, SEEK_CUR);

        if (meof(f))
        {
            mclose(&f);
            return (0);
        }

        p->PattLens[i] = ph.PattLen;
        if (ph.DataLen > 0)
        {
            p->Patt[i] = (TonTyp *)(calloc(sizeof (TonTyp), ph.PattLen * MAX_VOICES));
            if (p->Patt[i] == NULL)
            {
                mclose(&f);
                return (0);
            }

            patttmp = (uint8_t *)(malloc(ph.DataLen));
            if (patttmp == NULL)
            {
                mclose(&f);
                return (0);
            }

            mread(patttmp, ph.DataLen, 1, f);
            UnpackPatt(p, p->Patt[i], ph.PattLen, ph.DataLen, patttmp);
            free(patttmp);
        }

        if (PatternEmpty(p, i))
        {
            if (p->Patt[i] != NULL)
            {
                free(p->Patt[i]);
                p->Patt[i] = NULL;
            }

            p->PattLens[i] = 64;
        }
    }

    return (1);
}

static void ft2play_FreeSong(PLAYER *p)
{
    p->Playing          = 0;

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

    FreeMusic(p);

    p->ModuleLoaded = 0;
}

int8_t ft2play_LoadModule(void *_p, const uint8_t *buffer, size_t size)
{
    uint16_t i;

    PLAYER *p = (PLAYER *)_p;

    MEM *f;
    SongHeaderTyp h;

    if (p->ModuleLoaded)
        ft2play_FreeSong(p);

    p->ModuleLoaded = 0;

    /* instr 0 is a placeholder for invalid instruments */
    AllocateInstr(p, 0);
    p->Instr[0]->Samp[0].Vol = 0;
    /* ------------------------------------------------ */

    FreeMusic(p);
    p->LinearFrqTab = 0;

    f = mopen(buffer, size);
    if (f == NULL) return (0);

    /* start loading */
    mread(&h.Sig, 58, 1, f);
    mread_swap(&h.Ver, 2, 1, f, 0, 1);
    mread_swap(&h.HeaderSize, 4, 1, f, 0, 3);
    mread_swap(&h.Len, 2, 8, f, 0, 1);
    mread(&h.SongTab, 256, 1, f);

    if ((memcmp(h.Sig, "Extended Module: ", 17) != 0) || (h.Ver < 0x0102) || (h.Ver > 0x104))
    {
        mclose(&f);
        return (0);
    }

    if ((h.AntChn < 1) || (h.AntChn > MAX_VOICES) || (h.AntPtn > 256))
    {
        mclose(&f);
        return (0);
    }

    mseek(f, 60 + h.HeaderSize, SEEK_SET);
    if (meof(f))
    {
        mclose(&f);
        return (0);
    }

    memcpy(p->Song.Name,     h.Name,      20);
    memcpy(p->Song.ProgName, h.ProggName, 20);

    p->Song.Len       = h.Len;
    p->Song.RepS      = h.RepS;
    p->Song.AntChn    = (uint8_t)(h.AntChn);
    p->Song.Speed     = h.DefSpeed ? h.DefSpeed : 125;
    p->Song.Tempo     = h.DefTempo ? h.DefTempo : 6;
    p->Song.InitSpeed = p->Song.Speed;
    p->Song.InitTempo = p->Song.Tempo;
    p->Song.AntInstrs = h.AntInstrs;
    p->Song.AntPtn    = h.AntPtn;
    p->Song.Ver       = h.Ver;
    p->LinearFrqTab   = h.Flags & 1;

    memcpy(p->Song.SongTab, h.SongTab, 256);

    if (p->Song.Ver < 0x0104)
    {
        for (i = 1; i <= h.AntInstrs; ++i)
        {
            if (!LoadInstrHeader(p, f, i))
            {
                FreeAllInstr(p);
                mclose(&f);
                return (0);
            }
        }

        if (!LoadPatterns(p, f))
        {
            FreeAllInstr(p);
            mclose(&f);
            return (0);
        }

        for (i = 1; i <= h.AntInstrs; ++i)
        {
            if (!LoadInstrSample(p, f, i))
            {
                FreeAllInstr(p);
                mclose(&f);
                return (0);
            }
        }
    }
    else
    {
        if (!LoadPatterns(p, f))
        {
            mclose(&f);
            return (0);
        }

        for (i = 1; i <= h.AntInstrs; ++i)
        {
            if (!LoadInstrHeader(p, f, i))
            {
                FreeInstr(p, (uint8_t)(i));
                mclose(&f);
                break;
            }

            if (LoadInstrSample(p, f, i) == 0)
            {
                mclose(&f);
                break;
            }
        }
    }

    mclose(&f);

    if (p->LinearFrqTab)
        p->Note2Period = p->linearPeriods;
    else
        p->Note2Period = p->amigaPeriods;

    if (p->Song.RepS > p->Song.Len) p->Song.RepS = 0;

    StopVoices(p);
    SetPos(p, 0, 0);

    p->ModuleLoaded = 1;

    return (1);
}

static void setSamplesPerFrame(PLAYER *p, uint32_t val)
{
    p->samplesPerFrame  = val;
    p->tickVolRampMul_f = 1.0f / val;
}

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

static inline void voiceSetSource(PLAYER *p, uint8_t i, const int8_t *sampleData,
    int32_t sampleLength,  int32_t sampleLoopLength,
    int32_t sampleLoopEnd, int8_t loopEnabled,
    int8_t sixteenbit, int8_t stereo)
{
    VOICE *v;
    v = &p->voice[i];

    if (sixteenbit)
    {
        sampleLength     >>= 1;
        sampleLoopEnd    >>= 1;
        sampleLoopLength >>= 1;
    }

    if (stereo)
    {
        sampleLength     >>= 1;
        sampleLoopEnd    >>= 1;
        sampleLoopLength >>= 1;
    }

    v->sampleData       = sampleData;
    v->sampleLength     = sampleLength;
    v->sampleLoopBegin  = sampleLoopEnd - sampleLoopLength;
    v->sampleLoopEnd    = sampleLoopEnd;
    v->sampleLoopLength = sampleLoopLength;
    v->loopBidi         = loopEnabled & 2;
    v->loopEnabled      = loopEnabled;
    v->sixteenBit       = sixteenbit;
    v->loopingForward   = 1;
    v->stereo           = stereo;
    v->interpolating    = 1;
}

static inline void voiceSetSamplePosition(PLAYER *p, uint8_t i, uint16_t value)
{
    VOICE *v;
    v = &p->voice[i];

    v->samplePosition = value;
    if (v->samplePosition >= v->sampleLength)
    {
        v->samplePosition = 0;
        v->sampleData     = NULL;
    }

    v->interpolating = 1;
}

static inline void voiceSetVolume(PLAYER *p, uint8_t i, float vol, uint8_t pan, uint8_t status)
{
    float deltaMul_f;
    VOICE *v;
    
    v = &p->voice[i];

#ifdef USE_VOL_RAMP
    if (!(status & IS_NyTon) && (p->rampStyle > 1))
    {
        v->targetVolL = vol * p->PanningTab[256 - pan];
        v->targetVolR = vol * p->PanningTab[pan];
        
        deltaMul_f = (status & IS_QuickVol) ? p->quickVolRampMul_f : p->tickVolRampMul_f;
        
        v->volDeltaL  = (v->targetVolL - v->volumeL) * deltaMul_f;
        v->volDeltaR  = (v->targetVolR - v->volumeR) * deltaMul_f;
    }
    else
    {
        v->targetVolL = v->volumeL = vol * p->PanningTab[256 - pan];
        v->targetVolR = v->volumeR = vol * p->PanningTab[pan];
        v->volDeltaL = 0.0f;
        v->volDeltaR = 0.0f;
    }
#else
    v->volumeL = vol * p->PanningTab[256 - pan];
    v->volumeR = vol * p->PanningTab[pan];
#endif
}

static inline void voiceSetSamplingFrequency(PLAYER *p, uint8_t i, uint32_t samplingFrequency)
{
    if (p->f_outputFreq > 0.0f)
        p->voice[i].incRate = (float)(samplingFrequency) / p->f_outputFreq;
}

static inline void mix8b(PLAYER *p, uint32_t ch, uint32_t samples)
{
    uint32_t j;

    const int8_t *sampleData;

    VOICE *v;

    float sample;
    float sampleL;
    float sampleR;

    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;

    int8_t loopEnabled;
    int8_t loopBidi;
    int8_t loopingForward;

    int32_t interpolating;

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

    void * resampler;

    v = &p->voice[ch];

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = v->sampleLoopBegin;
    loopEnabled      = v->loopEnabled;
    loopBidi         = v->loopBidi;
    loopingForward   = v->loopingForward;
    interpolating    = v->interpolating;

    sampleData = v->sampleData;

    resampler = p->resampler[ch];

    resampler_set_rate(resampler, v->incRate);

    for (j = 0; (j < samples) && (v->sampleData != NULL); ++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 (loopingForward)
                ++samplePosition;
            else
                --samplePosition;

            if (loopEnabled)
            {
                if (loopBidi)
                {
                    if (loopingForward)
                    {
                        if (samplePosition == sampleLoopEnd)
                        {
                            samplePosition = sampleLoopEnd - 1;
                            loopingForward = 0;
                        }
                    }
                    else
                    {
                        if (samplePosition < sampleLoopBegin)
                        {
                            samplePosition = sampleLoopBegin;
                            loopingForward = 1;
                        }
                    }
                }
                else
                {
                    if (samplePosition == sampleLoopEnd)
                        samplePosition = sampleLoopBegin;
                }
            }
            else if ((samplePosition < 0) || (samplePosition >= sampleLength))
            {
                interpolating = -resampler_get_padding_size();
                break;
            }
        }

        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->loopingForward = loopingForward;
        v->interpolating  = (int8_t)interpolating;

        if ( !resampler_get_sample_count(resampler) )
        {
            resampler_clear(resampler);
            v->sampleData = NULL;
            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->sampleData = NULL;
            }

            sample *= v->fader;
        }
#endif

        sampleL = sample * v->volumeL;
        sampleR = sample * v->volumeR;

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            v->volumeL += v->volDeltaL;
            v->volumeR += v->volDeltaR;

            if ((v->volDeltaL > 0.0f) && (v->volumeL > v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }
            else if ((v->volDeltaL < 0.0f) && (v->volumeL < v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }

            if ((v->volDeltaR > 0.0f) && (v->volumeR > v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
            else if ((v->volDeltaR < 0.0f) && (v->volumeR < v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
        }
#endif

        p->masterBufferL[j] += sampleL;
        p->masterBufferR[j] += sampleR;
    }
}

static inline void mix8bstereo(PLAYER *p, uint32_t ch, uint32_t samples)
{
    uint32_t j;

    const int8_t *sampleData;

    VOICE *v;

    float sampleL;
    float sampleR;

    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;

    int8_t loopEnabled;
    int8_t loopBidi;
    int8_t loopingForward;

    int32_t interpolating;

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

    void * resampler[2];

    v = &p->voice[ch];

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = v->sampleLoopBegin;
    loopEnabled      = v->loopEnabled;
    loopBidi         = v->loopBidi;
    loopingForward   = v->loopingForward;
    interpolating    = v->interpolating;

    sampleData = v->sampleData;

    resampler[0] = p->resampler[ch];
    resampler[1] = p->resampler[ch+TOTAL_VOICES];

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

    for (j = 0; (j < samples) && (v->sampleData != NULL); ++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 (loopingForward)
                ++samplePosition;
            else
                --samplePosition;

            if (loopEnabled)
            {
                if (loopBidi)
                {
                    if (loopingForward)
                    {
                        if (samplePosition == sampleLoopEnd)
                        {
                            samplePosition = sampleLoopEnd - 1;
                            loopingForward = 0;
                        }
                    }
                    else
                    {
                        if (samplePosition < sampleLoopBegin)
                        {
                            samplePosition = sampleLoopBegin;
                            loopingForward = 1;
                        }
                    }
                }
                else
                {
                    if (samplePosition == sampleLoopEnd)
                        samplePosition = sampleLoopBegin;
                }
            }
            else if ((samplePosition < 0) || (samplePosition >= sampleLength))
            {
                interpolating = -resampler_get_padding_size();
                break;
            }
        }

        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->loopingForward = loopingForward;
        v->interpolating  = (int8_t)interpolating;

        if ( !resampler_get_sample_count(resampler[0]) )
        {
            resampler_clear(resampler[0]);
            resampler_clear(resampler[1]);
            v->sampleData = NULL;
            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[0]);
                resampler_clear(resampler[1]);
                v->sampleData = NULL;
            }

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

        sampleL *= v->volumeL;
        sampleR *= v->volumeR;

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            v->volumeL += v->volDeltaL;
            v->volumeR += v->volDeltaR;

            if ((v->volDeltaL > 0.0f) && (v->volumeL > v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }
            else if ((v->volDeltaL < 0.0f) && (v->volumeL < v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }

            if ((v->volDeltaR > 0.0f) && (v->volumeR > v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
            else if ((v->volDeltaR < 0.0f) && (v->volumeR < v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
        }
#endif

        p->masterBufferL[j] += sampleL;
        p->masterBufferR[j] += sampleR;
    }
}

static inline void mix16b(PLAYER *p, uint32_t ch, uint32_t samples)
{
    uint32_t j;

    const int16_t *sampleData;

    VOICE *v;

    float sample;
    float sampleL;
    float sampleR;

    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;

    int8_t loopEnabled;
    int8_t loopBidi;
    int8_t loopingForward;

    int32_t interpolating;

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

    void * resampler;

    v = &p->voice[ch];

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = v->sampleLoopBegin;
    loopEnabled      = v->loopEnabled;
    loopBidi         = v->loopBidi;
    loopingForward   = v->loopingForward;
    interpolating    = v->interpolating;

    sampleData = (const int16_t *) v->sampleData;

    resampler = p->resampler[ch];

    resampler_set_rate(resampler, v->incRate);

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

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

            if (loopingForward)
                ++samplePosition;
            else
                --samplePosition;

            if (loopEnabled)
            {
                if (loopBidi)
                {
                    if (loopingForward)
                    {
                        if (samplePosition == sampleLoopEnd)
                        {
                            samplePosition = sampleLoopEnd - 1;
                            loopingForward = 0;
                        }
                    }
                    else
                    {
                        if (samplePosition < sampleLoopBegin)
                        {
                            samplePosition = sampleLoopBegin;
                            loopingForward = 1;
                        }
                    }
                }
                else
                {
                    if (samplePosition == sampleLoopEnd)
                        samplePosition = sampleLoopBegin;
                }
            }
            else if ((samplePosition < 0) || (samplePosition >= sampleLength))
            {
                interpolating = -resampler_get_padding_size();
                break;
            }
        }

        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->loopingForward = loopingForward;
        v->interpolating  = (int8_t)interpolating;

        if ( !resampler_get_sample_count(resampler) )
        {
            resampler_clear(resampler);
            v->sampleData = NULL;
            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->sampleData = NULL;
            }

            sample *= v->fader;
        }
#endif

        sampleL = sample * v->volumeL;
        sampleR = sample * v->volumeR;

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            v->volumeL += v->volDeltaL;
            v->volumeR += v->volDeltaR;

            if ((v->volDeltaL > 0.0f) && (v->volumeL > v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }
            else if ((v->volDeltaL < 0.0f) && (v->volumeL < v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }

            if ((v->volDeltaR > 0.0f) && (v->volumeR > v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
            else if ((v->volDeltaR < 0.0f) && (v->volumeR < v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
        }
#endif

        p->masterBufferL[j] += sampleL;
        p->masterBufferR[j] += sampleR;
    }
}

static inline void mix16bstereo(PLAYER *p, uint32_t ch, uint32_t samples)
{
    uint32_t j;

    const int16_t *sampleData;

    VOICE *v;

    float sampleL;
    float sampleR;

    int32_t sampleLength;
    int32_t sampleLoopEnd;
    int32_t sampleLoopLength;
    int32_t sampleLoopBegin;
    int32_t samplePosition;

    int8_t loopEnabled;
    int8_t loopBidi;
    int8_t loopingForward;

    int32_t interpolating;

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

    void * resampler[2];

    v = &p->voice[ch];

    sampleLength     = v->sampleLength;
    sampleLoopLength = v->sampleLoopLength;
    sampleLoopEnd    = v->sampleLoopEnd;
    sampleLoopBegin  = v->sampleLoopBegin;
    loopEnabled      = v->loopEnabled;
    loopBidi         = v->loopBidi;
    loopingForward   = v->loopingForward;
    interpolating    = v->interpolating;

    sampleData = (const int16_t *) v->sampleData;

    resampler[0] = p->resampler[ch];
    resampler[1] = p->resampler[ch+TOTAL_VOICES];

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

    for (j = 0; (j < samples) && (v->sampleData != NULL); ++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], 16);
            resampler_write_sample_fixed(resampler[1], sampleData[sampleLength + samplePosition], 16);

            if (loopingForward)
                ++samplePosition;
            else
                --samplePosition;

            if (loopEnabled)
            {
                if (loopBidi)
                {
                    if (loopingForward)
                    {
                        if (samplePosition == sampleLoopEnd)
                        {
                            samplePosition = sampleLoopEnd - 1;
                            loopingForward = 0;
                        }
                    }
                    else
                    {
                        if (samplePosition < sampleLoopBegin)
                        {
                            samplePosition = sampleLoopBegin;
                            loopingForward = 1;
                        }
                    }
                }
                else
                {
                    if (samplePosition == sampleLoopEnd)
                        samplePosition = sampleLoopBegin;
                }
            }
            else if ((samplePosition < 0) || (samplePosition >= sampleLength))
            {
                interpolating = -resampler_get_padding_size();
                break;
            }
        }

        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->loopingForward = loopingForward;
        v->interpolating  = (int8_t)interpolating;

        if ( !resampler_get_sample_count(resampler[0]) )
        {
            resampler_clear(resampler[0]);
            resampler_clear(resampler[1]);
            v->sampleData = NULL;
            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[0]);
                resampler_clear(resampler[1]);
                v->sampleData = NULL;
            }

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

        sampleL *= v->volumeL;
        sampleR *= v->volumeR;

#ifdef USE_VOL_RAMP
        if (rampStyle > 1)
        {
            v->volumeL += v->volDeltaL;
            v->volumeR += v->volDeltaR;

            if ((v->volDeltaL > 0.0f) && (v->volumeL > v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }
            else if ((v->volDeltaL < 0.0f) && (v->volumeL < v->targetVolL))
            {
                v->volumeL = v->targetVolL;
            }

            if ((v->volDeltaR > 0.0f) && (v->volumeR > v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
            else if ((v->volDeltaR < 0.0f) && (v->volumeR < v->targetVolR))
            {
                v->volumeR = v->targetVolR;
            }
        }
#endif

        p->masterBufferL[j] += sampleL;
        p->masterBufferR[j] += sampleR;
    }
}

static inline void mixChannel(PLAYER *p, uint32_t i, uint32_t sampleBlockLength)
{
    if (!p->voice[i].incRate)
        return;
    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);
    }
}

static void mixSampleBlock(PLAYER *p, float *outputStream, uint32_t sampleBlockLength)
{
    float *streamPointer;
    uint32_t i;
#ifdef USE_VOL_RAMP
    int32_t rampStyle = p->rampStyle;
#endif

    float outL;
    float outR;

    streamPointer = outputStream;

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

    for (i = 0; i < p->numChannels; ++i)
    {
        if (p->muted[i / 8] & (1 << (i % 8)))
            continue;
        mixChannel(p, i, sampleBlockLength);
#ifdef USE_VOL_RAMP
        if (rampStyle > 0)
            mixChannel(p, i + SPARE_OFFSET, sampleBlockLength);
#endif
    }

    for (i = 0; i < sampleBlockLength; ++i)
    {
        outL = p->masterBufferL[i] * (1.0f / 3.0f);
        outR = p->masterBufferR[i] * (1.0f / 3.0f);

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

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

    if (p->Playing)
    {
        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);

                    outputStream  += (samplesTodo << 1);
                }

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

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

void ft2play_RenderFixed32(void *_p, int32_t *buffer, int32_t count, int8_t depth)
{
    int32_t i;
    float * fbuffer = (float *)buffer;
    float scale = (float)(1 << (depth - 1));
    float sample;
    assert(sizeof(int32_t) == sizeof(float));
    ft2play_RenderFloat(_p, fbuffer, count);
    if (buffer)
    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 ft2play_RenderFixed16(void *_p, int16_t *buffer, int32_t count, int8_t depth)
{
    int32_t i, SamplesTodo;
    float scale = (float)(1 << (depth - 1));
    float sample;
    float fbuffer[1024];
    if (!buffer)
        ft2play_RenderFloat(_p, 0, count);
    else
    while (count)
    {
        SamplesTodo = (count < 512) ? count : 512;
        ft2play_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 * ft2play_Alloc(uint32_t _samplingFrequency, int8_t interpolation, int8_t ramp_style)
{
    uint8_t j;
    uint16_t i;
    int16_t noteVal;
    uint16_t noteIndex;

    PLAYER * p = (PLAYER *) calloc(1, sizeof(PLAYER));

    if ( !p )
        return NULL;
   
    p->numChannels         = MAX_VOICES;
    p->outputFreq          = _samplingFrequency;
    p->f_outputFreq        = (float)(p->outputFreq);
    p->samplesPerFrame     = ((p->outputFreq * 5) / 2) / 125;
#ifdef USE_VOL_RAMP
    p->tickVolRampMul_f    = 1.0f / p->samplesPerFrame;
    p->quickVolRampMul_f   = 1.0f / (p->outputFreq / 200); // yes, truncate here
#endif
    p->soundBufferSize     = _soundBufferSize;

    p->masterBufferL       = (float *)(malloc(p->soundBufferSize * sizeof (float)));
    p->masterBufferR       = (float *)(malloc(p->soundBufferSize * sizeof (float)));
    if ( !p->masterBufferL || !p->masterBufferR )
        goto error;

    setSamplingInterpolation(p, interpolation);

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

    resampler_init();
    for ( i = 0; i < TOTAL_VOICES * 2; ++i )
    {
        p->resampler[i] = resampler_create();
        if ( !p->resampler[i] )
            goto error;
        resampler_set_quality(p->resampler[i], interpolation);
    }

    /* allocate memory for pointers */

    p->NilPatternLine = (TonTyp *)(calloc(sizeof (TonTyp), 256 * MAX_VOICES));
    if (p->NilPatternLine == NULL)
        goto error;

    p->linearPeriods = (int16_t *)(malloc(sizeof (int16_t) * ((12 * 10 * 16) + 16)));
    if (p->linearPeriods == NULL)
        goto error;

    p->amigaPeriods = (int16_t *)(malloc(sizeof (int16_t) * ((12 * 10 * 16) + 16)));
    if (p->amigaPeriods == NULL)
        goto error;

    p->VibSineTab = (int8_t *)(malloc(256));
    if (p->VibSineTab == NULL)
        goto error;

    p->PanningTab = (float *)(malloc(sizeof (float) * 257));
    if (p->PanningTab == NULL)
        goto error;

    p->LogTab = (uint32_t *)(malloc(sizeof (uint32_t) * 768));
    if (p->LogTab == NULL)
        goto error;

    /* generate tables */

    /* generate log table (value-exact to its original table) */
    for (i = 0; i < 768; ++i)
        p->LogTab[i] = (uint32_t)(floor(((256.0f * 8363.0f) * exp((float)(i) / 768.0f * logf(2.0f))) + 0.5f));

    /* generate linear table (value-exact to its original table) */
    for (i = 0; i < ((12 * 10 * 16) + 16); ++i)
        p->linearPeriods[i] = (((12 * 10 * 16) + 16) * 4) - (i << 2);

    /* generate amiga period table (value-exact to its original table, except for last 17 entries) */
    noteIndex = 0;
    for (i = 0; i < 10; ++i)
    {
        for (j = 0; j < ((i == 9) ? (96 + 8) : 96); ++j)
        {
            noteVal = ((AmigaFinePeriod[j % 96] << 6) + (-1 + (1 << i))) >> (i + 1);
            /* NON-FT2: j % 96. Added for safety. We're patching the values later anyways. */

            p->amigaPeriods[noteIndex++] = noteVal;
            p->amigaPeriods[noteIndex++] = noteVal;
        }
    }

    /* interpolate between points (end-result is exact to FT2's end-result, except for last 17 entries) */
    for (i = 0; i < (12 * 10 * 8) + 7; ++i)
        p->amigaPeriods[(i << 1) + 1] = (p->amigaPeriods[i << 1] + p->amigaPeriods[(i << 1) + 2]) >> 1;
    /*
    ** The amiga linear period table has its 17 last entries generated wrongly.
    ** The content seem to be garbage because of an "out of boundaries" read from AmigaFinePeriods.
    ** These 17 values were taken from a memdump of FT2 in DOSBox.
    ** They might change depending on what you ran before FT2, but let's not make it too complicated.
    */
    p->amigaPeriods[1919] = 22; p->amigaPeriods[1920] = 16; p->amigaPeriods[1921] =  8;
    p->amigaPeriods[1922] =  0; p->amigaPeriods[1923] = 16; p->amigaPeriods[1924] = 32;
    p->amigaPeriods[1925] = 24; p->amigaPeriods[1926] = 16; p->amigaPeriods[1927] =  8;
    p->amigaPeriods[1928] =  0; p->amigaPeriods[1929] = 16; p->amigaPeriods[1930] = 32;
    p->amigaPeriods[1931] = 24; p->amigaPeriods[1932] = 16; p->amigaPeriods[1933] =  8;
    p->amigaPeriods[1934] =  0; p->amigaPeriods[1935] = 0;

    /* generate auto-vibrato table (value-exact to its original table) */
    for (i = 0; i < 256; ++i)
        p->VibSineTab[i] = (int8_t)floorf((64.0f * sinf(((float)(-i) * (2.0f * 3.1415927f)) / 256.0f)) + 0.5f);

    /* generate FT2's pan table [round(65536*sqrt(n/256)) for n = 0...256] */
    for (i = 0; i < 257; ++i)
        p->PanningTab[i] = sqrtf((float)(i) / 256.0f);

    p->playedRows = NULL;

    return p;

error:
    ft2play_Free( p );
    return NULL;
}

void ft2play_Free(void *_p)
{
    uint32_t i;

    PLAYER * p = (PLAYER *)_p;

    if (p->Playing)
    {
        if (p->playedRows) bit_array_destroy(p->playedRows); p->playedRows = NULL;

        if (p->masterBufferL) free(p->masterBufferL); p->masterBufferL = NULL;
        if (p->masterBufferR) free(p->masterBufferR); p->masterBufferR = NULL;
    }

    p->Playing          = 0;

    ft2play_FreeSong(p);

    if (p->LogTab)         free(p->LogTab);         p->LogTab         = NULL;
    if (p->PanningTab)     free(p->PanningTab);     p->PanningTab     = NULL;
    if (p->VibSineTab)     free(p->VibSineTab);     p->VibSineTab     = NULL;
    if (p->amigaPeriods)   free(p->amigaPeriods);   p->amigaPeriods   = NULL;
    if (p->linearPeriods)  free(p->linearPeriods);  p->linearPeriods  = NULL;
    if (p->NilPatternLine) free(p->NilPatternLine); p->NilPatternLine = NULL;

    for ( i = 0; i < TOTAL_VOICES * 2; ++i )
    {
        if ( p->resampler[i] )
            resampler_delete( p->resampler[i] );
        p->resampler[i] = NULL;
    }

    free (p);
}

void ft2play_PlaySong(void *_p, int32_t startOrder)
{
    PLAYER * p = (PLAYER *)_p;

    if (!p->ModuleLoaded) return;

    StopVoices(p);

    p->Song.GlobVol = 64;
    p->numChannels  = p->Song.AntChn;
    p->Playing      = 1;

    setSamplesPerFrame(p, (((p->outputFreq * 5) / 2) / p->Song.Speed));

    SetPos(p, (int16_t)startOrder, 0);

    p->Song.startOrder = (int16_t)startOrder;

    p->loopCount = 0;

    if (p->playedRows) bit_array_destroy(p->playedRows);
    p->playedRows = bit_array_create(1024 * (p->Song.Len ? p->Song.Len : 1));
    bit_array_set(p->playedRows, startOrder * 1024);

    p->playedRowsPatLoop[0] = 0;
    memset(p->playedRowsPatLoop + 1, 0xFF, 1023 * 2);
}

static int mopen_is_big_endian;

static MEM *mopen(const uint8_t *src, size_t length)
{
    MEM *b;

    union
    {
        uint32_t a;
        uint8_t b[4];
    } endian_test;

    if ((src == NULL) || (length <= 0)) return (NULL);

    b = (MEM *)(malloc(sizeof (MEM)));
    if (b == NULL) return (NULL);

    endian_test.a = 1;
    mopen_is_big_endian = endian_test.b[3];

    b->_base   = (uint8_t *)(src);
    b->_ptr    = (uint8_t *)(src);
    b->_cnt    = length;
    b->_bufsiz = length;
    b->_eof    = 0;

    return (b);
}

static void mclose(MEM **buf)
{
    if (*buf != NULL)
        free(*buf);
    *buf = NULL;
}

static size_t mread(void *buffer, size_t size, size_t count, MEM *buf)
{
    size_t wrcnt;
    ssize_t pcnt;

    if (buf       == NULL) return (0);
    if (buf->_ptr == NULL) return (0);

    wrcnt = size * count;
    if ((size == 0) || buf->_eof) return (0);

    pcnt = (buf->_cnt > wrcnt) ? wrcnt : buf->_cnt;
    memcpy(buffer, buf->_ptr, pcnt);

    buf->_cnt -= pcnt;
    buf->_ptr += pcnt;

    if (buf->_cnt <= 0)
    {
        buf->_ptr = buf->_base + buf->_bufsiz;
        buf->_cnt = 0;
        buf->_eof = 1;
    }

    return (pcnt / size);
}

static size_t mread_swap(void *buffer, size_t size, size_t count, MEM *buf, uint8_t le_xor, uint8_t be_xor)
{
    size_t wrcnt;
    ssize_t pcnt;
    uint8_t xor;

    if (buf       == NULL) return (0);
    if (buf->_ptr == NULL) return (0);

    wrcnt = size * count;
    if ((size == 0) || buf->_eof) return (0);

    xor = mopen_is_big_endian ? be_xor : le_xor;

    pcnt = (buf->_cnt > wrcnt) ? wrcnt : buf->_cnt;

    if ( !xor )
        memcpy(buffer, buf->_ptr, pcnt);
    else
    {
        size_t i;
        uint8_t * bbuffer = (uint8_t *) buffer;
        for (i = 0; i < pcnt; i++)
            bbuffer[i ^ xor] = buf->_ptr[i];
    }

    buf->_cnt -= pcnt;
    buf->_ptr += pcnt;

    if (buf->_cnt <= 0)
    {
        buf->_ptr = buf->_base + buf->_bufsiz;
        buf->_cnt = 0;
        buf->_eof = 1;
    }

    return (pcnt / size);
}

static int32_t meof(MEM *buf)
{
    if (buf == NULL) return (1); /* XXX: Should return a different value? */

    return (buf->_eof);
}

static void mseek(MEM *buf, ssize_t offset, int32_t whence)
{
    if (buf == NULL) return;

    if (buf->_base)
    {
        switch (whence)
        {
            case SEEK_SET: buf->_ptr  = buf->_base + offset;                break;
            case SEEK_CUR: buf->_ptr += offset;                             break;
            case SEEK_END: buf->_ptr  = buf->_base + buf->_bufsiz + offset; break;
            default: break;
        }

        buf->_eof = 0;
        if (buf->_ptr >= (buf->_base + buf->_bufsiz))
        {
            buf->_ptr = buf->_base + buf->_bufsiz;
            buf->_eof = 1;
        }

        buf->_cnt = (buf->_base + buf->_bufsiz) - buf->_ptr;
    }
}

void ft2play_Mute(void *_p, int8_t channel, int8_t mute)
{
    PLAYER * p = (PLAYER *)_p;
    int8_t mask = 1 << (channel % 8);
    if (channel > MAX_VOICES)
        return;
    if (mute)
        p->muted[channel / 8] |= mask;
    else
        p->muted[channel / 8] &= ~mask;
}

uint32_t ft2play_GetLoopCount(void *_p)
{
    PLAYER * p = (PLAYER *)_p;
    return p->loopCount;
}

void ft2play_GetInfo(void *_p, ft2_info *info)
{
    int32_t i, channels_playing;
    PLAYER * p = (PLAYER *)_p;
    info->order = p->Song.SongPos;
    info->pattern = p->Song.PattNr;
    info->row = p->Song.PattPos;
    info->speed = p->Song.Tempo; /* Hurr */
    info->tempo = p->Song.Speed;
    channels_playing = 0;
    if (p->Playing)
    {
        for (i = 0; i < p->Song.AntChn; ++i)
        {
            if (p->voice[i].sampleData)
                ++channels_playing;
        }
    }
    info->channels_playing = (uint8_t)channels_playing;
}

/* EOF */
