use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering};
static FORCE_SCALAR: AtomicBool = AtomicBool::new(false);
#[allow(dead_code)] static FORCE_NO_AVX: AtomicBool = AtomicBool::new(false);
#[path = "imdct_simd.rs"]
mod kernels;
#[cfg(target_arch = "x86_64")]
#[path = "imdct_avx512.rs"]
mod avx512k;
#[derive(Clone, Copy)]
#[repr(C)]
struct C {
re: f32,
im: f32,
}
impl C {
fn new(re: f32, im: f32) -> Self {
Self { re, im }
}
#[inline(always)]
fn mul(self, o: Self) -> Self {
Self {
re: self.re * o.re - self.im * o.im,
im: self.re * o.im + self.im * o.re,
}
}
}
pub(crate) fn bitrev_table(n: usize) -> Vec<u16> {
let mut t = vec![0u16; n];
let mut j = 0usize;
for slot in t.iter_mut() {
*slot = j as u16;
let mut bit = n >> 1;
while bit != 0 && j >= bit {
j -= bit;
bit >>= 1;
}
j += bit;
}
t
}
pub(crate) fn twiddle_table(n: usize) -> (Vec<f32>, Vec<f32>) {
super::det_math::twiddle_table(n)
}
pub(crate) fn ifft_soa(
re: &mut [f32],
im: &mut [f32],
bitrev: &[u16],
tw_re: &[f32],
tw_im: &[f32],
) {
let wide = !FORCE_SCALAR.load(Ordering::Relaxed);
ifft_soa_ex(re, im, bitrev, tw_re, tw_im, wide);
}
#[cfg(test)]
pub(crate) struct FftModeGuard {
_lock: std::sync::MutexGuard<'static, ()>,
}
#[cfg(test)]
static FFT_MODE: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(test)]
fn take_fft_mode() -> FftModeGuard {
let lock = FFT_MODE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
FftModeGuard { _lock: lock }
}
#[cfg(test)]
impl Drop for FftModeGuard {
fn drop(&mut self) {
FORCE_SCALAR.store(false, Ordering::SeqCst);
FORCE_NO_AVX.store(false, Ordering::SeqCst);
}
}
#[cfg(test)]
pub(crate) fn fft_scalar() -> FftModeGuard {
let g = take_fft_mode();
FORCE_NO_AVX.store(false, Ordering::SeqCst);
FORCE_SCALAR.store(true, Ordering::SeqCst);
g
}
#[cfg(test)]
#[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
pub(crate) fn fft_sse2_only() -> FftModeGuard {
let g = take_fft_mode();
FORCE_SCALAR.store(false, Ordering::SeqCst);
FORCE_NO_AVX.store(true, Ordering::SeqCst);
g
}
#[cfg(test)]
pub(crate) fn ifft_soa_scalar(
re: &mut [f32],
im: &mut [f32],
bitrev: &[u16],
tw_re: &[f32],
tw_im: &[f32],
) {
ifft_soa_ex(re, im, bitrev, tw_re, tw_im, false);
}
fn ifft_soa_ex(
re: &mut [f32],
im: &mut [f32],
bitrev: &[u16],
tw_re: &[f32],
tw_im: &[f32],
wide: bool,
) {
let n = re.len();
kernels::bitrev_swap(re, im, bitrev);
#[cfg(target_arch = "x86_64")]
if wide
&& std::is_x86_feature_detected!("avx512f")
&& !FORCE_NO_AVX.load(Ordering::Relaxed)
&& im.len() == n
&& n.is_power_of_two()
&& tw_re.len() >= n.saturating_sub(1)
&& tw_im.len() >= n.saturating_sub(1)
{
unsafe { avx512k::ifft_stages_avx512(re, im, tw_re, tw_im) };
return;
}
#[cfg(target_arch = "x86_64")]
if wide
&& kernels::avx_ok()
&& !FORCE_NO_AVX.load(Ordering::Relaxed)
&& im.len() == n
&& n.is_power_of_two()
&& tw_re.len() >= n.saturating_sub(1)
&& tw_im.len() >= n.saturating_sub(1)
{
unsafe { kernels::ifft_stages_avx(re, im, tw_re, tw_im) };
return;
}
#[cfg(target_arch = "x86_64")]
let wide = wide && kernels::sse2_ok();
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
let wide = {
let _ = wide;
false
};
let mut len = 2usize;
let mut off = 0usize;
while len <= n {
let half = len / 2;
for i in (0..n).step_by(len) {
let mut k = 0usize;
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
if wide {
while k + 4 <= half {
unsafe {
kernels::butterfly4(re, im, i, k, half, off, tw_re, tw_im);
}
k += 4;
}
}
while k < half {
let wr = tw_re[off + k];
let wi = tw_im[off + k];
let j = i + k + half;
let tr = wr * re[j] - wi * im[j];
let ti = wr * im[j] + wi * re[j];
let ur = re[i + k];
let ui = im[i + k];
re[j] = ur - tr;
im[j] = ui - ti;
re[i + k] = ur + tr;
im[i + k] = ui + ti;
k += 1;
}
}
off += half;
len *= 2;
}
}
#[cfg(test)]
#[must_use]
pub fn imdct_naive(spec: &[f64], n_transform: usize) -> Vec<f64> {
let mut out = vec![0.0f64; n_transform];
imdct_naive_into(spec, &mut out);
out
}
fn imdct_naive_into(spec: &[f64], out: &mut [f64]) {
let n = out.len();
if n == 0 {
return;
}
let n0 = (n / 2 + 1) as f64 / 2.0;
let scale = 2.0 / n as f64;
let step = 2.0 * std::f64::consts::PI / n as f64;
for (n_i, slot) in out.iter_mut().enumerate() {
let np = n_i as f64 + n0;
let mut acc = 0.0f64;
for (k, &c) in spec.iter().enumerate() {
acc += c * (step * np * (k as f64 + 0.5)).cos();
}
*slot = scale * acc;
}
}
fn naive_f32(spec: &[f32], out: &mut [f32]) {
let spec64: Vec<f64> = spec.iter().copied().map(f64::from).collect();
let mut tmp = vec![0.0f64; out.len()];
imdct_naive_into(&spec64, &mut tmp);
for (d, s) in out.iter_mut().zip(tmp) {
*d = s as f32;
}
}
pub fn imdct_into_f32(spec: &[f32], out: &mut [f32]) {
let n = out.len();
if spec.len() != n / 2 {
naive_f32(spec, out);
return;
}
match n {
256 => PLAN_256.apply_into(spec, out),
1024 => PLAN_1024.apply_into(spec, out),
2048 => PLAN_2048.apply_into(spec, out),
_ => naive_f32(spec, out),
}
}
#[cfg(test)]
pub fn imdct_into(spec: &[f64], out: &mut [f64]) {
use std::cell::RefCell;
thread_local! {
static SPEC: RefCell<Vec<f32>> = const { RefCell::new(Vec::new()) };
static TMP: RefCell<Vec<f32>> = const { RefCell::new(Vec::new()) };
}
SPEC.with(|sc| {
TMP.with(|tc| {
let mut s = sc.borrow_mut();
let mut t = tc.borrow_mut();
s.clear();
s.extend(spec.iter().map(|&x| x as f32));
t.resize(out.len(), 0.0);
imdct_into_f32(&s, &mut t);
for (d, &v) in out.iter_mut().zip(t.iter()) {
*d = f64::from(v);
}
});
});
}
#[cfg(test)]
#[must_use]
pub fn imdct(spec: &[f64], n_transform: usize) -> Vec<f64> {
let mut out = vec![0.0f64; n_transform];
imdct_into(spec, &mut out);
out
}
struct Plan {
n: usize,
pre: Vec<C>,
post: Vec<C>,
bitrev: Vec<u16>,
tw_re: Vec<f32>,
tw_im: Vec<f32>,
}
static PLAN_256: LazyLock<Plan> = LazyLock::new(|| Plan::new(256));
static PLAN_1024: LazyLock<Plan> = LazyLock::new(|| Plan::new(1024));
static PLAN_2048: LazyLock<Plan> = LazyLock::new(|| Plan::new(2048));
impl Plan {
fn new(n: usize) -> Self {
let n4 = n / 4;
let two_pi_n = 2.0 * std::f32::consts::PI / n as f32;
let mut pre = Vec::with_capacity(n4);
let mut post = Vec::with_capacity(n4);
for k in 0..n4 {
let a = two_pi_n * (k as f32 + 0.125);
let (s, c) = super::det_math::sincos(a);
let cpx = C::new(c, s);
pre.push(cpx);
post.push(cpx);
}
let (tw_re, tw_im) = twiddle_table(n4);
Self {
n,
pre,
post,
bitrev: bitrev_table(n4),
tw_re,
tw_im,
}
}
fn apply_into(&self, spec: &[f32], out: &mut [f32]) {
match self.n {
2048 => {
let mut re = [0.0f32; 512];
let mut im = [0.0f32; 512];
kernels::apply_soa(self, spec, out, &mut re, &mut im);
}
1024 => {
let mut re = [0.0f32; 256];
let mut im = [0.0f32; 256];
kernels::apply_soa(self, spec, out, &mut re, &mut im);
}
256 => {
let mut re = [0.0f32; 64];
let mut im = [0.0f32; 64];
kernels::apply_soa(self, spec, out, &mut re, &mut im);
}
_ => naive_f32(spec, out),
}
}
}
#[cfg(test)]
type PlanTables = (Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>);
#[cfg(test)]
pub(crate) fn probe_plan_2048() -> PlanTables {
let p = &*PLAN_2048;
let split = |cs: &[C]| {
let mut re = Vec::with_capacity(cs.len());
let mut im = Vec::with_capacity(cs.len());
for c in cs {
re.push(c.re);
im.push(c.im);
}
(re, im)
};
let (pre_re, pre_im) = split(&p.pre);
let (post_re, post_im) = split(&p.post);
(
pre_re,
pre_im,
p.tw_re.clone(),
p.tw_im.clone(),
post_re,
post_im,
)
}
#[cfg(test)]
#[path = "imdct_tests.rs"]
mod imdct_tests;