use std::f64::consts::PI;
use std::sync::OnceLock;
use crate::frame::{BlockType, GranuleSideInfo, GRANULE_LINES, SUBBANDS, SUBBAND_LINES};
struct Kernels {
cos36: [[f32; 18]; 36],
cos12: [[f32; 6]; 12],
win: [[f32; 36]; 4],
win_short: [f32; 12],
}
fn kernels() -> &'static Kernels {
static T: OnceLock<Kernels> = OnceLock::new();
T.get_or_init(|| {
let mut cos36 = [[0f32; 18]; 36];
for (n, row) in cos36.iter_mut().enumerate() {
for (k, c) in row.iter_mut().enumerate() {
*c = (PI / 72.0 * (2 * n + 1 + 18) as f64 * (2 * k + 1) as f64).cos() as f32;
}
}
let mut cos12 = [[0f32; 6]; 12];
for (n, row) in cos12.iter_mut().enumerate() {
for (k, c) in row.iter_mut().enumerate() {
*c = (PI / 24.0 * (2 * n + 1 + 6) as f64 * (2 * k + 1) as f64).cos() as f32;
}
}
let sin = |x: f64| x.sin() as f32;
let mut win = [[0f32; 36]; 4];
for n in 0..36 {
win[0][n] = sin(PI / 36.0 * (n as f64 + 0.5)); }
for n in 0..18 {
win[1][n] = sin(PI / 36.0 * (n as f64 + 0.5)); }
for n in 18..24 {
win[1][n] = 1.0;
}
for n in 24..30 {
win[1][n] = sin(PI / 12.0 * ((n - 18) as f64 + 0.5));
}
for n in 6..12 {
win[3][n] = sin(PI / 12.0 * ((n - 6) as f64 + 0.5)); }
for n in 12..18 {
win[3][n] = 1.0;
}
for n in 18..36 {
win[3][n] = sin(PI / 36.0 * (n as f64 + 0.5));
}
let mut win_short = [0f32; 12];
for (n, w) in win_short.iter_mut().enumerate() {
*w = sin(PI / 12.0 * (n as f64 + 0.5));
}
Kernels {
cos36,
cos12,
win,
win_short,
}
})
}
const IMDCT_N: [usize; 18] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 18, 19, 20, 21, 22, 23, 24, 25, 26,
];
fn cos36_t() -> &'static [[f32; 24]; 18] {
static T: OnceLock<[[f32; 24]; 18]> = OnceLock::new();
T.get_or_init(|| {
let c = &kernels().cos36;
let mut t = [[0f32; 24]; 18];
for (j, &n) in IMDCT_N.iter().enumerate() {
for k in 0..18 {
t[k][j] = c[n][k];
}
}
t
})
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx")]
unsafe fn imdct36_avx(lines: &[f32], out: &mut [f32; 24]) {
use std::arch::x86_64::*;
let Some(lines) = lines.first_chunk::<18>() else {
return;
};
let ct = cos36_t();
#[allow(unused_unsafe)]
let mut acc = [unsafe { _mm256_setzero_ps() }; 3];
for (k, row) in ct.iter().enumerate() {
unsafe {
let x = _mm256_set1_ps(lines[k]);
for (v, accv) in acc.iter_mut().enumerate() {
let c = _mm256_loadu_ps(row.as_ptr().add(v * 8));
*accv = _mm256_add_ps(*accv, _mm256_mul_ps(c, x));
}
}
}
for (v, accv) in acc.iter().enumerate() {
unsafe { _mm256_storeu_ps(out.as_mut_ptr().add(v * 8), *accv) };
}
}
#[cfg(target_arch = "aarch64")]
unsafe fn imdct36_neon(lines: &[f32], out: &mut [f32; 24]) {
use std::arch::aarch64::*;
let Some(lines) = lines.first_chunk::<18>() else {
return;
};
let ct = cos36_t();
#[allow(unused_unsafe)]
let mut acc = [unsafe { vdupq_n_f32(0.0) }; 6];
for (k, row) in ct.iter().enumerate() {
unsafe {
let x = vdupq_n_f32(lines[k]);
for (v, accv) in acc.iter_mut().enumerate() {
let c = vld1q_f32(row.as_ptr().add(v * 4));
*accv = vaddq_f32(*accv, vmulq_f32(c, x));
}
}
}
for (v, accv) in acc.iter().enumerate() {
unsafe { vst1q_f32(out.as_mut_ptr().add(v * 4), *accv) };
}
}
#[inline]
unsafe fn imdct36_simd(lines: &[f32], out: &mut [f32; 24]) {
#[cfg(target_arch = "x86_64")]
unsafe {
imdct36_avx(lines, out);
}
#[cfg(target_arch = "aarch64")]
unsafe {
imdct36_neon(lines, out)
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
imdct36_scalar(lines, out)
}
#[inline]
fn imdct36_scalar(lines: &[f32], out: &mut [f32; 24]) {
let Some(lines) = lines.first_chunk::<18>() else {
return;
};
let cos36 = &kernels().cos36;
for (j, &n) in IMDCT_N.iter().enumerate() {
let row = &cos36[n];
let mut acc = 0f32;
for k in 0..18 {
acc += lines[k] * row[k];
}
out[j] = acc;
}
}
const IMDCT_N_AVX: [usize; 18] = [
0, 1, 2, 3, 4, 5, 6, 7, 18, 19, 20, 21, 22, 23, 24, 25, 8, 26,
];
fn cos36_t_avx() -> &'static [[f32; 24]; 18] {
static T: OnceLock<[[f32; 24]; 18]> = OnceLock::new();
T.get_or_init(|| {
let c = &kernels().cos36;
let mut t = [[0f32; 24]; 18];
for (j, &n) in IMDCT_N_AVX.iter().enumerate() {
for k in 0..18 {
t[k][j] = c[n][k];
}
}
t
})
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx")]
unsafe fn long_subband_avx(
lines: &[f32; 18],
win: &[f32; 36],
overlap: &mut [f32; 18],
out: &mut [f32; 18],
odd: bool,
) {
use std::arch::x86_64::*;
let ct = cos36_t_avx();
unsafe {
let mut acc = [_mm256_setzero_ps(); 3];
for (k, row) in ct.iter().enumerate() {
let x = _mm256_set1_ps(lines[k]);
for (v, accv) in acc.iter_mut().enumerate() {
let c = _mm256_loadu_ps(row.as_ptr().add(v * 8));
*accv = _mm256_add_ps(*accv, _mm256_mul_ps(c, x));
}
}
let [a, b, c] = acc;
let sign = _mm256_set1_ps(-0.0);
let rev = |x: __m256| {
let r = _mm256_permute_ps(x, 0x1B);
_mm256_permute2f128_ps(r, r, 0x01)
};
let w = win.as_ptr();
let mut cc = [0f32; 8];
_mm256_storeu_ps(cc.as_mut_ptr(), c);
let (acc8, acc26) = (cc[0], cc[1]);
let s_lo = _mm256_mul_ps(a, _mm256_loadu_ps(w));
let s_hi = _mm256_mul_ps(_mm256_xor_ps(rev(a), sign), _mm256_loadu_ps(w.add(10)));
let s8 = acc8 * win[8];
let s9 = -acc8 * win[9];
let t_lo = _mm256_mul_ps(b, _mm256_loadu_ps(w.add(18)));
let t_hi = _mm256_mul_ps(rev(b), _mm256_loadu_ps(w.add(28)));
let t8 = acc26 * win[26];
let t9 = acc26 * win[27];
let ov = overlap.as_mut_ptr();
let o = out.as_mut_ptr();
let mut o_lo = _mm256_add_ps(s_lo, _mm256_loadu_ps(ov));
let mut o_hi = _mm256_add_ps(s_hi, _mm256_loadu_ps(ov.add(10)));
let o8 = s8 + overlap[8];
let mut o9 = s9 + overlap[9];
if odd {
let odd_sign = _mm256_setr_ps(0.0, -0.0, 0.0, -0.0, 0.0, -0.0, 0.0, -0.0);
o_lo = _mm256_xor_ps(o_lo, odd_sign);
o_hi = _mm256_xor_ps(o_hi, odd_sign);
o9 = -o9;
}
_mm256_storeu_ps(o, o_lo);
_mm256_storeu_ps(o.add(10), o_hi);
out[8] = o8;
out[9] = o9;
_mm256_storeu_ps(ov, t_lo);
_mm256_storeu_ps(ov.add(10), t_hi);
overlap[8] = t8;
overlap[9] = t9;
}
}
#[cfg(test)]
fn have_simd() -> bool {
super::isa::simd_available()
}
pub fn hybrid(
gi: &GranuleSideInfo,
lines: &[f32; GRANULE_LINES],
overlap: &mut [f32; GRANULE_LINES],
) -> [f32; GRANULE_LINES] {
let simd = super::isa::use_simd();
let is_short = gi.window_switching && gi.block_type == BlockType::Short;
let long_subbands = match (is_short, gi.mixed_block) {
(false, _) => SUBBANDS as u64,
(true, true) => 2,
(true, false) => 0,
};
super::prof::kernel_tally(
if simd {
&super::prof::K_IMDCT_SIMD
} else {
&super::prof::K_IMDCT_SCALAR
},
long_subbands,
);
#[cfg(target_arch = "x86_64")]
if simd {
return unsafe { hybrid_avx(gi, lines, overlap) };
}
hybrid_impl(gi, lines, overlap, simd)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx")]
unsafe fn hybrid_avx(
gi: &GranuleSideInfo,
lines: &[f32; GRANULE_LINES],
overlap: &mut [f32; GRANULE_LINES],
) -> [f32; GRANULE_LINES] {
hybrid_impl(gi, lines, overlap, true)
}
#[allow(clippy::inline_always)]
#[inline(always)]
fn hybrid_impl(
gi: &GranuleSideInfo,
lines: &[f32; GRANULE_LINES],
overlap: &mut [f32; GRANULE_LINES],
simd: bool,
) -> [f32; GRANULE_LINES] {
let t = kernels();
let mut out = [0f32; GRANULE_LINES];
let is_short = gi.window_switching && gi.block_type == BlockType::Short;
for sb in 0..SUBBANDS {
let base = sb * SUBBAND_LINES;
let mut samp = [0f32; 36];
let short_here = is_short && !(gi.mixed_block && sb < 2);
if short_here {
for w in 0..3 {
let mut y = [0f32; 12];
for n in 0..3 {
let mut acc = 0f32;
for k in 0..6 {
acc += lines[base + w + 3 * k] * t.cos12[n][k];
}
y[n] = acc * t.win_short[n];
y[5 - n] = -acc * t.win_short[5 - n];
}
for n in 6..9 {
let mut acc = 0f32;
for k in 0..6 {
acc += lines[base + w + 3 * k] * t.cos12[n][k];
}
y[n] = acc * t.win_short[n];
y[17 - n] = acc * t.win_short[17 - n];
}
for n in 0..12 {
samp[6 + w * 6 + n] += y[n];
}
}
} else {
let wt = if gi.mixed_block && sb < 2 {
0
} else {
match gi.block_type {
BlockType::Start => 1,
BlockType::Stop => 3,
_ => 0,
}
};
#[cfg(target_arch = "x86_64")]
if simd {
let l: &[f32; 18] = lines[base..base + 18].try_into().expect("18 lines");
let ov: &mut [f32; 18] = (&mut overlap[base..base + 18])
.try_into()
.expect("18 lines");
let o: &mut [f32; 18] = (&mut out[base..base + 18]).try_into().expect("18 lines");
unsafe { long_subband_avx(l, &t.win[wt], ov, o, sb & 1 == 1) };
continue;
}
let mut dp = [0f32; 24];
if simd {
unsafe { imdct36_simd(&lines[base..base + 18], &mut dp) };
} else {
imdct36_scalar(&lines[base..base + 18], &mut dp);
}
for (j, &n) in IMDCT_N.iter().enumerate() {
let acc = dp[j];
if n < 9 {
samp[n] = acc * t.win[wt][n];
samp[17 - n] = -acc * t.win[wt][17 - n];
} else {
samp[n] = acc * t.win[wt][n];
samp[53 - n] = acc * t.win[wt][53 - n];
}
}
}
for n in 0..18 {
out[base + n] = samp[n] + overlap[base + n];
overlap[base + n] = samp[n + 18];
}
if sb & 1 == 1 {
let mut i = 1;
while i < 18 {
out[base + i] = -out[base + i];
i += 2;
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::needless_range_loop)]
fn hybrid_reference(
gi: &GranuleSideInfo,
lines: &[f32; GRANULE_LINES],
overlap: &mut [f32; GRANULE_LINES],
) -> [f32; GRANULE_LINES] {
let simd = false;
let t = kernels();
let mut out = [0f32; GRANULE_LINES];
let is_short = gi.window_switching && gi.block_type == BlockType::Short;
for sb in 0..SUBBANDS {
let base = sb * SUBBAND_LINES;
let mut samp = [0f32; 36];
let short_here = is_short && !(gi.mixed_block && sb < 2);
if short_here {
for w in 0..3 {
let mut y = [0f32; 12];
for n in 0..3 {
let mut acc = 0f32;
for k in 0..6 {
acc += lines[base + w + 3 * k] * t.cos12[n][k];
}
y[n] = acc * t.win_short[n];
y[5 - n] = -acc * t.win_short[5 - n];
}
for n in 6..9 {
let mut acc = 0f32;
for k in 0..6 {
acc += lines[base + w + 3 * k] * t.cos12[n][k];
}
y[n] = acc * t.win_short[n];
y[17 - n] = acc * t.win_short[17 - n];
}
for n in 0..12 {
samp[6 + w * 6 + n] += y[n];
}
}
} else {
let wt = if gi.mixed_block && sb < 2 {
0
} else {
match gi.block_type {
BlockType::Start => 1,
BlockType::Stop => 3,
_ => 0,
}
};
let mut dp = [0f32; 24];
if simd {
unsafe { imdct36_simd(&lines[base..base + 18], &mut dp) };
} else {
imdct36_scalar(&lines[base..base + 18], &mut dp);
}
for (j, &n) in IMDCT_N.iter().enumerate() {
let acc = dp[j];
if n < 9 {
samp[n] = acc * t.win[wt][n];
samp[17 - n] = -acc * t.win[wt][17 - n];
} else {
samp[n] = acc * t.win[wt][n];
samp[53 - n] = acc * t.win[wt][53 - n];
}
}
}
for n in 0..18 {
out[base + n] = samp[n] + overlap[base + n];
overlap[base + n] = samp[n + 18];
}
if sb & 1 == 1 {
let mut i = 1;
while i < 18 {
out[base + i] = -out[base + i];
i += 2;
}
}
}
out
}
#[test]
fn hybrid_matches_reference_bitwise() {
let mut st = 0x7F4A_7C15u32;
let mut next = move || {
st ^= st << 13;
st ^= st >> 17;
st ^= st << 5;
st
};
let cases = [
(BlockType::Long, false),
(BlockType::Start, false),
(BlockType::Stop, false),
(BlockType::Short, false),
(BlockType::Short, true),
(BlockType::Start, true),
(BlockType::Stop, true),
];
for simd in [true, false] {
if simd && !have_simd() {
continue;
}
for &(bt, mixed) in &cases {
let gi = GranuleSideInfo {
window_switching: bt != BlockType::Long,
block_type: bt,
mixed_block: mixed,
..Default::default()
};
let (mut ov_a, mut ov_b) = ([0f32; GRANULE_LINES], [0f32; GRANULE_LINES]);
for g in 0..48 {
let top = (next() % 33) as usize;
let mut lines = [0f32; GRANULE_LINES];
for (i, l) in lines.iter_mut().enumerate() {
let r = next();
let sb = i / SUBBAND_LINES;
*l = if sb >= top || (sb * 7 + g) % 5 == 0 {
if r & 1 == 0 {
0.0
} else {
-0.0
}
} else if r % 9 == 0 {
-0.0
} else {
(r >> 8) as f32 / (1u32 << 24) as f32 - 0.5
};
}
let a = hybrid_impl(&gi, &lines, &mut ov_a, simd);
let b = hybrid_reference(&gi, &lines, &mut ov_b);
for i in 0..GRANULE_LINES {
assert_eq!(
a[i].to_bits(),
b[i].to_bits(),
"out[{i}] simd={simd} {bt:?} mixed={mixed} granule {g}"
);
assert_eq!(
ov_a[i].to_bits(),
ov_b[i].to_bits(),
"overlap[{i}] simd={simd} {bt:?} mixed={mixed} granule {g}"
);
}
}
}
}
}
fn hybrid_dense(
gi: &GranuleSideInfo,
lines: &[f32; GRANULE_LINES],
overlap: &mut [f32; GRANULE_LINES],
) -> [f32; GRANULE_LINES] {
let t = kernels();
let mut out = [0f32; GRANULE_LINES];
let is_short = gi.window_switching && gi.block_type == BlockType::Short;
for sb in 0..SUBBANDS {
let base = sb * SUBBAND_LINES;
let mut samp = [0f32; 36];
let short_here = is_short && !(gi.mixed_block && sb < 2);
if short_here {
for w in 0..3 {
let mut y = [0f32; 12];
for n in 0..12 {
let mut acc = 0f32;
for k in 0..6 {
acc += lines[base + w + 3 * k] * t.cos12[n][k];
}
y[n] = acc * t.win_short[n];
}
for n in 0..12 {
samp[6 + w * 6 + n] += y[n];
}
}
} else {
let wt = if gi.mixed_block && sb < 2 {
0
} else {
match gi.block_type {
BlockType::Start => 1,
BlockType::Stop => 3,
_ => 0,
}
};
for n in 0..36 {
let mut acc = 0f32;
for k in 0..18 {
acc += lines[base + k] * t.cos36[n][k];
}
samp[n] = acc * t.win[wt][n];
}
}
for n in 0..18 {
out[base + n] = samp[n] + overlap[base + n];
overlap[base + n] = samp[n + 18];
}
if sb & 1 == 1 {
let mut i = 1;
while i < 18 {
out[base + i] = -out[base + i];
i += 2;
}
}
}
out
}
#[test]
fn half_work_imdct_matches_dense_for_every_block_type() {
let mut s = 0x9E37_79B9u32;
let mut rng = || {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(s >> 8) as f32 / (1u32 << 24) as f32 - 0.5
};
for &(bt, mixed) in &[
(BlockType::Long, false),
(BlockType::Start, false),
(BlockType::Stop, false),
(BlockType::Short, false),
(BlockType::Short, true), (BlockType::Start, true),
(BlockType::Stop, true),
] {
let gi = GranuleSideInfo {
window_switching: bt != BlockType::Long,
block_type: bt,
mixed_block: mixed,
..Default::default()
};
let (mut ov_a, mut ov_b) = ([0f32; GRANULE_LINES], [0f32; GRANULE_LINES]);
for g in 0..4 {
let mut lines = [0f32; GRANULE_LINES];
for l in &mut lines {
*l = rng();
}
let fast = hybrid(&gi, &lines, &mut ov_a);
let dense = hybrid_dense(&gi, &lines, &mut ov_b);
assert_eq!(
fast, dense,
"block_type={bt:?} mixed={mixed} granule={g}: half-work IMDCT diverged from dense"
);
assert_eq!(
ov_a, ov_b,
"overlap diverged: block_type={bt:?} mixed={mixed}"
);
}
}
}
#[test]
fn imdct_simd_matches_scalar() {
if !have_simd() {
eprintln!("no SIMD twin on this host - scalar path only, gate skipped");
return;
}
let mut st = 0x1F35_3D9Bu32;
let mut rng = || {
st = st.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(st >> 8) as f32 / (1u32 << 24) as f32 - 0.5
};
for trial in 0..128 {
let lines: Vec<f32> = (0..18).map(|_| rng()).collect();
let (mut a, mut b) = ([0f32; 24], [0f32; 24]);
imdct36_scalar(&lines, &mut a);
unsafe { imdct36_simd(&lines, &mut b) };
assert_eq!(
a[..18],
b[..18],
"IMDCT SIMD/scalar mismatch on trial {trial}"
);
}
}
#[test]
fn imdct_kernel_symmetries_are_bit_exact() {
let t = kernels();
for n in 0..9 {
for k in 0..18 {
assert_eq!(
t.cos36[17 - n][k],
-t.cos36[n][k],
"cos36 antisym n={n} k={k}"
);
}
}
for n in 18..27 {
for k in 0..18 {
assert_eq!(t.cos36[53 - n][k], t.cos36[n][k], "cos36 sym n={n} k={k}");
}
}
for n in 0..3 {
for k in 0..6 {
assert_eq!(
t.cos12[5 - n][k],
-t.cos12[n][k],
"cos12 antisym n={n} k={k}"
);
}
}
for n in 6..9 {
for k in 0..6 {
assert_eq!(t.cos12[17 - n][k], t.cos12[n][k], "cos12 sym n={n} k={k}");
}
}
}
#[test]
fn long_imdct_impulse_matches_formula() {
let mut lines = [0f32; GRANULE_LINES];
lines[0] = 1.0;
let mut overlap = [0f32; GRANULE_LINES];
let out = hybrid(&GranuleSideInfo::default(), &lines, &mut overlap);
let expected = (PI / 72.0 * 19.0).cos() as f32 * (PI / 36.0 * 0.5).sin() as f32;
assert!(
(out[0] - expected).abs() < 1e-5,
"out[0]={} expected={}",
out[0],
expected
);
let expect_ov = (PI / 72.0 * 55.0).cos() as f32 * (PI / 36.0 * 18.5).sin() as f32;
assert!((overlap[0] - expect_ov).abs() < 1e-5);
}
#[test]
fn odd_subband_frequency_inversion() {
let mut lines = [0f32; GRANULE_LINES];
lines[SUBBAND_LINES] = 1.0; let mut overlap = [0f32; GRANULE_LINES];
let out = hybrid(&GranuleSideInfo::default(), &lines, &mut overlap);
let raw1 = (PI / 72.0 * (2.0 + 1.0 + 18.0)).cos() as f32 * (PI / 36.0 * 1.5).sin() as f32;
assert!(
(out[SUBBAND_LINES + 1] + raw1).abs() < 1e-5,
"odd sample must be negated"
);
}
}