Skip to main content

Crate ardftsrc

Crate ardftsrc 

Source
Expand description

§ARDFTSRC

Crates.io Docs.rs

A rust implementation of the Arbitrary Rate Discrete Fourier Transform Sample Rate Converter (ARDFTSRC) algorithm.

ardftsrc is a streaming 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 more compute intensive than other resamplers, so consider sinc rubato if you want more efficiency. See PERFORMANCE.md for a detailed speed and quality comparison vs rubato.

§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:

  1. InterleavedResampler - for interleaved audio
  2. PlanarResampler - 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:

  1. Create the resampler with let resampler = Resampler::new(config)
  2. Query the required input buffer size and output buffer size with resampler.input_buffer_size() and resampler.output_buffer_size()
  3. Call process_chunk(...) for each chunk, using the appropriate buffer sizes.
  4. Call process_chunk_final(...) for the final chunk, it can be undersized.
  5. Finally, call finalize(...) once per stream to emit delayed tail samples and reset stream state.

To end the stream early, you may simply call reset().

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 track
  • post(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.

§Realtime Resampling

ardftsrc-rs provides both rodio integration via RodioResampler (rodio feature) and the ability to build your own custom realtime audio resampling pipline via RealtimeResampler (realtime feature).

§Rodio integration

Enable the rodio feature to use rodio::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.

Additional configuration settings are Config::with_realtime_input_range() and Config::with_realtime_max_channels() which lets you tune the resampler if you know ahead-of-time the shape of the input sample-rate and channel count. It is recommended to not change these settings - the default values are quite generous.

§Example:

#[cfg(feature = "rodio")]
{
    let stream = rodio::DeviceSinkBuilder::open_default_sink()?;
    let mixer = stream.mixer();

    let tone = rodio::source::SignalGenerator::new(
        NonZero::new(44_100 as u32).unwrap(),
        400, // 400 Hz
        rodio::source::Function::Sine,
    )
    .take_duration(Duration::from_secs(3.0));

    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));
}

More examples can be found:

§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.
  • PlanarResampler exposes the same batch(...) and batch_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);
PresetParametersRecommended useQuality Metrics
PRESET_FASTquality=512 bandwidth=0.832Fast preset for realtime workloads.f32, f64
PRESET_GOODquality=1878 bandwidth=0.911Balanced preset for realtime quality.f64
PRESET_HIGHquality=73622 bandwidth=0.987High quality for offline use.f64
PRESET_EXTREMEquality=524514 bandwidth=0.995Maximum quality, intended for offline use.f64

§Feature Flags

FlagEnablesDefault
rodiorodio integration via rodio::RodioResamplerNo
rayonParallelized resampling (batch() and process_all() APIs)No
realtimeRealtimeResampler streaming API backed by off-thread resamplingNo
avxFFT AVX SIMDYes
sseFFT SSE SIMDYes
neonFFT NEON SIMD for ARM / MacYes
wasm_simdFFT WebAssembly SIMDYes
audioadapterExperimental audioadapter supportNo

Runtime feature detection is in place for all SIMD except webassembly.

Structs§

Config
Configures the ardftsrc resampler.
InterleavedResampler
Chunked sample-rate converter for interleaved audio buffers.
PlanarResampler
Chunked sample-rate converter for planar audio buffers.
PlanarVecs
A channel-major container of audio samples with equal frame counts.
RealtimeResampler
Realtime reasampler for live audio streams. Requires the realtime feature. If you’re looking for rodio support, see RodioResampler.
RodioResampler
Wrap a rodio::Source and resample it in realtime in your rodio pipeline. Requires the rodio feature.

Enums§

Error
Ardftsrc error types.
TaperType
Transition profile used to shape the cutoff edge of the frequency mask.

Constants§

PRESET_EXTREME
Maximum quality preset, optimized for offline processing. Not recommended for realtime applications.
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 offline processing or realtime applications where quality is critical.