use std::collections::VecDeque;
use num_complex::Complex;
pub struct IqAccumulator {
buffer: VecDeque<Complex<f64>>,
block_size: usize,
}
impl IqAccumulator {
pub fn new(block_size: usize) -> Self {
debug_assert!(block_size > 0, "block_size must be > 0");
Self {
buffer: VecDeque::with_capacity(block_size * 4),
block_size,
}
}
pub fn set_block_size(&mut self, block_size: usize) {
debug_assert!(block_size > 0, "block_size must be > 0");
self.block_size = block_size;
self.buffer.clear();
}
pub fn push(&mut self, samples: &[Complex<f64>]) {
self.buffer.extend(samples.iter().copied());
let high_water = self.block_size * 16;
if self.buffer.len() > high_water {
let excess = self.buffer.len() - high_water;
log::warn!(
"IqAccumulator overflow: {} samples buffered (cap {}), draining {} excess",
self.buffer.len(),
high_water,
excess
);
self.buffer.drain(..excess);
}
}
pub fn next_block(&mut self, out: &mut [Complex<f64>]) -> bool {
debug_assert!(
out.len() >= self.block_size,
"IqAccumulator::next_block: out buffer length {} < block_size {}",
out.len(),
self.block_size
);
if self.buffer.len() < self.block_size {
return false;
}
let (a, b) = self.buffer.as_slices();
if a.len() >= self.block_size {
out[..self.block_size].copy_from_slice(&a[..self.block_size]);
} else {
let from_a = a.len();
out[..from_a].copy_from_slice(a);
out[from_a..self.block_size].copy_from_slice(&b[..self.block_size - from_a]);
}
self.buffer.drain(..self.block_size);
true
}
pub fn clear(&mut self) {
self.buffer.clear();
}
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c(re: f64) -> Complex<f64> {
Complex::new(re, 0.0)
}
#[test]
fn yields_complete_blocks() {
let block_size = 4;
let mut acc = IqAccumulator::new(block_size);
let mut out = vec![Complex::default(); block_size];
acc.push(&[c(1.0), c(2.0), c(3.0)]);
assert!(
!acc.next_block(&mut out),
"should not yield block with only 3 samples"
);
acc.push(&[c(4.0), c(5.0)]);
assert!(
acc.next_block(&mut out),
"should yield block with 5 samples buffered"
);
assert_eq!(out, vec![c(1.0), c(2.0), c(3.0), c(4.0)]);
assert_eq!(acc.len(), 1);
}
#[test]
fn clear_empties_buffer() {
let block_size = 4;
let mut acc = IqAccumulator::new(block_size);
let mut out = vec![Complex::default(); block_size];
acc.push(&[c(1.0), c(2.0)]);
acc.clear();
assert!(acc.is_empty());
assert!(!acc.next_block(&mut out));
}
#[test]
fn set_block_size_clears_buffer() {
let mut acc = IqAccumulator::new(4);
acc.push(&[c(1.0), c(2.0)]);
acc.set_block_size(2);
assert!(acc.is_empty(), "set_block_size must clear the buffer");
let mut out = vec![Complex::default(); 2];
acc.push(&[c(10.0), c(20.0)]);
assert!(acc.next_block(&mut out));
assert_eq!(out, vec![c(10.0), c(20.0)]);
}
#[test]
fn overflow_drains_excess() {
let block_size = 4;
let mut acc = IqAccumulator::new(block_size);
let samples: Vec<Complex<f64>> = (0..(block_size * 17) as i64)
.map(|i| Complex::new(i as f64, 0.0))
.collect();
acc.push(&samples);
assert_eq!(
acc.len(),
block_size * 16,
"overflow should drain to exactly high_water, got {}",
acc.len()
);
}
#[test]
fn split_deque_block_copy() {
let block_size = 4;
let mut acc = IqAccumulator::new(block_size);
let mut out = vec![Complex::default(); block_size];
acc.push(&[c(1.0), c(2.0), c(3.0), c(4.0), c(5.0)]);
assert!(acc.next_block(&mut out));
assert_eq!(out, vec![c(1.0), c(2.0), c(3.0), c(4.0)]);
assert_eq!(acc.len(), 1);
acc.push(&[c(6.0), c(7.0), c(8.0)]);
assert_eq!(acc.len(), 4);
assert!(acc.next_block(&mut out));
assert_eq!(out, vec![c(5.0), c(6.0), c(7.0), c(8.0)]);
assert_eq!(acc.len(), 0);
}
}