use rustfft::num_complex::Complex;
use wide::f32x8;
const LANES: usize = 8;
#[derive(Clone, Debug, Default)]
pub struct SplitSpectrum {
pub re: Vec<f32>,
pub im: Vec<f32>,
}
impl SplitSpectrum {
pub fn zeros(len: usize) -> Self {
Self {
re: vec![0.0f32; len],
im: vec![0.0f32; len],
}
}
pub fn from_complex(src: &[Complex<f32>]) -> Self {
let mut s = Self::zeros(src.len());
s.copy_from_complex(src);
s
}
#[inline]
pub fn len(&self) -> usize {
self.re.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.re.is_empty()
}
#[inline]
pub fn copy_from_complex(&mut self, src: &[Complex<f32>]) {
debug_assert_eq!(
src.len(),
self.re.len(),
"copy_from_complex: length mismatch"
);
for (i, c) in src.iter().enumerate() {
self.re[i] = c.re;
self.im[i] = c.im;
}
}
#[inline]
pub fn write_interleaved(&self, dst: &mut [Complex<f32>]) {
debug_assert_eq!(
dst.len(),
self.re.len(),
"write_interleaved: length mismatch"
);
for (i, c) in dst.iter_mut().enumerate() {
c.re = self.re[i];
c.im = self.im[i];
}
}
#[inline]
pub fn fill_zero(&mut self) {
self.re.fill(0.0);
self.im.fill(0.0);
}
}
#[inline(always)]
fn load8(s: &[f32]) -> f32x8 {
f32x8::from(<[f32; LANES]>::try_from(s).expect("load8: slice must be LANES long"))
}
#[inline]
fn complex_mac_split(
acc_re: &mut [f32],
acc_im: &mut [f32],
slot_re: &[f32],
slot_im: &[f32],
ir_re: &[f32],
ir_im: &[f32],
) {
let n = acc_re.len();
let chunks = n / LANES;
for c in 0..chunks {
let i = c * LANES;
let sr = load8(&slot_re[i..i + LANES]);
let si = load8(&slot_im[i..i + LANES]);
let hr = load8(&ir_re[i..i + LANES]);
let hi = load8(&ir_im[i..i + LANES]);
let ar = load8(&acc_re[i..i + LANES]);
let ai = load8(&acc_im[i..i + LANES]);
let out_re = ar + sr * hr - si * hi;
let out_im = ai + sr * hi + si * hr;
acc_re[i..i + LANES].copy_from_slice(&out_re.to_array());
acc_im[i..i + LANES].copy_from_slice(&out_im.to_array());
}
for i in (chunks * LANES)..n {
let out_re = acc_re[i] + slot_re[i] * ir_re[i] - slot_im[i] * ir_im[i];
let out_im = acc_im[i] + slot_re[i] * ir_im[i] + slot_im[i] * ir_re[i];
acc_re[i] = out_re;
acc_im[i] = out_im;
}
}
pub struct FreqDomainDelayLine {
slots: Vec<SplitSpectrum>,
write_pos: usize,
spectrum_size: usize,
}
impl FreqDomainDelayLine {
pub fn new(num_partitions: usize, spectrum_size: usize) -> Self {
assert!(num_partitions > 0, "num_partitions must be > 0");
assert!(spectrum_size > 0, "spectrum_size must be > 0");
Self {
slots: vec![SplitSpectrum::zeros(spectrum_size); num_partitions],
write_pos: 0,
spectrum_size,
}
}
#[inline]
pub fn num_partitions(&self) -> usize {
self.slots.len()
}
#[inline]
pub fn spectrum_size(&self) -> usize {
self.spectrum_size
}
pub fn push(&mut self, spectrum: &[Complex<f32>]) {
debug_assert_eq!(
spectrum.len(),
self.spectrum_size,
"push: spectrum length must equal spectrum_size"
);
self.slots[self.write_pos].copy_from_complex(spectrum);
self.write_pos = (self.write_pos + 1) % self.slots.len();
}
pub fn accumulate(&self, ir_spectra: &[SplitSpectrum], acc: &mut SplitSpectrum) {
debug_assert_eq!(
ir_spectra.len(),
self.slots.len(),
"accumulate: ir_spectra.len() must equal num_partitions"
);
debug_assert_eq!(
acc.len(),
self.spectrum_size,
"accumulate: acc.len() must equal spectrum_size"
);
let p = self.slots.len();
let (acc_re, acc_im) = (&mut acc.re, &mut acc.im);
for (k, ir) in ir_spectra.iter().enumerate() {
let slot_idx = (self.write_pos + p - 1 - k) % p;
let slot = &self.slots[slot_idx];
complex_mac_split(acc_re, acc_im, &slot.re, &slot.im, &ir.re, &ir.im);
}
}
pub fn reset(&mut self) {
for slot in &mut self.slots {
slot.fill_zero();
}
self.write_pos = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 1e-6;
fn const_spec(spectrum_size: usize, value: Complex<f32>) -> SplitSpectrum {
SplitSpectrum::from_complex(&vec![value; spectrum_size])
}
fn identity_ir(num_partitions: usize, spectrum_size: usize) -> Vec<SplitSpectrum> {
vec![const_spec(spectrum_size, Complex::new(1.0, 0.0)); num_partitions]
}
fn push_complex(fdl: &mut FreqDomainDelayLine, values: &[Complex<f32>]) {
fdl.push(values);
}
#[test]
fn split_spectrum_round_trips_complex() {
let src = vec![
Complex::new(1.0, 2.0),
Complex::new(-3.0, 4.0),
Complex::new(5.5, -6.5),
];
let split = SplitSpectrum::from_complex(&src);
assert_eq!(split.len(), 3);
assert_eq!(split.re, vec![1.0, -3.0, 5.5]);
assert_eq!(split.im, vec![2.0, 4.0, -6.5]);
let mut back = vec![Complex::default(); 3];
split.write_interleaved(&mut back);
assert_eq!(back, src);
}
#[test]
fn new_fdl_is_all_zeros() {
let fdl = FreqDomainDelayLine::new(3, 8);
let ir = identity_ir(3, 8);
let mut acc = SplitSpectrum::zeros(8);
fdl.accumulate(&ir, &mut acc);
for (re, im) in acc.re.iter().zip(acc.im.iter()) {
assert!(re.abs() < EPSILON && im.abs() < EPSILON, "expected zero");
}
}
#[test]
fn accessors_report_construction_params() {
let fdl = FreqDomainDelayLine::new(5, 16);
assert_eq!(fdl.num_partitions(), 5);
assert_eq!(fdl.spectrum_size(), 16);
}
#[test]
fn single_push_identity_ir() {
let spectrum_size = 4;
let mut fdl = FreqDomainDelayLine::new(1, spectrum_size);
let pushed = [
Complex::new(1.0, 2.0),
Complex::new(3.0, 4.0),
Complex::new(5.0, 6.0),
Complex::new(7.0, 8.0),
];
push_complex(&mut fdl, &pushed);
let ir = identity_ir(1, spectrum_size);
let mut acc = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc);
for (i, &p) in pushed.iter().enumerate() {
assert!((acc.re[i] - p.re).abs() < EPSILON, "re[{i}]");
assert!((acc.im[i] - p.im).abs() < EPSILON, "im[{i}]");
}
}
#[test]
fn delay_ordering_most_recent_is_slot_zero() {
let spectrum_size = 4;
let mut fdl = FreqDomainDelayLine::new(2, spectrum_size);
let a = [
Complex::new(10.0, 0.0),
Complex::new(20.0, 0.0),
Complex::new(0.0, 0.0),
Complex::new(0.0, 0.0),
];
let b = [
Complex::new(30.0, 0.0),
Complex::new(40.0, 0.0),
Complex::new(0.0, 0.0),
Complex::new(0.0, 0.0),
];
push_complex(&mut fdl, &a); push_complex(&mut fdl, &b);
let ir = vec![
SplitSpectrum::from_complex(&[
Complex::new(1.0, 0.0),
Complex::new(0.0, 0.0),
Complex::new(0.0, 0.0),
Complex::new(0.0, 0.0),
]),
SplitSpectrum::from_complex(&[
Complex::new(0.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(0.0, 0.0),
Complex::new(0.0, 0.0),
]),
];
let mut acc = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc);
assert!(
(acc.re[0] - 30.0).abs() < EPSILON,
"acc.re[0]={}",
acc.re[0]
);
assert!(
(acc.re[1] - 20.0).abs() < EPSILON,
"acc.re[1]={}",
acc.re[1]
);
assert!(acc.im[0].abs() < EPSILON);
assert!(acc.im[1].abs() < EPSILON);
}
#[test]
fn circular_wrap_evicts_oldest_slot() {
let spectrum_size = 2;
let mut fdl = FreqDomainDelayLine::new(2, spectrum_size);
let a = vec![Complex::new(100.0, 0.0); spectrum_size];
let b = vec![Complex::new(2.0, 0.0); spectrum_size];
let c = vec![Complex::new(3.0, 0.0); spectrum_size];
push_complex(&mut fdl, &a); push_complex(&mut fdl, &b); push_complex(&mut fdl, &c);
let ir = identity_ir(2, spectrum_size);
let mut acc = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc);
for &re in &acc.re {
assert!((re - 5.0).abs() < EPSILON, "expected 5.0, got {re}");
}
}
#[test]
fn complex_multiply_accumulate() {
let spectrum_size = 1;
let mut fdl = FreqDomainDelayLine::new(1, spectrum_size);
push_complex(&mut fdl, &[Complex::new(3.0, 4.0)]);
let ir = vec![SplitSpectrum::from_complex(&[Complex::new(2.0, 1.0)])];
let mut acc = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc);
assert!((acc.re[0] - 2.0).abs() < EPSILON, "re={}", acc.re[0]);
assert!((acc.im[0] - 11.0).abs() < EPSILON, "im={}", acc.im[0]);
}
#[test]
fn accumulate_adds_to_existing_acc() {
let spectrum_size = 2;
let mut fdl = FreqDomainDelayLine::new(1, spectrum_size);
push_complex(&mut fdl, &vec![Complex::new(1.0, 0.0); spectrum_size]);
let ir = identity_ir(1, spectrum_size);
let mut acc = const_spec(spectrum_size, Complex::new(5.0, 0.0));
fdl.accumulate(&ir, &mut acc);
for &re in &acc.re {
assert!((re - 6.0).abs() < EPSILON, "expected 6.0, got {re}");
}
}
#[test]
fn simd_kernel_matches_scalar_reference_with_tail() {
let spectrum_size = 13;
let mut fdl = FreqDomainDelayLine::new(1, spectrum_size);
let slot: Vec<Complex<f32>> = (0..spectrum_size)
.map(|i| Complex::new(i as f32 * 0.5 - 2.0, 1.0 - i as f32 * 0.25))
.collect();
let ir_c: Vec<Complex<f32>> = (0..spectrum_size)
.map(|i| Complex::new(0.3 * i as f32, -0.7 + i as f32 * 0.1))
.collect();
push_complex(&mut fdl, &slot);
let ir = vec![SplitSpectrum::from_complex(&ir_c)];
let mut acc = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc);
for i in 0..spectrum_size {
let expected = slot[i] * ir_c[i];
assert!((acc.re[i] - expected.re).abs() < EPSILON, "re[{i}]");
assert!((acc.im[i] - expected.im).abs() < EPSILON, "im[{i}]");
}
}
#[test]
fn reset_zeros_all_slots() {
let spectrum_size = 4;
let mut fdl = FreqDomainDelayLine::new(2, spectrum_size);
push_complex(&mut fdl, &vec![Complex::new(9.0, 9.0); spectrum_size]);
push_complex(&mut fdl, &vec![Complex::new(9.0, 9.0); spectrum_size]);
fdl.reset();
let ir = identity_ir(2, spectrum_size);
let mut acc = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc);
for (re, im) in acc.re.iter().zip(acc.im.iter()) {
assert!(
re.abs() < EPSILON && im.abs() < EPSILON,
"expected zero after reset"
);
}
}
#[test]
fn reset_produces_same_output_as_fresh_fdl() {
let spectrum_size = 4;
let p = 2;
let mut fdl = FreqDomainDelayLine::new(p, spectrum_size);
let mut fresh = FreqDomainDelayLine::new(p, spectrum_size);
let spec_a = vec![Complex::new(3.0, 1.0); spectrum_size];
let spec_b = vec![Complex::new(7.0, 2.0); spectrum_size];
push_complex(&mut fdl, &vec![Complex::new(99.0, 99.0); spectrum_size]);
push_complex(&mut fdl, &vec![Complex::new(99.0, 99.0); spectrum_size]);
fdl.reset();
push_complex(&mut fdl, &spec_a);
push_complex(&mut fresh, &spec_a);
push_complex(&mut fdl, &spec_b);
push_complex(&mut fresh, &spec_b);
let ir = identity_ir(p, spectrum_size);
let mut acc_fdl = SplitSpectrum::zeros(spectrum_size);
let mut acc_fresh = SplitSpectrum::zeros(spectrum_size);
fdl.accumulate(&ir, &mut acc_fdl);
fresh.accumulate(&ir, &mut acc_fresh);
for i in 0..spectrum_size {
assert!(
(acc_fdl.re[i] - acc_fresh.re[i]).abs() < EPSILON
&& (acc_fdl.im[i] - acc_fresh.im[i]).abs() < EPSILON,
"reset FDL diverged from fresh at bin {i}"
);
}
}
}