use audio_core_bsd::{
AudioFrame, AudioNode, PortDescriptor, PortDirection, ProcessContext, SampleFormat,
};
pub struct RingSource {
consumer: rtrb::Consumer<AudioFrame>,
out_port: [PortDescriptor; 1],
last: AudioFrame,
}
impl RingSource {
#[must_use]
pub fn new(
consumer: rtrb::Consumer<AudioFrame>,
channels: u16,
sample_rate: u32,
num_frames: usize,
) -> Self {
Self {
consumer,
out_port: [PortDescriptor::new(
PortDirection::Output,
channels,
SampleFormat::F32,
)],
last: AudioFrame::silence(channels, num_frames, sample_rate),
}
}
#[must_use]
pub fn consumer(&self) -> &rtrb::Consumer<AudioFrame> {
&self.consumer
}
#[must_use]
pub fn last(&self) -> &AudioFrame {
&self.last
}
}
impl AudioNode for RingSource {
fn inputs(&self) -> &[PortDescriptor] {
&[]
}
fn outputs(&self) -> &[PortDescriptor] {
&self.out_port
}
fn process(
&mut self,
_ctx: &mut ProcessContext,
_in_frames: &[AudioFrame],
out_frames: &mut [AudioFrame],
) {
let Some(out) = out_frames.get_mut(0) else {
return;
};
if let Ok(frame) = self.consumer.pop() {
out.channels = frame.channels;
out.sample_rate = frame.sample_rate;
let n = frame.samples.len().min(out.samples.len());
out.samples[..n].copy_from_slice(&frame.samples[..n]);
self.last.channels = frame.channels;
self.last.sample_rate = frame.sample_rate;
let cn = frame.samples.len().min(self.last.samples.len());
self.last.samples[..cn].copy_from_slice(&frame.samples[..cn]);
} else {
out.channels = self.last.channels;
out.sample_rate = self.last.sample_rate;
let n = self.last.samples.len().min(out.samples.len());
out.samples[..n].copy_from_slice(&self.last.samples[..n]);
}
}
}
pub struct RingSink {
producer: rtrb::Producer<AudioFrame>,
in_port: [PortDescriptor; 1],
stash: AudioFrame,
}
impl RingSink {
#[must_use]
pub fn new(
producer: rtrb::Producer<AudioFrame>,
channels: u16,
sample_rate: u32,
num_frames: usize,
) -> Self {
Self {
producer,
in_port: [PortDescriptor::new(
PortDirection::Input,
channels,
SampleFormat::F32,
)],
stash: AudioFrame::silence(channels, num_frames, sample_rate),
}
}
pub fn flush(&mut self) -> Result<(), rtrb::PushError<AudioFrame>> {
self.producer.push(self.stash.clone())
}
#[must_use]
pub fn producer(&self) -> &rtrb::Producer<AudioFrame> {
&self.producer
}
#[must_use]
pub fn stash(&self) -> &AudioFrame {
&self.stash
}
}
impl AudioNode for RingSink {
fn inputs(&self) -> &[PortDescriptor] {
&self.in_port
}
fn outputs(&self) -> &[PortDescriptor] {
&[]
}
fn process(
&mut self,
_ctx: &mut ProcessContext,
in_frames: &[AudioFrame],
_out_frames: &mut [AudioFrame],
) {
let Some(inp) = in_frames.first() else {
return;
};
self.stash.channels = inp.channels;
self.stash.sample_rate = inp.sample_rate;
let n = inp.samples.len().min(self.stash.samples.len());
self.stash.samples[..n].copy_from_slice(&inp.samples[..n]);
}
}
#[cfg(test)]
mod tests {
use super::*;
use audio_core_bsd::{AudioNode, PortDirection, ProcessContext};
fn approx_eq(a: f32, b: f32) -> bool {
(a - b).abs() < 1e-6
}
#[test]
fn ring_source_outputs_one_port_and_no_inputs() {
let (_producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
let src = RingSource::new(consumer, 1, 48_000, 8);
assert_eq!(src.inputs().len(), 0);
assert_eq!(src.outputs().len(), 1);
assert_eq!(src.outputs()[0].direction, PortDirection::Output);
assert_eq!(src.outputs()[0].channels, 1);
assert_eq!(src.outputs()[0].sample_format, SampleFormat::F32);
}
#[test]
fn ring_sink_inputs_one_port_and_no_outputs() {
let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
let sink = RingSink::new(producer, 2, 48_000, 8);
assert_eq!(sink.inputs().len(), 1);
assert_eq!(sink.outputs().len(), 0);
assert_eq!(sink.inputs()[0].channels, 2);
assert_eq!(sink.inputs()[0].direction, PortDirection::Input);
}
#[test]
fn ring_source_pops_and_outputs_frame() {
let (mut producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
producer
.push(AudioFrame::from_planar(1, 48_000, vec![0.5; 8]))
.unwrap();
let mut src = RingSource::new(consumer, 1, 48_000, 8);
let mut ctx = ProcessContext::new(8, 0, 48_000);
let mut out = [AudioFrame::silence(1, 8, 48_000)];
src.process(&mut ctx, &[], &mut out);
assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.5)));
assert!(src.last().samples.iter().all(|&s| approx_eq(s, 0.5)));
}
#[test]
fn ring_source_repeats_last_when_empty() {
let (_producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
let mut src = RingSource::new(consumer, 1, 48_000, 8);
let mut ctx = ProcessContext::new(8, 0, 48_000);
let mut out = [AudioFrame::silence(1, 8, 48_000)];
for s in &mut out[0].samples {
*s = 9.0;
}
src.process(&mut ctx, &[], &mut out);
assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.0)));
}
#[test]
fn ring_source_holds_after_underrun() {
let (mut producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
producer
.push(AudioFrame::from_planar(1, 48_000, vec![0.7; 4]))
.unwrap();
let mut src = RingSource::new(consumer, 1, 48_000, 4);
let mut ctx = ProcessContext::new(4, 0, 48_000);
let mut out = [AudioFrame::silence(1, 4, 48_000)];
src.process(&mut ctx, &[], &mut out); assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.7)));
src.process(&mut ctx, &[], &mut out);
assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.7)));
}
#[test]
fn ring_sink_stashes_input() {
let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
let mut sink = RingSink::new(producer, 1, 48_000, 8);
let inp = [AudioFrame::from_planar(1, 48_000, vec![0.25; 8])];
let mut ctx = ProcessContext::new(8, 0, 48_000);
let mut out_dummy: [AudioFrame; 0] = [];
sink.process(&mut ctx, &inp, &mut out_dummy);
assert!(sink.stash().samples.iter().all(|&s| approx_eq(s, 0.25)));
}
#[test]
fn ring_sink_flush_pushes_to_consumer() {
let (producer, mut consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
let mut sink = RingSink::new(producer, 1, 48_000, 4);
let inp = [AudioFrame::from_planar(1, 48_000, vec![1.0; 4])];
let mut ctx = ProcessContext::new(4, 0, 48_000);
let mut out_dummy: [AudioFrame; 0] = [];
sink.process(&mut ctx, &inp, &mut out_dummy);
sink.flush().unwrap();
let popped = consumer.pop().unwrap();
assert!(popped.samples.iter().all(|&s| approx_eq(s, 1.0)));
}
#[test]
fn ring_sink_flush_full_ring_errors() {
let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(1);
let mut sink = RingSink::new(producer, 1, 48_000, 2);
sink.flush().unwrap();
assert!(sink.flush().is_err());
}
#[test]
fn ring_source_last_starts_silent() {
let (_producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(2);
let src = RingSource::new(consumer, 1, 48_000, 4);
assert_eq!(src.last().channels, 1);
assert_eq!(src.last().sample_rate, 48_000);
assert!(src.last().samples.iter().all(|&s| approx_eq(s, 0.0)));
}
#[test]
fn ring_sink_stash_starts_silent() {
let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(2);
let sink = RingSink::new(producer, 2, 44_100, 4);
assert_eq!(sink.stash().channels, 2);
assert_eq!(sink.stash().sample_rate, 44_100);
assert_eq!(sink.stash().samples.len(), 8);
assert!(sink.stash().samples.iter().all(|&s| approx_eq(s, 0.0)));
}
}