ref: 7bc888ae5c8b31f7ccf657a7184b7b3957d48fba
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); }