use crate::bidirectional::{BiStore, BidirectionalStore, InPlaceStore};
use crate::mla::fmla;
use crate::twiddles::compute_twiddle;
use crate::util::{DctSample, try_vec};
use crate::{PxdctError, PxdctExecutor};
use num_complex::Complex;
use num_traits::AsPrimitive;
use std::sync::Arc;
pub(crate) struct Dst2Radix3<T: DctSample> {
dst2_subtransform: Arc<dyn PxdctExecutor<T> + Send + Sync>,
execution_length: usize,
twiddles: Box<[Complex<T>]>,
}
impl<T: DctSample> Dst2Radix3<T>
where
f64: AsPrimitive<T>,
{
pub(crate) fn new(
dst2_subtransform: Arc<dyn PxdctExecutor<T> + Send + Sync>,
) -> Result<Dst2Radix3<T>, PxdctError> {
let sub_len = dst2_subtransform.length();
let execution_length = sub_len * 3;
let n_sub = sub_len;
let fft_len = 12 * execution_length;
let four_n = 4 * execution_length;
let mut twiddles: Vec<Complex<T>> = Vec::with_capacity(n_sub * 3);
for i in 0..n_sub {
let n = i + 1;
let k = 3 * (2 * n - 1);
let t1 = compute_twiddle::<T>(k, fft_len);
twiddles.push(t1.conj());
let idx2 = (four_n as isize - k as isize).rem_euclid(fft_len as isize) as usize;
let t2 = compute_twiddle::<T>(idx2, fft_len);
twiddles.push(t2.conj());
let idx3 = (four_n + k) % fft_len;
let t3 = compute_twiddle::<T>(idx3, fft_len);
twiddles.push(t3.conj());
}
Ok(Dst2Radix3 {
dst2_subtransform,
execution_length,
twiddles: twiddles.into_boxed_slice(),
})
}
fn process_chunk<S: BidirectionalStore<T>>(
&self,
chunk: &mut S,
p: &mut [T],
q: &mut [T],
w: &mut [T],
sub_out: &mut [T],
sub_scratch: &mut [T],
) -> Result<(), PxdctError> {
let n_sub = self.execution_length / 3;
p.iter_mut()
.zip(q.iter_mut())
.zip(w.iter_mut())
.zip(self.twiddles.as_chunks::<3>().0.iter())
.enumerate()
.for_each(|(i, (((p_i, q_i), w_i), tw))| {
let x_1 = chunk[i];
let x_2 = chunk[2 * n_sub - 1 - i];
let x_3 = chunk[i + 2 * n_sub];
*p_i = x_1 - x_2 + x_3;
*q_i = fmla(x_1, tw[0].re, fmla(-x_2, tw[1].re, x_3 * tw[2].re));
let r_val = fmla(x_1, tw[0].im, fmla(x_2, tw[1].im, x_3 * tw[2].im));
*w_i = if i % 2 == 0 { r_val } else { -r_val };
});
let (y_p, rest) = sub_out.split_at_mut(n_sub);
let (y_q, y_w) = rest.split_at_mut(n_sub);
self.dst2_subtransform
.execute_into_with_scratch(p, y_p, sub_scratch)?;
self.dst2_subtransform
.execute_into_with_scratch(q, y_q, sub_scratch)?;
self.dst2_subtransform
.execute_into_with_scratch(w, y_w, sub_scratch)?;
for k in 0..n_sub - 1 {
let w_val = unsafe { *y_w.get_unchecked(n_sub - k - 2) };
let q_val = unsafe { *y_q.get_unchecked(n_sub - k - 2) };
unsafe { chunk[3 * k + 2] = *y_p.get_unchecked(k) };
unsafe { chunk[3 * k + 1] = *y_q.get_unchecked(k) - w_val };
unsafe { chunk[self.execution_length - 3 * k - 3] = q_val + *y_w.get_unchecked(k) };
}
let k = n_sub - 1;
unsafe { chunk[3 * k + 2] = *y_p.get_unchecked(k) };
unsafe { chunk[3 * k + 1] = *y_q.get_unchecked(k) };
unsafe { chunk[self.execution_length - 3 * k - 3] = *y_w.get_unchecked(k) };
Ok(())
}
}
impl<T: DctSample> PxdctExecutor<T> for Dst2Radix3<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 n_sub = self.execution_length / 3;
let (sub_buffers, rest) = scratch.split_at_mut(self.execution_length);
let (p, rest_buffers) = sub_buffers.split_at_mut(n_sub);
let (q, w) = rest_buffers.split_at_mut(n_sub);
let (sub_out, sub_scratch) = rest.split_at_mut(self.execution_length);
for chunk in data.chunks_exact_mut(self.execution_length) {
self.process_chunk(&mut InPlaceStore::new(chunk), p, q, w, sub_out, sub_scratch)?;
}
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 n_sub = self.execution_length / 3;
let (sub_buffers, rest) = scratch.split_at_mut(self.execution_length);
let (p, rest_buffers) = sub_buffers.split_at_mut(n_sub);
let (q, w) = rest_buffers.split_at_mut(n_sub);
let (sub_out, sub_scratch) = rest.split_at_mut(self.execution_length);
for (src, dst) in input
.chunks_exact(self.execution_length)
.zip(output.chunks_exact_mut(self.execution_length))
{
self.process_chunk(&mut BiStore::new(src, dst), p, q, w, sub_out, sub_scratch)?;
}
Ok(())
}
#[inline]
fn length(&self) -> usize {
self.execution_length
}
fn scratch_size(&self) -> usize {
(self.execution_length * 2) + self.dst2_subtransform.scratch_size()
}
}
#[cfg(test)]
mod tests {
use crate::tests::naive_dst2;
use crate::type2::dst_radix3::Dst2Radix3;
use crate::{Pxdct, PxdctExecutor};
#[test]
fn test_dct7_size9() {
let mut array = vec![
1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64,
];
let dct = Dst2Radix3::<f64>::new(Pxdct::make_dst2_f64(3).unwrap()).unwrap();
let control = naive_dst2(&array);
dct.execute(&mut array).unwrap();
array
.iter()
.zip(control.iter())
.enumerate()
.for_each(|(i, (&x, &c))| {
assert!((x - c).abs() < 1e-9, "index {i}: got {x}, expected {c}");
});
}
#[test]
fn test_dct7_size12() {
let mut array = vec![
1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64,
];
let dct = Dst2Radix3::<f64>::new(Pxdct::make_dst2_f64(4).unwrap()).unwrap();
let control = naive_dst2(&array);
dct.execute(&mut array).unwrap();
array
.iter()
.zip(control.iter())
.enumerate()
.for_each(|(i, (&x, &c))| {
assert!((x - c).abs() < 1e-9, "index {i}: got {x}, expected {c}");
});
}
#[test]
fn test_dct7_size27() {
let mut array = vec![
1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64,
1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64, 1.0f64, 3.0, 1.0f64,
1.0f64, 3.0, 1.0f64,
];
let dct = Dst2Radix3::<f64>::new(Pxdct::make_dst2_f64(9).unwrap()).unwrap();
let control = naive_dst2(&array);
dct.execute(&mut array).unwrap();
array
.iter()
.zip(control.iter())
.enumerate()
.for_each(|(i, (&x, &c))| {
assert!((x - c).abs() < 1e-9, "index {i}: got {x}, expected {c}");
});
}
}