use alloc::boxed::Box;
use num_complex::Complex32;
pub trait Fft {
fn process(&self, buf: &mut [Complex32]);
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub trait FftPlanner {
fn plan_forward(&mut self, len: usize) -> Box<dyn Fft>;
fn plan_inverse(&mut self, len: usize) -> Box<dyn Fft>;
}
#[cfg(feature = "fft-rustfft")]
mod rustfft_backend {
use super::*;
use alloc::sync::Arc;
pub struct RustFftPlanner {
inner: rustfft::FftPlanner<f32>,
}
impl RustFftPlanner {
pub fn new() -> Self {
Self {
inner: rustfft::FftPlanner::new(),
}
}
}
impl Default for RustFftPlanner {
fn default() -> Self {
Self::new()
}
}
struct RustFftAdapter {
inner: Arc<dyn rustfft::Fft<f32>>,
}
impl Fft for RustFftAdapter {
fn process(&self, buf: &mut [Complex32]) {
self.inner.process(buf);
}
fn len(&self) -> usize {
self.inner.len()
}
}
impl FftPlanner for RustFftPlanner {
fn plan_forward(&mut self, len: usize) -> Box<dyn Fft> {
Box::new(RustFftAdapter {
inner: self.inner.plan_fft_forward(len),
})
}
fn plan_inverse(&mut self, len: usize) -> Box<dyn Fft> {
Box::new(RustFftAdapter {
inner: self.inner.plan_fft_inverse(len),
})
}
}
}
#[cfg(feature = "fft-rustfft")]
pub use rustfft_backend::RustFftPlanner;
#[cfg(any(feature = "fft-rustfft", feature = "fft-extern"))]
#[inline]
pub fn default_planner() -> Box<dyn FftPlanner> {
#[cfg(feature = "fft-rustfft")]
{
Box::new(RustFftPlanner::new())
}
#[cfg(all(not(feature = "fft-rustfft"), feature = "fft-extern"))]
{
unsafe extern "Rust" {
fn mfsk_core_make_default_fft_planner() -> Box<dyn FftPlanner>;
}
unsafe { mfsk_core_make_default_fft_planner() }
}
}
#[cfg(all(test, feature = "fft-rustfft"))]
mod tests_rustfft {
use super::*;
use core::f32::consts::TAU;
#[test]
fn rustfft_roundtrip_64() {
let mut planner = RustFftPlanner::new();
let n = 64;
let mut buf: alloc::vec::Vec<Complex32> = (0..n)
.map(|k| Complex32::from_polar(1.0, TAU * 3.0 * k as f32 / n as f32))
.collect();
let original = buf.clone();
let fwd = planner.plan_forward(n);
let inv = planner.plan_inverse(n);
fwd.process(&mut buf);
inv.process(&mut buf);
let scale = 1.0 / n as f32;
for c in buf.iter_mut() {
*c *= scale;
}
for (a, b) in buf.iter().zip(original.iter()) {
assert!((a - b).norm() < 1e-4);
}
}
#[test]
fn rustfft_forward_picks_correct_bin() {
let mut planner = RustFftPlanner::new();
let n = 128;
let bin = 7;
let mut buf: alloc::vec::Vec<Complex32> = (0..n)
.map(|k| Complex32::from_polar(1.0, TAU * bin as f32 * k as f32 / n as f32))
.collect();
let fwd = planner.plan_forward(n);
fwd.process(&mut buf);
let peak = buf
.iter()
.enumerate()
.max_by(|a, b| a.1.norm().partial_cmp(&b.1.norm()).unwrap())
.unwrap()
.0;
assert_eq!(peak, bin);
}
}