shithub: ft2-clone

Download patch

ref: 028871c49c340acc62c3ca8771d2eadc5b4cbc75
parent: e8a29beb7a6ba80b8a6596010bee8cdfd8a24421
author: Olav Sørensen <olav.sorensen@live.no>
date: Tue Jun 23 16:06:27 EDT 2020

Pushed v1.26 code

- Song BPM is now more accurate (it used to be slightly off for some BPMs)
- Arpeggio is now 100% FT2-correct for ticks 31..99 (speed 31..99 is only
  obtainable by hex-editing XMs).
- Fixed compiling error on ARM CPUs
- Code cleanup

--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -161,7 +161,8 @@
 	if (bpm > MAX_BPM)
 		return;
 
-	audio.samplesPerTick = audio.speedValTab[bpm];
+	audio.dSamplesPerTick = audio.dSpeedValTab[bpm];
+	audio.samplesPerTick = (int32_t)(audio.dSamplesPerTick + 0.5);
 
 	// get tick time length for audio/video sync timestamp
 	const uint64_t tickTimeLen64 = audio.tickTimeLengthTab[bpm];
@@ -579,13 +580,13 @@
 }
 
 // used for song-to-WAV renderer
-uint32_t mixReplayerTickToBuffer(uint8_t *stream, uint8_t bitDepth)
+void mixReplayerTickToBuffer(uint32_t samplesToMix, uint8_t *stream, uint8_t bitDepth)
 {
 	voice_t *v, *r;
 
-	assert(audio.speedVal <= MAX_WAV_RENDER_SAMPLES_PER_TICK);
-	memset(audio.mixBufferL, 0, audio.samplesPerTick * sizeof (int32_t));
-	memset(audio.mixBufferR, 0, audio.samplesPerTick * sizeof (int32_t));
+	assert(samplesToMix <= MAX_WAV_RENDER_SAMPLES_PER_TICK);
+	memset(audio.mixBufferL, 0, samplesToMix * sizeof (int32_t));
+	memset(audio.mixBufferR, 0, samplesToMix * sizeof (int32_t));
 
 	// mix channels
 	v = voice; // normal voices
@@ -594,8 +595,8 @@
 	for (int32_t i = 0; i < song.antChn; i++, v++, r++)
 	{
 		// call the mixing routine currently set for the voice
-		if (v->mixRoutine != NULL) v->mixRoutine(v, audio.samplesPerTick); // mix normal voice
-		if (r->mixRoutine != NULL) r->mixRoutine(r, audio.samplesPerTick); // mix volume ramp voice
+		if (v->mixRoutine != NULL) v->mixRoutine(v, samplesToMix); // mix normal voice
+		if (r->mixRoutine != NULL) r->mixRoutine(r, samplesToMix); // mix volume ramp voice
 	}
 
 	// normalize mix buffer and send to audio stream
@@ -602,16 +603,14 @@
 	if (bitDepth == 16)
 	{
 		if (config.specialFlags2 & DITHERED_AUDIO)
-			sendSamples16BitDitherStereo(stream, audio.samplesPerTick, 2);
+			sendSamples16BitDitherStereo(stream, samplesToMix, 2);
 		else
-			sendSamples16BitStereo(stream, audio.samplesPerTick, 2);
+			sendSamples16BitStereo(stream, samplesToMix, 2);
 	}
 	else
 	{
-		sendSamples24BitStereo(stream, audio.samplesPerTick, 2);
+		sendSamples24BitStereo(stream, samplesToMix, 2);
 	}
-
-	return audio.samplesPerTick;
 }
 
 int32_t pattQueueReadSize(void)
@@ -930,9 +929,6 @@
 
 static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len)
 {
-	assert(len < 65536); // limitation in mixer
-	assert(pmpCountDiv > 0);
-
 	int32_t samplesLeft = len / pmpCountDiv;
 	if (samplesLeft <= 0)
 		return;
@@ -939,7 +935,7 @@
 
 	while (samplesLeft > 0)
 	{
-		if (audio.tickSampleCounter == 0)
+		if (audio.dTickSampleCounter <= 0.0)
 		{
 			// new replayer tick
 
@@ -952,23 +948,25 @@
 			mix_UpdateChannelVolPanFrq();
 			fillVisualsSyncBuffer();
 
-			audio.tickSampleCounter = audio.samplesPerTick;
+			audio.dTickSampleCounter += audio.dSamplesPerTick;
 
 			replayerBusy = false;
 		}
 
+		const int32_t remainingTick = (int32_t)ceil(audio.dTickSampleCounter);
+
 		int32_t samplesToMix = samplesLeft;
-		if (samplesToMix > audio.tickSampleCounter)
-			samplesToMix = audio.tickSampleCounter;
+		if (samplesToMix > remainingTick)
+			samplesToMix = remainingTick;
 
 		mixAudio(stream, samplesToMix, pmpChannels);
 		stream += samplesToMix * pmpCountDiv;
 
 		samplesLeft -= samplesToMix;
-		audio.tickSampleCounter -= samplesToMix;
+		audio.dTickSampleCounter -= samplesToMix;
 	}
 
-	(void)userdata;
+	(void)userdata; // make compiler not complain
 }
 
 static bool setupAudioBuffers(void)
@@ -1223,7 +1221,7 @@
 
 	stopAllScopes();
 
-	audio.tickSampleCounter = 0; // zero tick sample counter so that it will instantly initiate a tick
+	audio.dTickSampleCounter = 0.0; // zero tick sample counter so that it will instantly initiate a tick
 
 	calcReplayRate(audio.freq);
 
--- a/src/ft2_audio.h
+++ b/src/ft2_audio.h
@@ -50,12 +50,11 @@
 	bool linearFreqTable, rescanAudioDevicesSupported;
 	int32_t inputDeviceNum, outputDeviceNum, lastWorkingAudioFreq, lastWorkingAudioBits;
 	int32_t quickVolSizeVal, *mixBufferL, *mixBufferR, *mixBufferLUnaligned, *mixBufferRUnaligned;
-	int32_t rampQuickVolMul, rampSpeedValMul, speedValTab[MAX_BPM+1], rampSpeedValMulTab[MAX_BPM+1];
-	int32_t tickSampleCounter;
+	int32_t rampQuickVolMul, rampSpeedValMul, rampSpeedValMulTab[MAX_BPM+1];
 	uint32_t freq;
 	uint32_t audLatencyPerfValInt, audLatencyPerfValFrac, samplesPerTick, musicTimeSpeedVal;
 	uint64_t tickTime64, tickTime64Frac, tickTimeLengthTab[MAX_BPM+1];
-	double dAudioLatencyMs;
+	double dAudioLatencyMs, dSamplesPerTick, dTickSampleCounter, dSpeedValTab[MAX_BPM+1];
 	SDL_AudioDeviceID dev;
 	uint32_t wantFreq, haveFreq, wantSamples, haveSamples, wantChannels, haveChannels;
 } audio_t;
@@ -139,7 +138,7 @@
 void updateSendAudSamplesRoutine(bool lockMixer);
 void mix_SaveIPVolumes(void);
 void mix_UpdateChannelVolPanFrq(void);
-uint32_t mixReplayerTickToBuffer(uint8_t *stream, uint8_t bitDepth);
+void mixReplayerTickToBuffer(uint32_t samplesToMix, uint8_t *stream, uint8_t bitDepth);
 
 // in ft2_audio.c
 extern audio_t audio;
--- a/src/ft2_pattern_ed.c
+++ b/src/ft2_pattern_ed.c
@@ -2244,7 +2244,8 @@
 {
 	if (songPlaying)
 	{
-		uint32_t seconds = song.musicTime64 >> 32;
+		const uint32_t milliseconds = song.musicTime64 >> 32;
+		uint32_t seconds = milliseconds / 1000;
 
 		last_TimeH = seconds / 3600; seconds -= last_TimeH * 3600;
 		last_TimeM = seconds / 60;   seconds -= last_TimeM * 60;
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -27,7 +27,6 @@
 */
 
 // non-FT2 precalced stuff
-static uint32_t musicTimeTab[MAX_BPM+1];
 static double dPeriod2HzTab[65536], dLogTab[768], dAudioRateFactor;
 
 static bool bxxOverflow;
@@ -359,24 +358,21 @@
 	** but it doesn't take up a lot of RAM, so why not.
 	*/
 
-	audio.speedValTab[0] = 0;
-	musicTimeTab[0] = UINT32_MAX;
+	audio.dSpeedValTab[0] = 0.0;
 	audio.tickTimeLengthTab[0] = UINT64_MAX;
 	audio.rampSpeedValMulTab[0] = INT32_MAX;
 
-	const double dMul1 = (UINT32_MAX + 1.0) / audioFreq;
-	const double dMul2 = (editor.dPerfFreq / audioFreq) * (UINT32_MAX + 1.0);
-
-	for (int32_t i = 1; i <= MAX_BPM; i++)
+	const double dMul = (UINT32_MAX + 1.0) / audioFreq;
+	for (int32_t i = MIN_BPM; i <= MAX_BPM; i++)
 	{
-		const int32_t samplesPerTick = (int32_t)(((audioFreq * 2.5) / i) + 0.5); // rounded
-		audio.speedValTab[i] = samplesPerTick;
+		const double dBpmHz = i / 2.5;
+		const double dSamplesPerTick = audioFreq / dBpmHz;
+		const int32_t samplesPerTick = (int32_t)(dSamplesPerTick + 0.5); // rounded
 
-		// used for song playback counter (hh:mm:ss)
-		musicTimeTab[i] = (uint32_t)((samplesPerTick * dMul1) + 0.5);
+		audio.dSpeedValTab[i] = dSamplesPerTick;
 
 		// number of samples per tick -> tick length for performance counter (syncing visuals to audio)
-		audio.tickTimeLengthTab[i] = (uint64_t)(samplesPerTick * dMul2);
+		audio.tickTimeLengthTab[i] = (uint64_t)(dSamplesPerTick * dMul);
 
 		// for calculating volume ramp length for "tick" ramps
 		audio.rampSpeedValMulTab[i] = (int32_t)(((UINT32_MAX + 1.0) / samplesPerTick) + 0.5);
@@ -2100,8 +2096,8 @@
 		return;
 	}
 
-	assert(song.speed >= MIN_BPM && song.speed <= MAX_BPM);
-	song.musicTime64 += musicTimeTab[song.speed]; // for song playback counter (hh:mm:ss)
+	if (song.speed >= MIN_BPM && song.speed <= MAX_BPM)
+		song.musicTime64 += musicTimeTab64[song.speed]; // for song playback counter (hh:mm:ss)
 
 	readNewNote = false;
 
@@ -2780,7 +2776,7 @@
 	if (song.tempo == 0)
 		song.tempo = song.initialTempo;
 
-	audio.tickSampleCounter = 0; // zero tick sample counter so that it will instantly initiate a tick
+	audio.dTickSampleCounter = 0.0; // zero tick sample counter so that it will instantly initiate a tick
 
 	unlockMixerCallback();
 
--- a/src/ft2_tables.c
+++ b/src/ft2_tables.c
@@ -1006,3 +1006,182 @@
 	0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,
 	0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x40,0x1F,0x01,0x00,0x00,0x08,0x00,0x00,0x00
 };
+
+/* ----------------------------------------------------------------------- */
+/*                          MISCELLANEOUS TABLES                           */
+/* ----------------------------------------------------------------------- */
+
+/*
+** const double dBpmMs = 1000.0 / (bpm / 2.5);
+** x = (uint64_t)round((UINT32_MAX + 1.0) * dBpmMs);
+*/
+const uint64_t musicTimeTab64[MAX_BPM+1] =
+{
+	0x00000000000,0x9c400000000,0x4e200000000,0x34155555555,0x27100000000,0x1f400000000,
+	0x1a0aaaaaaab,0x16524924925,0x13880000000,0x115c71c71c7,0x0fa00000000,0x0e345d1745d,
+	0x0d055555555,0x0c04ec4ec4f,0x0b292492492,0x0a6aaaaaaab,0x09c40000000,0x0930f0f0f0f,
+	0x08ae38e38e4,0x0839435e50d,0x07d00000000,0x0770c30c30c,0x071a2e8ba2f,0x06cb21642c8,
+	0x0682aaaaaab,0x06400000000,0x06027627627,0x05c97b425ed,0x05949249249,0x05634f72c23,
+	0x05355555555,0x050a5294a53,0x04e20000000,0x04bc1f07c1f,0x04987878788,0x0476db6db6e,
+	0x04571c71c72,0x043914c1bad,0x041ca1af287,0x0401a41a41a,0x03e80000000,0x03cf9c18f9c,
+	0x03b86186186,0x03a23b88ee2,0x038d1745d17,0x0378e38e38e,0x036590b2164,0x03531057262,
+	0x03415555555,0x0330539782a,0x03200000000,0x03105050505,0x03013b13b14,0x02f2b78c135,
+	0x02e4bda12f7,0x02d745d1746,0x02ca4924925,0x02bdc11f704,0x02b1a7b9612,0x02a5f75270d,
+	0x029aaaaaaab,0x028fbcda3ac,0x0285294a529,0x027aebaebaf,0x02710000000,0x02676276276,
+	0x025e0f83e10,0x025503d2263,0x024c3c3c3c4,0x0243b5cc0ed,0x023b6db6db7,0x0233615a241,
+	0x022b8e38e39,0x0223f1f8fc8,0x021c8a60dd6,0x02155555555,0x020e50d7943,0x02077b03532,
+	0x0200d20d20d,0x01fa5440cf6,0x01f40000000,0x01edd3c0ca4,0x01e7ce0c7ce,0x01e1ed7e753,
+	0x01dc30c30c3,0x01d69696969,0x01d11dc4771,0x01cbc52640c,0x01c68ba2e8c,0x01c1702e05c,
+	0x01bc71c71c7,0x01b78f78f79,0x01b2c8590b2,0x01ae1b86e1c,0x01a9882b931,0x01a50d79436,
+	0x01a0aaaaaab,0x019c5f02a3a,0x019829cbc15,0x01940a57eb5,0x01900000000,0x018c0a237c3,
+	0x01882828283,0x01845979c95,0x01809d89d8a,0x017cf3cf3cf,0x01795bc609b,0x0175d4ef40a,
+	0x01725ed097b,0x016ef8f441c,0x016ba2e8ba3,0x01685c4093a,0x01652492492,0x0161fb78122,
+	0x015ee08fb82,0x015bd37a6f5,0x0158d3dcb09,0x0155e15e15e,0x0152fba9387,0x0150226b902,
+	0x014d5555555,0x014a9419637,0x0147de6d1d6,0x01453408534,0x014294a5295,0x01400000000,
+	0x013d75d75d7,0x013af5ebd7b,0x01388000000,0x013613d84f6,0x0133b13b13b,0x013157f05dd,
+	0x012f07c1f08,0x012cc07b302,0x012a81e9132,0x01284bda12f,0x01261e1e1e2,0x0123f8868a4,
+	0x0121dae6077,0x011fc510935,0x011db6db6db,0x011bb01d0cb,0x0119b0ad120,0x0117b864407,
+	0x0115c71c71c,0x0113dcb08d4,0x0111f8fc7e4,0x01101bdd2b9,0x010e45306eb,0x010c74d50c0,
+	0x010aaaaaaab,0x0108e691cd2,0x0107286bca2,0x0105701ac57,0x0103bd81a99,0x01021084211,
+	0x01006906907,0x00fec6ee105,0x00fd2a2067b,0x00fb9284067,0x00fa0000000,0x00f8727c066,
+	0x00f6e9e0652,0x00f56615fce,0x00f3e7063e7,0x00f26c9b26d,0x00f0f6bf3aa,0x00ef855d825,
+	0x00ee1861862,0x00ecafb74a4,0x00eb4b4b4b5,0x00e9eb0a7ac,0x00e88ee23b9,0x00e736c05eb,
+	0x00e5e293206,0x00e49249249,0x00e345d1746,0x00e1fd1b7af,0x00e0b81702e,0x00df76b4338,
+	0x00de38e38e4,0x00dcfe95ec3,0x00dbc7bc7bc,0x00da9448be4,0x00d9642c859,0x00d83759f23,
+	0x00d70dc370e,0x00d5e75bb8d,0x00d4c415c99,0x00d3a3e4e90,0x00d286bca1b,0x00d16c90c10,
+	0x00d05555555,0x00cf40feac7,0x00ce2f8151d,0x00cd20d20d2,0x00cc14e5e0a,0x00cb0bb207d,
+	0x00ca052bf5b,0x00c9014953a,0x00c80000000,0x00c701460cc,0x00c60511be2,0x00c50b59897,
+	0x00c41414141,0x00c31f3831f,0x00c22cbce4b,0x00c13c995a4,0x00c04ec4ec5,0x00bf63371ea,
+	0x00be79e79e8,0x00bd92ce418,0x00bcade304d,0x00bbcb1e0c0,0x00baea77a05,0x00ba0be82fa,
+	0x00b92f684be,0x00b854f0a9e,0x00b77c7a20e,0x00b6a5fda98,0x00b5d1745d1,0x00b4fed7750,
+	0x00b42e2049d,0x00b35f4852a,0x00b29249249,0x00b1c71c71c,0x00b0fdbc091,0x00b03621d52,
+	0x00af7047dc1,0x00aeac283ea,0x00ade9bd37a,0x00ad29011bb,0x00ac69ee584,0x00abac7f736,
+	0x00aaf0af0af,0x00aa3677d47,0x00a97dd49c3,0x00a8c6c0452,0x00a81135c81,0x00a75d30337,
+	0x00a6aaaaaab,0x00a5f9a0660,0x00a54a0cb1c,0x00a49beaee1,0x00a3ef368eb,0x00a343eb1a2,
+	0x00a29a0429a,0x00a1f17d68b,0x00a14a5294a,0x00a0a47f7c6,0x00a00000000,0x009f5cd0105,
+	0x009ebaebaec,0x009e1a4eecc,0x009d7af5ebd,0x009cdcdcdce,0x009c4000000,0x009ba45ba46,
+	0x009b09ec27b,0x009a70adf62,0x0099d89d89e,0x009941b76af,0x0098abf82ee,0x0098175c78b,
+	0x009783e0f84,0x0096f1826a4,0x0096603d981,0x0095d00f574,0x009540f4899,0x0094b2ea1c9,
+	0x009425ed098,0x009399fa550,0x00930f0f0f1,0x00928528528,0x0091fc43452,0x0091745d174,
+	0x0090ed7303b,0x009067824f8,0x008fe28849b,0x008f5e824b4,0x008edb6db6e,0x008e5947f8b,
+	0x008dd80e866,0x008d57bede8,0x008cd856890,0x008c59d3167,0x008bdc32204,0x008b5f71484,
+	0x008ae38e38e,0x008a6886a4c,0x0089ee5846a,0x00897500e13,0x0088fc7e3f2,0x008884ce32b,
+	0x00880dee95c,0x008797dd49c,0x00872298376,0x0086ae1d4e7,0x00863a6a860,0x0085c77ddc1,
+	0x00855555555,0x0084e3eefd7,0x00847348e69,0x00840361296,0x00839435e51,0x008325c53ef,
+	0x0082b80d62c,0x00824b0c821,0x0081dec0d4c,0x00817328987,0x00810842108,0x00809e0b863,
+	0x00803483483,0x007fcba7aaf,0x007f6377082,0x007efbefbf0,0x007e951033e,0x007e2ed6d06,
+	0x007dc942034,0x007d6450403,0x007d0000000,0x007c9c4fc03,0x007c393e033,0x007bd6c9501,
+	0x007b74f0329,0x007b13b13b1,0x007ab30afe7,0x007a52fc15f,0x0079f3831f4,0x0079949ebc5,
+	0x0079364d936,0x0078d88e4ee,0x00787b5f9d5,0x00781ec0313,0x0077c2aec12,0x0077672a07a,
+	0x00770c30c31,0x0076b1c1b59,0x007657dba52,0x0075fe7d5b6,0x0075a5a5a5a,0x00754d5354d,
+	0x0074f5853d6,0x00749e3a374,0x007447711dc,0x0073f128cfc,0x00739b602f6,0x0073461621f,
+	0x0072f149903,0x00729cf965f,0x00724924925,0x0071f5ca075,0x0071a2e8ba3,0x0071507fa33,
+	0x0070fe8dbd8,0x0070ad12073,0x00705c0b817,0x00700b79301,0x006fbb5a19c,0x006f6bad480,
+	0x006f1c71c72,0x006ecda6a5f,0x006e7f4af62,0x006e315dcbd,0x006de3de3de,0x006d96cb65b,
+	0x006d4a245f2,0x006cfde8489,0x006cb21642d,0x006c66ad711,0x006c1bacf91,0x006bd11402c,
+	0x006b86e1b87,0x006b3d1546b,0x006af3addc7,0x006aaaaaaab,0x006a620ae4c,0x006a19cdc03,
+	0x0069d1f2748,0x00698a783b7,0x0069435e50d,0x0068fca3f29,0x0068b648608,0x0068704adc9,
+	0x00682aaaaab,0x0067e56710a,0x0067a07f563,0x00675bf2c52,0x006717c0a8f,0x0066d3e84f0,
+	0x00669069069,0x00664d4220c,0x00660a72f05,0x0065c7fac9f,0x006585d903e,0x0065440cf64,
+	0x00650295fad,0x0064c1736d0,0x006480a4a9d,0x00644029101,0x00640000000,0x0063c028dba,
+	0x006380a3066,0x0063416de55,0x00630288df1,0x0062c3f35ba,0x006285acc4c,0x006247b4856,
+	0x00620a0a0a1,0x0061ccacc0d,0x00618f9c190,0x006152d7837,0x0061165e725,0x0060da30594,
+	0x00609e4cad2,0x006062b2e44,0x00602762762,0x005fec5adbc,0x005fb19b8f5,0x005f77240c4,
+	0x005f3cf3cf4,0x005f030a566,0x005ec96720c,0x005e9009aee,0x005e56f1827,0x005e1e1e1e2,
+	0x005de58f060,0x005dad43bf4,0x005d753bd02,0x005d3d76c02,0x005d05f417d,0x005cceb360d,
+	0x005c97b425f,0x005c60f5f30,0x005c2a7854f,0x005bf43ad9c,0x005bbe3d107,0x005b887e891,
+	0x005b52fed4c,0x005b1dbd859,0x005ae8ba2e9,0x005ab3f463e,0x005a7f6bba8,0x005a4b1fc88,
+	0x005a171024e,0x0059e33c679,0x0059afa4295,0x00597c47040,0x00594924925,0x0059163c6fc,
+	0x0058e38e38e,0x0058b1198b1,0x00587ede048,0x00584cdb446,0x00581b10ea9,0x0057e97e97f,
+	0x0057b823ee1,0x005787008f6,0x005756141f5,0x0057255e41d,0x0056f4de9bd,0x0056c494d30,
+	0x005694808de,0x005664a1739,0x005634f72c2,0x00560581606,0x0055d63fb9b,0x0055a731e26,
+	0x00557857858,0x005549b04ea,0x00551b3bea3,0x0054ecfa057,0x0054beea4e2,0x0054910c72c,
+	0x00546360229,0x005435e50d8,0x0054089ae41,0x0053db81578,0x0053ae9819b,0x005381dedd4,
+	0x00535555555,0x005328fb35c,0x0052fcd0330,0x0052d0d4022,0x0052a50658e,0x00527966ed8,
+	0x00524df5771,0x005222b1acf,0x0051f79b476,0x0051ccb1fef,0x0051a1f58d1,0x00517765ab9,
+	0x00514d0214d,0x005122ca83e,0x0050f8beb45,0x0050cede624,0x0050a5294a5,0x00507b9f29c,
+	0x0050523fbe3,0x0050290ac60,0x00500000000,0x004fd71f2b7,0x004fae68083,0x004f85da568,
+	0x004f5d75d76,0x004f353a4c1,0x004f0d27766,0x004ee53d18c,0x004ebd7af5f,0x004e95e0d14,
+	0x004e6e6e6e7,0x004e472391d,0x004e2000000,0x004df9037e4,0x004dd22dd23,0x004dab7ec1e,
+	0x004d84f613e,0x004d5e938f2,0x004d3856fb1,0x004d12401f9,0x004cec4ec4f,0x004cc682b3d,
+	0x004ca0dbb57,0x004c7b59935,0x004c55fc177,0x004c30c30c3,0x004c0bae3c6,0x004be6bd732,
+	0x004bc1f07c2,0x004b9d47235,0x004b78c1352,0x004b545e7e5,0x004b301ecc0,0x004b0c01ebd,
+	0x004ae807aba,0x004ac42fd9c,0x004aa07a44c,0x004a7ce6bbd,0x004a59750e4,0x004a36250be,
+	0x004a12f684c,0x0049efe9496,0x0049ccfd2a8,0x0049aa31f96,0x00498787878,0x004964fda6c,
+	0x00494294294,0x0049204ae19,0x0048fe21a29,0x0048dc183f7,0x0048ba2e8ba,0x004898645b1,
+	0x004876b981e,0x0048552dd48,0x004833c127c,0x0048127350c,0x0047f14424d,0x0047d03379d,
+	0x0047af4125a,0x00478e6cfea,0x00476db6db7,0x00474d1e92f,0x00472ca3fc6,0x00470c46ef3,
+	0x0046ec07433,0x0046cbe4d07,0x0046abdf6f4,0x00468bf6f85,0x00466c2b448,0x00464c7c2d0,
+	0x00462ce98b4,0x00460d7338f,0x0045ee19102,0x0045cedaeb0,0x0045afb8a42,0x004590b2164,
+	0x004571c71c7,0x004552f7920,0x00453443526,0x004515aa398,0x0044f72c235,0x0044d8c8ec3,
+	0x0044ba8070a,0x00449c528d6,0x00447e3f1f9,0x00446046046,0x00444267195,0x004424a23c3,
+	0x004406f74ae,0x0043e96623a,0x0043cbeea4e,0x0043ae90ad4,0x0043914c1bb,0x00437420cf3,
+	0x0043570ea74,0x00433a15834,0x00431d35430,0x0043006dc69,0x0042e3beee0,0x0042c72899e,
+	0x0042aaaaaab,0x00428e45014,0x004271f77ec,0x004255c2044,0x004239a4735,0x00421d9ead8,
+	0x004201b094b,0x0041e5da0af,0x0041ca1af28,0x0041ae732dd,0x004192e29f8,0x004177692a5,
+	0x00415c06b16,0x004140bb17d,0x00412586411,0x00410a6810a,0x0040ef606a6,0x0040d46f323,
+	0x0040b9944c4,0x00409ecf9cc,0x00408421084,0x00406988737,0x00404f05c31,0x00403498dc4,
+	0x00401a41a42,0x00400000000,0x003fe5d3d58,0x003fcbbd0a3,0x003fb1bb841,0x003f97cf292,
+	0x003f7df7df8,0x003f64358d9,0x003f4a8819f,0x003f30ef6b3,0x003f176b683,0x003efdfbf7f,
+	0x003ee4a101a,0x003ecb5a6c8,0x003eb228202,0x003e990a040,0x003e8000000,0x003e6709fc0,
+	0x003e4e27e02,0x003e3559948,0x003e1c9f019,0x003e03f80fe,0x003deb64a80,0x003dd2e4b2e,
+	0x003dba78195,0x003da21ec47,0x003d89d89d9,0x003d71a58df,0x003d59857f3,0x003d41785af,
+	0x003d297e0af,0x003d1196793,0x003cf9c18fa,0x003ce1ff388,0x003cca4f5e2,0x003cb2b1eb0,
+	0x003c9b26c9b,0x003c83ade4e,0x003c6c47277,0x003c54f27c5,0x003c3dafcea,0x003c267f09a,
+	0x003c0f6018a,0x003bf852e71,0x003be157609,0x003bca6d70e,0x003bb39503d,0x003b9cce055,
+	0x003b8618618,0x003b6f74049,0x003b58e0dac,0x003b425ed09,0x003b2bedd29,0x003b158dcd5,
+	0x003aff3eadb,0x003ae900608,0x003ad2d2d2d,0x003abcb5f1b,0x003aa6a9aa7,0x003a90adea4,
+	0x003a7ac29eb,0x003a64e7b54,0x003a4f1d1ba,0x003a3962bf9,0x003a23b88ee,0x003a0e1e77a,
+	0x0039f89467e,0x0039e31a4dd,0x0039cdb017b,0x0039b855b3e,0x0039a30b10f,0x00398dd01d7,
+	0x003978a4c81,0x00396388ffa,0x00394e7cb30,0x0039397fd12,0x00392492492,0x00390fb40a4,
+	0x0038fae503a,0x0038e62524c,0x0038d1745d1,0x0038bcd29c2,0x0038a83fd19,0x003893bbed3,
+	0x00387f46dec,0x00386ae0963,0x0038568903a,0x00384240171,0x00382e05c0c,0x003819d9f0f,
+	0x003805bc980,0x0037f1ada68,0x0037ddad0ce,0x0037c9babbd,0x0037b5d6a40,0x0037a200b65,
+	0x00378e38e39,0x00377a7f1cc,0x003766d3530,0x00375335775,0x00373fa57b1,0x00372c234f7,
+	0x003718aee5f,0x003705482fe,0x0036f1ef1ef,0x0036dea3a4b,0x0036cb65b2e,0x0036b8353b3,
+	0x0036a5122f9,0x003691fc81f,0x00367ef4244,0x00366bf908b,0x0036590b216,0x0036462a609,
+	0x00363356b89,0x003620901bb,0x00360dd67c9,0x0035fb29cd9,0x0035e88a016,0x0035d5f70ab,
+	0x0035c370dc3,0x0035b0f768d,0x00359e8aa36,0x00358c2a7ed,0x003579d6ee3,0x0035678fe4b,
+	0x00355555555,0x00354327338,0x00353105726,0x00351ef0057,0x00350ce6e01,0x0034fae9f5d,
+	0x0034e8f93a4,0x0034d714a10,0x0034c53c1dc,0x0034b36fa44,0x0034a1af287,0x00348ffa9e2,
+	0x00347e51f94,0x00346cb52df,0x00345b24304,0x0034499ef45,0x003438256e5,0x003426b7928,
+	0x00341555555,0x003403feab2,0x0033f2b3885,0x0033e173e17,0x0033d03fab2,0x0033bf16d9f,
+	0x0033adf9629,0x00339ce739d,0x00338be0547,0x00337ae4a76,0x003369f4278,0x0033590ec9c,
+	0x00334834835,0x00333765492,0x003326a1106,0x003315e7ce5,0x00330539783,0x0032f496034,
+	0x0032e3fd64f,0x0032d36f92b,0x0032c2ec81f,0x0032b274284,0x0032a2067b2,0x003291a3705,
+	0x0032814afd7,0x003270fd183,0x003260b9b68,0x00325080ce1,0x0032405254e,0x0032302e40e,
+	0x00322014880,0x00321005206,0x00320000000,0x0031f0051d1,0x0031e0146dd,0x0031d02de87,
+	0x0031c051833,0x0031b07f348,0x0031a0b6f2b,0x003190f8b43,0x003181446f8,0x0031719a1b3,
+	0x003161f9add,0x003152631e0,0x003142d6626,0x0031335371b,0x003123da42b,0x0031146acc3,
+	0x00310505050,0x0030f5a8e42,0x0030e656606,0x0030d70d70d,0x0030c7ce0c8,0x0030b8982a7,
+	0x0030a96bc1b,0x00309a48c99,0x00308b2f393,0x00307c1f07c,0x00306d182ca,0x00305e1a9f2,
+	0x00304f26569,0x0030403b4a7,0x00303159722,0x00302280c53,0x003013b13b1,0x003004eacb7,
+	0x002ff62d6de,0x002fe7791a1,0x002fd8cdc7a,0x002fca2b6e7,0x002fbb92062,0x002fad01869,
+	0x002f9e79e7a,0x002f8ffb213,0x002f81852b3,0x002f7317fd9,0x002f64b3906,0x002f5657dba,
+	0x002f4804d77,0x002f39ba7bf,0x002f2b78c13,0x002f1d3f9f8,0x002f0f0f0f1,0x002f00e7082,
+	0x002ef2c7830,0x002ee4b0781,0x002ed6a1dfa,0x002ec89bb22,0x002eba9de81,0x002eaca879e,
+	0x002e9ebb601,0x002e90d6934,0x002e82fa0bf,0x002e7525c2c,0x002e6759b07,0x002e5995cd9,
+	0x002e4bda12f,0x002e3e26795,0x002e307af98,0x002e22d78c4,0x002e153c2a8,0x002e07a8cd1,
+	0x002dfa1d6ce,0x002dec9a02f,0x002ddf1e884,0x002dd1aaf5c,0x002dc43f449,0x002db6db6db,
+	0x002da97f6a6,0x002d9c2b33b,0x002d8edec2c,0x002d819a10e,0x002d745d174,0x002d6727cf3,
+	0x002d59fa31f,0x002d4cd438d,0x002d3fb5dd4,0x002d329f189,0x002d258fe44,0x002d188839c,
+	0x002d0b88127,0x002cfe8f67f,0x002cf19e33c,0x002ce4b46f8,0x002cd7d214b,0x002ccaf71cf,
+	0x002cbe23820,0x002cb1573d8,0x002ca492492,0x002c97d49eb,0x002c8b1e37e,0x002c7e6f0e8,
+	0x002c71c71c7,0x002c65265b8,0x002c588cc59,0x002c4bfa548,0x002c3f6f024,0x002c32eac8d,
+	0x002c266da23,0x002c19f7885,0x002c0d88755,0x002c0120632,0x002bf4bf4bf,0x002be86529e,
+	0x002bdc11f70,0x002bcfc5ad9,0x002bc38047b,0x002bb741bfa,0x002bab0a0fa,0x002b9ed9320,
+	0x002b92af20f,0x002b868bd6c,0x002b7a6f4df,0x002b6e5980b,0x002b624a698,0x002b564202c,
+	0x002b4a4046f,0x002b3e45307,0x002b3250b9c,0x002b2662dd8,0x002b1a7b961,0x002b0e9ade2,
+	0x002b02c0b03,0x002af6ed06e,0x002aeb1fdcd,0x002adf592cc,0x002ad398f13,0x002ac7df24f,
+	0x002abc2bc2c,0x002ab07ec54,0x002aa4d8275,0x002a9937e3a,0x002a8d9df52,0x002a820a568,
+	0x002a767d02b,0x002a6af5f4a,0x002a5f75271,0x002a53fa950,0x002a4886396,0x002a3d180f2,
+	0x002a31b0115,0x002a264e3ad,0x002a1af286c,0x002a0f9cf02,0x002a044d720,0x0029f904078,
+	0x0029edc0abc,0x0029e28359d,0x0029d74c0ce,0x0029cc1ac01,0x0029c0ef6ea,0x0029b5ca13c,
+	0x0029aaaaaab,0x00299f912ea,0x0029947d9ae,0x0029896feac,0x00297e68198,0x00297366228,
+	0x0029686a011,0x00295d73b09,0x002952832c7,0x00294798700,0x00293cb376c,0x002931d43c2,
+	0x002926fabb8,0x00291c26f08,0x00291158d68,0x00290690690,0x0028fbcda3b,0x0028f11081f,
+	0x0028e658ff8,0x0028dba717d,0x0028d0fac68,0x0028c654075,0x0028bbb2d5c,0x0028b1172d9,
+	0x0028a6810a7,0x00289bf067f,0x0028916541f,0x002886df942,0x00287c5f5a3,0x002871e48ff,
+	0x0028676f312,0x00285cff39a,0x00285294a53,0x0028482f6fa,0x00283dcf94e,0x0028337510c,
+	0x0028291fdf2,0x00281ecffbe,0x00281485630,0x00280a40106
+};
--- a/src/ft2_tables.h
+++ b/src/ft2_tables.h
@@ -47,3 +47,5 @@
 extern const uint16_t scopeLenTab[16][32];
 
 extern const uint8_t defConfigData[CONFIG_FILE_SIZE];
+
+extern const uint64_t musicTimeTab64[MAX_BPM+1];
--- a/src/ft2_wav_renderer.c
+++ b/src/ft2_wav_renderer.c
@@ -265,7 +265,7 @@
 	return returnValue;
 }
 
-uint32_t dump_RenderTick(uint8_t *buffer) // returns bytes mixed
+void dump_RenderTick(uint32_t samplesPerTick, uint8_t *buffer)
 {
 	replayerBusy = true;
 
@@ -277,7 +277,7 @@
 
 	replayerBusy = false;
 
-	return mixReplayerTickToBuffer(buffer, WDBitDepth);
+	mixReplayerTickToBuffer(samplesPerTick, buffer, WDBitDepth);
 }
 
 static void updateVisuals(void)
@@ -304,7 +304,7 @@
 {
 	bool renderDone;
 	uint8_t *ptr8, loopCounter;
-	uint32_t samplesInChunk, tickSamples, sampleCounter;
+	uint32_t samplesInChunk, sampleCounter;
 	FILE *f;
 
 	(void)ptr;
@@ -325,6 +325,8 @@
 	renderDone = false;
 	loopCounter = 8;
 
+	double dTickSamples = audio.dSamplesPerTick;
+
 	editor.wavReachedEndFlag = false;
 	while (!renderDone)
 	{
@@ -340,16 +342,21 @@
 				break;
 			}
 
-			tickSamples = dump_RenderTick(ptr8) << 1; // *2 for stereo
+			int32_t tickSamples = (int32_t)dTickSamples;
+			dump_RenderTick(tickSamples, ptr8);
 
+			dTickSamples -= tickSamples; // keep fractional part
+			dTickSamples += audio.dSamplesPerTick;
+
+			tickSamples *= 2; // stereo
 			samplesInChunk += tickSamples;
-			sampleCounter  += tickSamples;
+			sampleCounter += tickSamples;
 
 			// increase buffer pointer
 			if (WDBitDepth == 16)
-				ptr8 += (tickSamples * sizeof (int16_t));
+				ptr8 += tickSamples * sizeof (int16_t);
 			else
-				ptr8 += (tickSamples * sizeof (float));
+				ptr8 += tickSamples * sizeof (float);
 
 			if (++loopCounter >= 8)
 			{
--- a/src/ft2_wav_renderer.h
+++ b/src/ft2_wav_renderer.h
@@ -22,7 +22,7 @@
 void showWavRenderer(void);
 void hideWavRenderer(void);
 void exitWavRenderer(void);
-uint32_t dump_RenderTick(uint8_t *buffer);
+void dump_RenderTick(uint32_t samplesPerTick, uint8_t *buffer);
 void pbWavRender(void);
 void pbWavExit(void);
 void pbWavFreqUp(void);