shithub: pt2-clone

Download patch

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;