use crate::mdct::kbd_window;
use core::f64::consts::PI;
pub fn apply_kbd_window(samples: &mut [f32], window: &[f32]) {
debug_assert_eq!(samples.len(), window.len());
for (s, w) in samples.iter_mut().zip(window.iter()) {
*s *= *w;
}
}
pub fn mdct_naive(x: &[f32]) -> Vec<f32> {
let two_n = x.len();
let n = two_n / 2;
let mut out = vec![0.0_f32; n];
let n_f = n as f64;
for (k, out_k) in out.iter_mut().enumerate() {
let kf = k as f64;
let mut acc = 0.0_f64;
for (nn, &xv) in x.iter().enumerate().take(two_n) {
let nf = nn as f64;
let theta = PI / n_f * (nf + 0.5 + n_f * 0.5) * (kf + 0.5);
acc += xv as f64 * theta.cos();
}
*out_k = (2.0 * acc) as f32;
}
out
}
#[derive(Debug, Clone)]
pub struct EncoderMdctState {
pub n: u32,
pub history: Vec<f32>,
}
impl EncoderMdctState {
pub fn new(n: u32) -> Self {
Self {
n,
history: vec![0.0_f32; n as usize],
}
}
pub fn analyse_frame(&mut self, frame: &[f32]) -> Vec<f32> {
let n = self.n as usize;
assert_eq!(frame.len(), n, "encoder: frame must be exactly N samples");
let mut buf = Vec::with_capacity(2 * n);
buf.extend_from_slice(&self.history);
buf.extend_from_slice(frame);
let window = kbd_window(self.n);
apply_kbd_window(&mut buf, &window);
let spec = mdct_naive(&buf);
self.history.clear();
self.history.extend_from_slice(frame);
spec
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mdct::{imdct, imdct_olap_symmetric, kbd_window};
#[test]
fn forward_mdct_zero_in_zero_out() {
let n = 8;
let x = vec![0.0_f32; 2 * n];
let spec = mdct_naive(&x);
assert_eq!(spec.len(), n);
for &s in &spec {
assert!(s.abs() < 1e-6, "got non-zero from zero input: {s}");
}
}
#[test]
fn forward_mdct_is_linear() {
let n = 8;
let mut x = vec![0.0_f32; 2 * n];
x[3] = 0.7;
x[10] = -0.3;
let s = mdct_naive(&x);
let scaled: Vec<f32> = x.iter().map(|v| v * 2.5).collect();
let s2 = mdct_naive(&scaled);
for (a, b) in s.iter().zip(s2.iter()) {
assert!(((*a) * 2.5 - *b).abs() < 1e-4, "linearity broken");
}
}
#[test]
fn princen_bradley_constant_signal_recovers_in_middle_frame() {
let n = 16u32;
let nsz = n as usize;
let frames: Vec<Vec<f32>> = (0..4).map(|_| vec![0.1_f32; nsz]).collect();
let mut enc = EncoderMdctState::new(n);
let specs: Vec<Vec<f32>> = frames.iter().map(|f| enc.analyse_frame(f)).collect();
let window = kbd_window(n);
let mut overlap = vec![0.0_f32; nsz];
let mut pcm_out: Vec<Vec<f32>> = Vec::new();
for spec in &specs {
let y = imdct(spec);
let pcm = imdct_olap_symmetric(&y, &window, &mut overlap);
pcm_out.push(pcm);
}
for &v in &pcm_out[2] {
assert!(
(v - 0.1).abs() < 0.01,
"Princen-Bradley failed: got {v}, expected 0.1"
);
}
}
#[test]
fn encoder_state_sinewave_round_trips() {
let n = 32u32;
let nsz = n as usize;
let freq_bin = 4.0_f32; let make_frame = |start: usize| -> Vec<f32> {
(0..nsz)
.map(|i| {
let t = (start + i) as f32;
(2.0 * std::f32::consts::PI * freq_bin * t / (2.0 * n as f32)).sin()
})
.collect()
};
let frames: Vec<Vec<f32>> = (0..4).map(|i| make_frame(i * nsz)).collect();
let mut enc = EncoderMdctState::new(n);
let specs: Vec<Vec<f32>> = frames.iter().map(|f| enc.analyse_frame(f)).collect();
let window = kbd_window(n);
let mut overlap = vec![0.0_f32; nsz];
let mut pcm_out: Vec<Vec<f32>> = Vec::new();
for spec in &specs {
let y = imdct(spec);
let pcm = imdct_olap_symmetric(&y, &window, &mut overlap);
pcm_out.push(pcm);
}
let orig = &frames[2];
let recon = &pcm_out[2];
let mut sig_e = 0.0_f64;
let mut err_e = 0.0_f64;
for (o, r) in orig.iter().zip(recon.iter()) {
sig_e += (*o as f64).powi(2);
err_e += (*o as f64 - *r as f64).powi(2);
}
let snr_db = 10.0 * (sig_e / err_e.max(1e-30)).log10();
assert!(snr_db > 40.0, "round-trip SNR too low: {snr_db:.1} dB");
}
}