use crate::Clamp;
use core::ops::{Add, Div, Mul};
use dsp_fixedpoint::Q;
use dsp_process::{Process, SplitInplace, SplitProcess};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use num_traits::float::FloatCore as _;
use num_traits::{AsPrimitive, Float, clamp};
#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Biquad<C> {
pub ba: [C; 5],
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct BiquadClamp<C, T = C> {
pub coeff: Biquad<C>,
pub u: T,
pub min: T,
pub max: T,
}
impl<C, T: Clamp> Default for BiquadClamp<C, T>
where
Biquad<C>: Default,
{
fn default() -> Self {
Self {
coeff: Biquad::default(),
u: T::ZERO,
min: T::MIN,
max: T::MAX,
}
}
}
impl<C: Clamp + Copy> Biquad<C> {
pub const IDENTITY: Self = Self::proportional(C::ONE);
pub const fn proportional(k: C) -> Self {
Self {
ba: [k, C::ZERO, C::ZERO, C::ZERO, C::ZERO],
}
}
pub const HOLD: Self = Self {
ba: [C::ZERO, C::ZERO, C::ZERO, C::ONE, C::ZERO],
};
}
impl<C: Copy + Add<Output = C>> Biquad<C> {
pub fn forward_gain(&self) -> C {
self.ba[0] + self.ba[1] + self.ba[2]
}
}
impl<C: Copy + Add<Output = C>, T: Copy + Div<C, Output = T> + Mul<C, Output = T>>
BiquadClamp<C, T>
{
pub fn input_offset(&self) -> T {
self.u / self.coeff.forward_gain()
}
pub fn set_input_offset(&mut self, i: T) {
self.u = i * self.coeff.forward_gain();
}
}
#[derive(Clone, Debug)]
pub struct DirectForm<T, const N: usize = 1, const M: usize = 2> {
pub x: [T; M],
pub y: [[T; M]; N],
}
impl<T, const N: usize, const M: usize> Default for DirectForm<T, N, M>
where
[T; M]: Default,
[[T; M]; N]: Default,
{
fn default() -> Self {
Self {
x: Default::default(),
y: Default::default(),
}
}
}
impl<T: Copy, const N: usize> DirectForm<T, N> {
pub fn x0(&self) -> T {
self.x[0]
}
pub fn y0(&self) -> T {
self.y.last().unwrap_or(&self.x)[0]
}
pub fn set_y(&mut self, y: T) {
if let Some(sy) = self.y.last_mut() {
*sy = [y, y];
}
}
}
impl<T: Copy + Add<Output = T>, const N: usize> Process<T> for DirectForm<T, N, 1> {
fn process(&mut self, x: T) -> T {
let (y0, y) = self.y.iter_mut().fold((x, &mut self.x), |(x0, x), y| {
let y0 = y[0] + x0 + x[0];
x[0] = x0;
(y0, y)
});
y[0] = y0;
y0
}
}
pub type DirectForm1<T, const M: usize = 2> = DirectForm<T, 1, M>;
#[derive(Default, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Cascade<C>(pub C);
impl<
const N: usize,
T: 'static + Copy,
C: Copy + Mul<T, Output = A>,
A: Add<Output = A> + AsPrimitive<T>,
> SplitProcess<T, T, DirectForm<T, N>> for Cascade<[Biquad<C>; N]>
{
fn process(&self, state: &mut DirectForm<T, N>, x0: T) -> T {
let (y0, y) =
self.0
.iter()
.zip(state.y.iter_mut())
.fold((x0, &mut state.x), |(x0, x), (c, y)| {
let y0 = (c.ba[0] * x0
+ c.ba[1] * x[0]
+ c.ba[2] * x[1]
+ c.ba[3] * y[0]
+ c.ba[4] * y[1])
.as_();
*x = [x0, x[0]];
(y0, y)
});
*y = [y0, y[0]];
y0
}
}
impl<
T: 'static + Copy + Add<Output = T> + PartialOrd,
C: Copy + Mul<T, Output = A>,
A: Add<Output = A> + AsPrimitive<T>,
> SplitProcess<T, T, DirectForm1<T>> for Biquad<C>
{
fn process(&self, state: &mut DirectForm1<T>, x0: T) -> T {
let y0 = (self.ba[0] * x0
+ self.ba[1] * state.x[0]
+ self.ba[2] * state.x[1]
+ self.ba[3] * state.y[0][0]
+ self.ba[4] * state.y[0][1])
.as_();
state.x = [x0, state.x[0]];
state.y[0] = [y0, state.y[0][0]];
y0
}
}
impl<T: Copy + Add<Output = T> + PartialOrd, C> SplitProcess<T, T, DirectForm1<T>>
for BiquadClamp<C, T>
where
Biquad<C>: SplitProcess<T, T, DirectForm1<T>>,
{
fn process(&self, state: &mut DirectForm1<T>, x0: T) -> T {
let y0 = clamp(self.coeff.process(state, x0) + self.u, self.min, self.max);
state.y[0][0] = y0; y0
}
}
pub type DirectForm2Transposed<T, const M: usize = 2> = DirectForm<T, 0, M>;
impl<T: Copy + Mul<Output = T> + Add<Output = T>> SplitProcess<T, T, DirectForm2Transposed<T>>
for Biquad<T>
{
fn process(&self, state: &mut DirectForm2Transposed<T>, x0: T) -> T {
let ba = &self.ba;
let y0 = state.x[0] + ba[0] * x0;
state.x[0] = state.x[1] + ba[1] * x0 + ba[3] * y0;
state.x[1] = ba[2] * x0 + ba[4] * y0;
y0
}
}
impl<T: Copy + Add<Output = T> + Mul<Output = T> + PartialOrd>
SplitProcess<T, T, DirectForm2Transposed<T>> for BiquadClamp<T, T>
{
fn process(&self, state: &mut DirectForm2Transposed<T>, x0: T) -> T {
let ba = &self.coeff.ba;
let y0 = clamp(state.x[0] + ba[0] * x0 + self.u, self.min, self.max);
state.x[0] = state.x[1] + ba[1] * x0 + ba[3] * y0;
state.x[1] = ba[2] * x0 + ba[4] * y0;
y0
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct DirectForm1Wide {
pub x: [i32; 2],
pub y: [i64; 2],
}
impl<const F: i8> SplitProcess<i32, i32, DirectForm1Wide> for Biquad<Q<i32, i64, F>> {
fn process(&self, state: &mut DirectForm1Wide, x0: i32) -> i32 {
const {
assert!(F >= 0 && F < 32);
}
let mut acc =
(self.ba[0] * x0 + self.ba[1] * state.x[0] + self.ba[2] * state.x[1]).into_bits();
state.x = [x0, state.x[0]];
acc += (state.y[0] as u32 as i64 * self.ba[3].into_bits() as i64) >> 32;
acc += (state.y[0] >> 32) as i32 as i64 * self.ba[3].into_bits() as i64;
acc += (state.y[1] as u32 as i64 * self.ba[4].into_bits() as i64) >> 32;
acc += (state.y[1] >> 32) as i32 as i64 * self.ba[4].into_bits() as i64;
acc <<= 32 - F;
state.y = [acc, state.y[0]];
(acc >> 32) as _
}
}
impl<const F: i8> SplitProcess<i32, i32, DirectForm1Wide> for BiquadClamp<Q<i32, i64, F>, i32> {
fn process(&self, state: &mut DirectForm1Wide, x0: i32) -> i32 {
let y0 = clamp(self.coeff.process(state, x0) + self.u, self.min, self.max);
state.y[0] = ((y0 as i64) << 32) | state.y[0] as u32 as i64; y0
}
}
#[derive(Clone, Debug, Default)]
pub struct DirectForm1Dither {
pub xy: DirectForm1<i32>,
pub e: u32,
}
impl<const F: i8> SplitProcess<i32, i32, DirectForm1Dither> for Biquad<Q<i32, i64, F>> {
fn process(&self, state: &mut DirectForm1Dither, x0: i32) -> i32 {
const {
assert!(F >= 0 && F < 32);
}
let mut acc = state.e as i64
+ (self.ba[0] * x0
+ self.ba[1] * state.xy.x[0]
+ self.ba[2] * state.xy.x[1]
+ self.ba[3] * state.xy.y[0][0]
+ self.ba[4] * state.xy.y[0][1])
.into_bits();
acc <<= 32 - F;
state.e = (acc as u32) >> (32 - F);
let y0 = (acc >> 32) as _;
state.xy.x = [x0, state.xy.x[0]];
state.xy.y[0] = [y0, state.xy.y[0][0]];
y0
}
}
impl<const F: i8> SplitProcess<i32, i32, DirectForm1Dither> for BiquadClamp<Q<i32, i64, F>, i32> {
fn process(&self, state: &mut DirectForm1Dither, x0: i32) -> i32 {
let y0 = clamp(self.coeff.process(state, x0) + self.u, self.min, self.max);
state.xy.y[0][0] = y0; y0
}
}
impl<C, T: Copy, S> SplitInplace<T, S> for Biquad<C> where Self: SplitProcess<T, T, S> {}
impl<C, T: Copy, S> SplitInplace<T, S> for BiquadClamp<C, T> where Self: SplitProcess<T, T, S> {}
impl<C, T: Copy, S> SplitInplace<T, S> for Cascade<C> where Self: SplitProcess<T, T, S> {}
macro_rules! impl_from_float {
($ty:ident) => {
impl<C> From<[[$ty; 3]; 2]> for Biquad<C>
where
[$ty; 5]: Into<Biquad<C>>,
{
fn from(ba: [[$ty; 3]; 2]) -> Self {
let a0 = 1.0 / ba[1][0];
[
ba[0][0] * a0,
ba[0][1] * a0,
ba[0][2] * a0,
-ba[1][1] * a0,
-ba[1][2] * a0,
]
.into()
}
}
};
}
impl_from_float!(f32);
impl_from_float!(f64);
impl<C: Copy + 'static, T: AsPrimitive<C>> From<[T; 5]> for Biquad<C> {
fn from(ba: [T; 5]) -> Self {
Self {
ba: ba.map(AsPrimitive::as_),
}
}
}
impl<C, T, F: Into<Biquad<C>>> From<F> for BiquadClamp<C, T>
where
Self: Default,
{
fn from(coeff: F) -> Self {
Self {
coeff: coeff.into(),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub enum Pair<T> {
Real([T; 2]),
Complex([T; 2]),
}
impl<T: Float> Pair<T> {
pub fn coeff(self) -> [T; 2] {
match self {
Self::Real([x, y]) => [x + y, x * y],
Self::Complex([x, y]) => [x + x, x * x + y * y],
}
}
}
impl<C> Biquad<C> {
pub fn from_zpk<T: Float>(zeros: Pair<T>, poles: Pair<T>, gain: T) -> Self
where
Self: From<[T; 5]>,
{
let b = zeros.coeff().map(|b| gain * b);
let a = poles.coeff();
[gain, -b[0], b[1], a[0], -a[1]].into()
}
}
#[cfg(test)]
mod test {
#![allow(dead_code)]
use super::*;
use dsp_fixedpoint::Q32;
use dsp_process::{SplitInplace, SplitProcess};
pub fn pnm(
config: &Cascade<[Biquad<Q32<29>>; 4]>,
state: &mut DirectForm<i32, 4>,
xy0: &mut [i32; 1 << 3],
) {
config.inplace(state, xy0);
}
#[test]
#[ignore]
fn sos_insn() {
let cfg = Cascade(
[
[[1., 3., 5.], [19., -9., 9.]],
[[3., 3., 5.], [21., -11., 11.]],
[[1., 3., 5.], [55., -17., 17.]],
[[1., 8., 5.], [77., -7., 7.]],
]
.map(Biquad::from),
);
let mut state = Default::default();
let mut x = [977371917; 1 << 7];
for _ in 0..1 << 20 {
x[9] = x[63];
let (x, []) = x.as_chunks_mut() else {
unreachable!()
};
for x in x {
pnm(&cfg, &mut state, x);
}
}
}
#[test]
fn direct_form1_matches_df2t_for_float_stream() {
let biquad = Biquad::from([[0.7, -0.4, 0.1], [1.0, -0.2, 0.05]]);
let mut df1 = DirectForm1::default();
let mut df2t = DirectForm2Transposed::default();
let x = [-1.0, 0.25, 0.75, -0.5, 0.125, 0.0, 0.5, -0.25];
for x0 in x {
let y1 = biquad.process(&mut df1, x0);
let y2 = biquad.process(&mut df2t, x0);
assert!(f32::abs(y1 - y2) < 1e-6, "{y1} != {y2} for x0={x0}");
}
}
#[test]
fn cascade_matches_repeated_single_stage_application() {
let stage: Biquad<f32> = Biquad::from([[0.5, 0.25, 0.125], [1.0, -0.1, 0.02]]);
let cascade = Cascade([stage.clone(), stage.clone(), stage.clone()]);
let mut cascade_state = DirectForm::<f32, 3>::default();
let mut repeated_state: [DirectForm1<f32>; 3] =
core::array::from_fn(|_| DirectForm1::<f32>::default());
let x = [-0.75, 0.5, 0.0, 0.25, -0.125, 1.0, -0.5, 0.375];
for x0 in x {
let yc = cascade.process(&mut cascade_state, x0);
let yr = repeated_state
.iter_mut()
.fold(x0, |y, state| stage.process(state, y));
assert!(f32::abs(yc - yr) < 1e-6, "{yc} != {yr} for x0={x0}");
}
}
}