use std::f64::consts::PI;
use std::sync::OnceLock;
use crate::decode::synth_window::SYNTH_D;
use crate::frame::{SUBBANDS, SUBBAND_LINES};
fn window() -> &'static [f32; 512] {
static C: OnceLock<[f32; 512]> = OnceLock::new();
C.get_or_init(|| {
let mut c = [0f32; 512];
for (i, ci) in c.iter_mut().enumerate() {
*ci = SYNTH_D[i] / 32.0;
}
c
})
}
fn matrix() -> &'static [[f32; 64]; SUBBANDS] {
static M: OnceLock<[[f32; 64]; SUBBANDS]> = OnceLock::new();
M.get_or_init(|| {
let mut m = [[0f32; 64]; SUBBANDS];
for (k, row) in m.iter_mut().enumerate() {
for (i, c) in row.iter_mut().enumerate() {
*c = (PI / 64.0 * (2 * k + 1) as f64 * (i as f64 - 16.0)).cos() as f32;
}
}
m
})
}
pub fn analyze(pcm: &[f32], fifo: &mut [f32; 512]) -> [[f32; SUBBAND_LINES]; SUBBANDS] {
let c = window();
let m = matrix();
let mut out = [[0f32; SUBBAND_LINES]; SUBBANDS];
for v in 0..SUBBAND_LINES {
fifo.copy_within(0..512 - 32, 32);
for t in 0..32 {
fifo[31 - t] = pcm[v * 32 + t];
}
let mut y = [0f32; 64];
for (i, yi) in y.iter_mut().enumerate() {
let mut acc = 0f32;
for j in 0..8 {
acc += c[i + 64 * j] * fifo[i + 64 * j];
}
*yi = acc;
}
for k in 0..SUBBANDS {
let mut acc = 0f32;
for i in 0..64 {
acc += m[k][i] * y[i];
}
out[k][v] = acc;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decode::synthesis;
use crate::frame::{GRANULE_LINES, SUBBANDS, SUBBAND_LINES};
use std::f32::consts::PI as PIf;
fn flatten(sb: &[[f32; SUBBAND_LINES]; SUBBANDS]) -> [f32; GRANULE_LINES] {
let mut t = [0f32; GRANULE_LINES];
for k in 0..SUBBANDS {
for v in 0..SUBBAND_LINES {
t[k * SUBBAND_LINES + v] = sb[k][v];
}
}
t
}
#[test]
fn analysis_synthesis_reconstructs_a_tone() {
let granules = 16;
let n = granules * GRANULE_LINES;
let input: Vec<f32> = (0..n)
.map(|i| 0.5 * (2.0 * PIf * 1000.0 * i as f32 / 44100.0).sin())
.collect();
let mut afifo = [0f32; 512];
let mut sfifo = [0f32; 1024];
let mut output = Vec::with_capacity(n);
for g in 0..granules {
let sb = analyze(&input[g * GRANULE_LINES..], &mut afifo);
let pcm = synthesis::polyphase(&flatten(&sb), &mut sfifo);
output.extend_from_slice(&pcm);
}
let (mut best_snr, mut best_delay) = (f64::NEG_INFINITY, 0usize);
for delay in 480..=482 {
let mut sig = 0f64;
let mut err = 0f64;
for i in delay..n {
let r = input[i - delay] as f64;
let o = output[i] as f64;
sig += r * r;
err += (r - o) * (r - o);
}
let snr = 10.0 * (sig / err).log10();
if snr > best_snr {
best_snr = snr;
best_delay = delay;
}
}
eprintln!("[L1] best reconstruction SNR {best_snr:.1} dB at delay {best_delay}");
assert!(
best_snr > 70.0,
"analysis/synthesis SNR too low: {best_snr:.1} dB"
);
}
#[test]
fn dc_maps_to_subband_zero() {
let input = [1.0f32; GRANULE_LINES * 4];
let mut afifo = [0f32; 512];
let mut sb = [[0f32; SUBBAND_LINES]; SUBBANDS];
for g in 0..4 {
sb = analyze(&input[g * GRANULE_LINES..], &mut afifo);
}
let sb0: f32 = sb[0].iter().map(|x| x.abs()).sum();
let high: f32 = (1..SUBBANDS)
.map(|k| sb[k].iter().map(|x| x.abs()).sum::<f32>())
.sum();
assert!(
sb0 > high,
"DC energy must concentrate in subband 0 (sb0={sb0}, high={high})"
);
}
}