From db1bc8c4b7402c33bf1a09beb096c281f7ab3ece Mon Sep 17 00:00:00 2001 From: David Reid Date: Fri, 13 Feb 2026 18:50:42 +1000 Subject: [PATCH] Resampler: Remove dependency on `ma_lpf`. This makes the resampler a bit more self-contained and allows us to do some resampler-specific optimizations to the filtering process. It also reduces the size of the `ma_linear_resampler` struct. --- miniaudio.h | 86 +++++++++++++---------------------------------------- 1 file changed, 21 insertions(+), 65 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index e65d0cf4..4a13b241 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -5636,26 +5636,20 @@ typedef struct float* f32; ma_int16* s16; } x1; /* The next input frame. */ - ma_lpf lpf; /* - We have some heap allocated data for the sample cache and the LPF state. This is an array of - either floats of int32s depending on whether or not `format` is f32 or s16. Below is the - structure: - - | Cached Samples | `channels` | - | LPF State | (`lpfOrder` / 2) * (4 + (`channels` * 2)) | - The low-pass filter is achieved with a series of 2nd-order biquads. This means there is one LPF state for each `lpfOrder`, divided by two. So if `lpfOrder` is 4, there will be 2 LPF states in the array. The structure of each LPF state is as follows: + +-------------------+------------+ | Biquad b1 | 1 | | Biquad b2 | 1 | | Biquad a1 | 1 | | Biquad a2 | 1 | | Biquad register 1 | `channels` | | Biquad register 2 | `channels` | + +-------------------+------------+ If you are familiar with biquads, you'll note that b0 and a0 are missing. This is because b0 is set the same value as b2, so we just reuse b2, and a0 is just not used. @@ -5664,7 +5658,7 @@ typedef struct { float* f32; ma_int32* s32; - } heap; + } lpf; /* Memory management. */ void* _pHeap; @@ -58957,8 +58951,6 @@ typedef struct size_t x0Offset; size_t x1Offset; size_t lpfOffset; - size_t cachedSamplesOffset; - size_t lpfStateOffset; } ma_linear_resampler_heap_layout; @@ -58981,16 +58973,14 @@ static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* p } /* A cache of samples unrelated to the LPF comes first and needs to be skipped. */ -#define MA_RESAMPLER_GET_LPF_HEAP_F32(pResampler, lpfIndex) pResampler->heap.f32 + pResampler->channels + (lpfIndex * (4 + (pResampler->channels*2))) -#define MA_RESAMPLER_GET_LPF_HEAP_S32(pResampler, lpfIndex) pResampler->heap.s32 + pResampler->channels + (lpfIndex * (4 + (pResampler->channels*2))) +#define MA_RESAMPLER_GET_LPF_HEAP_F32(pResampler, lpfIndex) pResampler->lpf.f32 + (lpfIndex * (4 + (pResampler->channels*2))) +#define MA_RESAMPLER_GET_LPF_HEAP_S32(pResampler, lpfIndex) pResampler->lpf.s32 + (lpfIndex * (4 + (pResampler->channels*2))) -static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, void* pHeap, ma_linear_resampler_heap_layout* pHeapLayout, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) +static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) { - ma_result result; ma_uint32 gcf; ma_uint32 lpfSampleRate; double lpfCutoffFrequency; - ma_lpf_config lpfConfig; ma_uint32 oldSampleRateOut; /* Required for adjusting time advance down the bottom. */ ma_uint32 minSampleRate; ma_uint32 maxSampleRate; @@ -59085,25 +59075,6 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes } } - - /* Old LPF. Will be removed later. */ - lpfConfig = ma_lpf_config_init(pResampler->format, pResampler->channels, lpfSampleRate, lpfCutoffFrequency, pResampler->lpfOrder); - - /* - If the resampler is already initialized we don't want to do a fresh initialization of the low-pass filter because it will result in the cached frames - getting cleared. Instead we re-initialize the filter which will maintain any cached frames. - */ - if (isResamplerAlreadyInitialized) { - result = ma_lpf_reinit(&lpfConfig, &pResampler->lpf); - } else { - result = ma_lpf_init_preallocated(&lpfConfig, ma_offset_ptr(pHeap, pHeapLayout->lpfOffset), &pResampler->lpf); - } - - if (result != MA_SUCCESS) { - return result; - } - - pResampler->inAdvanceInt = pResampler->sampleRateIn / pResampler->sampleRateOut; pResampler->inAdvanceFrac = pResampler->sampleRateIn % pResampler->sampleRateOut; @@ -59159,30 +59130,8 @@ static ma_result ma_linear_resampler_get_heap_layout(const ma_linear_resampler_c pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); - /* LPF */ + /* LPF. */ pHeapLayout->lpfOffset = pHeapLayout->sizeInBytes; - { - ma_result result; - size_t lpfHeapSizeInBytes; - ma_lpf_config lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, 1, 1, lpfOrder); /* Sample rate and cutoff frequency do not matter. */ - - result = ma_lpf_get_heap_size(&lpfConfig, &lpfHeapSizeInBytes); - if (result != MA_SUCCESS) { - return result; - } - - pHeapLayout->sizeInBytes += lpfHeapSizeInBytes; - } - - - /* Cached samples. These are always stored as either f32 or s32, so either way it's 4 bytes per sample, even when the format is s16. */ - pHeapLayout->cachedSamplesOffset = pHeapLayout->sizeInBytes; - { - pHeapLayout->sizeInBytes += sizeof(ma_int32) * pConfig->channels; - } - - /* LPF state. */ - pHeapLayout->lpfStateOffset = pHeapLayout->sizeInBytes; { pHeapLayout->sizeInBytes += sizeof(ma_int32) * ((lpfOrder / 2) * (4 + (pConfig->channels * 2))); } @@ -59259,10 +59208,10 @@ MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler pResampler->x1.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x1Offset); } - pResampler->heap.s32 = (ma_int32*)ma_offset_ptr(pHeap, heapLayout.cachedSamplesOffset); + pResampler->lpf.s32 = (ma_int32*)ma_offset_ptr(pHeap, heapLayout.lpfOffset); /* Setting the rate will set up the filter and time advances for us. */ - result = ma_linear_resampler_set_rate_internal(pResampler, pHeap, &heapLayout, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); + result = ma_linear_resampler_set_rate_internal(pResampler, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); if (result != MA_SUCCESS) { return result; } @@ -59315,8 +59264,6 @@ MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma return; } - ma_lpf_uninit(&pResampler->lpf, pAllocationCallbacks); - if (pResampler->_ownsHeap) { ma_free(pResampler->_pHeap, pAllocationCallbacks); } @@ -61273,7 +61220,7 @@ MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pRe MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { - return ma_linear_resampler_set_rate_internal(pResampler, NULL, NULL, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); + return ma_linear_resampler_set_rate_internal(pResampler, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); } MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut) @@ -61307,7 +61254,7 @@ MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler return 0; } - return 1 + ma_lpf_get_latency(&pResampler->lpf); + return 1 + pResampler->lpfOrder; /*pResampler->lpfCount*2;*/ } MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) @@ -61427,6 +61374,7 @@ MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_li MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) { ma_uint32 iChannel; + ma_uint32 iLPF; if (pResampler == NULL) { return MA_INVALID_ARGS; @@ -61450,7 +61398,15 @@ MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) } /* The low pass filter needs to have its cache reset. */ - ma_lpf_clear_cache(&pResampler->lpf); + for (iLPF = 0; iLPF < pResampler->lpfOrder/2; iLPF += 1) { + if (pResampler->format == ma_format_f32) { + float* pLPF = MA_RESAMPLER_GET_LPF_HEAP_F32(pResampler, iLPF); + MA_ZERO_MEMORY(pLPF + 4, sizeof(float) * pResampler->channels * 2); + } else { + ma_int32* pLPF = MA_RESAMPLER_GET_LPF_HEAP_S32(pResampler, iLPF); + MA_ZERO_MEMORY(pLPF + 4, sizeof(ma_int32) * pResampler->channels * 2); + } + } return MA_SUCCESS; }