use crate::Clamp;
use core::ops::{Add, Div, Mul};
use dsp_fixedpoint::Q;
use dsp_process::{SplitInplace, SplitProcess};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use num_traits::float::FloatCore as _;
use num_traits::{AsPrimitive, clamp};
#[derive(Clone, Debug, Default, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
pub struct Biquad<C> {
pub ba: [C; 5],
}
#[derive(Clone, Debug, PartialEq, PartialOrd, 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, Default, serde::Deserialize, serde::Serialize)]
pub struct DirectForm1<T> {
pub xy: [T; 4],
}
impl<T: 'static + Copy, 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 xy = &mut state.xy;
let ba = &self.ba;
let y0 = (ba[0] * x0 + ba[1] * xy[0] + ba[2] * xy[1] + ba[3] * xy[2] + ba[4] * xy[3]).as_();
*xy = [x0, xy[0], y0, xy[2]];
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.xy[2] = y0; y0
}
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct DirectForm2Transposed<T> {
pub u: [T; 2],
}
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 u = &mut state.u;
let ba = &self.ba;
let y0 = u[0] + ba[0] * x0;
u[0] = u[1] + ba[1] * x0 + ba[3] * y0;
u[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 u = &mut state.u;
let ba = &self.coeff.ba;
let y0 = clamp(u[0] + ba[0] * x0 + self.u, self.min, self.max);
u[0] = u[1] + ba[1] * x0 + ba[3] * y0;
u[1] = ba[2] * x0 + ba[4] * y0;
y0
}
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
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 {
let x = &mut state.x;
let y = &mut state.y;
let ba = &self.ba;
let mut acc = (ba[0] * x0 + ba[1] * x[0] + ba[2] * x[1]).inner;
*x = [x0, x[0]];
acc += (y[0] as u32 as i64 * ba[3].inner as i64) >> 32;
acc += (y[0] >> 32) as i32 as i64 * ba[3].inner as i64;
acc += (y[1] as u32 as i64 * ba[4].inner as i64) >> 32;
acc += (y[1] >> 32) as i32 as i64 * ba[4].inner as i64;
acc <<= 32 - F;
*y = [acc, 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 x = &mut state.x;
let y = &mut state.y;
let ba = &self.coeff.ba;
let mut acc = (ba[0] * x0 + ba[1] * x[0] + ba[2] * x[1]).inner;
*x = [x0, x[0]];
acc += (y[0] as u32 as i64 * ba[3].inner as i64) >> 32;
acc += (y[0] >> 32) as i32 as i64 * ba[3].inner as i64;
acc += (y[1] as u32 as i64 * ba[4].inner as i64) >> 32;
acc += (y[1] >> 32) as i32 as i64 * ba[4].inner as i64;
acc <<= 32 - F;
let y0 = Ord::clamp((acc >> 32) as i32 + self.u, self.min, self.max);
*y = [((y0 as i64) << 32) | acc as u32 as i64, y[0]];
y0
}
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct DirectForm1Dither {
pub xy: [i32; 4],
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 {
let xy = &mut state.xy;
let e = &mut state.e;
let ba = &self.ba;
let mut acc = *e as i64
+ (ba[0] * x0 + ba[1] * xy[0] + ba[2] * xy[1] + ba[3] * xy[2] + ba[4] * xy[3]).inner;
acc <<= 32 - F;
*e = acc as _;
let y0 = (acc >> 32) as _;
*xy = [x0, xy[0], y0, xy[2]];
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 xy = &mut state.xy;
let e = &mut state.e;
let ba = &self.coeff.ba;
let mut acc = *e as i64
+ (ba[0] * x0 + ba[1] * xy[0] + ba[2] * xy[1] + ba[3] * xy[2] + ba[4] * xy[3]).inner;
acc <<= 32 - F;
*e = acc as _;
let y0 = Ord::clamp((acc >> 32) as i32 + self.u, self.min, self.max);
*xy = [x0, xy[0], y0, xy[2]];
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> {}
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> From<[T; 5]> for Biquad<C>
where
T: AsPrimitive<C>,
{
fn from(ba: [T; 5]) -> Self {
Self {
ba: ba.map(AsPrimitive::as_),
}
}
}
impl<C, T, F> From<F> for BiquadClamp<C, T>
where
F: Into<Biquad<C>>,
Self: Default,
{
fn from(coeff: F) -> Self {
Self {
coeff: coeff.into(),
..Default::default()
}
}
}
#[cfg(test)]
mod test {
#![allow(dead_code)]
use super::*;
use dsp_fixedpoint::Q32;
use dsp_process::SplitInplace;
pub fn pnm(
config: &[Biquad<Q32<29>>; 4],
state: &mut [DirectForm1<i32>; 4],
xy0: &mut [i32; 1 << 3],
) {
config.inplace(state, xy0);
}
#[test]
#[ignore]
fn sos_insn() {
let cfg = [
[[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(|c| Biquad::from(c));
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);
}
}
}
}