#[derive(Debug, Clone)]
pub struct SampleRing<const N: usize> {
buf: [i16; N],
head: usize,
len: usize,
overruns: u32,
}
impl<const N: usize> SampleRing<N> {
#[must_use]
pub const fn new() -> Self {
Self {
buf: [0; N],
head: 0,
len: 0,
overruns: 0,
}
}
#[must_use]
pub const fn capacity(&self) -> usize {
N
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn free(&self) -> usize {
N - self.len
}
#[must_use]
pub const fn overruns(&self) -> u32 {
self.overruns
}
pub const fn take_overruns(&mut self) -> u32 {
let n = self.overruns;
self.overruns = 0;
n
}
pub const fn push(&mut self, sample: i16) -> bool {
if self.len >= N {
self.overruns = self.overruns.saturating_add(1);
return false;
}
let tail = (self.head + self.len) % N;
self.buf[tail] = sample;
self.len += 1;
true
}
pub fn push_slice(&mut self, samples: &[i16]) -> usize {
let mut stored = 0;
for &s in samples {
if !self.push(s) {
let rest = samples.len() - stored - 1;
self.overruns = self.overruns.saturating_add(rest as u32);
break;
}
stored += 1;
}
stored
}
pub const fn pop(&mut self) -> Option<i16> {
if self.len == 0 {
return None;
}
let sample = self.buf[self.head];
self.head = (self.head + 1) % N;
self.len -= 1;
Some(sample)
}
pub fn pop_slice(&mut self, out: &mut [i16]) -> usize {
let mut written = 0;
for slot in out.iter_mut() {
match self.pop() {
Some(s) => {
*slot = s;
written += 1;
}
None => break,
}
}
written
}
pub const fn clear(&mut self) {
self.head = 0;
self.len = 0;
}
}
impl<const N: usize> Default for SampleRing<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_ring_is_empty_with_full_capacity() {
let ring: SampleRing<4> = SampleRing::new();
assert!(ring.is_empty());
assert_eq!(ring.len(), 0);
assert_eq!(ring.free(), 4);
assert_eq!(ring.capacity(), 4);
assert_eq!(ring.overruns(), 0);
}
#[test]
fn push_pop_fifo_order() {
let mut ring: SampleRing<4> = SampleRing::new();
assert!(ring.push(10));
assert!(ring.push(-20));
assert!(ring.push(30));
assert_eq!(ring.pop(), Some(10));
assert_eq!(ring.pop(), Some(-20));
assert_eq!(ring.pop(), Some(30));
assert_eq!(ring.pop(), None);
}
#[test]
fn wraparound_preserves_order_across_many_cycles() {
let mut ring: SampleRing<5> = SampleRing::new();
let mut next_in: i16 = 0;
let mut next_out: i16 = 0;
for _ in 0..40 {
assert_eq!(ring.push_slice(&[next_in, next_in + 1, next_in + 2]), 3);
next_in += 3;
let mut out = [0i16; 3];
assert_eq!(ring.pop_slice(&mut out), 3);
assert_eq!(out, [next_out, next_out + 1, next_out + 2]);
next_out += 3;
}
assert!(ring.is_empty());
assert_eq!(ring.overruns(), 0);
}
#[test]
fn overrun_drops_newest_and_counts() {
let mut ring: SampleRing<3> = SampleRing::new();
assert_eq!(ring.push_slice(&[1, 2, 3]), 3);
assert!(!ring.push(4));
assert_eq!(ring.overruns(), 1);
assert_eq!(ring.push_slice(&[5, 6]), 0);
assert_eq!(ring.overruns(), 3);
let mut out = [0i16; 3];
assert_eq!(ring.pop_slice(&mut out), 3);
assert_eq!(out, [1, 2, 3]);
assert_eq!(ring.take_overruns(), 3);
assert_eq!(ring.overruns(), 0);
}
#[test]
fn partial_slice_push_stores_prefix() {
let mut ring: SampleRing<4> = SampleRing::new();
assert!(ring.push(9));
assert_eq!(ring.push_slice(&[1, 2, 3, 4, 5]), 3);
assert_eq!(ring.overruns(), 2);
let mut out = [0i16; 8];
assert_eq!(ring.pop_slice(&mut out), 4);
assert_eq!(&out[..4], &[9, 1, 2, 3]);
}
#[test]
fn pop_slice_with_short_output_leaves_remainder() {
let mut ring: SampleRing<8> = SampleRing::new();
assert_eq!(ring.push_slice(&[1, 2, 3, 4, 5]), 5);
let mut out = [0i16; 2];
assert_eq!(ring.pop_slice(&mut out), 2);
assert_eq!(out, [1, 2]);
assert_eq!(ring.len(), 3);
assert_eq!(ring.pop(), Some(3));
}
#[test]
fn clear_empties_but_keeps_overruns() {
let mut ring: SampleRing<2> = SampleRing::new();
assert_eq!(ring.push_slice(&[1, 2, 3]), 2);
assert_eq!(ring.overruns(), 1);
ring.clear();
assert!(ring.is_empty());
assert_eq!(ring.free(), 2);
assert_eq!(ring.overruns(), 1);
assert!(ring.push(7));
assert_eq!(ring.pop(), Some(7));
}
#[test]
fn const_constructible_in_static_position() {
const RING: SampleRing<16> = SampleRing::new();
assert_eq!(RING.capacity(), 16);
assert!(RING.is_empty());
}
}