use alloc::vec;
use alloc::vec::Vec;
use core::f32::consts::TAU;
use num_complex::Complex;
#[cfg(not(feature = "std"))]
use num_traits::Float;
pub fn gen_w_r2_sc16(n: usize) -> Vec<Complex<i16>> {
assert!(n.is_power_of_two(), "gen_w_r2_sc16: N must be power of 2");
let half = n / 2;
let mut w: Vec<Complex<i16>> = vec![Complex::new(0i16, 0); half];
let e = TAU / n as f32;
for i in 0..half {
let re = (i16::MAX as f32 * (i as f32 * e).cos()) as i16;
let im = (i16::MAX as f32 * (i as f32 * e).sin()) as i16;
w[i] = Complex::new(re, im);
}
bit_rev_sc16(&mut w);
w
}
pub fn bit_rev_sc16(data: &mut [Complex<i16>]) {
let n = data.len();
assert!(n.is_power_of_two(), "bit_rev_sc16: N must be power of 2");
let mut j = 0usize;
for i in 1..(n - 1) {
let mut k = n >> 1;
while k <= j {
j -= k;
k >>= 1;
}
j += k;
if i < j {
data.swap(i, j);
}
}
}
const MULT_SHIFT_CONST: i32 = 0x7fff;
const ADD_ROUND_MULT: i32 = 0x7fff;
#[inline]
fn xtfixed_bf_1(a0: i16, a1: i16, a2: i16, a3: i16, a4: i16) -> i16 {
let mut result: i32 = a0 as i32 * MULT_SHIFT_CONST;
result -= a1 as i32 * a2 as i32 + a3 as i32 * a4 as i32;
result += ADD_ROUND_MULT;
(result >> 16) as i16
}
#[inline]
fn xtfixed_bf_2(a0: i16, a1: i16, a2: i16, a3: i16, a4: i16) -> i16 {
let mut result: i32 = a0 as i32 * MULT_SHIFT_CONST;
result -= a1 as i32 * a2 as i32 - a3 as i32 * a4 as i32;
result += ADD_ROUND_MULT;
(result >> 16) as i16
}
#[inline]
fn xtfixed_bf_3(a0: i16, a1: i16, a2: i16, a3: i16, a4: i16) -> i16 {
let mut result: i32 = a0 as i32 * MULT_SHIFT_CONST;
result += a1 as i32 * a2 as i32 + a3 as i32 * a4 as i32;
result += ADD_ROUND_MULT;
(result >> 16) as i16
}
#[inline]
fn xtfixed_bf_4(a0: i16, a1: i16, a2: i16, a3: i16, a4: i16) -> i16 {
let mut result: i32 = a0 as i32 * MULT_SHIFT_CONST;
result += a1 as i32 * a2 as i32 - a3 as i32 * a4 as i32;
result += ADD_ROUND_MULT;
(result >> 16) as i16
}
pub fn fft2r_sc16(data: &mut [Complex<i16>], w: &[Complex<i16>]) {
let n = data.len();
assert!(n.is_power_of_two(), "fft2r_sc16: N must be power of 2");
assert_eq!(w.len(), n / 2, "fft2r_sc16: w table must be N/2 entries");
let mut ie: usize = 1;
let mut n2 = n / 2;
while n2 > 0 {
let mut ia: usize = 0;
for j in 0..ie {
let cs = w[j];
for _ in 0..n2 {
let m = ia + n2;
let m_data = data[m];
let a_data = data[ia];
let m1_re = xtfixed_bf_1(a_data.re, cs.re, m_data.re, cs.im, m_data.im);
let m1_im = xtfixed_bf_2(a_data.im, cs.re, m_data.im, cs.im, m_data.re);
data[m] = Complex::new(m1_re, m1_im);
let m2_re = xtfixed_bf_3(a_data.re, cs.re, m_data.re, cs.im, m_data.im);
let m2_im = xtfixed_bf_4(a_data.im, cs.re, m_data.im, cs.im, m_data.re);
data[ia] = Complex::new(m2_re, m2_im);
ia += 1;
}
ia += n2;
}
ie <<= 1;
n2 >>= 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sc16_impulse_256() {
let n = 256;
let w = gen_w_r2_sc16(n);
let mut buf: Vec<Complex<i16>> = vec![Complex::new(0i16, 0); n];
buf[0] = Complex::new(i16::MAX, 0);
fft2r_sc16(&mut buf, &w);
bit_rev_sc16(&mut buf);
for c in buf.iter() {
let mag2 = (c.re as i32) * (c.re as i32) + (c.im as i32) * (c.im as i32);
assert!(
(4000..50_000).contains(&mag2),
"impulse bin mag²={mag2} out of range"
);
}
}
#[test]
fn sc16_pure_tone_256() {
let n = 256;
let k_bin = 32;
let w = gen_w_r2_sc16(n);
let mut buf: Vec<Complex<i16>> = vec![Complex::new(0i16, 0); n];
for i in 0..n {
let phase = TAU * (k_bin as f32) * (i as f32) / (n as f32);
let re = (16000.0 * phase.cos()) as i16;
let im = (16000.0 * phase.sin()) as i16;
buf[i] = Complex::new(re, im);
}
fft2r_sc16(&mut buf, &w);
bit_rev_sc16(&mut buf);
let peak_mag2 = (buf[k_bin].re as i32).pow(2) + (buf[k_bin].im as i32).pow(2);
assert!(
peak_mag2 > 200_000_000,
"peak bin mag²={peak_mag2} too low (expected ~16k²=256M)"
);
for (i, c) in buf.iter().enumerate() {
if i == k_bin {
continue;
}
let mag2 = (c.re as i32).pow(2) + (c.im as i32).pow(2);
assert!(
mag2 < 1_000_000,
"off-bin {i} leaked mag²={mag2} (expected near 0)"
);
}
}
}