meadow-dsp-essentials 0.1.4

Liberally-licensed essential audio DSP library used in the Meadowlark DAW project
Documentation
use core::num::NonZeroU32;

#[cfg(not(feature = "std"))]
use num_traits::Float;

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

use crate::filter::smoothing::f32::{
    DEFAULT_SETTLE_EPSILON, DEFAULT_SMOOTH_SECONDS, SmoothingFilter, SmoothingFilterCoeff,
};

/// A parameter range with a linear mapping
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinearRange {
    pub min: f32,
    pub max: f32,
}

impl LinearRange {
    pub fn new(min: f32, max: f32) -> Self {
        Self { min, max }
    }

    /// Map a value to its corresponding raw value for use in DSP
    pub fn clamp(&self, val: f32) -> f32 {
        if self.min > self.max {
            val.min(self.min).max(self.max)
        } else {
            val.min(self.max).max(self.min)
        }
    }
}

impl Default for LinearRange {
    fn default() -> Self {
        Self { min: 0.0, max: 1.0 }
    }
}

/// A parameter range that takes a normalized value in the range `[0.0, 1.0]`
/// as input and outputs a frequency value in Hz.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NormToFreqRange {
    min_hz: f32,
    max_hz: f32,

    min_log2: f32,
    range: f32,
}

impl NormToFreqRange {
    pub fn new(min_hz: f32, max_hz: f32) -> Self {
        assert!(min_hz < max_hz);
        assert_ne!(min_hz, 0.0);
        assert_ne!(max_hz, 0.0);

        let min_log2 = min_hz.log2();
        let range = max_hz.log2() - min_log2;

        Self {
            min_hz,
            max_hz,
            min_log2,
            range,
        }
    }

    pub fn min_hz(&self) -> f32 {
        self.min_hz
    }

    pub fn max_hz(&self) -> f32 {
        self.max_hz
    }

    /// Convert the normalized value in the range `[0.0, 1.0]` to the
    /// corresponding frequency value in hz.
    pub fn to_hz(&self, normalized: f32) -> f32 {
        if normalized <= 0.0 {
            return self.min_hz;
        }

        if normalized >= 1.0 {
            return self.max_hz;
        }

        2.0f32.powf((normalized * self.range) + self.min_log2)
    }
}

/// A parameter range that takes a normalized value in the range `[0.0, 1.0]`
/// as input and outputs a corresponding value using a power curve.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NormToPowRange {
    pub exponent: f32,
    min: f32,
    max: f32,
}

impl NormToPowRange {
    pub fn new(min: f32, max: f32, exponent: f32) -> Self {
        assert!(min <= max);

        Self { exponent, min, max }
    }

    pub fn min(&self) -> f32 {
        self.min
    }

    pub fn max(&self) -> f32 {
        self.max
    }

    /// Convert the normalized value in the range `[0.0, 1.0]` to the
    /// corresponding value for use in DSP.
    pub fn to_dsp(&self, normalized: f32) -> f32 {
        if normalized <= 0.0 {
            return self.min;
        }

        if normalized >= 1.0 {
            return self.max;
        }

        normalized.powf(self.exponent) * (self.max - self.min) + self.min
    }
}

/// The configuration for a [`SmoothedParam`]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SmootherConfig {
    /// The amount of smoothing in seconds
    ///
    /// By default this is set to 5 milliseconds.
    pub smooth_seconds: f32,
    /// The threshold at which the smoothing will complete
    ///
    /// By default this is set to `0.00001`.
    pub settle_epsilon: f32,
}

impl Default for SmootherConfig {
    fn default() -> Self {
        Self {
            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
            settle_epsilon: DEFAULT_SETTLE_EPSILON,
        }
    }
}

/// A helper struct to smooth an f32 parameter.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SmoothedParam {
    target_value: f32,
    target_times_a: f32,
    filter: SmoothingFilter,
    coeff: SmoothingFilterCoeff,
    smooth_secs: f32,
    settle_epsilon: f32,
}

impl SmoothedParam {
    /// Construct a new smoothed f32 parameter with the given configuration.
    pub fn new(value: f32, config: SmootherConfig, sample_rate: NonZeroU32) -> Self {
        let smooth_secs = config.smooth_seconds.max(0.00001);
        let settle_epsilon = config.settle_epsilon.max(f32::EPSILON);

        let coeff = SmoothingFilterCoeff::new(sample_rate, smooth_secs);

        Self {
            target_value: value,
            target_times_a: value * coeff.a0,
            filter: SmoothingFilter::new(value),
            coeff,
            smooth_secs,
            settle_epsilon,
        }
    }

    /// The target value of the parameter.
    pub fn target_value(&self) -> f32 {
        self.target_value
    }

    /// Set the target value of the parameter.
    pub fn set_value(&mut self, value: f32) {
        self.target_value = value;
        self.target_times_a = value * self.coeff.a0;
    }

    /// 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) -> bool {
        self.filter.settle(self.target_value, self.settle_epsilon)
    }

    /// Returns `true` if this parameter is currently smoothing this process cycle,
    /// `false` if not.
    pub fn is_smoothing(&self) -> bool {
        !self.filter.has_settled(self.target_value)
    }

    /// Returns `false` if this parameter is currently smoothing this process cycle,
    /// `true` if not.
    pub fn has_settled(&self) -> bool {
        self.filter.has_settled(self.target_value)
    }

    /// Returns `true` if this parameter has settled to the given value, `false`
    /// if not.
    pub fn has_settled_at(&self, value: f32) -> bool {
        self.target_value == value && self.filter.has_settled(self.target_value)
    }

    /// Returns `true` if this parameter has settled to a value less than or
    /// equal to the given value, `false` if not.
    pub fn has_settled_at_or_below(&self, value: f32) -> bool {
        self.target_value <= value && self.filter.has_settled(self.target_value)
    }

    /// Reset the internal smoothing filter to the current target value.
    pub fn reset_to_target(&mut self) {
        self.filter = SmoothingFilter::new(self.target_value);
    }

    /// Return the next smoothed value.
    #[inline(always)]
    pub fn next_smoothed(&mut self) -> f32 {
        self.filter
            .process_sample_a(self.target_times_a, self.coeff.b1)
    }

    /// Fill the given buffer with the smoothed values.
    pub fn process_into_buffer(&mut self, buffer: &mut [f32]) {
        if self.is_smoothing() {
            self.filter
                .process_into_buffer(buffer, self.target_value, self.coeff);

            self.filter.settle(self.target_value, self.settle_epsilon);
        } else {
            buffer.fill(self.target_value);
        }
    }

    pub fn set_smooth_seconds(&mut self, seconds: f32, sample_rate: NonZeroU32) {
        self.coeff = SmoothingFilterCoeff::new(sample_rate, seconds);
        self.smooth_secs = seconds;
    }

    /// Update the sample rate.
    pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
        self.coeff = SmoothingFilterCoeff::new(sample_rate, self.smooth_secs);
    }
}

/// A helper struct to smooth an f32 parameter, along with a buffer of smoothed values.
#[cfg(feature = "alloc")]
pub struct SmoothedParamBuffer {
    smoother: SmoothedParam,
    buffer: Vec<f32>,
    buffer_is_constant: bool,
}

#[cfg(feature = "alloc")]
impl SmoothedParamBuffer {
    /// Construct a new smoothed f32 parameter with the given configuration.
    pub fn new(
        value: f32,
        config: SmootherConfig,
        sample_rate: NonZeroU32,
        max_block_frames: usize,
    ) -> Self {
        let mut buffer = Vec::new();
        buffer.reserve_exact(max_block_frames);
        buffer.resize(max_block_frames, value);

        Self {
            smoother: SmoothedParam::new(value, config, sample_rate),
            buffer,
            buffer_is_constant: true,
        }
    }

    /// The current target value that is being smoothed to.
    pub fn target_value(&self) -> f32 {
        self.smoother.target_value()
    }

    /// Set the target value of the parameter.
    pub fn set_value(&mut self, value: f32) {
        self.smoother.set_value(value);
    }

    /// Reset the smoother.
    pub fn reset(&mut self) {
        if self.smoother.is_smoothing() || !self.buffer_is_constant {
            self.buffer.fill(self.smoother.target_value);
            self.buffer_is_constant = true;
        }

        self.smoother.reset_to_target();
    }

    /// Get the buffer of smoothed samples.
    ///
    /// The second value is `true` if all the values in the buffer are the same.
    pub fn get_buffer(&mut self, frames: usize) -> (&[f32], bool) {
        self.buffer_is_constant = !self.smoother.is_smoothing();

        self.smoother
            .process_into_buffer(&mut self.buffer[..frames]);

        (
            &self.buffer[..frames],
            self.buffer_is_constant || frames < 2,
        )
    }

    /// Returns `true` if this parameter is currently smoothing this process cycle,
    /// `false` if not.
    pub fn is_smoothing(&self) -> bool {
        self.smoother.is_smoothing()
    }

    /// Returns `false` if this parameter is currently smoothing this process cycle,
    /// `true` if not.
    pub fn has_settled(&self) -> bool {
        self.smoother.has_settled()
    }

    /// Returns `true` if this parameter has settled to the given value, `false`
    /// if not.
    pub fn has_settled_at(&self, value: f32) -> bool {
        self.smoother.has_settled_at(value)
    }

    /// Returns `true` if this parameter has settled to a value less than or
    /// equal to the given value, `false` if not.
    pub fn has_settled_at_or_below(&self, value: f32) -> bool {
        self.smoother.has_settled_at_or_below(value)
    }

    /// Update the stream information.
    pub fn update_stream(&mut self, sample_rate: NonZeroU32, max_block_frames: usize) {
        self.smoother.update_sample_rate(sample_rate);

        if self.buffer.len() > max_block_frames {
            self.buffer.resize(max_block_frames, 0.0);
        } else if self.buffer.len() < max_block_frames {
            self.buffer
                .reserve_exact(max_block_frames - self.buffer.len());
            self.buffer
                .resize(max_block_frames, self.smoother.target_value());
        }
    }
}