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
})
}
fn matrix_t() -> &'static [[f32; SUBBANDS]; 64] {
static T: OnceLock<[[f32; SUBBANDS]; 64]> = OnceLock::new();
T.get_or_init(|| {
let m = matrix();
let mut t = [[0f32; SUBBANDS]; 64];
for (k, row) in m.iter().enumerate() {
for (i, &v) in row.iter().enumerate() {
t[i][k] = v;
}
}
t
})
}
#[inline]
fn fold_scalar(c: &[f32; 512], fifo: &[f32; 512], head: usize) -> [f32; 64] {
let mut y = [0f32; 64];
for (h, yh) in y.chunks_exact_mut(32).enumerate() {
for j in 0..8 {
let at = (head + 32 * h + 64 * j) & 511;
let (cs, xs) = (&c[32 * h + 64 * j..][..32], &fifo[at..at + 32]);
for i in 0..32 {
yh[i] += cs[i] * xs[i];
}
}
}
y
}
#[inline]
fn matrix_scalar(m: &[[f32; 64]; SUBBANDS], y: &[f32; 64]) -> [f32; SUBBANDS] {
let mut s = [0f32; SUBBANDS];
for k in 0..SUBBANDS {
let mut acc = 0f32;
for i in 0..64 {
acc += m[k][i] * y[i];
}
s[k] = acc;
}
s
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx")]
unsafe fn fold_avx(c: &[f32; 512], fifo: &[f32; 512], head: usize) -> [f32; 64] {
use std::arch::x86_64::*;
let head = head & !31;
#[allow(unused_unsafe)]
let mut acc = [unsafe { _mm256_setzero_ps() }; 8];
for j in 0..8 {
for h in 0..2 {
let at = (head + 32 * h + 64 * j) & 511;
for q in 0..4 {
unsafe {
let cv = _mm256_loadu_ps(c.as_ptr().add(32 * h + 64 * j + 8 * q));
let xv = _mm256_loadu_ps(fifo.as_ptr().add(at + 8 * q));
let a = &mut acc[4 * h + q];
*a = _mm256_add_ps(*a, _mm256_mul_ps(cv, xv));
}
}
}
}
let mut y = [0f32; 64];
for (v, a) in acc.iter().enumerate() {
unsafe { _mm256_storeu_ps(y.as_mut_ptr().add(8 * v), *a) };
}
y
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx")]
unsafe fn matrix_avx(mt: &[[f32; SUBBANDS]; 64], y: &[f32; 64]) -> [f32; SUBBANDS] {
use std::arch::x86_64::*;
#[allow(unused_unsafe)]
let mut acc = [unsafe { _mm256_setzero_ps() }; 4];
for (i, row) in mt.iter().enumerate() {
unsafe {
let x = _mm256_set1_ps(y[i]);
for (v, a) in acc.iter_mut().enumerate() {
*a = _mm256_add_ps(
*a,
_mm256_mul_ps(_mm256_loadu_ps(row.as_ptr().add(8 * v)), x),
);
}
}
}
let mut s = [0f32; SUBBANDS];
for (v, a) in acc.iter().enumerate() {
unsafe { _mm256_storeu_ps(s.as_mut_ptr().add(8 * v), *a) };
}
s
}
#[cfg(target_arch = "aarch64")]
unsafe fn fold_neon(c: &[f32; 512], fifo: &[f32; 512], head: usize) -> [f32; 64] {
use std::arch::aarch64::*;
let head = head & !31;
#[allow(unused_unsafe)]
let mut acc = [unsafe { vdupq_n_f32(0.0) }; 16];
for j in 0..8 {
for h in 0..2 {
let at = (head + 32 * h + 64 * j) & 511;
for q in 0..8 {
unsafe {
let cv = vld1q_f32(c.as_ptr().add(32 * h + 64 * j + 4 * q));
let xv = vld1q_f32(fifo.as_ptr().add(at + 4 * q));
let a = &mut acc[8 * h + q];
*a = vaddq_f32(*a, vmulq_f32(cv, xv));
}
}
}
}
let mut y = [0f32; 64];
for (v, a) in acc.iter().enumerate() {
unsafe { vst1q_f32(y.as_mut_ptr().add(4 * v), *a) };
}
y
}
#[cfg(target_arch = "aarch64")]
unsafe fn matrix_neon(mt: &[[f32; SUBBANDS]; 64], y: &[f32; 64]) -> [f32; SUBBANDS] {
use std::arch::aarch64::*;
#[allow(unused_unsafe)]
let mut acc = [unsafe { vdupq_n_f32(0.0) }; 8];
for (i, row) in mt.iter().enumerate() {
unsafe {
let x = vdupq_n_f32(y[i]);
for (v, a) in acc.iter_mut().enumerate() {
*a = vaddq_f32(*a, vmulq_f32(vld1q_f32(row.as_ptr().add(4 * v)), x));
}
}
}
let mut s = [0f32; SUBBANDS];
for (v, a) in acc.iter().enumerate() {
unsafe { vst1q_f32(s.as_mut_ptr().add(4 * v), *a) };
}
s
}
#[inline]
unsafe fn fold_simd(c: &[f32; 512], fifo: &[f32; 512], head: usize) -> [f32; 64] {
debug_assert_eq!(head % 32, 0);
#[cfg(target_arch = "x86_64")]
unsafe {
fold_avx(c, fifo, head)
}
#[cfg(target_arch = "aarch64")]
unsafe {
fold_neon(c, fifo, head)
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
fold_scalar(c, fifo, head)
}
#[inline]
unsafe fn matrix_simd(mt: &[[f32; SUBBANDS]; 64], y: &[f32; 64]) -> [f32; SUBBANDS] {
#[cfg(target_arch = "x86_64")]
unsafe {
matrix_avx(mt, y)
}
#[cfg(target_arch = "aarch64")]
unsafe {
matrix_neon(mt, y)
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
matrix_scalar(matrix(), y)
}
pub fn analyze(pcm: &[f32], fifo: &mut [f32; 512]) -> [[f32; SUBBAND_LINES]; SUBBANDS] {
let c = window();
let m = matrix();
let mt = matrix_t();
let mut out = [[0f32; SUBBAND_LINES]; SUBBANDS];
let simd = crate::decode::isa::use_simd();
super::prof::FB_PASSES[usize::from(simd)]
.fetch_add(SUBBAND_LINES as u64, std::sync::atomic::Ordering::Relaxed);
let Some(pcm) = pcm.first_chunk::<{ SUBBAND_LINES * 32 }>() else {
return out;
};
let mut head = 0usize;
for v in 0..SUBBAND_LINES {
head = (head + 512 - 32) & 511;
let new: &mut [f32; 32] = (&mut fifo[head..head + 32])
.try_into()
.expect("32-aligned run");
for t in 0..32 {
new[31 - t] = pcm[v * 32 + t];
}
let s = if simd {
unsafe { matrix_simd(mt, &fold_simd(c, fifo, head)) }
} else {
matrix_scalar(m, &fold_scalar(c, fifo, head))
};
for k in 0..SUBBANDS {
out[k][v] = s[k];
}
}
fifo.rotate_left(head);
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 = f64::from(input[i - delay]);
let o = f64::from(output[i]);
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"
);
}
fn rng(seed: u32) -> impl FnMut() -> f32 {
let mut st = seed;
move || {
st = st.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(st >> 8) as f32 / (1u32 << 24) as f32 - 0.5
}
}
#[test]
fn fold_simd_matches_scalar() {
if !crate::decode::isa::simd_available() {
eprintln!("no SIMD twin on this host - scalar path only, gate skipped");
return;
}
let mut r = rng(0x2545_F491);
let mut fifo = [0f32; 512];
for x in &mut fifo {
*x = r();
}
let c = window();
for head in (0..512).step_by(32) {
let a = fold_scalar(c, &fifo, head);
let b = unsafe { fold_simd(c, &fifo, head) };
assert_eq!(a, b, "fold SIMD/scalar mismatch at head={head}");
}
}
#[test]
fn matrix_simd_matches_scalar() {
if !crate::decode::isa::simd_available() {
eprintln!("no SIMD twin on this host - scalar path only, gate skipped");
return;
}
let mut r = rng(0x9E37_79B9);
for trial in 0..128 {
let mut y = [0f32; 64];
for x in &mut y {
*x = r();
}
let a = matrix_scalar(matrix(), &y);
let b = unsafe { matrix_simd(matrix_t(), &y) };
assert_eq!(a, b, "matrix SIMD/scalar mismatch on trial {trial}");
}
}
#[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})"
);
}
}