From b74b0bb0ac41c26556a9337168c1eb573f1a9285 Mon Sep 17 00:00:00 2001 From: Anthony Samms Date: Fri, 21 Nov 2025 16:47:05 -0500 Subject: [PATCH] speex resampling instead of libsamplerate? --- libs/audio/Makefile | 30 +++++++++++----- libs/audio/audio.c | 86 +++++++++++++++++++++++++++++---------------- 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/libs/audio/Makefile b/libs/audio/Makefile index 6c1dbcd..735d246 100644 --- a/libs/audio/Makefile +++ b/libs/audio/Makefile @@ -71,10 +71,15 @@ ifneq (,$(findstring MINGW,$(UNAME_S))) CORE_LIBS += -lsndfile endif - ifneq (,$(wildcard /mingw64/lib/libsamplerate.a)) + # Resampling libraries - prefer speexdsp, fallback to libsamplerate + ifneq (,$(wildcard /mingw64/lib/libspeexdsp.a)) + CORE_LIBS += /mingw64/lib/libspeexdsp.a + CFLAGS += -DHAVE_SPEEXDSP + else ifneq (,$(wildcard /mingw64/lib/libsamplerate.a)) CORE_LIBS += /mingw64/lib/libsamplerate.a + CFLAGS += -DHAVE_SAMPLERATE else - CORE_LIBS += -lsamplerate + CORE_LIBS += -lspeexdsp -lsamplerate endif # Windows system libraries (these provide the missing symbols) @@ -103,7 +108,10 @@ else ifeq ($(UNAME_S),Darwin) CORE_LIBS += -lportaudio endif - CORE_LIBS += -lsndfile -lsamplerate + CORE_LIBS += -lsndfile + + # Resampling libraries - prefer speexdsp, fallback to libsamplerate + CORE_LIBS += -lspeexdsp -lsamplerate # macOS frameworks LIBS = $(CORE_LIBS) -framework CoreAudio -framework AudioToolbox -framework AudioUnit @@ -127,12 +135,15 @@ else ifeq ($(UNAME_S),Linux) CORE_LIBS += -lsndfile - # Check for libsamplerate - ifeq ($(call check_lib,samplerate),yes) + # Check for speexdsp (preferred) or libsamplerate + ifeq ($(call check_lib,speexdsp),yes) + CORE_LIBS += -lspeexdsp + CFLAGS += -DHAVE_SPEEXDSP + else ifeq ($(call check_lib,samplerate),yes) CORE_LIBS += -lsamplerate CFLAGS += -DHAVE_SAMPLERATE else - $(warning libsamplerate not found - building without sample rate conversion) + $(warning Neither speexdsp nor libsamplerate found - building without sample rate conversion) endif # Audio backend libraries (optional) @@ -188,7 +199,7 @@ else ifeq ($(UNAME_S),Linux) else # Generic Unix fallback - minimal dependencies LIBNAME = libaudio.so - LIBS = -lportaudio -lsndfile -lpthread -lm + LIBS = -lportaudio -lsndfile -lspeexdsp -lpthread -lm OBJ_EXT = .o endif @@ -250,7 +261,7 @@ ifneq (,$(findstring MINGW,$(UNAME_S))) @echo "Checking for Windows-specific libportaudio:" @ls -la ./libportaudio-win.a 2>/dev/null && echo "✓ Found local libportaudio-win.a" || echo "✗ No local libportaudio-win.a" @echo "Available system static libraries:" - @ls /mingw64/lib/lib{portaudio,sndfile,samplerate,FLAC,vorbis*,ogg}.a 2>/dev/null || echo "None found" + @ls /mingw64/lib/lib{portaudio,sndfile,speexdsp,samplerate,FLAC,vorbis*,ogg}.a 2>/dev/null || echo "None found" @echo "Static pthread library:" @ls /mingw64/lib/libwinpthread.a 2>/dev/null && echo "✓ Found libwinpthread.a" || echo "✗ Missing libwinpthread.a" @echo "Libraries to link: $(LIBS)" @@ -262,6 +273,7 @@ ifdef PKG_CONFIG @echo "pkg-config available: yes" @echo -n "PortAudio: "; pkg-config --exists portaudio-2.0 && echo "✓" || echo "✗" @echo -n "libsndfile: "; pkg-config --exists sndfile && echo "✓" || echo "✗" + @echo -n "speexdsp: "; pkg-config --exists speexdsp && echo "✓" || echo "✗" @echo -n "libsamplerate: "; pkg-config --exists samplerate && echo "✓" || echo "✗" @echo -n "ALSA: "; pkg-config --exists alsa && echo "✓" || echo "✗" @echo -n "PulseAudio: "; pkg-config --exists libpulse && echo "✓" || echo "✗" @@ -288,7 +300,7 @@ list-libs: @echo "All libraries: $(LIBS)" # Build with only essential libraries (fallback) -minimal: override LIBS = -lportaudio -lsndfile -lpthread -lm +minimal: override LIBS = -lportaudio -lsndfile -lspeexdsp -lpthread -lm minimal: $(LIBNAME) @echo "Built minimal version with basic dependencies only" diff --git a/libs/audio/audio.c b/libs/audio/audio.c index 9f9759f..bfb6efc 100644 --- a/libs/audio/audio.c +++ b/libs/audio/audio.c @@ -15,7 +15,8 @@ #include #include #include -#include +//#include +#include #include #include #include @@ -78,7 +79,7 @@ typedef struct music { // Music context data, required for music streaming typedef struct music_ctx { SNDFILE *snd_file; - SRC_STATE *resampler; + SpeexResamplerState *resampler; double src_ratio; } music_ctx; @@ -693,28 +694,50 @@ sound load_sound_from_wave(wave wave) { if (wave.sampleRate != AUDIO.System.sampleRate) { TRACELOG(LOG_INFO, "Resampling wave from %d Hz to %f Hz", wave.sampleRate, AUDIO.System.sampleRate); - SRC_DATA src_data; - src_data.data_in = wave.data; - src_data.input_frames = wave.frameCount; - src_data.src_ratio = AUDIO.System.sampleRate / wave.sampleRate; - src_data.output_frames = (sf_count_t)(wave.frameCount * src_data.src_ratio); + int error = 0; + SpeexResamplerState *resampler = speex_resampler_init( + wave.channels, + wave.sampleRate, + (int)AUDIO.System.sampleRate, + SPEEX_RESAMPLER_QUALITY_DESKTOP, + &error + ); - resampled_wave.data = calloc(src_data.output_frames * wave.channels, sizeof(float)); - if (resampled_wave.data == NULL) { - TRACELOG(LOG_WARNING, "Failed to allocate memory for resampling"); + if (error || resampler == NULL) { + TRACELOG(LOG_WARNING, "Failed to initialize resampler: %d", error); return sound; } - src_data.data_out = resampled_wave.data; - int error = src_simple(&src_data, SRC_SINC_BEST_QUALITY, wave.channels); - if (error) { - TRACELOG(LOG_WARNING, "Resampling failed: %s", src_strerror(error)); + spx_uint32_t out_frames = (spx_uint32_t)(wave.frameCount * AUDIO.System.sampleRate / wave.sampleRate) + 10; + + resampled_wave.data = calloc(out_frames * wave.channels, sizeof(float)); + if (resampled_wave.data == NULL) { + TRACELOG(LOG_WARNING, "Failed to allocate memory for resampling"); + speex_resampler_destroy(resampler); + return sound; + } + + spx_uint32_t in_len = wave.frameCount; + spx_uint32_t out_len = out_frames; + + error = speex_resampler_process_interleaved_float( + resampler, + wave.data, + &in_len, + resampled_wave.data, + &out_len + ); + + speex_resampler_destroy(resampler); + + if (error != RESAMPLER_ERR_SUCCESS) { + TRACELOG(LOG_WARNING, "Resampling failed with error: %d", error); FREE(resampled_wave.data); return sound; } - resampled_wave.frameCount = src_data.output_frames_gen; - resampled_wave.sampleRate = AUDIO.System.sampleRate; + resampled_wave.frameCount = out_len; + resampled_wave.sampleRate = (int)AUDIO.System.sampleRate; resampled_wave.channels = wave.channels; resampled_wave.sampleSize = wave.sampleSize; is_resampled = true; @@ -913,9 +936,9 @@ music load_music_stream(const char* filename) { if (sf_info.samplerate != AUDIO.System.sampleRate) { TRACELOG(LOG_INFO, "Resampling music from %d Hz to %f Hz", sf_info.samplerate, AUDIO.System.sampleRate); int error; - ctx->resampler = src_new(SRC_SINC_FASTEST, sf_info.channels, &error); + ctx->resampler = speex_resampler_init(sf_info.channels, sf_info.samplerate, AUDIO.System.sampleRate, SPEEX_RESAMPLER_QUALITY_DESKTOP, &error); if (ctx->resampler == NULL) { - TRACELOG(LOG_WARNING, "Failed to create resampler: %s", src_strerror(error)); + TRACELOG(LOG_WARNING, "Failed to create resampler"); free(ctx); sf_close(snd_file); return music; @@ -962,7 +985,7 @@ void unload_music_stream(music music) { if (music.ctxData) { music_ctx *ctx = (music_ctx *)music.ctxData; if (ctx->snd_file) sf_close(ctx->snd_file); - if (ctx->resampler) src_delete(ctx->resampler); + if (ctx->resampler) speex_resampler_destroy(ctx->resampler); free(ctx); } unload_audio_stream(music.stream); @@ -1036,19 +1059,22 @@ void update_music_stream(music music) { sf_count_t frames_written = 0; if (ctx->resampler) { - SRC_DATA src_data; - src_data.data_in = input_ptr; - src_data.input_frames = frames_read; - src_data.data_out = buffer_data + subBufferOffset; - src_data.output_frames = subBufferSizeFrames; - src_data.src_ratio = ctx->src_ratio; - src_data.end_of_input = (frames_read < frames_to_read); + spx_uint32_t in_len = frames_read; + spx_uint32_t out_len = subBufferSizeFrames; - int error = src_process(ctx->resampler, &src_data); - if (error) { - TRACELOG(LOG_WARNING, "Resampling failed: %s", src_strerror(error)); + int error = speex_resampler_process_interleaved_float( + ctx->resampler, + input_ptr, + &in_len, + buffer_data + subBufferOffset, + &out_len + ); + + if (error != RESAMPLER_ERR_SUCCESS) { + TRACELOG(LOG_WARNING, "Resampling failed with error: %d", error); } - frames_written = src_data.output_frames_gen; + + frames_written = out_len; } else { if (music.stream.channels == 1 && AUDIO_DEVICE_CHANNELS == 2) { for (int j = 0; j < frames_read; j++) {