ref: cea086da84e938d9396df9d250072ec956a125c6
dir: /pmp_main.c/
// XM replayer #include "common.h" #include "pmplay.h" #include "pmp_mix.h" #include "snd_masm.h" #include "tables.h" #define MAX_NOTES ((12 * 10 * 16) + 16) static tonTyp nilPatternLine[32]; // 8bb: used for non-allocated (empty) patterns typedef void (*volKolEfxRoutine)(stmTyp *ch); typedef void (*volKolEfxRoutine2)(stmTyp *ch, uint8_t *volKol); typedef void (*efxRoutine)(stmTyp *ch, uint8_t param); static void retrigVolume(stmTyp *ch) { ch->realVol = ch->oldVol; ch->outVol = ch->oldVol; ch->outPan = ch->oldPan; ch->status |= IS_Vol + IS_Pan + IS_QuickVol; } static void retrigEnvelopeVibrato(stmTyp *ch) { if (!(ch->waveCtrl & 0x04)) ch->vibPos = 0; if (!(ch->waveCtrl & 0x40)) ch->tremPos = 0; ch->retrigCnt = 0; ch->tremorPos = 0; ch->envSustainActive = true; instrTyp *ins = ch->instrSeg; if (ins->envVTyp & 1) { ch->envVCnt = 65535; ch->envVPos = 0; } if (ins->envPTyp & 1) { ch->envPCnt = 65535; ch->envPPos = 0; } ch->fadeOutSpeed = ins->fadeOut; // 8bb: FT2 doesn't check if fadeout is more than 4095 ch->fadeOutAmp = 32768; if (ins->vibDepth > 0) { ch->eVibPos = 0; if (ins->vibSweep > 0) { ch->eVibAmp = 0; ch->eVibSweep = (ins->vibDepth << 8) / ins->vibSweep; } else { ch->eVibAmp = ins->vibDepth << 8; ch->eVibSweep = 0; } } } static void keyOff(stmTyp *ch) { ch->envSustainActive = false; instrTyp *ins = ch->instrSeg; if (!(ins->envPTyp & 1)) // 8bb: yes, FT2 does this (!) { if (ch->envPCnt >= (uint16_t)ins->envPP[ch->envPPos][0]) ch->envPCnt = ins->envPP[ch->envPPos][0]-1; } if (ins->envVTyp & 1) { if (ch->envVCnt >= (uint16_t)ins->envVP[ch->envVPos][0]) ch->envVCnt = ins->envVP[ch->envVPos][0]-1; } else { ch->realVol = 0; ch->outVol = 0; ch->status |= IS_Vol + IS_QuickVol; } } uint32_t getFrequenceValue(uint16_t period) { uint32_t delta; if (period == 0) return 0; if (linearFrqTab) { const uint16_t invPeriod = (12 * 192 * 4) - period; // 8bb: this intentionally overflows uint16_t to be accurate to FT2 const int32_t octave = (14 - (invPeriod / 768)) & 0x1F; delta = (uint32_t)(((int64_t)logTab[invPeriod % 768] * frequenceMulFactor) >> 24); delta >>= octave; } else { delta = frequenceDivFactor / period; } return delta; } static void startTone(uint8_t ton, uint8_t effTyp, uint8_t eff, stmTyp *ch) { if (ton == 97) { keyOff(ch); return; } // 8bb: if we came from Rxy (retrig), we didn't check note (Ton) yet if (ton == 0) { ton = ch->tonNr; if (ton == 0) return; // 8bb: if still no note, return } ch->tonNr = ton; instrTyp *ins = instr[ch->instrNr]; if (ins == NULL) ins = instr[0]; ch->instrSeg = ins; ch->mute = ins->mute; uint8_t smp = ins->ta[ton-1] & 0xF; // 8bb: added safety-AND ch->sampleNr = smp; sampleTyp *s = &ins->samp[smp]; ch->relTonNr = s->relTon; ton += ch->relTonNr; if (ton >= 12*10) return; ch->oldVol = s->vol; ch->oldPan = s->pan; if (effTyp == 0x0E && (eff & 0xF0) == 0x50) ch->fineTune = ((eff & 0x0F) << 4) - 128; else ch->fineTune = s->fine; if (ton != 0) { const uint16_t tmpTon = ((ton - 1) << 4) + (((ch->fineTune >> 3) + 16) & 0xFF); if (tmpTon < MAX_NOTES) ch->outPeriod = ch->realPeriod = note2Period[tmpTon]; } ch->status |= IS_Period + IS_Vol + IS_Pan + IS_NyTon + IS_QuickVol; if (effTyp == 9) { if (eff) ch->smpOffset = ch->eff; ch->smpStartPos = ch->smpOffset << 8; } else { ch->smpStartPos = 0; } P_StartTone(s, ch->smpStartPos); } static void volume(stmTyp *ch, uint8_t param); // 8bb: actually volume slide static void vibrato2(stmTyp *ch); static void tonePorta(stmTyp *ch, uint8_t param); static void dummy(stmTyp *ch, uint8_t param) { USED(ch); USED(param); return; } static void finePortaUp(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->fPortaUpSpeed; ch->fPortaUpSpeed = param; ch->realPeriod -= param << 2; if ((int16_t)ch->realPeriod < 1) ch->realPeriod = 1; ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; } static void finePortaDown(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->fPortaDownSpeed; ch->fPortaDownSpeed = param; ch->realPeriod += param << 2; if ((int16_t)ch->realPeriod > 32000-1) ch->realPeriod = 32000-1; ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; } static void setGlissCtrl(stmTyp *ch, uint8_t param) { ch->glissFunk = param; } static void setVibratoCtrl(stmTyp *ch, uint8_t param) { ch->waveCtrl = (ch->waveCtrl & 0xF0) | param; } static void jumpLoop(stmTyp *ch, uint8_t param) { if (param == 0) { ch->pattPos = song.pattPos & 0xFF; } else if (ch->loopCnt == 0) { ch->loopCnt = param; song.pBreakPos = ch->pattPos; song.pBreakFlag = true; } else if (--ch->loopCnt > 0) { song.pBreakPos = ch->pattPos; song.pBreakFlag = true; } } static void setTremoloCtrl(stmTyp *ch, uint8_t param) { ch->waveCtrl = (param << 4) | (ch->waveCtrl & 0x0F); } static void volFineUp(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->fVolSlideUpSpeed; ch->fVolSlideUpSpeed = param; ch->realVol += param; if (ch->realVol > 64) ch->realVol = 64; ch->outVol = ch->realVol; ch->status |= IS_Vol; } static void volFineDown(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->fVolSlideDownSpeed; ch->fVolSlideDownSpeed = param; ch->realVol -= param; if ((int8_t)ch->realVol < 0) ch->realVol = 0; ch->outVol = ch->realVol; ch->status |= IS_Vol; } static void noteCut0(stmTyp *ch, uint8_t param) { if (param == 0) // 8bb: only a parameter of zero is handled here { ch->realVol = 0; ch->outVol = 0; ch->status |= IS_Vol + IS_QuickVol; } } static void pattDelay(stmTyp *ch, uint8_t param) { if (song.pattDelTime2 == 0) song.pattDelTime = param + 1; USED(ch); } static const efxRoutine EJumpTab_TickZero[16] = { dummy, // 0 finePortaUp, // 1 finePortaDown, // 2 setGlissCtrl, // 3 setVibratoCtrl, // 4 dummy, // 5 jumpLoop, // 6 setTremoloCtrl, // 7 dummy, // 8 dummy, // 9 volFineUp, // A volFineDown, // B noteCut0, // C dummy, // D pattDelay, // E dummy // F }; static void E_Effects_TickZero(stmTyp *ch, uint8_t param) { EJumpTab_TickZero[param >> 4](ch, param & 0x0F); } static void posJump(stmTyp *ch, uint8_t param) { song.songPos = (int16_t)param - 1; song.pBreakPos = 0; song.posJumpFlag = true; USED(ch); } static void pattBreak(stmTyp *ch, uint8_t param) { song.posJumpFlag = true; param = ((param >> 4) * 10) + (param & 0x0F); if (param <= 63) song.pBreakPos = param; else song.pBreakPos = 0; USED(ch); } static void setSpeed(stmTyp *ch, uint8_t param) { if (param >= 32) { song.speed = param; P_SetSpeed(song.speed); } else { song.timer = song.tempo = param; } USED(ch); } static void setGlobaVol(stmTyp *ch, uint8_t param) { if (param > 64) param = 64; song.globVol = param; stmTyp *c = stm; for (int32_t i = 0; i < song.antChn; i++, c++) // 8bb: update all voice volumes c->status |= IS_Vol; USED(ch); } static void setEnvelopePos(stmTyp *ch, uint8_t param) { int8_t envPos; bool envUpdate; int16_t newEnvPos; instrTyp *ins = ch->instrSeg; // *** VOLUME ENVELOPE *** if (ins->envVTyp & 1) { ch->envVCnt = param - 1; envPos = 0; envUpdate = true; newEnvPos = param; if (ins->envVPAnt > 1) { envPos++; for (int32_t i = 0; i < ins->envVPAnt-1; i++) { if (newEnvPos < ins->envVP[envPos][0]) { envPos--; newEnvPos -= ins->envVP[envPos][0]; if (newEnvPos == 0) { envUpdate = false; break; } if (ins->envVP[envPos+1][0] <= ins->envVP[envPos][0]) { envUpdate = true; break; } ch->envVIPValue = ((ins->envVP[envPos+1][1] - ins->envVP[envPos][1]) & 0xFF) << 8; ch->envVIPValue /= (ins->envVP[envPos+1][0] - ins->envVP[envPos][0]); ch->envVAmp = (ch->envVIPValue * (newEnvPos - 1)) + ((ins->envVP[envPos][1] & 0xFF) << 8); envPos++; envUpdate = false; break; } envPos++; } if (envUpdate) envPos--; } if (envUpdate) { ch->envVIPValue = 0; ch->envVAmp = (ins->envVP[envPos][1] & 0xFF) << 8; } if (envPos >= ins->envVPAnt) { envPos = ins->envVPAnt - 1; if (envPos < 0) envPos = 0; } ch->envVPos = envPos; } // *** PANNING ENVELOPE *** if (ins->envVTyp & 2) // 8bb: probably an FT2 bug { ch->envPCnt = param - 1; envPos = 0; envUpdate = true; newEnvPos = param; if (ins->envPPAnt > 1) { envPos++; for (int32_t i = 0; i < ins->envPPAnt-1; i++) { if (newEnvPos < ins->envPP[envPos][0]) { envPos--; newEnvPos -= ins->envPP[envPos][0]; if (newEnvPos == 0) { envUpdate = false; break; } if (ins->envPP[envPos + 1][0] <= ins->envPP[envPos][0]) { envUpdate = true; break; } ch->envPIPValue = ((ins->envPP[envPos+1][1] - ins->envPP[envPos][1]) & 0xFF) << 8; ch->envPIPValue /= (ins->envPP[envPos+1][0] - ins->envPP[envPos][0]); ch->envPAmp = (ch->envPIPValue * (newEnvPos - 1)) + ((ins->envPP[envPos][1] & 0xFF) << 8); envPos++; envUpdate = false; break; } envPos++; } if (envUpdate) envPos--; } if (envUpdate) { ch->envPIPValue = 0; ch->envPAmp = (ins->envPP[envPos][1] & 0xFF) << 8; } if (envPos >= ins->envPPAnt) { envPos = ins->envPPAnt - 1; if (envPos < 0) envPos = 0; } ch->envPPos = envPos; } } /* -- tick-zero volume column effects -- ** 2nd parameter is used for a volume column quirk with the Rxy command (multiretrig) */ static void v_SetVibSpeed(stmTyp *ch, uint8_t *volKol) { *volKol = (ch->volKolVol & 0x0F) << 2; if (*volKol != 0) ch->vibSpeed = *volKol; } static void v_Volume(stmTyp *ch, uint8_t *volKol) { *volKol -= 16; if (*volKol > 64) // 8bb: no idea why FT2 has this check... *volKol = 64; ch->outVol = ch->realVol = *volKol; ch->status |= IS_Vol + IS_QuickVol; } static void v_FineSlideDown(stmTyp *ch, uint8_t *volKol) { *volKol = (uint8_t)(0 - (ch->volKolVol & 0x0F)) + ch->realVol; if ((int8_t)*volKol < 0) *volKol = 0; ch->outVol = ch->realVol = *volKol; ch->status |= IS_Vol; } static void v_FineSlideUp(stmTyp *ch, uint8_t *volKol) { *volKol = (ch->volKolVol & 0x0F) + ch->realVol; if (*volKol > 64) *volKol = 64; ch->outVol = ch->realVol = *volKol; ch->status |= IS_Vol; } static void v_SetPan(stmTyp *ch, uint8_t *volKol) { *volKol <<= 4; ch->outPan = *volKol; ch->status |= IS_Pan; } // -- non-tick-zero volume column effects -- static void v_SlideDown(stmTyp *ch) { uint8_t newVol = (uint8_t)(0 - (ch->volKolVol & 0x0F)) + ch->realVol; if ((int8_t)newVol < 0) newVol = 0; ch->outVol = ch->realVol = newVol; ch->status |= IS_Vol; } static void v_SlideUp(stmTyp *ch) { uint8_t newVol = (ch->volKolVol & 0x0F) + ch->realVol; if (newVol > 64) newVol = 64; ch->outVol = ch->realVol = newVol; ch->status |= IS_Vol; } static void v_Vibrato(stmTyp *ch) { const uint8_t param = ch->volKolVol & 0xF; if (param > 0) ch->vibDepth = param; vibrato2(ch); } static void v_PanSlideLeft(stmTyp *ch) { uint16_t tmp16 = (uint8_t)(0 - (ch->volKolVol & 0x0F)) + ch->outPan; if (tmp16 < 256) // 8bb: includes an FT2 bug: pan-slide-left of 0 = set pan to 0 tmp16 = 0; ch->outPan = (uint8_t)tmp16; ch->status |= IS_Pan; } static void v_PanSlideRight(stmTyp *ch) { uint16_t tmp16 = (ch->volKolVol & 0x0F) + ch->outPan; if (tmp16 > 255) tmp16 = 255; ch->outPan = (uint8_t)tmp16; ch->status |= IS_Pan; } static void v_TonePorta(stmTyp *ch) { tonePorta(ch, 0); // 8bb: the last parameter is actually not used in tonePorta() } static void v_dummy(stmTyp *ch) { USED(ch); return; } static void v_dummy2(stmTyp *ch, uint8_t *volKol) { USED(ch); USED(volKol); return; } static const volKolEfxRoutine VJumpTab_TickNonZero[16] = { v_dummy, v_dummy, v_dummy, v_dummy, v_dummy, v_dummy, v_SlideDown, v_SlideUp, v_dummy, v_dummy, v_dummy, v_Vibrato, v_dummy, v_PanSlideLeft, v_PanSlideRight, v_TonePorta }; static const volKolEfxRoutine2 VJumpTab_TickZero[16] = { v_dummy2, v_Volume, v_Volume, v_Volume, v_Volume, v_Volume, v_dummy2, v_dummy2, v_FineSlideDown, v_FineSlideUp, v_SetVibSpeed, v_dummy2, v_SetPan, v_dummy2, v_dummy2, v_dummy2 }; static void setPan(stmTyp *ch, uint8_t param) { ch->outPan = param; ch->status |= IS_Pan; } static void setVol(stmTyp *ch, uint8_t param) { if (param > 64) param = 64; ch->outVol = ch->realVol = param; ch->status |= IS_Vol + IS_QuickVol; } static void xFinePorta(stmTyp *ch, uint8_t param) { const uint8_t type = param >> 4; param &= 0x0F; if (type == 0x1) // extra fine porta up { if (param == 0) param = ch->ePortaUpSpeed; ch->ePortaUpSpeed = param; uint16_t newPeriod = ch->realPeriod; newPeriod -= param; if ((int16_t)newPeriod < 1) newPeriod = 1; ch->outPeriod = ch->realPeriod = newPeriod; ch->status |= IS_Period; } else if (type == 0x2) // extra fine porta down { if (param == 0) param = ch->ePortaDownSpeed; ch->ePortaDownSpeed = param; uint16_t newPeriod = ch->realPeriod; newPeriod += param; if ((int16_t)newPeriod > 32000-1) newPeriod = 32000-1; ch->outPeriod = ch->realPeriod = newPeriod; ch->status |= IS_Period; } } static void doMultiRetrig(stmTyp *ch, uint8_t param) // 8bb: "param" is never used (needed for efx jumptable structure) { uint8_t cnt = ch->retrigCnt + 1; if (cnt < ch->retrigSpeed) { ch->retrigCnt = cnt; return; } ch->retrigCnt = 0; int16_t vol = ch->realVol; switch (ch->retrigVol) { case 0x1: vol -= 1; break; case 0x2: vol -= 2; break; case 0x3: vol -= 4; break; case 0x4: vol -= 8; break; case 0x5: vol -= 16; break; case 0x6: vol = (vol >> 1) + (vol >> 3) + (vol >> 4); break; case 0x7: vol >>= 1; break; case 0x8: break; // 8bb: does not change the volume case 0x9: vol += 1; break; case 0xA: vol += 2; break; case 0xB: vol += 4; break; case 0xC: vol += 8; break; case 0xD: vol += 16; break; case 0xE: vol = (vol >> 1) + vol; break; case 0xF: vol += vol; break; default: break; } vol = CLAMP(vol, 0, 64); ch->realVol = (uint8_t)vol; ch->outVol = ch->realVol; if (ch->volKolVol >= 0x10 && ch->volKolVol <= 0x50) { ch->outVol = ch->volKolVol - 0x10; ch->realVol = ch->outVol; } else if (ch->volKolVol >= 0xC0 && ch->volKolVol <= 0xCF) { ch->outPan = (ch->volKolVol & 0x0F) << 4; } startTone(0, 0, 0, ch); (void)param; } static void multiRetrig(stmTyp *ch, uint8_t param, uint8_t volumeColumnData) { uint8_t tmpParam; tmpParam = param & 0x0F; if (tmpParam == 0) tmpParam = ch->retrigSpeed; ch->retrigSpeed = tmpParam; tmpParam = param >> 4; if (tmpParam == 0) tmpParam = ch->retrigVol; ch->retrigVol = tmpParam; if (volumeColumnData == 0) doMultiRetrig(ch, 0); // 8bb: the second parameter is never used (needed for efx jumptable structure) } static const efxRoutine JumpTab_TickZero[36] = { dummy, // 0 dummy, // 1 dummy, // 2 dummy, // 3 dummy, // 4 dummy, // 5 dummy, // 6 dummy, // 7 setPan, // 8 dummy, // 9 dummy, // A posJump, // B setVol, // C pattBreak, // D E_Effects_TickZero, // E setSpeed, // F setGlobaVol, // G dummy, // H dummy, // I dummy, // J dummy, // K setEnvelopePos, // L dummy, // M dummy, // N dummy, // O dummy, // P dummy, // Q dummy, // R dummy, // S dummy, // T dummy, // U dummy, // V dummy, // W xFinePorta, // X dummy, // Y dummy // Z }; static void checkEffects(stmTyp *ch) // tick0 effect handling { // volume column effects uint8_t newVolKol = ch->volKolVol; // 8bb: manipulated by vol. column effects, then used for multiretrig check (FT2 quirk) VJumpTab_TickZero[ch->volKolVol >> 4](ch, &newVolKol); // normal effects const uint8_t param = ch->eff; if ((ch->effTyp == 0 && param == 0) || ch->effTyp > 35) return; // no effect // 8bb: this one has to be done here instead of in the jumptable, as it needs the "newVolKol" parameter (FT2 quirk) if (ch->effTyp == 27) { multiRetrig(ch, param, newVolKol); return; } JumpTab_TickZero[ch->effTyp](ch, ch->eff); } static void fixTonePorta(stmTyp *ch, const tonTyp *p, uint8_t inst) { if (p->ton > 0) { if (p->ton == 97) { keyOff(ch); } else { const uint16_t portaTmp = ((((p->ton - 1) + ch->relTonNr) & 0xFF) * 16) + (((ch->fineTune >> 3) + 16) & 0xFF); if (portaTmp < MAX_NOTES) { ch->wantPeriod = note2Period[portaTmp]; if (ch->wantPeriod == ch->realPeriod) ch->portaDir = 0; else if (ch->wantPeriod > ch->realPeriod) ch->portaDir = 1; else ch->portaDir = 2; } } } if (inst > 0) { retrigVolume(ch); if (p->ton != 97) retrigEnvelopeVibrato(ch); } } static void getNewNote(stmTyp *ch, const tonTyp *p) { ch->volKolVol = p->vol; if (ch->effTyp == 0) { if (ch->eff > 0) // 8bb: we have an arpeggio running, set period back { ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; } } else { // 8bb: if we have a vibrato on previous row (ch) that ends at current row (p), set period back if ((ch->effTyp == 4 || ch->effTyp == 6) && (p->effTyp != 4 && p->effTyp != 6)) { // 8bb: but it's ending at the next (this) row, so set period back ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; } } ch->effTyp = p->effTyp; ch->eff = p->eff; ch->tonTyp = (p->instr << 8) | p->ton; // 8bb: 'inst' var is used for later if checks... uint8_t inst = p->instr; if (inst > 0) { if (inst <= 128) ch->instrNr = inst; else inst = 0; } bool checkEfx = true; if (p->effTyp == 0x0E) { if (p->eff >= 0xD1 && p->eff <= 0xDF) return; // 8bb: we have a note delay (ED1..EDF) else if (p->eff == 0x90) checkEfx = false; } if (checkEfx) { if ((ch->volKolVol & 0xF0) == 0xF0) // gxx { const uint8_t volKolParam = ch->volKolVol & 0x0F; if (volKolParam > 0) ch->portaSpeed = volKolParam << 6; fixTonePorta(ch, p, inst); checkEffects(ch); return; } if (p->effTyp == 3 || p->effTyp == 5) // 3xx or 5xx { if (p->effTyp != 5 && p->eff != 0) ch->portaSpeed = p->eff << 2; fixTonePorta(ch, p, inst); checkEffects(ch); return; } if (p->effTyp == 0x14 && p->eff == 0) // K00 (KeyOff - only handle tick 0 here) { keyOff(ch); if (inst) retrigVolume(ch); checkEffects(ch); return; } if (p->ton == 0) { if (inst > 0) { retrigVolume(ch); retrigEnvelopeVibrato(ch); } checkEffects(ch); return; } } if (p->ton == 97) keyOff(ch); else startTone(p->ton, p->effTyp, p->eff, ch); if (inst > 0) { retrigVolume(ch); if (p->ton != 97) retrigEnvelopeVibrato(ch); } checkEffects(ch); } static void fixaEnvelopeVibrato(stmTyp *ch) { bool envInterpolateFlag, envDidInterpolate; uint8_t envPos; int16_t autoVibVal; uint16_t autoVibAmp, envVal; uint32_t vol; instrTyp *ins = ch->instrSeg; // *** FADEOUT *** if (!ch->envSustainActive) { ch->status |= IS_Vol; // 8bb: unsigned clamp + reset if (ch->fadeOutAmp >= ch->fadeOutSpeed) { ch->fadeOutAmp -= ch->fadeOutSpeed; } else { ch->fadeOutAmp = 0; ch->fadeOutSpeed = 0; } } if (!ch->mute) { // *** VOLUME ENVELOPE *** envVal = 0; if (ins->envVTyp & 1) { envDidInterpolate = false; envPos = ch->envVPos; if (++ch->envVCnt == ins->envVP[envPos][0]) { ch->envVAmp = ins->envVP[envPos][1] << 8; envPos++; if (ins->envVTyp & 4) { envPos--; if (envPos == ins->envVRepE) { if (!(ins->envVTyp & 2) || envPos != ins->envVSust || ch->envSustainActive) { envPos = ins->envVRepS; ch->envVCnt = ins->envVP[envPos][0]; ch->envVAmp = ins->envVP[envPos][1] << 8; } } envPos++; } if (envPos < ins->envVPAnt) { envInterpolateFlag = true; if ((ins->envVTyp & 2) && ch->envSustainActive) { if (envPos-1 == ins->envVSust) { envPos--; ch->envVIPValue = 0; envInterpolateFlag = false; } } if (envInterpolateFlag) { ch->envVPos = envPos; ch->envVIPValue = 0; if (ins->envVP[envPos][0] > ins->envVP[envPos-1][0]) { ch->envVIPValue = (ins->envVP[envPos][1] - ins->envVP[envPos-1][1]) << 8; ch->envVIPValue /= (ins->envVP[envPos][0] - ins->envVP[envPos-1][0]); envVal = ch->envVAmp; envDidInterpolate = true; } } } else { ch->envVIPValue = 0; } } if (!envDidInterpolate) { ch->envVAmp += ch->envVIPValue; envVal = ch->envVAmp; if (envVal > 64*256) { if (envVal > 128*256) envVal = 64*256; else envVal = 0; ch->envVIPValue = 0; } } envVal >>= 8; vol = (envVal * ch->outVol * ch->fadeOutAmp) >> (16+2); vol = (vol * song.globVol) >> 7; ch->status |= IS_Vol; // 8bb: update vol every tick because vol envelope is enabled } else { vol = ((ch->outVol << 4) * ch->fadeOutAmp) >> 16; vol = (vol * song.globVol) >> 7; } ch->finalVol = (uint16_t)vol; // 0..256 } else { ch->finalVol = 0; } // *** PANNING ENVELOPE *** envVal = 0; if (ins->envPTyp & 1) { envDidInterpolate = false; envPos = ch->envPPos; if (++ch->envPCnt == ins->envPP[envPos][0]) { ch->envPAmp = ins->envPP[envPos][1] << 8; envPos++; if (ins->envPTyp & 4) { envPos--; if (envPos == ins->envPRepE) { if (!(ins->envPTyp & 2) || envPos != ins->envPSust || ch->envSustainActive) { envPos = ins->envPRepS; ch->envPCnt = ins->envPP[envPos][0]; ch->envPAmp = ins->envPP[envPos][1] << 8; } } envPos++; } if (envPos < ins->envPPAnt) { envInterpolateFlag = true; if ((ins->envPTyp & 2) && ch->envSustainActive) { if (envPos-1 == ins->envPSust) { envPos--; ch->envPIPValue = 0; envInterpolateFlag = false; } } if (envInterpolateFlag) { ch->envPPos = envPos; ch->envPIPValue = 0; if (ins->envPP[envPos][0] > ins->envPP[envPos-1][0]) { ch->envPIPValue = (ins->envPP[envPos][1] - ins->envPP[envPos-1][1]) << 8; ch->envPIPValue /= (ins->envPP[envPos][0] - ins->envPP[envPos-1][0]); envVal = ch->envPAmp; envDidInterpolate = true; } } } else { ch->envPIPValue = 0; } } if (!envDidInterpolate) { ch->envPAmp += ch->envPIPValue; envVal = ch->envPAmp; if (envVal > 64*256) { if (envVal > 128*256) envVal = 64*256; else envVal = 0; ch->envPIPValue = 0; } } int16_t panTmp = ch->outPan - 128; if (panTmp > 0) panTmp = 0 - panTmp; panTmp += 128; panTmp <<= 3; envVal -= 32*256; ch->finalPan = ch->outPan + (uint8_t)(((int16_t)envVal * panTmp) >> 16); ch->status |= IS_Pan; } else { ch->finalPan = ch->outPan; } // *** AUTO VIBRATO *** if (ins->vibDepth > 0) { if (ch->eVibSweep > 0) { autoVibAmp = ch->eVibSweep; if (ch->envSustainActive) { autoVibAmp += ch->eVibAmp; if ((autoVibAmp >> 8) > ins->vibDepth) { autoVibAmp = ins->vibDepth << 8; ch->eVibSweep = 0; } ch->eVibAmp = autoVibAmp; } } else { autoVibAmp = ch->eVibAmp; } ch->eVibPos += ins->vibRate; if (ins->vibTyp == 1) autoVibVal = (ch->eVibPos > 127) ? 64 : -64; // square else if (ins->vibTyp == 2) autoVibVal = (((ch->eVibPos >> 1) + 64) & 127) - 64; // ramp up else if (ins->vibTyp == 3) autoVibVal = ((-(ch->eVibPos >> 1) + 64) & 127) - 64; // ramp down else autoVibVal = vibSineTab[ch->eVibPos]; // sine autoVibVal <<= 2; uint16_t tmpPeriod = (autoVibVal * (int16_t)autoVibAmp) >> 16; tmpPeriod += ch->outPeriod; if (tmpPeriod >= 32000) tmpPeriod = 0; // 8bb: yes, FT2 does this (!) ch->finalPeriod = tmpPeriod; ch->status |= IS_Period; } else { ch->finalPeriod = ch->outPeriod; } } // 8bb: for arpeggio and portamento (semitone-slide mode) static uint16_t relocateTon(uint16_t period, uint8_t arpNote, stmTyp *ch) { int32_t tmpPeriod; const int32_t fineTune = ((ch->fineTune >> 3) + 16) << 1; int32_t hiPeriod = (8 * 12 * 16) * 2; int32_t loPeriod = 0; for (int32_t i = 0; i < 8; i++) { tmpPeriod = (((loPeriod + hiPeriod) >> 1) & 0xFFFFFFE0) + fineTune; int32_t tableIndex = (uint32_t)(tmpPeriod - 16) >> 1; tableIndex = CLAMP(tableIndex, 0, 1935); // 8bb: added security check if (period >= note2Period[tableIndex]) hiPeriod = (tmpPeriod - fineTune) & 0xFFFFFFE0; else loPeriod = (tmpPeriod - fineTune) & 0xFFFFFFE0; } tmpPeriod = loPeriod + fineTune + (arpNote << 5); if (tmpPeriod < 0) // 8bb: added security check tmpPeriod = 0; if (tmpPeriod >= (8*12*16+15)*2-1) // 8bb: FT2 bug, off-by-one edge case tmpPeriod = (8*12*16+15)*2; return note2Period[(uint32_t)tmpPeriod>>1]; } static void vibrato2(stmTyp *ch) { uint8_t tmpVib = (ch->vibPos >> 2) & 0x1F; switch (ch->waveCtrl & 3) { // 0: sine case 0: tmpVib = vibTab[tmpVib]; break; // 1: ramp case 1: { tmpVib <<= 3; if ((int8_t)ch->vibPos < 0) tmpVib = ~tmpVib; } break; // 2/3: square default: tmpVib = 255; break; } tmpVib = (tmpVib * ch->vibDepth) >> 5; if ((int8_t)ch->vibPos < 0) ch->outPeriod = ch->realPeriod - tmpVib; else ch->outPeriod = ch->realPeriod + tmpVib; ch->status |= IS_Period; ch->vibPos += ch->vibSpeed; } static void arp(stmTyp *ch, uint8_t param) { const uint8_t tick = arpTab[song.timer & 0xFF]; // 8bb: non-FT2 protection (we have 248 extra overflow bytes in LUT, but not more!) if (tick == 0) { ch->outPeriod = ch->realPeriod; } else { const uint8_t note = (tick == 1) ? (param >> 4) : (param & 0x0F); ch->outPeriod = relocateTon(ch->realPeriod, note, ch); } ch->status |= IS_Period; } static void portaUp(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->portaUpSpeed; ch->portaUpSpeed = param; ch->realPeriod -= param << 2; if ((int16_t)ch->realPeriod < 1) ch->realPeriod = 1; ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; } static void portaDown(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->portaDownSpeed; ch->portaDownSpeed = param; ch->realPeriod += param << 2; if ((int16_t)ch->realPeriod > 32000-1) // 8bb: FT2 bug, should've been unsigned comparison! ch->realPeriod = 32000-1; ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; } static void tonePorta(stmTyp *ch, uint8_t param) { if (ch->portaDir == 0) return; if (ch->portaDir > 1) { ch->realPeriod -= ch->portaSpeed; if ((int16_t)ch->realPeriod <= (int16_t)ch->wantPeriod) { ch->portaDir = 1; ch->realPeriod = ch->wantPeriod; } } else { ch->realPeriod += ch->portaSpeed; if (ch->realPeriod >= ch->wantPeriod) { ch->portaDir = 1; ch->realPeriod = ch->wantPeriod; } } if (ch->glissFunk) // 8bb: semitone-slide flag ch->outPeriod = relocateTon(ch->realPeriod, 0, ch); else ch->outPeriod = ch->realPeriod; ch->status |= IS_Period; (void)param; } static void vibrato(stmTyp *ch, uint8_t param) { uint8_t tmp8; if (ch->eff > 0) { tmp8 = param & 0x0F; if (tmp8 > 0) ch->vibDepth = tmp8; tmp8 = (param & 0xF0) >> 2; if (tmp8 > 0) ch->vibSpeed = tmp8; } vibrato2(ch); } static void tonePlusVol(stmTyp *ch, uint8_t param) { tonePorta(ch, 0); // 8bb: the last parameter is actually not used in tonePorta() volume(ch, param); (void)param; } static void vibratoPlusVol(stmTyp *ch, uint8_t param) { vibrato2(ch); volume(ch, param); (void)param; } static void tremolo(stmTyp *ch, uint8_t param) { uint8_t tmp8; int16_t tremVol; const uint8_t tmpEff = param; if (tmpEff > 0) { tmp8 = tmpEff & 0x0F; if (tmp8 > 0) ch->tremDepth = tmp8; tmp8 = (tmpEff & 0xF0) >> 2; if (tmp8 > 0) ch->tremSpeed = tmp8; } uint8_t tmpTrem = (ch->tremPos >> 2) & 0x1F; switch ((ch->waveCtrl >> 4) & 3) { // 0: sine case 0: tmpTrem = vibTab[tmpTrem]; break; // 1: ramp case 1: { tmpTrem <<= 3; if ((int8_t)ch->vibPos < 0) // 8bb: FT2 bug, should've been ch->tremPos tmpTrem = ~tmpTrem; } break; // 2/3: square default: tmpTrem = 255; break; } tmpTrem = (tmpTrem * ch->tremDepth) >> 6; if ((int8_t)ch->tremPos < 0) { tremVol = ch->realVol - tmpTrem; if (tremVol < 0) tremVol = 0; } else { tremVol = ch->realVol + tmpTrem; if (tremVol > 64) tremVol = 64; } ch->outVol = (uint8_t)tremVol; ch->status |= IS_Vol; ch->tremPos += ch->tremSpeed; } static void volume(stmTyp *ch, uint8_t param) // 8bb: actually volume slide { if (param == 0) param = ch->volSlideSpeed; ch->volSlideSpeed = param; uint8_t newVol = ch->realVol; if ((param & 0xF0) == 0) { newVol -= param; if ((int8_t)newVol < 0) newVol = 0; } else { param >>= 4; newVol += param; if (newVol > 64) newVol = 64; } ch->outVol = ch->realVol = newVol; ch->status |= IS_Vol; } static void globalVolSlide(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->globVolSlideSpeed; ch->globVolSlideSpeed = param; uint8_t newVol = (uint8_t)song.globVol; if ((param & 0xF0) == 0) { newVol -= param; if ((int8_t)newVol < 0) newVol = 0; } else { param >>= 4; newVol += param; if (newVol > 64) newVol = 64; } song.globVol = newVol; stmTyp *c = stm; for (int32_t i = 0; i < song.antChn; i++, c++) // 8bb: update all voice volumes c->status |= IS_Vol; } static void keyOffCmd(stmTyp *ch, uint8_t param) { if ((uint8_t)(song.tempo-song.timer) == (param & 31)) keyOff(ch); } static void panningSlide(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->panningSlideSpeed; ch->panningSlideSpeed = param; int16_t newPan = (int16_t)ch->outPan; if ((param & 0xF0) == 0) { newPan -= param; if (newPan < 0) newPan = 0; } else { param >>= 4; newPan += param; if (newPan > 255) newPan = 255; } ch->outPan = (uint8_t)newPan; ch->status |= IS_Pan; } static void tremor(stmTyp *ch, uint8_t param) { if (param == 0) param = ch->tremorSave; ch->tremorSave = param; uint8_t tremorSign = ch->tremorPos & 0x80; uint8_t tremorData = ch->tremorPos & 0x7F; tremorData--; if ((int8_t)tremorData < 0) { if (tremorSign == 0x80) { tremorSign = 0x00; tremorData = param & 0x0F; } else { tremorSign = 0x80; tremorData = param >> 4; } } ch->tremorPos = tremorSign | tremorData; ch->outVol = (tremorSign == 0x80) ? ch->realVol : 0; ch->status |= IS_Vol + IS_QuickVol; } static void retrigNote(stmTyp *ch, uint8_t param) { if (param == 0) // 8bb: E9x with a param of zero is handled in getNewNote() return; if ((song.tempo-song.timer) % param == 0) { startTone(0, 0, 0, ch); retrigEnvelopeVibrato(ch); } } static void noteCut(stmTyp *ch, uint8_t param) { if ((uint8_t)(song.tempo-song.timer) == param) { ch->outVol = ch->realVol = 0; ch->status |= IS_Vol + IS_QuickVol; } } static void noteDelay(stmTyp *ch, uint8_t param) { if ((uint8_t)(song.tempo-song.timer) == param) { startTone(ch->tonTyp & 0xFF, 0, 0, ch); if ((ch->tonTyp & 0xFF00) > 0) retrigVolume(ch); retrigEnvelopeVibrato(ch); if (ch->volKolVol >= 0x10 && ch->volKolVol <= 0x50) { ch->outVol = ch->volKolVol - 16; ch->realVol = ch->outVol; } else if (ch->volKolVol >= 0xC0 && ch->volKolVol <= 0xCF) { ch->outPan = (ch->volKolVol & 0x0F) << 4; } } } static const efxRoutine EJumpTab_TickNonZero[16] = { dummy, // 0 dummy, // 1 dummy, // 2 dummy, // 3 dummy, // 4 dummy, // 5 dummy, // 6 dummy, // 7 dummy, // 8 retrigNote, // 9 dummy, // A dummy, // B noteCut, // C noteDelay, // D dummy, // E dummy // F }; static void E_Effects_TickNonZero(stmTyp *ch, uint8_t param) { EJumpTab_TickNonZero[param >> 4](ch, param & 0xF); } static const efxRoutine JumpTab_TickNonZero[36] = { arp, // 0 portaUp, // 1 portaDown, // 2 tonePorta, // 3 vibrato, // 4 tonePlusVol, // 5 vibratoPlusVol, // 6 tremolo, // 7 dummy, // 8 dummy, // 9 volume, // A dummy, // B dummy, // C dummy, // D E_Effects_TickNonZero, // E dummy, // F dummy, // G globalVolSlide, // H dummy, // I dummy, // J keyOffCmd, // K dummy, // L dummy, // M dummy, // N dummy, // O panningSlide, // P dummy, // Q doMultiRetrig, // R dummy, // S tremor, // T dummy, // U dummy, // V dummy, // W dummy, // X dummy, // Y dummy // Z }; static void doEffects(stmTyp *ch) // tick>0 effect handling { const uint8_t volKolEfx = ch->volKolVol >> 4; if (volKolEfx > 0) VJumpTab_TickNonZero[volKolEfx](ch); if ((ch->eff == 0 && ch->effTyp == 0) || ch->effTyp > 35) return; JumpTab_TickNonZero[ch->effTyp](ch, ch->eff); } static void getNextPos(void) { if (song.timer != 1) return; song.pattPos++; if (song.pattDelTime > 0) { song.pattDelTime2 = song.pattDelTime; song.pattDelTime = 0; } if (song.pattDelTime2 > 0) { song.pattDelTime2--; if (song.pattDelTime2 > 0) song.pattPos--; } if (song.pBreakFlag) { song.pBreakFlag = false; song.pattPos = song.pBreakPos; } if (song.pattPos >= song.pattLen || song.posJumpFlag) { song.pattPos = song.pBreakPos; song.pBreakPos = 0; song.posJumpFlag = false; song.songPos++; if (song.songPos >= song.len) song.songPos = song.repS; song.pattNr = song.songTab[(uint8_t)song.songPos]; song.pattLen = pattLens[(uint8_t)song.pattNr]; } } void mainPlayer(void) { if (musicPaused) return; bool tickZero = false; song.timer--; if (song.timer == 0) { song.timer = song.tempo; tickZero = true; } const bool readNewNote = tickZero && (song.pattDelTime2 == 0); if (readNewNote) { const tonTyp *pattPtr = nilPatternLine; if (patt[song.pattNr] != NULL) pattPtr = &patt[song.pattNr][song.pattPos * song.antChn]; stmTyp *c = stm; for (uint8_t i = 0; i < song.antChn; i++, c++, pattPtr++) { PMPTmpActiveChannel = i; // 8bb: for P_StartTone() getNewNote(c, pattPtr); fixaEnvelopeVibrato(c); } } else { stmTyp *c = stm; for (uint8_t i = 0; i < song.antChn; i++, c++) { PMPTmpActiveChannel = i; // 8bb: for P_StartTone() doEffects(c); fixaEnvelopeVibrato(c); } } getNextPos(); }