shithub: cstory

Download patch

ref: 48386d443bf6e1e2828b9347fae7e08cdb745751
parent: 39067057c137c809b1ba1d58004e4863726f226c
author: Clownacy <Clownacy@users.noreply.github.com>
date: Mon Aug 31 12:07:25 EDT 2020

Add optional Lanczos filter to the audio mixer

Should be higher-quality than the linear-interpolator, but also much
slower, so it's disabled by default.

--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,7 @@
 option(FIX_BUGS "Fix various bugs in the game" OFF)
 option(DEBUG_SAVE "Re-enable the ability to drag-and-drop save files onto the window" OFF)
 option(DOCONFIG "Compile a DoConfig clone tool - not useful for console ports" ON)
+option(LANCZOS_RESAMPLER "Use Lanczos filtering for audio resampling instead of linear-interpolation (Lanczos is more performance-intensive, but higher quality)" OFF)
 
 set(BACKEND_RENDERER "SDLTexture" CACHE STRING "Which renderer the game should use: 'OpenGL3' for an OpenGL 3.2 renderer, 'OpenGLES2' for an OpenGL ES 2.0 renderer, 'SDLTexture' for SDL2's hardware-accelerated Texture API, 'SDLSurface' for SDL2's software-rendered Surface API, 'Wii U' for the Wii U's hardware-accelerated GX2 API, or 'Software' for a handwritten software renderer")
 set(BACKEND_AUDIO "SDL2" CACHE STRING "Which audio backend the game should use: 'SDL2', 'miniaudio', 'WiiU-Hardware', 'WiiU-Software', or 'Null'")
@@ -266,6 +267,10 @@
 
 if(DEBUG_SAVE)
 	target_compile_definitions(CSE2 PRIVATE DEBUG_SAVE)
+endif()
+
+if(LANCZOS_RESAMPLER)
+	target_compile_definitions(CSE2 PRIVATE LANCZOS_RESAMPLER)
 endif()
 
 if(PKG_CONFIG_STATIC_LIBS)
--- a/README.md
+++ b/README.md
@@ -95,6 +95,7 @@
 `-DDEBUG_SAVE=ON` | Re-enable the ability to drag-and-drop save files onto the window
 `-DDOCONFIG=OFF` | Disable compiling the DoConfig tool (it is not useful for console ports)
 `-DDOCONFIG_LEGACY_OPENGL=ON` | Make DoConfig use OpenGL 2.1 instead of OpenGL 3.2 (useful for older/limited platforms)
+`-DLANCZOS_RESAMPLER=ON` | Use Lanczos filtering for audio resampling instead of linear-interpolation (Lanczos is more performance-intensive, but higher quality)
 `-DBACKEND_RENDERER=OpenGL3` | Render with OpenGL 3.2 (hardware-accelerated)
 `-DBACKEND_RENDERER=OpenGLES2` | Render with OpenGL ES 2.0 (hardware-accelerated)
 `-DBACKEND_RENDERER=SDLTexture` | (Default) Render with SDL2's Texture API (hardware-accelerated) (note: requires `-DBACKEND_PLATFORM=SDL2`)
--- a/src/Backends/Audio/SoftwareMixer.cpp
+++ b/src/Backends/Audio/SoftwareMixer.cpp
@@ -51,7 +51,11 @@
 	if (sound == NULL)
 		return NULL;
 
-	sound->samples = (signed char*)malloc(length + 1);
+#ifdef LANCZOS_RESAMPLER
+	sound->samples = (signed char*)malloc(length);
+#else
+	sound->samples = (signed char*)malloc(length + 1);	// +1 for the linear-interpolator
+#endif
 
 	if (sound->samples == NULL)
 	{
@@ -96,7 +100,9 @@
 	sound->playing = true;
 	sound->looping = looping;
 
+#ifndef LANCZOS_RESAMPLER
 	sound->samples[sound->frames] = looping ? sound->samples[0] : 0;	// For the linear interpolator
+#endif
 }
 
 void Mixer_StopSound(Mixer_Sound *sound)
@@ -143,15 +149,43 @@
 
 			for (size_t frames_done = 0; frames_done < frames_total; ++frames_done)
 			{
+			#ifdef LANCZOS_RESAMPLER
+				// Perform Lanczos resampling
+				const int kernel_radius = 2;
+
+				double accumulator = 0;
+
+				for (int i = -MIN(kernel_radius - 1, sound->position); i <= kernel_radius; ++i)
+				{
+					const signed char input_sample = sound->samples[(sound->position + i) % sound->frames];
+
+					const double kernel_input = ((double)sound->position_subsample / 0x10000) - i;
+
+					if (kernel_input == 0.0)
+					{
+						accumulator += input_sample;
+					}
+					else
+					{
+						const double nx = 3.14159265358979323846 * kernel_input;
+						const double nxa = nx / kernel_radius;
+
+						accumulator += input_sample * (sin(nx) / nx) * (sin(nxa) / nxa);
+					}
+				}
+
+				const short output_sample = (short)(accumulator * 0x100);
+			#else
 				// Perform linear interpolation
-				const unsigned char subsample = sound->position_subsample >> 8;
+				const unsigned char interpolation_scale = sound->position_subsample >> 8;
 
-				const short interpolated_sample = sound->samples[sound->position] * (0x100 - subsample)
-				                                + sound->samples[sound->position + 1] * subsample;
+				const short output_sample = sound->samples[sound->position] * (0x100 - interpolation_scale)
+				                          + sound->samples[sound->position + 1] * interpolation_scale;
+			#endif
 
 				// Mix, and apply volume
-				*stream_pointer++ += (interpolated_sample * sound->volume_l) >> 8;
-				*stream_pointer++ += (interpolated_sample * sound->volume_r) >> 8;
+				*stream_pointer++ += (output_sample * sound->volume_l) >> 8;
+				*stream_pointer++ += (output_sample * sound->volume_r) >> 8;
 
 				// Increment sample
 				const unsigned long next_position_subsample = sound->position_subsample + sound->advance_delta;