sim_lib_stream_audio/io.rs
1use std::collections::VecDeque;
2
3use sim_kernel::{Error, Result};
4
5use crate::{PcmBuffer, PcmSpec};
6
7/// Pull source of PCM audio buffers in a fixed [`PcmSpec`].
8///
9/// Implementors yield [`PcmBuffer`]s until exhausted. Every buffer a source
10/// produces is expected to match the source's [`spec`](PcmSource::spec).
11pub trait PcmSource {
12 /// Returns the audio format every buffer from this source uses.
13 fn spec(&self) -> &PcmSpec;
14
15 /// Reads the next buffer, or `Ok(None)` once the source is drained.
16 fn read_buffer(&mut self) -> Result<Option<PcmBuffer>>;
17}
18
19/// Push sink for PCM audio buffers in a fixed [`PcmSpec`].
20///
21/// Implementors accept [`PcmBuffer`]s that match their
22/// [`spec`](PcmSink::spec) and signal end-of-stream through
23/// [`flush`](PcmSink::flush).
24pub trait PcmSink {
25 /// Returns the audio format this sink accepts.
26 fn spec(&self) -> &PcmSpec;
27
28 /// Writes one buffer to the sink.
29 ///
30 /// Returns an error when the buffer's spec does not match the sink's spec.
31 fn write_buffer(&mut self, buffer: PcmBuffer) -> Result<()>;
32
33 /// Flushes any pending output, marking a stream boundary.
34 fn flush(&mut self) -> Result<()>;
35}
36
37/// In-memory [`PcmSource`] that replays a fixed queue of buffers.
38///
39/// This is the deterministic test backend for the source side of the PCM
40/// fabric: it hands out the buffers it was constructed with, in order, then
41/// reports end-of-stream.
42///
43/// # Examples
44///
45/// ```
46/// use sim_lib_stream_audio::{MemoryPcmSource, PcmBuffer, PcmSource, PcmSpec};
47///
48/// let spec = PcmSpec::i16(1, 48_000)?;
49/// let buffer = PcmBuffer::i16(spec, 2, vec![1, 2])?;
50/// let mut source = MemoryPcmSource::new(spec, vec![buffer])?;
51/// assert_eq!(source.remaining(), 1);
52/// assert!(source.read_buffer()?.is_some());
53/// assert!(source.read_buffer()?.is_none());
54/// # Ok::<(), sim_kernel::Error>(())
55/// ```
56#[derive(Clone, Debug)]
57pub struct MemoryPcmSource {
58 spec: PcmSpec,
59 buffers: VecDeque<PcmBuffer>,
60}
61
62impl MemoryPcmSource {
63 /// Builds a source that will replay `buffers` in order.
64 ///
65 /// Returns an error if any buffer's spec does not match `spec`.
66 pub fn new(spec: PcmSpec, buffers: Vec<PcmBuffer>) -> Result<Self> {
67 for buffer in &buffers {
68 ensure_spec("source", spec, buffer.spec())?;
69 }
70 Ok(Self {
71 spec,
72 buffers: buffers.into(),
73 })
74 }
75
76 /// Returns the number of buffers not yet read.
77 pub fn remaining(&self) -> usize {
78 self.buffers.len()
79 }
80}
81
82impl PcmSource for MemoryPcmSource {
83 fn spec(&self) -> &PcmSpec {
84 &self.spec
85 }
86
87 fn read_buffer(&mut self) -> Result<Option<PcmBuffer>> {
88 Ok(self.buffers.pop_front())
89 }
90}
91
92/// In-memory [`PcmSink`] that records every written buffer and flush.
93///
94/// This is the deterministic test backend for the sink side of the PCM fabric:
95/// written buffers accumulate in order and can be inspected through
96/// [`buffers`](MemoryPcmSink::buffers) or taken with
97/// [`into_buffers`](MemoryPcmSink::into_buffers), while flushes are counted.
98///
99/// # Examples
100///
101/// ```
102/// use sim_lib_stream_audio::{MemoryPcmSink, PcmBuffer, PcmSink, PcmSpec};
103///
104/// let spec = PcmSpec::i16(1, 48_000)?;
105/// let mut sink = MemoryPcmSink::new(spec);
106/// sink.write_buffer(PcmBuffer::i16(spec, 1, vec![7])?)?;
107/// sink.flush()?;
108/// assert_eq!(sink.buffers().len(), 1);
109/// assert_eq!(sink.flush_count(), 1);
110/// # Ok::<(), sim_kernel::Error>(())
111/// ```
112#[derive(Clone, Debug)]
113pub struct MemoryPcmSink {
114 spec: PcmSpec,
115 buffers: Vec<PcmBuffer>,
116 flush_count: usize,
117}
118
119impl MemoryPcmSink {
120 /// Builds an empty sink that accepts buffers matching `spec`.
121 pub fn new(spec: PcmSpec) -> Self {
122 Self {
123 spec,
124 buffers: Vec::new(),
125 flush_count: 0,
126 }
127 }
128
129 /// Returns the buffers written so far, in write order.
130 pub fn buffers(&self) -> &[PcmBuffer] {
131 &self.buffers
132 }
133
134 /// Returns how many times [`flush`](PcmSink::flush) has been called.
135 pub fn flush_count(&self) -> usize {
136 self.flush_count
137 }
138
139 /// Consumes the sink and returns the recorded buffers.
140 pub fn into_buffers(self) -> Vec<PcmBuffer> {
141 self.buffers
142 }
143}
144
145impl PcmSink for MemoryPcmSink {
146 fn spec(&self) -> &PcmSpec {
147 &self.spec
148 }
149
150 fn write_buffer(&mut self, buffer: PcmBuffer) -> Result<()> {
151 ensure_spec("sink", self.spec, buffer.spec())?;
152 self.buffers.push(buffer);
153 Ok(())
154 }
155
156 fn flush(&mut self) -> Result<()> {
157 self.flush_count += 1;
158 Ok(())
159 }
160}
161
162/// Tally of how much audio moved through a pump or stream-adapter run.
163///
164/// Returned by [`pump_pcm`] and the spine adapters, it records the number of
165/// buffers transferred and the total frame count across them.
166#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
167pub struct PcmPumpSummary {
168 buffers: usize,
169 frames: usize,
170}
171
172impl PcmPumpSummary {
173 /// Returns the number of buffers transferred.
174 pub fn buffers(self) -> usize {
175 self.buffers
176 }
177
178 /// Returns the total number of frames across all transferred buffers.
179 pub fn frames(self) -> usize {
180 self.frames
181 }
182
183 pub(crate) fn record(&mut self, buffer: &PcmBuffer) {
184 self.buffers += 1;
185 self.frames += buffer.frames();
186 }
187}
188
189/// Drains `source` into `sink`, then flushes the sink.
190///
191/// Reads every buffer from `source` and writes it to `sink`, returning a
192/// [`PcmPumpSummary`] of the transfer. Returns an error when the source and
193/// sink specs differ or when any write fails.
194pub fn pump_pcm(source: &mut impl PcmSource, sink: &mut impl PcmSink) -> Result<PcmPumpSummary> {
195 ensure_spec("sink", *source.spec(), *sink.spec())?;
196 let mut summary = PcmPumpSummary::default();
197 while let Some(buffer) = source.read_buffer()? {
198 summary.record(&buffer);
199 sink.write_buffer(buffer)?;
200 }
201 sink.flush()?;
202 Ok(summary)
203}
204
205pub(crate) fn ensure_spec(role: &str, expected: PcmSpec, actual: PcmSpec) -> Result<()> {
206 if expected != actual {
207 return Err(Error::Eval(format!(
208 "PCM {role} spec mismatch: expected {:?}, got {:?}",
209 expected, actual
210 )));
211 }
212 Ok(())
213}