shithub: ft²

Download patch

ref: 544e74e4829b1856bd7a60de24317af9f461fdb5
parent: 7f215f857b4adc6c13ecd36713737c8861816e20
author: Olav Sørensen <olav.sorensen@live.no>
date: Sun Nov 3 14:31:06 EST 2024

Mega-commit (unfortunately) for v1.87

--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -177,6 +177,7 @@
 
 	audio.samplesPerTickInt = audio.samplesPerTickIntTab[i];
 	audio.samplesPerTickFrac = audio.samplesPerTickFracTab[i];
+	audio.fSamplesPerTickIntMul = (float)(1.0 / (double)audio.samplesPerTickInt);
 
 	// for audio/video sync timestamp
 	tickTimeLenInt = audio.tickTimeIntTab[i];
@@ -261,11 +262,9 @@
 			const float fVolumeRDiff = 0.0f - f->fCurrVolumeR;
 
 			f->volumeRampLength = audio.quickVolRampSamples; // 5ms
-			const float fVolumeRampLength = (float)(int32_t)f->volumeRampLength;
+			f->fVolumeLDelta = fVolumeLDiff * audio.fQuickVolRampSamplesMul;
+			f->fVolumeRDelta = fVolumeRDiff * audio.fQuickVolRampSamplesMul;
 
-			f->fVolumeLDelta = fVolumeLDiff / fVolumeRampLength;
-			f->fVolumeRDelta = fVolumeRDiff / fVolumeRampLength;
-
 			f->isFadeOutVoice = true;
 		}
 
@@ -282,12 +281,20 @@
 		const float fVolumeLDiff = v->fTargetVolumeL - v->fCurrVolumeL;
 		const float fVolumeRDiff = v->fTargetVolumeR - v->fCurrVolumeR;
 
-		// IS_QuickVol = 5ms, otherwise the duration of a tick
-		v->volumeRampLength = (status & IS_QuickVol) ? audio.quickVolRampSamples : audio.samplesPerTickInt;
-		const float fVolumeRampLength = (float)(int32_t)v->volumeRampLength;
+		float fRampLengthMul;
+		if (status & IS_QuickVol) // duration of 5ms
+		{
+			v->volumeRampLength = audio.quickVolRampSamples;
+			fRampLengthMul = audio.fQuickVolRampSamplesMul;
+		}
+		else // duration of a tick
+		{
+			v->volumeRampLength = audio.samplesPerTickInt;
+			fRampLengthMul = audio.fSamplesPerTickIntMul;
+		}
 
-		v->fVolumeLDelta = fVolumeLDiff / fVolumeRampLength;
-		v->fVolumeRDelta = fVolumeRDiff / fVolumeRampLength;
+		v->fVolumeLDelta = fVolumeLDiff * fRampLengthMul;
+		v->fVolumeRDelta = fVolumeRDiff * fRampLengthMul;
 	}
 }
 
@@ -340,7 +347,7 @@
 		return;
 	}
 
-	v->mixFuncOffset = ((int32_t)sample16Bit * 15) + (audio.interpolationType * 3) + loopType;
+	v->mixFuncOffset = ((int32_t)sample16Bit * 18) + (audio.interpolationType * 3) + loopType;
 	v->active = true;
 }
 
@@ -462,6 +469,8 @@
 	voice_t *v = voice; // normal voices
 	voice_t *r = &voice[MAX_CHANNELS]; // volume ramp fadeout-voices
 
+	const int32_t mixOffsetBias = 3 * NUM_INTERPOLATORS * 2; // 3 = loop types (off/fwd/bidi), 2 = bit depths (8-bit/16-bit)
+
 	for (int32_t i = 0; i < song.numChannels; i++, v++, r++)
 	{
 		if (v->active)
@@ -470,11 +479,11 @@
 			if (!volRampFlag && v->fCurrVolumeL == 0.0f && v->fCurrVolumeR == 0.0f)
 				silenceMixRoutine(v, samplesToMix);
 			else
-				mixFuncTab[((int32_t)volRampFlag * (3*5*2)) + v->mixFuncOffset](v, bufferPosition, samplesToMix);
+				mixFuncTab[((int32_t)volRampFlag * mixOffsetBias) + v->mixFuncOffset](v, bufferPosition, samplesToMix);
 		}
 
 		if (r->active) // volume ramp fadeout-voice
-			mixFuncTab[(3*5*2) + r->mixFuncOffset](r, bufferPosition, samplesToMix);
+			mixFuncTab[mixOffsetBias + r->mixFuncOffset](r, bufferPosition, samplesToMix);
 	}
 }
 
--- a/src/ft2_audio.h
+++ b/src/ft2_audio.h
@@ -49,7 +49,7 @@
 
 	uint64_t tickTime64, tickTime64Frac;
 
-	float *fMixBufferL, *fMixBufferR;
+	float *fMixBufferL, *fMixBufferR, fQuickVolRampSamplesMul, fSamplesPerTickIntMul;
 	double dHz2MixDeltaMul;
 
 	SDL_AudioDeviceID dev;
--- a/src/ft2_checkboxes.c
+++ b/src/ft2_checkboxes.c
@@ -82,7 +82,7 @@
 	// ------ CONFIG CHECKBOXES ------
 	//x,   y,   w,   h,  funcOnUp
 	{   3,  91,  77, 12, cbToggleAutoSaveConfig },
-	{ 508, 158, 107, 12, cbConfigVolRamp },
+	{ 512, 158, 107, 12, cbConfigVolRamp },
 	{ 113,  14, 108, 12, cbConfigPattStretch },
 	{ 113,  27, 117, 12, cbConfigHexCount },
 	{ 113,  40,  81, 12, cbConfigAccidential },
--- a/src/ft2_config.c
+++ b/src/ft2_config.c
@@ -139,7 +139,7 @@
 	config.recMIDIVolSens = CLAMP(config.recMIDIVolSens, 0, 200);
 	config.recMIDIChn  = CLAMP(config.recMIDIChn, 1, 16);
 
-	if (config.interpolation > 4)
+	if (config.interpolation >= NUM_INTERPOLATORS)
 		config.interpolation = INTERPOLATION_SINC8; // default (sinc, 8 point)
 
 	if (config.recTrueInsert > 1)
@@ -837,6 +837,8 @@
 		tmpID = RB_CONFIG_AUDIO_INTRP_SINC16;
 	else if (config.interpolation == INTERPOLATION_CUBIC)
 		tmpID = RB_CONFIG_AUDIO_INTRP_CUBIC;
+	else if (config.interpolation == INTERPOLATION_GAUSSIAN)
+		tmpID = RB_CONFIG_AUDIO_INTRP_GAUSSIAN;
 	else
 		tmpID = RB_CONFIG_AUDIO_INTRP_SINC8; // default case
 
@@ -1126,13 +1128,13 @@
 			drawFramework(110,   0, 276, 87, FRAMEWORK_TYPE1);
 			drawFramework(110,  87, 276, 86, FRAMEWORK_TYPE1);
 
-			drawFramework(386,   0, 119, 58, FRAMEWORK_TYPE1);
-			drawFramework(386,  58, 119, 44, FRAMEWORK_TYPE1);
-			drawFramework(386, 102, 119, 71, FRAMEWORK_TYPE1);
+			drawFramework(386,   0, 123, 58, FRAMEWORK_TYPE1);
+			drawFramework(386,  58, 123, 29, FRAMEWORK_TYPE1);
+			drawFramework(386,  87, 123, 86, FRAMEWORK_TYPE1);
 
-			drawFramework(505,   0, 127, 58, FRAMEWORK_TYPE1);
-			drawFramework(505, 102, 127, 71, FRAMEWORK_TYPE1);
-			drawFramework(505,  58, 127, 44, FRAMEWORK_TYPE1);
+			drawFramework(509,   0, 123, 58, FRAMEWORK_TYPE1);
+			drawFramework(509, 102, 123, 71, FRAMEWORK_TYPE1);
+			drawFramework(509,  58, 123, 44, FRAMEWORK_TYPE1);
 
 			drawFramework(112,  16, AUDIO_SELECTORS_BOX_WIDTH+4, 69, FRAMEWORK_TYPE2);
 			drawFramework(112, 103, AUDIO_SELECTORS_BOX_WIDTH+4, 47, FRAMEWORK_TYPE2);
@@ -1161,33 +1163,34 @@
 			textOutShadow(336, 157, PAL_FORGRND, PAL_DSKTOP2, "96.0kHz");
 
 			textOutShadow(390,   3, PAL_FORGRND, PAL_DSKTOP2, "Audio buffer size:");
-			textOutShadow(406,  17, PAL_FORGRND, PAL_DSKTOP2, "Small");
-			textOutShadow(406,  31, PAL_FORGRND, PAL_DSKTOP2, "Medium (default)");
-			textOutShadow(406,  45, PAL_FORGRND, PAL_DSKTOP2, "Large");
+			textOutShadow(405,  17, PAL_FORGRND, PAL_DSKTOP2, "Small");
+			textOutShadow(405,  31, PAL_FORGRND, PAL_DSKTOP2, "Medium (default)");
+			textOutShadow(405,  45, PAL_FORGRND, PAL_DSKTOP2, "Large");
 
 			textOutShadow(390,  61, PAL_FORGRND, PAL_DSKTOP2, "Audio bit depth:");
-			textOutShadow(406,  75, PAL_FORGRND, PAL_DSKTOP2, "16-bit");
-			textOutShadow(406,  89, PAL_FORGRND, PAL_DSKTOP2, "32-bit (float)");
+			textOutShadow(405,  74, PAL_FORGRND, PAL_DSKTOP2, "16-bit");
+			textOutShadow(468,  74, PAL_FORGRND, PAL_DSKTOP2, "32-bit");
 
-			textOutShadow(406, 105, PAL_FORGRND, PAL_DSKTOP2, "No interpolation");
-			textOutShadow(406, 119, PAL_FORGRND, PAL_DSKTOP2, "Linear (FT2)");
-			textOutShadow(406, 133, PAL_FORGRND, PAL_DSKTOP2, "Cubic spline");
-			textOutShadow(406, 147, PAL_FORGRND, PAL_DSKTOP2, "Sinc (8 point)");
-			textOutShadow(406, 161, PAL_FORGRND, PAL_DSKTOP2, "Sinc (16 point)");
+			textOutShadow(405,  91, PAL_FORGRND, PAL_DSKTOP2, "No interpolation");
+			textOutShadow(405, 105, PAL_FORGRND, PAL_DSKTOP2, "Linear (FT2)");
+			textOutShadow(405, 119, PAL_FORGRND, PAL_DSKTOP2, "Gaussian (SNES)");
+			textOutShadow(405, 133, PAL_FORGRND, PAL_DSKTOP2, "Cubic Hermite");
+			textOutShadow(405, 147, PAL_FORGRND, PAL_DSKTOP2, "Sinc (8 point)");
+			textOutShadow(405, 161, PAL_FORGRND, PAL_DSKTOP2, "Sinc (16 point)");
 
-			textOutShadow(509,   3, PAL_FORGRND, PAL_DSKTOP2, "Audio output rate:");
-			textOutShadow(525,  17, PAL_FORGRND, PAL_DSKTOP2, "44100Hz");
-			textOutShadow(525,  31, PAL_FORGRND, PAL_DSKTOP2, "48000Hz");
-			textOutShadow(525,  45, PAL_FORGRND, PAL_DSKTOP2, "96000Hz");
+			textOutShadow(513,   3, PAL_FORGRND, PAL_DSKTOP2, "Audio output rate:");
+			textOutShadow(528,  17, PAL_FORGRND, PAL_DSKTOP2, "44100Hz");
+			textOutShadow(528,  31, PAL_FORGRND, PAL_DSKTOP2, "48000Hz");
+			textOutShadow(528,  45, PAL_FORGRND, PAL_DSKTOP2, "96000Hz");
 
-			textOutShadow(509,  61, PAL_FORGRND, PAL_DSKTOP2, "Frequency slides:");
-			textOutShadow(525,  75, PAL_FORGRND, PAL_DSKTOP2, "Amiga");
-			textOutShadow(525,  89, PAL_FORGRND, PAL_DSKTOP2, "Linear (default)");
+			textOutShadow(513,  61, PAL_FORGRND, PAL_DSKTOP2, "Frequency slides:");
+			textOutShadow(528,  75, PAL_FORGRND, PAL_DSKTOP2, "Amiga");
+			textOutShadow(528,  89, PAL_FORGRND, PAL_DSKTOP2, "Linear (default)");
 
-			textOutShadow(509, 105, PAL_FORGRND, PAL_DSKTOP2, "Amplification:");
+			textOutShadow(513, 105, PAL_FORGRND, PAL_DSKTOP2, "Amplification:");
 			charOutShadow(621, 105, PAL_FORGRND, PAL_DSKTOP2, 'x');
-			textOutShadow(509, 133, PAL_FORGRND, PAL_DSKTOP2, "Master volume:");
-			textOutShadow(525, 160, PAL_FORGRND, PAL_DSKTOP2, "Volume ramping");
+			textOutShadow(513, 133, PAL_FORGRND, PAL_DSKTOP2, "Master volume:");
+			textOutShadow(529, 160, PAL_FORGRND, PAL_DSKTOP2, "Volume ramping");
 
 			setConfigAudioRadioButtonStates();
 			setConfigAudioCheckButtonStates();
@@ -1623,6 +1626,13 @@
 	config.interpolation = INTERPOLATION_LINEAR;
 	audioSetInterpolationType(config.interpolation);
 	checkRadioButton(RB_CONFIG_AUDIO_INTRP_LINEAR);
+}
+
+void rbConfigAudioIntrpGaussian(void)
+{
+	config.interpolation = INTERPOLATION_GAUSSIAN;
+	audioSetInterpolationType(config.interpolation);
+	checkRadioButton(RB_CONFIG_AUDIO_INTRP_GAUSSIAN);
 }
 
 void rbConfigAudioIntrpCubic(void)
--- a/src/ft2_config.h
+++ b/src/ft2_config.h
@@ -20,14 +20,6 @@
 	CONFIG_HIDE_ERRORS = 0,
 	CONFIG_SHOW_ERRORS = 1,
 
-	// don't change the order of these! (yes, it looks off)
-	INTERPOLATION_DISABLED = 0,
-	INTERPOLATION_SINC8 = 1,
-	INTERPOLATION_LINEAR = 2,
-	INTERPOLATION_SINC16 = 3,
-	INTERPOLATION_CUBIC = 4,
-	// ------
-
 	MOUSE_IDLE_SHAPE_NICE = 0,
 	MOUSE_IDLE_SHAPE_UGLY = 1,
 	MOUSE_IDLE_SHAPE_AWFUL = 2,
@@ -55,22 +47,14 @@
 	PAL_JUNGLE = 5,
 	PAL_LITHE_DARK = 6,
 	PAL_ROSE = 7,
-	PAL_DARK_MODE = 8,
+	PAL_DARK_MODE = 8, // default
 	PAL_VIOLENT = 9,
-	PAL_WHY_COLORS = 10, // default
+	PAL_WHY_COLORS = 10,
 	PAL_USER_DEFINED = 11,
 
 	FILESORT_EXT = 0,
 	FILESORT_NAME = 1,
 
-	ONE_PLAYER = 0,
-	TWO_PLAYERS = 1,
-
-	DIFFICULTY_NOVICE = 0,
-	DIFFICULTY_AVERAGE = 1,
-	DIFFICULTY_PRO = 2,
-	DIFFICULTY_MANIAC = 3,
-
 	DONT_SHOW_IMPORT_WARNING_FLAG = 64,
 	DONT_SHOW_NOT_YET_APPLIED_WARNING_FLAG = 32,
 
@@ -221,6 +205,7 @@
 void rbConfigAudio32BitFloat(void);
 void rbConfigAudioIntrpDisabled(void);
 void rbConfigAudioIntrpLinear(void);
+void rbConfigAudioIntrpGaussian(void);
 void rbConfigAudioIntrpCubic(void);
 void rbConfigAudioIntrp8PointSinc(void);
 void rbConfigAudioIntrp16PointSinc(void);
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
 #endif
 #include "ft2_replayer.h"
 
-#define PROG_VER_STR "1.86"
+#define PROG_VER_STR "1.87"
 
 // do NOT change these! It will only mess things up...
 
--- a/src/ft2_inst_ed.c
+++ b/src/ft2_inst_ed.c
@@ -55,13 +55,13 @@
 	uint8_t fractions;
 	int32_t sampleLength, loopStart, loopEnd;
 	uint16_t sampleRate;
-	int32_t lowFrq, highFreq, rootFrq;
-	int16_t finetune;
-	uint8_t panning, envRate[6], envOfs[6], tremSweep, tremRate;
+	int32_t lowFreq, highFreq, rootFreq;
+	int16_t tune; // -512 to +512, EXCLUDING 0 cause it is a multiplier. 512 is one octave off, and 1 is a neutral value
+	uint8_t balance, envRate[6], envOfs[6], tremSweep, tremRate;
 	uint8_t tremDepth, vibSweep, vibRate, vibDepth, flags;
-	int16_t junk1;
-	uint16_t junk2;
-	char junk3[36];
+	int16_t scaleFreq;
+	uint16_t scaleFactor;
+	char reserved[36];
 }
 #ifdef __GNUC__
 __attribute__ ((packed))
@@ -3092,12 +3092,10 @@
 	SDL_DetachThread(thread);
 }
 
-static int16_t getPATNote(int32_t freq)
+static int16_t getPATNote(uint32_t freq)
 {
-	const double dNote = (log2(freq / 440000.0) * 12.0) + 57.0;
-	const int32_t note = (const int32_t)(dNote + 0.5); // rounded
-
-	return (int16_t)note;
+	const double dNote = (log2((freq / 1000.0) / 440.0) * 12.0) + 9.0;
+	return (int16_t)round(NOTE_C4 + dNote);
 }
 
 static int32_t SDLCALL loadInstrThread(void *ptr)
@@ -3314,8 +3312,8 @@
 		{
 			// PAT - Gravis Ultrasound patch
 
-			if (pat_h.numSamples == 0)
-				pat_h.numSamples = 1; // to some patch makers, 0 means 1
+			if (pat_h.numSamples == 0) // 0 samples really means 1 (for quirky .PAT files)
+				pat_h.numSamples = 1;
 
 			if (pat_h.layers > 1 || pat_h.numSamples > MAX_SMP_PER_INST)
 			{
@@ -3375,8 +3373,8 @@
 				if (i == 0)
 				{
 					ins->autoVibSweep = patWave_h.vibSweep;
-					ins->autoVibRate = (patWave_h.vibRate + 2) >> 2; // rounded
-					ins->autoVibDepth = (patWave_h.vibDepth + 1) >> 1; // rounded
+					ins->autoVibRate = (patWave_h.vibRate + 1) >> 2;
+					ins->autoVibDepth = (patWave_h.vibDepth+1) >> 1;
 				}
 
 				s = &instr[editor.curInstr]->smp[i];
@@ -3392,7 +3390,7 @@
 						s->flags |= LOOP_FWD;
 				}
 
-				s->panning = ((patWave_h.panning << 4) & 0xF0) | (patWave_h.panning & 0xF);
+				s->panning = ((patWave_h.balance << 4) & 0xF0) | (patWave_h.balance & 0xF); // 0..15 -> 0..255
 				s->loopStart = patWave_h.loopStart;
 				s->loopLength = patWave_h.loopEnd - patWave_h.loopStart;
 
@@ -3402,13 +3400,17 @@
 					s->loopLength >>= 1;
 				}
 
-				const double dFreq = (1.0 + (patWave_h.finetune / 512.0)) * patWave_h.sampleRate;
-				tuneSample(s, (int32_t)(dFreq + 0.5), audio.linearPeriodsFlag);
+				double dC4Freq = patWave_h.sampleRate;
+				if (patWave_h.scaleFactor > 0)
+				{
+					const double dMidiC4Freq = 261.6255653005986347; // 440*2^(-9/12)
+					const double dRatio = dMidiC4Freq / (patWave_h.rootFreq / 1000.0);
+					dC4Freq *= dRatio;
+				}
 
-				a = getPATNote(patWave_h.rootFrq) - (12 * 3);
-				s->relativeNote -= (uint8_t)a;
+				setSampleC4Hz(s, dC4Freq);
 
-				a = getPATNote(patWave_h.lowFrq);
+				a = getPATNote(patWave_h.lowFreq);
 				b = getPATNote(patWave_h.highFreq);
 
 				a = CLAMP(a, 0, 95);
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -146,7 +146,7 @@
 #ifdef __APPLE__
 	osxSetDirToProgramDirFromArgs(argv);
 #endif
-	if (!setupExecutablePath() || !loadBMPs() || !calcCubicSplineTable() || !calcWindowedSincTables())
+	if (!setupExecutablePath() || !loadBMPs() || !calcGaussianTable() || !calcCubicSplineTable() || !calcWindowedSincTables())
 	{
 		cleanUpAndExit();
 		return 1;
--- a/src/ft2_pushbuttons.c
+++ b/src/ft2_pushbuttons.c
@@ -317,9 +317,9 @@
 	{ 365,  72, 18, 13, 1, 4, ARROW_DOWN_STRING,  NULL,    scrollAudOutputDevListDown, NULL },
 	{ 365, 103, 18, 13, 1, 4, ARROW_UP_STRING,    NULL,    scrollAudInputDevListUp,    NULL },
 	{ 365, 137, 18, 13, 1, 4, ARROW_DOWN_STRING,  NULL,    scrollAudInputDevListDown,  NULL },
-	{ 508, 117, 21, 13, 1, 4, ARROW_LEFT_STRING,  NULL,    configAmpDown,              NULL },
+	{ 512, 117, 21, 13, 1, 4, ARROW_LEFT_STRING,  NULL,    configAmpDown,              NULL },
 	{ 608, 117, 21, 13, 1, 4, ARROW_RIGHT_STRING, NULL,    configAmpUp,                NULL },
-	{ 508, 143, 21, 13, 1, 0, ARROW_LEFT_STRING,  NULL,    configMasterVolDown,        NULL },
+	{ 512, 143, 21, 13, 1, 0, ARROW_LEFT_STRING,  NULL,    configMasterVolDown,        NULL },
 	{ 608, 143, 21, 13, 1, 0, ARROW_RIGHT_STRING, NULL,    configMasterVolUp,          NULL },
 
 	// ------ CONFIG LAYOUT PUSHBUTTONS ------
--- a/src/ft2_radiobuttons.c
+++ b/src/ft2_radiobuttons.c
@@ -75,28 +75,29 @@
 
 	// audio buffer size
 	//x,   y,  w,   group,                           funcOnUp
-	{ 390, 16,  46, RB_GROUP_CONFIG_SOUND_BUFF_SIZE, rbConfigAudioBuffSmall  },
-	{ 390, 30, 113, RB_GROUP_CONFIG_SOUND_BUFF_SIZE, rbConfigAudioBuffMedium },
-	{ 390, 44,  50, RB_GROUP_CONFIG_SOUND_BUFF_SIZE, rbConfigAudioBuffLarge },
+	{ 390, 16,  45, RB_GROUP_CONFIG_SOUND_BUFF_SIZE, rbConfigAudioBuffSmall  },
+	{ 390, 30, 112, RB_GROUP_CONFIG_SOUND_BUFF_SIZE, rbConfigAudioBuffMedium },
+	{ 390, 44,  49, RB_GROUP_CONFIG_SOUND_BUFF_SIZE, rbConfigAudioBuffLarge },
 
 	// audio bit depth
 	//x,   y,  w,  group,                           funcOnUp
-	{ 390, 74, 52, RB_GROUP_CONFIG_AUDIO_BIT_DEPTH, rbConfigAudio16Bit },
-	{ 390, 88, 93, RB_GROUP_CONFIG_AUDIO_BIT_DEPTH, rbConfigAudio32BitFloat },
+	{ 390, 73, 51, RB_GROUP_CONFIG_AUDIO_BIT_DEPTH, rbConfigAudio16Bit },
+	{ 453, 73, 51, RB_GROUP_CONFIG_AUDIO_BIT_DEPTH, rbConfigAudio32BitFloat },
 
 	// audio interpolation
 	//x,   y,   w,   group,                               funcOnUp
-	{ 390, 104, 109, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpDisabled },
-	{ 390, 118,  91, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpLinear },
-	{ 390, 132,  86, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpCubic },
-	{ 390, 146,  95, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrp8PointSinc },
-	{ 390, 160, 102, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrp16PointSinc },
+	{ 390,  90, 108, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpDisabled },
+	{ 390, 104,  90, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpLinear },
+	{ 390, 118, 115, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpGaussian },
+	{ 390, 132,  95, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrpCubic },
+	{ 390, 146,  94, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrp8PointSinc },
+	{ 390, 160, 101, RB_GROUP_CONFIG_AUDIO_INTERPOLATION, rbConfigAudioIntrp16PointSinc },
 
 	// audio output frequency
 	//x,   y,  w,  group,                      funcOnUp
-	{ 509, 16, 66, RB_GROUP_CONFIG_AUDIO_FREQ, rbConfigAudio44kHz },
-	{ 509, 30, 66, RB_GROUP_CONFIG_AUDIO_FREQ, rbConfigAudio48kHz },
-	{ 509, 44, 66, RB_GROUP_CONFIG_AUDIO_FREQ, rbConfigAudio96kHz },
+	{ 513, 16, 65, RB_GROUP_CONFIG_AUDIO_FREQ, rbConfigAudio44kHz },
+	{ 513, 30, 65, RB_GROUP_CONFIG_AUDIO_FREQ, rbConfigAudio48kHz },
+	{ 513, 44, 65, RB_GROUP_CONFIG_AUDIO_FREQ, rbConfigAudio96kHz },
 
 	// audio input frequency
 	//x,   y,   w,  group,                            funcOnUp
@@ -106,8 +107,8 @@
 
 	// frequency slides
 	//x,   y,  w,   group,                       funcOnUp
-	{ 509, 74,  50, RB_GROUP_CONFIG_FREQ_SLIDES, rbConfigFreqSlidesAmiga  },
-	{ 509, 88, 108, RB_GROUP_CONFIG_FREQ_SLIDES, rbConfigFreqSlidesLinear },
+	{ 513, 74,  49, RB_GROUP_CONFIG_FREQ_SLIDES, rbConfigFreqSlidesAmiga  },
+	{ 513, 88, 107, RB_GROUP_CONFIG_FREQ_SLIDES, rbConfigFreqSlidesLinear },
 
 	// ------ CONFIG LAYOUT ------
 
--- a/src/ft2_radiobuttons.h
+++ b/src/ft2_radiobuttons.h
@@ -19,7 +19,7 @@
 	RB_NIBBLES_NOVICE,
 	RB_NIBBLES_AVERAGE,
 	RB_NIBBLES_PRO,
-	RB_NIBBLES_MANIAC,
+	RB_NIBBLES_TRITON,
 
 	// SAMPLER
 	RB_SAMPLE_NO_LOOP,
@@ -57,6 +57,7 @@
 	// AUDIO INTERPOLATION
 	RB_CONFIG_AUDIO_INTRP_DISABLED,
 	RB_CONFIG_AUDIO_INTRP_LINEAR,
+	RB_CONFIG_AUDIO_INTRP_GAUSSIAN,
 	RB_CONFIG_AUDIO_INTRP_CUBIC,
 	RB_CONFIG_AUDIO_INTRP_SINC8,
 	RB_CONFIG_AUDIO_INTRP_SINC16,
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -27,6 +27,7 @@
 #include "ft2_sample_loader.h"
 #include "ft2_tables.h"
 #include "ft2_structs.h"
+#include "mixer/ft2_gaussian.h"
 #include "mixer/ft2_cubic_spline.h"
 #include "mixer/ft2_windowed_sinc.h"
 
@@ -139,73 +140,23 @@
 	editor.updateWindowTitle = true;
 }
 
-// used on external sample load and during sample loading (on some module formats)
-void tuneSample(sample_t *s, const int32_t midCFreq, bool linearPeriodsFlag)
+void setSampleC4Hz(sample_t *s, double dC4Hz)
 {
-	#define MIN_PERIOD (0)
-	#define MAX_PERIOD (((10*12*16)-1)-1) /* -1 (because of bugged amigaPeriods table values) */
+	/* Sets a sample's relative note and finetune according to input C-4 rate.
+	**
+	** Note:
+	** This algorithm uses only 5 finetune bits (like FT2 internally),
+	** so that the resulting finetune is the same when loading it in a
+	** tracker that does support the full 8 finetune bits.
+	*/
 
-	double (*dGetHzFromPeriod)(int32_t) = linearPeriodsFlag ? dLinearPeriod2Hz : dAmigaPeriod2Hz;
-	const uint16_t *periodTab = linearPeriodsFlag ? linearPeriods : amigaPeriods;
+	const double dC4PeriodOffset = (NOTE_C4 * 16) + 16;
+	int32_t period = (int32_t)round(dC4PeriodOffset + (log2(dC4Hz / C4_FREQ) * 12.0 * 16.0));
 
-	if (midCFreq <= 0 || periodTab == NULL)
-	{
-		s->finetune = s->relativeNote = 0;
-		return;
-	}
+	// Hi-limit is A#9 at highest finetune. B-9 is bugged in FT2, don't include it.
+	period = CLAMP(period, 0, (12 * 16 * 10) - 1);
 
-	// handle frequency boundaries first...
-
-	if (midCFreq <= (int32_t)dGetHzFromPeriod(periodTab[MIN_PERIOD]))
-	{
-		s->finetune = -128;
-		s->relativeNote = -48;
-		return;
-	}
-
-	if (midCFreq >= (int32_t)dGetHzFromPeriod(periodTab[MAX_PERIOD]))
-	{
-		s->finetune = 127;
-		s->relativeNote = 71;
-		return;
-	}
-
-	// check if midCFreq is matching any of the non-finetuned note frequencies (C-0..B-9)
-
-	for (int8_t i = 0; i < 10*12; i++)
-	{
-		if (midCFreq == (int32_t)dGetHzFromPeriod(periodTab[16 + (i<<4)]))
-		{
-			s->finetune = 0;
-			s->relativeNote = i - NOTE_C4;
-			return;
-		}
-	}
-
-	// find closest frequency in period table
-
-	int32_t period = MAX_PERIOD;
-	for (; period >= MIN_PERIOD; period--)
-	{
-		const int32_t curr = (int32_t)dGetHzFromPeriod(periodTab[period]);
-		if (midCFreq == curr)
-			break;
-
-		if (midCFreq > curr)
-		{
-			const int32_t next = (int32_t)dGetHzFromPeriod(periodTab[period+1]);
-			const int32_t errorCurr = ABS(curr-midCFreq);
-			const int32_t errorNext = ABS(next-midCFreq);
-
-			if (errorCurr <= errorNext)
-				break; // current is the closest
-
-			period++;
-			break; // current+1 is the closest
-		}
-	}
-
-	s->finetune = ((period & 31) - 16) << 3;
+	s->finetune = ((period & 31) - 16) << 3; // 0..31 -> -128..120
 	s->relativeNote = (int8_t)(((period & ~31) >> 4) - NOTE_C4);
 }
 
@@ -452,7 +403,8 @@
 		return;
 
 	audio.dHz2MixDeltaMul = (double)MIXER_FRAC_SCALE / audioFreq;
-	audio.quickVolRampSamples = (uint32_t)round(audioFreq / (double)FT2_QUICKRAMP_SAMPLES);
+	audio.quickVolRampSamples = (uint32_t)round(audioFreq / (1000.0 / FT2_QUICK_VOLRAMP_MILLISECONDS));
+	audio.fQuickVolRampSamplesMul = (float)(1.0 / (double)audio.quickVolRampSamples);
 
 	for (int32_t bpm = MIN_BPM; bpm <= MAX_BPM; bpm++)
 	{
@@ -477,7 +429,7 @@
 }
 
 // for piano in Instr. Ed. (values outside 0..95 can happen)
-int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote) 
+int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote)
 {
 	assert(note2Period != NULL);
 	if (period > note2Period[0])
@@ -2838,6 +2790,7 @@
 		instr[131] = NULL;
 	}
 
+	freeGaussianTable();
 	freeCubicSplineTable();
 	freeWindowedSincTables();
 }
--- a/src/ft2_replayer.h
+++ b/src/ft2_replayer.h
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include "ft2_unicode.h"
+#include "mixer/ft2_gaussian.h"
 #include "mixer/ft2_cubic_spline.h"
 #include "mixer/ft2_windowed_sinc.h"
 
@@ -47,7 +48,7 @@
 #define C4_FREQ 8363
 #define NOTE_C4 (4*12)
 #define NOTE_OFF 97
-#define MAX_NOTES (10*12*16+16)
+#define MAX_NOTES ((10*12*16)+16)
 #define MAX_PATTERNS 256
 #define MAX_PATT_LEN 256
 #define MAX_INST 128
@@ -57,7 +58,7 @@
 #define INSTR_HEADER_SIZE 263
 #define INSTR_XI_HEADER_SIZE 298
 #define MAX_SAMPLE_LEN 0x3FFFFFFF
-#define FT2_QUICKRAMP_SAMPLES 200
+#define FT2_QUICK_VOLRAMP_MILLISECONDS 5
 #define PROG_NAME_STR "Fasttracker II clone"
 
 enum // sample flags
@@ -287,10 +288,7 @@
 void fixInstrAndSampleNames(int16_t insNum);
 
 void calcReplayerVars(int32_t rate);
-
-// used on external sample load and during sample loading in some module formats
-void tuneSample(sample_t *s, const int32_t midCFreq, bool linearPeriodsFlag);
-
+void setSampleC4Hz(sample_t *s, double dC4Hz);
 void calcReplayerLogTab(void); // for linear period -> hz calculation
 
 double dLinearPeriod2Hz(int32_t period);
--- a/src/ft2_sampling.c
+++ b/src/ft2_sampling.c
@@ -383,7 +383,7 @@
 
 	smpL = &instr[editor.curInstr]->smp[editor.curSmp];
 	freeSample(editor.curInstr, editor.curSmp); // also sets pan to 128 and vol to 64
-	tuneSample(smpL, samplingRate, audio.linearPeriodsFlag);
+	setSampleC4Hz(smpL, samplingRate);
 	smpL->flags |= SAMPLE_16BIT;
 
 	if (sampleInStereo)
@@ -390,7 +390,7 @@
 	{
 		smpR = &instr[editor.curInstr]->smp[editor.curSmp+1];
 		freeSample(editor.curInstr, editor.curSmp+1); // also sets pan to 128 and vol to 64
-		tuneSample(smpR, samplingRate, audio.linearPeriodsFlag);
+		setSampleC4Hz(smpR, samplingRate);
 		smpR->flags |= SAMPLE_16BIT;
 
 		strcpy(smpL->name, "Left sample");
--- a/src/ft2_scrollbars.c
+++ b/src/ft2_scrollbars.c
@@ -87,8 +87,8 @@
 	//x,   y,   w,  h,  type,                 style                         funcOnDown
 	{ 365,  29, 18, 43, SCROLLBAR_VERTICAL,   SCROLLBAR_DYNAMIC_THUMB_SIZE, sbAudOutputSetPos },
 	{ 365, 116, 18, 21, SCROLLBAR_VERTICAL,   SCROLLBAR_DYNAMIC_THUMB_SIZE, sbAudInputSetPos },
-	{ 529, 117, 79, 13, SCROLLBAR_HORIZONTAL, SCROLLBAR_FIXED_THUMB_SIZE,   sbAmp },
-	{ 529, 143, 79, 13, SCROLLBAR_HORIZONTAL, SCROLLBAR_FIXED_THUMB_SIZE,   sbMasterVol },
+	{ 533, 117, 75, 13, SCROLLBAR_HORIZONTAL, SCROLLBAR_FIXED_THUMB_SIZE,   sbAmp },
+	{ 533, 143, 75, 13, SCROLLBAR_HORIZONTAL, SCROLLBAR_FIXED_THUMB_SIZE,   sbMasterVol },
 
 	// ------ CONFIG LAYOUT SCROLLBARS ------
 	//x,   y,  w,  h,  type,                 style                       funcOnDown
--- a/src/mixer/ft2_cubic_spline.c
+++ b/src/mixer/ft2_cubic_spline.c
@@ -1,3 +1,7 @@
+/*
+** Cubic Hermite spline (Catmull-Rom) interpolation LUT generator
+*/
+
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -22,10 +26,15 @@
 		const double x2 = x1 * x1; // x^2
 		const double x3 = x2 * x1; // x^3
 
-		*fPtr++ = (float)(-0.5 * x3 + 1.0 * x2 - 0.5 * x1);
-		*fPtr++ = (float)( 1.5 * x3 - 2.5 * x2 + 1.0);
-		*fPtr++ = (float)(-1.5 * x3 + 2.0 * x2 + 0.5 * x1);
-		*fPtr++ = (float)( 0.5 * x3 - 0.5 * x2);
+		const double t1 = -0.5 * x3 +       x2 - 0.5 * x1;
+		const double t2 =  1.5 * x3 - 2.5 * x2 + 1.0;
+		const double t3 = -1.5 * x3 + 2.0 * x2 + 0.5 * x1;
+		const double t4 =  0.5 * x3 - 0.5 * x2;
+
+		*fPtr++ = (float)t1;
+		*fPtr++ = (float)t2;
+		*fPtr++ = (float)t3;
+		*fPtr++ = (float)t4;
 	}
 
 	return true;
--- a/src/mixer/ft2_cubic_spline.h
+++ b/src/mixer/ft2_cubic_spline.h
@@ -6,12 +6,8 @@
 
 #define CUBIC_SPLINE_TAPS 4
 #define CUBIC_SPLINE_WIDTH_BITS 2 // log2(CUBIC_SPLINE_TAPS)
-
-// 8192 is a good compromise
 #define CUBIC_SPLINE_PHASES 8192
 #define CUBIC_SPLINE_PHASES_BITS 13 // log2(CUBIC_SPLINE_PHASES)
-
-// do not change these!
 #define CUBIC_SPLINE_FSHIFT (MIXER_FRAC_BITS-(CUBIC_SPLINE_PHASES_BITS+CUBIC_SPLINE_WIDTH_BITS))
 #define CUBIC_SPLINE_FMASK ((CUBIC_SPLINE_TAPS*CUBIC_SPLINE_PHASES)-CUBIC_SPLINE_TAPS)
 
--- /dev/null
+++ b/src/mixer/ft2_gaussian.c
@@ -1,0 +1,72 @@
+/*
+** Super Nintendo SPC700 interpolation LUT generator.
+** This has long believed to have a Gaussian curve, but it doesn't.
+**
+** Based on code by Mednafen and nocash:
+** https://forums.nesdev.org/viewtopic.php?t=10586
+**
+*/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include "ft2_gaussian.h"
+#include "../ft2_video.h" // showErrorMsgBox()
+
+#define MY_PI 3.14159265358979323846264338327950288
+
+float *fGaussianLUT = NULL; // globalized
+
+bool calcGaussianTable(void)
+{
+	fGaussianLUT = (float *)malloc(GAUSSIAN_TAPS*GAUSSIAN_PHASES * sizeof (float));
+	if (fGaussianLUT == NULL)
+	{
+		showErrorMsgBox("Not enough memory!");
+		return false;
+	}
+
+	float *fPtr = fGaussianLUT;
+	for (int32_t i = 0; i < GAUSSIAN_PHASES; i++)
+	{
+		const int32_t i1 = GAUSSIAN_PHASES + i;
+		const int32_t i2 = i;
+		const int32_t i3 = (GAUSSIAN_PHASES-1) - i;
+		const int32_t i4 = ((GAUSSIAN_PHASES*2)-1) - i;
+
+		const double x1 = (0.5 + i1) * (1.0 / ((GAUSSIAN_PHASES*4)-1));
+		const double x2 = (0.5 + i2) * (1.0 / ((GAUSSIAN_PHASES*4)-1));
+		const double x3 = (0.5 + i3) * (1.0 / ((GAUSSIAN_PHASES*4)-1));
+		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 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;
+
+		// normalization value (assures unity gain when summing taps)
+		const double dScale = 1.0 / (t1 + t2 + t3 + t4);
+
+		*fPtr++ = (float)(t1 * dScale);
+		*fPtr++ = (float)(t2 * dScale);
+		*fPtr++ = (float)(t3 * dScale);
+		*fPtr++ = (float)(t4 * dScale);
+	}
+
+	return true;
+}
+
+void freeGaussianTable(void)
+{
+	if (fGaussianLUT != NULL)
+	{
+		free(fGaussianLUT);
+		fGaussianLUT = NULL;
+	}
+}
--- /dev/null
+++ b/src/mixer/ft2_gaussian.h
@@ -1,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "ft2_mix.h" // MIXER_FRAC_BITS
+
+#define GAUSSIAN_TAPS 4
+#define GAUSSIAN_WIDTH_BITS 2 // log2(GAUSSIAN_TAPS)
+#define GAUSSIAN_PHASES 8192
+#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)
+
+extern float *fGaussianLUT;
+
+bool calcGaussianTable(void);
+void freeGaussianTable(void);
--- a/src/mixer/ft2_mix.c
+++ b/src/mixer/ft2_mix.c
@@ -1,5 +1,6 @@
 #include <stdint.h>
 #include <stdbool.h>
+#include "../ft2_header.h" // CLAMP16()
 #include "ft2_mix.h"
 #include "ft2_mix_macros.h"
 
@@ -819,6 +820,135 @@
 	SET_BACK_MIXER_POS
 }
 
+static void mix8bNoLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int8_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL
+	GET_MIXER_VARS
+	SET_BASE8
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		samplesLeft -= samplesToMix;
+
+		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
+		}
+
+		HANDLE_SAMPLE_END
+	}
+
+	SET_BACK_MIXER_POS
+}
+
+static void mix8bLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int8_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL
+	GET_MIXER_VARS
+	SET_BASE8
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		samplesLeft -= samplesToMix;
+
+		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
+	}
+
+	SET_BACK_MIXER_POS
+}
+
+static void mix8bBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int8_t *base, *revBase, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac, tmpDelta;
+
+	GET_VOL
+	GET_MIXER_VARS
+	SET_BASE8_BIDI
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		samplesLeft -= samplesToMix;
+
+		START_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
+
+		WRAP_BIDI_LOOP
+	}
+
+	SET_BACK_MIXER_POS
+}
+
+
 static void mix8bRampNoLoop(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int8_t *base, *smpPtr;
@@ -1759,6 +1889,158 @@
 	SET_BACK_MIXER_POS
 }
 
+static void mix8bRampNoLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int8_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL_RAMP
+	GET_MIXER_VARS_RAMP
+	SET_BASE8
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		LIMIT_MIX_NUM_RAMP
+		samplesLeft -= samplesToMix;
+
+		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
+		}
+
+		HANDLE_SAMPLE_END
+	}
+
+	SET_VOL_BACK
+	SET_BACK_MIXER_POS
+}
+
+static void mix8bRampLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int8_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL_RAMP
+	GET_MIXER_VARS_RAMP
+	SET_BASE8
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		LIMIT_MIX_NUM_RAMP
+		samplesLeft -= samplesToMix;
+
+		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
+	}
+
+	SET_VOL_BACK
+	SET_BACK_MIXER_POS
+}
+
+static void mix8bRampBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int8_t *base, *revBase, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac, tmpDelta;
+
+	GET_VOL_RAMP
+	GET_MIXER_VARS_RAMP
+	SET_BASE8_BIDI
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		LIMIT_MIX_NUM_RAMP
+		samplesLeft -= samplesToMix;
+
+		START_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
+
+		WRAP_BIDI_LOOP
+	}
+	
+	SET_VOL_BACK
+	SET_BACK_MIXER_POS
+}
+
 /* ----------------------------------------------------------------------- */
 /*                          16-BIT MIXING ROUTINES                         */
 /* ----------------------------------------------------------------------- */
@@ -2553,6 +2835,134 @@
 	SET_BACK_MIXER_POS
 }
 
+static void mix16bNoLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int16_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL
+	GET_MIXER_VARS
+	SET_BASE16
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		samplesLeft -= samplesToMix;
+
+		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
+		}
+
+		HANDLE_SAMPLE_END
+	}
+
+	SET_BACK_MIXER_POS
+}
+
+static void mix16bLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int16_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL
+	GET_MIXER_VARS
+	SET_BASE16
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		samplesLeft -= samplesToMix;
+
+		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
+	}
+
+	SET_BACK_MIXER_POS
+}
+
+static void mix16bBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int16_t *base, *revBase, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac, tmpDelta;
+
+	GET_VOL
+	GET_MIXER_VARS
+	SET_BASE16_BIDI
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		samplesLeft -= samplesToMix;
+
+		START_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
+
+		WRAP_BIDI_LOOP
+	}
+
+	SET_BACK_MIXER_POS
+}
+
 static void mix16bRampNoLoop(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
 {
 	const int16_t *base, *smpPtr;
@@ -3493,6 +3903,158 @@
 	SET_BACK_MIXER_POS
 }
 
+static void mix16bRampNoLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int16_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL_RAMP
+	GET_MIXER_VARS_RAMP
+	SET_BASE16
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		LIMIT_MIX_NUM_RAMP
+		samplesLeft -= samplesToMix;
+
+		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
+		}
+
+		HANDLE_SAMPLE_END
+	}
+
+	SET_VOL_BACK
+	SET_BACK_MIXER_POS
+}
+
+static void mix16bRampLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int16_t *base, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac;
+
+	GET_VOL_RAMP
+	GET_MIXER_VARS_RAMP
+	SET_BASE16
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		LIMIT_MIX_NUM_RAMP
+		samplesLeft -= samplesToMix;
+
+		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
+	}
+
+	SET_VOL_BACK
+	SET_BACK_MIXER_POS
+}
+
+static void mix16bRampBidiLoopGIntrp(voice_t *v, uint32_t bufferPos, uint32_t numSamples)
+{
+	const int16_t *base, *revBase, *smpPtr;
+	float fSample, *fMixBufferL, *fMixBufferR;
+	int32_t position;
+	float fVolumeLDelta, fVolumeRDelta, fVolumeL, fVolumeR;
+	uint32_t i, samplesToMix, samplesLeft;
+	uint64_t positionFrac, tmpDelta;
+
+	GET_VOL_RAMP
+	GET_MIXER_VARS_RAMP
+	SET_BASE16_BIDI
+
+	samplesLeft = numSamples;
+	while (samplesLeft > 0)
+	{
+		LIMIT_MIX_NUM
+		LIMIT_MIX_NUM_RAMP
+		samplesLeft -= samplesToMix;
+
+		START_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
+	}
+
+	SET_VOL_BACK
+	SET_BACK_MIXER_POS
+}
+
 // -----------------------------------------------------------------------
 
 const mixFunc mixFuncTab[] =
@@ -3515,6 +4077,9 @@
 	(mixFunc)mix8bNoLoopCIntrp,
 	(mixFunc)mix8bLoopCIntrp,
 	(mixFunc)mix8bBidiLoopCIntrp,
+	(mixFunc)mix8bNoLoopGIntrp,
+	(mixFunc)mix8bLoopGIntrp,
+	(mixFunc)mix8bBidiLoopGIntrp,
 
 	// 16-bit
 	(mixFunc)mix16bNoLoop,
@@ -3532,6 +4097,9 @@
 	(mixFunc)mix16bNoLoopCIntrp,
 	(mixFunc)mix16bLoopCIntrp,
 	(mixFunc)mix16bBidiLoopCIntrp,
+	(mixFunc)mix16bNoLoopGIntrp,
+	(mixFunc)mix16bLoopGIntrp,
+	(mixFunc)mix16bBidiLoopGIntrp,
 
 	// volume ramping
 
@@ -3551,6 +4119,9 @@
 	(mixFunc)mix8bRampNoLoopCIntrp,
 	(mixFunc)mix8bRampLoopCIntrp,
 	(mixFunc)mix8bRampBidiLoopCIntrp,
+	(mixFunc)mix8bRampNoLoopGIntrp,
+	(mixFunc)mix8bRampLoopGIntrp,
+	(mixFunc)mix8bRampBidiLoopGIntrp,
 
 	// 16-bit
 	(mixFunc)mix16bRampNoLoop,
@@ -3567,5 +4138,8 @@
 	(mixFunc)mix16bRampBidiLoopS16Intrp,
 	(mixFunc)mix16bRampNoLoopCIntrp,
 	(mixFunc)mix16bRampLoopCIntrp,
-	(mixFunc)mix16bRampBidiLoopCIntrp
+	(mixFunc)mix16bRampBidiLoopCIntrp,
+	(mixFunc)mix16bRampNoLoopGIntrp,
+	(mixFunc)mix16bRampLoopGIntrp,
+	(mixFunc)mix16bRampBidiLoopGIntrp
 };
--- a/src/mixer/ft2_mix.h
+++ b/src/mixer/ft2_mix.h
@@ -2,6 +2,20 @@
 
 #include <stdint.h>
 
+enum
+{
+	// don't change the order of these! (yes, it looks off)
+	INTERPOLATION_DISABLED = 0,
+	INTERPOLATION_SINC8 = 1,
+	INTERPOLATION_LINEAR = 2,
+	INTERPOLATION_SINC16 = 3,
+	INTERPOLATION_CUBIC = 4,
+	INTERPOLATION_GAUSSIAN = 5,
+	// ------
+
+	NUM_INTERPOLATORS,
+};
+
 #define MAX_TAPS 16
 #define MAX_LEFT_TAPS ((MAX_TAPS/2)-1)
 #define MAX_RIGHT_TAPS (MAX_TAPS/2)
--- a/src/mixer/ft2_mix_macros.h
+++ b/src/mixer/ft2_mix_macros.h
@@ -1,6 +1,8 @@
 #pragma once
 
 #include "../ft2_audio.h"
+#include "ft2_cubic_spline.h"
+#include "ft2_gaussian.h"
 #include "ft2_windowed_sinc.h"
 
 /* ----------------------------------------------------------------------- */
@@ -79,7 +81,7 @@
 	fVolumeR += fVolumeRDelta;
 
 /* ----------------------------------------------------------------------- */
-/*                            NO INTERPOLATION                             */
+/*                  NO INTERPOLATION (NEAREST NEIGHBOR)                    */
 /* ----------------------------------------------------------------------- */
 
 #define RENDER_8BIT_SMP \
@@ -163,6 +165,36 @@
 #define RENDER_16BIT_SMP_CINTRP_TAP_FIX \
 	smpTapPtr = (smpPtr <= leftEdgePtr) ? (int16_t *)&v->leftEdgeTaps16[(int32_t)(smpPtr-loopStartPtr)] : (int16_t *)smpPtr; \
 	CUBIC_SPLINE_INTERPOLATION(smpTapPtr, positionFrac, 32768) \
+	*fMixBufferL++ += fSample * fVolumeL; \
+	*fMixBufferR++ += fSample * fVolumeR;
+
+/* ----------------------------------------------------------------------- */
+/*                         GAUSSIAN INTERPOLATION                          */
+/* ----------------------------------------------------------------------- */
+
+// 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).
+*/
+
+#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); \
+}
+
+#define RENDER_8BIT_SMP_GINTRP \
+	GAUSSIAN_INTERPOLATION(smpPtr, positionFrac, 128.0f) \
+	*fMixBufferL++ += fSample * fVolumeL; \
+	*fMixBufferR++ += fSample * fVolumeR;
+
+#define RENDER_16BIT_SMP_GINTRP \
+	GAUSSIAN_INTERPOLATION(smpPtr, positionFrac, 32768.0f) \
 	*fMixBufferL++ += fSample * fVolumeL; \
 	*fMixBufferR++ += fSample * fVolumeR;
 
--- a/src/mixer/ft2_silence_mix.c
+++ b/src/mixer/ft2_silence_mix.c
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <stdint.h>
 #include "../ft2_audio.h"
 
@@ -5,13 +6,13 @@
 
 void silenceMixRoutine(voice_t *v, int32_t numSamples)
 {
-	const uint64_t samplesToMix = (uint64_t)v->delta * (uint32_t)numSamples; // fixed-point
+	assert((uint32_t)numSamples <= UINT16_MAX);
 
-	const uint32_t samples = (uint32_t)(samplesToMix >> MIXER_FRAC_BITS);
-	const uint64_t samplesFrac = (samplesToMix & MIXER_FRAC_MASK) + v->positionFrac;
+	const uint32_t samplesInt = (uint32_t)(v->delta >> MIXER_FRAC_BITS) * (uint32_t)numSamples;
+	const uint64_t samplesFrac = (uint64_t)(v->delta & MIXER_FRAC_SCALE) * (uint32_t)numSamples;
 
-	uint32_t position = v->position + samples + (uint32_t)(samplesFrac >> MIXER_FRAC_BITS);
-	uint64_t positionFrac = samplesFrac & MIXER_FRAC_MASK;
+	uint32_t position = v->position + samplesInt + (uint32_t)(samplesFrac >> MIXER_FRAC_BITS);
+	uint32_t positionFrac = samplesFrac & MIXER_FRAC_MASK;
 
 	if (position < (uint32_t)v->sampleEnd) // we haven't reached the sample's end yet
 	{
@@ -39,6 +40,8 @@
 	{
 		if (v->loopLength >= 2)
 		{
+			// wrap as forward loop (position is inverted if sampling backwards, when needed)
+
 			const uint32_t overflow = position - v->sampleEnd;
 			const uint32_t cycles = overflow / v->loopLength;
 			const uint32_t phase = overflow % v->loopLength;
--- a/src/mixer/ft2_windowed_sinc.c
+++ b/src/mixer/ft2_windowed_sinc.c
@@ -1,5 +1,8 @@
-/* The code in this file is based on code from the OpenMPT project,
-** which shares the same coding license as this project.
+/*
+** Windowed-sinc (Kaiser window) w/ low-pass interpolation LUT generator
+**
+** This was originally based on OpenMPT's source code,
+** but it has been heavily modified.
 */
 
 #include <stdint.h>
@@ -38,36 +41,38 @@
 	return s;
 }
 
-static void getSinc(uint32_t numTaps, float *fLUTPtr, const double beta, const double cutoff)
+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 * cutoff;
-	const double xMul = 1.0 / ((numTaps / 2) * (numTaps / 2));
+	const double kPi = MY_PI * lpCutoff;
+	const double iMul = 1.0 / numPhases;
+	const double xMul = 1.0 / ((filterWidth / 2) * (filterWidth / 2));
 
-	const uint32_t length = numTaps * SINC_PHASES;
-	const uint32_t tapBits = (uint32_t)log2(numTaps);
-	const uint32_t tapsMinus1 = numTaps - 1;
-	const int32_t midPoint = (numTaps / 2) * SINC_PHASES;
+	const uint32_t length = filterWidth * numPhases;
+	const uint32_t tapBits = (uint32_t)log2(filterWidth);
+	const uint32_t phasesBits = (uint32_t)log2(numPhases);
+	const uint32_t tapsMinus1 = filterWidth - 1;
+	const int32_t midPoint = (filterWidth / 2) * numPhases;
 
 	for (uint32_t i = 0; i < length; i++)
 	{
-		const int32_t ix = ((tapsMinus1 - (i & tapsMinus1)) << SINC_PHASES_BITS) + (i >> tapBits);
+		const int32_t ix = ((tapsMinus1 - (i & tapsMinus1)) << phasesBits) + (i >> tapBits);
 
 		double dSinc = 1.0;
 		if (ix != midPoint)
 		{
-			const double x = (ix - midPoint) * (1.0 / SINC_PHASES);
+			const double x = (ix - midPoint) * iMul;
 			const double xPi = x * kPi;
 
-			// sinc with Kaiser window
+			// Kaiser window
 			dSinc = (sin(xPi) * besselI0(beta * sqrt(1.0 - (x * x * xMul)))) / (I0Beta * xPi);
 		}
 
-		fLUTPtr[i] = (float)(dSinc * cutoff);
+		fOutput[i] = (float)(dSinc * lpCutoff);
 	}
 }
 
-static double dBToKaiserBeta(double dB)
+static double decibelsToKaiserBeta(double dB)
 {
 	if (dB < 21.0)
 		return 0.0;
@@ -77,16 +82,20 @@
 		return 0.1102 * (dB - 8.7);
 }
 
+static double rippleToDecibels(double ripple)
+{
+	return 20.0 * log10(ripple);
+}
+
 bool calcWindowedSincTables(void)
 {
-	fKaiserSinc_8  = (float *)malloc(SINC1_TAPS*SINC_PHASES * sizeof (float));
-	fDownSample1_8 = (float *)malloc(SINC1_TAPS*SINC_PHASES * sizeof (float));
-	fDownSample2_8 = (float *)malloc(SINC1_TAPS*SINC_PHASES * sizeof (float));
+	fKaiserSinc_8   = (float *)malloc(SINC1_TAPS*SINC1_PHASES * sizeof (float));
+	fDownSample1_8  = (float *)malloc(SINC1_TAPS*SINC1_PHASES * sizeof (float));
+	fDownSample2_8  = (float *)malloc(SINC1_TAPS*SINC1_PHASES * sizeof (float));
+	fKaiserSinc_16  = (float *)malloc(SINC2_TAPS*SINC2_PHASES * sizeof (float));
+	fDownSample1_16 = (float *)malloc(SINC2_TAPS*SINC2_PHASES * sizeof (float));
+	fDownSample2_16 = (float *)malloc(SINC2_TAPS*SINC2_PHASES * sizeof (float));
 
-	fKaiserSinc_16  = (float *)malloc(SINC2_TAPS*SINC_PHASES * sizeof (float));
-	fDownSample1_16 = (float *)malloc(SINC2_TAPS*SINC_PHASES * sizeof (float));
-	fDownSample2_16 = (float *)malloc(SINC2_TAPS*SINC_PHASES * sizeof (float));
-
 	if (fKaiserSinc_8  == NULL || fDownSample1_8  == NULL || fDownSample2_8  == NULL ||
 		fKaiserSinc_16 == NULL || fDownSample1_16 == NULL || fDownSample2_16 == NULL)
 	{
@@ -94,23 +103,47 @@
 		return false;
 	}
 
-	sincDownsample1Ratio = (uint64_t)(1.1875 * MIXER_FRAC_SCALE);
-	sincDownsample2Ratio = (uint64_t)(1.5    * MIXER_FRAC_SCALE);
+	// resampling ratios for picking suitable downsample LUT
+	const double ratio1 = 1.1875; // fKaiserSinc if <=
+	const double ratio2 = 1.5; // fDownSample1 if <=, fDownSample2 if >
 
-	// sidelobe attenuation (Kaiser beta)
-	const double b0 = dBToKaiserBeta(96.15645);
-	const double b1 = dBToKaiserBeta(85.83249);
-	const double b2 = dBToKaiserBeta(72.22088);
+	sincDownsample1Ratio = (uint64_t)(ratio1 * MIXER_FRAC_SCALE);
+	sincDownsample2Ratio = (uint64_t)(ratio2 * MIXER_FRAC_SCALE);
 
+	/* Max ripple (to be converted into Kaiser beta parameter)
+	**
+	** NOTE:
+	**  These may not be the correct values. Proper calculation
+	**  is needed, but is beyond my knowledge.
+	*/
+	const double maxRipple1 = 1 << 16;
+	const double maxRipple2 = 1 << 14;
+	const double maxRipple3 = 1 << 12;
+
+	// convert max ripple into sidelode attenuation (dB)
+	double db1 = rippleToDecibels(maxRipple1);
+	double db2 = rippleToDecibels(maxRipple2);
+	double db3 = rippleToDecibels(maxRipple3);
+
+	// convert sidelobe attenuation (dB) into Kaiser beta
+	const double b1 = decibelsToKaiserBeta(db1);
+	const double b2 = decibelsToKaiserBeta(db2);
+	const double b3 = decibelsToKaiserBeta(db3);
+
+	// low-pass cutoffs (could maybe use some further tweaking)
+	const double c1 = 1.000;
+	const double c2 = 0.500;
+	const double c3 = 0.425;
+
 	// 8 point
-	getSinc(SINC1_TAPS, fKaiserSinc_8,  b0, 1.0);
-	getSinc(SINC1_TAPS, fDownSample1_8, b1, 0.5);
-	getSinc(SINC1_TAPS, fDownSample2_8, b2, 0.425);
+	generateSincLUT(fKaiserSinc_8,   SINC1_TAPS, SINC1_PHASES, b1, c1);
+	generateSincLUT(fDownSample1_8,  SINC1_TAPS, SINC1_PHASES, b2, c2);
+	generateSincLUT(fDownSample2_8,  SINC1_TAPS, SINC1_PHASES, b3, c3);
 
 	// 16 point
-	getSinc(SINC2_TAPS, fKaiserSinc_16,  b0, 1.0);
-	getSinc(SINC2_TAPS, fDownSample1_16, b1, 0.5);
-	getSinc(SINC2_TAPS, fDownSample2_16, b2, 0.425);
+	generateSincLUT(fKaiserSinc_16,  SINC2_TAPS, SINC2_PHASES, b1, c1);
+	generateSincLUT(fDownSample1_16, SINC2_TAPS, SINC2_PHASES, b2, c2);
+	generateSincLUT(fDownSample2_16, SINC2_TAPS, SINC2_PHASES, b3, c3);
 
 	return true;
 }
--- a/src/mixer/ft2_windowed_sinc.h
+++ b/src/mixer/ft2_windowed_sinc.h
@@ -4,21 +4,19 @@
 #include <stdbool.h>
 #include "ft2_mix.h" // MIXER_FRAC_BITS
 
-// 8192 is a good compromise
-#define SINC_PHASES 8192
-#define SINC_PHASES_BITS 13 // log2(SINC_PHASES)
-
-// do not change these!
-
 #define SINC1_TAPS 8
 #define SINC8_WIDTH_BITS 3 // log2(SINC1_TAPS)
-#define SINC8_FSHIFT (MIXER_FRAC_BITS-(SINC_PHASES_BITS+SINC8_WIDTH_BITS))
-#define SINC8_FMASK ((SINC1_TAPS*SINC_PHASES)-SINC1_TAPS)
+#define SINC1_PHASES 8192
+#define SINC1_PHASES_BITS 13 // log2(SINC1_PHASES)
+#define SINC8_FSHIFT (MIXER_FRAC_BITS-(SINC1_PHASES_BITS+SINC8_WIDTH_BITS))
+#define SINC8_FMASK ((SINC1_TAPS*SINC1_PHASES)-SINC1_TAPS)
 
 #define SINC2_TAPS 16
 #define SINC16_WIDTH_BITS 4 // log2(SINC2_TAPS)
-#define SINC16_FSHIFT (MIXER_FRAC_BITS-(SINC_PHASES_BITS+SINC16_WIDTH_BITS))
-#define SINC16_FMASK ((SINC2_TAPS*SINC_PHASES)-SINC2_TAPS)
+#define SINC2_PHASES 8192
+#define SINC2_PHASES_BITS 13 // log2(SINC2_PHASES)
+#define SINC16_FSHIFT (MIXER_FRAC_BITS-(SINC2_PHASES_BITS+SINC16_WIDTH_BITS))
+#define SINC16_FMASK ((SINC2_TAPS*SINC2_PHASES)-SINC2_TAPS)
 
 extern float *fKaiserSinc_8, *fDownSample1_8, *fDownSample2_8;
 extern float *fKaiserSinc_16, *fDownSample1_16, *fDownSample2_16;
--- a/src/modloaders/ft2_load_s3m.c
+++ b/src/modloaders/ft2_load_s3m.c
@@ -563,7 +563,7 @@
 				s->loopStart = smpHdr.loopStart;
 				s->loopLength = smpHdr.loopEnd - smpHdr.loopStart;
 
-				tuneSample(s, smpHdr.midCFreq, tmpLinearPeriodsFlag);
+				setSampleC4Hz(s, smpHdr.midCFreq);
 
 				if (sample16Bit)
 				{
--- a/src/modloaders/ft2_load_stm.c
+++ b/src/modloaders/ft2_load_stm.c
@@ -212,7 +212,7 @@
 			s->loopLength = hdr.smp[i].loopEnd - hdr.smp[i].loopStart;
 
 			memcpy(s->name, hdr.smp[i].name, 12);
-			tuneSample(s, hdr.smp[i].midCFreq, tmpLinearPeriodsFlag);
+			setSampleC4Hz(s, hdr.smp[i].midCFreq);
 
 			if (s->loopStart < s->length && hdr.smp[i].loopEnd > s->loopStart && hdr.smp[i].loopEnd != 0xFFFF)
 			{
--- a/src/smploaders/ft2_load_aiff.c
+++ b/src/smploaders/ft2_load_aiff.c
@@ -15,7 +15,7 @@
 #include "../ft2_sysreqs.h"
 #include "../ft2_sample_loader.h"
 
-static uint32_t getAIFFSampleRate(uint8_t *in);
+static double getAIFFSampleRate(uint8_t *in);
 static bool aiffIsStereo(FILE *f); // only ran on files that are confirmed to be AIFFs
 
 bool loadAIFF(FILE *f, uint32_t filesize)
@@ -128,7 +128,7 @@
 		return false;
 	}
 
-	uint32_t sampleRate = getAIFFSampleRate(sampleRateBytes);
+	double dSampleRate = getAIFFSampleRate(sampleRateBytes);
 
 	// sample data chunk
 
@@ -578,14 +578,14 @@
 	s->volume = 64;
 	s->panning = 128;
 
-	tuneSample(s, sampleRate, audio.linearPeriodsFlag);
+	setSampleC4Hz(s, dSampleRate);
 
 	return true;
 }
 
-static uint32_t getAIFFSampleRate(uint8_t *in)
+static double getAIFFSampleRate(uint8_t *in)
 {
-	/* 80-bit IEEE-754 to unsigned 32-bit integer (rounded).
+	/* 80-bit IEEE-754 to unsigned double-precision float.
 	** Sign bit is ignored.
 	*/
 
@@ -598,8 +598,7 @@
 	double dExp = exp15 - EXP_BIAS;
 	double dMantissa = mantissa63 / (INT64_MAX+1.0);
 
-	double dResult = (1.0 + dMantissa) * exp2(dExp);
-	return (uint32_t)round(dResult);
+	return (1.0 + dMantissa) * exp2(dExp);
 }
 
 static bool aiffIsStereo(FILE *f) // only ran on files that are confirmed to be AIFFs
--- a/src/smploaders/ft2_load_flac.c
+++ b/src/smploaders/ft2_load_flac.c
@@ -94,7 +94,7 @@
 	FLAC__stream_decoder_finish(decoder);
 	FLAC__stream_decoder_delete(decoder);
 
-	tuneSample(s, sampleRate, audio.linearPeriodsFlag);
+	setSampleC4Hz(s, sampleRate);
 
 	return true;
 
@@ -378,7 +378,7 @@
 			{
 				int16_t *dst16 = (int16_t *)s->dataPtr + samplesRead;
 				for (uint32_t i = 0; i < blockSize; i++)
-					dst16[i] = (int16_t)((src32_L[i] + src32_R[i]) >> (16+1));
+					dst16[i] = (int16_t)((src32_L[i] + src32_R[i]) >> ((24-16)+1));
 			}
 			break;
 
@@ -411,7 +411,7 @@
 			{
 				int16_t *dst16 = (int16_t *)s->dataPtr + samplesRead;
 				for (uint32_t i = 0; i < blockSize; i++)
-					dst16[i] = (int16_t)(src32[i] >> 8);
+					dst16[i] = (int16_t)(src32[i] >> (24-16));
 			}
 			break;
 
--- a/src/smploaders/ft2_load_iff.c
+++ b/src/smploaders/ft2_load_iff.c
@@ -143,7 +143,7 @@
 	s->volume = (uint8_t)volume;
 	s->panning = 128;
 
-	tuneSample(s, sampleRate, audio.linearPeriodsFlag);
+	setSampleC4Hz(s, sampleRate);
 
 	// set name
 	if (namePtr != 0 && nameLen > 0)
--- a/src/smploaders/ft2_load_wav.c
+++ b/src/smploaders/ft2_load_wav.c
@@ -592,7 +592,7 @@
 	bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
 	reallocateSmpData(s, sampleLength, sample16Bit); // readjust memory needed
 
-	tuneSample(s, sampleRate, audio.linearPeriodsFlag);
+	setSampleC4Hz(s, sampleRate);
 
 	s->volume = 64;
 	s->panning = 128;
--