good_web_game/
audio.rs

1//! Loading and playing sounds.
2//!
3//! Note that audio functionality in good-web-game is very different from ggez, as it uses quad-snd
4//! instead of rodio, for maximum portability.
5
6use crate::{filesystem, Context, GameResult};
7use std::collections::HashMap;
8
9#[cfg(all(feature = "audio", not(target_os = "ios")))]
10use quad_snd::{AudioContext as QuadSndContext, Sound as QuadSndSound};
11
12#[cfg(all(feature = "audio", not(target_os = "ios")))]
13pub use quad_snd::PlaySoundParams;
14
15#[cfg(any(not(feature = "audio"), target_os = "ios"))]
16mod dummy_audio {
17    use crate::audio::PlaySoundParams;
18
19    pub struct AudioContext {}
20
21    impl AudioContext {
22        pub fn new() -> AudioContext {
23            AudioContext {}
24        }
25
26        pub fn pause(&mut self) {}
27
28        pub fn resume(&mut self) {}
29    }
30
31    pub struct Sound {}
32
33    impl Sound {
34        pub fn load(_ctx: &mut AudioContext, _data: &[u8]) -> Sound {
35            Sound {}
36        }
37
38        pub fn is_loaded(&self) -> bool {
39            true
40        }
41
42        pub fn play(&mut self, _ctx: &mut AudioContext, _params: PlaySoundParams) {}
43
44        pub fn stop(&mut self, _ctx: &mut AudioContext) {}
45
46        pub fn set_volume(&mut self, _ctx: &mut AudioContext, _volume: f32) {}
47    }
48}
49
50#[cfg(any(not(feature = "audio"), target_os = "ios"))]
51use dummy_audio::{AudioContext as QuadSndContext, Sound as QuadSndSound};
52
53#[cfg(any(not(feature = "audio"), target_os = "ios"))]
54pub struct PlaySoundParams {
55    pub looped: bool,
56    pub volume: f32,
57}
58
59pub struct AudioContext {
60    native_ctx: QuadSndContext,
61    sounds: HashMap<usize, QuadSndSound>,
62    id: usize,
63}
64
65impl AudioContext {
66    pub fn new() -> AudioContext {
67        AudioContext {
68            native_ctx: QuadSndContext::new(),
69            sounds: HashMap::new(),
70            id: 0,
71        }
72    }
73
74    #[cfg(target_os = "android")]
75    pub fn pause(&mut self) {
76        self.native_ctx.pause()
77    }
78
79    #[cfg(target_os = "android")]
80    pub fn resume(&mut self) {
81        self.native_ctx.resume()
82    }
83}
84
85impl Default for AudioContext {
86    fn default() -> Self {
87        AudioContext::new()
88    }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct Sound(usize);
93
94pub struct Source {
95    sound: Sound,
96    params: PlaySoundParams,
97}
98
99impl Source {
100    /// Load audio file.
101    ///
102    /// Attempts to automatically detect the format of the source of data.
103    pub fn new(ctx: &mut Context, path: &str) -> GameResult<Source> {
104        use std::io::Read;
105
106        let mut file = filesystem::open(ctx, path)?;
107
108        let mut bytes = vec![];
109        file.bytes.read_to_end(&mut bytes)?;
110
111        Self::from_bytes(ctx, bytes.as_slice())
112    }
113
114    /// Load audio from file.
115    ///
116    /// Attempts to automatically detect the format of the source of data.
117    pub fn from_bytes(ctx: &mut Context, bytes: &[u8]) -> GameResult<Source> {
118        let sound = QuadSndSound::load(&mut ctx.audio_context.native_ctx, bytes);
119
120        // only on wasm the sound is not ready right away
121        #[cfg(target_arch = "wasm32")]
122        while sound.is_loaded() {
123            std::thread::yield_now();
124        }
125
126        let id = ctx.audio_context.id;
127        ctx.audio_context.sounds.insert(id, sound);
128        ctx.audio_context.id += 1;
129        Ok(Source {
130            sound: Sound(id),
131            params: PlaySoundParams::default(),
132        })
133    }
134
135    pub fn play(&self, ctx: &mut Context) -> GameResult<()> {
136        let ctx = &mut ctx.audio_context;
137        let sound = &mut ctx.sounds.get_mut(&self.sound.0).unwrap();
138
139        let params = PlaySoundParams {
140            looped: self.params.looped,
141            volume: self.params.volume,
142        };
143        sound.play(&mut ctx.native_ctx, params);
144        Ok(())
145    }
146
147    pub fn stop(&self, ctx: &mut Context) -> GameResult {
148        let ctx = &mut ctx.audio_context;
149        let sound = &mut ctx.sounds.get_mut(&self.sound.0).unwrap();
150
151        sound.stop(&mut ctx.native_ctx);
152        Ok(())
153    }
154
155    pub fn set_volume(&mut self, ctx: &mut Context, volume: f32) -> GameResult<()> {
156        let ctx = &mut ctx.audio_context;
157        self.params.volume = volume;
158        let sound = &mut ctx.sounds.get_mut(&self.sound.0).unwrap();
159
160        sound.set_volume(&mut ctx.native_ctx, volume);
161        Ok(())
162    }
163
164    pub fn volume(&self) -> f32 {
165        self.params.volume
166    }
167
168    pub fn set_repeat(&mut self, repeat: bool) {
169        self.params.looped = repeat;
170    }
171
172    pub fn repeat(&self) -> bool {
173        self.params.looped
174    }
175}