use crate::mla::fmla;
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::{FftDirection, FftExecutor};
pub(crate) struct MdctFft<T> {
fft_executor: Arc<dyn FftExecutor<T> + Send + Sync>,
n: usize,
twiddles: Vec<Complex<T>>,
fft_scratch_size: usize,
}
impl<T: DctSample> MdctFft<T>
where
f64: AsPrimitive<T>,
{
pub(crate) fn new(n: usize) -> Result<MdctFft<T>, PxdctError> {
if n < 4 {
return Err(PxdctError::InvalidSizeMultiplier(n, 4));
}
if !n.is_multiple_of(2) {
return Err(PxdctError::InvalidSizeMultiplier(n, 2));
}
let m = n / 2; let inner_fft = T::make_fft(m, FftDirection::Forward)?;
let fft_scratch_size = inner_fft.scratch_length();
let mut twiddles = Vec::with_capacity(m);
for k in 0..m {
let angle: T = (-((8 * k + 1) as f64 / (8 * n) as f64)).as_();
let (v_sin, v_cos) = angle.sincos_pi();
twiddles.push(Complex {
re: v_cos,
im: v_sin,
});
}
Ok(MdctFft {
fft_executor: inner_fft,
n,
twiddles,
fft_scratch_size,
})
}
fn pre_twiddle(&self, input: &[T], fft_buf: &mut [Complex<T>]) {
let n = self.n;
let n2 = n / 2;
let n32 = 3 * n / 2;
let n52 = 5 * n / 2;
fft_buf
.iter_mut()
.zip(self.twiddles.iter())
.enumerate()
.for_each(|(k, (buf, twiddle))| {
let nn = 2 * k;
let (r0, i0) = unsafe {
if nn < n2 {
(
*input.get_unchecked(n32 - 1 - nn) + *input.get_unchecked(n32 + nn),
*input.get_unchecked(n2 + nn) - *input.get_unchecked(n2 - 1 - nn),
)
} else {
(
*input.get_unchecked(n32 - 1 - nn) - *input.get_unchecked(nn - n2),
*input.get_unchecked(n2 + nn) + *input.get_unchecked(n52 - 1 - nn),
)
}
};
let c = twiddle.re;
let neg_s = twiddle.im;
*buf = Complex {
re: fmla(r0, c, -i0 * neg_s),
im: fmla(r0, neg_s, i0 * c),
};
});
}
fn post_twiddle(&self, fft_out: &[Complex<T>], output: &mut [T]) {
let n = self.n;
fft_out
.iter()
.zip(self.twiddles.iter())
.enumerate()
.for_each(|(k, (z, twiddle))| {
let c = twiddle.re;
let neg_s = twiddle.im;
let w_re = fmla(z.re, c, -z.im * neg_s);
let w_im = fmla(z.re, neg_s, z.im * c);
let nn = 2 * k;
unsafe {
*output.get_unchecked_mut(nn) = w_re.neg();
*output.get_unchecked_mut(n - 1 - nn) = w_im;
}
});
}
}
impl<T: DctSample> PxdctExecutor<T> for MdctFft<T>
where
f64: AsPrimitive<T>,
{
fn execute(&self, _data: &mut [T]) -> Result<(), PxdctError> {
Err(PxdctError::InvalidSizeMultiplier(self.n, 2 * self.n))
}
fn execute_with_scratch(&self, _data: &mut [T], _scratch: &mut [T]) -> Result<(), PxdctError> {
Err(PxdctError::InvalidSizeMultiplier(self.n, 2 * self.n))
}
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> {
let in_block = 2 * self.n;
let out_block = self.n;
if !input.len().is_multiple_of(in_block) {
return Err(PxdctError::InvalidSizeMultiplier(input.len(), in_block));
}
if !output.len().is_multiple_of(out_block) {
return Err(PxdctError::InvalidSizeMultiplier(output.len(), out_block));
}
if input.len() / in_block != output.len() / out_block {
return Err(PxdctError::InvalidSizeMultiplier(
output.len(),
input.len() / 2,
));
}
let m = self.n / 2;
let full_scratch = validate_scratch!(scratch, self.scratch_size());
let (fft_buf_real, rest) = full_scratch.split_at_mut(m * 2);
let fft_buf = force_cast_real_scratch_to_complex(fft_buf_real, m);
let fft_scratch = force_cast_real_scratch_to_complex(rest, self.fft_scratch_size);
for (src, dst) in input
.chunks_exact(in_block)
.zip(output.chunks_exact_mut(out_block))
{
self.pre_twiddle(src, fft_buf);
self.fft_executor
.execute_with_scratch(fft_buf, fft_scratch)
.map_err(|e| PxdctError::FftError(e.to_string()))?;
self.post_twiddle(fft_buf, dst);
}
Ok(())
}
fn length(&self) -> usize {
self.n
}
fn scratch_size(&self) -> usize {
self.n + self.fft_scratch_size * 2
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PxdctExecutor;
fn naive_mdct(input: &[f64]) -> Vec<f64> {
let len = input.len();
assert!(len.is_multiple_of(2));
let n = len / 2;
let mut out = vec![0.0; n];
for k in 0..n {
let mut s = 0.0;
for nn in 0..len {
let arg = std::f64::consts::PI / n as f64
* (nn as f64 + 0.5 + n as f64 / 2.0)
* (k as f64 + 0.5);
s += input[nn] * arg.cos();
}
out[k] = s;
}
out
}
fn run_case(n: usize, tol: f64) {
let input: Vec<f64> = (1..=2 * n).map(|x| x as f64).collect();
let mut output = vec![0.0f64; n];
let mdct = MdctFft::<f64>::new(n).unwrap();
mdct.execute_into(&input, &mut output).unwrap();
let control = naive_mdct(&input);
for (i, (&x, &c)) in output.iter().zip(control.iter()).enumerate() {
let abs_tol = tol * (1.0 + c.abs());
assert!(
(x - c).abs() < abs_tol,
"N={n}, k={i}: got {x}, expected {c}, diff {}",
(x - c).abs()
);
}
}
#[test]
fn test_mdct_size4() {
run_case(4, 1e-10);
}
#[test]
fn test_mdct_size8() {
run_case(8, 1e-10);
}
#[test]
fn test_mdct_size16() {
run_case(16, 1e-10);
}
#[test]
fn test_mdct_size_odd_fft_len() {
for n in [6, 10, 14, 22, 30, 50] {
run_case(n, 1e-10);
}
}
#[test]
fn test_mdct_power_of_two_sizes() {
for shift in 2..=10 {
run_case(1 << shift, 1e-9);
}
}
#[test]
fn test_mdct_multiple_blocks() {
let n = 8;
let input: Vec<f64> = (1..=6 * n).map(|x| x as f64).collect();
let mut output = vec![0.0f64; 3 * n];
let mdct = MdctFft::<f64>::new(n).unwrap();
mdct.execute_into(&input, &mut output).unwrap();
for block in 0..3 {
let block_in = &input[block * 2 * n..(block + 1) * 2 * n];
let block_out = &output[block * n..(block + 1) * n];
let control = naive_mdct(block_in);
for (i, (&x, &c)) in block_out.iter().zip(control.iter()).enumerate() {
assert!(
(x - c).abs() < 1e-9,
"block {block}, k={i}: got {x}, expected {c}"
);
}
}
}
#[test]
fn test_mdct_rejects_odd_n() {
assert!(MdctFft::<f64>::new(5).is_err());
assert!(MdctFft::<f64>::new(7).is_err());
}
#[test]
fn test_mdct_rejects_too_small() {
assert!(MdctFft::<f64>::new(2).is_err());
}
}