use std::cell::RefCell;
use array_init::map_array_init;
#[rustfmt::skip]
use ::realfft::num_complex::Complex;
#[rustfmt::skip]
use ::realfft::FftNum;
#[rustfmt::skip]
use ::realfft::num_traits::Zero;
use crate::with_fft_algorithm;
use crate::with_inverse_fft_algorithm;
#[cfg(feature = "const-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];
}
pub trait FftMut<T, const SIZE: usize> {
fn fft_mut(&mut self);
}
pub trait IfftMut<T, const SIZE: usize> {
fn ifft_mut(&mut self);
}
trait StaticScratchFft<T: FftNum, const SIZE: usize>: rustfft::Fft<T> {
fn process_with_static_scratch(&self, buffer: &mut [Complex<T>; SIZE]);
}
impl<T: FftNum + Default, U: ?Sized + rustfft::Fft<T>, const SIZE: usize> StaticScratchFft<T, SIZE>
for U
{
fn process_with_static_scratch(&self, buffer: &mut [Complex<T>; SIZE]) {
generic_singleton::get_or_init_thread_local!(
|| RefCell::new([Complex::default(); SIZE]),
|scratch_buffer_ref| {
let mut scratch_buffer_ref = scratch_buffer_ref.borrow_mut();
self.process_with_scratch(buffer, &mut *scratch_buffer_ref);
}
);
}
}
impl<T: FftNum + Default + Zero, const SIZE: usize> Fft<T, SIZE> for [T; SIZE] {
fn fft(&self) -> [Complex<T>; SIZE] {
let mut buffer = map_array_init(self, |sample| Complex::new(*sample, T::zero()));
with_fft_algorithm::<T>(SIZE, |algorithm| {
algorithm.process_with_static_scratch(&mut buffer);
});
buffer
}
}
impl<T: FftNum + std::default::Default, const SIZE: usize> Fft<T, SIZE> for [Complex<T>; SIZE] {
fn fft(&self) -> [Complex<T>; SIZE] {
let mut buffer: [Complex<T>; SIZE] = *self;
with_fft_algorithm::<T>(SIZE, |algorithm| {
algorithm.process_with_static_scratch(&mut buffer);
});
buffer
}
}
impl<T: FftNum + Default, const SIZE: usize> Ifft<T, SIZE> for [Complex<T>; SIZE] {
fn ifft(&self) -> [Complex<T>; SIZE] {
let mut buffer = *self;
with_inverse_fft_algorithm::<T>(SIZE, |fft| fft.process_with_static_scratch(&mut buffer));
buffer
}
}
impl<T: FftNum + std::default::Default, const SIZE: usize> FftMut<T, SIZE> for [Complex<T>; SIZE] {
fn fft_mut(&mut self) {
with_fft_algorithm::<T>(SIZE, |algorithm| {
algorithm.process_with_static_scratch(self);
});
}
}
impl<T: FftNum + Default, const SIZE: usize> IfftMut<T, SIZE> for [Complex<T>; SIZE] {
fn ifft_mut(&mut self) {
with_inverse_fft_algorithm::<T>(SIZE, |fft| fft.process_with_static_scratch(self));
}
}
#[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.000_000_000_000_01;
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);
}
}