Skip to main content

shuyi_ui/audio/
samples.rs

1//! [`Samples`]: the sound a game makes at run time, 16-bit samples at a rate,
2//! mono or stereo, whole when it is made.
3//! [`AudioQueue::add_sound`](super::AudioQueue::add_sound) makes them a cue;
4//! every backend reads them as they are.
5
6use crate::prelude::*;
7
8// What two owners both hold. A target without shared atomic counts has one thread.
9#[cfg(not(target_has_atomic = "ptr"))]
10use alloc::rc::Rc as Shared;
11#[cfg(target_has_atomic = "ptr")]
12use alloc::sync::Arc as Shared;
13
14/// [`Samples`] that the queue and whatever plays them share, never copied
15/// on their way.
16pub type SharedSamples = Shared<Samples>;
17
18/// Why [`Samples`] couldn't be made.
19#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
20#[non_exhaustive]
21pub enum SamplesError {
22    /// A sound has one or two channels.
23    #[error("{0} channels: a sound has one or two")]
24    Channels(u32),
25    /// A rate of zero, or over [`Samples::MAX_RATE`].
26    #[error("{0} samples a second: it must be from 1 to {max}", max = Samples::MAX_RATE)]
27    Rate(u32),
28    /// Samples that don't divide into frames.
29    #[error("{samples} samples aren't whole frames of {channels} channels")]
30    PartialFrame {
31        /// How many samples there are.
32        samples: usize,
33        /// How many channels a frame has.
34        channels: u16,
35    },
36}
37
38/// A sound made at run time: 16-bit samples, interleaved (left first in
39/// stereo), at a rate. Any rate from 1 to [`Self::MAX_RATE`]: a backend
40/// plays it at its own.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Samples {
43    rate: u32,
44    channels: u16,
45    data: Vec<i16>,
46}
47
48impl Samples {
49    /// The highest rate, in samples a second.
50    pub const MAX_RATE: u32 = 192_000;
51
52    /// `samples`, interleaved, at `rate` with `channels` (1 or 2), if they
53    /// are whole frames.
54    pub fn new(rate: u32, channels: u32, samples: Vec<i16>) -> Result<Self, SamplesError> {
55        let channels = match channels {
56            1 => 1,
57            2 => 2,
58            other => return Err(SamplesError::Channels(other)),
59        };
60        if rate == 0 || rate > Self::MAX_RATE {
61            return Err(SamplesError::Rate(rate));
62        }
63        if !samples.len().is_multiple_of(usize::from(channels)) {
64            return Err(SamplesError::PartialFrame {
65                samples: samples.len(),
66                channels,
67            });
68        }
69        Ok(Self {
70            rate,
71            channels,
72            data: samples,
73        })
74    }
75
76    /// `bytes` of 8-bit unsigned samples (128 is silence), as old formats keep
77    /// them, interleaved, at `rate` with `channels`: each becomes
78    /// `(b − 128) × 256`.
79    pub fn from_u8(rate: u32, channels: u32, bytes: &[u8]) -> Result<Self, SamplesError> {
80        let samples = bytes.iter().map(|&b| (i16::from(b) - 128) * 256).collect();
81        Self::new(rate, channels, samples)
82    }
83
84    /// Its rate, in frames a second.
85    pub const fn rate(&self) -> u32 {
86        self.rate
87    }
88
89    /// How many channels it has: 1 or 2.
90    pub const fn channels(&self) -> u16 {
91        self.channels
92    }
93
94    /// Its samples, interleaved.
95    pub fn samples(&self) -> &[i16] {
96        &self.data
97    }
98
99    /// Its samples, handed over.
100    pub fn into_samples(self) -> Vec<i16> {
101        self.data
102    }
103
104    /// How many frames it has.
105    pub fn frames(&self) -> u64 {
106        u64::try_from(self.data.len() / usize::from(self.channels)).unwrap_or(u64::MAX)
107    }
108
109    /// How long it plays, in whole milliseconds rounded down.
110    pub fn millis(&self) -> u64 {
111        let rate = u64::from(self.rate);
112        let frames = self.frames();
113        (frames / rate)
114            .saturating_mul(1000)
115            .saturating_add(frames % rate * 1000 / rate)
116    }
117
118    /// The bytes its samples hold: two a sample.
119    pub fn bytes(&self) -> u64 {
120        u64::try_from(self.data.len())
121            .unwrap_or(u64::MAX)
122            .saturating_mul(2)
123    }
124
125    /// It as a WAV file of 16-bit integer samples, at its rate and channels:
126    /// for a device that loads a sound from a file's bytes.
127    pub fn to_wav(&self) -> Vec<u8> {
128        let block = self.channels * 2;
129        let data_len = wav_data_bytes(self.data.len(), block);
130        let written = usize::try_from(data_len / 2).unwrap_or(0);
131        let mut out = Vec::with_capacity(44 + written * 2);
132        out.extend_from_slice(b"RIFF");
133        out.extend_from_slice(&(36 + data_len).to_le_bytes());
134        out.extend_from_slice(b"WAVEfmt ");
135        out.extend_from_slice(&16u32.to_le_bytes());
136        out.extend_from_slice(&1u16.to_le_bytes());
137        out.extend_from_slice(&self.channels.to_le_bytes());
138        out.extend_from_slice(&self.rate.to_le_bytes());
139        let byte_rate = self.rate.saturating_mul(u32::from(block));
140        out.extend_from_slice(&byte_rate.to_le_bytes());
141        out.extend_from_slice(&block.to_le_bytes());
142        out.extend_from_slice(&16u16.to_le_bytes());
143        out.extend_from_slice(b"data");
144        out.extend_from_slice(&data_len.to_le_bytes());
145        for sample in &self.data[..written] {
146            out.extend_from_slice(&sample.to_le_bytes());
147        }
148        out
149    }
150}
151
152/// The bytes of samples a WAV of `samples` samples, `block` bytes a frame,
153/// holds: all of them, or the whole frames its 32-bit sizes can say.
154fn wav_data_bytes(samples: usize, block: u16) -> u32 {
155    let room = (u32::MAX - 36) / u32::from(block) * u32::from(block);
156    u32::try_from(samples.saturating_mul(2)).map_or(room, |bytes| bytes.min(room))
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn eight_bit_unsigned_samples_centre_on_128() {
165        let made = Samples::from_u8(11_025, 1, &[128, 255, 0, 129, 127]);
166        let made = made.unwrap_or_else(|e| panic!("{e}"));
167        assert_eq!(made.samples(), [0, 0x7F00, i16::MIN, 256, -256]);
168        assert_eq!((made.rate(), made.channels()), (11_025, 1));
169        let stereo = Samples::from_u8(8_000, 2, &[0, 255]);
170        let stereo = stereo.unwrap_or_else(|e| panic!("{e}"));
171        assert_eq!((stereo.channels(), stereo.frames()), (2, 1));
172        assert_eq!(
173            Samples::from_u8(8_000, 2, &[1, 2, 3]),
174            Err(SamplesError::PartialFrame {
175                samples: 3,
176                channels: 2
177            })
178        );
179        assert_eq!(Samples::from_u8(0, 1, &[1]), Err(SamplesError::Rate(0)));
180        assert_eq!(
181            Samples::from_u8(192_001, 1, &[1]),
182            Err(SamplesError::Rate(192_001))
183        );
184        assert_eq!(
185            Samples::from_u8(8_000, 3, &[1]),
186            Err(SamplesError::Channels(3))
187        );
188        assert_eq!(
189            Samples::new(8_000, 0, vec![]),
190            Err(SamplesError::Channels(0))
191        );
192    }
193
194    #[test]
195    fn length_and_size_are_exact() {
196        let second = Samples::new(44_100, 2, vec![0; 88_200]);
197        let second = second.unwrap_or_else(|e| panic!("{e}"));
198        assert_eq!(
199            (second.frames(), second.millis(), second.bytes()),
200            (44_100, 1000, 176_400)
201        );
202        let short = Samples::new(11_025, 1, vec![0; 1102]).unwrap_or_else(|e| panic!("{e}"));
203        assert_eq!(short.millis(), 99);
204        assert_eq!(short.clone().into_samples().len(), 1102);
205        let empty = Samples::new(1, 1, Vec::new()).unwrap_or_else(|e| panic!("{e}"));
206        assert_eq!((empty.frames(), empty.millis()), (0, 0));
207    }
208
209    #[test]
210    fn a_wav_holds_the_samples_after_a_44_byte_header() {
211        let made = Samples::new(44_100, 2, vec![1, -2, 0x7FFF, i16::MIN]);
212        let wav = made.unwrap_or_else(|e| panic!("{e}")).to_wav();
213        assert_eq!(wav.len(), 44 + 8);
214        assert_eq!(&wav[..4], b"RIFF");
215        assert_eq!(wav[4..8], 44u32.to_le_bytes());
216        assert_eq!(&wav[8..16], b"WAVEfmt ");
217        assert_eq!(wav[16..20], 16u32.to_le_bytes());
218        // Integer samples, two channels, at 44.1 kHz, four bytes a frame, 16 bits.
219        assert_eq!(wav[20..22], 1u16.to_le_bytes());
220        assert_eq!(wav[22..24], 2u16.to_le_bytes());
221        assert_eq!(wav[24..28], 44_100u32.to_le_bytes());
222        assert_eq!(wav[28..32], (44_100u32 * 4).to_le_bytes());
223        assert_eq!(wav[32..34], 4u16.to_le_bytes());
224        assert_eq!(wav[34..36], 16u16.to_le_bytes());
225        assert_eq!(&wav[36..40], b"data");
226        assert_eq!(wav[40..44], 8u32.to_le_bytes());
227        assert_eq!(wav[44..], [1, 0, 0xFE, 0xFF, 0xFF, 0x7F, 0, 0x80]);
228    }
229
230    #[test]
231    fn a_mono_wav_has_two_bytes_a_frame() {
232        let made = Samples::new(8_000, 1, vec![5, -5, 7]).unwrap_or_else(|e| panic!("{e}"));
233        let wav = made.to_wav();
234        assert_eq!(wav.len(), 44 + 6);
235        assert_eq!(wav[4..8], 42u32.to_le_bytes());
236        assert_eq!(wav[22..24], 1u16.to_le_bytes());
237        assert_eq!(wav[28..32], 16_000u32.to_le_bytes());
238        assert_eq!(wav[32..34], 2u16.to_le_bytes());
239        assert_eq!(wav[40..44], 6u32.to_le_bytes());
240        assert_eq!(wav[44..], [5, 0, 0xFB, 0xFF, 7, 0]);
241    }
242
243    #[test]
244    fn a_wav_says_all_its_samples_or_the_whole_frames_32_bits_can() {
245        // Room for 4,294,967,259 bytes after the header, in whole frames.
246        assert_eq!(wav_data_bytes(0, 2), 0);
247        assert_eq!(wav_data_bytes(3, 2), 6);
248        assert_eq!(wav_data_bytes(2_147_483_629, 2), 4_294_967_258);
249        assert_eq!(wav_data_bytes(2_147_483_630, 2), 4_294_967_258);
250        assert_eq!(wav_data_bytes(2_147_483_628, 4), 4_294_967_256);
251        assert_eq!(wav_data_bytes(2_147_483_630, 4), 4_294_967_256);
252        assert_eq!(wav_data_bytes(usize::MAX, 2), 4_294_967_258);
253        assert_eq!(wav_data_bytes(usize::MAX, 4), 4_294_967_256);
254    }
255
256    proptest::proptest! {
257        /// Whatever it is asked, `new` answers and never panics, and what it
258        /// holds is whole frames at a rate within the limit.
259        #[test]
260        fn new_never_panics(count in 0usize..64, channels in 0u32..4, rate in 0u32..200_000) {
261            if let Ok(made) = Samples::new(rate, channels, vec![0; count]) {
262                proptest::prop_assert!((1..=Samples::MAX_RATE).contains(&made.rate()));
263                let channels = usize::from(made.channels());
264                proptest::prop_assert_eq!(made.samples().len() % channels, 0);
265                proptest::prop_assert_eq!(made.to_wav().len(), 44 + count * 2);
266            }
267        }
268    }
269}