use array_init::array_init;
use realfft::num_traits::NumAssign;
use realfft::num_traits::Zero;
use rustfft::FftNum;
use rustfft::num_complex::Complex;
use std::cell::RefCell;
use std::ops::Add;
use std::ops::AddAssign;
use std::ops::Deref;
use std::ops::Mul;
use std::ops::MulAssign;
use crate::with_inverse_real_fft_algorithm;
use crate::with_real_fft_algorithm;
pub trait RealFft<T, const SIZE: usize>
where
[T; SIZE / 2 + 1]: Sized,
{
fn real_fft(&self) -> RealDft<T, SIZE>;
}
pub trait RealIfft<T, const SIZE: usize> {
fn real_ifft(&self) -> [T; SIZE];
}
#[derive(Debug)]
pub struct RealDft<T, const SIZE: usize>
where
[T; SIZE / 2 + 1]: Sized,
{
inner: [Complex<T>; SIZE / 2 + 1],
}
impl<T: Default + Zero + FftNum, const SIZE: usize> RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
pub fn new(zeroth_bin: T, frequency_bins: [Complex<T>; SIZE / 2]) -> Self {
let mut inner = [Complex::default(); SIZE / 2 + 1];
inner[0] = Complex::new(zeroth_bin, T::zero());
inner[1..frequency_bins.len() + 1].copy_from_slice(&frequency_bins);
Self { inner }
}
}
impl<T, const SIZE: usize> Deref for RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
type Target = [Complex<T>; SIZE / 2 + 1];
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: FftNum, const SIZE: usize> Mul for &RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
type Output = RealDft<T, SIZE>;
fn mul(self, rhs: Self) -> Self::Output {
let inner =
array_init(|index| unsafe { self.get_unchecked(index) * rhs.get_unchecked(index) });
RealDft { inner }
}
}
impl<T: Default + FftNum + NumAssign, const SIZE: usize> MulAssign<&Self> for RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
fn mul_assign(&mut self, rhs: &Self) {
for (bin_self, bin_rhs) in self.inner.iter_mut().zip(rhs.iter()) {
*bin_self *= bin_rhs;
}
}
}
impl<T, const SIZE: usize> RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
#[allow(clippy::missing_panics_doc)]
pub fn get_frequency_bins(&mut self) -> &[Complex<T>; (SIZE - 1) / 2] {
(&self.inner[1..(SIZE - 1) / 2]).try_into().unwrap()
}
#[allow(clippy::missing_panics_doc)]
pub fn get_frequency_bins_mut(&mut self) -> &mut [Complex<T>; (SIZE - 1) / 2] {
(&mut self.inner[1..(SIZE - 1) / 2]).try_into().unwrap()
}
pub fn get_offset(&self) -> &T {
&self.inner[0].re
}
pub fn get_offset_mut(&mut self) -> &mut T {
&mut self.inner[0].re
}
}
impl<T, const SIZE: usize> From<RealDft<T, SIZE>> for [Complex<T>; SIZE / 2 + 1]
where
[T; SIZE / 2 + 1]: Sized,
{
fn from(real_dft: RealDft<T, SIZE>) -> Self {
real_dft.inner
}
}
impl<T: FftNum + Default, const SIZE: usize> RealFft<T, SIZE> for [T; SIZE]
where
[T; SIZE / 2 + 1]: Sized,
{
fn real_fft(&self) -> RealDft<T, SIZE> {
let mut output = [Complex::default(); SIZE / 2 + 1];
with_real_fft_algorithm::<T>(SIZE, |r2c| {
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();
unsafe {
r2c.process_with_scratch(
&mut self.clone(),
&mut output,
&mut *scratch_buffer_ref,
)
.unwrap_unchecked();
}
}
);
});
RealDft { inner: output }
}
}
impl<T: FftNum + Default, const SIZE: usize> RealIfft<T, SIZE> for RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
fn real_ifft(&self) -> [T; SIZE] {
let mut output = [T::default(); SIZE];
with_inverse_real_fft_algorithm::<T>(SIZE, |c2r| {
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();
unsafe {
c2r.process_with_scratch(
&mut (*self).clone(),
&mut output,
&mut *scratch_buffer_ref,
)
.unwrap_unchecked();
}
}
);
});
output
}
}
impl<T: Default + FftNum, const SIZE: usize> Add for &RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
type Output = RealDft<T, SIZE>;
fn add(self, rhs: Self) -> Self::Output {
let mut inner = self.inner;
for (i, r) in inner.iter_mut().zip(rhs.iter()) {
*i = *i + r;
}
RealDft { inner }
}
}
impl<T: Default + FftNum, const SIZE: usize> AddAssign for RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
fn add_assign(&mut self, rhs: Self) {
for (i, r) in self.inner.iter_mut().zip(rhs.iter()) {
*i = *i + r;
}
}
}
impl<T: Default + FftNum, const SIZE: usize> Mul<T> for &RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
type Output = RealDft<T, SIZE>;
fn mul(self, rhs: T) -> Self::Output {
#[allow(clippy::suspicious_arithmetic_impl)]
let mut inner = [Complex::default(); SIZE / 2 + 1];
for index in 0..self.len() {
inner[index] = self[index] * rhs;
}
RealDft { inner }
}
}
impl<T: Default + FftNum + NumAssign, const SIZE: usize> MulAssign<T> for RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
fn mul_assign(&mut self, rhs: T) {
for bin_self in &mut self.inner {
*bin_self *= rhs;
}
}
}
impl<T: Default + FftNum, const SIZE: usize> Mul<&[T]> for &RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
type Output = RealDft<T, SIZE>;
fn mul(self, rhs: &[T]) -> Self::Output {
assert_eq!(self.len(), rhs.len());
let mut inner = [Complex::default(); SIZE / 2 + 1];
for index in 0..self.len() {
inner[index] = self[index] * rhs[index];
}
RealDft { inner }
}
}
impl<T: Default + FftNum + NumAssign, const SIZE: usize> MulAssign<&[T]> for RealDft<T, SIZE>
where
[T; SIZE / 2 + 1]: Sized,
{
fn mul_assign(&mut self, rhs: &[T]) {
assert_eq!(self.len(), rhs.len());
for (bin_self, bin_rhs) in self.inner.iter_mut().zip(rhs.iter()) {
*bin_self *= bin_rhs;
}
}
}
#[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 real_fft_and_real_ifft_are_inverse_operations<const SIZE: usize>(array: [f64; SIZE])
where
[f64; SIZE / 2 + 1]: Sized,
{
let converted: Vec<_> = array
.real_fft()
.real_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, original, epsilon = ACCEPTABLE_ERROR);
}
}
#[test]
fn real_fft_and_real_ifft_are_inverse_operations_even() {
real_fft_and_real_ifft_are_inverse_operations(ARBITRARY_EVEN_TEST_ARRAY);
}
#[test]
fn real_fft_and_real_ifft_are_inverse_operations_odd() {
real_fft_and_real_ifft_are_inverse_operations(ARBITRARY_ODD_TEST_ARRAY);
}
}