diff --git a/src/pt2_audio.c b/src/pt2_audio.c index 77667a9..885e815 100644 --- a/src/pt2_audio.c +++ b/src/pt2_audio.c @@ -116,19 +116,6 @@ void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac) tickTimeLenFrac = timeLenFrac; } -static void generateBpmTables(void) -{ - for (int32_t i = 32; i <= 255; i++) - { - const double dBpmHz = i / 2.5; - - audio.bpmTab[i-32] = audio.outputRate / dBpmHz; - audio.bpmTab28kHz[i-32] = PAT2SMP_HI_FREQ / dBpmHz; // PAT2SMP hi quality - audio.bpmTab22kHz[i-32] = PAT2SMP_LO_FREQ / dBpmHz; // PAT2SMP low quality - audio.bpmTabMod2Wav[i-32] = MOD2WAV_FREQ / dBpmHz; // MOD2WAV - } -} - void lockAudio(void) { if (dev != 0) @@ -788,6 +775,69 @@ void mixerCalcVoicePans(uint8_t stereoSeparation) // 0..100 (percentage) setVoicePan(3, panL); } +static double ciaBpm2Hz(int32_t bpm) +{ + if (bpm == 0) + return 0.0; + + const uint32_t ciaPeriod = 1773447 / bpm; // yes, PT truncates here + return (double)CIA_PAL_CLK / ciaPeriod; +} + +static void generateBpmTables(bool vblankTimingFlag) +{ + for (int32_t bpm = 32; bpm <= 255; bpm++) + { + double dHz; + + if (vblankTimingFlag) + dHz = AMIGA_PAL_VBLANK_HZ; + else + dHz = ciaBpm2Hz(bpm); + + audio.bpmTable[bpm-32] = audio.outputRate / dHz; + audio.bpmTable28kHz[bpm-32] = PAT2SMP_HI_FREQ / dHz; // PAT2SMP hi quality + audio.bpmTable22kHz[bpm-32] = PAT2SMP_LO_FREQ / dHz; // PAT2SMP low quality + audio.bpmTableMod2Wav[bpm-32] = MOD2WAV_FREQ / dHz; // MOD2WAV + } +} + +static void generateTickLengthTable(bool vblankTimingFlag) +{ + for (int32_t bpm = 32; bpm <= 255; bpm++) + { + double dHz; + + if (vblankTimingFlag) + dHz = AMIGA_PAL_VBLANK_HZ; + else + dHz = ciaBpm2Hz(bpm); + + // BPM -> Hz -> tick length for performance counter (syncing visuals to audio) + double dTimeInt; + double dTimeFrac = modf(editor.dPerfFreq / dHz, &dTimeInt); + const int32_t timeInt = (int32_t)dTimeInt; + + dTimeFrac = floor((UINT32_MAX+1.0) * dTimeFrac); // fractional part (scaled to 0..2^32-1) + + audio.tickLengthTable[bpm-32] = ((uint64_t)timeInt << 32) | (uint32_t)dTimeFrac; + } +} + +void updateReplayerTimingMode(void) +{ + const bool audioWasntLocked = !audio.locked; + if (audioWasntLocked) + lockAudio(); + + const bool vblankTimingMode = (editor.timingMode == TEMPO_MODE_VBLANK); + generateBpmTables(vblankTimingMode); + generateTickLengthTable(vblankTimingMode); + + if (audioWasntLocked) + unlockAudio(); +} + bool setupAudio(void) { SDL_AudioSpec want, have; @@ -822,12 +872,12 @@ bool setupAudio(void) audio.audioBufferSize = have.samples; audio.dPeriodToDeltaDiv = (double)PAULA_PAL_CLK / audio.outputRate; - generateBpmTables(); + updateReplayerTimingMode(); const int32_t lowestBPM = 32; - const int32_t pat2SmpMaxSamples = (int32_t)ceil(audio.bpmTab22kHz[lowestBPM-32]); - const int32_t mod2WavMaxSamples = (int32_t)ceil(audio.bpmTabMod2Wav[lowestBPM-32]); - const int32_t renderMaxSamples = (int32_t)ceil(audio.bpmTab[lowestBPM-32]); + const int32_t pat2SmpMaxSamples = (int32_t)ceil(audio.bpmTable22kHz[lowestBPM-32]); + const int32_t mod2WavMaxSamples = (int32_t)ceil(audio.bpmTableMod2Wav[lowestBPM-32]); + const int32_t renderMaxSamples = (int32_t)ceil(audio.bpmTable[lowestBPM-32]); const int32_t maxSamplesToMix = MAX(pat2SmpMaxSamples, MAX(mod2WavMaxSamples, renderMaxSamples)); @@ -850,24 +900,13 @@ bool setupAudio(void) ledFilterEnabled = false; calculateFilterCoeffs(); - audio.dSamplesPerTick = audio.bpmTab[125-32]; // BPM 125 + audio.dSamplesPerTick = audio.bpmTable[125-32]; // BPM 125 audio.dTickSampleCounter = 0.0; calcAudioLatencyVars(audio.audioBufferSize, audio.outputRate); - for (int32_t i = 32; i <= 255; i++) - { - const double dBpmHz = i / 2.5; - // BPM -> Hz -> tick length for performance counter (syncing visuals to audio) - double dTimeInt; - double dTimeFrac = modf(editor.dPerfFreq / dBpmHz, &dTimeInt); - const int32_t timeInt = (int32_t)dTimeInt; - dTimeFrac *= UINT32_MAX+1.0; // fractional part (scaled to 0..2^32-1) - - audio.tickTimeLengthTab[i-32] = ((uint64_t)timeInt << 32) | (uint32_t)dTimeFrac; - } audio.resetSyncTickTimeFlag = true; SDL_PauseAudioDevice(dev, false); diff --git a/src/pt2_audio.h b/src/pt2_audio.h index fedd495..ddd190e 100644 --- a/src/pt2_audio.h +++ b/src/pt2_audio.h @@ -9,7 +9,7 @@ typedef struct audio_t volatile bool locked, isSampling; bool forceMixerOff; - double bpmTab[256-32], bpmTab28kHz[256-32], bpmTab22kHz[256-32], bpmTabMod2Wav[256-32]; + double bpmTable[256-32], bpmTable28kHz[256-32], bpmTable22kHz[256-32], bpmTableMod2Wav[256-32]; uint32_t outputRate, audioBufferSize; double dPeriodToDeltaDiv; @@ -20,7 +20,7 @@ typedef struct audio_t // for audio/video syncing bool resetSyncTickTimeFlag; - uint64_t tickTimeLengthTab[224]; + uint64_t tickLengthTable[224]; } audio_t; typedef struct voice_t @@ -39,6 +39,8 @@ typedef struct voice_t const int8_t *syncTriggerData; } paulaVoice_t; +void updateReplayerTimingMode(void); + void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac); void resetCachedMixerPeriod(void); void resetAudioDithering(void); diff --git a/src/pt2_header.h b/src/pt2_header.h index 43ee052..753fd81 100644 --- a/src/pt2_header.h +++ b/src/pt2_header.h @@ -14,7 +14,7 @@ #include "pt2_unicode.h" #include "pt2_palette.h" -#define PROG_VER_STR "1.23" +#define PROG_VER_STR "1.24" #ifdef _WIN32 #define DIR_DELIMITER '\\' @@ -225,8 +225,8 @@ void modStop(void); void doStopIt(bool resetPlayMode); void playPattern(int8_t startRow); void modPlay(int16_t patt, int16_t order, int8_t row); -void modSetSpeed(uint8_t speed); -void modSetTempo(uint16_t bpm, bool doLockAudio); +void modSetSpeed(int32_t speed); +void modSetTempo(int32_t bpm, bool doLockAudio); void modFree(void); bool setupAudio(void); void audioClose(void); diff --git a/src/pt2_keyboard.c b/src/pt2_keyboard.c index 4806283..b25921f 100644 --- a/src/pt2_keyboard.c +++ b/src/pt2_keyboard.c @@ -699,17 +699,26 @@ void keyDownHandler(SDL_Scancode scancode, SDL_Keycode keycode) { if (keyb.leftCtrlPressed) { + const bool audioWasntLocked = !audio.locked; + if (audioWasntLocked) + lockAudio(); + editor.timingMode ^= 1; + updateReplayerTimingMode(); + if (editor.timingMode == TEMPO_MODE_VBLANK) { editor.oldTempo = song->currBPM; - modSetTempo(125, true); + modSetTempo(125, false); } else { - modSetTempo(editor.oldTempo, true); + modSetTempo(editor.oldTempo, false); } + if (audioWasntLocked) + unlockAudio(); + ui.updateSongTiming = true; } else if (keyb.shiftPressed) @@ -3452,10 +3461,7 @@ void handleKeyRepeat(SDL_Scancode scancode) break; } - // repeat keys at 49.92Hz (Amiga PAL) rate - const uint64_t keyRepeatDelta = (uint64_t)(((UINT32_MAX+1.0) * (AMIGA_PAL_VBLANK_HZ / (double)VBLANK_HZ)) + 0.5); - - keyb.repeatFrac += keyRepeatDelta; // 32.32 fixed-point counter + keyb.repeatFrac += keyb.repeatDelta; // 32.32 fixed-point counter if (keyb.repeatFrac > 0xFFFFFFFF) { keyb.repeatFrac &= 0xFFFFFFFF; diff --git a/src/pt2_main.c b/src/pt2_main.c index 1783fac..a8d5191 100644 --- a/src/pt2_main.c +++ b/src/pt2_main.c @@ -471,6 +471,10 @@ static bool initializeVars(void) editor.repeatKeyFlag = (SDL_GetModState() & KMOD_CAPS) ? true : false; + // set key repeat rate to 49.9204Hz (Amiga PAL vblank rate) + const double dVblankHzRatio = AMIGA_PAL_VBLANK_HZ / (double)VBLANK_HZ; + keyb.repeatDelta = (uint64_t)floor((UINT32_MAX+1.0) * dVblankHzRatio); + strcpy(editor.mixText, "MIX 01+02 TO 03"); // allocate some memory diff --git a/src/pt2_mod2wav.c b/src/pt2_mod2wav.c index 44ec54d..afc241c 100644 --- a/src/pt2_mod2wav.c +++ b/src/pt2_mod2wav.c @@ -157,7 +157,8 @@ bool renderToWav(char *fileName, bool checkIfFileExist) return false; } - const int32_t maxSamplesToMix = (int32_t)ceil(TICKS_PER_RENDER_CHUNK * audio.bpmTabMod2Wav[32-32]); // BPM 32, stereo + const int32_t lowestBPM = 32; + const int32_t maxSamplesToMix = (int32_t)ceil(TICKS_PER_RENDER_CHUNK * audio.bpmTableMod2Wav[lowestBPM-32]); // stereo mod2WavBuffer = (int16_t *)malloc(maxSamplesToMix * (2 * sizeof (int16_t))); if (mod2WavBuffer == NULL) diff --git a/src/pt2_module_loader.c b/src/pt2_module_loader.c index c86681e..f85c8a9 100644 --- a/src/pt2_module_loader.c +++ b/src/pt2_module_loader.c @@ -946,6 +946,7 @@ void setupLoadedMod(void) updateWindowTitle(MOD_NOT_MODIFIED); editor.timingMode = TEMPO_MODE_CIA; + updateReplayerTimingMode(); modSetSpeed(6); modSetTempo(song->header.initialTempo, false); // 125 for normal MODs, custom value for certain STK/UST MODs diff --git a/src/pt2_mouse.c b/src/pt2_mouse.c index da19fb8..46924ae 100644 --- a/src/pt2_mouse.c +++ b/src/pt2_mouse.c @@ -1048,7 +1048,7 @@ void sampleRepeatLengthDownButton(bool fast) void tempoUpButton(void) { - int16_t val; + int32_t val; if (editor.timingMode == TEMPO_MODE_VBLANK) return; @@ -1069,7 +1069,7 @@ void tempoUpButton(void) void tempoDownButton(void) { - int16_t val; + int32_t val; if (editor.timingMode == TEMPO_MODE_VBLANK) return; diff --git a/src/pt2_replayer.c b/src/pt2_replayer.c index 8143bbe..a632c3e 100644 --- a/src/pt2_replayer.c +++ b/src/pt2_replayer.c @@ -24,9 +24,9 @@ static bool posJumpAssert, pBreakFlag, updateUIPositions, modHasBeenPlayed; static int8_t pBreakPosition, oldRow, modPattern; -static uint8_t pattDelTime, setBPMFlag, lowMask = 0xFF, pattDelTime2, oldSpeed; +static uint8_t pattDelTime, lowMask = 0xFF, pattDelTime2; static int16_t modOrder, oldPattern, oldOrder; -static uint16_t modBPM, oldBPM; +static int32_t modBPM, oldBPM, oldSpeed, ciaSetBPM; static const uint8_t funkTable[16] = // EFx (FunkRepeat/InvertLoop) { @@ -53,10 +53,9 @@ int8_t *allocMemForAllSamples(void) return (int8_t *)calloc(1, allocLen); } -void modSetSpeed(uint8_t speed) +void modSetSpeed(int32_t speed) { - song->speed = speed; - song->currSpeed = speed; + song->currSpeed = song->speed = speed; song->tick = 0; } @@ -345,7 +344,7 @@ static void setSpeed(moduleChannel_t *ch) if (editor.timingMode == TEMPO_MODE_VBLANK || (ch->n_cmd & 0xFF) < 32) modSetSpeed(ch->n_cmd & 0xFF); else - setBPMFlag = ch->n_cmd & 0xFF; // CIA doesn't refresh its registers until the next interrupt, so change it later + ciaSetBPM = ch->n_cmd & 0xFF; // the CIA chip doesn't use its new timer value until the next interrupt, so change it later } else { @@ -976,6 +975,13 @@ bool intMusic(void) uint16_t *patt; moduleChannel_t *c; + // Quirk: CIA uses newly set timer values on the next interrupt, so handle BPM change now (ciaSetBPM was set on previous interrupt) + if (ciaSetBPM != -1) + { + modSetTempo(ciaSetBPM, false); + ciaSetBPM = -1; + } + if (editor.playMode != PLAY_MODE_PATTERN && modBPM >= 32 && modBPM <= 255) editor.musicTime64 += musicTimeTab64[modBPM-32]; // for playback counter (don't increase in "play/rec pattern" mode) @@ -1006,20 +1012,13 @@ bool intMusic(void) } } - // PT quirk: CIA refreshes its timer values on the next interrupt, so do the real tempo change here - if (setBPMFlag != 0) - { - modSetTempo(setBPMFlag, false); - setBPMFlag = 0; - } - if (editor.isWAVRendering && song->tick == 0) editor.rowVisitTable[(modOrder * MOD_ROWS) + song->row] = true; if (!editor.stepPlayEnabled) song->tick++; - if (song->tick >= song->speed || editor.stepPlayEnabled) + if ((uint32_t)song->tick >= (uint32_t)song->speed || editor.stepPlayEnabled) { song->tick = 0; @@ -1178,13 +1177,12 @@ void modSetPos(int16_t order, int16_t row) ui.updateStatusText = true; } -void modSetTempo(uint16_t bpm, bool doLockAudio) +void modSetTempo(int32_t bpm, bool doLockAudio) { if (bpm < 32) return; const bool audioWasntLocked = !audio.locked; - if (doLockAudio && audioWasntLocked) lockAudio(); @@ -1199,16 +1197,16 @@ void modSetTempo(uint16_t bpm, bool doLockAudio) double dSamplesPerTick; if (editor.isSMPRendering) - dSamplesPerTick = editor.pat2SmpHQ ? audio.bpmTab28kHz[bpm] : audio.bpmTab22kHz[bpm]; + dSamplesPerTick = editor.pat2SmpHQ ? audio.bpmTable28kHz[bpm] : audio.bpmTable22kHz[bpm]; else if (editor.isWAVRendering) - dSamplesPerTick = audio.bpmTabMod2Wav[bpm]; + dSamplesPerTick = audio.bpmTableMod2Wav[bpm]; else - dSamplesPerTick = audio.bpmTab[bpm]; + dSamplesPerTick = audio.bpmTable[bpm]; audio.dSamplesPerTick = dSamplesPerTick; // calculate tick time length for audio/video sync timestamp - const uint64_t tickTimeLen64 = audio.tickTimeLengthTab[bpm]; + const uint64_t tickTimeLen64 = audio.tickLengthTable[bpm]; const uint32_t tickTimeLen = tickTimeLen64 >> 32; const uint32_t tickTimeLenFrac = tickTimeLen64 & 0xFFFFFFFF; @@ -1225,10 +1223,9 @@ void modStop(void) if (song != NULL) { - for (int32_t i = 0; i < AMIGA_VOICES; i++) + moduleChannel_t *c = song->channels; + for (int32_t i = 0; i < AMIGA_VOICES; i++, c++) { - moduleChannel_t *c = &song->channels[i]; - c->n_wavecontrol = 0; c->n_glissfunk = 0; c->n_finetune = 0; @@ -1250,9 +1247,9 @@ void playPattern(int8_t startRow) pointerSetMode(POINTER_MODE_PLAY, DO_CARRY); audio.dTickSampleCounter = 0.0; // zero tick sample counter so that it will instantly initiate a tick - song->currRow = song->row = startRow & 0x3F; song->tick = song->speed; + ciaSetBPM = -1; editor.playMode = PLAY_MODE_PATTERN; editor.currMode = MODE_PLAY; @@ -1295,6 +1292,7 @@ void modPlay(int16_t patt, int16_t order, int8_t row) doStopIt(false); turnOffVoices(); audio.dTickSampleCounter = 0.0; // zero tick sample counter so that it will instantly initiate a tick + ciaSetBPM = -1; if (row != -1) { diff --git a/src/pt2_structs.h b/src/pt2_structs.h index 17561ae..fa17da5 100644 --- a/src/pt2_structs.h +++ b/src/pt2_structs.h @@ -87,7 +87,7 @@ typedef struct module_t bool loaded, modified; int8_t *sampleData; - volatile uint8_t tick, speed; + volatile int32_t tick, speed; int8_t row; // used for different things, so must not be internal to replayer @@ -98,8 +98,8 @@ typedef struct module_t // for pattern viewer int8_t currRow; - uint8_t currSpeed; - uint16_t currOrder, currPattern, currBPM; + int32_t currSpeed, currBPM; + uint16_t currOrder, currPattern; // for MOD2WAV progress bar uint32_t rowsCounter, rowsInTotal; @@ -111,7 +111,7 @@ typedef struct keyb_t bool shiftPressed, leftCtrlPressed, leftAltPressed; bool leftCommandPressed, leftAmigaPressed, keypadEnterPressed; uint8_t repeatCounter, delayCounter; - uint64_t repeatFrac; + uint64_t repeatDelta, repeatFrac; SDL_Scancode lastRepKey, lastKey; } keyb_t; @@ -175,9 +175,9 @@ typedef struct editor_t int16_t modulateSpeed; uint16_t metroSpeed, metroChannel, sampleVol, samplePos, chordLength; - uint16_t effectMacros[10], oldTempo, currPlayNote, vol1, vol2, lpCutOff, hpCutOff; + uint16_t effectMacros[10], currPlayNote, vol1, vol2, lpCutOff, hpCutOff; uint16_t smpRedoLoopStarts[MOD_SAMPLES], smpRedoLoopLengths[MOD_SAMPLES], smpRedoLengths[MOD_SAMPLES]; - int32_t modulatePos, modulateOffset, markStartOfs, markEndOfs, pat2SmpPos; + int32_t oldTempo, modulatePos, modulateOffset, markStartOfs, markEndOfs, pat2SmpPos; uint32_t vblankTimeLen, vblankTimeLenFrac; uint64_t musicTime64; double dPerfFreq, dPerfFreqMulMicro, *dPat2SmpBuf;