use num_complex::Complex;
use num_complex::Complex64;
use rill_core::Transcendental;
#[inline(always)]
pub fn bilinear_transform(s: Complex64) -> Complex64 {
let two = Complex64::new(2.0, 0.0);
(two + s) / (two - s)
}
#[inline(always)]
pub fn prewarp_frequency(freq: f64, sample_rate: f64) -> f64 {
2.0 * (std::f64::consts::PI * freq / sample_rate).tan()
}
#[inline(always)]
pub fn conjugate_pair_to_coeffs(z1: Complex64, z2: Complex64) -> (f64, f64) {
let a1 = -(z1 + z2).re;
let a2 = (z1 * z2).re;
(a1, a2)
}
#[inline(always)]
pub fn single_pole_to_coeffs(z: Complex64) -> (f64, f64) {
(-z.re, 0.0)
}
#[inline(always)]
pub fn mul_complex<T>(a: Complex<T>, b: Complex<T>) -> Complex<T>
where
T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T>,
{
Complex::new(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re)
}
#[inline(always)]
pub fn mul_complex_add<T>(acc: &mut Complex<T>, a: Complex<T>, b: Complex<T>)
where
T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T>,
{
acc.re = acc.re + (a.re * b.re - a.im * b.im);
acc.im = acc.im + (a.re * b.im + a.im * b.re);
}
#[inline(always)]
pub fn mul_complex_4<T>(
a_re: &[T],
a_im: &[T],
b_re: &[T],
b_im: &[T],
out_re: &mut [T],
out_im: &mut [T],
) where
T: Transcendental + 'static,
{
use rill_core::prelude::{ComplexSoa, ScalarVector4};
let a = ComplexSoa::<T, ScalarVector4<T>>::load(a_re, a_im);
let b = ComplexSoa::<T, ScalarVector4<T>>::load(b_re, b_im);
let prod = a * b;
prod.store(out_re, out_im);
}
#[inline(always)]
pub fn mul_complex_add_4<T>(
acc_re: &mut [T],
acc_im: &mut [T],
a_re: &[T],
a_im: &[T],
b_re: &[T],
b_im: &[T],
) where
T: Transcendental + 'static,
{
use rill_core::prelude::{ComplexSoa, ScalarVector4};
let mut acc = ComplexSoa::<T, ScalarVector4<T>>::load(acc_re, acc_im);
let a = ComplexSoa::<T, ScalarVector4<T>>::load(a_re, a_im);
let b = ComplexSoa::<T, ScalarVector4<T>>::load(b_re, b_im);
acc.cmul_add(&a, &b);
acc.store(acc_re, acc_im);
}
pub fn soa_from_interleaved<T: Transcendental + 'static>(
slice: &[Complex<T>],
) -> rill_core::prelude::ComplexSoa<T, rill_core::prelude::ScalarVector4<T>> {
use rill_core::prelude::ComplexSoa;
ComplexSoa::from_pairs([
(slice[0].re, slice[0].im),
(slice[1].re, slice[1].im),
(slice[2].re, slice[2].im),
(slice[3].re, slice[3].im),
])
}
pub fn soa_to_interleaved<T: Transcendental + 'static>(
soa: &rill_core::prelude::ComplexSoa<T, rill_core::prelude::ScalarVector4<T>>,
slice: &mut [Complex<T>],
) {
let c = soa.to_complexes();
slice[0] = Complex::new(c[0].0, c[0].1);
slice[1] = Complex::new(c[1].0, c[1].1);
slice[2] = Complex::new(c[2].0, c[2].1);
slice[3] = Complex::new(c[3].0, c[3].1);
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ComplexMat2<T> {
pub m00: Complex<T>,
pub m01: Complex<T>,
pub m10: Complex<T>,
pub m11: Complex<T>,
}
impl<T> ComplexMat2<T>
where
T: Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ std::ops::Neg<Output = T>
+ num_traits::Float
+ Transcendental
+ 'static,
{
pub fn new(m00: Complex<T>, m01: Complex<T>, m10: Complex<T>, m11: Complex<T>) -> Self {
Self { m00, m01, m10, m11 }
}
pub fn zero() -> Self {
Self {
m00: Complex::new(T::zero(), T::zero()),
m01: Complex::new(T::zero(), T::zero()),
m10: Complex::new(T::zero(), T::zero()),
m11: Complex::new(T::zero(), T::zero()),
}
}
fn pack_soa(&self) -> rill_core::prelude::ComplexSoa<T, rill_core::prelude::ScalarVector4<T>>
where
T: Transcendental + 'static,
{
rill_core::prelude::ComplexSoa::from_pairs([
(self.m00.re, self.m00.im),
(self.m01.re, self.m01.im),
(self.m10.re, self.m10.im),
(self.m11.re, self.m11.im),
])
}
fn unpack_soa(
soa: &rill_core::prelude::ComplexSoa<T, rill_core::prelude::ScalarVector4<T>>,
) -> Self
where
T: Transcendental + 'static,
{
let c = soa.to_complexes();
Self {
m00: Complex::new(c[0].0, c[0].1),
m01: Complex::new(c[1].0, c[1].1),
m10: Complex::new(c[2].0, c[2].1),
m11: Complex::new(c[3].0, c[3].1),
}
}
pub fn identity() -> Self {
let zero = Complex::new(T::zero(), T::zero());
let one = Complex::new(T::one(), T::zero());
Self {
m00: one,
m01: zero,
m10: zero,
m11: one,
}
}
pub fn det(&self) -> Complex<T> {
self.m00 * self.m11 - self.m01 * self.m10
}
pub fn inv(&self) -> Option<Self> {
let d = self.det();
if d.norm_sqr() <= T::epsilon() {
return None;
}
Some(Self {
m00: self.m11 / d,
m01: -self.m01 / d,
m10: -self.m10 / d,
m11: self.m00 / d,
})
}
pub fn trace(&self) -> Complex<T> {
self.m00 + self.m11
}
pub fn eigenvalues(&self) -> Option<[Complex<T>; 2]> {
let tr = self.trace();
let det = self.det();
let half = T::from(0.5).unwrap();
let disc = tr * tr - Complex::new(T::from(4.0).unwrap(), T::zero()) * det;
if disc.norm_sqr() <= T::epsilon() {
return None;
}
let sqrt_disc = disc.sqrt();
Some([
Complex::new(half, T::zero()) * (tr + sqrt_disc),
Complex::new(half, T::zero()) * (tr - sqrt_disc),
])
}
pub fn mul_vec(&self, x: Complex<T>, y: Complex<T>) -> [Complex<T>; 2] {
let m = self.pack_soa();
let vec = rill_core::prelude::ComplexSoa::<T, rill_core::prelude::ScalarVector4<T>>::load(
&[x.re, y.re, x.re, y.re],
&[x.im, y.im, x.im, y.im],
);
let prod = m * vec;
let c0 = prod.extract_complex(0);
let c1 = prod.extract_complex(1);
let c2 = prod.extract_complex(2);
let c3 = prod.extract_complex(3);
[
Complex::new(c0.0 + c1.0, c0.1 + c1.1),
Complex::new(c2.0 + c3.0, c2.1 + c3.1),
]
}
pub fn scale(&self, s: T) -> Self {
use rill_core::prelude::Vector;
let soa = self.pack_soa();
let s_soa = rill_core::prelude::ScalarVector4::<T>::splat(s);
let scaled = soa * s_soa;
Self::unpack_soa(&scaled)
}
}
impl<T> std::ops::Add for ComplexMat2<T>
where
T: Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ std::ops::Neg<Output = T>
+ num_traits::Float
+ Transcendental
+ 'static,
{
type Output = Self;
fn add(self, rhs: Self) -> Self {
let a = self.pack_soa();
let b = rhs.pack_soa();
let sum = a + b;
Self::unpack_soa(&sum)
}
}
impl<T> std::ops::Sub for ComplexMat2<T>
where
T: Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ std::ops::Neg<Output = T>
+ num_traits::Float
+ Transcendental
+ 'static,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self {
let a = self.pack_soa();
let b = rhs.pack_soa();
let diff = a - b;
Self::unpack_soa(&diff)
}
}
impl<T> std::ops::Mul for ComplexMat2<T>
where
T: Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ std::ops::Neg<Output = T>
+ num_traits::Float
+ Transcendental
+ 'static,
{
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self {
m00: self.m00 * rhs.m00 + self.m01 * rhs.m10,
m01: self.m00 * rhs.m01 + self.m01 * rhs.m11,
m10: self.m10 * rhs.m00 + self.m11 * rhs.m10,
m11: self.m10 * rhs.m01 + self.m11 * rhs.m11,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ComplexMat3<T> {
pub m: [[Complex<T>; 3]; 3],
}
impl<T> ComplexMat3<T>
where
T: Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ std::ops::Neg<Output = T>
+ num_traits::Float,
{
pub fn from_rows(rows: [[Complex<T>; 3]; 3]) -> Self {
Self { m: rows }
}
pub fn zero() -> Self {
Self {
m: [[Complex::new(T::zero(), T::zero()); 3]; 3],
}
}
pub fn identity() -> Self {
let zero = Complex::new(T::zero(), T::zero());
let one = Complex::new(T::one(), T::zero());
let mut m = [[zero; 3]; 3];
m[0][0] = one;
m[1][1] = one;
m[2][2] = one;
Self { m }
}
pub fn det(&self) -> Complex<T> {
let a = self.m[0][0];
let b = self.m[0][1];
let c = self.m[0][2];
let d = self.m[1][0];
let e = self.m[1][1];
let f = self.m[1][2];
let g = self.m[2][0];
let h = self.m[2][1];
let i = self.m[2][2];
a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
}
pub fn trace(&self) -> Complex<T> {
self.m[0][0] + self.m[1][1] + self.m[2][2]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_det_is_one() {
let m = ComplexMat2::<f32>::identity();
let d = m.det();
assert!((d.re - 1.0).abs() < 1e-6);
assert!(d.im.abs() < 1e-6);
}
#[test]
fn test_inv_times_mat_is_identity() {
let m = ComplexMat2::<f32>::new(
Complex::new(2.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(3.0, 0.0),
);
let inv = m.inv().unwrap();
let prod = m * inv;
assert!((prod.m00.re - 1.0).abs() < 1e-4);
assert!((prod.m11.re - 1.0).abs() < 1e-4);
}
#[test]
fn test_singular_inv_returns_none() {
let m = ComplexMat2::<f32>::new(
Complex::new(1.0, 0.0),
Complex::new(2.0, 0.0),
Complex::new(2.0, 0.0),
Complex::new(4.0, 0.0),
);
assert!(m.inv().is_none());
}
#[test]
fn test_eigenvalues() {
let m = ComplexMat2::<f32>::new(
Complex::new(0.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(-2.0, 0.0),
Complex::new(-3.0, 0.0),
);
let ev = m.eigenvalues().unwrap();
assert!((ev[0].re + 1.0).abs() < 1e-4 || (ev[0].re + 2.0).abs() < 1e-4);
assert!((ev[1].re + 1.0).abs() < 1e-4 || (ev[1].re + 2.0).abs() < 1e-4);
}
#[test]
fn test_mat3_det() {
let m = ComplexMat3::<f32>::identity();
let d = m.det();
assert!((d.re - 1.0).abs() < 1e-6);
}
#[test]
fn test_complex_mat2_f32_rt_steps() {
let a1 = Complex::new(0.3, 0.5);
let a2 = Complex::new(-0.2, -0.1);
let mut y = [Complex::new(0.0f32, 0.0f32); 2];
let mut all_finite = true;
for n in 0..100 {
let x = Complex::new((n as f32 * 0.1).sin(), (n as f32 * 0.07).cos());
let y_n = x - a1 * y[0] - a2 * y[1];
y[1] = y[0];
y[0] = y_n;
all_finite &= y_n.re.is_finite() && y_n.im.is_finite();
}
assert!(all_finite, "complex biquad produced NaN/Inf in RT path");
}
#[test]
fn test_complex_mat2_f32_rotation() {
let freq = 1000.0f32;
let sr = 44100.0f32;
let phase_step = 2.0 * std::f32::consts::PI * freq / sr;
let rot = ComplexMat2::<f32>::new(
Complex::new(phase_step.cos(), phase_step.sin()), Complex::new(0.0, 0.0), Complex::new(0.0, 0.0), Complex::new(phase_step.cos(), phase_step.sin()), );
let z = Complex::new(1.0f32, 0.0);
let [re, _im] = rot.mul_vec(z, Complex::new(0.0, 0.0));
assert!((re.re - phase_step.cos()).abs() < 1e-4);
assert!((re.im - phase_step.sin()).abs() < 1e-4);
}
#[test]
fn test_complex_mat2_f64_rt_stability() {
let a1 = Complex::new(0.3f64, 0.5f64);
let a2 = Complex::new(-0.2f64, -0.1f64);
let mut y = [Complex::new(0.0f64, 0.0f64); 2];
let mut all_finite = true;
for n in 0..1000 {
let x = Complex::new((n as f64 * 0.1).sin(), (n as f64 * 0.07).cos());
let y_n = x - a1 * y[0] - a2 * y[1];
y[1] = y[0];
y[0] = y_n;
all_finite &= y_n.re.is_finite() && y_n.im.is_finite();
}
assert!(all_finite, "complex biquad f64 produced NaN/Inf");
}
#[test]
fn test_mul_complex_4() {
let a_re = [1.0f32, 0.0, 0.5, -0.5];
let a_im = [0.0f32, 1.0, 0.5, 0.5];
let b_re = [2.0f32, 0.0, 1.0, 1.0];
let b_im = [3.0f32, 1.0, 1.0, -1.0];
let mut out_re = [0.0f32; 4];
let mut out_im = [0.0f32; 4];
mul_complex_4(&a_re, &a_im, &b_re, &b_im, &mut out_re, &mut out_im);
assert!((out_re[0] - 2.0).abs() < 1e-4);
assert!((out_im[0] - 3.0).abs() < 1e-4);
assert!((out_re[1] + 1.0).abs() < 1e-4);
assert!((out_im[1] - 0.0).abs() < 1e-4);
}
#[test]
fn test_mul_complex_add_4() {
let mut acc_re = [1.0f32; 4];
let mut acc_im = [0.0f32; 4];
let a_re = [1.0f32, 0.0, 2.0, 0.0];
let a_im = [0.0f32, 1.0, 0.0, 0.0];
let b_re = [2.0f32, 0.0, 3.0, 0.0];
let b_im = [3.0f32, 1.0, 0.0, 0.0];
mul_complex_add_4(&mut acc_re, &mut acc_im, &a_re, &a_im, &b_re, &b_im);
assert!((acc_re[0] - 3.0).abs() < 1e-4);
assert!((acc_im[0] - 3.0).abs() < 1e-4);
assert!((acc_re[1] - 0.0).abs() < 1e-4);
assert!((acc_im[1] - 0.0).abs() < 1e-4);
}
}