br41ndmg 0.1.0

A polyphase sinc audio resampler with offline and streaming APIs for Rust.
Documentation
//! Window functions used to truncate the sinc kernel into a finite FIR filter.
//!
//! All windows are parameterized by a normalized coordinate `t` in `[-1, 1]`
//! and are symmetric about `t = 0`. Use [`apply_window`] / [`apply_window_f32`]
//! to sample a window of a given length.

use std::f32::consts::PI as PI_F32;
use std::f64::consts::PI as PI_F64;

/// Window shapes supported by the sinc kernel and polyphase filter bank.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Window {
    /// Hann window: smooth rolloff, good all-purpose choice.
    Hann,
    /// Hamming window: slightly higher first sidelobe than Hann.
    Hamming,
    /// Blackman window: wider main lobe, strong sidelobe suppression
    /// (the default for the polyphase filter bank).
    Blackman,
    /// Kaiser window with tunable shape factor `beta`. Larger `beta` trades a
    /// wider main lobe for lower sidelobes.
    Kaiser { beta: f64 },
}

pub fn apply_window(window: Window, length: usize) -> Vec<f64> {
    if length == 0 {
        return Vec::new();
    }

    if length == 1 {
        return vec![1.0];
    }

    let denom = (length as f64 - 1.0) * 0.5;
    let mut values = Vec::with_capacity(length);
    for n in 0..length {
        let t = (n as f64 - denom) / denom;
        values.push(window_value(window, t));
    }
    values
}

pub fn apply_window_f32(window: Window, length: usize) -> Vec<f32> {
    if length == 0 {
        return Vec::new();
    }

    if length == 1 {
        return vec![1.0];
    }

    let denom = (length as f32 - 1.0) * 0.5;
    let mut values = Vec::with_capacity(length);
    for n in 0..length {
        let t = (n as f32 - denom) / denom;
        values.push(window_value_f32(window, t));
    }
    values
}

pub(crate) fn window_value(window: Window, t: f64) -> f64 {
    match window {
        Window::Hann => hann_value(t),
        Window::Hamming => hamming_value(t),
        Window::Blackman => blackman_value(t),
        Window::Kaiser { beta } => kaiser_value(t, beta),
    }
}

pub(crate) fn window_value_f32(window: Window, t: f32) -> f32 {
    match window {
        Window::Hann => hann_value_f32(t),
        Window::Hamming => hamming_value_f32(t),
        Window::Blackman => blackman_value_f32(t),
        Window::Kaiser { beta } => kaiser_value_f32(t, beta as f32),
    }
}

fn hann_value(t: f64) -> f64 {
    0.5 * (1.0 + (PI_F64 * t).cos())
}

fn hann_value_f32(t: f32) -> f32 {
    0.5 * (1.0 + (PI_F32 * t).cos())
}

fn hamming_value(t: f64) -> f64 {
    0.54 + 0.46 * (PI_F64 * t).cos()
}

fn hamming_value_f32(t: f32) -> f32 {
    0.54 + 0.46 * (PI_F32 * t).cos()
}

fn blackman_value(t: f64) -> f64 {
    0.42 + 0.5 * (PI_F64 * t).cos() + 0.08 * (2.0 * PI_F64 * t).cos()
}

fn blackman_value_f32(t: f32) -> f32 {
    0.42 + 0.5 * (PI_F32 * t).cos() + 0.08 * (2.0 * PI_F32 * t).cos()
}

fn kaiser_value(t: f64, beta: f64) -> f64 {
    assert!(
        beta >= 0.0 && beta.is_finite(),
        "beta must be non-negative and finite"
    );
    let arg = (1.0 - t * t).max(0.0).sqrt();
    i0(beta * arg) / i0(beta)
}

fn kaiser_value_f32(t: f32, beta: f32) -> f32 {
    assert!(
        beta >= 0.0 && beta.is_finite(),
        "beta must be non-negative and finite"
    );
    let arg = (1.0 - t * t).max(0.0).sqrt();
    i0_f32(beta * arg) / i0_f32(beta)
}

fn i0(x: f64) -> f64 {
    let ax = x.abs();
    if ax < 3.75 {
        let y = (x / 3.75).powi(2);
        1.0 + y
            * (3.5156229
                + y * (3.0899424
                    + y * (1.2067492 + y * (0.2659732 + y * (0.0360768 + y * 0.0045813)))))
    } else {
        let y = 3.75 / ax;
        (ax.exp() / ax.sqrt())
            * (0.39894228
                + y * (0.01328592
                    + y * (0.00225319
                        + y * (-0.00157565
                            + y * (0.00916281
                                + y * (-0.02057706
                                    + y * (0.02635537 + y * (-0.01647633 + y * 0.00392377))))))))
    }
}

fn i0_f32(x: f32) -> f32 {
    i0(x as f64) as f32
}