Expand description
§ARDFTSRC
A rust implementation of the Arbitrary Rate Discrete Fourier Transform Sample Rate Converter (ARDFTSRC) algorithm.
ardftsrc is a high-quality audio sample-rate converter, and is appropriate for both realtime and offline resampling.
Generally ardftsrc is preferred over other resamplers when quality is paramount. Although it is generic over both f32 and f64, it is highly recommended to use it with f64, even when processing an f32 audio stream.
It is ranked among the top scoring resampling libraries on HydrogenAudio Sample Rate Conversion Comparison, topping out with a balanced score of 99.92%. It is more compute intensive than other resamplers, so consider sinc rubato if you want more efficiency (see PERFORMANCE.md).
§Quick Start
Use InterleavedResampler::process_all to resample a complete interleaved audio stream for a single track.
use ardftsrc::{InterleavedResampler, PRESET_HIGH};
fn resample_all(input: &[f32], in_rate: usize, out_rate: usize, channels: usize) -> Vec<f32> {
// When using a preset other than "FAST", f64 processing is preferred.
let input_f64: Vec<f64> = input.iter().map(|v| *v as f64).collect();
let config = PRESET_HIGH
.with_input_rate(in_rate)
.with_output_rate(out_rate)
.with_channels(channels);
let mut resampler = InterleavedResampler::<f64>::new(config).unwrap();
let output = resampler.process_all(&input_f64).unwrap();
// Convert back to the original interleaved f32
output.interleave().into_iter().map(|v| v as f32).collect()
}§Chunk Resampling
Use chunk resampling when you can control both read and write buffer sizes. Query input_buffer_size() and output_buffer_size() and size your input and output slices to the sizes required. The chunk API is more efficient than the streaming API and is preferred when you are not doing live resampling.
There are two chunked resamplers depending on the shape of your audio:
InterleavedResampler- for interleaved audioPlanarResampler- for planar audio.
Internally ardftsrc uses planar representation, so PlanarResampler is more efficient, but if you’re already working with interleaved audio, prefer InterleavedResampler since it has an optimized de-interleave / re-interleave path. Working with all chunked resamplers is the same:
- Create the resampler with
let resampler = Resampler::new(config) - Query the required input buffer size and output buffer size with
resampler.input_buffer_size()andresampler.output_buffer_size() - Call
process_chunk(...)for each chunk, using the appropriate buffer sizes. - Call
process_chunk_final(...)for the final chunk, it can be undersized. - Finally, call
finalize(...)once per stream to emit delayed tail samples and reset stream state.
use ardftsrc::{InterleavedResampler, PRESET_GOOD};
fn resample_chunked(input: Vec<f32>, in_rate: usize, out_rate: usize, channels: usize) -> Vec<f32> {
// When using a preset other than "FAST", f64 processing is preferred.
let input_f64: Vec<f64> = input.into_iter().map(|v| v as f64).collect();
let config = PRESET_GOOD
.with_input_rate(in_rate)
.with_output_rate(out_rate)
.with_channels(channels);
let mut resampler = InterleavedResampler::<f64>::new(config).unwrap();
// Get the input and output chunk sizes
// You must read and write in these buffer sizes
let input_chunk_size = resampler.input_buffer_size();
let output_chunk_size = resampler.output_buffer_size();
let mut out_buf = vec![0.0_f64; output_chunk_size];
let mut out_f64 = Vec::<f64>::new();
let mut offset = 0;
// Process whole chunks in the size of input_chunk_size
while offset + input_chunk_size <= input_f64.len() {
let chunk = &input_f64[offset..offset + input_chunk_size];
// Process the chunk
let written = resampler.process_chunk(chunk, &mut out_buf).unwrap();
// Process output
out_f64.extend_from_slice(&out_buf[..written]);
offset += input_chunk_size;
}
// The final chunk can be undersized (or even zero sized)
let final_chunk = &input_f64[offset..];
// Process Output
let written = resampler.process_chunk_final(final_chunk, &mut out_buf).unwrap();
out_f64.extend_from_slice(&out_buf[..written]);
// After processing the final chunk, you must call "finalize()" to get tail content.
// finalize() also resets the resampler instance so it can be used again.
let written = resampler.finalize(&mut out_buf).unwrap();
out_f64.extend_from_slice(&out_buf[..written]);
// Convert back into f32
out_f64.into_iter().map(|v| v as f32).collect()
}§Gapless Context
For adjacent tracks, you can set edge context before processing:
pre(Vec<T>): tail frames from the previous trackpost(Vec<T>): head frames from the next track
post(...) may be called any time while the current stream is still active, but it must be
set before process_chunk_final(...).
This enables live gapless handoff: while track A is streaming, once track B is known you can
call post(...) on A with B’s head samples so A’s stop-edge uses real next-track context.
§When pre and post are not set
Missing edge samples are synthesized with Extrapolation, set with Config::with_extrapolation. Passed pre and post samples are used first, and extrapolation fills any remainder. A missing start edge is predicted backward from the first chunk. A missing stop edge is predicted forward from the last window.
| Strategy | Description |
|---|---|
Extrapolation::Lpc | Linear prediction from the nearby samples. Default. A diverging prediction fades to silence. |
Extrapolation::OddMirror | Point-reflects existing samples through the edge sample, so both value and slope stay continuous. |
Extrapolation::Mirror | Reflects existing samples back across the edge, without repeating the edge sample. The slope reverses at the edge. |
Extrapolation::Zero | Silence. Can click if the signal near the extrapolation edge is not near zero. |
let config = ardftsrc::PRESET_GOOD.with_extrapolation(ardftsrc::Extrapolation::Mirror);§Realtime Resampling
ardftsrc-rs provides both rodio integration via RodioResampler (rodio feature) and the ability to build your own custom realtime audio resampling pipeline via RealtimeResampler.
Realtime resampling (including rodio integration) is very sensitive to the rust compiler’s optimization settings. As an example, in release mode, the GOOD preset is able to operate with a buffer of only 64 frames and is suitable for realtime use, but in an unoptimized debug (dev) mode, requires a buffer of 2048 frames to operate smoothly.
There are two strategies to deal with this:
Option 1: optimize debug builds
In your cargo.toml set:
[profile.dev]
opt-level = 3Option 2: Swap quality and buffer size in debug build
Set your buffer size and quality depending on debug or release:
let (buffer_frames, preset) = if cfg!(debug_assertions) {
(2048, ardftsrc::PRESET_FAST) // unoptimized builds need a big buffer with a lighweight preset
} else {
(512, ardftsrc::PRESET_HIGH) // Optimized builds can use a small buffer and a high quality preset
};§Rodio integration
Enable the rodio feature to use RodioResampler to wrap a rodio::Source and resample it in realtime in your rodio pipeline.
When playing from a buffered audio source such as a file or a buffered stream, it is recommended to use config.with_rodio_fast_start(true), which will
avoid initial output delay by pulling samples from the upstream source to prime the resampler. For very-realtime sources such as microphones or similar, do not enable fast-start.
#[cfg(feature = "rodio")]
fn play_resampled_tone() -> Result<(), Box<dyn std::error::Error>> {
use ardftsrc::{PRESET_FAST, RodioResampler};
use rodio::Source;
use std::num::NonZero;
use std::time::Duration;
let stream = rodio::DeviceSinkBuilder::open_default_sink()?;
let mixer = stream.mixer();
let tone = rodio::source::SignalGenerator::new(
NonZero::new(44_100).unwrap(),
400.0, // 400 Hz
rodio::source::Function::Sine,
)
.take_duration(Duration::from_secs(3));
let config = PRESET_FAST.with_channels(1).with_input_rate(44_100).with_output_rate(48_000);
let resampled_tone = RodioResampler::new(tone, config)?;
mixer.add(resampled_tone);
std::thread::sleep(Duration::from_secs(4));
Ok(())
}More examples can be found:
- Basic rodio example:
examples/rodio_adapter.rs - Span-switching rodio example:
examples/rodio_adapter_with_spans.rs
§Batching
Use batching when you have multiple full tracks to convert with the same configuration.
InterleavedResampler::batch(...): processes each interleaved input as an independent stream (no context shared between tracks).InterleavedResampler::batch_gapless(...): preserves adjacent-track context for gapless album-style playback.PlanarResamplerexposes the samebatch(...)andbatch_gapless(...)APIs for already-planar inputs.
Enable the rayon feature to parallelize work across tracks.
use ardftsrc::{InterleavedResampler, PRESET_GOOD, PlanarVecs};
fn resample_tracks(
inputs: &[&[f64]],
in_rate: usize,
out_rate: usize,
channels: usize,
) -> Vec<PlanarVecs<f64>> {
let config = PRESET_GOOD
.with_input_rate(in_rate)
.with_output_rate(out_rate)
.with_channels(channels);
let driver = InterleavedResampler::<f64>::new(config).unwrap();
// Independent tracks (podcasts, unrelated files, etc.).
let _independent = driver.batch(inputs).unwrap();
// Gapless sequence (album tracks played back-to-back).
let gapless = driver.batch_gapless(inputs).unwrap();
// Return one of the two results based on your use case.
gapless
}§Quality Tuning and Presets
ARDFTSRC is built for quality over speed, and despite supporting both f32 and f64 should almost always be run as f64. To resample f32 audio, it is recommended to convert f32 samples to f64, resample them using InterleavedResampler<f64> or PlanarResampler<f64>, then convert back to f32.
If you want better performance than what this project offers, consider using a sinc resampler such as rubato.
Presets are pre-vetted Config for various quality levels.
let config = ardftsrc::PRESET_GOOD
.with_input_rate(44_100)
.with_output_rate(48_000)
.with_channels(2);| Preset | Quality | Bandwidth | Recommended use | Quality metrics |
|---|---|---|---|---|
PRESET_FAST | 512 | 0.920 | Fast preset for realtime workloads. | f64, f32, HA-f64, HA-f32 |
PRESET_GOOD † | 2304 | 0.975 | Balanced preset for realtime quality. | f64, HA-f64 |
PRESET_HIGH | 7056 | 0.996 | High quality for realtime or offline use. | f64, HA-f64, HA-f64-dd |
See PERFORMANCE.md for throughput, latency, and which presets suit realtime monitoring, playback, and offline use.
§Taper profiles
The cutoff transition is TaperType, set with Config::with_taper_type.
| Taper | Description |
|---|---|
TaperType::Cosine(alpha) | Sigmoid-warped cosine. |
TaperType::Planck | Planck taper. |
TaperType::Tanh(alpha) | Endpoint-normalized hyperbolic tangent. |
TaperType::BetaCdf { alpha, beta } | Beta cumulative distribution. Equal alpha and beta are symmetric. |
TaperType::Kaiser(beta) | Cumulative distribution of a Kaiser window |
let config = ardftsrc::PRESET_GOOD.with_taper_type(ardftsrc::TaperType::Tanh(3.0));§Extreme Downsampling Ratios
If you need to support very large downsampling ratios (e.g. 192kHz → 24kHz), set .with_decimate(true) to speed things up. It enables a conservative pre-decimator (only at ratios of 4:1 or higher) that applies progressive 2:1 decimations before the main resampling stage. It is designed so it respects the configured bandwidth, and so that the FFT stage still performs at least a genuine 2:1 reduction of its own.
let config = ardftsrc::Config::new(192_000, 8_000, 1).with_decimate(true);It’s a speed/memory optimization, not a way to reduce buffering or latency — for that, lower quality instead. If you do lower quality for a large ratio, turn decimate on too: it keeps a low-quality conversion sounding good at ratios where it would otherwise struggle.
§Feature Flags
| Flag | Enables | Default |
|---|---|---|
rodio | rodio integration via rodio::RodioResampler | No |
rayon | Parallelized resampling (batch() and process_all() APIs) | No |
avx | FFT AVX SIMD | Yes |
sse | FFT SSE SIMD | Yes |
neon | FFT NEON SIMD for ARM / Mac | Yes |
wasm_simd | FFT WebAssembly SIMD | Yes |
audioadapter | Experimental audioadapter support | No |
high_precision | DoubleDouble, F128, and F256 sample types for extreme quality | No |
Runtime feature detection is in place for all SIMD except webassembly.
Structs§
- Config
- Configures the ardftsrc resampler.
- Double
Double - Double-double floating point (an unevaluated sum of two
f64s, ~106-bit mantissa), for resampling at higher precision thanf64. The fastest of the high-precision sample types, since it is built from hardwaref64operations, but not correctly rounded. - F128
- IEEE 754 binary128 floating point (113-bit mantissa), for resampling at higher precision
than
f64. Correctly rounded software floating point. - F256
- IEEE 754 binary256 floating point (237-bit mantissa), for resampling at higher precision
than
f64. Correctly rounded software floating point; the most precise and slowest of the high-precision sample types. - Interleaved
Resampler - Chunked sample-rate converter for interleaved audio buffers.
- Planar
Resampler - Chunked sample-rate converter for planar audio buffers.
- Planar
Vecs - A channel-major container of audio samples with equal frame counts.
- Realtime
Resampler - Realtime reasampler for live audio streams. If you’re looking for rodio support, see
RodioResampler. - Rodio
Resampler - Wrap a
rodio::Sourceand resample it in realtime in your rodio pipeline. Requires therodiofeature.
Enums§
- Alias
Floor - Lowest frequency that the resampler may fold (downsampling) or image (upsampling) energy
onto. See
Config::alias_floor. - Error
- Ardftsrc error types.
- Extrapolation
- Strategy used to synthesize missing samples at a stream’s start or end edge.
- Samples
Left InSpan - The length of a span in output samples for RealtimeResampler.
- Taper
Type - Transition profile used to shape the cutoff edge of the frequency mask.
Constants§
- PRESET_
FAST - Low-latency, lower-quality preset.
- PRESET_
GOOD - Balanced preset for good realtime quality. You should probably use this one.
- PRESET_
HIGH - High quality preset suitable for realtime applications where quality is critical, or for offline processing.
Traits§
- AsChannels
- A read-only, per-channel view over planar audio data.
- AsChannels
Mut - A mutable, per-channel view over planar audio data.
- Sample
- A sample type ardftsrc can resample in.