use std::f64::consts::PI;
use std::sync::OnceLock;
use crate::frame::{GRANULE_LINES, SUBBANDS, SUBBAND_LINES};
use super::synth_window::SYNTH_D;
fn matrix() -> &'static [[f32; SUBBANDS]; 64] {
static T: OnceLock<[[f32; SUBBANDS]; 64]> = OnceLock::new();
T.get_or_init(|| {
let mut n = [[0f32; SUBBANDS]; 64];
for (i, row) in n.iter_mut().enumerate() {
for (k, c) in row.iter_mut().enumerate() {
*c = (PI / 64.0 * (16 + i) as f64 * (2 * k + 1) as f64).cos() as f32;
}
}
n
})
}
fn half_dct() -> &'static [[f32; 16]; 32] {
static T: OnceLock<[[f32; 16]; 32]> = OnceLock::new();
T.get_or_init(|| {
let mut c = [[0f32; 16]; 32];
for (m, row) in c.iter_mut().enumerate() {
for (k, e) in row.iter_mut().enumerate() {
*e = (PI / 64.0 * m as f64 * (2 * k + 1) as f64).cos() as f32;
}
}
c
})
}
fn matrixing_fast(s: &[f32; SUBBANDS]) -> [f32; 64] {
let c = half_dct();
let mut plus = [0f32; 16];
let mut minus = [0f32; 16];
for k in 0..16 {
plus[k] = s[k] + s[31 - k];
minus[k] = s[k] - s[31 - k];
}
let mut g = [0f32; 32];
for (m, gm) in g.iter_mut().enumerate() {
let src = if m & 1 == 0 { &plus } else { &minus };
let mut acc = 0f32;
for k in 0..16 {
acc += c[m][k] * src[k];
}
*gm = acc;
}
let mut vv = [0f32; 64];
for (i, vi) in vv.iter_mut().enumerate() {
*vi = if i < 16 {
g[16 + i]
} else if i == 16 {
0.0
} else if i < 48 {
-g[48 - i]
} else {
-g[i - 48]
};
}
vv
}
pub fn polyphase(time: &[f32; GRANULE_LINES], fifo: &mut [f32; 1024]) -> [f32; GRANULE_LINES] {
let d = &SYNTH_D;
let mut pcm = [0f32; GRANULE_LINES];
for v in 0..SUBBAND_LINES {
let mut s = [0f32; SUBBANDS];
for (k, sv) in s.iter_mut().enumerate() {
*sv = time[k * SUBBAND_LINES + v];
}
fifo.copy_within(0..1024 - 64, 64);
fifo[..64].copy_from_slice(&matrixing_fast(&s));
for j in 0..32 {
let mut sum = 0f32;
for i in 0..8 {
sum += fifo[i * 128 + j] * d[i * 64 + j];
sum += fifo[i * 128 + 96 + j] * d[i * 64 + 32 + j];
}
pcm[v * 32 + j] = sum;
}
}
pcm
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matrix_matches_cosine_formula() {
let n = matrix();
let e =
|i: usize, k: usize| (PI / 64.0 * (16 + i) as f64 * (2 * k + 1) as f64).cos() as f32;
assert!((n[0][0] - e(0, 0)).abs() < 1e-6);
assert!((n[33][7] - e(33, 7)).abs() < 1e-6);
assert!((n[63][31] - e(63, 31)).abs() < 1e-6);
}
fn matrixing_dense(s: &[f32; SUBBANDS]) -> [f32; 64] {
let n = matrix();
let mut v = [0f32; 64];
for (i, vi) in v.iter_mut().enumerate() {
let mut acc = 0f32;
for k in 0..SUBBANDS {
acc += n[i][k] * s[k];
}
*vi = acc;
}
v
}
#[test]
fn fast_matrixing_matches_dense() {
let mut st = 0x1234_9ABCu32;
let mut rng = || {
st = st.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(st >> 8) as f32 / (1u32 << 24) as f32 * 2.0 - 1.0
};
let mut worst = 0f32;
for _ in 0..2000 {
let mut s = [0f32; SUBBANDS];
for sv in s.iter_mut() {
*sv = rng() * 100.0;
}
let dense = matrixing_dense(&s);
let fast = matrixing_fast(&s);
for i in 0..64 {
let scale = s.iter().map(|x| x.abs()).sum::<f32>().max(1.0);
worst = worst.max((dense[i] - fast[i]).abs() / scale);
}
}
eprintln!("[B3] worst relative matrixing error fast vs dense: {worst:.2e}");
assert!(
worst < 1e-5,
"fast matrixing diverges from dense: {worst:.2e}"
);
}
#[test]
fn fifo_advances_and_output_is_finite() {
let time = [1f32; GRANULE_LINES];
let mut fifo = [0f32; 1024];
let pcm = polyphase(&time, &mut fifo);
assert!(pcm.iter().all(|v| v.is_finite()));
assert!(
fifo.iter().any(|&v| v != 0.0),
"matrixing must fill the FIFO"
);
}
}