extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use core::f32::consts::TAU;
use num_complex::Complex32;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use super::fft_15::fft_15;
pub const N: usize = 3840;
const N1: usize = 256;
const N2: usize = 15;
pub fn fft_3840_with(
buf: &mut [Complex32; N],
fft_256: &mut dyn FnMut(&mut [Complex32; N1]),
twiddles: &[Complex32; N],
) {
let mut m: Vec<Complex32> = vec![Complex32::new(0.0, 0.0); N];
for n1 in 0..N1 {
for n2 in 0..N2 {
m[n2 * N1 + n1] = buf[15 * n1 + n2];
}
}
for n2 in 0..N2 {
let row: &mut [Complex32; N1] = (&mut m[n2 * N1..(n2 + 1) * N1])
.try_into()
.expect("row slice = N1 elements");
fft_256(row);
}
for n2 in 0..N2 {
for k1 in 0..N1 {
m[n2 * N1 + k1] *= twiddles[n2 * N1 + k1];
}
}
let mut col = [Complex32::new(0.0, 0.0); N2];
for k1 in 0..N1 {
for k2 in 0..N2 {
col[k2] = m[k2 * N1 + k1];
}
fft_15(&mut col);
for k2 in 0..N2 {
m[k2 * N1 + k1] = col[k2];
}
}
for k2 in 0..N2 {
for k1 in 0..N1 {
buf[N1 * k2 + k1] = m[k2 * N1 + k1];
}
}
}
pub fn build_twiddles() -> alloc::boxed::Box<[Complex32; N]> {
let mut t = vec![Complex32::new(0.0, 0.0); N].into_boxed_slice();
for n2 in 0..N2 {
for k1 in 0..N1 {
let phi = -TAU * (n2 as f32) * (k1 as f32) / (N as f32);
t[n2 * N1 + k1] = Complex32::new(phi.cos(), phi.sin());
}
}
let raw = alloc::boxed::Box::into_raw(t) as *mut [Complex32; N];
unsafe { alloc::boxed::Box::from_raw(raw) }
}
#[cfg(test)]
#[cfg(feature = "fft-rustfft")]
mod tests {
use super::*;
fn rustfft_3840(input: &[Complex32; N]) -> [Complex32; N] {
use rustfft::FftPlanner;
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(N);
let mut buf: Vec<Complex32> = input.to_vec();
fft.process(&mut buf);
let mut out = [Complex32::new(0.0, 0.0); N];
out.copy_from_slice(&buf);
out
}
fn rustfft_256(buf: &mut [Complex32; N1]) {
use rustfft::FftPlanner;
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(N1);
let mut tmp: Vec<Complex32> = buf.to_vec();
fft.process(&mut tmp);
buf.copy_from_slice(&tmp);
}
fn close(a: Complex32, b: Complex32, eps: f32) -> bool {
(a.re - b.re).abs() < eps && (a.im - b.im).abs() < eps
}
fn random_input(seed: u64) -> [Complex32; N] {
let mut x = [Complex32::new(0.0, 0.0); N];
let mut s = seed;
for c in x.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let r = (s >> 33) as f32 / (1u32 << 31) as f32 - 1.0;
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let i = (s >> 33) as f32 / (1u32 << 31) as f32 - 1.0;
*c = Complex32::new(r, i);
}
x
}
#[test]
fn fft3840_impulse() {
let mut x = [Complex32::new(0.0, 0.0); N];
x[0] = Complex32::new(1.0, 0.0);
let expected = rustfft_3840(&x);
let tw = build_twiddles();
fft_3840_with(&mut x, &mut rustfft_256, &tw);
for k in 0..N {
assert!(
close(x[k], expected[k], 1e-4),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
#[test]
fn fft3840_pure_bin() {
let mut x = [Complex32::new(0.0, 0.0); N];
for n in 0..N {
let phi = -TAU * 137.0 * (n as f32) / (N as f32);
x[n] = Complex32::new(phi.cos(), phi.sin());
}
let expected = rustfft_3840(&x);
let tw = build_twiddles();
fft_3840_with(&mut x, &mut rustfft_256, &tw);
for k in 0..N {
assert!(
close(x[k], expected[k], 1e-2), "k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
#[test]
fn fft3840_random() {
let xs = random_input(0xfeed_face_dead_beef);
let expected = rustfft_3840(&xs);
let tw = build_twiddles();
let mut x = xs;
fft_3840_with(&mut x, &mut rustfft_256, &tw);
let mut max_err = 0.0f32;
for k in 0..N {
let dr = (x[k].re - expected[k].re).abs();
let di = (x[k].im - expected[k].im).abs();
max_err = max_err.max(dr.max(di));
}
assert!(
max_err < 1e-2,
"max error {max_err:.4e} exceeds 1e-2 over 3840-pt FFT"
);
}
}