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
}
}
#[repr(align(16))]
#[derive(Clone, Copy)]
struct Align16Quad(
#[allow(dead_code)] [Complex32; 4],
);
impl Align16Quad {
const ZERO: Self = Self([Complex32::new(0.0, 0.0); 4]);
}
pub struct AlignedComplexBuf {
quads: alloc::vec::Vec<Align16Quad>,
len: usize,
}
impl AlignedComplexBuf {
pub fn zeroed(len: usize) -> Self {
Self {
quads: alloc::vec![Align16Quad::ZERO; len.div_ceil(4)],
len,
}
}
pub fn as_mut_slice(&mut self) -> &mut [Complex32] {
unsafe {
core::slice::from_raw_parts_mut(self.quads.as_mut_ptr() as *mut Complex32, self.len)
}
}
pub fn as_slice(&self) -> &[Complex32] {
unsafe { core::slice::from_raw_parts(self.quads.as_ptr() as *const Complex32, self.len) }
}
}
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::engine::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::engine::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(test)]
mod tests_aligned_buf {
use super::*;
#[test]
fn zeroed_is_16_byte_aligned_and_the_requested_length() {
for len in [4usize, 512, 4096, 8192, 7] {
let mut buf = AlignedComplexBuf::zeroed(len);
assert_eq!(buf.as_mut_slice().len(), len, "len {len}");
assert_eq!(buf.as_slice().len(), len, "len {len}");
assert_eq!(
buf.as_mut_slice().as_ptr() as usize % 16,
0,
"len {len} not 16-byte aligned",
);
assert!(
buf.as_slice().iter().all(|c| c.re == 0.0 && c.im == 0.0),
"len {len} not zeroed",
);
}
}
#[test]
fn writes_are_visible_through_the_shared_view() {
let mut buf = AlignedComplexBuf::zeroed(8);
buf.as_mut_slice()[5] = Complex32::new(1.5, -2.5);
assert_eq!(buf.as_slice()[5], Complex32::new(1.5, -2.5));
assert_eq!(buf.as_slice()[4], Complex32::new(0.0, 0.0));
}
}
#[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);
}
}