From ac5796ee590060c57d058e982605cd0ba46f74e6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 7 Jun 2022 15:10:55 +0200 Subject: [PATCH] Move the Crossover biquad definition --- plugins/crossover/src/crossover/fir.rs | 2 +- plugins/crossover/src/crossover/iir.rs | 4 +- plugins/crossover/src/crossover/iir/biquad.rs | 198 ++++++++++++++++++ plugins/crossover/src/lib.rs | 1 - 4 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 plugins/crossover/src/crossover/iir/biquad.rs diff --git a/plugins/crossover/src/crossover/fir.rs b/plugins/crossover/src/crossover/fir.rs index 18e76ac2..174321a3 100644 --- a/plugins/crossover/src/crossover/fir.rs +++ b/plugins/crossover/src/crossover/fir.rs @@ -19,7 +19,7 @@ use nih_plug::debug::*; use std::f32; use std::simd::{f32x2, StdFloat}; -use crate::biquad::{Biquad, BiquadCoefficients, NEUTRAL_Q}; +use crate::crossover::iir::biquad::{Biquad, BiquadCoefficients, NEUTRAL_Q}; use crate::NUM_BANDS; // TODO: Move this to FFT convolution so we can increase the filter size and improve low latency performance diff --git a/plugins/crossover/src/crossover/iir.rs b/plugins/crossover/src/crossover/iir.rs index 5c9c90cd..60204600 100644 --- a/plugins/crossover/src/crossover/iir.rs +++ b/plugins/crossover/src/crossover/iir.rs @@ -18,9 +18,11 @@ use nih_plug::buffer::ChannelSamples; use nih_plug::debug::*; use std::simd::f32x2; -use crate::biquad::{Biquad, BiquadCoefficients, NEUTRAL_Q}; +use self::biquad::{Biquad, BiquadCoefficients, NEUTRAL_Q}; use crate::NUM_BANDS; +pub mod biquad; + #[derive(Debug)] pub struct IirCrossover { /// The kind of crossover to use. `.update_filters()` must be called after changing this. diff --git a/plugins/crossover/src/crossover/iir/biquad.rs b/plugins/crossover/src/crossover/iir/biquad.rs new file mode 100644 index 00000000..d238acd9 --- /dev/null +++ b/plugins/crossover/src/crossover/iir/biquad.rs @@ -0,0 +1,198 @@ +// Crossover: clean crossovers as a multi-out plugin +// Copyright (C) 2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use nih_plug::debug::*; + +use std::f32::consts; +use std::ops::{Add, Mul, Sub}; +use std::simd::f32x2; + +pub const NEUTRAL_Q: f32 = std::f32::consts::FRAC_1_SQRT_2; + +/// A simple biquad filter with functions for generating coefficients for second order low-pass and +/// high-pass filters. Since these filters have 3 dB of attenuation at the center frequency, we'll +/// two of them in series to get 6 dB of attenutation at the crossover point for the LR24 +/// crossovers. +/// +/// Based on . +/// +/// The type parameter T should be either an `f32` or a SIMD type. +#[derive(Clone, Copy, Debug)] +pub struct Biquad { + pub coefficients: BiquadCoefficients, + s1: T, + s2: T, +} + +/// The coefficients `[b0, b1, b2, a1, a2]` for [`Biquad`]. These coefficients are all +/// prenormalized, i.e. they have been divided by `a0`. +/// +/// The type parameter T should be either an `f32` or a SIMD type. +#[derive(Clone, Copy, Debug)] +pub struct BiquadCoefficients { + b0: T, + b1: T, + b2: T, + a1: T, + a2: T, +} + +/// Either an `f32` or some SIMD vector type of `f32`s that can be used with our biquads. +pub trait SimdType: + Mul + Sub + Add + Copy + Sized +{ + fn from_f32(value: f32) -> Self; +} + +impl Default for Biquad { + /// Before setting constants the filter should just act as an identity function. + fn default() -> Self { + Self { + coefficients: BiquadCoefficients::identity(), + s1: T::from_f32(0.0), + s2: T::from_f32(0.0), + } + } +} + +impl Biquad { + /// Process a single sample. + pub fn process(&mut self, sample: T) -> T { + let result = self.coefficients.b0 * sample + self.s1; + + self.s1 = self.coefficients.b1 * sample - self.coefficients.a1 * result + self.s2; + self.s2 = self.coefficients.b2 * sample - self.coefficients.a2 * result; + + result + } + + /// Reset the state to zero, useful after making making large, non-interpolatable changes to the + /// filter coefficients. + pub fn reset(&mut self) { + self.s1 = T::from_f32(0.0); + self.s2 = T::from_f32(0.0); + } +} + +impl BiquadCoefficients { + /// Convert scalar coefficients into the correct vector type. + pub fn from_f32s(scalar: BiquadCoefficients) -> Self { + Self { + b0: T::from_f32(scalar.b0), + b1: T::from_f32(scalar.b1), + b2: T::from_f32(scalar.b2), + a1: T::from_f32(scalar.a1), + a2: T::from_f32(scalar.a2), + } + } + + /// Filter coefficients that would cause the sound to be passed through as is. + pub fn identity() -> Self { + Self::from_f32s(BiquadCoefficients { + b0: 1.0, + b1: 0.0, + b2: 0.0, + a1: 0.0, + a2: 0.0, + }) + } + + /// Compute the coefficients for a low-pass filter. + /// + /// Based on . + pub fn lowpass(sample_rate: f32, frequency: f32, q: f32) -> Self { + nih_debug_assert!(sample_rate > 0.0); + nih_debug_assert!(frequency > 0.0); + nih_debug_assert!(frequency < sample_rate / 2.0); + nih_debug_assert!(q > 0.0); + + let omega0 = consts::TAU * (frequency / sample_rate); + let cos_omega0 = omega0.cos(); + let alpha = omega0.sin() / (2.0 * q); + + // We'll prenormalize everything with a0 + let a0 = 1.0 + alpha; + let b0 = ((1.0 - cos_omega0) / 2.0) / a0; + let b1 = (1.0 - cos_omega0) / a0; + let b2 = ((1.0 - cos_omega0) / 2.0) / a0; + let a1 = (-2.0 * cos_omega0) / a0; + let a2 = (1.0 - alpha) / a0; + + Self::from_f32s(BiquadCoefficients { b0, b1, b2, a1, a2 }) + } + + /// Compute the coefficients for a high-pass filter. + /// + /// Based on . + pub fn highpass(sample_rate: f32, frequency: f32, q: f32) -> Self { + nih_debug_assert!(sample_rate > 0.0); + nih_debug_assert!(frequency > 0.0); + nih_debug_assert!(frequency < sample_rate / 2.0); + nih_debug_assert!(q > 0.0); + + let omega0 = consts::TAU * (frequency / sample_rate); + let cos_omega0 = omega0.cos(); + let alpha = omega0.sin() / (2.0 * q); + + // We'll prenormalize everything with a0 + let a0 = 1.0 + alpha; + let b0 = ((1.0 + cos_omega0) / 2.0) / a0; + let b1 = -(1.0 + cos_omega0) / a0; + let b2 = ((1.0 + cos_omega0) / 2.0) / a0; + let a1 = (-2.0 * cos_omega0) / a0; + let a2 = (1.0 - alpha) / a0; + + Self::from_f32s(BiquadCoefficients { b0, b1, b2, a1, a2 }) + } + + /// Compute the coefficients for an all-pass filter. + /// + /// Based on . + pub fn allpass(sample_rate: f32, frequency: f32, q: f32) -> Self { + nih_debug_assert!(sample_rate > 0.0); + nih_debug_assert!(frequency > 0.0); + nih_debug_assert!(frequency < sample_rate / 2.0); + nih_debug_assert!(q > 0.0); + + let omega0 = consts::TAU * (frequency / sample_rate); + let cos_omega0 = omega0.cos(); + let alpha = omega0.sin() / (2.0 * q); + + // We'll prenormalize everything with a0 + let a0 = 1.0 + alpha; + let b0 = (1.0 - alpha) / a0; + let b1 = (-2.0 * cos_omega0) / a0; + let b2 = (1.0 + alpha) / a0; + let a1 = (-2.0 * cos_omega0) / a0; + let a2 = (1.0 - alpha) / a0; + + Self::from_f32s(BiquadCoefficients { b0, b1, b2, a1, a2 }) + } +} + +impl SimdType for f32 { + #[inline(always)] + fn from_f32(value: f32) -> Self { + value + } +} + +impl SimdType for f32x2 { + #[inline(always)] + fn from_f32(value: f32) -> Self { + f32x2::splat(value) + } +} diff --git a/plugins/crossover/src/lib.rs b/plugins/crossover/src/lib.rs index cb1895e3..52ed442b 100644 --- a/plugins/crossover/src/lib.rs +++ b/plugins/crossover/src/lib.rs @@ -26,7 +26,6 @@ use nih_plug::prelude::*; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -mod biquad; mod crossover; /// The number of bands. Not used directly here, but this avoids hardcoding some constants in the