Skip to main content

mirage_engine/sound/
data.rs

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