Mercurial > codedump
diff win95kggui/dep/ft2play/pmp_main.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/pmp_main.c Sun Oct 01 03:48:43 2023 -0400 @@ -0,0 +1,1853 @@ +/* - main XM replayer - +** +** NOTE: Effect handling is slightly different because +** I've removed the channel muting logic. +** Muted channels would only process *some* effects, but +** since we can't mute channels, we don't care about this. +** +** In FT2, the only way to mute a channel is through the +** tracker itself, so this is not really needed in a replayer. +*/ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include "pmplay.h" +#include "pmp_mix.h" +#include "snd_masm.h" +#include "tables.h" + +#define MAX_FRQ 32000 +#define MAX_NOTES (10*12*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) +{ + // 8bb: reset vibrato position + if (!(ch->waveCtrl & 0x04)) + ch->vibPos = 0; + + /* + ** 8bb: + ** In FT2.00 .. FT2.09, if the sixth bit of "ch->waveCtrl" is set + ** (from effect E7x where x is $4..$7 or $C..$F) and you trigger a note, + ** the replayer interrupt will freeze / lock up. This is because of a + ** label bug in the original code, causing it to jump back to itself + ** indefinitely. + */ + + // 8bb: safely reset tremolo position + if (!(ch->waveCtrl & 0x40)) + ch->tremPos = 0; + + ch->retrigCnt = 0; + ch->tremorPos = 0; + + ch->envSustainActive = true; + + instrTyp *ins = ch->instrSeg; + + if (ins->envVTyp & ENV_ENABLED) + { + ch->envVCnt = 65535; + ch->envVPos = 0; + } + + if (ins->envPTyp & ENV_ENABLED) + { + ch->envPCnt = 65535; + ch->envPPos = 0; + } + + ch->fadeOutSpeed = ins->fadeOut; // 8bb: ranges 0..4095 (FT2 doesn't check if it's higher than 4095!) + + // 8bb: final fadeout range is in fact 0..32768, and not 0..65536 like the XM format doc says + 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) +{ + instrTyp *ins = ch->instrSeg; + + if (!(ins->envPTyp & ENV_ENABLED)) // 8bb: probably an FT2 bug + { + if (ch->envPCnt >= (uint16_t)ins->envPP[ch->envPPos][0]) + ch->envPCnt = ins->envPP[ch->envPPos][0]-1; + } + + if (ins->envVTyp & ENV_ENABLED) + { + 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; + } + + ch->envSustainActive = false; +} + +uint32_t getFrequenceValue(uint16_t period) // 8bb: converts period to 16.16fp resampling delta +{ + uint32_t delta; + + if (period == 0) + return 0; + + if (linearFrqTab) + { + const uint16_t invPeriod = (12 * 192 * 4) - period; // 8bb: this intentionally underflows uint16_t to be accurate to FT2 + + const uint32_t quotient = invPeriod / 768; + const uint32_t remainder = invPeriod % 768; + + const int32_t octShift = 14 - quotient; + + delta = (uint32_t)(((int64_t)logTab[remainder] * (int32_t)frequenceMulFactor) >> 24); + delta >>= (octShift & 31); // 8bb: added needed 32-bit bitshift mask + } + else + { + delta = frequenceDivFactor / (uint32_t)period; + } + + return delta; +} + +static void startTone(uint8_t ton, uint8_t effTyp, uint8_t eff, stmTyp *ch) +{ + if (ton == NOTE_KEYOFF) + { + 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 for safety + ch->sampleNr = smp; + + sampleTyp *s = &ins->samp[smp]; + ch->relTonNr = s->relTon; + + ton += ch->relTonNr; + if (ton >= 10*12) + return; + + ch->oldVol = s->vol; + ch->oldPan = s->pan; + + if (effTyp == 0x0E && (eff & 0xF0) == 0x50) // 8bb: EFx - Set Finetune + ch->fineTune = ((eff & 0x0F) << 4) - 128; + else + ch->fineTune = s->fine; + + if (ton != 0) + { + const uint16_t tmpTon = ((ton-1) << 4) + (((int8_t)ch->fineTune >> 3) + 16); // 8bb: 0..1935 + if (tmpTon < MAX_NOTES) // 8bb: tmpTon is *always* below MAX_NOTES here, this check is not needed + ch->outPeriod = ch->realPeriod = note2Period[tmpTon]; + } + + ch->status |= IS_Period + IS_Vol + IS_Pan + IS_NyTon + IS_QuickVol; + + if (effTyp == 9) // 8bb: 9xx - Set Sample Offset + { + 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: volume slide +static void vibrato2(stmTyp *ch); +static void tonePorta(stmTyp *ch, uint8_t param); + +static void dummy(stmTyp *ch, uint8_t param) +{ + return; + + (void)ch; + (void)param; +} + +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 > MAX_FRQ-1) // 8bb: FT2 bug, should've been unsigned comparison! + ch->realPeriod = MAX_FRQ-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; + + (void)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; + + (void)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; + + (void)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; + } + + (void)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: this updates the volume for all voices + c->status |= IS_Vol; + + (void)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 & ENV_ENABLED) + { + 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 & ENV_SUSTAIN) // 8bb: FT2 bug? (should probably have been "ins->envPTyp & ENV_ENABLED") + { + 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, this can't happen... + *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) +{ + (void)ch; + return; +} + +static void v_dummy2(stmTyp *ch, uint8_t *volKol) +{ + (void)ch; + (void)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 > MAX_FRQ-1) // 8bb: FT2 bug, should've been unsigned comparison! + newPeriod = MAX_FRQ-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) // 8bb: Set Volume (volume column) + { + ch->outVol = ch->volKolVol - 0x10; + ch->realVol = ch->outVol; + } + else if (ch->volKolVol >= 0xC0 && ch->volKolVol <= 0xCF) // 8bb: Set Panning (volume column) + { + 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; + + // 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) // 8bb: Rxy - Multi Retrig + { + 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 == NOTE_KEYOFF) + { + keyOff(ch); + } + else + { + const uint16_t portaTmp = (((p->ton-1) + ch->relTonNr) << 4) + (((int8_t)ch->fineTune >> 3) + 16); + 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 != NOTE_KEYOFF) + 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 (0xy) running, set period back + { + ch->outPeriod = ch->realPeriod; + ch->status |= IS_Period; + } + } + else + { + // 8bb: if we have a vibrato (4xy/6xy) 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)) + { + 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) // 8bb: check for EDx (Note Delay) and E90 (Retrigger Note) + { + if (p->eff >= 0xD1 && p->eff <= 0xDF) // 8bb: ED1..EDF (Note Delay) + return; + else if (p->eff == 0x90) // 8bb: E90 (Retrigger Note) + checkEfx = false; + } + + if (checkEfx) + { + if ((ch->volKolVol & 0xF0) == 0xF0) // 8bb: Portamento (volume column) + { + 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) // 8bb: Portamento (3xx/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) // 8bb: K00 (Key Off - 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 == NOTE_KEYOFF) + keyOff(ch); + else + startTone(p->ton, p->effTyp, p->eff, ch); + + if (inst > 0) + { + retrigVolume(ch); + if (p->ton != NOTE_KEYOFF) + 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; + + 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 & ENV_ENABLED) + { + envDidInterpolate = false; + envPos = ch->envVPos; + + if (++ch->envVCnt == ins->envVP[envPos][0]) + { + ch->envVAmp = ins->envVP[envPos][1] << 8; + + envPos++; + if (ins->envVTyp & ENV_LOOP) + { + envPos--; + + if (envPos == ins->envVRepE) + { + if (!(ins->envVTyp & ENV_SUSTAIN) || 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 & ENV_SUSTAIN) && 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: this updates vol on 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 & ENV_ENABLED) + { + envDidInterpolate = false; + envPos = ch->envPPos; + + if (++ch->envPCnt == ins->envPP[envPos][0]) + { + ch->envPAmp = ins->envPP[envPos][1] << 8; + + envPos++; + if (ins->envPTyp & ENV_LOOP) + { + envPos--; + + if (envPos == ins->envPRepE) + { + if (!(ins->envPTyp & ENV_SUSTAIN) || 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 & ENV_SUSTAIN) && 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 >= MAX_FRQ) + tmpPeriod = 0; // 8bb: yes, FT2 does this (!) + + ch->finalPeriod = tmpPeriod; + ch->status |= IS_Period; + } + else + { + ch->finalPeriod = ch->outPeriod; + } +} + +// 8bb: converts period to note number, for arpeggio and portamento (in semitone-slide mode) +static uint16_t relocateTon(uint16_t period, uint8_t arpNote, stmTyp *ch) +{ + int32_t tmpPeriod; + + const int32_t fineTune = ((int8_t)ch->fineTune >> 3) + 16; + + /* 8bb: FT2 bug, should've been 10*12*16. Notes above B-7 (95) will have issues. + ** You can only achieve such high notes by having a high relative note value + ** in the sample. + */ + int32_t hiPeriod = 8*12*16; + + int32_t loPeriod = 0; + + for (int32_t i = 0; i < 8; i++) + { + tmpPeriod = (((loPeriod + hiPeriod) >> 1) & ~15) + fineTune; + + int32_t lookUp = tmpPeriod - 8; + if (lookUp < 0) + lookUp = 0; // 8bb: safety fix (C-0 w/ ftune <= -65). This buggy read seems to return 0 in FT2 (TODO: verify) + + if (period >= note2Period[lookUp]) + hiPeriod = (tmpPeriod - fineTune) & ~15; + else + loPeriod = (tmpPeriod - fineTune) & ~15; + } + + tmpPeriod = loPeriod + fineTune + (arpNote << 4); + if (tmpPeriod >= (8*12*16+15)-1) // 8bb: FT2 bug, should've been 10*12*16+16 (also notice the +2 difference) + tmpPeriod = (8*12*16+16)-1; + + return note2Period[tmpPeriod]; +} + +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) +{ + /* 8bb: The original arpTab table only supports 16 ticks, so it can and will overflow. + ** I have added overflown values to the table so that we can handle up to 256 ticks. + ** The added overflow entries are accurate to the overflow-read in FT2.08/FT2.09. + */ + const uint8_t tick = arpTab[song.timer & 0xFF]; + + 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 > MAX_FRQ-1) // 8bb: FT2 bug, should've been unsigned comparison! + ch->realPeriod = MAX_FRQ-1; + + ch->outPeriod = ch->realPeriod; + ch->status |= IS_Period; +} + +static void tonePorta(stmTyp *ch, uint8_t param) // 8bb: param is a placeholder (not used) +{ + 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 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: 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: this updates the volume for all voices + 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) // 8bb: do we have an instrument number? + retrigVolume(ch); + + retrigEnvelopeVibrato(ch); + + if (ch->volKolVol >= 0x10 && ch->volKolVol <= 0x50) // 8bb: Set Volume (volume column) + { + ch->outVol = ch->volKolVol - 16; + ch->realVol = ch->outVol; + } + else if (ch->volKolVol >= 0xC0 && ch->volKolVol <= 0xCF) // 8bb: Set Panning (volume column) + { + 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) +{ + 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); + } + } + + if (song.timer == 1) + getNextPos(); +}