shithub: ft²

Download patch

ref: 17c5a219af0c5622d1ec063ad21a222331fe1870
parent: ceccaa32be75e49c8fc0b27ff46770c668d853d7
author: Olav Sørensen <olav.sorensen@live.no>
date: Wed Nov 6 16:38:00 EST 2024

Some scope and interpolation fixes

--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -23,6 +23,7 @@
 #include "ft2_sample_ed.h"
 #include "ft2_diskop.h"
 #include "scopes/ft2_scopes.h"
+#include "scopes/ft2_scopedraw.h"
 #include "ft2_about.h"
 #include "ft2_pattern_ed.h"
 #include "ft2_module_loader.h"
@@ -367,6 +368,7 @@
 	freeTextBoxes();
 	freeMouseCursors();
 	freeBMPs();
+	freeScopeIntrpLUT();
 
 	if (editor.audioDevConfigFileLocationU != NULL)
 	{
--- a/src/mixer/ft2_cubic_spline.c
+++ b/src/mixer/ft2_cubic_spline.c
@@ -5,7 +5,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdlib.h>
-#include "ft2_cubic_spline.h"
+#include "ft2_cubic_spline.h" // CUBIC_SPLINE_TAPS, CUBIC_SPLINE_PHASES
 #include "../ft2_video.h" // showErrorMsgBox()
 
 float *fCubicSplineLUT = NULL; // globalized
--- a/src/mixer/ft2_gaussian.c
+++ b/src/mixer/ft2_gaussian.c
@@ -1,7 +1,10 @@
 /*
-** Super Nintendo SPC700 interpolation LUT generator.
-** This has long believed to have a Gaussian curve, but it doesn't.
+** Super Nintendo (SPC700) Gaussian interpolation LUT generator
 **
+** It was long believed that it uses a Gaussian curve, but it doesn't!
+** We still call it Gaussian interpolation in the FT2 clone though, so
+** that people recognize it.
+**
 ** Based on code by Mednafen and nocash:
 ** https://forums.nesdev.org/viewtopic.php?t=10586
 **
@@ -10,11 +13,18 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdlib.h>
-#include "ft2_gaussian.h"
+#include "ft2_gaussian.h" // GAUSSIAN_TAPS, GAUSSIAN_PHASES
+#include "../ft2_header.h" // PI
 #include "../ft2_video.h" // showErrorMsgBox()
 
-#define MY_PI 3.14159265358979323846264338327950288
+/*
+**  1.28 = Super Nintendo
+** 2.048 = Sony PlayStation (less aliasing on very low pitches)
+*/
+#define PI_MULTIPLIER 1.28
 
+#define TAP_SUM_SCALE 1.0
+
 float *fGaussianLUT = NULL; // globalized
 
 bool calcGaussianTable(void)
@@ -40,18 +50,18 @@
 		const double x4 = (0.5 + i4) * (1.0 / ((GAUSSIAN_PHASES*4)-1));
 
 		// Blackman window
-		const double w1 = (0.42 + (0.50 * cos(2.0 * MY_PI * x1)) + (0.08 * cos(4.0 * MY_PI * x1))) / x1;
-		const double w2 = (0.42 + (0.50 * cos(2.0 * MY_PI * x2)) + (0.08 * cos(4.0 * MY_PI * x2))) / x2;
-		const double w3 = (0.42 + (0.50 * cos(2.0 * MY_PI * x3)) + (0.08 * cos(4.0 * MY_PI * x3))) / x3;
-		const double w4 = (0.42 + (0.50 * cos(2.0 * MY_PI * x4)) + (0.08 * cos(4.0 * MY_PI * x4))) / x4;
+		const double w1 = (0.42 + (0.50 * cos(2.0 * PI * x1)) + (0.08 * cos(4.0 * PI * x1))) / x1;
+		const double w2 = (0.42 + (0.50 * cos(2.0 * PI * x2)) + (0.08 * cos(4.0 * PI * x2))) / x2;
+		const double w3 = (0.42 + (0.50 * cos(2.0 * PI * x3)) + (0.08 * cos(4.0 * PI * x3))) / x3;
+		const double w4 = (0.42 + (0.50 * cos(2.0 * PI * x4)) + (0.08 * cos(4.0 * PI * x4))) / x4;
 
-		const double t1 = sin(1.28 * MY_PI * x1) * w1;
-		const double t2 = sin(1.28 * MY_PI * x2) * w2;
-		const double t3 = sin(1.28 * MY_PI * x3) * w3;
-		const double t4 = sin(1.28 * MY_PI * x4) * w4;
+		const double t1 = sin(PI_MULTIPLIER * PI * x1) * w1;
+		const double t2 = sin(PI_MULTIPLIER * PI * x2) * w2;
+		const double t3 = sin(PI_MULTIPLIER * PI * x3) * w3;
+		const double t4 = sin(PI_MULTIPLIER * PI * x4) * w4;
 
-		// normalization value (assures unity gain when summing taps)
-		const double dScale = 1.0 / (t1 + t2 + t3 + t4);
+		// calculate normalization value (also assures unity gain when summing taps)
+		const double dScale = TAP_SUM_SCALE / (t1 + t2 + t3 + t4);
 
 		*fPtr++ = (float)(t1 * dScale);
 		*fPtr++ = (float)(t2 * dScale);
--- a/src/mixer/ft2_gaussian.h
+++ b/src/mixer/ft2_gaussian.h
@@ -6,7 +6,7 @@
 
 #define GAUSSIAN_TAPS 4
 #define GAUSSIAN_WIDTH_BITS 2 // log2(GAUSSIAN_TAPS)
-#define GAUSSIAN_PHASES 8192
+#define GAUSSIAN_PHASES 8192 /* originally 256 on SNES/PSX, but more is better! */
 #define GAUSSIAN_PHASES_BITS 13 // log2(GAUSSIAN_PHASES)
 #define GAUSSIAN_FSHIFT (MIXER_FRAC_BITS-(GAUSSIAN_PHASES_BITS+GAUSSIAN_WIDTH_BITS))
 #define GAUSSIAN_FMASK ((GAUSSIAN_TAPS*GAUSSIAN_PHASES)-GAUSSIAN_TAPS)
--- a/src/mixer/ft2_mix.c
+++ b/src/mixer/ft2_mix.c
@@ -9,7 +9,7 @@
 **       (Note: Mixing macros can be found in ft2_mix_macros.h)
 **
 ** Specifications:
-** - Interpolation: None, 2-tap linear, 4-tap cubic spline, 8-tap windowed-sinc, 16-tap windowed-sinc
+** - Interpolation: None, 2-tap linear, 4-tap "gaussian", 4-tap cubic hermite, 8-tap/16-tap windowed-sinc
 ** - FT2-styled linear volume ramping (can be turned off)
 ** - 32.32 fixed-point precision for resampling delta/position
 ** - 32-bit floating-point precision for mixing and interpolation
@@ -865,6 +865,7 @@
 static void mix8bLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int8_t *base, *smpPtr;
+	int8_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	uint32_t i, samplesToMix, samplesLeft;
@@ -873,6 +874,7 @@
 	GET_VOL
 	GET_MIXER_VARS
 	SET_BASE8
+	PREPARE_TAP_FIX8
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -880,22 +882,45 @@
 		LIMIT_MIX_NUM
 		samplesLeft -= samplesToMix;
 
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS
+			}
 		}
 
 		WRAP_LOOP
@@ -907,6 +932,7 @@
 static void mix8bBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int8_t *base, *revBase, *smpPtr;
+	int8_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	uint32_t i, samplesToMix, samplesLeft;
@@ -915,6 +941,7 @@
 	GET_VOL
 	GET_MIXER_VARS
 	SET_BASE8_BIDI
+	PREPARE_TAP_FIX8
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -923,22 +950,45 @@
 		samplesLeft -= samplesToMix;
 
 		START_BIDI
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS_BIDI
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS_BIDI
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS_BIDI
-			RENDER_8BIT_SMP_GINTRP
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP
+				INC_POS_BIDI
+			}
 		}
 		END_BIDI
 
@@ -1942,6 +1992,7 @@
 static void mix8bRampLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int8_t *base, *smpPtr;
+	int8_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
@@ -1951,6 +2002,7 @@
 	GET_VOL_RAMP
 	GET_MIXER_VARS_RAMP
 	SET_BASE8
+	PREPARE_TAP_FIX8
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -1958,28 +2010,56 @@
 		LIMIT_MIX_NUM
 		LIMIT_MIX_NUM_RAMP
 		samplesLeft -= samplesToMix;
-
-		for (i = 0; i < (samplesToMix & 3); i++)
+		
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+			}
 		}
 
 		WRAP_LOOP
@@ -1992,6 +2072,7 @@
 static void mix8bRampBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int8_t *base, *revBase, *smpPtr;
+	int8_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
@@ -2001,6 +2082,7 @@
 	GET_VOL_RAMP
 	GET_MIXER_VARS_RAMP
 	SET_BASE8_BIDI
+	PREPARE_TAP_FIX8
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -2010,27 +2092,55 @@
 		samplesLeft -= samplesToMix;
 
 		START_BIDI
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
-			RENDER_8BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_8BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
 		}
 		END_BIDI
 
@@ -2880,6 +2990,7 @@
 static void mix16bLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int16_t *base, *smpPtr;
+	int16_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	uint32_t i, samplesToMix, samplesLeft;
@@ -2888,6 +2999,7 @@
 	GET_VOL
 	GET_MIXER_VARS
 	SET_BASE16
+	PREPARE_TAP_FIX16
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -2895,22 +3007,45 @@
 		LIMIT_MIX_NUM
 		samplesLeft -= samplesToMix;
 
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS
+			}
 		}
 
 		WRAP_LOOP
@@ -2922,6 +3057,7 @@
 static void mix16bBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int16_t *base, *revBase, *smpPtr;
+	int16_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	uint32_t i, samplesToMix, samplesLeft;
@@ -2930,6 +3066,7 @@
 	GET_VOL
 	GET_MIXER_VARS
 	SET_BASE16_BIDI
+	PREPARE_TAP_FIX16
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -2938,22 +3075,45 @@
 		samplesLeft -= samplesToMix;
 
 		START_BIDI
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				INC_POS_BIDI
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS_BIDI
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS_BIDI
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS_BIDI
-			RENDER_16BIT_SMP_GINTRP
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP
+				INC_POS_BIDI
+			}
 		}
 		END_BIDI
 
@@ -3956,6 +4116,7 @@
 static void mix16bRampLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int16_t *base, *smpPtr;
+	int16_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
@@ -3965,6 +4126,7 @@
 	GET_VOL_RAMP
 	GET_MIXER_VARS_RAMP
 	SET_BASE16
+	PREPARE_TAP_FIX16
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -3973,27 +4135,55 @@
 		LIMIT_MIX_NUM_RAMP
 		samplesLeft -= samplesToMix;
 
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS
+			}
 		}
 
 		WRAP_LOOP
@@ -4006,6 +4196,7 @@
 static void mix16bRampBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int16_t *base, *revBase, *smpPtr;
+	int16_t *smpTapPtr;
 	float fSample, *fMixBufferL, *fMixBufferR;
 	int32_t position;
 	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
@@ -4015,6 +4206,7 @@
 	GET_VOL_RAMP
 	GET_MIXER_VARS_RAMP
 	SET_BASE16_BIDI
+	PREPARE_TAP_FIX16
 
 	samplesLeft = numSamples;
 	while (samplesLeft > 0)
@@ -4024,28 +4216,57 @@
 		samplesLeft -= samplesToMix;
 
 		START_BIDI
-		for (i = 0; i < (samplesToMix & 3); i++)
+		if (v->hasLooped) // the negative interpolation taps need a special case after the sample has looped once
 		{
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP_TAP_FIX
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
 		}
-		samplesToMix >>= 2;
-		for (i = 0; i < samplesToMix; i++)
+		else
 		{
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
-			RENDER_16BIT_SMP_GINTRP
-			VOLUME_RAMPING
-			INC_POS_BIDI
+			for (i = 0; i < (samplesToMix & 3); i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
+			samplesToMix >>= 2;
+			for (i = 0; i < samplesToMix; i++)
+			{
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+				RENDER_16BIT_SMP_GINTRP
+				VOLUME_RAMPING
+				INC_POS_BIDI
+			}
 		}
+
 		END_BIDI
 
 		WRAP_BIDI_LOOP
--- a/src/mixer/ft2_mix_macros.h
+++ b/src/mixer/ft2_mix_macros.h
@@ -175,26 +175,44 @@
 // through LUT: mixer/ft2_gaussian.c
 
 /* It may look like we are potentially going out of bounds while looking up the sample points,
-** but the sample data is actually padded on the right side, where correct tap are stored according
-** to loop mode (or no loop).
+** but the sample data is actually padded on both the left (negative) and right side, where correct tap
+** samples are stored according to loop mode (or no loop).
+**
+** There is also a second special case for the left edge (negative taps) after the sample has looped once.
 */
 
 #define GAUSSIAN_INTERPOLATION(s, f, scale) \
 { \
 	const float *t = fGaussianLUT + (((uint32_t)(f) >> GAUSSIAN_FSHIFT) & GAUSSIAN_FMASK); \
-	fSample = ((s[0] * t[0]) + \
-			   (s[1] * t[1]) + \
-			   (s[2] * t[2]) + \
-			   (s[3] * t[3])) * (1.0f / scale); \
+	fSample = ((s[-1] * t[0]) + \
+	           ( s[0] * t[1]) + \
+	           ( s[1] * t[2]) + \
+	           ( s[2] * t[3])) * (1.0f / scale); \
 }
 
 #define RENDER_8BIT_SMP_GINTRP \
-	GAUSSIAN_INTERPOLATION(smpPtr, positionFrac, 128.0f) \
+	GAUSSIAN_INTERPOLATION(smpPtr, positionFrac, 128) \
 	*fMixBufferL++ += fSample * fVolumeL; \
 	*fMixBufferR++ += fSample * fVolumeR;
 
 #define RENDER_16BIT_SMP_GINTRP \
-	GAUSSIAN_INTERPOLATION(smpPtr, positionFrac, 32768.0f) \
+	GAUSSIAN_INTERPOLATION(smpPtr, positionFrac, 32768) \
+	*fMixBufferL++ += fSample * fVolumeL; \
+	*fMixBufferR++ += fSample * fVolumeR;
+
+/* Special left-edge case mixers to get proper tap data after one loop cycle.
+** These are only used on looped samples.
+*/
+
+#define RENDER_8BIT_SMP_GINTRP_TAP_FIX  \
+	smpTapPtr = (smpPtr <= leftEdgePtr) ? (int8_t *)&v->leftEdgeTaps8[(int32_t)(smpPtr-loopStartPtr)] : (int8_t *)smpPtr; \
+	GAUSSIAN_INTERPOLATION(smpTapPtr, positionFrac, 128) \
+	*fMixBufferL++ += fSample * fVolumeL; \
+	*fMixBufferR++ += fSample * fVolumeR;
+
+#define RENDER_16BIT_SMP_GINTRP_TAP_FIX \
+	smpTapPtr = (smpPtr <= leftEdgePtr) ? (int16_t *)&v->leftEdgeTaps16[(int32_t)(smpPtr-loopStartPtr)] : (int16_t *)smpPtr; \
+	GAUSSIAN_INTERPOLATION(smpTapPtr, positionFrac, 32768) \
 	*fMixBufferL++ += fSample * fVolumeL; \
 	*fMixBufferR++ += fSample * fVolumeR;
 
--- a/src/mixer/ft2_windowed_sinc.c
+++ b/src/mixer/ft2_windowed_sinc.c
@@ -9,11 +9,10 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <math.h>
-#include "ft2_windowed_sinc.h"
+#include "ft2_windowed_sinc.h" // SINCx_TAPS, SINCx_PHASES
+#include "../ft2_header.h" // PI
 #include "../ft2_video.h" // showErrorMsgBox()
 
-#define MY_PI 3.14159265358979323846264338327950288
-
 // globalized
 float *fKaiserSinc_8 = NULL, *fDownSample1_8 = NULL, *fDownSample2_8 = NULL;
 float *fKaiserSinc_16 = NULL, *fDownSample1_16 = NULL, *fDownSample2_16 = NULL;
@@ -44,7 +43,7 @@
 static void generateSincLUT(float *fOutput, uint32_t filterWidth, uint32_t numPhases, const double beta, const double lpCutoff)
 {
 	const double I0Beta = besselI0(beta);
-	const double kPi = MY_PI * lpCutoff;
+	const double kPi = PI * lpCutoff;
 	const double iMul = 1.0 / numPhases;
 	const double xMul = 1.0 / ((filterWidth / 2) * (filterWidth / 2));
 
--- a/src/scopes/ft2_scope_macros.h
+++ b/src/scopes/ft2_scope_macros.h
@@ -65,19 +65,43 @@
 
 #define INTERPOLATE_SMP8(pos, frac) \
 	const int8_t *s8 = s->base8 + pos; \
-	const int16_t *t = scopeGaussianLUT + (((frac) >> (SCOPE_FRAC_BITS-8)) << 2); \
-	sample = ((s8[0] * t[0]) + \
-	          (s8[1] * t[1]) + \
-	          (s8[2] * t[2]) + \
-	          (s8[3] * t[3])) >> (15-8);
+	if (config.interpolation == INTERPOLATION_DISABLED) \
+	{ \
+		sample = s8[0] << 8; \
+	} \
+	else if (config.interpolation == INTERPOLATION_LINEAR) \
+	{ \
+		const int32_t f = (frac) >> (SCOPE_FRAC_BITS-15); \
+		sample = (s8[0] << 8) + ((((s8[1] - s8[0]) << 8) * f) >> 15); \
+	} \
+	else \
+	{ \
+		const int16_t *t = scopeIntrpLUT + (((frac) >> (SCOPE_FRAC_BITS-SCOPE_INTRP_PHASES_BITS)) << 2); \
+		sample = ((s8[0] * t[0]) + \
+		          (s8[1] * t[1]) + \
+		          (s8[2] * t[2]) + \
+		          (s8[3] * t[3])) >> (SCOPE_INTRP_SCALE_BITS-8); \
+	}
 
 #define INTERPOLATE_SMP16(pos, frac) \
 	const int16_t *s16 = s->base16 + pos; \
-	const int16_t *t = scopeGaussianLUT + (((frac) >> (SCOPE_FRAC_BITS-8)) << 2); \
-	sample = ((s16[0] * t[0]) + \
-	          (s16[1] * t[1]) + \
-	          (s16[2] * t[2]) + \
-	          (s16[3] * t[3])) >> 15;
+	if (config.interpolation == INTERPOLATION_DISABLED) \
+	{ \
+		sample = s16[0]; \
+	} \
+	else if (config.interpolation == INTERPOLATION_LINEAR) \
+	{ \
+		const int32_t f = (frac) >> (SCOPE_FRAC_BITS-15); \
+		sample = s16[0] + (((s16[1] - s16[0]) * f) >> 15); \
+	} \
+	else \
+	{ \
+		const int16_t *t = scopeIntrpLUT + (((frac) >> (SCOPE_FRAC_BITS-SCOPE_INTRP_PHASES_BITS)) << 2); \
+		sample = ((s16[0] * t[0]) + \
+		          (s16[1] * t[1]) + \
+		          (s16[2] * t[2]) + \
+		          (s16[3] * t[3])) >> SCOPE_INTRP_SCALE_BITS; \
+	}
 
 #define SCOPE_GET_SMP8 \
 	if (s->active) \
@@ -145,7 +169,7 @@
 	if (s->active) \
 	{ \
 		GET_BIDI_POSITION \
-		INTERPOLATE_SMP8(actualPos, samplingBackwards ? ((uint32_t)positionFrac ^ UINT32_MAX) : positionFrac) \
+		INTERPOLATE_SMP8(actualPos, samplingBackwards ? ((uint32_t)positionFrac ^ UINT32_MAX) : (uint32_t)positionFrac) \
 		sample = (sample * volume) >> 16; \
 	} \
 	else \
@@ -157,7 +181,7 @@
 	if (s->active) \
 	{ \
 		GET_BIDI_POSITION \
-		INTERPOLATE_SMP16(actualPos, samplingBackwards ? ((uint32_t)positionFrac ^ UINT32_MAX) : positionFrac) \
+		INTERPOLATE_SMP16(actualPos, samplingBackwards ? ((uint32_t)positionFrac ^ UINT32_MAX) : (uint32_t)positionFrac) \
 		sample = (sample * volume) >> 16; \
 	} \
 	else \
--- a/src/scopes/ft2_scopedraw.c
+++ b/src/scopes/ft2_scopedraw.c
@@ -1,3 +1,9 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <math.h>
+#include "../ft2_config.h"
 #include "../ft2_video.h"
 #include "../ft2_palette.h"
 #include "../mixer/ft2_gaussian.h"
@@ -5,142 +11,62 @@
 #include "ft2_scopedraw.h"
 #include "ft2_scope_macros.h"
 
-/* 15-bit Gaussian interpolation LUT with no overshoot (sum <= 1.0).
-** Suitable for tracker scopes.
-*/
-static const int16_t scopeGaussianLUT[4 * 256] =
-{
-	 4807,22963, 4871,   -1, 4744,22962, 4935,   -1,
-	 4681,22960, 5000,   -1, 4619,22957, 5065,   -1,
-	 4557,22953, 5131,   -1, 4495,22948, 5197,   -1,
-	 4435,22942, 5264,   -1, 4374,22935, 5332,   -1,
-	 4315,22927, 5399,   -1, 4255,22918, 5468,   -1,
-	 4197,22908, 5536,   -1, 4138,22897, 5606,   -1,
-	 4081,22885, 5676,   -1, 4023,22872, 5746,   -1,
-	 3967,22857, 5817,   -1, 3910,22842, 5888,   -1,
-	 3855,22826, 5959,    0, 3799,22809, 6032,    0,
-	 3745,22791, 6104,    0, 3691,22772, 6177,    0,
-	 3637,22752, 6251,    0, 3584,22731, 6325,    0,
-	 3531,22709, 6400,    0, 3479,22686, 6475,    1,
-	 3427,22662, 6550,    1, 3376,22637, 6626,    1,
-	 3325,22611, 6702,    1, 3275,22584, 6779,    2,
-	 3225,22556, 6856,    2, 3176,22527, 6934,    2,
-	 3128,22498, 7012,    3, 3079,22467, 7091,    3,
-	 3032,22435, 7170,    3, 2985,22402, 7249,    4,
-	 2938,22369, 7329,    4, 2892,22334, 7409,    5,
-	 2846,22299, 7490,    5, 2801,22262, 7571,    6,
-	 2756,22225, 7653,    7, 2712,22187, 7735,    7,
-	 2668,22148, 7817,    8, 2624,22107, 7900,    9,
-	 2582,22066, 7983,    9, 2539,22025, 8066,   10,
-	 2497,21982, 8150,   11, 2456,21938, 8234,   12,
-	 2415,21893, 8319,   13, 2374,21848, 8404,   14,
-	 2334,21801, 8489,   15, 2295,21754, 8575,   16,
-	 2256,21706, 8661,   17, 2217,21657, 8748,   18,
-	 2179,21607, 8834,   19, 2141,21556, 8922,   21,
-	 2104,21505, 9009,   22, 2067,21452, 9097,   24,
-	 2031,21399, 9185,   25, 1995,21345, 9273,   27,
-	 1959,21290, 9362,   28, 1924,21235, 9451,   30,
-	 1890,21178, 9541,   32, 1856,21121, 9630,   33,
-	 1822,21063, 9720,   35, 1789,21004, 9811,   37,
-	 1756,20944, 9901,   39, 1723,20884, 9992,   41,
-	 1691,20822,10083,   44, 1660,20760,10174,   46,
-	 1628,20698,10266,   48, 1598,20634,10358,   51,
-	 1567,20570,10450,   53, 1537,20505,10542,   56,
-	 1508,20439,10635,   58, 1479,20373,10727,   61,
-	 1450,20306,10820,   64, 1422,20238,10913,   67,
-	 1394,20169,11007,   70, 1366,20100,11100,   73,
-	 1339,20030,11194,   77, 1312,19959,11288,   80,
-	 1286,19888,11382,   84, 1260,19816,11476,   87,
-	 1234,19744,11571,   91, 1209,19671,11665,   95,
-	 1184,19597,11760,   99, 1160,19522,11855,  103,
-	 1136,19447,11950,  107, 1112,19372,12045,  111,
-	 1089,19295,12140,  116, 1066,19219,12236,  120,
-	 1043,19141,12331,  125, 1020,19063,12427,  130,
-	  999,18985,12522,  135,  977,18905,12618,  140,
-	  956,18826,12714,  145,  935,18746,12809,  150,
-	  914,18665,12905,  156,  894,18584,13001,  161,
-	  874,18502,13097,  167,  854,18420,13193,  173,
-	  835,18337,13289,  179,  816,18254,13385,  186,
-	  797,18170,13481,  192,  779,18086,13577,  199,
-	  761,18001,13673,  205,  743,17916,13769,  212,
-	  726,17830,13865,  219,  708,17744,13961,  227,
-	  692,17658,14056,  234,  675,17571,14152,  242,
-	  659,17484,14248,  250,  643,17396,14343,  257,
-	  627,17308,14439,  266,  612,17220,14534,  274,
-	  597,17131,14630,  283,  582,17042,14725,  291,
-	  567,16953,14820,  300,  553,16863,14915,  309,
-	  539,16773,15010,  319,  525,16682,15104,  328,
-	  512,16592,15199,  338,  498,16500,15293,  348,
-	  485,16409,15387,  358,  473,16317,15481,  369,
-	  460,16226,15575,  379,  448,16133,15669,  390,
-	  436,16041,15762,  401,  424,15948,15855,  412,
-	  412,15855,15948,  424,  401,15762,16041,  436,
-	  390,15669,16133,  448,  379,15575,16226,  460,
-	  369,15481,16317,  473,  358,15387,16409,  485,
-	  348,15293,16500,  498,  338,15199,16592,  512,
-	  328,15104,16682,  525,  319,15010,16773,  539,
-	  309,14915,16863,  553,  300,14820,16953,  567,
-	  291,14725,17042,  582,  283,14630,17131,  597,
-	  274,14534,17220,  612,  266,14439,17308,  627,
-	  257,14343,17396,  643,  250,14248,17484,  659,
-	  242,14152,17571,  675,  234,14056,17658,  692,
-	  227,13961,17744,  708,  219,13865,17830,  726,
-	  212,13769,17916,  743,  205,13673,18001,  761,
-	  199,13577,18086,  779,  192,13481,18170,  797,
-	  186,13385,18254,  816,  179,13289,18337,  835,
-	  173,13193,18420,  854,  167,13097,18502,  874,
-	  161,13001,18584,  894,  156,12905,18665,  914,
-	  150,12809,18746,  935,  145,12714,18826,  956,
-	  140,12618,18905,  977,  135,12522,18985,  999,
-	  130,12427,19063, 1020,  125,12331,19141, 1043,
-	  120,12236,19219, 1066,  116,12140,19295, 1089,
-	  111,12045,19372, 1112,  107,11950,19447, 1136,
-	  103,11855,19522, 1160,   99,11760,19597, 1184,
-	   95,11665,19671, 1209,   91,11571,19744, 1234,
-	   87,11476,19816, 1260,   84,11382,19888, 1286,
-	   80,11288,19959, 1312,   77,11194,20030, 1339,
-	   73,11100,20100, 1366,   70,11007,20169, 1394,
-	   67,10913,20238, 1422,   64,10820,20306, 1450,
-	   61,10727,20373, 1479,   58,10635,20439, 1508,
-	   56,10542,20505, 1537,   53,10450,20570, 1567,
-	   51,10358,20634, 1598,   48,10266,20698, 1628,
-	   46,10174,20760, 1660,   44,10083,20822, 1691,
-	   41, 9992,20884, 1723,   39, 9901,20944, 1756,
-	   37, 9811,21004, 1789,   35, 9720,21063, 1822,
-	   33, 9630,21121, 1856,   32, 9541,21178, 1890,
-	   30, 9451,21235, 1924,   28, 9362,21290, 1959,
-	   27, 9273,21345, 1995,   25, 9185,21399, 2031,
-	   24, 9097,21452, 2067,   22, 9009,21505, 2104,
-	   21, 8922,21556, 2141,   19, 8834,21607, 2179,
-	   18, 8748,21657, 2217,   17, 8661,21706, 2256,
-	   16, 8575,21754, 2295,   15, 8489,21801, 2334,
-	   14, 8404,21848, 2374,   13, 8319,21893, 2415,
-	   12, 8234,21938, 2456,   11, 8150,21982, 2497,
-	   10, 8066,22025, 2539,    9, 7983,22066, 2582,
-	    9, 7900,22107, 2624,    8, 7817,22148, 2668,
-	    7, 7735,22187, 2712,    7, 7653,22225, 2756,
-	    6, 7571,22262, 2801,    5, 7490,22299, 2846,
-	    5, 7409,22334, 2892,    4, 7329,22369, 2938,
-	    4, 7249,22402, 2985,    3, 7170,22435, 3032,
-	    3, 7091,22467, 3079,    3, 7012,22498, 3128,
-	    2, 6934,22527, 3176,    2, 6856,22556, 3225,
-	    2, 6779,22584, 3275,    1, 6702,22611, 3325,
-	    1, 6626,22637, 3376,    1, 6550,22662, 3427,
-	    1, 6475,22686, 3479,    0, 6400,22709, 3531,
-	    0, 6325,22731, 3584,    0, 6251,22752, 3637,
-	    0, 6177,22772, 3691,    0, 6104,22791, 3745,
-	    0, 6032,22809, 3799,    0, 5959,22826, 3855,
-	   -1, 5888,22842, 3910,   -1, 5817,22857, 3967,
-	   -1, 5746,22872, 4023,   -1, 5676,22885, 4081,
-	   -1, 5606,22897, 4138,   -1, 5536,22908, 4197,
-	   -1, 5468,22918, 4255,   -1, 5399,22927, 4315,
-	   -1, 5332,22935, 4374,   -1, 5264,22942, 4435,
-	   -1, 5197,22948, 4495,   -1, 5131,22953, 4557,
-	   -1, 5065,22957, 4619,   -1, 5000,22960, 4681,
-	   -1, 4935,22962, 4744,   -1, 4871,22963, 4807
-};
+static int16_t *scopeIntrpLUT;
 
 static void scopeLine(int32_t x1, int32_t y1, int32_t y2, uint32_t color);
+
+bool calcScopeIntrpLUT(void)
+{
+	scopeIntrpLUT = (int16_t *)malloc(4 * SCOPE_INTRP_PHASES * sizeof (int16_t));
+	if (scopeIntrpLUT == NULL)
+		return false;
+
+	int16_t *ptr = scopeIntrpLUT;
+	for (int32_t i = 0; i < SCOPE_INTRP_PHASES; i++)
+	{
+#define PI_MULTIPLIER 2.048
+
+		const int32_t i1 = SCOPE_INTRP_PHASES + i;
+		const int32_t i2 = i;
+		const int32_t i3 = (SCOPE_INTRP_PHASES-1) - i;
+		const int32_t i4 = ((SCOPE_INTRP_PHASES*2)-1) - i;
+
+		const double x1 = (0.5 + i1) * (1.0 / ((SCOPE_INTRP_PHASES*4)-1));
+		const double x2 = (0.5 + i2) * (1.0 / ((SCOPE_INTRP_PHASES*4)-1));
+		const double x3 = (0.5 + i3) * (1.0 / ((SCOPE_INTRP_PHASES*4)-1));
+		const double x4 = (0.5 + i4) * (1.0 / ((SCOPE_INTRP_PHASES*4)-1));
+
+		// Blackman window
+		const double w1 = (0.42 + (0.50 * cos(2.0 * PI * x1)) + (0.08 * cos(4.0 * PI * x1))) / x1;
+		const double w2 = (0.42 + (0.50 * cos(2.0 * PI * x2)) + (0.08 * cos(4.0 * PI * x2))) / x2;
+		const double w3 = (0.42 + (0.50 * cos(2.0 * PI * x3)) + (0.08 * cos(4.0 * PI * x3))) / x3;
+		const double w4 = (0.42 + (0.50 * cos(2.0 * PI * x4)) + (0.08 * cos(4.0 * PI * x4))) / x4;
+
+		const double t1 = sin(PI_MULTIPLIER * PI * x1) * w1;
+		const double t2 = sin(PI_MULTIPLIER * PI * x2) * w2;
+		const double t3 = sin(PI_MULTIPLIER * PI * x3) * w3;
+		const double t4 = sin(PI_MULTIPLIER * PI * x4) * w4;
+
+		// calculate normalization value (also assures unity gain when summing taps)
+		const double dScale = SCOPE_INTRP_SCALE / (t1 + t2 + t3 + t4);
+
+		*ptr++ = (int16_t)((t1 * dScale) + 0.5);
+		*ptr++ = (int16_t)((t2 * dScale) + 0.5);
+		*ptr++ = (int16_t)((t3 * dScale) + 0.5);
+		*ptr++ = (int16_t)((t4 * dScale) + 0.5);
+	}
+
+	return true;
+}
+
+void freeScopeIntrpLUT(void)
+{
+	if (scopeIntrpLUT != NULL)
+	{
+		free(scopeIntrpLUT);
+		scopeIntrpLUT = NULL;
+	}
+}
 
 /* ----------------------------------------------------------------------- */
 /*                         SCOPE DRAWING ROUTINES                          */
--- a/src/scopes/ft2_scopedraw.h
+++ b/src/scopes/ft2_scopedraw.h
@@ -6,3 +6,7 @@
 typedef void (*scopeDrawRoutine)(const scope_t *, uint32_t, uint32_t, uint32_t);
 
 extern const scopeDrawRoutine scopeDrawRoutineTable[12]; // ft2_scopedraw.c
+
+bool calcScopeIntrpLUT(void);
+void freeScopeIntrpLUT(void);
+
--- a/src/scopes/ft2_scopes.c
+++ b/src/scopes/ft2_scopes.c
@@ -544,6 +544,12 @@
 		return false;
 	}
 
+	if (!calcScopeIntrpLUT())
+	{
+		showErrorMsgBox("Not enough memory!");
+		return false;
+	}
+
 	SDL_DetachThread(scopeThread);
 	return true;
 }
--- a/src/scopes/ft2_scopes.h
+++ b/src/scopes/ft2_scopes.h
@@ -5,6 +5,11 @@
 #include "../ft2_header.h"
 #include "../ft2_audio.h"
 
+#define SCOPE_INTRP_SCALE 32767
+#define SCOPE_INTRP_SCALE_BITS 15 /* ceil(log2(SCOPE_INTRP_SCALE)) */
+#define SCOPE_INTRP_PHASES 1024 /* good enough for the FT2 scopes */
+#define SCOPE_INTRP_PHASES_BITS 10 /* log2(SCOPE_INTRP_PHASES) */
+
 #define SCOPE_HEIGHT 36
 
 #define SCOPE_FRAC_BITS 32
--