pub struct CircularBuffer {
data: Vec<f64>,
head: usize,
mask: usize,
written: usize,
}
impl CircularBuffer {
pub fn new(min_capacity: usize) -> Self {
let capacity = min_capacity.next_power_of_two();
Self {
data: vec![0.0; capacity],
head: 0,
mask: capacity - 1,
written: 0,
}
}
pub fn capacity(&self) -> usize {
self.mask + 1
}
pub fn write(&mut self, samples: &[f64]) {
for (i, &s) in samples.iter().enumerate() {
self.data[(self.head + i) & self.mask] = s;
}
self.head = (self.head + samples.len()) & self.mask;
self.written += samples.len();
}
pub fn get(&self, offset: isize) -> f64 {
self.data[(self.head as isize + offset) as usize & self.mask]
}
pub fn read_into(&self, output: &mut [f64], offset: isize) {
for (i, out) in output.iter_mut().enumerate() {
*out = self.get(offset + i as isize);
}
}
pub fn total_written(&self) -> usize {
self.written
}
pub fn clear(&mut self) {
self.data.fill(0.0);
self.head = 0;
self.written = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capacity_is_power_of_two() {
assert_eq!(CircularBuffer::new(1).capacity(), 1);
assert_eq!(CircularBuffer::new(2).capacity(), 2);
assert_eq!(CircularBuffer::new(3).capacity(), 4);
assert_eq!(CircularBuffer::new(1000).capacity(), 1024);
assert_eq!(CircularBuffer::new(2048).capacity(), 2048);
assert_eq!(CircularBuffer::new(2049).capacity(), 4096);
}
#[test]
fn write_and_read_back() {
let mut buf = CircularBuffer::new(8);
buf.write(&[1.0, 2.0, 3.0]);
assert_eq!(buf.get(-3), 1.0);
assert_eq!(buf.get(-2), 2.0);
assert_eq!(buf.get(-1), 3.0);
}
#[test]
fn wrap_around() {
let mut buf = CircularBuffer::new(4); buf.write(&[1.0, 2.0, 3.0, 4.0]);
buf.write(&[5.0, 6.0]);
assert_eq!(buf.get(-1), 6.0);
assert_eq!(buf.get(-2), 5.0);
assert_eq!(buf.get(-3), 4.0);
assert_eq!(buf.get(-4), 3.0);
}
#[test]
fn read_into_basic() {
let mut buf = CircularBuffer::new(8);
buf.write(&[10.0, 20.0, 30.0, 40.0, 50.0]);
let mut out = [0.0; 3];
buf.read_into(&mut out, -3);
assert_eq!(out, [30.0, 40.0, 50.0]);
}
#[test]
fn read_into_across_wrap() {
let mut buf = CircularBuffer::new(4); buf.write(&[1.0, 2.0, 3.0]);
buf.write(&[4.0, 5.0]);
let mut out = [0.0; 4];
buf.read_into(&mut out, -4);
assert_eq!(out, [2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn total_written_monotonic() {
let mut buf = CircularBuffer::new(4);
assert_eq!(buf.total_written(), 0);
buf.write(&[1.0, 2.0]);
assert_eq!(buf.total_written(), 2);
buf.write(&[3.0, 4.0, 5.0]);
assert_eq!(buf.total_written(), 5);
buf.write(&[6.0, 7.0, 8.0, 9.0, 10.0]);
assert_eq!(buf.total_written(), 10);
}
#[test]
fn clear_resets_state() {
let mut buf = CircularBuffer::new(8);
buf.write(&[1.0, 2.0, 3.0]);
buf.clear();
assert_eq!(buf.total_written(), 0);
assert_eq!(buf.get(0), 0.0);
assert_eq!(buf.get(-1), 0.0);
}
#[test]
fn stft_scenario() {
let n_fft = 8;
let hop_length = 4;
let mut buf = CircularBuffer::new(n_fft);
let chunk: Vec<f64> = (0..16).map(|i| i as f64).collect();
buf.write(&chunk[0..n_fft]);
let mut frames = Vec::new();
let mut frame_buf = vec![0.0; n_fft];
buf.read_into(&mut frame_buf, -(n_fft as isize));
frames.push(frame_buf.clone());
assert_eq!(frames[0], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]);
buf.write(&chunk[n_fft..n_fft + hop_length]);
buf.read_into(&mut frame_buf, -(n_fft as isize));
frames.push(frame_buf.clone());
assert_eq!(frames[1], [4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0]);
buf.write(&chunk[n_fft + hop_length..n_fft + 2 * hop_length]);
buf.read_into(&mut frame_buf, -(n_fft as isize));
frames.push(frame_buf.clone());
assert_eq!(frames[2], [8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0]);
}
}