use crate::util::{DctSample, force_cast_real_scratch_to_complex, try_vec, validate_scratch};
use crate::{PxdctError, PxdctExecutor};
use num_complex::Complex;
use num_traits::AsPrimitive;
use std::sync::Arc;
use zaft::R2CFftExecutor;
pub(crate) struct Dct4Fft<T> {
fft_executor: Arc<dyn R2CFftExecutor<T> + Send + Sync>,
fft_scratch_size: usize,
execution_length: usize,
}
impl<T: DctSample> Dct4Fft<T> {
pub fn new(fft_executor: Arc<dyn R2CFftExecutor<T> + Send + Sync>) -> Self {
let len = fft_executor.real_length();
let fft_scratch_size = fft_executor.complex_scratch_length();
assert!(
!len.is_multiple_of(2),
"Dct4Odd size must be odd. Got {}",
len
);
Self {
execution_length: len,
fft_executor,
fft_scratch_size,
}
}
fn run_r2c(
&self,
src: &[T],
real_buf: &mut [T], spectrum: &mut [Complex<T>], full: &mut [Complex<T>], fft_scratch: &mut [Complex<T>],
) -> Result<(), PxdctError> {
let len = self.execution_length;
let half_len = len / 2;
let mut input_index = half_len;
let mut fft_index = 0usize;
while input_index < len {
unsafe { *real_buf.get_unchecked_mut(fft_index) = *src.get_unchecked(input_index) };
input_index += 4;
fft_index += 1;
}
input_index -= len;
while input_index < len {
unsafe {
*real_buf.get_unchecked_mut(fft_index) = -*src.get_unchecked(len - input_index - 1)
};
input_index += 4;
fft_index += 1;
}
input_index -= len;
while input_index < len {
unsafe { *real_buf.get_unchecked_mut(fft_index) = -*src.get_unchecked(input_index) };
input_index += 4;
fft_index += 1;
}
input_index -= len;
while input_index < len {
unsafe {
*real_buf.get_unchecked_mut(fft_index) = *src.get_unchecked(len - input_index - 1)
};
input_index += 4;
fft_index += 1;
}
input_index -= len;
while fft_index < len {
unsafe { *real_buf.get_unchecked_mut(fft_index) = *src.get_unchecked(input_index) };
input_index += 4;
fft_index += 1;
}
self.fft_executor
.execute_with_scratch(real_buf, spectrum, fft_scratch)
.map_err(|x| PxdctError::FftError(x.to_string()))?;
full[0] = spectrum[0];
for k in 1..=len / 2 {
let s = unsafe { *spectrum.get_unchecked(k) };
unsafe {
*full.get_unchecked_mut(k) = s;
*full.get_unchecked_mut(len - k) = s.conj();
}
}
Ok(())
}
}
impl<T: DctSample> PxdctExecutor<T> for Dct4Fft<T>
where
f64: AsPrimitive<T>,
{
fn execute(&self, data: &mut [T]) -> Result<(), PxdctError> {
let mut scratch = try_vec![T::default(); self.scratch_size()];
self.execute_with_scratch(data, &mut scratch)
}
fn execute_with_scratch(&self, data: &mut [T], scratch: &mut [T]) -> Result<(), PxdctError> {
if !data.len().is_multiple_of(self.execution_length) {
return Err(PxdctError::InvalidSizeMultiplier(
data.len(),
self.execution_length,
));
}
let full_scratch = validate_scratch!(scratch, self.scratch_size());
let len = self.execution_length;
let half_len = len / 2;
let r2c_out_len = len / 2 + 1;
let (real_buf_slice, rest) = full_scratch.split_at_mut(len);
let (spectrum_slice, rest) = rest.split_at_mut(r2c_out_len * 2);
let (full_slice, fft_scratch_slice) = rest.split_at_mut(len * 2);
let spectrum = force_cast_real_scratch_to_complex(spectrum_slice, r2c_out_len);
let full = force_cast_real_scratch_to_complex(full_slice, len);
let fft_scratch =
force_cast_real_scratch_to_complex(fft_scratch_slice, self.fft_scratch_size);
for chunk in data.chunks_exact_mut(self.execution_length) {
self.run_r2c(chunk, real_buf_slice, spectrum, full, fft_scratch)?;
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), s) in left
.as_chunks_mut::<2>()
.0
.iter_mut()
.zip(right.as_rchunks_mut::<2>().1.iter_mut().rev())
.zip(full.as_chunks::<4>().0.iter())
{
let fft_result = s[1] * (output_sign * result_scale);
let next_result = s[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] = full[0].re * output_sign * result_scale;
} else {
let fft_result = full[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] = -full[0].re * output_sign * result_scale;
}
}
Ok(())
}
fn execute_into(&self, input: &[T], output: &mut [T]) -> Result<(), PxdctError> {
let mut scratch = try_vec![T::default(); self.scratch_size()];
self.execute_into_with_scratch(input, output, &mut scratch)
}
fn execute_into_with_scratch(
&self,
input: &[T],
output: &mut [T],
scratch: &mut [T],
) -> Result<(), PxdctError> {
use crate::util::validate_oof_sizes;
validate_oof_sizes!(input, output, self.execution_length);
let full_scratch = validate_scratch!(scratch, self.scratch_size());
let len = self.execution_length;
let half_len = len / 2;
let r2c_out_len = len / 2 + 1;
let (real_buf_slice, rest) = full_scratch.split_at_mut(len);
let (spectrum_slice, rest) = rest.split_at_mut(r2c_out_len * 2);
let (full_slice, fft_scratch_slice) = rest.split_at_mut(len * 2);
let spectrum = force_cast_real_scratch_to_complex(spectrum_slice, r2c_out_len);
let full = force_cast_real_scratch_to_complex(full_slice, len);
let fft_scratch =
force_cast_real_scratch_to_complex(fft_scratch_slice, self.fft_scratch_size);
for (src, dst) in input
.chunks_exact(self.execution_length)
.zip(output.chunks_exact_mut(self.execution_length))
{
self.run_r2c(src, real_buf_slice, spectrum, full, fft_scratch)?;
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) = dst.split_at_mut(half_len);
for ((l_dst, r_dst), s) in left
.as_chunks_mut::<2>()
.0
.iter_mut()
.zip(right.as_rchunks_mut::<2>().1.iter_mut().rev())
.zip(full.as_chunks::<4>().0.iter())
{
let fft_result = s[1] * (output_sign * result_scale);
let next_result = s[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 {
dst[half_len] = full[0].re * output_sign * result_scale;
} else {
let fft_result = full[len - 2] * (output_sign * result_scale);
dst[half_len - 1] = fft_result.re + fft_result.im;
dst[half_len + 1] = -fft_result.re + fft_result.im;
dst[half_len] = -full[0].re * output_sign * result_scale;
}
}
Ok(())
}
#[inline]
fn length(&self) -> usize {
self.execution_length
}
#[inline]
fn scratch_size(&self) -> usize {
let len = self.execution_length;
let r2c_out_len = len / 2 + 1;
len + r2c_out_len * 2 + len * 2 + self.fft_scratch_size * 2
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::naive_dct4;
use rand::RngExt;
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_r2c_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_r2c_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_r2c_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()
)
});
}
}