use std::sync::OnceLock;
use crate::frame::{BlockType, GranuleSideInfo, GRANULE_LINES};
const CI: [f32; 8] = [
-0.6, -0.535, -0.33, -0.185, -0.095, -0.041, -0.0142, -0.0037,
];
fn weights() -> &'static ([f32; 8], [f32; 8]) {
static T: OnceLock<([f32; 8], [f32; 8])> = OnceLock::new();
T.get_or_init(|| {
let mut cs = [0f32; 8];
let mut ca = [0f32; 8];
for i in 0..8 {
let d = (1.0 + CI[i] * CI[i]).sqrt();
cs[i] = 1.0 / d;
ca[i] = CI[i] / d;
}
(cs, ca)
})
}
pub fn reduce(gi: &GranuleSideInfo, lines: &mut [f32; GRANULE_LINES]) {
let is_short = gi.window_switching && gi.block_type == BlockType::Short;
let boundaries = if is_short {
usize::from(gi.mixed_block)
} else {
31
};
let (cs, ca) = weights();
for sb in 1..=boundaries {
let base = sb * 18;
let w = &mut lines[base - 8..base + 8];
for i in 0..8 {
let (lower, upper) = (7 - i, 8 + i);
let a = w[lower];
let b = w[upper];
w[lower] = a * cs[i] - b * ca[i];
w[upper] = b * cs[i] + a * ca[i];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn butterfly_preserves_energy_long_block() {
let mut lines = [0f32; GRANULE_LINES];
for i in 10..26 {
lines[i] = i as f32 * 0.1; }
let before: f32 = lines.iter().map(|v| v * v).sum();
reduce(&GranuleSideInfo::default(), &mut lines); let after: f32 = lines.iter().map(|v| v * v).sum();
assert!(
(before - after).abs() < 1e-2,
"before {before} after {after}"
);
}
#[test]
fn short_block_is_skipped() {
let mut lines = [0f32; GRANULE_LINES];
lines[17] = 1.0;
lines[18] = 2.0;
let snapshot = lines;
let gi = GranuleSideInfo {
window_switching: true,
block_type: BlockType::Short,
mixed_block: false,
..Default::default()
};
reduce(&gi, &mut lines);
assert_eq!(
lines, snapshot,
"pure short blocks must not be alias-reduced"
);
}
}