#![deny(missing_docs)]
#![deny(clippy::undocumented_unsafe_blocks)]
#![doc = include_str!("../README.md")]
#![cfg_attr(feature = "realfft", allow(incomplete_features))]
#![cfg_attr(feature = "realfft", feature(generic_const_exprs))]
use rustfft::FftPlanner;
use std::cell::RefCell;
use std::sync::Arc;
pub use rustfft::num_complex::Complex;
pub use rustfft::FftNum;
#[cfg(feature = "realfft")]
pub mod realfft;
pub trait Fft<T, const SIZE: usize> {
fn fft(&self) -> [Complex<T>; SIZE];
}
pub trait Ifft<T, const SIZE: usize> {
fn ifft(&self) -> [Complex<T>; SIZE];
}
impl<T: FftNum + Default, const SIZE: usize> Fft<T, SIZE> for [T; SIZE] {
fn fft(&self) -> [Complex<T>; SIZE] {
let mut buffer: [Complex<T>; SIZE] = unsafe {
array_init::from_iter(
self.iter()
.map(|sample| Complex::new(*sample, T::default())),
)
.unwrap_unchecked()
};
get_fft_algorithm::<T>(SIZE).process(&mut buffer);
buffer
}
}
impl<T: FftNum, const SIZE: usize> Fft<T, SIZE> for [Complex<T>; SIZE] {
fn fft(&self) -> [Complex<T>; SIZE] {
let mut buffer: [Complex<T>; SIZE] = *self;
get_fft_algorithm::<T>(SIZE).process(&mut buffer);
buffer
}
}
pub(crate) mod generic_singleton {
use anymap::AnyMap;
use std::cell::RefCell;
pub fn get_or_init<T: 'static>(init: fn() -> T) -> &'static T {
thread_local! {
static REF_CELL_MAP: RefCell<AnyMap> = RefCell::new(AnyMap::new());
};
REF_CELL_MAP.with(|map_cell| {
let mut map = map_cell.borrow_mut();
if !map.contains::<T>() {
map.insert(init());
}
let t_ref = unsafe { map.get::<T>().unwrap_unchecked() };
let ptr = t_ref as *const T;
let optional_ref = unsafe { ptr.as_ref() };
unsafe { optional_ref.unwrap_unchecked() }
})
}
}
fn get_fft_algorithm<T: FftNum>(size: usize) -> Arc<dyn rustfft::Fft<T>> {
generic_singleton::get_or_init(|| RefCell::new(FftPlanner::new()))
.borrow_mut()
.plan_fft_forward(size)
}
fn get_inverse_fft_algorithm<T: FftNum>(size: usize) -> Arc<dyn rustfft::Fft<T>> {
generic_singleton::get_or_init(|| RefCell::new(FftPlanner::new()))
.borrow_mut()
.plan_fft_inverse(size)
}
impl<T: FftNum + Default, const SIZE: usize> Ifft<T, SIZE> for [Complex<T>; SIZE] {
fn ifft(&self) -> [Complex<T>; SIZE] {
let mut buffer = *self;
get_inverse_fft_algorithm::<T>(SIZE).process(&mut buffer);
buffer
}
}
#[cfg(test)]
mod tests {
use super::*;
const ARBITRARY_EVEN_TEST_ARRAY: [f64; 6] = [1.5, 3.0, 2.1, 3.2, 2.2, 3.1];
const ARBITRARY_ODD_TEST_ARRAY: [f64; 7] = [1.5, 3.0, 2.1, 3.2, 2.2, 3.1, 1.2];
const ACCEPTABLE_ERROR: f64 = 0.00000000000001;
fn fft_and_ifft_are_inverse_operations<const SIZE: usize>(array: [f64; SIZE]) {
let converted: Vec<_> = array
.fft()
.ifft()
.iter_mut()
.map(|sample| *sample / array.len() as f64)
.collect();
assert_eq!(array.len(), converted.len());
for (converted, original) in converted.iter().zip(array.iter()) {
approx::assert_ulps_eq!(converted.re, original, epsilon = ACCEPTABLE_ERROR);
approx::assert_ulps_eq!(converted.im, 0.0);
}
}
#[test]
fn fft_and_ifft_are_inverse_operations_even() {
fft_and_ifft_are_inverse_operations(ARBITRARY_EVEN_TEST_ARRAY);
}
#[test]
fn fft_and_ifft_are_inverse_operations_odd() {
fft_and_ifft_are_inverse_operations(ARBITRARY_ODD_TEST_ARRAY);
}
}