1use audio_core_bsd::{
11 AudioFrame, AudioNode, PortDescriptor, PortDirection, ProcessContext, SampleFormat,
12};
13
14pub struct RingSource {
29 consumer: rtrb::Consumer<AudioFrame>,
31 out_port: [PortDescriptor; 1],
33 last: AudioFrame,
35}
36
37impl RingSource {
38 #[must_use]
44 pub fn new(
45 consumer: rtrb::Consumer<AudioFrame>,
46 channels: u16,
47 sample_rate: u32,
48 num_frames: usize,
49 ) -> Self {
50 Self {
51 consumer,
52 out_port: [PortDescriptor::new(
53 PortDirection::Output,
54 channels,
55 SampleFormat::F32,
56 )],
57 last: AudioFrame::silence(channels, num_frames, sample_rate),
58 }
59 }
60
61 #[must_use]
63 pub fn consumer(&self) -> &rtrb::Consumer<AudioFrame> {
64 &self.consumer
65 }
66
67 #[must_use]
69 pub fn last(&self) -> &AudioFrame {
70 &self.last
71 }
72}
73
74impl AudioNode for RingSource {
75 fn inputs(&self) -> &[PortDescriptor] {
76 &[]
77 }
78 fn outputs(&self) -> &[PortDescriptor] {
79 &self.out_port
80 }
81 fn process(
82 &mut self,
83 _ctx: &mut ProcessContext,
84 _in_frames: &[AudioFrame],
85 out_frames: &mut [AudioFrame],
86 ) {
87 let Some(out) = out_frames.get_mut(0) else {
88 return;
89 };
90 if let Ok(frame) = self.consumer.pop() {
93 out.channels = frame.channels;
94 out.sample_rate = frame.sample_rate;
95 let n = frame.samples.len().min(out.samples.len());
96 out.samples[..n].copy_from_slice(&frame.samples[..n]);
97
98 self.last.channels = frame.channels;
100 self.last.sample_rate = frame.sample_rate;
101 let cn = frame.samples.len().min(self.last.samples.len());
102 self.last.samples[..cn].copy_from_slice(&frame.samples[..cn]);
103 } else {
104 out.channels = self.last.channels;
106 out.sample_rate = self.last.sample_rate;
107 let n = self.last.samples.len().min(out.samples.len());
108 out.samples[..n].copy_from_slice(&self.last.samples[..n]);
109 }
110 }
111}
112
113pub struct RingSink {
130 producer: rtrb::Producer<AudioFrame>,
132 in_port: [PortDescriptor; 1],
134 stash: AudioFrame,
136}
137
138impl RingSink {
139 #[must_use]
144 pub fn new(
145 producer: rtrb::Producer<AudioFrame>,
146 channels: u16,
147 sample_rate: u32,
148 num_frames: usize,
149 ) -> Self {
150 Self {
151 producer,
152 in_port: [PortDescriptor::new(
153 PortDirection::Input,
154 channels,
155 SampleFormat::F32,
156 )],
157 stash: AudioFrame::silence(channels, num_frames, sample_rate),
158 }
159 }
160
161 pub fn flush(&mut self) -> Result<(), rtrb::PushError<AudioFrame>> {
172 self.producer.push(self.stash.clone())
173 }
174
175 #[must_use]
177 pub fn producer(&self) -> &rtrb::Producer<AudioFrame> {
178 &self.producer
179 }
180
181 #[must_use]
183 pub fn stash(&self) -> &AudioFrame {
184 &self.stash
185 }
186}
187
188impl AudioNode for RingSink {
189 fn inputs(&self) -> &[PortDescriptor] {
190 &self.in_port
191 }
192 fn outputs(&self) -> &[PortDescriptor] {
193 &[]
194 }
195 fn process(
196 &mut self,
197 _ctx: &mut ProcessContext,
198 in_frames: &[AudioFrame],
199 _out_frames: &mut [AudioFrame],
200 ) {
201 let Some(inp) = in_frames.first() else {
202 return;
203 };
204 self.stash.channels = inp.channels;
206 self.stash.sample_rate = inp.sample_rate;
207 let n = inp.samples.len().min(self.stash.samples.len());
208 self.stash.samples[..n].copy_from_slice(&inp.samples[..n]);
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use audio_core_bsd::{AudioNode, PortDirection, ProcessContext};
216
217 fn approx_eq(a: f32, b: f32) -> bool {
219 (a - b).abs() < 1e-6
220 }
221
222 #[test]
223 fn ring_source_outputs_one_port_and_no_inputs() {
224 let (_producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
225 let src = RingSource::new(consumer, 1, 48_000, 8);
226 assert_eq!(src.inputs().len(), 0);
227 assert_eq!(src.outputs().len(), 1);
228 assert_eq!(src.outputs()[0].direction, PortDirection::Output);
229 assert_eq!(src.outputs()[0].channels, 1);
230 assert_eq!(src.outputs()[0].sample_format, SampleFormat::F32);
231 }
232
233 #[test]
234 fn ring_sink_inputs_one_port_and_no_outputs() {
235 let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
236 let sink = RingSink::new(producer, 2, 48_000, 8);
237 assert_eq!(sink.inputs().len(), 1);
238 assert_eq!(sink.outputs().len(), 0);
239 assert_eq!(sink.inputs()[0].channels, 2);
240 assert_eq!(sink.inputs()[0].direction, PortDirection::Input);
241 }
242
243 #[test]
244 fn ring_source_pops_and_outputs_frame() {
245 let (mut producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
246 producer
247 .push(AudioFrame::from_planar(1, 48_000, vec![0.5; 8]))
248 .unwrap();
249
250 let mut src = RingSource::new(consumer, 1, 48_000, 8);
251 let mut ctx = ProcessContext::new(8, 0, 48_000);
252 let mut out = [AudioFrame::silence(1, 8, 48_000)];
253 src.process(&mut ctx, &[], &mut out);
254 assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.5)));
255 assert!(src.last().samples.iter().all(|&s| approx_eq(s, 0.5)));
257 }
258
259 #[test]
260 fn ring_source_repeats_last_when_empty() {
261 let (_producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
262 let mut src = RingSource::new(consumer, 1, 48_000, 8);
263 let mut ctx = ProcessContext::new(8, 0, 48_000);
264 let mut out = [AudioFrame::silence(1, 8, 48_000)];
265 for s in &mut out[0].samples {
267 *s = 9.0;
268 }
269 src.process(&mut ctx, &[], &mut out);
271 assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.0)));
272 }
273
274 #[test]
275 fn ring_source_holds_after_underrun() {
276 let (mut producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
278 producer
279 .push(AudioFrame::from_planar(1, 48_000, vec![0.7; 4]))
280 .unwrap();
281 let mut src = RingSource::new(consumer, 1, 48_000, 4);
282 let mut ctx = ProcessContext::new(4, 0, 48_000);
283 let mut out = [AudioFrame::silence(1, 4, 48_000)];
284 src.process(&mut ctx, &[], &mut out); assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.7)));
286 src.process(&mut ctx, &[], &mut out);
288 assert!(out[0].samples.iter().all(|&s| approx_eq(s, 0.7)));
289 }
290
291 #[test]
292 fn ring_sink_stashes_input() {
293 let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
294 let mut sink = RingSink::new(producer, 1, 48_000, 8);
295 let inp = [AudioFrame::from_planar(1, 48_000, vec![0.25; 8])];
296 let mut ctx = ProcessContext::new(8, 0, 48_000);
297 let mut out_dummy: [AudioFrame; 0] = [];
298 sink.process(&mut ctx, &inp, &mut out_dummy);
299 assert!(sink.stash().samples.iter().all(|&s| approx_eq(s, 0.25)));
300 }
301
302 #[test]
303 fn ring_sink_flush_pushes_to_consumer() {
304 let (producer, mut consumer) = rtrb::RingBuffer::<AudioFrame>::new(4);
305 let mut sink = RingSink::new(producer, 1, 48_000, 4);
306 let inp = [AudioFrame::from_planar(1, 48_000, vec![1.0; 4])];
307 let mut ctx = ProcessContext::new(4, 0, 48_000);
308 let mut out_dummy: [AudioFrame; 0] = [];
309 sink.process(&mut ctx, &inp, &mut out_dummy);
310 sink.flush().unwrap();
311 let popped = consumer.pop().unwrap();
312 assert!(popped.samples.iter().all(|&s| approx_eq(s, 1.0)));
313 }
314
315 #[test]
316 fn ring_sink_flush_full_ring_errors() {
317 let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(1);
318 let mut sink = RingSink::new(producer, 1, 48_000, 2);
319 sink.flush().unwrap();
321 assert!(sink.flush().is_err());
323 }
324
325 #[test]
326 fn ring_source_last_starts_silent() {
327 let (_producer, consumer) = rtrb::RingBuffer::<AudioFrame>::new(2);
328 let src = RingSource::new(consumer, 1, 48_000, 4);
329 assert_eq!(src.last().channels, 1);
330 assert_eq!(src.last().sample_rate, 48_000);
331 assert!(src.last().samples.iter().all(|&s| approx_eq(s, 0.0)));
332 }
333
334 #[test]
335 fn ring_sink_stash_starts_silent() {
336 let (producer, _consumer) = rtrb::RingBuffer::<AudioFrame>::new(2);
337 let sink = RingSink::new(producer, 2, 44_100, 4);
338 assert_eq!(sink.stash().channels, 2);
339 assert_eq!(sink.stash().sample_rate, 44_100);
340 assert_eq!(sink.stash().samples.len(), 8);
341 assert!(sink.stash().samples.iter().all(|&s| approx_eq(s, 0.0)));
342 }
343}