meadow-dsp-essentials 0.1.4

Liberally-licensed essential audio DSP library used in the Meadowlark DAW project
Documentation
#[cfg(not(feature = "std"))]
use num_traits::Float;

use core::num::NonZeroU32;

pub const DEFAULT_SMOOTH_SECONDS: f64 = 15.0 / 1_000.0;
pub const DEFAULT_SETTLE_EPSILON: f64 = 0.001f64;

/// The coefficients for a simple smoothing/declicking filter where:
///
/// `y[n] = (target_value * a) + (x[n-1] * b)`
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SmoothingFilterCoeff {
    pub a0: f64,
    pub b1: f64,
}

impl SmoothingFilterCoeff {
    pub fn new(sample_rate: NonZeroU32, smooth_secs: f64) -> Self {
        let smooth_secs = smooth_secs.max(0.00001);

        let b1 = (-1.0f64 / (smooth_secs * sample_rate.get() as f64)).exp();
        let a0 = 1.0f64 - b1;

        Self { a0, b1 }
    }
}

/// The state of a simple smoothing/declicking filter where:
///
/// `y[n] = (target_value * a) + (x[n-1] * b)`
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SmoothingFilter {
    pub z1: f64,
}

impl SmoothingFilter {
    pub fn new(value: f64) -> Self {
        Self { z1: value }
    }

    #[inline(always)]
    pub fn process(&mut self, target: f64, coeff: SmoothingFilterCoeff) -> f64 {
        self.z1 = (target * coeff.a0) + (self.z1 * coeff.b1);
        self.z1
    }

    #[inline(always)]
    pub fn process_sample_a(&mut self, target_times_a: f64, coeff_b: f64) -> f64 {
        self.z1 = target_times_a + (self.z1 * coeff_b);
        self.z1
    }

    pub fn process_into_buffer(
        &mut self,
        buffer: &mut [f64],
        target: f64,
        coeff: SmoothingFilterCoeff,
    ) {
        let target_times_a = target * coeff.a0;

        for s in buffer.iter_mut() {
            *s = self.process_sample_a(target_times_a, coeff.b1);
        }
    }

    /// Settle the filter if its state is close enough to the target value.
    ///
    /// Returns `true` if this filter is settled, `false` if not.
    pub fn settle(&mut self, target: f64, settle_epsilon: f64) -> bool {
        if self.z1 == target {
            true
        } else if (self.z1 - target).abs() < (target.abs() * settle_epsilon) + settle_epsilon {
            self.z1 = target;
            true
        } else {
            false
        }
    }

    pub fn has_settled(&self, target: f64) -> bool {
        self.z1 == target
    }
}