Skip to main content

shuyi_sound/
pcm.rs

1//! Decoded samples: 16 bits, interleaved, one or two channels.
2
3use alloc::vec::Vec;
4
5use crate::error::Error;
6use crate::source::PcmSource;
7
8/// How many channels a frame has.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Channels {
11    /// One sample a frame.
12    Mono,
13    /// Two samples a frame, left first.
14    Stereo,
15}
16
17impl Channels {
18    /// `count` channels, if a sound can have that many.
19    pub const fn from_count(count: u32) -> Result<Self, Error> {
20        match count {
21            1 => Ok(Self::Mono),
22            2 => Ok(Self::Stereo),
23            other => Err(Error::Channels(other)),
24        }
25    }
26
27    /// How many samples a frame has.
28    pub const fn count(self) -> u16 {
29        match self {
30            Self::Mono => 1,
31            Self::Stereo => 2,
32        }
33    }
34}
35
36/// What a decoder may produce: a malformed or hostile file stops here, before
37/// it costs the memory it asks for.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub struct Limits {
41    /// The highest rate, in samples a second.
42    pub max_rate: u32,
43    /// The most frames one source may yield.
44    pub max_frames: u64,
45}
46
47impl Limits {
48    /// The highest rate of the default limits.
49    pub const DEFAULT_MAX_RATE: u32 = 192_000;
50    /// The most frames of the default limits: about 25 minutes at 44.1 kHz,
51    /// which is 256 MiB of stereo decoded whole.
52    pub const DEFAULT_MAX_FRAMES: u64 = 1 << 26;
53
54    /// These limits.
55    pub const fn new(max_rate: u32, max_frames: u64) -> Self {
56        Self {
57            max_rate,
58            max_frames,
59        }
60    }
61}
62
63impl Default for Limits {
64    fn default() -> Self {
65        Self::new(Self::DEFAULT_MAX_RATE, Self::DEFAULT_MAX_FRAMES)
66    }
67}
68
69/// The rate and channels of some samples.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[non_exhaustive]
72pub struct PcmFormat {
73    /// Frames a second.
74    pub rate: u32,
75    /// How many samples a frame has.
76    pub channels: Channels,
77}
78
79impl PcmFormat {
80    /// `rate` and `channels`, if they are within `limits`.
81    pub fn new(rate: u32, channels: u32, limits: &Limits) -> Result<Self, Error> {
82        let channels = Channels::from_count(channels)?;
83        if rate == 0 || rate > limits.max_rate {
84            return Err(Error::Rate {
85                rate,
86                max: limits.max_rate,
87            });
88        }
89        Ok(Self { rate, channels })
90    }
91
92    /// How many samples `frames` frames are, if that fits.
93    pub fn samples_of(&self, frames: u64) -> Option<usize> {
94        let samples = frames.checked_mul(u64::from(self.channels.count()))?;
95        usize::try_from(samples).ok()
96    }
97
98    /// How long `frames` frames play, in whole milliseconds rounded down, if
99    /// that fits.
100    pub fn millis_of(&self, frames: u64) -> Option<u64> {
101        let rate = u64::from(self.rate);
102        let whole = frames / rate;
103        let part = frames % rate;
104        whole.checked_mul(1000)?.checked_add(part * 1000 / rate)
105    }
106
107    /// How many whole frames play in `millis` milliseconds, if that fits.
108    pub fn frames_of_millis(&self, millis: u64) -> Option<u64> {
109        let rate = u64::from(self.rate);
110        let whole = millis / 1000;
111        let part = millis % 1000;
112        whole.checked_mul(rate)?.checked_add(part * rate / 1000)
113    }
114}
115
116/// `sample` of 8 bits as one of 16: the same fraction of full scale.
117pub const fn from_i8(sample: i8) -> i16 {
118    (sample as i16) << 8
119}
120
121/// `sample` of 24 bits, in the low bits of an `i32`, as one of 16: the bits
122/// below are dropped, as a truncating converter does. Out of range, it clamps.
123pub fn from_i24(sample: i32) -> i16 {
124    clamp_i16(sample >> 8)
125}
126
127/// `sample` of 32 bits as one of 16: the bits below are dropped.
128pub fn from_i32(sample: i32) -> i16 {
129    clamp_i16(sample >> 16)
130}
131
132fn clamp_i16(value: i32) -> i16 {
133    i16::try_from(value.clamp(i32::from(i16::MIN), i32::from(i16::MAX))).unwrap_or(0)
134}
135
136/// A sound decoded whole.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct Pcm {
139    format: PcmFormat,
140    samples: Vec<i16>,
141}
142
143impl Pcm {
144    /// `samples`, interleaved, if they are whole frames within `limits`.
145    pub fn new(format: PcmFormat, samples: Vec<i16>, limits: &Limits) -> Result<Self, Error> {
146        let channels = format.channels.count();
147        let count = samples.len() as u64;
148        if !count.is_multiple_of(u64::from(channels)) {
149            return Err(Error::PartialFrame {
150                samples: count,
151                channels,
152            });
153        }
154        let frames = count / u64::from(channels);
155        if frames > limits.max_frames {
156            return Err(Error::TooLong {
157                frames,
158                limit: limits.max_frames,
159            });
160        }
161        Ok(Self { format, samples })
162    }
163
164    /// Its rate and channels.
165    pub const fn format(&self) -> PcmFormat {
166        self.format
167    }
168
169    /// Its samples, interleaved.
170    pub fn samples(&self) -> &[i16] {
171        &self.samples
172    }
173
174    /// Its samples, handed over.
175    pub fn into_samples(self) -> Vec<i16> {
176        self.samples
177    }
178
179    /// How many frames it has.
180    pub fn frames(&self) -> u64 {
181        self.samples.len() as u64 / u64::from(self.format.channels.count())
182    }
183
184    /// How long it plays, in whole milliseconds rounded down.
185    pub fn millis(&self) -> u64 {
186        // Under `u64::MAX / 1000` frames, as memory can't hold more.
187        self.format.millis_of(self.frames()).unwrap_or(u64::MAX)
188    }
189
190    /// A source of its samples, from the start.
191    pub const fn source(&self) -> PcmSource<'_> {
192        PcmSource::new(self)
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use proptest::prelude::*;
200
201    const STEREO: PcmFormat = PcmFormat {
202        rate: 44_100,
203        channels: Channels::Stereo,
204    };
205
206    #[test]
207    fn a_sound_has_one_or_two_channels() {
208        assert_eq!(Channels::from_count(1).map(Channels::count), Ok(1));
209        assert_eq!(Channels::from_count(2).map(Channels::count), Ok(2));
210        assert_eq!(Channels::from_count(0), Err(Error::Channels(0)));
211        assert_eq!(Channels::from_count(3), Err(Error::Channels(3)));
212    }
213
214    #[test]
215    fn a_rate_is_from_one_to_the_limit() {
216        let limits = Limits::new(48_000, 10);
217        let bad = |rate| Err(Error::Rate { rate, max: 48_000 });
218        assert_eq!(PcmFormat::new(0, 1, &limits), bad(0));
219        assert_eq!(PcmFormat::new(48_001, 1, &limits), bad(48_001));
220        assert_eq!(
221            PcmFormat::new(48_000, 1, &limits).map(|f| f.rate),
222            Ok(48_000)
223        );
224        assert_eq!(PcmFormat::new(1, 2, &limits).map(|f| f.rate), Ok(1));
225        assert_eq!(PcmFormat::new(1, 5, &limits), Err(Error::Channels(5)));
226    }
227
228    #[test]
229    fn the_default_limits_are_the_ones_written() {
230        let limits = Limits::default();
231        assert_eq!(limits.max_rate, 192_000);
232        assert_eq!(limits.max_frames, 67_108_864);
233    }
234
235    #[test]
236    fn arithmetic_is_exact_and_checked() {
237        assert_eq!(STEREO.samples_of(3), Some(6));
238        assert_eq!(STEREO.samples_of(u64::MAX), None);
239        assert_eq!(STEREO.millis_of(44_100), Some(1000));
240        assert_eq!(STEREO.millis_of(66_150), Some(1500));
241        assert_eq!(STEREO.millis_of(44), Some(0));
242        assert_eq!(STEREO.millis_of(45), Some(1));
243        assert_eq!(STEREO.frames_of_millis(1500), Some(66_150));
244        assert_eq!(STEREO.frames_of_millis(1), Some(44));
245        let fast = PcmFormat {
246            rate: 1,
247            channels: Channels::Mono,
248        };
249        assert_eq!(fast.millis_of(u64::MAX), None);
250        assert_eq!(
251            fast.millis_of(u64::MAX / 1000),
252            Some(u64::MAX / 1000 * 1000)
253        );
254        let high = PcmFormat {
255            rate: u32::MAX,
256            channels: Channels::Mono,
257        };
258        assert_eq!(high.frames_of_millis(u64::MAX), None);
259    }
260
261    #[test]
262    fn widths_keep_the_fraction_of_full_scale() {
263        assert_eq!(from_i8(i8::MAX), 0x7F00);
264        assert_eq!(from_i8(i8::MIN), i16::MIN);
265        assert_eq!(from_i8(1), 256);
266        assert_eq!(from_i24(0x7F_FFFF), i16::MAX);
267        assert_eq!(from_i24(-0x80_0000), i16::MIN);
268        assert_eq!(from_i24(0x100), 1);
269        assert_eq!(from_i24(0x0FF), 0);
270        assert_eq!(from_i24(i32::MAX), i16::MAX);
271        assert_eq!(from_i24(i32::MIN), i16::MIN);
272        assert_eq!(from_i32(i32::MAX), i16::MAX);
273        assert_eq!(from_i32(i32::MIN), i16::MIN);
274        assert_eq!(from_i32(0x1_0000), 1);
275    }
276
277    #[test]
278    fn pcm_holds_whole_frames_within_the_limit() {
279        let limits = Limits::new(48_000, 2);
280        let pcm = Pcm::new(STEREO, vec![1, 2, 3, 4], &limits);
281        let pcm = pcm.unwrap_or_else(|e| panic!("{e}"));
282        assert_eq!(pcm.frames(), 2);
283        assert_eq!(pcm.samples(), [1, 2, 3, 4]);
284        assert_eq!(pcm.format(), STEREO);
285        assert_eq!(pcm.millis(), 0);
286        assert_eq!(pcm.clone().into_samples(), [1, 2, 3, 4]);
287        assert_eq!(
288            Pcm::new(STEREO, vec![1, 2, 3], &limits),
289            Err(Error::PartialFrame {
290                samples: 3,
291                channels: 2
292            })
293        );
294        assert_eq!(
295            Pcm::new(STEREO, vec![0; 6], &limits),
296            Err(Error::TooLong {
297                frames: 3,
298                limit: 2
299            })
300        );
301    }
302
303    #[test]
304    fn a_seconds_worth_of_pcm_plays_a_second() {
305        let samples = vec![0; 88_200];
306        let pcm = Pcm::new(STEREO, samples, &Limits::default());
307        assert_eq!(pcm.map(|p| p.millis()), Ok(1000));
308    }
309
310    proptest! {
311        /// Frames to milliseconds and back loses less than a millisecond's worth.
312        #[test]
313        fn frames_and_millis_round_trip(rate in 1u32..=192_000, frames in 0u64..1 << 40) {
314            let format = PcmFormat { rate, channels: Channels::Mono };
315            let millis = format.millis_of(frames).unwrap_or(0);
316            let back = format.frames_of_millis(millis).unwrap_or(0);
317            prop_assert!(back <= frames);
318            prop_assert!(frames - back <= u64::from(rate) / 1000 + 1);
319        }
320
321        /// However many samples, `new` answers and never panics, and what it
322        /// holds is whole frames.
323        #[test]
324        fn pcm_new_never_panics(count in 0usize..64, stereo: bool, limit in 0u64..64) {
325            let channels = if stereo { Channels::Stereo } else { Channels::Mono };
326            let format = PcmFormat { rate: 8_000, channels };
327            let limits = Limits::new(8_000, limit);
328            if let Ok(pcm) = Pcm::new(format, vec![0; count], &limits) {
329                prop_assert!(pcm.frames() <= limit);
330                prop_assert_eq!(pcm.samples().len() % usize::from(channels.count()), 0);
331            }
332        }
333    }
334}