A streaming, anti-aliased sample-rate resampler for mono f32 audio.
- Streaming and stateful: processes successive chunks, carrying filter state across calls so chunk boundaries are invisible in the output.
- Anti-aliased polyphase windowed-sinc conversion: over 80 dB of stopband attenuation and under 0.001 dB of passband ripple, measured on the 48000 to 16000 and 44100 to 16000 conversions.
- Deterministic by construction: a fixed accumulation order, no fused multiply-add, and no platform-dependent transcendentals in the coefficient path.
- Reports its algorithmic latency in output samples (106 for 48000 to 16000 and 44100 to 16000).
- Identity bypass at equal input and output rates: byte-identical passthrough with zero added latency.
- No per-call heap allocation in steady state.
- Exact handling of common integer-Hz conversions, with an arbitrary-ratio path for other rates.
- Optional
tracingdiagnostics at construction, flush, and reset, behind the off-by-defaulttracingfeature; without the feature the crate has no dependency ontracingand the emit sites compile to nothing.
use decibri_resampler::{PolyphaseResampler, Resampler};
// Construct for an input and output rate in Hz. The rate pair is fixed for the
// lifetime of the instance.
let mut resampler = PolyphaseResampler::new(48_000, 16_000).unwrap();
// Feed successive chunks of any size; resampled output is appended to the buffer.
let mut output = Vec::new();
resampler.process(&[0.0_f32; 480], &mut output).unwrap();
resampler.process(&[0.0_f32; 320], &mut output).unwrap();
// Drain the filter tail at end of stream. To start another stream on the same
// instance, call reset first: process after flush without a reset is rejected.
resampler.flush(&mut output);
assert!(!output.is_empty());The crate's public surface is the Resampler trait, implemented by PolyphaseResampler. Input and output are a single mono f32 channel; channel handling and sample-format conversion are out of scope.
PolyphaseResampler::new(in_rate, out_rate)constructs a resampler for a fixed input and output rate pair. It returnsResamplerError::ZeroSampleRateif either rate is zero, andResamplerError::RatePairUnsupportedif the pair would require an anti-aliasing filter beyond the supported maximum length (reachable only for pathological integer rates, never for real audio rates).process(&[f32], &mut Vec<f32>) -> Result<(), ResamplerError>consumes an input chunk of any length and appends the resampled output, which is variable in length and may be empty for a small input. It returnsResamplerError::ProcessAfterFlushwhen called afterflushwith no interveningreset; the rejected call consumes nothing, appends nothing, and changes no state.flush(&mut Vec<f32>)drains the group-delay tail into the output buffer at end of stream.latency_samples() -> usizereports the constant algorithmic latency in output samples.is_identity() -> boolis true when the input and output rates are equal.reset()clears all streaming state, keeping the configured rates.
The streaming lifecycle: feed process for the life of a stream, call flush at end of stream, and call reset to start the next stream on the same instance. flush is idempotent (a repeated flush appends nothing) and appends nothing when no samples were processed. After a flush, a process call without an intervening reset is rejected with ResamplerError::ProcessAfterFlush, in release builds as well as debug; the rejected call consumes nothing, appends nothing, and changes no state, and the instance remains usable after a reset. The asymmetry is deliberate: a repeated flush restates that the stream has ended, which is redundant but not contradictory, while a process after flush contradicts it, and the contradiction is reported rather than silently resolved.
Apache-2.0 © 2026 Decibri.