shithub: ft²

Download patch

ref: 972dbd93232a868c586c79121db648fb0c397d33
parent: 65356a7a59258839e2c91869a43fc76e547c3025
author: Olav Sørensen <olav.sorensen@live.no>
date: Fri Dec 20 17:00:22 EST 2024

Scopes interpolation fix

--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -380,8 +380,8 @@
 		{
 			v->fVolume = ch->fFinalVol;
 
-			// set scope volume
-			const int32_t scopeVolume = (int32_t)((SCOPE_HEIGHT * ch->fFinalVol) + 0.5f); // rounded
+			// scale volume for scopes (0..128)
+			const int32_t scopeVolume = (int32_t)((ch->fFinalVol * 128.0f) + 0.5f); // rounded
 			v->scopeVolume = (uint8_t)scopeVolume;
 		}
 
--- a/src/scopes/ft2_scope_macros.h
+++ b/src/scopes/ft2_scope_macros.h
@@ -10,7 +10,7 @@
 /* ----------------------------------------------------------------------- */
 
 #define SCOPE_REGS_NO_LOOP \
-	const int32_t volume = s->volume; \
+	const int32_t volume = s->volume * SCOPE_HEIGHT; \
 	const int32_t sampleEnd = s->sampleEnd; \
 	const uint64_t delta = s->drawDelta; \
 	const uint32_t color = video.palette[PAL_PATTEXT]; \
@@ -20,7 +20,7 @@
 	uint64_t positionFrac = 0;
 
 #define SCOPE_REGS_LOOP \
-	const int32_t volume = s->volume; \
+	const int32_t volume = s->volume * SCOPE_HEIGHT; \
 	const int32_t sampleEnd = s->sampleEnd; \
 	const int32_t loopStart = s->loopStart; \
 	const int32_t loopLength = s->loopLength; \
@@ -32,7 +32,7 @@
 	uint64_t positionFrac = 0;
 
 #define SCOPE_REGS_BIDI \
-	const int32_t volume = s->volume; \
+	const int32_t volume = s->volume * SCOPE_HEIGHT; \
 	const int32_t sampleEnd = s->sampleEnd; \
 	const int32_t loopStart = s->loopStart; \
 	const int32_t loopLength = s->loopLength; \
@@ -74,19 +74,38 @@
 		const int32_t f = (frac) >> (SCOPE_FRAC_BITS-15); \
 		sample = (s8[0] << 8) + ((((s8[1] - s8[0]) << 8) * f) >> 15); \
 	} \
-	else \
+	else /* interpolate scopes using 6-tap cubic B-spline */ \
 	{ \
 		const float *t = fScopeIntrpLUT + (((frac) >> (SCOPE_FRAC_BITS-SCOPE_INTRP_PHASES_BITS)) * SCOPE_INTRP_TAPS); \
 		\
-		/* This has a delay of 2 samples, but that's acceptable for a tracker scope. */ \
-		/* Not having to look-up previous samples significantly reduces the */ \
-		/* logic needed in the scopes. */ \
-		float fSample = (s8[0] * t[0]) + \
-		                (s8[1] * t[1]) + \
-		                (s8[2] * t[2]) + \
-		                (s8[3] * t[3]) + \
-		                (s8[4] * t[4]) + \
-		                (s8[5] * t[5]); \
+		/* get correct negative tap sample points */ \
+		int32_t p1 = pos - 2; \
+		int32_t p2 = pos - 1; \
+		float fSample; \
+		if (s->loopType != LOOP_DISABLED && s->hasLooped && (int32_t)pos-2 < (int32_t)s->loopStart) \
+		{ \
+			const int32_t overflow1 = (int32_t)s->loopStart - p1; \
+			const int32_t overflow2 = (int32_t)s->loopStart - p2; \
+			if (s->loopType == LOOP_BIDI) /* direction is always backwards at this point */ \
+			{ \
+				p1 = s->loopStart + overflow1; \
+				if (overflow2 > 0) \
+					p2 = s->loopStart + overflow2; \
+			} \
+			else \
+			{ \
+				p1 = s->loopEnd - overflow1; \
+				if (overflow2 > 0) \
+					p2 = s->loopEnd - overflow2; \
+			} \
+		} \
+		\
+		fSample = (s->base8[p1] * t[0]) + \
+		          (s->base8[p2] * t[1]) + \
+		          (       s8[0] * t[2]) + \
+		          (       s8[1] * t[3]) + \
+		          (       s8[2] * t[4]) + \
+		          (       s8[3] * t[5]); \
 		sample = (int32_t)(fSample * 256.0f); \
 	}
 
@@ -101,30 +120,50 @@
 		const int32_t f = (frac) >> (SCOPE_FRAC_BITS-15); \
 		sample = s16[0] + (((s16[1] - s16[0]) * f) >> 15); \
 	} \
-	else \
+	else /* interpolate scopes using 6-tap cubic B-spline */ \
 	{ \
 		const float *t = fScopeIntrpLUT + (((frac) >> (SCOPE_FRAC_BITS-SCOPE_INTRP_PHASES_BITS)) * SCOPE_INTRP_TAPS); \
 		\
-		/* This has a delay of 2 samples, but that's acceptable for a tracker scope. */ \
-		/* Not having to look-up previous samples significantly reduces the */ \
-		/* logic needed in the scopes. */ \
-		float fSample = (s16[0] * t[0]) + \
-		                (s16[1] * t[1]) + \
-		                (s16[2] * t[2]) + \
-		                (s16[3] * t[3]) + \
-		                (s16[4] * t[4]) + \
-		                (s16[5] * t[5]); \
+		/* get correct negative tap sample points */ \
+		int32_t p1 = pos - 2; \
+		int32_t p2 = pos - 1; \
+		float fSample; \
+		if (s->loopType != LOOP_DISABLED && s->hasLooped && (int32_t)pos-2 < (int32_t)s->loopStart) \
+		{ \
+			const int32_t overflow1 = (int32_t)s->loopStart - p1; \
+			const int32_t overflow2 = (int32_t)s->loopStart - p2; \
+			if (s->loopType == LOOP_BIDI) /* direction is always backwards at this point */ \
+			{ \
+				p1 = s->loopStart + overflow1; \
+				if (overflow2 > 0) \
+					p2 = s->loopStart + overflow2; \
+			} \
+			else \
+			{ \
+				p1 = s->loopEnd - overflow1; \
+				if (overflow2 > 0) \
+					p2 = s->loopEnd - overflow2; \
+			} \
+		} \
+		\
+		fSample = (s->base16[p1] * t[0]) + \
+		          (s->base16[p2] * t[1]) + \
+		          (       s16[0] * t[2]) + \
+		          (       s16[1] * t[3]) + \
+		          (       s16[2] * t[4]) + \
+		          (       s16[3] * t[5]); \
+		\
 		sample = (int32_t)fSample; \
 	}
 #define SCOPE_GET_SMP8 \
 	if (s->active) \
-		sample = (s->base8[position] * volume) >> 8; \
+		sample = (s->base8[position] * volume) >> (8+7); \
 	else \
 		sample = 0;
 
 #define SCOPE_GET_SMP16 \
 	if (s->active) \
-		sample = (s->base16[position] * volume) >> 16; \
+		sample = (s->base16[position] * volume) >> (16+7); \
 	else \
 		sample = 0;
 
@@ -132,7 +171,7 @@
 	if (s->active) \
 	{ \
 		GET_BIDI_POSITION \
-		sample = (s->base8[actualPos] * volume) >> 8; \
+		sample = (s->base8[actualPos] * volume) >> (8+7); \
 	} \
 	else \
 	{ \
@@ -143,7 +182,7 @@
 	if (s->active) \
 	{ \
 		GET_BIDI_POSITION \
-		sample = (s->base16[actualPos] * volume) >> 16; \
+		sample = (s->base16[actualPos] * volume) >> (16+7); \
 	} \
 	else \
 	{ \
@@ -154,7 +193,7 @@
 	if (s->active) \
 	{ \
 		INTERPOLATE_SMP8(position, (uint32_t)positionFrac) \
-		sample = (sample * volume) >> 16; \
+		sample = (sample * volume) >> (16+7); \
 	} \
 	else \
 	{ \
@@ -165,7 +204,7 @@
 	if (s->active) \
 	{ \
 		INTERPOLATE_SMP16(position, (uint32_t)positionFrac) \
-		sample = (sample * volume) >> 16; \
+		sample = (sample * volume) >> (16+7); \
 	} \
 	else \
 	{ \
@@ -183,7 +222,7 @@
 	{ \
 		GET_BIDI_POSITION \
 		INTERPOLATE_SMP8(actualPos, samplingBackwards ? ((uint32_t)positionFrac ^ UINT32_MAX) : (uint32_t)positionFrac) \
-		sample = (sample * volume) >> 16; \
+		sample = (sample * volume) >> (16+7); \
 	} \
 	else \
 	{ \
@@ -195,7 +234,7 @@
 	{ \
 		GET_BIDI_POSITION \
 		INTERPOLATE_SMP16(actualPos, samplingBackwards ? ((uint32_t)positionFrac ^ UINT32_MAX) : (uint32_t)positionFrac) \
-		sample = (sample * volume) >> 16; \
+		sample = (sample * volume) >> (16+7); \
 	} \
 	else \
 	{ \
@@ -246,6 +285,8 @@
 			position = loopStart + ((position - sampleEnd) % loopLength); \
 		else \
 			position = loopStart; \
+		\
+		s->hasLooped = true; \
 	}
 
 #define SCOPE_HANDLE_POS_BIDI \
@@ -263,4 +304,6 @@
 		{ \
 			position = loopStart; \
 		} \
+		\
+		s->hasLooped = true; \
 	}
--- a/src/scopes/ft2_scopes.c
+++ b/src/scopes/ft2_scopes.c
@@ -314,10 +314,12 @@
 
 	tempState.sample16Bit = sample16Bit;
 	tempState.loopType = loopType;
+	tempState.hasLooped = false;
 	tempState.samplingBackwards = false;
 	tempState.sampleEnd = (loopType == LOOP_OFF) ? length : loopEnd;
 	tempState.loopStart = loopStart;
 	tempState.loopLength = loopLength;
+	tempState.loopEnd = loopEnd;
 	tempState.position = playOffset;
 	tempState.positionFrac = 0;
 	
@@ -376,6 +378,8 @@
 				{
 					s.position = s.loopStart;
 				}
+
+				s.hasLooped = true;
 			}
 			else if (s.loopType == LOOP_FORWARD)
 			{
@@ -383,6 +387,8 @@
 					s.position = s.loopStart + ((s.position - s.sampleEnd) % s.loopLength);
 				else
 					s.position = s.loopStart;
+
+				s.hasLooped = true;
 			}
 			else // no loop
 			{
--- a/src/scopes/ft2_scopes.h
+++ b/src/scopes/ft2_scopes.h
@@ -35,9 +35,9 @@
 	volatile bool active;
 	const int8_t *base8;
 	const int16_t *base16;
-	bool wasCleared, sample16Bit, samplingBackwards;
+	bool wasCleared, sample16Bit, samplingBackwards, hasLooped;
 	uint8_t loopType;
-	int32_t volume, loopStart, loopLength, sampleEnd, position;
+	int32_t volume, loopStart, loopLength, loopEnd, sampleEnd, position;
 	uint64_t delta, drawDelta, positionFrac;
 } scope_t;
 
--