use crate::mla::fmla;
use crate::twiddles::FftTrigonometry;
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 ImdctFft<T> {
fft_executor: Arc<dyn FftExecutor<T> + Send + Sync>,
n: usize,
twiddles: Vec<Complex<T>>,
fft_scratch_size: usize,
}
impl<T: DctSample> ImdctFft<T>
where
f64: AsPrimitive<T>,
{
pub(crate) fn new(n: usize) -> Result<ImdctFft<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 alpha = 1.0 / (8.0 * n as f64);
let omega = 1.0 / n as f64;
let mut twiddles = Vec::with_capacity(m);
for k in 0..m {
let theta = omega * k as f64 + alpha;
twiddles.push(Complex {
re: theta.cospi().as_(),
im: theta.sinpi().as_(),
});
}
Ok(ImdctFft {
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 m = n / 2;
for (k, (output, twiddle)) in fft_buf[..m]
.iter_mut()
.zip(self.twiddles.iter())
.enumerate()
{
let nn = 2 * k;
let r0 = input[nn];
let i0 = input[n - 1 - nn];
let c = twiddle.re;
let s = twiddle.im;
*output = Complex {
re: i0.neg() * s - r0 * c,
im: i0.neg() * c + r0 * s,
};
}
}
fn post_twiddle(&self, fft_out: &[Complex<T>], output: &mut [T]) {
let n = self.n;
let m = n / 2;
let n2 = n / 2;
let n32 = 3 * n / 2;
let n52 = 5 * n / 2;
for (k, (fft_val, twiddle)) in fft_out[..m].iter().zip(self.twiddles.iter()).enumerate() {
let nn = 2 * k;
let r0 = fft_val.re;
let i0 = fft_val.im;
let c = twiddle.re;
let s = twiddle.im;
let r1 = fmla(r0, c, i0 * s);
let i1 = fmla(r0, s, -i0 * c);
if nn < n2 {
unsafe {
*output.get_unchecked_mut(n32 - 1 - nn) = r1;
*output.get_unchecked_mut(n32 + nn) = r1;
*output.get_unchecked_mut(n2 + nn) = i1;
*output.get_unchecked_mut(n2 - 1 - nn) = i1.neg();
}
} else {
unsafe {
*output.get_unchecked_mut(n32 - 1 - nn) = r1;
*output.get_unchecked_mut(nn - n2) = r1.neg();
*output.get_unchecked_mut(n2 + nn) = i1;
*output.get_unchecked_mut(n52 - 1 - nn) = i1;
}
}
}
}
}
impl<T: DctSample> PxdctExecutor<T> for ImdctFft<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 = self.n;
let out_block = 2 * 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_imdct(coeffs: &[f64]) -> Vec<f64> {
let n = coeffs.len();
let mut out = vec![0.0; 2 * n];
for nn in 0..2 * n {
let mut s = 0.0;
for k in 0..n {
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 += coeffs[k] * arg.cos();
}
out[nn] = s;
}
out
}
fn run_case(n: usize, tol: f64) {
let input: Vec<f64> = (1..=n).map(|x| x as f64).collect();
let mut output = vec![0.0f64; 2 * n];
let imdct = ImdctFft::<f64>::new(n).unwrap();
imdct.execute_into(&input, &mut output).unwrap();
let control = naive_imdct(&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}, n={i}: got {x}, expected {c}, diff {}",
(x - c).abs()
);
}
}
#[test]
fn test_imdct_size4() {
run_case(4, 1e-10);
}
#[test]
fn test_imdct_size8() {
run_case(8, 1e-10);
}
#[test]
fn test_imdct_size16() {
run_case(16, 1e-10);
}
#[test]
fn test_imdct_odd_fft_lengths() {
for n in [6, 10, 14, 22, 30, 50] {
run_case(n, 1e-10);
}
}
#[test]
fn test_imdct_power_of_two_sizes() {
for shift in 2..=10 {
run_case(1 << shift, 1e-9);
}
}
#[test]
fn test_imdct_multiple_blocks() {
let n = 8;
let input: Vec<f64> = (1..=3 * n).map(|x| x as f64).collect();
let mut output = vec![0.0f64; 6 * n];
let imdct = ImdctFft::<f64>::new(n).unwrap();
imdct.execute_into(&input, &mut output).unwrap();
for block in 0..3 {
let block_in = &input[block * n..(block + 1) * n];
let block_out = &output[block * 2 * n..(block + 1) * 2 * n];
let control = naive_imdct(block_in);
for (i, (&x, &c)) in block_out.iter().zip(control.iter()).enumerate() {
assert!(
(x - c).abs() < 1e-9,
"block {block}, n={i}: got {x}, expected {c}"
);
}
}
}
fn naive_mdct(x: &[f64]) -> Vec<f64> {
let len = x.len();
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 += x[nn] * arg.cos();
}
out[k] = s;
}
out
}
#[test]
fn test_imdct_matches_naive_roundtrip() {
let n = 32;
let x: Vec<f64> = (1..=2 * n).map(|v| v as f64 * 0.5).collect();
let coeffs = naive_mdct(&x);
let mut fast_out = vec![0.0f64; 2 * n];
ImdctFft::<f64>::new(n)
.unwrap()
.execute_into(&coeffs, &mut fast_out)
.unwrap();
let naive_out = naive_imdct(&coeffs);
for (i, (&fa, &na)) in fast_out.iter().zip(naive_out.iter()).enumerate() {
assert!(
(fa - na).abs() < 1e-9 * (1.0 + na.abs()),
"roundtrip n={i}: fast={fa}, naive={na}"
);
}
}
#[test]
fn test_imdct_rejects_odd_n() {
assert!(ImdctFft::<f64>::new(5).is_err());
assert!(ImdctFft::<f64>::new(7).is_err());
}
#[test]
fn test_imdct_rejects_too_small() {
assert!(ImdctFft::<f64>::new(2).is_err());
}
}