firewheel-core 0.10.1

Shared types for Firewheel crates
Documentation
use core::num::NonZeroU32;

#[cfg(not(feature = "std"))]
use bevy_platform::prelude::Vec;

use crate::{
    dsp::filter::smoothing_filter::{self, SmoothingFilter, SmoothingFilterCoeff},
    StreamInfo,
};

/// The configuration for a [`SmoothedParam`]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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: smoothing_filter::DEFAULT_SMOOTH_SECONDS,
            settle_epsilon: smoothing_filter::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.
#[derive(Debug, Clone)]
pub struct SmoothedParamBuffer {
    smoother: SmoothedParam,
    buffer: Vec<f32>,
    buffer_is_constant: bool,
}

impl SmoothedParamBuffer {
    /// Construct a new smoothed f32 parameter with the given configuration.
    pub fn new(value: f32, config: SmootherConfig, stream_info: &StreamInfo) -> Self {
        let mut buffer = Vec::new();
        buffer.reserve_exact(stream_info.max_block_frames.get() as usize);
        buffer.resize(stream_info.max_block_frames.get() as usize, value);

        Self {
            smoother: SmoothedParam::new(value, config, stream_info.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, stream_info: &StreamInfo) {
        self.smoother.update_sample_rate(stream_info.sample_rate);

        let max_block_frames = stream_info.max_block_frames.get() as usize;

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