Skip to main content

mirage_engine/sound/
data.rs

1use core::time::Duration;
2use std::sync::Arc;
3
4/// A sound's samples, or the loaded bytes they are decoded from.
5///
6/// Build one by hand for computed audio; a loaded one comes from
7/// [`Assets::sound`](crate::Assets::sound).
8#[derive(Clone, Debug)]
9pub struct SoundData {
10    source: Source,
11}
12
13impl SoundData {
14    /// A sound of `rate` frames a second, one sample per frame.
15    ///
16    /// The rate must be more than zero; checked only in debug builds.
17    pub fn mono(rate: u32, samples: Vec<f32>) -> Self {
18        Self::from_samples(SampleRate::new(rate), Channels::Mono, samples)
19    }
20
21    /// A sound of `rate` frames a second, `samples` interleaved left then
22    /// right.
23    ///
24    /// The rate must be more than zero and the length even; checked only in
25    /// debug builds.
26    pub fn stereo(rate: u32, samples: Vec<f32>) -> Self {
27        debug_assert_eq!(
28            samples.len() % 2,
29            0,
30            "a stereo sound needs a left and a right sample per frame"
31        );
32
33        Self::from_samples(SampleRate::new(rate), Channels::Stereo, samples)
34    }
35
36    /// How long the sound plays for, at its own pitch.
37    pub fn duration(&self) -> Duration {
38        self.rate().duration_of(self.frames())
39    }
40
41    /// Decodes the sound while it plays, in place of keeping every sample in
42    /// memory.
43    ///
44    /// For music and other long loaded clips. A sound built by hand is
45    /// already in memory, so this returns it as it is, with a debug log.
46    #[must_use]
47    pub fn streamed(self) -> Self {
48        let source = match self.source {
49            Source::Resident(clip) | Source::Streamed(clip) => Source::Streamed(clip),
50            Source::Samples(samples) => {
51                log::debug!("a sound built out of samples is already in memory, not streamed");
52                Source::Samples(samples)
53            }
54        };
55
56        Self { source }
57    }
58
59    /// A sound with nothing to play — what a name that never resolved
60    /// becomes.
61    pub(crate) fn empty() -> Self {
62        Self::from_samples(SampleRate::new(1), Channels::Mono, Vec::new())
63    }
64
65    /// A loaded clip, decoded before it plays until [`SoundData::streamed`]
66    /// sets it streamed.
67    pub(crate) fn loaded(clip: Arc<Encoded>) -> Self {
68        Self {
69            source: Source::Resident(clip),
70        }
71    }
72
73    pub(crate) fn source(&self) -> &Source {
74        &self.source
75    }
76
77    pub(crate) fn rate(&self) -> SampleRate {
78        match &self.source {
79            Source::Samples(samples) => samples.rate,
80            Source::Resident(clip) | Source::Streamed(clip) => clip.rate(),
81        }
82    }
83
84    pub(crate) fn channels(&self) -> Channels {
85        match &self.source {
86            Source::Samples(samples) => samples.channels,
87            Source::Resident(clip) | Source::Streamed(clip) => clip.channels(),
88        }
89    }
90
91    fn frames(&self) -> u64 {
92        match &self.source {
93            Source::Samples(samples) => {
94                samples.values.len() as u64 / samples.channels.count() as u64
95            }
96            Source::Resident(clip) | Source::Streamed(clip) => clip.frames(),
97        }
98    }
99
100    fn from_samples(rate: SampleRate, channels: Channels, samples: Vec<f32>) -> Self {
101        Self {
102            source: Source::Samples(Samples {
103                rate,
104                channels,
105                values: samples.into(),
106            }),
107        }
108    }
109}
110
111/// Source of a sound's samples, and whether a loaded one is decoded before
112/// it plays or while it plays.
113#[derive(Clone, Debug)]
114pub(crate) enum Source {
115    Samples(Samples),
116    Resident(Arc<Encoded>),
117    Streamed(Arc<Encoded>),
118}
119
120/// Samples in memory, interleaved, one value per channel per frame.
121#[derive(Clone, Debug)]
122pub(crate) struct Samples {
123    pub(crate) rate: SampleRate,
124    pub(crate) channels: Channels,
125    pub(crate) values: Arc<[f32]>,
126}
127
128/// How many samples one frame of a sound holds: one, or two for left and
129/// right.
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub(crate) enum Channels {
132    Mono,
133    Stereo,
134}
135
136impl Channels {
137    pub(crate) fn count(self) -> usize {
138        match self {
139            Self::Mono => 1,
140            Self::Stereo => 2,
141        }
142    }
143
144    /// The channels a source with `count` of them decodes to: `1` stays
145    /// mono, anything else becomes stereo by keeping only the first two
146    /// channels and dropping the rest.
147    pub(crate) fn of(count: u8) -> Self {
148        match count {
149            1 => Self::Mono,
150            _ => Self::Stereo,
151        }
152    }
153}
154
155/// A sound the engine can play: samples already in memory, or an encoded
156/// clip decoded a packet at a time as it plays.
157#[derive(Debug)]
158pub(crate) struct Clip {
159    pub(crate) rate: SampleRate,
160    pub(crate) channels: Channels,
161    pub(crate) frames: u64,
162    pub(crate) body: Body,
163}
164
165/// Source of a clip's samples while it plays.
166#[derive(Debug)]
167pub(crate) enum Body {
168    Samples(Arc<[f32]>),
169    Encoded(Arc<Encoded>),
170}
171
172/// A loaded clip's bytes exactly as its source held them, plus the rate,
173/// channels, and frame count that decoding it once at startup reported.
174#[derive(Debug)]
175pub(crate) struct Encoded {
176    bytes: Arc<[u8]>,
177    rate: SampleRate,
178    channels: Channels,
179    frames: u64,
180    /// A frame position about every second, ascending, that a seek starts
181    /// from.
182    seeks: Vec<ClipFrame>,
183}
184
185impl Encoded {
186    pub(crate) fn new(
187        bytes: Arc<[u8]>,
188        rate: SampleRate,
189        channels: Channels,
190        frames: u64,
191        seeks: Vec<ClipFrame>,
192    ) -> Self {
193        Self {
194            bytes,
195            rate,
196            channels,
197            frames,
198            seeks,
199        }
200    }
201
202    pub(crate) fn bytes(&self) -> &Arc<[u8]> {
203        &self.bytes
204    }
205
206    pub(crate) fn rate(&self) -> SampleRate {
207        self.rate
208    }
209
210    pub(crate) fn channels(&self) -> Channels {
211        self.channels
212    }
213
214    pub(crate) fn frames(&self) -> u64 {
215        self.frames
216    }
217
218    /// The last indexed position at or before `frame`, which a seek to it
219    /// starts from.
220    pub(crate) fn seek_before(&self, frame: ClipFrame) -> ClipFrame {
221        self.seeks
222            .partition_point(|&indexed| indexed <= frame)
223            .checked_sub(1)
224            .map_or(ClipFrame::ZERO, |at| self.seeks[at])
225    }
226}
227
228/// Frames of audio a second; more than zero.
229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230pub(crate) struct SampleRate(u32);
231
232impl SampleRate {
233    /// `rate` frames a second; the rate must be more than zero, checked only
234    /// in debug builds, and held to at least one otherwise.
235    pub(crate) const fn new(rate: u32) -> Self {
236        debug_assert!(
237            rate > 0,
238            "a sound needs a rate of more than zero frames a second"
239        );
240
241        Self(if rate > 0 { rate } else { 1 })
242    }
243
244    /// How long `frames` take to play at this rate.
245    pub(crate) fn duration_of(self, frames: u64) -> Duration {
246        Duration::from_secs_f64(frames as f64 / f64::from(self.0))
247    }
248
249    /// The frame count at `rate` that spans the same duration as `frames` of
250    /// this rate.
251    pub(crate) fn frames_at(self, frames: u64, rate: SampleRate) -> u64 {
252        (frames * u64::from(rate)).div_ceil(u64::from(self))
253    }
254
255    /// The clip frame nearest to `span` at this rate: `span` is a
256    /// nanosecond count, so it rounds to that frame, not the frame before
257    /// it.
258    pub(crate) fn frame_at(self, span: Duration) -> ClipFrame {
259        ClipFrame::new((span.as_secs_f64() * f64::from(self.0)).round() as u64)
260    }
261}
262
263/// A position on a clip's own timeline, in frames.
264#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
265pub(crate) struct ClipFrame(u64);
266
267impl ClipFrame {
268    pub(crate) const ZERO: Self = Self(0);
269
270    pub(crate) const fn new(frame: u64) -> Self {
271        Self(frame)
272    }
273
274    pub(crate) const fn get(self) -> u64 {
275        self.0
276    }
277
278    /// Frames from `other` up to `self`; zero when `other` is past `self`.
279    pub(crate) fn saturating_sub(self, other: Self) -> u64 {
280        self.0.saturating_sub(other.0)
281    }
282
283    /// `self` less `count` frames, held at zero rather than wrapping under
284    /// it.
285    pub(crate) fn back(self, count: u64) -> Self {
286        Self(self.0.saturating_sub(count))
287    }
288}
289
290impl core::ops::Add for ClipFrame {
291    type Output = Self;
292
293    fn add(self, other: Self) -> Self {
294        Self(self.0 + other.0)
295    }
296}
297
298impl core::ops::Sub for ClipFrame {
299    type Output = Self;
300
301    fn sub(self, other: Self) -> Self {
302        Self(self.0 - other.0)
303    }
304}
305
306impl core::ops::Rem for ClipFrame {
307    type Output = Self;
308
309    fn rem(self, other: Self) -> Self {
310        Self(self.0 % other.0)
311    }
312}
313
314impl core::ops::Add<u64> for ClipFrame {
315    type Output = Self;
316
317    fn add(self, count: u64) -> Self {
318        Self(self.0 + count)
319    }
320}
321
322impl core::ops::AddAssign<u64> for ClipFrame {
323    fn add_assign(&mut self, count: u64) {
324        self.0 += count;
325    }
326}
327
328impl From<SampleRate> for u64 {
329    fn from(rate: SampleRate) -> Self {
330        Self::from(rate.0)
331    }
332}
333
334impl From<SampleRate> for f64 {
335    fn from(rate: SampleRate) -> Self {
336        f64::from(rate.0)
337    }
338}
339
340impl From<SampleRate> for f32 {
341    fn from(rate: SampleRate) -> Self {
342        rate.0 as f32
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn a_sound_lasts_as_long_as_its_frames_take_at_its_rate() {
352        let mono = SoundData::mono(8_000, vec![0.0; 4_000]);
353        let stereo = SoundData::stereo(8_000, vec![0.0; 4_000]);
354
355        assert_eq!(mono.duration(), Duration::from_millis(500));
356        assert_eq!(stereo.duration(), Duration::from_millis(250));
357        assert_eq!(SoundData::empty().duration(), Duration::ZERO);
358    }
359
360    #[test]
361    fn a_seek_starts_from_the_last_indexed_position_before_it() {
362        let clip = Encoded::new(
363            Arc::from(&b""[..]),
364            SampleRate::new(44_100),
365            Channels::Mono,
366            132_300,
367            vec![
368                ClipFrame::new(0),
369                ClipFrame::new(44_100),
370                ClipFrame::new(88_200),
371            ],
372        );
373
374        assert_eq!(clip.seek_before(ClipFrame::new(0)).get(), 0);
375        assert_eq!(clip.seek_before(ClipFrame::new(44_099)).get(), 0);
376        assert_eq!(clip.seek_before(ClipFrame::new(44_100)).get(), 44_100);
377        assert_eq!(clip.seek_before(ClipFrame::new(120_000)).get(), 88_200);
378    }
379}