use crate::util::{DctSample, try_vec};
use crate::{PxdctError, PxdctExecutor};
use num_complex::Complex;
use num_traits::AsPrimitive;
use std::sync::Arc;
use zaft::{FftDirection, FftExecutor};
pub struct Dct4Fft<T> {
fft_executor: Arc<dyn FftExecutor<T> + Send + Sync>,
execution_length: usize,
}
impl<T: DctSample> Dct4Fft<T> {
pub fn new(fft_executor: Arc<dyn FftExecutor<T> + Send + Sync>) -> Self {
assert_eq!(
fft_executor.direction(),
FftDirection::Forward,
"Dct4Odd requires a forward FFT, but an inverse FFT was provided"
);
let len = fft_executor.length();
assert!(
!len.is_multiple_of(2),
"Dct4Odd size must be odd. Got {}",
len
);
Self {
execution_length: len,
fft_executor,
}
}
}
impl<T: DctSample> PxdctExecutor<T> for Dct4Fft<T>
where
f64: AsPrimitive<T>,
{
fn execute(&self, data: &mut [T]) -> Result<(), PxdctError> {
if !data.len().is_multiple_of(self.execution_length) {
return Err(PxdctError::InvalidSizeMultiplier(
data.len(),
self.execution_length,
));
}
let mut scratch = try_vec![Complex::<T>::default(); self.execution_length];
let len = self.length();
let half_len = len / 2;
for chunk in data.chunks_exact_mut(self.execution_length) {
let mut input_index = half_len;
let mut fft_index = 0;
while input_index < len {
scratch[fft_index] = Complex {
re: chunk[input_index],
im: T::zero(),
};
input_index += 4;
fft_index += 1;
}
input_index -= len;
while input_index < len {
scratch[fft_index] = Complex {
re: -chunk[len - input_index - 1],
im: T::zero(),
};
input_index += 4;
fft_index += 1;
}
input_index -= len;
while input_index < len {
scratch[fft_index] = Complex {
re: -chunk[input_index],
im: T::zero(),
};
input_index += 4;
fft_index += 1;
}
input_index -= len;
while input_index < len {
scratch[fft_index] = Complex {
re: chunk[len - input_index - 1],
im: T::zero(),
};
input_index += 4;
fft_index += 1;
}
input_index -= len;
while fft_index < len {
scratch[fft_index] = Complex {
re: chunk[input_index],
im: T::zero(),
};
input_index += 4;
fft_index += 1;
}
self.fft_executor
.execute(&mut scratch)
.map_err(|x| PxdctError::FftError(x.to_string()))?;
let result_scale = T::SQRT_2 * T::HALF;
let second_half_sign = if len % 4 == 1 { T::one() } else { -T::one() };
let mut output_sign = T::one();
let (left, right) = chunk.split_at_mut(half_len);
for ((l_dst, r_dst), scratch) in left
.chunks_exact_mut(2)
.zip(right.rchunks_exact_mut(2))
.zip(scratch.chunks_exact(4))
{
let fft_result = scratch[1] * (output_sign * result_scale);
let next_result = scratch[3] * (output_sign * result_scale);
l_dst[0] = fft_result.re + fft_result.im;
l_dst[1] = -next_result.re + next_result.im;
r_dst[0] = (next_result.re + next_result.im) * second_half_sign;
r_dst[1] = (fft_result.re - fft_result.im) * second_half_sign;
output_sign = output_sign.neg();
}
if len % 4 == 1 {
chunk[half_len] = scratch[0].re * output_sign * result_scale;
} else {
let fft_result = scratch[len - 2] * (output_sign * result_scale);
chunk[half_len - 1] = fft_result.re + fft_result.im;
chunk[half_len + 1] = -fft_result.re + fft_result.im;
chunk[half_len] = -scratch[0].re * output_sign * result_scale;
}
}
Ok(())
}
#[inline]
fn length(&self) -> usize {
self.execution_length
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::naive_dct4;
use rand::Rng;
use zaft::Zaft;
#[test]
fn test_split_dct4() {
let mut input = vec![0.; 15];
for z in input.iter_mut() {
*z = rand::rng().random_range(1.0..2.0);
}
let reference_input = input.clone();
let reference_input = naive_dct4(&reference_input);
let bf = Dct4Fft::new(Zaft::make_forward_fft_f64(15).unwrap());
bf.execute(&mut input).unwrap();
input
.iter()
.zip(reference_input.iter())
.enumerate()
.for_each(|(i, (&src, &r0))| {
assert!(
(src - r0).abs() < 1e-7,
"Difference must be < {}, but it was {}, at position {i}",
1e-7,
(src - r0).abs()
)
});
}
#[test]
fn test_split_dct4_35() {
let mut input = vec![0.; 35];
for z in input.iter_mut() {
*z = rand::rng().random_range(1.0..2.0);
}
let reference_input = input.clone();
let reference_input = naive_dct4(&reference_input);
let bf = Dct4Fft::new(Zaft::make_forward_fft_f64(35).unwrap());
bf.execute(&mut input).unwrap();
input
.iter()
.zip(reference_input.iter())
.enumerate()
.for_each(|(i, (&src, &r0))| {
assert!(
(src - r0).abs() < 1e-7,
"Difference must be < {}, but it was {}, at position {i}",
1e-7,
(src - r0).abs()
)
});
}
#[test]
fn test_split_dct4_17() {
let mut input = vec![0.; 17];
for z in input.iter_mut() {
*z = rand::rng().random_range(1.0..2.0);
}
let reference_input = input.clone();
let reference_input = naive_dct4(&reference_input);
let bf = Dct4Fft::new(Zaft::make_forward_fft_f64(17).unwrap());
bf.execute(&mut input).unwrap();
input
.iter()
.zip(reference_input.iter())
.enumerate()
.for_each(|(i, (&src, &r0))| {
assert!(
(src - r0).abs() < 1e-7,
"Difference must be < {}, but it was {}, at position {i}",
1e-7,
(src - r0).abs()
)
});
}
}