use alloc::boxed::Box;
use num_complex::{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>;
}
pub trait Fft16 {
fn process(&self, buf: &mut [Complex<i16>]);
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub trait FftPlanner16 {
fn plan_forward(&mut self, len: usize) -> Box<dyn Fft16>;
fn plan_inverse(&mut self, len: usize) -> Box<dyn Fft16>;
}
#[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(feature = "fft-rustfft")]
mod rustfft_backend_i16 {
use super::*;
use alloc::sync::Arc;
pub struct RustFftPlanner16 {
inner: rustfft::FftPlanner<f32>,
}
impl RustFftPlanner16 {
pub fn new() -> Self {
Self {
inner: rustfft::FftPlanner::new(),
}
}
}
impl Default for RustFftPlanner16 {
fn default() -> Self {
Self::new()
}
}
struct RustFft16Adapter {
inner: Arc<dyn rustfft::Fft<f32>>,
}
impl Fft16 for RustFft16Adapter {
fn process(&self, buf: &mut [Complex<i16>]) {
assert_eq!(buf.len(), self.inner.len());
let mut tmp: alloc::vec::Vec<Complex32> = buf
.iter()
.map(|c| Complex32::new(c.re as f32, c.im as f32))
.collect();
self.inner.process(&mut tmp);
let n = tmp.len() as f32;
let scale = 1.0 / n;
for (dst, src) in buf.iter_mut().zip(tmp.iter()) {
let re = (src.re * scale)
.round()
.clamp(i16::MIN as f32, i16::MAX as f32);
let im = (src.im * scale)
.round()
.clamp(i16::MIN as f32, i16::MAX as f32);
dst.re = re as i16;
dst.im = im as i16;
}
}
fn len(&self) -> usize {
self.inner.len()
}
}
struct MixedRadix3840Sc16Adapter {
plan: crate::core::dsp::fft_mixed_3840_sc16::Plan3840Sc16,
}
impl Fft16 for MixedRadix3840Sc16Adapter {
fn process(&self, buf: &mut [Complex<i16>]) {
self.plan.process(buf);
}
fn len(&self) -> usize {
3840
}
}
impl FftPlanner16 for RustFftPlanner16 {
fn plan_forward(&mut self, len: usize) -> Box<dyn Fft16> {
if len == 3840 {
return Box::new(MixedRadix3840Sc16Adapter {
plan: crate::core::dsp::fft_mixed_3840_sc16::Plan3840Sc16::new(),
});
}
Box::new(RustFft16Adapter {
inner: self.inner.plan_fft_forward(len),
})
}
fn plan_inverse(&mut self, len: usize) -> Box<dyn Fft16> {
assert_ne!(
len, 3840,
"inverse 3840-pt sc16 FFT not implemented \
(FT8 spectrogram path is forward only)"
);
Box::new(RustFft16Adapter {
inner: self.inner.plan_fft_inverse(len),
})
}
}
}
#[cfg(feature = "fft-rustfft")]
pub use rustfft_backend_i16::RustFftPlanner16;
#[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(
feature = "fixed-point",
any(feature = "fft-rustfft", feature = "fft-extern")
))]
#[inline]
pub fn default_planner_16() -> Box<dyn FftPlanner16> {
#[cfg(feature = "fft-rustfft")]
{
Box::new(RustFftPlanner16::new())
}
#[cfg(all(not(feature = "fft-rustfft"), feature = "fft-extern"))]
{
unsafe extern "Rust" {
fn mfsk_core_make_default_fft_planner_16() -> Box<dyn FftPlanner16>;
}
unsafe { mfsk_core_make_default_fft_planner_16() }
}
}
#[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);
}
}