meadow-dsp-essentials 0.1.4

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

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

use super::Declicker;

impl Declicker {
    pub fn fade_to_enabled_f32(&mut self, enabled: bool, declick_values: &DeclickValuesF32) {
        if enabled {
            self.fade_to_1_f32(declick_values);
        } else {
            self.fade_to_0_f32(declick_values);
        }
    }

    pub fn fade_to_0_f32(&mut self, declick_values: &DeclickValuesF32) {
        match self {
            Self::SettledAt1 => {
                *self = Self::FadingTo0 {
                    frames_left: declick_values.frames(),
                }
            }
            Self::FadingTo1 { frames_left } => {
                let frames_left = if *frames_left <= declick_values.frames() {
                    declick_values.frames() - *frames_left
                } else {
                    declick_values.frames()
                };

                *self = Self::FadingTo0 { frames_left }
            }
            _ => {}
        }
    }

    pub fn fade_to_1_f32(&mut self, declick_values: &DeclickValuesF32) {
        match self {
            Self::SettledAt0 => {
                *self = Self::FadingTo1 {
                    frames_left: declick_values.frames(),
                }
            }
            Self::FadingTo0 { frames_left } => {
                let frames_left = if *frames_left <= declick_values.frames() {
                    declick_values.frames() - *frames_left
                } else {
                    declick_values.frames()
                };

                *self = Self::FadingTo1 { frames_left }
            }
            _ => {}
        }
    }

    /// Crossfade between the two buffers, where `DeclickValuesF32::SettledAt0` is fully
    /// `buffers_a`, and `DeclickValuesF32::SettledAt1` is fully `buffers_b`.
    pub fn process_crossfade_f32<VA: AsRef<[f32]>, VB: AsMut<[f32]>>(
        &mut self,
        buffers_a: &[VA],
        buffers_b: &mut [VB],
        frames: usize,
        declick_values: &DeclickValuesF32,
    ) {
        let mut crossfade_buffers =
            |declick_frames_left: &mut usize, values_a: &[f32], values_b: &[f32]| -> usize {
                let process_frames = frames.min(*declick_frames_left);

                let values_start = values_a.len() - *declick_frames_left;
                let values_a = &values_a[values_start..values_start + process_frames];
                let values_b = &values_b[values_start..values_start + process_frames];

                for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
                    let slice_a = &ch_a.as_ref()[..process_frames];
                    let slice_b = &mut ch_b.as_mut()[..process_frames];

                    for i in 0..process_frames {
                        slice_b[i] = (slice_a[i] * values_a[i]) + (slice_b[i] * values_b[i]);
                    }
                }

                *declick_frames_left -= process_frames;

                process_frames
            };

        match self {
            Self::SettledAt0 => {
                for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
                    let slice_a = &ch_a.as_ref()[..frames];
                    let slice_b = &mut ch_b.as_mut()[..frames];

                    slice_b.copy_from_slice(slice_a);
                }
            }
            Self::FadingTo0 { frames_left } => {
                let frames_processed = crossfade_buffers(
                    frames_left,
                    &declick_values.values_0_to_1,
                    &declick_values.values_1_to_0,
                );

                if frames_processed < frames {
                    for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
                        let slice_a = &ch_a.as_ref()[frames_processed..frames];
                        let slice_b = &mut ch_b.as_mut()[frames_processed..frames];

                        slice_b.copy_from_slice(slice_a);
                    }
                }

                if *frames_left == 0 {
                    *self = Self::SettledAt0;
                }
            }
            Self::FadingTo1 { frames_left } => {
                crossfade_buffers(
                    frames_left,
                    &declick_values.values_1_to_0,
                    &declick_values.values_0_to_1,
                );

                if *frames_left == 0 {
                    *self = Self::SettledAt1;
                }
            }
            _ => {}
        }
    }

    pub fn process_f32<V: AsMut<[f32]>>(
        &mut self,
        buffers: &mut [V],
        range_in_buffer: Range<usize>,
        declick_values: &DeclickValuesF32,
        gain: f32,
    ) {
        let mut fade_buffers = |declick_frames_left: &mut usize, values: &[f32]| -> usize {
            let buffer_frames = range_in_buffer.end - range_in_buffer.start;
            let process_frames = buffer_frames.min(*declick_frames_left);
            let start_frame = values.len() - *declick_frames_left;

            if gain == 1.0 {
                for b in buffers.iter_mut() {
                    let b = &mut b.as_mut()
                        [range_in_buffer.start..range_in_buffer.start + process_frames];

                    for (s, &g) in b
                        .iter_mut()
                        .zip(values[start_frame..start_frame + process_frames].iter())
                    {
                        *s *= g;
                    }
                }
            } else {
                for b in buffers.iter_mut() {
                    let b = &mut b.as_mut()
                        [range_in_buffer.start..range_in_buffer.start + process_frames];

                    for (s, &g) in b
                        .iter_mut()
                        .zip(values[start_frame..start_frame + process_frames].iter())
                    {
                        *s *= g * gain;
                    }
                }
            }

            *declick_frames_left -= process_frames;

            process_frames
        };

        match self {
            Self::SettledAt0 => {
                for b in buffers.iter_mut() {
                    let b = &mut b.as_mut();
                    b[range_in_buffer.clone()].fill(0.0);
                }
            }
            Self::FadingTo0 { frames_left } => {
                let frames_processed = fade_buffers(frames_left, &declick_values.values_1_to_0);

                if frames_processed < range_in_buffer.end - range_in_buffer.start {
                    for b in buffers.iter_mut() {
                        let b = &mut b.as_mut()
                            [range_in_buffer.start + frames_processed..range_in_buffer.end];
                        b.fill(0.0);
                    }
                }

                if *frames_left == 0 {
                    *self = Self::SettledAt0;
                }
            }
            Self::FadingTo1 { frames_left } => {
                let frames_processed = fade_buffers(frames_left, &declick_values.values_0_to_1);

                if frames_processed < range_in_buffer.end - range_in_buffer.start && gain != 1.0 {
                    for b in buffers.iter_mut() {
                        let b = &mut b.as_mut()
                            [range_in_buffer.start + frames_processed..range_in_buffer.end];
                        for s in b.iter_mut() {
                            *s *= gain;
                        }
                    }
                }

                if *frames_left == 0 {
                    *self = Self::SettledAt1;
                }
            }
            _ => {}
        }
    }
}

/// A buffer of values that ramp up/down between `0.0` and `1.0`.
///
/// This approach is more SIMD-friendly than using a smoothing filter or
/// incrementing the gain per-sample.
pub struct DeclickValuesF32 {
    pub values_0_to_1: Vec<f32>,
    pub values_1_to_0: Vec<f32>,
}

impl DeclickValuesF32 {
    pub const DEFAULT_FADE_SECONDS: f32 = 10.0 / 1_000.0;

    /// Create a buffer of values that linearly ramp up/down between `0.0` and `1.0`.
    pub fn linear(frames: NonZeroU32) -> Self {
        let frames = frames.get() as usize;
        let frames_recip = (frames as f32).recip();

        let mut values_0_to_1 = Vec::new();
        let mut values_1_to_0 = Vec::new();

        values_0_to_1.reserve_exact(frames);
        values_1_to_0.reserve_exact(frames);

        values_0_to_1 = (0..frames).map(|i| i as f32 * frames_recip).collect();
        values_1_to_0 = values_0_to_1.iter().rev().copied().collect();

        Self {
            values_0_to_1,
            values_1_to_0,
        }
    }

    /// Create a buffer of values that ramp up/down between `0.0` and `1.0` using
    /// an equal power (circular) curve.
    pub fn equal_power_3db(frames: NonZeroU32) -> Self {
        let frames = frames.get() as usize;
        let frames_recip = (frames as f32).recip();

        let mut values_0_to_1 = Vec::new();
        let mut values_1_to_0 = Vec::new();

        values_0_to_1.reserve_exact(frames);
        values_1_to_0.reserve_exact(frames);

        values_0_to_1 = (0..frames)
            .map(|i| (i as f32 * frames_recip * FRAC_PI_2).sin())
            .collect();
        values_1_to_0 = values_0_to_1.iter().rev().copied().collect();

        Self {
            values_0_to_1,
            values_1_to_0,
        }
    }

    pub fn frames(&self) -> usize {
        self.values_0_to_1.len()
    }
}