Skip to main content

shuyi_sound/
source.rs

1//! A source of samples in bounded chunks.
2
3use alloc::boxed::Box;
4use alloc::vec::Vec;
5
6use crate::error::Error;
7use crate::mix::SharedPcm;
8use crate::pcm::{Limits, Pcm, PcmFormat};
9
10/// How many samples [`read_all`] reads at a time.
11const CHUNK_SAMPLES: usize = 8192;
12
13/// Something that yields whole frames on request, as a decoder working through
14/// a file does. A caller decides how much it holds at once: a read never fills
15/// more than the buffer it is given.
16pub trait Source {
17    /// The rate and channels of what it yields.
18    fn format(&self) -> PcmFormat;
19
20    /// How many frames it yields from its start, if that is known without
21    /// decoding.
22    fn total_frames(&self) -> Option<u64>;
23
24    /// Fills `out` with the next whole frames, interleaved, and says how many
25    /// frames that was. `0` means it has ended. Short of the end it gives at
26    /// least one, and a buffer without room for one is [`Error::NoRoom`].
27    ///
28    /// A source that mustn't keep its reader waiting gives [`Error::Underrun`] when
29    /// it has no frame ready yet.
30    fn read(&mut self, out: &mut [i16]) -> Result<usize, Error>;
31
32    /// Goes back, so that the next read gives frame `frame`, without waiting:
33    /// what a mix asks at a loop's join. A source that can't says
34    /// [`Error::NoRewind`], as this does unless it is written otherwise.
35    fn rewind_to(&mut self, frame: u64) -> Result<(), Error> {
36        let _ = frame;
37        Err(Error::NoRewind)
38    }
39}
40
41impl<S: Source + ?Sized> Source for Box<S> {
42    fn format(&self) -> PcmFormat {
43        (**self).format()
44    }
45
46    fn total_frames(&self) -> Option<u64> {
47        (**self).total_frames()
48    }
49
50    fn read(&mut self, out: &mut [i16]) -> Result<usize, Error> {
51        (**self).read(out)
52    }
53
54    fn rewind_to(&mut self, frame: u64) -> Result<(), Error> {
55        (**self).rewind_to(frame)
56    }
57}
58
59/// Whole frames `out` has room for, of `format`'s, or why it has none.
60pub(crate) fn room(format: PcmFormat, out: &[i16]) -> Result<usize, Error> {
61    let channels = format.channels.count();
62    let frames = out.len() / usize::from(channels);
63    if frames == 0 {
64        return Err(Error::NoRoom {
65            samples: out.len(),
66            channels,
67        });
68    }
69    Ok(frames)
70}
71
72/// Everything `source` still yields, as one [`Pcm`], within `limits`.
73pub fn read_all(source: &mut impl Source, limits: &Limits) -> Result<Pcm, Error> {
74    let format = source.format();
75    let channels = usize::from(format.channels.count());
76    if let Some(frames) = source.total_frames().filter(|f| *f > limits.max_frames) {
77        return Err(Error::TooLong {
78            frames,
79            limit: limits.max_frames,
80        });
81    }
82    let mut samples = Vec::new();
83    let mut chunk = [0; CHUNK_SAMPLES];
84    loop {
85        let frames = source.read(&mut chunk)?;
86        if frames == 0 {
87            return Pcm::new(format, samples, limits);
88        }
89        samples.extend_from_slice(&chunk[..frames * channels]);
90        let frames = samples.len() as u64 / channels as u64;
91        if frames > limits.max_frames {
92            return Err(Error::TooLong {
93                frames,
94                limit: limits.max_frames,
95            });
96        }
97    }
98}
99
100/// A [`Pcm`]'s samples as a source.
101#[derive(Debug, Clone)]
102pub struct PcmSource<'a> {
103    pcm: &'a Pcm,
104    /// The next sample.
105    at: usize,
106}
107
108impl<'a> PcmSource<'a> {
109    /// `pcm`'s samples, from the start.
110    pub const fn new(pcm: &'a Pcm) -> Self {
111        Self { pcm, at: 0 }
112    }
113}
114
115impl Source for PcmSource<'_> {
116    fn format(&self) -> PcmFormat {
117        self.pcm.format()
118    }
119
120    fn total_frames(&self) -> Option<u64> {
121        Some(self.pcm.frames())
122    }
123
124    fn read(&mut self, out: &mut [i16]) -> Result<usize, Error> {
125        let format = self.format();
126        let channels = usize::from(format.channels.count());
127        let room = room(format, out)?;
128        let left = &self.pcm.samples()[self.at..];
129        let frames = room.min(left.len() / channels);
130        let count = frames * channels;
131        out[..count].copy_from_slice(&left[..count]);
132        self.at += count;
133        Ok(frames)
134    }
135
136    /// Past the end, the next read gives nothing.
137    fn rewind_to(&mut self, frame: u64) -> Result<(), Error> {
138        self.at = sample_index(self.pcm, frame);
139        Ok(())
140    }
141}
142
143/// Where frame `frame` of `pcm` starts, or its end if it hasn't one.
144fn sample_index(pcm: &Pcm, frame: u64) -> usize {
145    let channels = u64::from(pcm.format().channels.count());
146    let frame = frame.min(pcm.frames());
147    usize::try_from(frame * channels).unwrap_or(pcm.samples().len())
148}
149
150/// A shared [`Pcm`]'s samples as a source that owns its share, so a voice can
151/// stream it from anywhere.
152#[derive(Debug, Clone)]
153pub struct SharedSource {
154    pcm: SharedPcm,
155    /// The next sample.
156    at: usize,
157}
158
159impl SharedSource {
160    /// `pcm`'s samples, from the start.
161    pub const fn new(pcm: SharedPcm) -> Self {
162        Self { pcm, at: 0 }
163    }
164}
165
166impl Source for SharedSource {
167    fn format(&self) -> PcmFormat {
168        self.pcm.format()
169    }
170
171    fn total_frames(&self) -> Option<u64> {
172        Some(self.pcm.frames())
173    }
174
175    fn read(&mut self, out: &mut [i16]) -> Result<usize, Error> {
176        let mut source = PcmSource {
177            pcm: &self.pcm,
178            at: self.at,
179        };
180        let frames = source.read(out)?;
181        self.at = source.at;
182        Ok(frames)
183    }
184
185    /// Past the end, the next read gives nothing.
186    fn rewind_to(&mut self, frame: u64) -> Result<(), Error> {
187        self.at = sample_index(&self.pcm, frame);
188        Ok(())
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::pcm::Channels;
196    use proptest::prelude::*;
197
198    fn format(channels: Channels) -> PcmFormat {
199        PcmFormat::new(8_000, u32::from(channels.count()), &Limits::default())
200            .unwrap_or_else(|e| panic!("{e}"))
201    }
202
203    fn pcm(channels: Channels, samples: Vec<i16>) -> Pcm {
204        Pcm::new(format(channels), samples, &Limits::default()).unwrap_or_else(|e| panic!("{e}"))
205    }
206
207    #[test]
208    fn a_read_gives_whole_frames_up_to_the_room() {
209        let sound = pcm(Channels::Stereo, vec![1, 2, 3, 4, 5, 6]);
210        let mut source = sound.source();
211        assert_eq!(source.format(), sound.format());
212        assert_eq!(source.total_frames(), Some(3));
213        let mut out = [0; 5];
214        assert_eq!(source.read(&mut out), Ok(2));
215        assert_eq!(out, [1, 2, 3, 4, 0]);
216        assert_eq!(source.read(&mut out), Ok(1));
217        assert_eq!(out[..2], [5, 6]);
218        assert_eq!(source.read(&mut out), Ok(0));
219        assert_eq!(source.read(&mut out), Ok(0));
220    }
221
222    #[test]
223    fn a_read_without_room_for_a_frame_says_so() {
224        let sound = pcm(Channels::Stereo, vec![1, 2]);
225        let mut one = [0; 1];
226        assert_eq!(
227            sound.source().read(&mut one),
228            Err(Error::NoRoom {
229                samples: 1,
230                channels: 2
231            })
232        );
233        assert_eq!(
234            sound.source().read(&mut []),
235            Err(Error::NoRoom {
236                samples: 0,
237                channels: 2
238            })
239        );
240    }
241
242    /// Says nothing of its length and has no end, as far as a reader can tell.
243    /// A reader that ignores its limit fails here, before it fills the memory.
244    struct Endless(Channels, u32);
245
246    impl Source for Endless {
247        fn format(&self) -> PcmFormat {
248            format(self.0)
249        }
250
251        fn total_frames(&self) -> Option<u64> {
252            None
253        }
254
255        fn read(&mut self, out: &mut [i16]) -> Result<usize, Error> {
256            self.1 += 1;
257            assert!(self.1 < 16, "read on long past the limit");
258            out.fill(7);
259            Ok(out.len() / usize::from(self.0.count()))
260        }
261    }
262
263    #[test]
264    fn a_shared_source_reads_and_goes_back_as_a_borrowed_one_does() {
265        let sound = pcm(Channels::Stereo, vec![1, 2, 3, 4, 5, 6]);
266        let mut shared = SharedSource::new(SharedPcm::new(sound.clone()));
267        let mut borrowed = sound.source();
268        assert_eq!(shared.format(), sound.format());
269        assert_eq!(shared.total_frames(), Some(3));
270        let (mut a, mut b) = ([0; 4], [0; 4]);
271        assert_eq!(shared.read(&mut a), Ok(2));
272        assert_eq!(borrowed.read(&mut b), Ok(2));
273        assert_eq!(a, [1, 2, 3, 4]);
274        assert_eq!(a, b);
275        assert_eq!(shared.rewind_to(1), Ok(()));
276        assert_eq!(borrowed.rewind_to(1), Ok(()));
277        assert_eq!(shared.read(&mut a), Ok(2));
278        assert_eq!(borrowed.read(&mut b), Ok(2));
279        assert_eq!(a, [3, 4, 5, 6]);
280        assert_eq!(a, b);
281        assert_eq!(shared.rewind_to(9), Ok(()));
282        assert_eq!(borrowed.rewind_to(9), Ok(()));
283        assert_eq!(shared.read(&mut a), Ok(0));
284        assert_eq!(borrowed.read(&mut b), Ok(0));
285    }
286
287    #[test]
288    fn a_source_goes_back_only_if_it_says_it_can() {
289        let mut endless = Endless(Channels::Mono, 0);
290        assert_eq!(endless.rewind_to(0), Err(Error::NoRewind));
291    }
292
293    #[test]
294    fn read_all_stops_at_the_limit_of_an_endless_source() {
295        let limits = Limits::new(8_000, 10_000);
296        for (channels, chunk) in [(Channels::Mono, 8192), (Channels::Stereo, 4096)] {
297            let Err(Error::TooLong { frames, limit }) =
298                read_all(&mut Endless(channels, 0), &limits)
299            else {
300                panic!("it should have stopped");
301            };
302            assert_eq!(limit, 10_000);
303            // It counts frames, and goes no further than one chunk past.
304            assert!(frames > 10_000 && frames <= 10_000 + chunk, "{frames}");
305        }
306    }
307
308    #[test]
309    fn read_all_refuses_a_known_length_over_the_limit_before_reading() {
310        let sound = pcm(Channels::Mono, vec![0; 5]);
311        assert_eq!(
312            read_all(&mut sound.source(), &Limits::new(8_000, 4)),
313            Err(Error::TooLong {
314                frames: 5,
315                limit: 4
316            })
317        );
318    }
319
320    #[test]
321    fn read_all_takes_what_is_exactly_at_the_limit() {
322        let sound = pcm(Channels::Mono, vec![3; 5]);
323        assert_eq!(
324            read_all(&mut sound.source(), &Limits::new(8_000, 5)).as_ref(),
325            Ok(&sound)
326        );
327    }
328
329    proptest! {
330        /// However it is cut into chunks, what comes out is what went in.
331        #[test]
332        fn chunks_rebuild_the_whole(
333            samples in proptest::collection::vec(any::<i16>(), 0..512),
334            stereo: bool,
335            room in 2usize..64,
336        ) {
337            let channels = if stereo { Channels::Stereo } else { Channels::Mono };
338            let whole = samples.len() / usize::from(channels.count());
339            let samples = samples[..whole * usize::from(channels.count())].to_vec();
340            let sound = pcm(channels, samples.clone());
341            let mut source = sound.source();
342            let mut out = vec![0; room];
343            let mut rebuilt = Vec::new();
344            loop {
345                let frames = source.read(&mut out).unwrap_or_else(|e| panic!("{e}"));
346                if frames == 0 {
347                    break;
348                }
349                prop_assert!(frames * usize::from(channels.count()) <= room);
350                rebuilt.extend_from_slice(&out[..frames * usize::from(channels.count())]);
351            }
352            prop_assert_eq!(rebuilt, samples);
353            let again = read_all(&mut sound.source(), &Limits::default());
354            prop_assert_eq!(again.as_ref(), Ok(&sound));
355        }
356    }
357}