/*
** 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];
Lines 1–500 of 1411. Load more
/* ** 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];