use core::f64::consts::PI;
pub const NUM_BANDS: usize = 4;
pub const PROTO_LEN: usize = 96;
pub const Q_HALF: [f64; 48] = [
9.765529100757551e-5,
1.3809589379038567e-4,
9.840074925662353e-5,
-8.667154478233572e-5,
-4.6217998911921346e-4,
-1.0211814095158174e-3,
-1.6772149340010668e-3,
-2.253333895141108e-3,
-2.4987888343213967e-3,
-2.139081596676188e-3,
-9.559539745459777e-4,
1.1172111530118943e-3,
3.909130912734858e-3,
6.963570342011867e-3,
9.559544215947834e-3,
1.081576654002136e-2,
9.87705149917153e-3,
6.156256729132736e-3,
-4.179394606362971e-4,
-9.212874309770764e-3,
-1.883077587336902e-2,
-2.7226498457701823e-2,
-3.2022840857588906e-2,
-3.099633252775461e-2,
-2.2656858741499447e-2,
-6.803111385896335e-3,
1.5085400948280744e-2,
3.975099338827274e-2,
6.244536362943674e-2,
7.762232774872133e-2,
7.996833849613293e-2,
6.561549306847558e-2,
3.331365830088269e-2,
-1.4691563058190206e-2,
-7.230789047533415e-2,
-1.2993222541703875e-1,
-1.7551641029040532e-1,
-1.9626543957670528e-1,
-1.807333067021503e-1,
-1.2097653136035738e-1,
-1.4377370758549035e-2,
1.3522730742860303e-1,
3.1737852699301633e-1,
5.159002179848223e-1,
7.108002037976138e-1,
8.80906324884448e-1,
1.0068321641150089e0,
1.0737914947736096e0,
];
#[must_use]
pub fn prototype() -> [f64; PROTO_LEN] {
let mut q = [0.0f64; PROTO_LEN];
q[..48].copy_from_slice(&Q_HALF);
for j in 48..PROTO_LEN {
q[j] = Q_HALF[95 - j];
}
q
}
#[must_use]
fn synthesis_coef(q: &[f64; PROTO_LEN], b: usize, j: usize) -> f64 {
let angle = (2.0 * b as f64 + 1.0) * (2.0 * j as f64 - 3.0) * PI / 16.0;
q[j] * angle.cos()
}
#[derive(Debug, Clone)]
pub struct Ipqf {
coefs: [[f64; PROTO_LEN]; NUM_BANDS],
history: [Vec<f64>; NUM_BANDS],
}
const HISTORY: usize = PROTO_LEN.div_ceil(NUM_BANDS);
impl Default for Ipqf {
fn default() -> Self {
Self::new()
}
}
impl Ipqf {
#[must_use]
pub fn new() -> Self {
let q = prototype();
let mut coefs = [[0.0f64; PROTO_LEN]; NUM_BANDS];
for (b, band) in coefs.iter_mut().enumerate() {
for (j, slot) in band.iter_mut().enumerate() {
*slot = synthesis_coef(&q, b, j);
}
}
let history = core::array::from_fn(|_| vec![0.0f64; HISTORY]);
Ipqf { coefs, history }
}
#[must_use]
pub fn synthesize(&mut self, bands: &[&[f64]; NUM_BANDS], len: usize) -> Vec<f64> {
let mut out = Vec::with_capacity(NUM_BANDS * len);
for step in 0..len {
for (hist, band) in self.history.iter_mut().zip(bands.iter()) {
hist.push(band[step]);
}
for p in 0..NUM_BANDS {
let mut acc = 0.0f64;
for (coefs, hist) in self.coefs.iter().zip(self.history.iter()) {
let l = hist.len();
let mut j = p;
while j < PROTO_LEN {
let t = j / NUM_BANDS; if t < l {
acc += coefs[j] * hist[l - 1 - t];
}
j += NUM_BANDS;
}
}
out.push(acc);
}
for hist in &mut self.history {
let l = hist.len();
if l > HISTORY {
hist.drain(0..l - HISTORY);
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prototype_is_symmetric() {
let q = prototype();
for j in 0..PROTO_LEN {
assert!((q[j] - q[95 - j]).abs() < 1e-15, "Q({j}) != Q({})", 95 - j);
}
assert!((q[0] - 9.765529100757551e-5).abs() < 1e-18);
assert!((q[47] - 1.0737914947736096e0).abs() < 1e-15);
assert!((q[48] - 1.0737914947736096e0).abs() < 1e-15);
assert!((q[95] - 9.765529100757551e-5).abs() < 1e-18);
}
#[test]
fn silence_produces_silence() {
let mut ipqf = Ipqf::new();
let z = vec![0.0f64; 16];
let bands: [&[f64]; NUM_BANDS] = [&z, &z, &z, &z];
let out = ipqf.synthesize(&bands, 16);
assert_eq!(out.len(), NUM_BANDS * 16);
assert!(out.iter().all(|&x| x == 0.0));
}
#[test]
fn output_length_is_four_times_band_steps() {
let mut ipqf = Ipqf::new();
let s: Vec<f64> = (0..10).map(|i| i as f64).collect();
let bands: [&[f64]; NUM_BANDS] = [&s, &s, &s, &s];
let out = ipqf.synthesize(&bands, 10);
assert_eq!(out.len(), 40);
assert!(out.iter().all(|x| x.is_finite()));
}
#[test]
fn synthesis_coef_first_band_zero_tap() {
let q = prototype();
let expect = q[0] * ((-3.0) * PI / 16.0).cos();
assert!((synthesis_coef(&q, 0, 0) - expect).abs() < 1e-15);
}
#[test]
fn impulse_response_matches_direct_convolution() {
let q = prototype();
let mut ipqf = Ipqf::new();
let mut b0 = vec![0.0f64; 30];
b0[0] = 1.0; let z = vec![0.0f64; 30];
let bands: [&[f64]; NUM_BANDS] = [&b0, &z, &z, &z];
let out = ipqf.synthesize(&bands, 30);
for (n, &got) in out.iter().take(PROTO_LEN).enumerate() {
let expect = synthesis_coef(&q, 0, n);
assert!(
(got - expect).abs() < 1e-12,
"AS({n}) = {got} != Q_0({n}) = {expect}"
);
}
}
}