ref: 585a0447fc00c6fb6c4d1b4d87020daee0bf0191
parent: 7e106b7505fbd4682ba11b7854c92f15dc5ea44e
author: Olav Sørensen <olav.sorensen@live.no>
date: Sun Oct 4 10:24:49 EDT 2020
Pushed v1.24 code - BPM is now slightly more accurate to CIA specifications, and also more accurate in vblank (VBL) timing mode using the true Amiga PAL vblank rate. NOTE: We're speaking tiny variations in BPM here... - Vblank (VBL) timing mode: Fixed a bug with setting a speed of FF (255) on the first row. The tick duration would be about twice as long... - Small code cleanup
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -116,19 +116,6 @@
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 @@
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 @@
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 @@
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);
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -9,7 +9,7 @@
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 @@
// for audio/video syncing
bool resetSyncTickTimeFlag;
- uint64_t tickTimeLengthTab[224];
+ uint64_t tickLengthTable[224];
} audio_t;
typedef struct voice_t
@@ -38,6 +38,8 @@
int32_t syncTriggerLength;
const int8_t *syncTriggerData;
} paulaVoice_t;
+
+void updateReplayerTimingMode(void);
void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac);
void resetCachedMixerPeriod(void);
--- 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 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);
--- a/src/pt2_keyboard.c
+++ b/src/pt2_keyboard.c
@@ -699,17 +699,26 @@
{
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 @@
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;
--- a/src/pt2_main.c
+++ b/src/pt2_main.c
@@ -471,6 +471,10 @@
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
--- a/src/pt2_mod2wav.c
+++ b/src/pt2_mod2wav.c
@@ -157,7 +157,8 @@
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)
--- a/src/pt2_module_loader.c
+++ b/src/pt2_module_loader.c
@@ -946,6 +946,7 @@
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
--- a/src/pt2_mouse.c
+++ b/src/pt2_mouse.c
@@ -1048,7 +1048,7 @@
void tempoUpButton(void)
{
- int16_t val;
+ int32_t val;
if (editor.timingMode == TEMPO_MODE_VBLANK)
return;
@@ -1069,7 +1069,7 @@
void tempoDownButton(void)
{
- int16_t val;
+ int32_t val;
if (editor.timingMode == TEMPO_MODE_VBLANK)
return;
--- 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 @@
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 @@
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 @@
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,13 +1012,6 @@
}
}
- // 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;
@@ -1019,7 +1018,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
doStopIt(false);
turnOffVoices();
audio.dTickSampleCounter = 0.0; // zero tick sample counter so that it will instantly initiate a tick
+ ciaSetBPM = -1;
if (row != -1)
{
--- a/src/pt2_structs.h
+++ b/src/pt2_structs.h
@@ -87,7 +87,7 @@
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 @@
// 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 @@
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 @@
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;