shithub: ft²

Download patch

ref: 813029d72b6b62ce7af6643860acbed73068f80e
parent: 82e421b0dd0fc4154271c390d02971674e2654cb
author: Olav Sørensen <olav.sorensen@live.no>
date: Fri Nov 21 17:49:27 EST 2025

Some different tweaks

--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -28,8 +28,7 @@
 static int32_t smpShiftValue;
 static uint32_t oldAudioFreq, tickTimeLenInt, randSeed = INITIAL_DITHER_SEED;
 static uint64_t tickTimeLenFrac;
-static float fSqrtPanningTable[256+1];
-static double dAudioNormalizeMul, dPrngStateL, dPrngStateR;
+static float fSqrtPanningTable[256+1], fAudioNormalizeMul, fPrngStateL, fPrngStateR;
 static voice_t voice[MAX_CHANNELS * 2];
 
 // globalized
@@ -103,11 +102,11 @@
 	amp = CLAMP(amp, 1, 32);
 	masterVol = CLAMP(masterVol, 0, 256);
 
-	double dAmp = (amp * masterVol) / (32.0 * 256.0);
+	float fAmp = (amp * masterVol) / (32.0f * 256.0f);
 	if (!bitDepth32Flag)
-		dAmp *= 32768.0;
+		fAmp *= 32768.0f;
 
-	dAudioNormalizeMul = dAmp;
+	fAudioNormalizeMul = fAmp;
 }
 
 void decreaseMasterVol(void)
@@ -194,17 +193,15 @@
 	// set sinc LUT pointers
 	if (config.interpolation == INTERPOLATION_SINC8)
 	{
-		fSinc_1 = fSinc8_1;
-		fSinc_2 = fSinc8_2;
-		fSinc_3 = fSinc8_3;
+		for (int32_t i = 0; i < SINC_KERNELS; i++)
+			fSinc[i] = fSinc8[i];
 
 		audio.sincInterpolation = true;
 	}
 	else if (config.interpolation == INTERPOLATION_SINC16)
 	{
-		fSinc_1 = fSinc16_1;
-		fSinc_2 = fSinc16_2;
-		fSinc_3 = fSinc16_3;
+		for (int32_t i = 0; i < SINC_KERNELS; i++)
+			fSinc[i] = fSinc16[i];
 
 		audio.sincInterpolation = true;
 	}
@@ -381,19 +378,16 @@
 
 		if (status & CF_UPDATE_PERIOD)
 		{
-			const double dVoiceHz = dPeriod2Hz(ch->finalPeriod);
+			v->delta = period2VoiceDelta(ch->finalPeriod);
 
-			// set voice delta
-			v->delta = (int64_t)((dVoiceHz * audio.dHz2MixDeltaMul) + 0.5); // Hz -> fixed-point delta (rounded)
 			if (audio.sincInterpolation)
 			{
-				// decide which sinc LUT to use according to the resampling ratio
 				if (v->delta <= sincRatio1)
-					v->fSincLUT = fSinc_1;
+					v->fSincLUT = fSinc[0];
 				else if (v->delta <= sincRatio2)
-					v->fSincLUT = fSinc_2;
+					v->fSincLUT = fSinc[1];
 				else
-					v->fSincLUT = fSinc_3;
+					v->fSincLUT = fSinc[2];
 			}
 		}
 
@@ -405,7 +399,7 @@
 void resetAudioDither(void)
 {
 	randSeed = INITIAL_DITHER_SEED;
-	dPrngStateL = dPrngStateR = 0.0;
+	fPrngStateL = fPrngStateR = 0.0f;
 }
 
 static inline int32_t random32(void)
@@ -420,26 +414,26 @@
 static void sendSamples16BitStereo(void *stream, uint32_t sampleBlockLength)
 {
 	int32_t out32;
-	double dOut, dPrng;
+	float fOut, fPrng;
 
 	int16_t *streamPtr16 = (int16_t *)stream;
 	for (uint32_t i = 0; i < sampleBlockLength; i++)
 	{
 		// left channel - 1-bit triangular dithering
-		dPrng = random32() * (1.0 / (UINT32_MAX+1.0)); // -0.5 .. 0.5
-		dOut = (double)audio.fMixBufferL[i] * dAudioNormalizeMul;
-		dOut = (dOut + dPrng) - dPrngStateL;
-		dPrngStateL = dPrng;
-		out32 = (int32_t)dOut;
+		fPrng = (float)random32() * (1.0f / (UINT32_MAX+1.0f)); // -0.5f .. 0.5f
+		fOut = audio.fMixBufferL[i] * fAudioNormalizeMul;
+		fOut = (fOut + fPrng) - fPrngStateL;
+		fPrngStateL = fPrng;
+		out32 = (int32_t)fOut;
 		CLAMP16(out32);
 		*streamPtr16++ = (int16_t)out32;
 
 		// right channel - 1-bit triangular dithering
-		dPrng = random32() * (1.0 / (UINT32_MAX+1.0)); // -0.5 .. 0.5
-		dOut = (double)audio.fMixBufferR[i] * dAudioNormalizeMul;
-		dOut = (dOut + dPrng) - dPrngStateR;
-		dPrngStateR = dPrng;
-		out32 = (int32_t)dOut;
+		fPrng = (float)random32() * (1.0f / (UINT32_MAX+1.0f)); // -0.5f .. 0.5f
+		fOut = audio.fMixBufferR[i] * fAudioNormalizeMul;
+		fOut = (fOut + fPrng) - fPrngStateR;
+		fPrngStateR = fPrng;
+		out32 = (int32_t)fOut;
 		CLAMP16(out32);
 		*streamPtr16++ = (int16_t)out32;
 
@@ -450,20 +444,20 @@
 
 static void sendSamples32BitFloatStereo(void *stream, uint32_t sampleBlockLength)
 {
-	double dOut;
+	float fOut;
 
 	float *fStreamPtr32 = (float *)stream;
 	for (uint32_t i = 0; i < sampleBlockLength; i++)
 	{
 		// left channel
-		dOut = (double)audio.fMixBufferL[i] * dAudioNormalizeMul;
-		dOut = CLAMP(dOut, -1.0, 1.0);
-		*fStreamPtr32++ = (float)dOut;
+		fOut = audio.fMixBufferL[i] * fAudioNormalizeMul;
+		fOut = CLAMP(fOut, -1.0f, 1.0f);
+		*fStreamPtr32++ = fOut;
 
 		// right channel
-		dOut = (double)audio.fMixBufferR[i] * dAudioNormalizeMul;
-		dOut = CLAMP(dOut, -1.0, 1.0);
-		*fStreamPtr32++ = (float)dOut;
+		fOut = audio.fMixBufferR[i] * fAudioNormalizeMul;
+		fOut = CLAMP(fOut, -1.0f, 1.0f);
+		*fStreamPtr32++ = fOut;
 
 		// clear what we read from the mixing buffer
 		audio.fMixBufferL[i] = audio.fMixBufferR[i] = 0.0f;
--- a/src/ft2_audio.h
+++ b/src/ft2_audio.h
@@ -50,7 +50,6 @@
 	uint64_t tickTime64, tickTime64Frac;
 
 	float *fMixBufferL, *fMixBufferR, fQuickVolRampSamplesMul, fSamplesPerTickIntMul;
-	double dHz2MixDeltaMul;
 
 	SDL_AudioDeviceID dev;
 	uint32_t wantFreq, haveFreq, wantSamples, haveSamples;
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -278,6 +278,8 @@
 	memset(&chSync, 0, sizeof (chSync));
 	memset(&song, 0, sizeof (song));
 
+	calcMiscReplayerVars();
+
 	// used for scopes and sampling position line (sampler screen)
 	for (int32_t i = 0; i < MAX_CHANNELS; i++)
 	{
@@ -317,7 +319,6 @@
 	editor.diskOpReadOnOpen = true;
 
 	audio.linearPeriodsFlag = true;
-	calcReplayerLogTab();
 
 #ifdef HAS_MIDI
 	midi.enable = true;
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -30,6 +30,8 @@
 #include "mixer/ft2_cubic_spline.h"
 #include "mixer/ft2_windowed_sinc.h"
 
+static uint64_t logTab[4*12*16], scopeLogTab[4*12*16], scopeDrawLogTab[4*12*16];
+static uint64_t amigaPeriodDiv, scopeAmigaPeriodDiv, scopeDrawAmigaPeriodDiv;
 static double dLogTab[4*12*16], dExp2MulTab[32];
 static bool bxxOverflow;
 static note_t nilPatternLine[MAX_CHANNELS];
@@ -232,7 +234,7 @@
 	return i+1;
 }
 
-double dLinearPeriod2Hz(int32_t period)
+double dPeriod2Hz(uint32_t period)
 {
 	period &= 0xFFFF; // just in case (actual period range is 0..65535)
 
@@ -239,29 +241,87 @@
 	if (period == 0)
 		return 0.0; // in FT2, a period of 0 results in 0Hz
 
-	const uint32_t invPeriod = ((12 * 192 * 4) - period) & 0xFFFF; // mask needed for FT2 period overflow quirk
+	if (audio.linearPeriodsFlag)
+	{
+		const uint32_t invPeriod = ((12 * 192 * 4) - period) & 0xFFFF; // mask needed for FT2 period overflow quirk
 
-	const uint32_t quotient  = invPeriod / (12 * 16 * 4);
-	const uint32_t remainder = invPeriod % (12 * 16 * 4);
+		const uint32_t quotient  = invPeriod / (12 * 16 * 4);
+		const uint32_t remainder = invPeriod % (12 * 16 * 4);
 
-	return dLogTab[remainder] * dExp2MulTab[(14-quotient) & 31]; // x = y >> ((14-quotient) & 31);
+		return dLogTab[remainder] * dExp2MulTab[(14-quotient) & 31]; // x = y >> ((14-quotient) & 31);
+	}
+	else
+	{
+		return (8363.0 * 1712.0) / (int32_t)period;
+	}
 }
 
-double dAmigaPeriod2Hz(int32_t period)
+uint64_t period2VoiceDelta(uint32_t period)
 {
 	period &= 0xFFFF; // just in case (actual period range is 0..65535)
 
 	if (period == 0)
-		return 0.0; // in FT2, a period of 0 results in 0Hz
+		return 0; // in FT2, a period of 0 results in 0Hz
 
-	return (8363.0 * 1712.0) / period;
+	if (audio.linearPeriodsFlag)
+	{
+		const uint32_t invPeriod = ((12 * 192 * 4) - period) & 0xFFFF; // mask needed for FT2 period overflow quirk
+
+		const uint32_t quotient  = invPeriod / (12 * 16 * 4);
+		const uint32_t remainder = invPeriod % (12 * 16 * 4);
+
+		return logTab[remainder] >> ((14-quotient) & 31);
+	}
+	else
+	{
+		return amigaPeriodDiv / period;
+	}
 }
 
-double dPeriod2Hz(int32_t period)
+uint64_t period2ScopeDelta(uint32_t period)
 {
-	return audio.linearPeriodsFlag ? dLinearPeriod2Hz(period) : dAmigaPeriod2Hz(period);
+	period &= 0xFFFF; // just in case (actual period range is 0..65535)
+
+	if (period == 0)
+		return 0; // in FT2, a period of 0 results in 0Hz
+
+	if (audio.linearPeriodsFlag)
+	{
+		const uint32_t invPeriod = ((12 * 192 * 4) - period) & 0xFFFF; // mask needed for FT2 period overflow quirk
+
+		const uint32_t quotient  = invPeriod / (12 * 16 * 4);
+		const uint32_t remainder = invPeriod % (12 * 16 * 4);
+
+		return scopeLogTab[remainder] >> ((14-quotient) & 31);
+	}
+	else
+	{
+		return scopeAmigaPeriodDiv / period;
+	}
 }
 
+uint64_t period2ScopeDrawDelta(uint32_t period)
+{
+	period &= 0xFFFF; // just in case (actual period range is 0..65535)
+
+	if (period == 0)
+		return 0; // in FT2, a period of 0 results in 0Hz
+
+	if (audio.linearPeriodsFlag)
+	{
+		const uint32_t invPeriod = ((12 * 192 * 4) - period) & 0xFFFF; // mask needed for FT2 period overflow quirk
+
+		const uint32_t quotient  = invPeriod / (12 * 16 * 4);
+		const uint32_t remainder = invPeriod % (12 * 16 * 4);
+
+		return scopeDrawLogTab[remainder] >> ((14-quotient) & 31);
+	}
+	else
+	{
+		return scopeDrawAmigaPeriodDiv / period;
+	}
+}
+
 // returns *exact* FT2 C-4 voice rate (depending on finetune, relativeNote and linear/Amiga period mode)
 double getSampleC4Rate(sample_t *s)
 {
@@ -386,15 +446,6 @@
 	}
 }
 
-void calcReplayerLogTab(void) // for linear period -> hz calculation
-{
-	for (int32_t i = 0; i < 32; i++)
-		dExp2MulTab[i] = 1.0 / exp2(i); // 1/(2^i)
-
-	for (int32_t i = 0; i < 4*12*16; i++)
-		dLogTab[i] = (8363.0 * 256.0) * exp2(i / (4.0 * 12.0 * 16.0));
-}
-
 void calcReplayerVars(int32_t audioFreq)
 {
 	assert(audioFreq > 0);
@@ -401,7 +452,12 @@
 	if (audioFreq <= 0)
 		return;
 
-	audio.dHz2MixDeltaMul = (double)MIXER_FRAC_SCALE / audioFreq;
+	const double logTabMul = (UINT32_MAX+1.0) / audioFreq;
+	for (int32_t i = 0; i < 4*12*16; i++)
+		logTab[i] = (uint64_t)round(dLogTab[i] * logTabMul);
+
+	amigaPeriodDiv = (uint64_t)round((MIXER_FRAC_SCALE * (1712.0*8363.0)) / audioFreq);
+
 	audio.quickVolRampSamples = (uint32_t)round(audioFreq / (1000.0 / FT2_QUICK_VOLRAMP_MILLISECONDS));
 	audio.fQuickVolRampSamplesMul = (float)(1.0 / audio.quickVolRampSamples);
 
@@ -2805,6 +2861,22 @@
 	freeQuadraticSplineTable();
 	freeCubicSplineTable();
 	freeWindowedSincTables();
+}
+
+void calcMiscReplayerVars(void)
+{
+	for (int32_t i = 0; i < 32; i++)
+		dExp2MulTab[i] = 1.0 / exp2(i); // 1/(2^i)
+
+	for (int32_t i = 0; i < 4*12*16; i++)
+	{
+		dLogTab[i] = (8363.0 * 256.0) * exp2(i / (4.0 * 12.0 * 16.0));
+		scopeLogTab[i] = (uint64_t)round(dLogTab[i] * (SCOPE_FRAC_SCALE / SCOPE_HZ));
+		scopeDrawLogTab[i] = (uint64_t)round(dLogTab[i] * (SCOPE_FRAC_SCALE / (C4_FREQ/2.0)));
+	}
+
+	scopeAmigaPeriodDiv = (uint64_t)round((SCOPE_FRAC_SCALE * (1712.0*8363.0)) / SCOPE_HZ);
+	scopeDrawAmigaPeriodDiv = (uint64_t)round((SCOPE_FRAC_SCALE * (1712.0*8363.0)) / (C4_FREQ/2.0));
 }
 
 bool setupReplayer(void)
--- a/src/ft2_replayer.h
+++ b/src/ft2_replayer.h
@@ -288,11 +288,11 @@
 
 void calcReplayerVars(int32_t rate);
 void setSampleC4Hz(sample_t *s, double dC4Hz);
-void calcReplayerLogTab(void); // for linear period -> hz calculation
 
-double dLinearPeriod2Hz(int32_t period);
-double dAmigaPeriod2Hz(int32_t period);
-double dPeriod2Hz(int32_t period);
+double dPeriod2Hz(uint32_t period);
+uint64_t period2VoiceDelta(uint32_t period);
+uint64_t period2ScopeDelta(uint32_t period);
+uint64_t period2ScopeDrawDelta(uint32_t period);
 
 int32_t getPianoKey(int32_t period, int8_t finetune, int8_t relativeNote); // for piano in Instr. Ed.
 void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch);
@@ -305,6 +305,7 @@
 
 void freeAllPatterns(void);
 void updateChanNums(void);
+void calcMiscReplayerVars(void);
 bool setupReplayer(void);
 void closeReplayer(void);
 void resetMusic(void);
--- a/src/mixer/ft2_cubic_spline.c
+++ b/src/mixer/ft2_cubic_spline.c
@@ -20,19 +20,19 @@
 	float *fPtr = fCubicSplineLUT;
 	for (int32_t i = 0; i < CUBIC_SPLINE_PHASES; i++)
 	{
-		const double x1 = i * (1.0 / CUBIC_SPLINE_PHASES);
-		const double x2 = x1 * x1; // x^2
-		const double x3 = x2 * x1; // x^3
+		const float x1 = (float)i * (1.0f / CUBIC_SPLINE_PHASES);
+		const float x2 = x1 * x1; // x^2
+		const float x3 = x2 * x1; // x^3
 
-		const double t1 = (x1 * -0.5) + (x2 *  1.0) + (x3 * -0.5);
-		const double t2 =               (x2 * -2.5) + (x3 *  1.5) + 1.0;
-		const double t3 = (x1 *  0.5) + (x2 *  2.0) + (x3 * -1.5);
-		const double t4 =               (x2 * -0.5) + (x3 *  0.5);
+		const float t1 = (x1 * -0.5f) + (x2 *  1.0f) + (x3 * -0.5f);
+		const float t2 =                (x2 * -2.5f) + (x3 *  1.5f) + 1.0f;
+		const float t3 = (x1 *  0.5f) + (x2 *  2.0f) + (x3 * -1.5f);
+		const float t4 =                (x2 * -0.5f) + (x3 *  0.5f);
 
-		*fPtr++ = (float)t1; // tap #1 at sample offset -1
-		*fPtr++ = (float)t2; // tap #2 at sample offset  0 (center)
-		*fPtr++ = (float)t3; // tap #3 at sample offset  1
-		*fPtr++ = (float)t4; // tap #4 at sample offset  2
+		*fPtr++ = t1; // tap #1 at sample offset -1
+		*fPtr++ = t2; // tap #2 at sample offset  0 (center)
+		*fPtr++ = t3; // tap #3 at sample offset  1
+		*fPtr++ = t4; // tap #4 at sample offset  2
 	}
 
 	/*
@@ -43,23 +43,23 @@
 	/*
 	for (int32_t i = 0; i < CUBIC_SPLINE_PHASES; i++)
 	{
-		const double x1 = i * (1.0 / CUBIC_SPLINE_PHASES);
-		const double x2 = x1 * x1; // x^2
-		const double x3 = x2 * x1; // x^3
+		const float x1 = i * (1.0f / CUBIC_SPLINE_PHASES);
+		const float x2 = x1 * x1; // x^2
+		const float x3 = x2 * x1; // x^3
 
-		const double t1 = (x1 *  (1.0/12.0)) + (x2 * -(1.0/ 6.0)) + (x3 *  (1.0/12.0));
-		const double t2 = (x1 * -(2.0/ 3.0)) + (x2 *  (5.0/ 4.0)) + (x3 * -(7.0/12.0));
-		const double t3 =                      (x2 * -(7.0/ 3.0)) + (x3 *  (4.0/ 3.0)) + 1.0;
-		const double t4 = (x1 *  (2.0/ 3.0)) + (x2 *  (5.0/ 3.0)) + (x3 * -(4.0/ 3.0));
-		const double t5 = (x1 * -(1.0/12.0)) + (x2 * -(1.0/ 2.0)) + (x3 *  (7.0/12.0));
-		const double t6 =                      (x2 *  (1.0/12.0)) + (x3 * -(1.0/12.0));
+		const float t1 = (x1 *  (1.0f/12.0f)) + (x2 * -(1.0f/ 6.0f)) + (x3 *  (1.0f/12.0f));
+		const float t2 = (x1 * -(2.0f/ 3.0f)) + (x2 *  (5.0f/ 4.0f)) + (x3 * -(7.0f/12.0f));
+		const float t3 =                        (x2 * -(7.0f/ 3.0f)) + (x3 *  (4.0f/ 3.0f)) + 1.0f;
+		const float t4 = (x1 *  (2.0f/ 3.0f)) + (x2 *  (5.0f/ 3.0f)) + (x3 * -(4.0f/ 3.0f));
+		const float t5 = (x1 * -(1.0f/12.0f)) + (x2 * -(1.0f/ 2.0f)) + (x3 *  (7.0f/12.0f));
+		const float t6 =                        (x2 *  (1.0f/12.0f)) + (x3 * -(1.0f/12.0f));
 
-		*fPtr++ = (float)t1; // tap #1 at sample offset -2
-		*fPtr++ = (float)t2; // tap #2 at sample offset -1
-		*fPtr++ = (float)t3; // tap #3 at sample offset  0 (center)
-		*fPtr++ = (float)t4; // tap #4 at sample offset  1
-		*fPtr++ = (float)t5; // tap #5 at sample offset  2
-		*fPtr++ = (float)t6; // tap #6 at sample offset  3
+		*fPtr++ = t1; // tap #1 at sample offset -2
+		*fPtr++ = t2; // tap #2 at sample offset -1
+		*fPtr++ = t3; // tap #3 at sample offset  0 (center)
+		*fPtr++ = t4; // tap #4 at sample offset  1
+		*fPtr++ = t5; // tap #5 at sample offset  2
+		*fPtr++ = t6; // tap #6 at sample offset  3
 	}
 	*/
 
--- a/src/mixer/ft2_cubic_spline.h
+++ b/src/mixer/ft2_cubic_spline.h
@@ -6,8 +6,8 @@
 
 #define CUBIC_SPLINE_WIDTH 4
 #define CUBIC_SPLINE_WIDTH_BITS 2 /* log2(CUBIC_SPLINE_WIDTH) */
-#define CUBIC_SPLINE_PHASES 8192
-#define CUBIC_SPLINE_PHASES_BITS 13 /* log2(CUBIC_SPLINE_PHASES) */
+#define CUBIC_SPLINE_PHASES 4096
+#define CUBIC_SPLINE_PHASES_BITS 12 /* log2(CUBIC_SPLINE_PHASES) */
 #define CUBIC_SPLINE_FRACSHIFT (MIXER_FRAC_BITS-(CUBIC_SPLINE_PHASES_BITS+CUBIC_SPLINE_WIDTH_BITS))
 #define CUBIC_SPLINE_FRACMASK ((CUBIC_SPLINE_WIDTH*CUBIC_SPLINE_PHASES)-CUBIC_SPLINE_WIDTH)
 
--- a/src/mixer/ft2_mix_macros.h
+++ b/src/mixer/ft2_mix_macros.h
@@ -200,7 +200,7 @@
 
 #define WINDOWED_SINC8_INTERPOLATION(s, f, scale) \
 { \
-	const float *t = v->fSincLUT + (((uint32_t)(f) >> SINC1_FRACSHIFT) & SINC1_FRACMASK); \
+	const float *t = v->fSincLUT + (((uint32_t)(f) >> SINC8_FRACSHIFT) & SINC8_FRACMASK); \
 	fSample = ((s[-3] * t[0]) + \
 	           (s[-2] * t[1]) + \
 	           (s[-1] * t[2]) + \
@@ -213,7 +213,7 @@
 
 #define WINDOWED_SINC16_INTERPOLATION(s, f, scale) \
 { \
-	const float *t = v->fSincLUT + (((uint32_t)(f) >> SINC2_FRACSHIFT) & SINC2_FRACMASK); \
+	const float *t = v->fSincLUT + (((uint32_t)(f) >> SINC16_FRACSHIFT) & SINC16_FRACMASK); \
 	fSample = (( s[-7] * t[0]) + \
 	           ( s[-6] * t[1]) + \
 	           ( s[-5] * t[2]) + \
--- a/src/mixer/ft2_quadratic_spline.c
+++ b/src/mixer/ft2_quadratic_spline.c
@@ -20,16 +20,16 @@
 	float *fPtr = fQuadraticSplineLUT;
 	for (int32_t i = 0; i < QUADRATIC_SPLINE_PHASES; i++)
 	{
-		const double x1 = i * (1.0 / QUADRATIC_SPLINE_PHASES);
-		const double x2 = x1 * x1; // x^2
+		const float x1 = (float)i * (1.0f / QUADRATIC_SPLINE_PHASES);
+		const float x2 = x1 * x1; // x^2
 
-		const double t1 = (x1 * -1.5) + (x2 *  0.5) + 1.0;
-		const double t2 = (x1 *  2.0) + (x2 * -1.0);
-		const double t3 = (x1 * -0.5) + (x2 *  0.5);
+		const float t1 = (x1 * -1.5f) + (x2 *  0.5f) + 1.0f;
+		const float t2 = (x1 *  2.0f) + (x2 * -1.0f);
+		const float t3 = (x1 * -0.5f) + (x2 *  0.5f);
 
-		*fPtr++ = (float)t1; // tap #1 at sample offset 0 (center)
-		*fPtr++ = (float)t2; // tap #2 at sample offset 1
-		*fPtr++ = (float)t3; // tap #3 at sample offset 2
+		*fPtr++ = t1; // tap #1 at sample offset 0 (center)
+		*fPtr++ = t2; // tap #2 at sample offset 1
+		*fPtr++ = t3; // tap #3 at sample offset 2
 	}
 
 	return true;
--- a/src/mixer/ft2_quadratic_spline.h
+++ b/src/mixer/ft2_quadratic_spline.h
@@ -5,8 +5,8 @@
 #include "ft2_mix.h" // MIXER_FRAC_BITS
 
 #define QUADRATIC_SPLINE_WIDTH 3
-#define QUADRATIC_SPLINE_PHASES 8192
-#define QUADRATIC_SPLINE_PHASES_BITS 13 /* log2(QUADRATIC_SPLINE_PHASES) */
+#define QUADRATIC_SPLINE_PHASES 4096
+#define QUADRATIC_SPLINE_PHASES_BITS 12 /* log2(QUADRATIC_SPLINE_PHASES) */
 #define QUADRATIC_SPLINE_FRACSHIFT (MIXER_FRAC_BITS-QUADRATIC_SPLINE_PHASES_BITS)
 
 extern float *fQuadraticSplineLUT;
--- a/src/mixer/ft2_windowed_sinc.c
+++ b/src/mixer/ft2_windowed_sinc.c
@@ -4,189 +4,139 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <math.h>
-#include "ft2_windowed_sinc.h" // SINCx_WIDTH, SINCx_PHASES
+#include "ft2_windowed_sinc.h"
 #include "../ft2_header.h" // PI
 #include "../ft2_video.h" // showErrorMsgBox()
 
+typedef struct
+{
+	float kaiserBeta, sincCutoff;
+} sincKernel_t;
+
 // globalized
-float *fSinc8_1 = NULL, *fSinc8_2 = NULL, *fSinc8_3 = NULL;
-float *fSinc16_1 = NULL, *fSinc16_2 = NULL, *fSinc16_3 = NULL;
-float *fSinc_1 = NULL, *fSinc_2 = NULL, *fSinc_3 = NULL;
+float *fSinc[SINC_KERNELS], *fSinc8[SINC_KERNELS], *fSinc16[SINC_KERNELS];
 uint64_t sincRatio1, sincRatio2;
 
-// zeroth-order modified Bessel function of the first kind (series approximation)
-static inline double besselI0(double z)
+static sincKernel_t sincKernelConfig[2][SINC_KERNELS] =
 {
-#define EPSILON (1E-12) /* verified: lower than this makes no change when LUT output is single-precision float */
+	/* Some notes on the Kaiser-Bessel beta parameter:
+	** Lower beta = less treble cut off, more aliasing (narrower mainlobe, stronger sidelobe)
+	** Higher beta = more treble cut off, less aliasing (wider mainlobe, weaker sidelobe)
+	**
+	** The 8-point kernel should not have a beta lower than around 9.2, as it results
+	** in audible ringing at very low resampling ratios (well below 1.0, that is).
+	*/
 
-	double s = 1.0, ds = 1.0, d = 2.0;
-	const double zz = z * z;
+	{ // -- settings for 8-point sinc --
+		// beta, cutoff
+		{   9.2f, 1.000f }, // kernel #1
+		{   8.5f, 0.750f }, // kernel #2
+		{   7.3f, 0.425f }  // kernel #3
+	},
 
+	{ // -- settings for 16-point sinc --
+		// beta, cutoff
+		{   8.6f, 1.000f }, // kernel #1
+		{   8.5f, 0.750f }, // kernel #2
+		{   7.3f, 0.425f }  // kernel #3
+	}
+};
+
+// zeroth-order modified Bessel function of the first kind (series approximation)
+static inline float besselI0(float z)
+{
+	float s = 1.0f, ds = 1.0f, d = 2.0f;
+	const float zz = z * z;
+
 	do
 	{
 		ds *= zz / (d * d);
 		s += ds;
-		d += 2.0;
+		d += 2.0f;
 	}
-	while (ds > s*EPSILON);
+	while (ds > s*(1E-7f));
 
 	return s;
 }
 
-static inline double sinc(double x)
+static inline float sinc(float x, const float cutoff)
 {
-	if (x == 0.0)
+	if (x == 0.0f)
 	{
-		return 1.0;
-	}
-	else
-	{
-		x *= PI;
-		return sin(x) / x;
-	}
-}
-
-static inline double sincWithCutoff(double x, const double cutoff)
-{
-	if (x == 0.0)
-	{
 		return cutoff;
 	}
 	else
 	{
-		x *= PI;
-		return sin(cutoff * x) / x;
+		x *= (float)PI;
+		return sinf(cutoff * x) / x;
 	}
 }
 
-static void generateWindowedSinc(float *fOutput, const int32_t filterWidth, const int32_t filterPhases, const double beta, const double cutoff)
+// note: numPoints must be 2^n!
+static void makeSincKernel(float *out, int32_t numPoints, int32_t numPhases, float beta, float cutoff)
 {
-	const int32_t filterWidthBits = (int32_t)log2(filterWidth);
-	const int32_t filterWidthMask = filterWidth - 1;
-	const int32_t filterCenter = (filterWidth / 2) - 1;
-	const double besselI0Beta = 1.0 / besselI0(beta);
-	const double phaseMul = 1.0 / filterPhases;
-	const double xMul = 1.0 / (filterWidth / 2);
+	const int32_t kernelLen = numPhases * numPoints;
+	const int32_t pointBits = (int32_t)log2(numPoints);
+	const int32_t pointMask = numPoints - 1;
+	const int32_t centerPoint = (numPoints / 2) - 1;
+	const float besselI0Beta = 1.0f / besselI0(beta);
+	const float phaseMul = 1.0f / numPhases;
+	const float xMul = 1.0f / (numPoints / 2);
 
-	if (cutoff < 1.0)
+	for (int32_t i = 0; i < kernelLen; i++)
 	{
-		// windowed-sinc with frequency cutoff
-		for (int32_t i = 0; i < filterPhases * filterWidth; i++)
-		{
-			const double x = ((i & filterWidthMask) - filterCenter) - ((i >> filterWidthBits) * phaseMul);
+		const float x = (float)((i & pointMask) - centerPoint) - ((float)(i >> pointBits) * phaseMul);
 
-			// Kaiser-Bessel window
-			const double n = x * xMul;
-			const double window = besselI0(beta * sqrt(1.0 - n * n)) * besselI0Beta;
+		// Kaiser-Bessel window
+		const float n = x * xMul;
+		const float window = besselI0(beta * sqrtf(1.0f - n * n)) * besselI0Beta;
 
-			fOutput[i] = (float)(sincWithCutoff(x, cutoff) * window);
-		}
+		out[i] = sinc(x, cutoff) * window;
 	}
-	else
-	{
-		// windowed-sinc with no frequency cutoff
-		for (int32_t i = 0; i < filterPhases * filterWidth; i++)
-		{
-			const double x = ((i & filterWidthMask) - filterCenter) - ((i >> filterWidthBits) * phaseMul);
-
-			// Kaiser-Bessel window
-			const double n = x * xMul;
-			const double window = besselI0(beta * sqrt(1.0 - n * n)) * besselI0Beta;
-
-			fOutput[i] = (float)(sinc(x) * window);
-		}
-	}
 }
 
 bool setupWindowedSincTables(void)
 {
-	fSinc8_1  = (float *)malloc(SINC1_WIDTH*SINC1_PHASES * sizeof (float));
-	fSinc8_2  = (float *)malloc(SINC1_WIDTH*SINC1_PHASES * sizeof (float));
-	fSinc8_3  = (float *)malloc(SINC1_WIDTH*SINC1_PHASES * sizeof (float));
-	fSinc16_1 = (float *)malloc(SINC2_WIDTH*SINC2_PHASES * sizeof (float));
-	fSinc16_2 = (float *)malloc(SINC2_WIDTH*SINC2_PHASES * sizeof (float));
-	fSinc16_3 = (float *)malloc(SINC2_WIDTH*SINC2_PHASES * sizeof (float));
-
-	if (fSinc8_1  == NULL || fSinc8_2  == NULL || fSinc8_3  == NULL ||
-		fSinc16_1 == NULL || fSinc16_2 == NULL || fSinc16_3 == NULL)
+	sincKernel_t *k;
+	for (int32_t i = 0; i < SINC_KERNELS; i++, k++)
 	{
-		showErrorMsgBox("Not enough memory!");
-		return false;
-	}
+		fSinc8[i]  = (float *)malloc( 8 * SINC_PHASES * sizeof (float));
+		fSinc16[i] = (float *)malloc(16 * SINC_PHASES * sizeof (float));
 
-	// LUT-select resampling ratios
-	const double ratio1 = 1.1875; // fSinc_1 if <=
-	const double ratio2 = 1.5; // fSinc_2 if <=, fSinc_3 if >
+		if (fSinc8[i] == NULL || fSinc16[i] == NULL)
+		{
+			showErrorMsgBox("Not enough memory!");
+			return false;
+		}
 
-	// calculate mixer delta limits for LUT-selector
-	sincRatio1 = (uint64_t)(ratio1 * MIXER_FRAC_SCALE);
-	sincRatio2 = (uint64_t)(ratio2 * MIXER_FRAC_SCALE);
+		k = &sincKernelConfig[0][i];
+		makeSincKernel(fSinc8[i], 8, SINC_PHASES, k->kaiserBeta, k->sincCutoff);
 
-	/* Kaiser-Bessel beta parameter
-	**
-	** Basically;
-	** Lower beta = less treble cut off, but more aliasing (shorter main lobe, more side lobe ripple)
-	** Higher beta = more treble cut off, but less aliasing (wider main lobe, less side lobe ripple)
-	**
-	** There simply isn't any optimal value here, it has to be tweaked to personal preference.
-	*/
-	const double b1 = 3.0 * M_PI; // alpha = 3.00 (beta = ~9.425)
-	const double b2 = 8.5;
-	const double b3 = 7.3;
+		k = &sincKernelConfig[1][i];
+		makeSincKernel(fSinc16[i], 16, SINC_PHASES, k->kaiserBeta, k->sincCutoff);
+	}
 
-	// sinc low-pass cutoff (could maybe use some further tweaking)
-	const double c1 = 1.000;
-	const double c2 = 0.500;
-	const double c3 = 0.425;
+	// resampling ratios for sinc kernel selection
+	sincRatio1 = (uint64_t)(1.1875 * MIXER_FRAC_SCALE); // fSinc[0] if <=
+	sincRatio2 = (uint64_t)(1.5000 * MIXER_FRAC_SCALE); // fSinc[1] if <=, else fSinc[2] if >
 
-	// 8 point
-	generateWindowedSinc(fSinc8_1,  SINC1_WIDTH, SINC1_PHASES, b1, c1);
-	generateWindowedSinc(fSinc8_2,  SINC1_WIDTH, SINC1_PHASES, b2, c2);
-	generateWindowedSinc(fSinc8_3,  SINC1_WIDTH, SINC1_PHASES, b3, c3);
-
-	// 16 point
-	generateWindowedSinc(fSinc16_1, SINC2_WIDTH, SINC2_PHASES, b1, c1);
-	generateWindowedSinc(fSinc16_2, SINC2_WIDTH, SINC2_PHASES, b2, c2);
-	generateWindowedSinc(fSinc16_3, SINC2_WIDTH, SINC2_PHASES, b3, c3);
-
 	return true;
 }
 
 void freeWindowedSincTables(void)
 {
-	if (fSinc8_1 != NULL)
+	for (int32_t i = 0; i < SINC_KERNELS; i++)
 	{
-		free(fSinc8_1);
-		fSinc8_1 = NULL;
-	}
+		if (fSinc8[i] != NULL)
+		{
+			free(fSinc8[i]);
+			fSinc8[i] = NULL;
+		}
 
-	if (fSinc8_2 != NULL)
-	{
-		free(fSinc8_2);
-		fSinc8_2 = NULL;
-	}
-
-	if (fSinc8_3 != NULL)
-	{
-		free(fSinc8_3);
-		fSinc8_3 = NULL;
-	}
-
-	if (fSinc16_1 != NULL)
-	{
-		free(fSinc16_1);
-		fSinc16_1 = NULL;
-	}
-
-	if (fSinc16_2 != NULL)
-	{
-		free(fSinc16_2);
-		fSinc16_2 = NULL;
-	}
-
-	if (fSinc16_3 != NULL)
-	{
-		free(fSinc16_3);
-		fSinc16_3 = NULL;
+		if (fSinc16[i] != NULL)
+		{
+			free(fSinc16[i]);
+			fSinc16[i] = NULL;
+		}
 	}
 }
--- a/src/mixer/ft2_windowed_sinc.h
+++ b/src/mixer/ft2_windowed_sinc.h
@@ -4,23 +4,19 @@
 #include <stdbool.h>
 #include "ft2_mix.h" // MIXER_FRAC_BITS
 
-#define SINC1_WIDTH 8
-#define SINC1_WIDTH_BITS 3 /* log2(SINC1_WIDTH) */
-#define SINC1_PHASES 8192
-#define SINC1_PHASES_BITS 13 /* log2(SINC1_PHASES) */
-#define SINC1_FRACSHIFT (MIXER_FRAC_BITS-(SINC1_PHASES_BITS+SINC1_WIDTH_BITS))
-#define SINC1_FRACMASK ((SINC1_WIDTH*SINC1_PHASES)-SINC1_WIDTH)
+#define SINC_KERNELS 3
+#define SINC_PHASES 4096
+#define SINC_PHASES_BITS 12 /* log2(SINC_PHASES) */
 
-#define SINC2_WIDTH 16
-#define SINC2_WIDTH_BITS 4 /* log2(SINC2_WIDTH) */
-#define SINC2_PHASES 8192
-#define SINC2_PHASES_BITS 13 /* log2(SINC2_PHASES) */
-#define SINC2_FRACSHIFT (MIXER_FRAC_BITS-(SINC2_PHASES_BITS+SINC2_WIDTH_BITS))
-#define SINC2_FRACMASK ((SINC2_WIDTH*SINC2_PHASES)-SINC2_WIDTH)
+#define SINC8_WIDTH_BITS 3 /* log2(8) */
+#define SINC8_FRACSHIFT (MIXER_FRAC_BITS-(SINC_PHASES_BITS+SINC8_WIDTH_BITS))
+#define SINC8_FRACMASK ((8*SINC_PHASES)-8)
 
-extern float *fSinc8_1, *fSinc8_2, *fSinc8_3;
-extern float *fSinc16_1, *fSinc16_2, *fSinc16_3;
-extern float *fSinc_1, *fSinc_2, *fSinc_3;
+#define SINC16_WIDTH_BITS 4 /* log2(16) */
+#define SINC16_FRACSHIFT (MIXER_FRAC_BITS-(SINC_PHASES_BITS+SINC16_WIDTH_BITS))
+#define SINC16_FRACMASK ((16*SINC_PHASES)-16)
+
+extern float *fSinc[SINC_KERNELS], *fSinc8[SINC_KERNELS], *fSinc16[SINC_KERNELS];
 extern uint64_t sincRatio1, sincRatio2;
 
 bool setupWindowedSincTables(void);
--- a/src/scopes/ft2_scopedraw.c
+++ b/src/scopes/ft2_scopedraw.c
@@ -31,14 +31,14 @@
 	int16_t *ptr16 = scopeIntrpLUT;
 	for (int32_t i = 0; i < SCOPE_INTRP_PHASES; i++)
 	{
-		const double x1 = i * (1.0 / SCOPE_INTRP_PHASES);
-		const double x2 = x1 * x1; // x^2
-		const double x3 = x2 * x1; // x^3
+		const float x1 = i * (1.0f / SCOPE_INTRP_PHASES);
+		const float x2 = x1 * x1; // x^2
+		const float x3 = x2 * x1; // x^3
 
-		const double t1 = (x1 * -(1.0/2.0)) + (x2 * (1.0/2.0)) + (x3 * -(1.0/6.0)) + (1.0/6.0);
-		const double t2 =                     (x2 *     -1.0 ) + (x3 *  (1.0/2.0)) + (2.0/3.0);
-		const double t3 = (x1 *  (1.0/2.0)) + (x2 * (1.0/2.0)) + (x3 * -(1.0/2.0)) + (1.0/6.0);
-		const double t4 =                                         x3 *  (1.0/6.0);
+		const float t1 = (x1 * -(1.0f/2.0f)) + (x2 * (1.0f/2.0f)) + (x3 * -(1.0f/6.0f)) + (1.0f/6.0f);
+		const float t2 =                       (x2 *      -1.0f ) + (x3 *  (1.0f/2.0f)) + (2.0f/3.0f);
+		const float t3 = (x1 *  (1.0f/2.0f)) + (x2 * (1.0f/2.0f)) + (x3 * -(1.0f/2.0f)) + (1.0f/6.0f);
+		const float t4 =                                             x3 *  (1.0f/6.0f);
 
 		// truncate, do not round!
 		*ptr16++ = (int16_t)(t1 * SCOPE_INTRP_SCALE); // tap #1 at sample offset -1
--- a/src/scopes/ft2_scopes.c
+++ b/src/scopes/ft2_scopes.c
@@ -490,10 +490,8 @@
 
 		if (status & CF_UPDATE_PERIOD)
 		{
-			const double dHz = dPeriod2Hz(ch->period);
-
-			sc->delta = (uint64_t)(dHz * (SCOPE_FRAC_SCALE / (double)SCOPE_HZ));
-			sc->drawDelta = (uint64_t)(dHz * (SCOPE_FRAC_SCALE / ((double)C4_FREQ/2.0)));
+			sc->delta = period2ScopeDelta(ch->period);
+			sc->drawDelta = period2ScopeDrawDelta(ch->period);
 		}
 
 		if (status & CS_TRIGGER_VOICE)
--