Mercurial > codedump
diff win95kggui/dep/ft2play/pmplay.c @ 126:8e4ee43d3b81
remove submodules
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sun, 01 Oct 2023 03:48:43 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/win95kggui/dep/ft2play/pmplay.c Sun Oct 01 03:48:43 2023 -0400 @@ -0,0 +1,1414 @@ +/* +** - loaders and replayer handlers - +*/ + +#define DEFAULT_AMP 4 +#define DEFAULT_MASTER_VOL 256 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <math.h> +#include <assert.h> +#include "pmplay.h" +#include "pmp_mix.h" +#include "snd_masm.h" +#include "tables.h" + +#define INSTR_HEADER_SIZE 263 + +#define SWAP16(value) \ +( \ + (((uint16_t)((value) & 0x00FF)) << 8) | \ + (((uint16_t)((value) & 0xFF00)) >> 8) \ +) + +#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)) +#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 + +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; +uint32_t frequenceDivFactor, frequenceMulFactor, 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 & SAMPLE_16BIT); + 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 tap fix. + ** + ** 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 & LOOP_FORWARD) + { + if (sample16Bit) + ptr16[loopEnd] = ptr16[loopStart]; + else + s->pek[loopEnd] = s->pek[loopStart]; + + return; + } + else if (loopType & LOOP_PINGPONG) + { + 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 >97, remove it (prevents out-of-range read in note->sample LUT) + 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; + + if (ih.instrSize < 4) // 8bb: added protection + return false; + + 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) + { + bool sample16Bit = !!(s->typ & SAMPLE_16BIT); + + s->pek = (int8_t *)malloc(s->len+2); // 8bb: +2 for fixed interpolation tap sample + if (s->pek == NULL) + return false; + + mread(s->pek, 1, s->len, f); + delta2Samp(s->pek, s->len, sample16Bit); + } + + 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; + + freeMusic(); + setFrqTab(false); + + moduleLoaded = false; + + MEMFILE *f = mopen(data, dataLength); + if (f == NULL) + return false; + + // 8bb: instr 0 is a placeholder for empty instruments + allocateInstr(0); + instr[0]->samp[0].vol = 0; + + 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; + bool linearFrequencies = !!(h.flags & LINEAR_FREQUENCIES); + setFrqTab(linearFrequencies); + 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 = (uint32_t)round(65536.0*1712.0/realReplayRate*8363.0); + frequenceMulFactor = (uint32_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); +}