feat(audio): implement lock-free audio sample ring buffer

This commit is contained in:
katR 2026-09-29 21:10:02 +05:30
parent f3c5a1eef6
commit 4629456002
3 changed files with 101 additions and 0 deletions

View file

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct AudioRingBuffer {
data: Vec<f32>,
capacity: usize,
read_pos: AtomicUsize,
write_pos: AtomicUsize,
}
impl AudioRingBuffer {
pub fn new(capacity: usize) -> Self {
Self {
data: vec![0.0; capacity],
capacity,
read_pos: AtomicUsize::new(0),
write_pos: AtomicUsize::new(0),
}
}
pub fn available_read(&self) -> usize {
let r = self.read_pos.load(Ordering::Acquire);
let w = self.write_pos.load(Ordering::Acquire);
if w >= r {
w - r
} else {
self.capacity - (r - w)
}
}
pub fn available_write(&self) -> usize {
self.capacity.saturating_sub(self.available_read() + 1)
}
pub fn write(&mut self, samples: &[f32]) -> usize {
let mut written = 0;
let mut w = self.write_pos.load(Ordering::Relaxed);
for &sample in samples {
let next_w = (w + 1) % self.capacity;
let r = self.read_pos.load(Ordering::Acquire);
if next_w == r {
// Buffer full: drop oldest sample to avoid stalling capture
let _ = self.read_pos.fetch_add(1, Ordering::Release);
}
self.data[w] = sample;
w = next_w;
written += 1;
}
self.write_pos.store(w, Ordering::Release);
written
}
pub fn read(&mut self, dest: &mut [f32]) -> usize {
let mut read = 0;
let mut r = self.read_pos.load(Ordering::Relaxed);
let w = self.write_pos.load(Ordering::Acquire);
while r != w && read < dest.len() {
dest[read] = self.data[r];
r = (r + 1) % self.capacity;
read += 1;
}
self.read_pos.store(r, Ordering::Release);
read
}
pub fn clear(&mut self) {
self.read_pos.store(0, Ordering::Release);
self.write_pos.store(0, Ordering::Release);
}
}

View file

@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
pub mod builder; pub mod builder;
pub mod buffer;
pub mod types; pub mod types;

View file

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use molotov_core::audio::buffer::AudioRingBuffer;
#[test]
fn test_ring_buffer_write_and_read() {
let mut buffer = AudioRingBuffer::new(1024);
let samples = [0.1f32, 0.2, 0.3, 0.4];
assert_eq!(buffer.write(&samples), 4);
assert_eq!(buffer.available_read(), 4);
let mut out = [0.0f32; 4];
assert_eq!(buffer.read(&mut out), 4);
assert_eq!(out, samples);
assert_eq!(buffer.available_read(), 0);
}
#[test]
fn test_ring_buffer_underrun_fills_silence() {
let mut buffer = AudioRingBuffer::new(1024);
let mut out = [1.0f32; 4];
// Reading from empty buffer returns 0 read, but caller can zero memory
let read_count = buffer.read(&mut out);
assert_eq!(read_count, 0);
}