Skip to main content

game_toolkit_audio/
stream.rs

1//! Audio generated while it plays: [`AudioStream`] sources and their [`StreamHandle`].
2//!
3//! A stream is pulled from the audio callback itself, so it needs no decoder thread and works
4//! on the web as well as natively. Keep [`AudioStream::fill`] cheap and non-blocking: it runs
5//! on the audio thread.
6
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
9
10use kira::sound::{Sound, SoundData};
11use kira::{Frame, info::Info};
12
13/// A source of mono samples, produced on demand at its own sample rate.
14///
15/// Implemented for any `FnMut(&mut [f32]) + Send + 'static`, so a closure that owns a
16/// synthesizer or an emulated sound chip can be played directly.
17pub trait AudioStream: Send + 'static {
18    /// Overwrite `out` with the next samples in `-1.0..=1.0`.
19    fn fill(&mut self, out: &mut [f32]);
20}
21
22impl<F: FnMut(&mut [f32]) + Send + 'static> AudioStream for F {
23    fn fill(&mut self, out: &mut [f32]) {
24        self(out);
25    }
26}
27
28/// A source of stereo frames (`[left, right]`), produced on demand at its own sample rate.
29///
30/// Implemented for any `FnMut(&mut [[f32; 2]]) + Send + 'static`; played with
31/// [`crate::Audio::play_stereo_stream`]. Otherwise behaves like [`AudioStream`].
32pub trait StereoStream: Send + 'static {
33    /// Overwrite `out` with the next frames, each channel in `-1.0..=1.0`.
34    fn fill(&mut self, out: &mut [[f32; 2]]);
35}
36
37impl<F: FnMut(&mut [[f32; 2]]) + Send + 'static> StereoStream for F {
38    fn fill(&mut self, out: &mut [[f32; 2]]) {
39        self(out);
40    }
41}
42
43/// Either kind of stream; mono samples are played on both channels.
44pub(crate) enum Source {
45    Mono(Box<dyn AudioStream>, Vec<f32>),
46    Stereo(Box<dyn StereoStream>),
47}
48
49impl Source {
50    pub(crate) fn mono(stream: Box<dyn AudioStream>) -> Self {
51        Source::Mono(stream, vec![0.0; CHUNK])
52    }
53
54    fn fill(&mut self, out: &mut [[f32; 2]]) {
55        match self {
56            Source::Mono(stream, buf) => {
57                buf.resize(out.len(), 0.0);
58                stream.fill(buf);
59                for (o, &s) in out.iter_mut().zip(buf.iter()) {
60                    *o = [s, s];
61                }
62            }
63            Source::Stereo(stream) => stream.fill(out),
64        }
65    }
66}
67
68/// Samples requested from a stream at once.
69const CHUNK: usize = 512;
70
71/// State shared between a [`StreamHandle`] and the playing sound.
72#[derive(Debug)]
73struct Shared {
74    /// Linear amplitude as `f32` bits.
75    volume: AtomicU32,
76    paused: AtomicBool,
77    stopped: AtomicBool,
78    /// Still queued until audio starts (see [`crate::PlayOptions::deferred`]).
79    pending: AtomicBool,
80}
81
82/// Controls one playing [`AudioStream`], as returned by [`crate::Audio::play_stream`].
83///
84/// Dropping the handle does not stop the stream; it plays until [`StreamHandle::stop`].
85/// Clones refer to the same stream. Changes apply from the next audio block, and volume
86/// changes, pausing and stopping ramp over that block so they do not click.
87#[derive(Clone, Debug)]
88pub struct StreamHandle {
89    shared: Arc<Shared>,
90}
91
92impl StreamHandle {
93    fn new(volume: f32, pending: bool) -> Self {
94        Self {
95            shared: Arc::new(Shared {
96                // Clamped like set_volume: a negative start volume is silence, not an
97                // inverted signal.
98                volume: AtomicU32::new(volume.max(0.0).to_bits()),
99                paused: AtomicBool::new(false),
100                stopped: AtomicBool::new(false),
101                pending: AtomicBool::new(pending),
102            }),
103        }
104    }
105
106    /// Set the volume as a linear amplitude (`1.0` is the stream's own level, `0.0` silence).
107    pub fn set_volume(&self, volume: f32) {
108        self.shared
109            .volume
110            .store(volume.max(0.0).to_bits(), Ordering::Relaxed);
111    }
112
113    /// The current linear volume.
114    pub fn volume(&self) -> f32 {
115        f32::from_bits(self.shared.volume.load(Ordering::Relaxed))
116    }
117
118    /// Pause: the stream fades out over one audio block, then outputs silence and is not
119    /// asked for samples until resumed.
120    pub fn pause(&self) {
121        self.shared.paused.store(true, Ordering::Relaxed);
122    }
123
124    /// Resume a paused stream where it left off, fading back in over one audio block.
125    pub fn resume(&self) {
126        self.shared.paused.store(false, Ordering::Relaxed);
127    }
128
129    /// Stop the stream for good; it fades out over one audio block and is then dropped from
130    /// the mixer.
131    pub fn stop(&self) {
132        self.shared.stopped.store(true, Ordering::Relaxed);
133    }
134
135    /// Whether the stream is audible: started, not paused and not stopped.
136    pub fn is_playing(&self) -> bool {
137        !self.is_pending() && !self.is_paused() && !self.is_finished()
138    }
139
140    /// Whether the stream is paused.
141    pub fn is_paused(&self) -> bool {
142        self.shared.paused.load(Ordering::Relaxed)
143    }
144
145    /// Whether the stream is still waiting for audio to start.
146    pub fn is_pending(&self) -> bool {
147        self.shared.pending.load(Ordering::Relaxed)
148    }
149
150    /// Whether the stream was stopped.
151    pub fn is_finished(&self) -> bool {
152        self.shared.stopped.load(Ordering::Relaxed)
153    }
154
155    pub(crate) fn mark_started(&self) {
156        self.shared.pending.store(false, Ordering::Relaxed);
157    }
158}
159
160/// A stream ready to hand to kira.
161pub(crate) struct StreamData {
162    stream: Source,
163    sample_rate: u32,
164    handle: StreamHandle,
165}
166
167impl StreamData {
168    pub(crate) fn new(
169        stream: Source,
170        sample_rate: u32,
171        volume: f32,
172        pending: bool,
173    ) -> (Self, StreamHandle) {
174        let handle = StreamHandle::new(volume, pending);
175        let data = Self {
176            stream,
177            sample_rate: sample_rate.max(1),
178            handle: handle.clone(),
179        };
180        (data, handle)
181    }
182
183    pub(crate) fn handle(&self) -> &StreamHandle {
184        &self.handle
185    }
186}
187
188impl SoundData for StreamData {
189    type Error = std::convert::Infallible;
190    type Handle = ();
191
192    fn into_sound(self) -> Result<(Box<dyn Sound>, ()), Self::Error> {
193        let sound = StreamSound::new(self.stream, self.sample_rate, self.handle.shared);
194        Ok((Box::new(sound), ()))
195    }
196}
197
198/// The kira side: pulls chunks from the stream and resamples them linearly to the output
199/// rate.
200struct StreamSound {
201    stream: Source,
202    sample_rate: f64,
203    shared: Arc<Shared>,
204    chunk: Vec<[f32; 2]>,
205    next_in_chunk: usize,
206    /// The two stream frames the output position lies between, and how far along it is.
207    previous: [f32; 2],
208    current: [f32; 2],
209    phase: f64,
210    /// Whether `previous` and `current` hold the first two samples yet.
211    primed: bool,
212    /// The gain the last block ended on; each block ramps from it to the target gain.
213    applied_volume: f32,
214}
215
216impl StreamSound {
217    fn new(stream: Source, sample_rate: u32, shared: Arc<Shared>) -> Self {
218        // Start at the target volume (or silence if paused) so playback does not fade in.
219        let applied_volume = if shared.paused.load(Ordering::Relaxed) {
220            0.0
221        } else {
222            f32::from_bits(shared.volume.load(Ordering::Relaxed))
223        };
224        Self {
225            stream,
226            sample_rate: f64::from(sample_rate),
227            shared,
228            chunk: vec![[0.0; 2]; CHUNK],
229            next_in_chunk: CHUNK,
230            previous: [0.0; 2],
231            current: [0.0; 2],
232            phase: 0.0,
233            primed: false,
234            applied_volume,
235        }
236    }
237
238    fn next_sample(&mut self) -> [f32; 2] {
239        if self.next_in_chunk == self.chunk.len() {
240            self.stream.fill(&mut self.chunk);
241            self.next_in_chunk = 0;
242        }
243        let s = self.chunk[self.next_in_chunk];
244        self.next_in_chunk += 1;
245        s
246    }
247
248    /// Produces `out` at an output frame duration of `dt` seconds.
249    fn render(&mut self, out: &mut [Frame], dt: f64) {
250        let silenced = self.shared.paused.load(Ordering::Relaxed)
251            || self.shared.stopped.load(Ordering::Relaxed);
252        let target = if silenced {
253            0.0
254        } else {
255            f32::from_bits(self.shared.volume.load(Ordering::Relaxed))
256        };
257        if silenced && self.applied_volume == 0.0 {
258            out.fill(Frame::ZERO);
259            return;
260        }
261        if !self.primed {
262            self.previous = self.next_sample();
263            self.current = self.next_sample();
264            self.primed = true;
265        }
266        let from = self.applied_volume;
267        let len = out.len() as f32;
268        let step = self.sample_rate * dt;
269        for (i, frame) in out.iter_mut().enumerate() {
270            // Linear ramp that lands exactly on `target` at the block's last frame.
271            let volume = from + (target - from) * ((i + 1) as f32 / len);
272            let t = self.phase as f32;
273            let l = self.previous[0] + (self.current[0] - self.previous[0]) * t;
274            let r = self.previous[1] + (self.current[1] - self.previous[1]) * t;
275            *frame = Frame::new(l * volume, r * volume);
276            self.phase += step;
277            while self.phase >= 1.0 {
278                self.phase -= 1.0;
279                self.previous = self.current;
280                self.current = self.next_sample();
281            }
282        }
283        if !out.is_empty() {
284            self.applied_volume = target;
285        }
286    }
287}
288
289impl Sound for StreamSound {
290    fn process(&mut self, out: &mut [Frame], dt: f64, _info: &Info<'_>) {
291        self.render(out, dt);
292    }
293
294    fn finished(&self) -> bool {
295        // Only once the fade-out block has played, so stopping does not click.
296        self.shared.stopped.load(Ordering::Relaxed) && self.applied_volume == 0.0
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    fn sound(stream: impl AudioStream, rate: u32) -> (StreamSound, StreamHandle) {
305        sound_at(stream, rate, 1.0)
306    }
307
308    fn sound_at(stream: impl AudioStream, rate: u32, volume: f32) -> (StreamSound, StreamHandle) {
309        let (data, handle) = StreamData::new(Source::mono(Box::new(stream)), rate, volume, false);
310        let sound = StreamSound::new(data.stream, data.sample_rate, handle.shared.clone());
311        (sound, handle)
312    }
313
314    fn ramp() -> impl AudioStream {
315        let mut n = 0u32;
316        move |out: &mut [f32]| {
317            for s in out {
318                *s = n as f32;
319                n += 1;
320            }
321        }
322    }
323
324    #[test]
325    fn same_rate_passes_samples_through() {
326        let (mut s, _) = sound(ramp(), 100);
327        let mut out = [Frame::ZERO; 4];
328        s.render(&mut out, 1.0 / 100.0);
329        let left: Vec<f32> = out.iter().map(|f| f.left).collect();
330        assert_eq!(left, [0.0, 1.0, 2.0, 3.0]);
331    }
332
333    #[test]
334    fn half_rate_interpolates() {
335        let (mut s, _) = sound(ramp(), 50);
336        let mut out = [Frame::ZERO; 6];
337        s.render(&mut out, 1.0 / 100.0);
338        let left: Vec<f32> = out.iter().map(|f| f.left).collect();
339        assert_eq!(left, [0.0, 0.5, 1.0, 1.5, 2.0, 2.5]);
340    }
341
342    #[test]
343    fn paused_and_stopped_streams_fade_out_then_go_silent() {
344        let (mut s, handle) = sound(|out: &mut [f32]| out.fill(1.0), 100);
345        let mut out = [Frame::ZERO; 4];
346        handle.pause();
347        s.render(&mut out, 0.01);
348        let left: Vec<f32> = out.iter().map(|f| f.left).collect();
349        assert_eq!(left, [0.75, 0.5, 0.25, 0.0]);
350        s.render(&mut out, 0.01);
351        assert!(out.iter().all(|f| *f == Frame::ZERO));
352
353        handle.resume();
354        s.render(&mut out, 0.01);
355        let left: Vec<f32> = out.iter().map(|f| f.left).collect();
356        assert_eq!(left, [0.25, 0.5, 0.75, 1.0]);
357
358        handle.stop();
359        assert!(handle.is_finished() && !handle.is_playing());
360        assert!(!s.finished(), "dropped before the fade-out block");
361        s.render(&mut out, 0.01);
362        assert_eq!(out[3].left, 0.0);
363        assert!(s.finished());
364    }
365
366    #[test]
367    fn a_paused_stream_is_not_asked_for_samples() {
368        use std::sync::atomic::AtomicUsize;
369
370        let pulled = Arc::new(AtomicUsize::new(0));
371        let counter = Arc::clone(&pulled);
372        let (mut s, handle) = sound(
373            move |out: &mut [f32]| {
374                counter.fetch_add(1, Ordering::Relaxed);
375                out.fill(1.0);
376            },
377            100,
378        );
379        let mut out = [Frame::ZERO; 4];
380        handle.pause();
381        s.render(&mut out, 0.01); // fade-out block
382        let after_fade = pulled.load(Ordering::Relaxed);
383        s.render(&mut out, 0.01);
384        s.render(&mut out, 0.01);
385        assert_eq!(pulled.load(Ordering::Relaxed), after_fade);
386    }
387
388    #[test]
389    fn the_mixer_pulls_samples_from_a_playing_stream() {
390        use std::sync::atomic::AtomicUsize;
391
392        use kira::backend::mock::{MockBackend, MockBackendSettings};
393        use kira::{AudioManager, AudioManagerSettings};
394
395        let pulled = Arc::new(AtomicUsize::new(0));
396        let counter = Arc::clone(&pulled);
397        let stream = move |out: &mut [f32]| {
398            counter.fetch_add(out.len(), Ordering::Relaxed);
399            out.fill(0.5);
400        };
401        let mut manager = AudioManager::<MockBackend>::new(AudioManagerSettings {
402            backend_settings: MockBackendSettings { sample_rate: 100 },
403            ..AudioManagerSettings::default()
404        })
405        .unwrap();
406        let (data, handle) = StreamData::new(Source::mono(Box::new(stream)), 100, 1.0, false);
407        manager.play(data).unwrap();
408        handle.mark_started();
409        manager.backend_mut().on_start_processing();
410        manager.backend_mut().process();
411        assert!(
412            pulled.load(Ordering::Relaxed) > 0,
413            "stream was never asked for samples"
414        );
415        assert!(handle.is_playing());
416
417        handle.stop();
418        manager.backend_mut().on_start_processing();
419        manager.backend_mut().process();
420        assert!(handle.is_finished());
421    }
422
423    #[test]
424    fn negative_start_volume_is_silence() {
425        let (mut s, handle) = sound_at(|out: &mut [f32]| out.fill(1.0), 100, -0.5);
426        assert_eq!(handle.volume(), 0.0);
427        let mut out = [Frame::ZERO; 2];
428        s.render(&mut out, 0.01);
429        assert!(out.iter().all(|f| f.left == 0.0 && f.right == 0.0));
430    }
431
432    #[test]
433    fn volume_changes_ramp_over_one_block() {
434        let (mut s, handle) = sound(|out: &mut [f32]| out.fill(1.0), 100);
435        handle.set_volume(0.25);
436        let mut out = [Frame::ZERO; 3];
437        s.render(&mut out, 0.01);
438        let left: Vec<f32> = out.iter().map(|f| f.left).collect();
439        assert_eq!(left, [0.75, 0.5, 0.25]);
440        s.render(&mut out, 0.01);
441        assert!(out.iter().all(|f| f.left == 0.25));
442    }
443
444    #[test]
445    fn a_stream_starts_at_its_volume_without_fading_in() {
446        let (mut s, _) = sound_at(|out: &mut [f32]| out.fill(1.0), 100, 0.5);
447        let mut out = [Frame::ZERO; 3];
448        s.render(&mut out, 0.01);
449        assert!(out.iter().all(|f| f.left == 0.5));
450    }
451
452    #[test]
453    fn stereo_frames_keep_their_channels() {
454        let mut n = 0u32;
455        let stream = move |out: &mut [[f32; 2]]| {
456            for f in out {
457                *f = [n as f32, -(n as f32)];
458                n += 1;
459            }
460        };
461        let (data, handle) = StreamData::new(Source::Stereo(Box::new(stream)), 50, 1.0, false);
462        let mut s = StreamSound::new(data.stream, data.sample_rate, handle.shared.clone());
463        let mut out = [Frame::ZERO; 4];
464        s.render(&mut out, 1.0 / 100.0);
465        let lr: Vec<(f32, f32)> = out.iter().map(|f| (f.left, f.right)).collect();
466        assert_eq!(lr, [(0.0, 0.0), (0.5, -0.5), (1.0, -1.0), (1.5, -1.5)]);
467    }
468}