ref: 1933d1c0d5a61983615cb3a1ddcafa50874a2989
dir: /pmplay.c/
/*
** C-port of FT2.09's XM replayer, by 8bitbubsy nov. 2020
**
** Note: This is not the exact same code used in the FT2 clone!
** This is a direct port meant to give bit-accurate results to
** FT2.08/FT2.09 (non-GUS mode). It's very handy to use as a
** reference if you are trying to make your own, accurate XM
** player.
*/
#define INSTR_HEADER_SIZE 263
#define DEFAULT_AMP 4
#define DEFAULT_MASTER_VOL 256
#include "common.h"
#include "pmplay.h"
#include "pmp_mix.h"
#include "snd_masm.h"
#include "tables.h"
#define SWAP16(value) \
( \
(((uint16_t)((value) & 0x00FF)) << 8) | \
(((uint16_t)((value) & 0xFF00)) >> 8) \
)
#ifdef __plan9__
#pragma pack on
#endif
#ifdef _MSC_VER
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct songHeaderTyp_t
{
char sig[17], name[21], progName[20];
uint16_t ver;
int32_t headerSize;
uint16_t len, repS, antChn, antPtn, antInstrs, flags, defTempo, defSpeed;
uint8_t songTab[256];
}
#ifdef __GNUC__
__attribute__ ((packed))
#elif defined(__plan9__)
#pragma pack on
#endif
songHeaderTyp;
typedef struct modSampleTyp
{
char name[22];
uint16_t len;
uint8_t fine, vol;
uint16_t repS, repL;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
modSampleTyp;
typedef struct songMOD31HeaderTyp
{
char name[20];
modSampleTyp sample[31];
uint8_t len, repS, songTab[128];
char Sig[4];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songMOD31HeaderTyp;
typedef struct songMOD15HeaderTyp
{
char name[20];
modSampleTyp sample[15];
uint8_t len, repS, songTab[128];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songMOD15HeaderTyp;
typedef struct sampleHeaderTyp_t
{
int32_t len, repS, repL;
uint8_t vol;
int8_t fine;
uint8_t typ, 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], envPP[12][2];
uint8_t envVPAnt, envPPAnt, envVSust, envVRepS, envVRepE, envPSust, envPRepS;
uint8_t envPRepE, envVTyp, envPTyp, vibTyp, vibSweep, vibDepth, vibRate;
uint16_t fadeOut;
uint8_t midiOn, midiChannel;
int16_t midiProgram, 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, dataLen;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
patternHeaderTyp;
#ifdef _MSC_VER
#pragma pack(pop)
#endif
#ifdef __plan9__
#pragma pack off
#endif
static int32_t soundBufferSize;
// globalized
volatile bool interpolationFlag, volumeRampingFlag, moduleLoaded, musicPaused, WAVDump_Flag;
bool linearFrqTab;
volatile const uint16_t *note2Period;
uint16_t pattLens[256];
int16_t PMPTmpActiveChannel, boostLevel = DEFAULT_AMP;
int32_t masterVol = DEFAULT_MASTER_VOL, PMPLeft = 0;
int32_t realReplayRate, quickVolSizeVal, speedVal;
int32_t frequenceDivFactor, frequenceMulFactor;
uint32_t CDA_Amp = 8*DEFAULT_AMP;
tonTyp *patt[256];
instrTyp *instr[1+128];
songTyp song;
stmTyp stm[32];
// ------------------
// 8bb: added these for loader
typedef struct
{
uint8_t *_ptr, *_base;
bool _eof;
size_t _cnt, _bufsiz;
} MEMFILE;
static MEMFILE *mopen(const uint8_t *src, uint32_t length);
static void mclose(MEMFILE **buf);
static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf);
static bool meof(MEMFILE *buf);
static void mseek(MEMFILE *buf, int32_t offset, int32_t whence);
static void mrewind(MEMFILE *buf);
// --------------------------
static void resetMusic(void);
static void freeAllPatterns(void);
static void setFrqTab(bool linear);
static CIType *getVoice(int32_t ch) // 8bb: added this
{
if (ch < 0 || ch > 31)
return NULL;
return &CI[chnReloc[ch]];
}
/***************************************************************************
* ROUTINES FOR SAMPLE HANDLING ETC. *
***************************************************************************/
// 8bb: modifies wrapped sample after loop/end (for branchless mixer interpolation)
static void fixSample(sampleTyp *s)
{
if (s->pek == NULL)
return; // empty sample
const bool sample16Bit = (s->typ >> 4) & 1;
uint8_t loopType = s->typ & 3;
int16_t *ptr16 = (int16_t *)s->pek;
int32_t len = s->len;
int32_t loopStart = s->repS;
int32_t loopEnd = s->repS + s->repL;
if (sample16Bit)
{
len >>= 1;
loopStart >>= 1;
loopEnd >>= 1;
}
if (len < 1)
return;
/* 8bb:
** This is the exact bit test order of which FT2 handles
** the sample fix in SND_MASM.ASM.
**
** This order is important for rare cases where both the
** "forward" and "pingpong" loop bits are set at once.
**
** This means that if both flags are set, the mixer will
** play the sample with pingpong looping, but the sample fix
** is handled as if it was a forward loop. This results in
** the wrong interpolation tap sample being written after the
** loop end point.
*/
if (loopType & 1)
{
// forward loop
if (sample16Bit)
ptr16[loopEnd] = ptr16[loopStart];
else
s->pek[loopEnd] = s->pek[loopStart];
return;
}
else if (loopType & 2)
{
// pingpong loop
if (sample16Bit)
ptr16[loopEnd] = ptr16[loopEnd-1];
else
s->pek[loopEnd] = s->pek[loopEnd-1];
}
else
{
// no loop
if (sample16Bit)
ptr16[len] = 0;
else
s->pek[len] = 0;
}
}
static void checkSampleRepeat(int32_t nr, int32_t nr2)
{
instrTyp *i = instr[nr];
if (i == NULL) return;
sampleTyp *s = &i->samp[nr2];
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;
}
static void upDateInstrs(void)
{
for (int32_t i = 0; i <= 128; i++)
{
instrTyp *ins = instr[i];
if (ins == NULL)
continue;
sampleTyp *s = ins->samp;
for (int32_t j = 0; j < 16; j++, s++)
{
checkSampleRepeat(i, j);
fixSample(s);
if (s->pek == NULL)
{
s->len = 0;
s->repS = 0;
s->repL = 0;
}
}
}
}
static bool patternEmpty(uint16_t nr)
{
if (patt[nr] == NULL)
return true;
const uint8_t *scanPtr = (const uint8_t *)patt[nr];
const int32_t scanLen = pattLens[nr] * song.antChn * sizeof (tonTyp);
for (int32_t i = 0; i < scanLen; i++)
{
if (scanPtr[i] != 0)
return false;
}
return true;
}
static bool allocateInstr(uint16_t i)
{
if (instr[i] != NULL)
return true;
instrTyp *p = (instrTyp *)calloc(1, sizeof (instrTyp));
if (p == NULL)
return false;
sampleTyp *s = p->samp;
for (int32_t j = 0; j < 16; j++, s++)
{
s->pan = 128;
s->vol = 64;
}
instr[i] = p;
return true;
}
static void freeInstr(uint16_t nr)
{
if (nr > 128)
return;
instrTyp *ins = instr[nr];
if (ins == NULL)
return;
sampleTyp *s = ins->samp;
for (uint8_t i = 0; i < 16; i++, s++)
{
if (s->pek != NULL)
free(s->pek);
}
free(ins);
instr[nr] = NULL;
}
static void freeAllInstr(void)
{
for (uint16_t i = 0; i <= 128; i++)
freeInstr(i);
}
static void freeAllPatterns(void) // 8bb: added this one, since it's handy
{
for (int32_t i = 0; i < 256; i++)
{
if (patt[i] != NULL)
{
free(patt[i]);
patt[i] = NULL;
}
pattLens[i] = 64;
}
}
static void delta2Samp(int8_t *p, uint32_t len, bool sample16Bit)
{
if (sample16Bit)
{
len >>= 1;
int16_t *p16 = (int16_t *)p;
int16_t olds16 = 0;
for (uint32_t i = 0; i < len; i++)
{
const int16_t news16 = p16[i] + olds16;
p16[i] = news16;
olds16 = news16;
}
}
else
{
int8_t *p8 = (int8_t *)p;
int8_t olds8 = 0;
for (uint32_t i = 0; i < len; i++)
{
const int8_t news8 = p8[i] + olds8;
p8[i] = news8;
olds8 = news8;
}
}
}
static void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn)
{
if (dst == NULL)
return;
const uint8_t *src = dst + inn;
const int32_t srcEnd = len * (sizeof (tonTyp) * antChn);
int32_t srcIdx = 0;
for (int32_t i = 0; i < len; i++)
{
for (int32_t j = 0; j < antChn; j++)
{
if (srcIdx >= srcEnd)
return; // error!
const uint8_t note = *src++;
if (note & 0x80)
{
*dst++ = (note & 0x01) ? *src++ : 0;
*dst++ = (note & 0x02) ? *src++ : 0;
*dst++ = (note & 0x04) ? *src++ : 0;
*dst++ = (note & 0x08) ? *src++ : 0;
*dst++ = (note & 0x10) ? *src++ : 0;
}
else
{
*dst++ = note;
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src++;
}
// 8bb: added this. If note is overflowing (>97), remove it (prevent LUT buffer overrun)
if (*(dst-5) > 97)
*(dst-5) = 0;
srcIdx += sizeof (tonTyp);
}
}
}
void freeMusic(void)
{
stopMusic();
freeAllInstr();
freeAllPatterns();
song.tempo = 6;
song.speed = 125;
song.timer = 1;
setFrqTab(true);
resetMusic();
}
void stopVoices(void)
{
lockMixer();
stmTyp *ch = stm;
for (uint8_t i = 0; i < 32; i++, ch++)
{
ch->tonTyp = 0;
ch->relTonNr = 0;
ch->instrNr = 0;
ch->instrSeg = instr[0]; // 8bb: placeholder instrument
ch->status = IS_Vol;
ch->realVol = 0;
ch->outVol = 0;
ch->oldVol = 0;
ch->finalVol = 0;
ch->oldPan = 128;
ch->outPan = 128;
ch->finalPan = 128;
ch->vibDepth = 0;
}
unlockMixer();
}
static void resetMusic(void)
{
song.timer = 1;
stopVoices();
setPos(0, 0);
}
void setPos(int32_t pos, int32_t row) // -1 = don't change
{
if (pos != -1)
{
song.songPos = (int16_t)pos;
if (song.len > 0 && song.songPos >= song.len)
song.songPos = song.len - 1;
song.pattNr = song.songTab[song.songPos];
song.pattLen = pattLens[song.pattNr];
}
if (row != -1)
{
song.pattPos = (int16_t)row;
if (song.pattPos >= song.pattLen)
song.pattPos = song.pattLen - 1;
}
song.timer = 1;
}
/***************************************************************************
* MODULE LOADING ROUTINES *
***************************************************************************/
static bool loadInstrHeader(MEMFILE *f, uint16_t i)
{
instrHeaderTyp ih;
memset(&ih, 0, INSTR_HEADER_SIZE);
mread(&ih.instrSize, 4, 1, f);
if (ih.instrSize > INSTR_HEADER_SIZE) ih.instrSize = INSTR_HEADER_SIZE;
mread(ih.name, ih.instrSize-4, 1, f);
if (ih.antSamp > 16)
return false;
if (ih.antSamp > 0)
{
if (!allocateInstr(i))
return false;
instrTyp *ins = instr[i];
memcpy(ins->name, ih.name, 22);
ins->name[22] = '\0';
// 8bb: copy instrument header elements to our instrument struct
memcpy(ins->ta, ih.ta, 96);
memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t));
memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t));
ins->envVPAnt = ih.envVPAnt;
ins->envPPAnt = ih.envPPAnt;
ins->envVSust = ih.envVSust;
ins->envVRepS = ih.envVRepS;
ins->envVRepE = ih.envVRepE;
ins->envPSust = ih.envPSust;
ins->envPRepS = ih.envPRepS;
ins->envPRepE = ih.envPRepE;
ins->envVTyp = ih.envVTyp;
ins->envPTyp = ih.envPTyp;
ins->vibTyp = ih.vibTyp;
ins->vibSweep = ih.vibSweep;
ins->vibDepth = ih.vibDepth;
ins->vibRate = ih.vibRate;
ins->fadeOut = ih.fadeOut;
ins->mute = (ih.mute == 1) ? true : false; // 8bb: correct logic!
ins->antSamp = ih.antSamp;
if (mread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1)
return false;
sampleTyp *s = instr[i]->samp;
sampleHeaderTyp *src = ih.samp;
for (int32_t j = 0; j < ih.antSamp; j++, s++, src++)
{
memcpy(s->name, src->name, 22);
s->name[22] = '\0';
s->len = src->len;
s->repS = src->repS;
s->repL = src->repL;
s->vol = src->vol;
s->fine = src->fine;
s->typ = src->typ;
s->pan = src->pan;
s->relTon = src->relTon;
}
}
return true;
}
static bool loadInstrSample(MEMFILE *f, uint16_t i)
{
if (instr[i] == NULL)
return true; // empty instrument
sampleTyp *s = instr[i]->samp;
for (uint16_t j = 0; j < instr[i]->antSamp; j++, s++)
{
if (s->len > 0)
{
s->pek = (int8_t *)malloc(s->len+2); // 8bb: +2 for linear interpolation point fix
if (s->pek == NULL)
return false;
mread(s->pek, 1, s->len, f);
delta2Samp(s->pek, s->len, (s->typ >> 4) & 1);
}
checkSampleRepeat(i, j);
}
return true;
}
static bool loadPatterns(MEMFILE *f, uint16_t antPtn)
{
uint8_t tmpLen;
patternHeaderTyp ph;
for (uint16_t i = 0; i < antPtn; i++)
{
mread(&ph.patternHeaderSize, 4, 1, f);
mread(&ph.typ, 1, 1, f);
ph.pattLen = 0;
if (song.ver == 0x0102)
{
mread(&tmpLen, 1, 1, f);
mread(&ph.dataLen, 2, 1, f);
ph.pattLen = (uint16_t)tmpLen + 1; // 8bb: +1 in v1.02
if (ph.patternHeaderSize > 8)
mseek(f, ph.patternHeaderSize - 8, SEEK_CUR);
}
else
{
mread(&ph.pattLen, 2, 1, f);
mread(&ph.dataLen, 2, 1, f);
if (ph.patternHeaderSize > 9)
mseek(f, ph.patternHeaderSize - 9, SEEK_CUR);
}
if (meof(f))
{
mclose(&f);
return false;
}
pattLens[i] = ph.pattLen;
if (ph.dataLen)
{
const uint16_t a = ph.pattLen * song.antChn * sizeof (tonTyp);
patt[i] = (tonTyp *)malloc(a);
if (patt[i] == NULL)
return false;
uint8_t *pattPtr = (uint8_t *)patt[i];
memset(pattPtr, 0, a);
mread(&pattPtr[a - ph.dataLen], 1, ph.dataLen, f);
unpackPatt(pattPtr, a - ph.dataLen, ph.pattLen, song.antChn);
}
if (patternEmpty(i))
{
if (patt[i] != NULL)
{
free(patt[i]);
patt[i] = NULL;
}
pattLens[i] = 64;
}
}
return true;
}
static bool loadMusicMOD(MEMFILE *f)
{
uint8_t ha[sizeof (songMOD31HeaderTyp)];
songMOD31HeaderTyp *h_MOD31 = (songMOD31HeaderTyp *)ha;
songMOD15HeaderTyp *h_MOD15 = (songMOD15HeaderTyp *)ha;
mread(ha, sizeof (ha), 1, f);
if (meof(f)) goto loadError2;
memcpy(song.name, h_MOD31->name, 20);
song.name[20] = '\0';
uint8_t j = 0;
for (uint8_t i = 1; i <= 16; i++)
{
if (memcmp(h_MOD31->Sig, MODSig[i-1], 4) == 0)
j = i + i;
}
if (memcmp(h_MOD31->Sig, "M!K!", 4) == 0 || memcmp(h_MOD31->Sig, "FLT4", 4) == 0)
j = 4;
if (memcmp(h_MOD31->Sig, "OCTA", 4) == 0)
j = 8;
uint8_t typ;
if (j > 0)
{
typ = 1;
song.antChn = j;
}
else
{
typ = 2;
song.antChn = 4;
}
int16_t ai;
if (typ == 1)
{
mseek(f, sizeof (songMOD31HeaderTyp), SEEK_SET);
song.len = h_MOD31->len;
song.repS = h_MOD31->repS;
memcpy(song.songTab, h_MOD31->songTab, 128);
ai = 31;
}
else
{
mseek(f, sizeof (songMOD15HeaderTyp), SEEK_SET);
song.len = h_MOD15->len;
song.repS = h_MOD15->repS;
memcpy(song.songTab, h_MOD15->songTab, 128);
ai = 15;
}
song.antInstrs = ai; // 8bb: added this
if (meof(f)) goto loadError2;
int32_t b = 0;
for (int32_t a = 0; a < 128; a++)
{
if (song.songTab[a] > b)
b = song.songTab[a];
}
uint8_t pattBuf[32 * 4 * 64]; // 8bb: max pattern size (32 channels, 64 rows)
for (uint16_t a = 0; a <= b; a++)
{
patt[a] = (tonTyp *)calloc(song.antChn * 64, sizeof (tonTyp));
if (patt[a] == NULL)
goto loadError;
pattLens[a] = 64;
mread(pattBuf, 1, song.antChn * 4 * 64, f);
if (meof(f)) goto loadError;
// convert pattern
uint8_t *bytes = pattBuf;
tonTyp *ton = patt[a];
for (int32_t i = 0; i < 64 * song.antChn; i++, bytes += 4, ton++)
{
const uint16_t period = ((bytes[0] & 0x0F) << 8) | bytes[1];
for (uint8_t k = 0; k < 96; k++)
{
if (period >= amigaPeriod[k])
{
ton->ton = k+1;
break;
}
}
ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4);
ton->effTyp = bytes[2] & 0x0F;
ton->eff = bytes[3];
switch (ton->effTyp)
{
case 0xC:
{
if (ton->eff > 64)
ton->eff = 64;
}
break;
case 0x1:
case 0x2:
{
if (ton->eff == 0)
ton->effTyp = 0;
}
break;
case 0x5:
{
if (ton->eff == 0)
ton->effTyp = 3;
}
break;
case 0x6:
{
if (ton->eff == 0)
ton->effTyp = 4;
}
break;
case 0xA:
{
if (ton->eff == 0)
ton->effTyp = 0;
}
break;
case 0xE:
{
const uint8_t effTyp = ton->effTyp >> 4;
const uint8_t eff = ton->effTyp & 15;
if (eff == 0 && (effTyp == 0x1 || effTyp == 0x2 || effTyp == 0xA || effTyp == 0xB))
{
ton->eff = 0;
ton->effTyp = 0;
}
}
break;
default: break;
}
}
if (patternEmpty(a))
{
free(patt[a]);
patt[a] = NULL;
pattLens[a] = 64;
}
}
for (uint16_t a = 1; a <= ai; a++)
{
modSampleTyp *modSmp = &h_MOD31->sample[a-1];
uint32_t len = 2 * SWAP16(modSmp->len);
if (len == 0)
continue;
if (!allocateInstr(a))
goto loadError;
sampleTyp *xmSmp = &instr[a]->samp[0];
memcpy(xmSmp->name, modSmp->name, 22);
xmSmp->name[22] = '\0';
uint32_t repS = 2 * SWAP16(modSmp->repS);
uint32_t repL = 2 * SWAP16(modSmp->repL);
if (repL <= 2)
{
repS = 0;
repL = 0;
}
if (repS+repL > len)
{
if (repS >= len)
{
repS = 0;
repL = 0;
}
else
{
repL = len-repS;
}
}
xmSmp->typ = (repL > 2) ? 1 : 0;
xmSmp->len = len;
xmSmp->vol = (modSmp->vol <= 64) ? modSmp->vol : 64;
xmSmp->fine = 8 * ((2 * ((modSmp->fine & 15) ^ 8)) - 16);
xmSmp->repL = repL;
xmSmp->repS = repS;
xmSmp->pek = (int8_t *)malloc(len + 2);
if (xmSmp->pek == NULL)
goto loadError;
mread(xmSmp->pek, 1, len, f);
}
mclose(&f);
if (song.repS > song.len)
song.repS = 0;
resetMusic();
upDateInstrs();
moduleLoaded = true;
return true;
loadError:
freeAllInstr();
freeAllPatterns();
loadError2:
mclose(&f);
return false;
}
bool loadMusicFromData(const uint8_t *data, uint32_t dataLength) // .XM/.MOD/.FT
{
uint16_t i;
songHeaderTyp h;
MEMFILE *f;
freeMusic();
setFrqTab(false);
// 8bb: instr 0 is a placeholder for empty instruments
allocateInstr(0);
instr[0]->samp[0].vol = 0;
moduleLoaded = false;
f = mopen(data, dataLength);
if (f == NULL) return false;
mread(&h, sizeof (h), 1, f);
if (meof(f)) goto loadError2;
if (memcmp(h.sig, "Extended Module: ", 17) != 0)
{
mrewind(f);
return loadMusicMOD(f);
}
if (h.ver < 0x0102 || h.ver > 0x104 || h.antChn < 2 || h.antChn > 32 || (h.antChn & 1) != 0 ||
h.antPtn > 256 || h.antInstrs > 128)
{
goto loadError2;
}
mseek(f, 60+h.headerSize, SEEK_SET);
if (meof(f)) goto loadError2;
memcpy(song.name, h.name, 20);
song.name[20] = '\0';
song.len = h.len;
song.repS = h.repS;
song.antChn = (uint8_t)h.antChn;
setFrqTab(h.flags & 1);
memcpy(song.songTab, h.songTab, 256);
song.antInstrs = h.antInstrs; // 8bb: added this
if (h.defSpeed == 0) h.defSpeed = 125; // 8bb: (BPM) FT2 doesn't do this, but we do it for safety
song.speed = h.defSpeed;
song.tempo = h.defTempo;
song.ver = h.ver;
// 8bb: bugfixes...
if (song.speed < 1) song.speed = 1;
if (song.tempo < 1) song.tempo = 1;
// ----------------
if (song.ver < 0x0104) // old FT2 XM format
{
for (i = 1; i <= h.antInstrs; i++)
{
if (!loadInstrHeader(f, i))
goto loadError;
}
if (!loadPatterns(f, h.antPtn))
goto loadError;
for (i = 1; i <= h.antInstrs; i++)
{
if (!loadInstrSample(f, i))
goto loadError;
}
}
else // latest FT2 XM format
{
if (!loadPatterns(f, h.antPtn))
goto loadError;
for (i = 1; i <= h.antInstrs; i++)
{
if (!loadInstrHeader(f, i)) goto loadError;
if (!loadInstrSample(f, i)) goto loadError;
}
}
mclose(&f);
if (song.repS > song.len)
song.repS = 0;
resetMusic();
upDateInstrs();
moduleLoaded = true;
return true;
loadError:
freeAllInstr();
freeAllPatterns();
loadError2:
mclose(&f);
return false;
}
bool loadMusic(const char *fileName) // .XM/.MOD/.FT
{
FILE *f = fopen(fileName, "rb");
if (f == NULL)
return false;
fseek(f, 0, SEEK_END);
const uint32_t fileSize = (uint32_t)ftell(f);
rewind(f);
uint8_t *fileBuffer = (uint8_t *)malloc(fileSize);
if (fileBuffer == NULL)
{
fclose(f);
return false;
}
if (fread(fileBuffer, 1, fileSize, f) != fileSize)
{
free(fileBuffer);
fclose(f);
return false;
}
fclose(f);
if (!loadMusicFromData((const uint8_t *)fileBuffer, fileSize))
{
free(fileBuffer);
return false;
}
free(fileBuffer);
return true;
}
/***************************************************************************
* PROCESS HANDLING *
***************************************************************************/
bool startMusic(void)
{
if (!moduleLoaded || song.speed == 0)
return false;
mix_ClearChannels();
stopVoices();
song.globVol = 64;
speedVal = ((realReplayRate * 5) / 2) / song.speed;
quickVolSizeVal = realReplayRate / 200;
if (!mix_Init(soundBufferSize))
return false;
if (openMixer(realReplayRate, soundBufferSize))
{
musicPaused = false;
return true;
}
return false;
}
void stopMusic(void)
{
pauseMusic();
closeMixer();
mix_Free();
song.globVol = 64;
resumeMusic();
}
void startPlaying(void)
{
stopMusic();
song.pattDelTime = song.pattDelTime2 = 0; // 8bb: added these
setPos(0, 0);
startMusic();
}
void stopPlaying(void)
{
stopMusic();
stopVoices();
}
void pauseMusic(void)
{
musicPaused = true;
}
void resumeMusic(void)
{
musicPaused = false;
}
// 8bb: added these three, handy
void toggleMusic(void)
{
musicPaused ^= 1;
}
void setInterpolation(bool on)
{
interpolationFlag = on;
mix_ClearChannels();
}
void setVolumeRamping(bool on)
{
volumeRampingFlag = on;
mix_ClearChannels();
}
/***************************************************************************
* CONFIGURATION ROUTINES *
***************************************************************************/
void setMasterVol(int32_t v) // 0..256
{
masterVol = CLAMP(v, 0, 256);
stmTyp *ch = stm;
for (int32_t i = 0; i < 32; i++, ch++)
ch->status |= IS_Vol;
}
void setAmp(int32_t level) // 1..32
{
boostLevel = (int16_t)CLAMP(level, 1, 32);
CDA_Amp = boostLevel * 8;
}
int32_t getMasterVol(void) // 8bb: added this
{
return masterVol;
}
int32_t getAmp(void) // 8bb: added this
{
return boostLevel;
}
uint8_t getNumActiveVoices(void) // 8bb: added this
{
uint8_t activeVoices = 0;
for (int32_t i = 0; i < song.antChn; i++)
{
CIType *v = getVoice(i);
if (!(v->SType & SType_Off) && v->SVol > 0)
activeVoices++;
}
return activeVoices;
}
static void setFrqTab(bool linear)
{
linearFrqTab = linear;
note2Period = linear ? linearPeriods : amigaPeriods;
}
void updateReplayRate(void)
{
lockMixer();
// 8bb: bit-exact to FT2
frequenceDivFactor = (int32_t)round(65536.0*1712.0/realReplayRate*8363.0);
frequenceMulFactor = (int32_t)round(256.0*65536.0/realReplayRate*8363.0);
unlockMixer();
}
/***************************************************************************
* INITIALIZATION ROUTINES *
***************************************************************************/
bool initMusic(int32_t audioFrequency, int32_t audioBufferSize, bool interpolation, bool volumeRamping)
{
closeMixer();
freeMusic();
memset(stm, 0, sizeof (stm));
realReplayRate = CLAMP(audioFrequency, 8000, 96000);
updateReplayRate();
soundBufferSize = audioBufferSize;
interpolationFlag = interpolation;
volumeRampingFlag = volumeRamping;
song.tempo = 6;
song.speed = 125;
setFrqTab(true);
resetMusic();
return true;
}
/***************************************************************************
* WAV DUMPING ROUTINES *
***************************************************************************/
static void WAV_WriteHeader(FILE *f, int32_t frq)
{
uint16_t w;
uint32_t l;
// 12 bytes
const uint32_t RIFF = 0x46464952;
fwrite(&RIFF, 4, 1, f);
fseek(f, 4, SEEK_CUR);
const uint32_t WAVE = 0x45564157;
fwrite(&WAVE, 4, 1, f);
// 24 bytes
const uint32_t fmt = 0x20746D66;
fwrite(&fmt, 4, 1, f);
l = 16; fwrite(&l, 4, 1, f);
w = 1; fwrite(&w, 2, 1, f);
w = 2; fwrite(&w, 2, 1, f);
l = frq; fwrite(&l, 4, 1, f);
l = frq*2*2; fwrite(&l, 4, 1, f);
w = 2*2; fwrite(&w, 2, 1, f);
w = 8*2; fwrite(&w, 2, 1, f);
// 8 bytes
const uint32_t DATA = 0x61746164;
fwrite(&DATA, 4, 1, f);
fseek(f, 4, SEEK_CUR);
}
static void WAV_WriteEnd(FILE *f, uint32_t size)
{
fseek(f, 4, SEEK_SET);
uint32_t l = size+4+24+8;
fwrite(&l, 4, 1, f);
fseek(f, 12+24+4, SEEK_SET);
fwrite(&size, 4, 1, f);
}
void WAVDump_Abort(void) // 8bb: added this
{
WAVDump_Flag = false;
}
bool WAVDump_Record(const char *filenameOut)
{
FILE *fil = fopen(filenameOut, "wb");
if (fil == NULL)
{
WAVDump_Flag = false;
return false;
}
const int32_t WDFrequency = realReplayRate;
const int32_t WDAmp = boostLevel;
const uint32_t maxSamplesPerTick = (WDFrequency*5 / 2) / 1; // 8bb: added this (min. BPM = 1, through hex editing)
int16_t *pBlock = (int16_t *)malloc(maxSamplesPerTick * (2 * sizeof (int16_t)));
if (pBlock == NULL)
{
fclose(fil);
WAVDump_Flag = false;
return false;
}
WAV_WriteHeader(fil, WDFrequency);
stopMusic();
mix_Init(maxSamplesPerTick);
uint16_t WDStartPos = 0;
uint16_t WDStopPos = song.len-1;
dump_Init(WDFrequency, WDAmp, WDStartPos);
uint32_t totSize = 0;
WAVDump_Flag = true;
while (!dump_EndOfTune(WDStopPos))
{
if (!WAVDump_Flag) // extra check so that external threads can force-abort render
break;
const uint32_t size = dump_GetFrame(pBlock);
fwrite(pBlock, 1, size, fil);
totSize += size;
}
WAVDump_Flag = false;
mix_Free();
WAV_WriteEnd(fil, totSize);
dump_Close();
stopMusic();
fclose(fil);
free(pBlock);
WAVDump_Flag = false;
return true;
}
/***************************************************************************
* MEMORY READ ROUTINES (8bb: added these) *
***************************************************************************/
static MEMFILE *mopen(const uint8_t *src, uint32_t length)
{
if (src == NULL || length == 0)
return NULL;
MEMFILE *b = (MEMFILE *)malloc(sizeof (MEMFILE));
if (b == NULL)
return NULL;
b->_base = (uint8_t *)src;
b->_ptr = (uint8_t *)src;
b->_cnt = length;
b->_bufsiz = length;
b->_eof = false;
return b;
}
static void mclose(MEMFILE **buf)
{
if (*buf != NULL)
{
free(*buf);
*buf = NULL;
}
}
static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf)
{
if (buf == NULL || buf->_ptr == NULL)
return 0;
size_t wrcnt = size * count;
if (size == 0 || buf->_eof)
return 0;
int32_t pcnt = (buf->_cnt > wrcnt) ? (int32_t)wrcnt : (int32_t)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 = true;
}
return pcnt / size;
}
static bool meof(MEMFILE *buf)
{
if (buf == NULL)
return true;
return buf->_eof;
}
static void mseek(MEMFILE *buf, int32_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 = false;
if (buf->_ptr >= buf->_base+buf->_bufsiz)
{
buf->_ptr = buf->_base + buf->_bufsiz;
buf->_eof = true;
}
buf->_cnt = (buf->_base + buf->_bufsiz) - buf->_ptr;
}
}
static void mrewind(MEMFILE *buf)
{
mseek(buf, 0, SEEK_SET);
}