Skip to main content

sim_lib_stream_audio/
buffer.rs

1use sim_kernel::{Error, Result};
2use sim_lib_stream_core::PcmPacket;
3
4use crate::{PcmSampleFormat, PcmSpec};
5
6/// Owned block of PCM audio: a [`PcmSpec`] plus a frame count and the matching
7/// interleaved sample data.
8///
9/// A `PcmBuffer` is the unit of audio carried across a source or sink. Its
10/// sample storage always agrees with its spec: an [`PcmSampleFormat::I16`] spec
11/// holds `i16` samples and an [`PcmSampleFormat::F32`] spec holds `f32` samples,
12/// with exactly `channels * frames` interleaved values. The constructors
13/// enforce that invariant, so [`samples_i16`](PcmBuffer::samples_i16) and
14/// [`samples_f32`](PcmBuffer::samples_f32) only need to panic on a caller that
15/// asks for the wrong format.
16///
17/// Equality is bit-exact for `f32` samples (NaN bit patterns compare equal to
18/// themselves), which keeps the type usable as a deterministic test fixture.
19///
20/// # Examples
21///
22/// ```
23/// use sim_lib_stream_audio::{PcmBuffer, PcmSpec};
24///
25/// let spec = PcmSpec::i16(2, 48_000)?;
26/// let buffer = PcmBuffer::i16(spec, 2, vec![1, 2, 3, 4])?;
27/// assert_eq!(buffer.frames(), 2);
28/// assert_eq!(buffer.samples_i16(), &[1, 2, 3, 4]);
29/// # Ok::<(), sim_kernel::Error>(())
30/// ```
31#[derive(Clone, Debug)]
32pub struct PcmBuffer {
33    spec: PcmSpec,
34    frames: usize,
35    samples: PcmBufferSamples,
36}
37
38impl PartialEq for PcmBuffer {
39    fn eq(&self, other: &Self) -> bool {
40        self.spec == other.spec && self.frames == other.frames && self.samples == other.samples
41    }
42}
43
44impl Eq for PcmBuffer {}
45
46#[derive(Clone, Debug)]
47enum PcmBufferSamples {
48    I16(Vec<i16>),
49    F32(Vec<f32>),
50}
51
52impl PartialEq for PcmBufferSamples {
53    fn eq(&self, other: &Self) -> bool {
54        match (self, other) {
55            (Self::I16(left), Self::I16(right)) => left == right,
56            (Self::F32(left), Self::F32(right)) => {
57                left.len() == right.len()
58                    && left
59                        .iter()
60                        .zip(right)
61                        .all(|(left, right)| left.to_bits() == right.to_bits())
62            }
63            _ => false,
64        }
65    }
66}
67
68impl Eq for PcmBufferSamples {}
69
70impl PcmBuffer {
71    /// Builds an `i16` buffer from a spec, frame count, and interleaved samples.
72    ///
73    /// Returns an error when `spec` is not an [`PcmSampleFormat::I16`] spec or
74    /// when `samples_i16.len()` is not `spec.channels() * frames`.
75    pub fn i16(spec: PcmSpec, frames: usize, samples_i16: Vec<i16>) -> Result<Self> {
76        if spec.sample_format() != PcmSampleFormat::I16 {
77            return Err(Error::Eval(
78                "PCM buffer requires an i16 PCM spec".to_owned(),
79            ));
80        }
81        let expected = expected_samples(spec.channels(), frames)?;
82        if samples_i16.len() != expected {
83            return Err(Error::Eval(format!(
84                "PCM buffer sample length {} does not match channels {} * frames {}",
85                samples_i16.len(),
86                spec.channels(),
87                frames
88            )));
89        }
90        Ok(Self {
91            spec,
92            frames,
93            samples: PcmBufferSamples::I16(samples_i16),
94        })
95    }
96
97    /// Builds an `f32` buffer from a spec, frame count, and interleaved samples.
98    ///
99    /// Returns an error when `spec` is not an [`PcmSampleFormat::F32`] spec, when
100    /// `samples_f32.len()` is not `spec.channels() * frames`, or when any sample
101    /// is not finite.
102    pub fn f32(spec: PcmSpec, frames: usize, samples_f32: Vec<f32>) -> Result<Self> {
103        if spec.sample_format() != PcmSampleFormat::F32 {
104            return Err(Error::Eval(
105                "PCM buffer requires an f32 PCM spec".to_owned(),
106            ));
107        }
108        let expected = expected_samples(spec.channels(), frames)?;
109        if samples_f32.len() != expected {
110            return Err(Error::Eval(format!(
111                "PCM buffer sample length {} does not match channels {} * frames {}",
112                samples_f32.len(),
113                spec.channels(),
114                frames
115            )));
116        }
117        validate_f32_samples(&samples_f32)?;
118        Ok(Self {
119            spec,
120            frames,
121            samples: PcmBufferSamples::F32(samples_f32),
122        })
123    }
124
125    /// Builds a buffer from a `sim-lib-stream-core` [`PcmPacket`], adopting
126    /// `spec`.
127    ///
128    /// Returns an error when the packet's channel count or sample format does
129    /// not match `spec`.
130    pub fn from_packet(spec: PcmSpec, packet: &PcmPacket) -> Result<Self> {
131        if packet.channels() != spec.channels() {
132            return Err(Error::Eval(format!(
133                "PCM packet channel count {} does not match sink spec {}",
134                packet.channels(),
135                spec.channels()
136            )));
137        }
138        if packet.sample_format() != spec.sample_format() {
139            return Err(Error::Eval(format!(
140                "PCM packet sample format {:?} does not match sink spec {:?}",
141                packet.sample_format(),
142                spec.sample_format()
143            )));
144        }
145        match spec.sample_format() {
146            PcmSampleFormat::I16 => Self::i16(spec, packet.frames(), packet.samples_i16().to_vec()),
147            PcmSampleFormat::F32 => Self::f32(spec, packet.frames(), packet.samples_f32().to_vec()),
148        }
149    }
150
151    /// Encodes this buffer as a `sim-lib-stream-core` [`PcmPacket`] for stream
152    /// transport.
153    pub fn to_packet(&self) -> Result<PcmPacket> {
154        match &self.samples {
155            PcmBufferSamples::I16(samples) => {
156                PcmPacket::i16(self.spec.channels(), self.frames, samples.clone())
157            }
158            PcmBufferSamples::F32(samples) => {
159                PcmPacket::f32(self.spec.channels(), self.frames, samples.clone())
160            }
161        }
162    }
163
164    /// Returns the audio format of this buffer.
165    pub fn spec(&self) -> PcmSpec {
166        self.spec
167    }
168
169    /// Returns the number of audio frames (samples per channel) in this buffer.
170    pub fn frames(&self) -> usize {
171        self.frames
172    }
173
174    /// Returns the interleaved `i16` samples.
175    ///
176    /// # Panics
177    ///
178    /// Panics if this buffer holds `f32` samples rather than `i16`.
179    pub fn samples_i16(&self) -> &[i16] {
180        match &self.samples {
181            PcmBufferSamples::I16(samples) => samples,
182            PcmBufferSamples::F32(_) => panic!("PCM buffer does not contain i16 samples"),
183        }
184    }
185
186    /// Returns the interleaved `f32` samples.
187    ///
188    /// # Panics
189    ///
190    /// Panics if this buffer holds `i16` samples rather than `f32`.
191    pub fn samples_f32(&self) -> &[f32] {
192        match &self.samples {
193            PcmBufferSamples::F32(samples) => samples,
194            PcmBufferSamples::I16(_) => panic!("PCM buffer does not contain f32 samples"),
195        }
196    }
197}
198
199/// Converts one `i16` sample to the normalized `f32` range `[-1.0, 1.0]`.
200///
201/// Negative values scale by `32768` and non-negative values by [`i16::MAX`], so
202/// the full `i16` range maps symmetrically into the float interval.
203///
204/// # Examples
205///
206/// ```
207/// use sim_lib_stream_audio::i16_sample_to_f32;
208///
209/// assert_eq!(i16_sample_to_f32(0), 0.0);
210/// assert_eq!(i16_sample_to_f32(i16::MAX), 1.0);
211/// ```
212pub fn i16_sample_to_f32(sample: i16) -> f32 {
213    if sample < 0 {
214        f32::from(sample) / 32768.0
215    } else {
216        f32::from(sample) / f32::from(i16::MAX)
217    }
218}
219
220/// Converts one normalized `f32` sample to `i16`, clamping to `[-1.0, 1.0]`.
221///
222/// Returns an error when the input is not finite. In-range values are rounded
223/// to the nearest integer and saturated at the `i16` bounds.
224pub fn f32_sample_to_i16(sample: f32) -> Result<i16> {
225    if !sample.is_finite() {
226        return Err(Error::Eval("PCM f32 sample must be finite".to_owned()));
227    }
228    let clamped = sample.clamp(-1.0, 1.0);
229    if clamped < 0.0 {
230        Ok((clamped * 32768.0).round().max(f32::from(i16::MIN)) as i16)
231    } else {
232        Ok((clamped * f32::from(i16::MAX))
233            .round()
234            .min(f32::from(i16::MAX)) as i16)
235    }
236}
237
238/// Converts a slice of `i16` samples to normalized `f32` samples via
239/// [`i16_sample_to_f32`].
240pub fn i16_samples_to_f32(samples: &[i16]) -> Vec<f32> {
241    samples
242        .iter()
243        .map(|sample| i16_sample_to_f32(*sample))
244        .collect()
245}
246
247/// Converts a slice of normalized `f32` samples to `i16` via
248/// [`f32_sample_to_i16`].
249///
250/// Returns an error at the first non-finite sample.
251pub fn f32_samples_to_i16(samples: &[f32]) -> Result<Vec<i16>> {
252    samples
253        .iter()
254        .map(|sample| f32_sample_to_i16(*sample))
255        .collect()
256}
257
258/// Splits interleaved `i16` samples into one `Vec` per channel.
259///
260/// Returns an error when `channels` is zero or `samples.len()` is not a
261/// multiple of `channels`.
262pub fn i16_interleaved_to_planar(samples: &[i16], channels: usize) -> Result<Vec<Vec<i16>>> {
263    interleaved_to_planar(samples, channels)
264}
265
266/// Interleaves per-channel `i16` planes into a single interleaved `Vec`.
267///
268/// Returns an error when no channel is supplied or the channel planes differ in
269/// length.
270pub fn i16_planar_to_interleaved(channels: &[Vec<i16>]) -> Result<Vec<i16>> {
271    planar_to_interleaved(channels)
272}
273
274/// Splits interleaved `f32` samples into one `Vec` per channel.
275///
276/// Returns an error when any sample is not finite, `channels` is zero, or
277/// `samples.len()` is not a multiple of `channels`.
278pub fn f32_interleaved_to_planar(samples: &[f32], channels: usize) -> Result<Vec<Vec<f32>>> {
279    validate_f32_samples(samples)?;
280    interleaved_to_planar(samples, channels)
281}
282
283/// Interleaves per-channel `f32` planes into a single interleaved `Vec`.
284///
285/// Returns an error when no channel is supplied, the channel planes differ in
286/// length, or any sample is not finite.
287pub fn f32_planar_to_interleaved(channels: &[Vec<f32>]) -> Result<Vec<f32>> {
288    let samples = planar_to_interleaved(channels)?;
289    validate_f32_samples(&samples)?;
290    Ok(samples)
291}
292
293fn expected_samples(channels: usize, frames: usize) -> Result<usize> {
294    channels
295        .checked_mul(frames)
296        .ok_or_else(|| Error::Eval("PCM buffer sample count overflow".to_owned()))
297}
298
299fn validate_f32_samples(samples: &[f32]) -> Result<()> {
300    if let Some(index) = samples.iter().position(|sample| !sample.is_finite()) {
301        return Err(Error::Eval(format!(
302            "PCM f32 sample at {index} must be finite"
303        )));
304    }
305    Ok(())
306}
307
308fn interleaved_to_planar<T>(samples: &[T], channels: usize) -> Result<Vec<Vec<T>>>
309where
310    T: Copy,
311{
312    if channels == 0 {
313        return Err(Error::Eval(
314            "PCM channel count must be greater than zero".to_owned(),
315        ));
316    }
317    if !samples.len().is_multiple_of(channels) {
318        return Err(Error::Eval(format!(
319            "PCM interleaved sample length {} is not divisible by channels {}",
320            samples.len(),
321            channels
322        )));
323    }
324    let frames = samples.len() / channels;
325    let mut planar = vec![Vec::with_capacity(frames); channels];
326    for frame in samples.chunks(channels) {
327        for (channel, sample) in frame.iter().enumerate() {
328            planar[channel].push(*sample);
329        }
330    }
331    Ok(planar)
332}
333
334fn planar_to_interleaved<T>(channels: &[Vec<T>]) -> Result<Vec<T>>
335where
336    T: Copy,
337{
338    let Some(first) = channels.first() else {
339        return Err(Error::Eval(
340            "PCM planar data must contain at least one channel".to_owned(),
341        ));
342    };
343    let frames = first.len();
344    if let Some((index, channel)) = channels
345        .iter()
346        .enumerate()
347        .find(|(_, channel)| channel.len() != frames)
348    {
349        return Err(Error::Eval(format!(
350            "PCM planar channel {index} length {} does not match frame count {frames}",
351            channel.len()
352        )));
353    }
354    let mut interleaved = Vec::with_capacity(frames * channels.len());
355    for frame in 0..frames {
356        for channel in channels {
357            interleaved.push(channel[frame]);
358        }
359    }
360    Ok(interleaved)
361}