Skip to main content

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}