simple_game_utils/
sound_effect.rs

1use crate::error::GameUtilError;
2use crate::timing::Timing;
3use audio_engine::{AudioEngine, Sound, WavDecoder};
4use std::fmt::{Debug, Formatter};
5use std::io::Cursor;
6
7/// Sound effect (although it can also be used for music)
8/// You must call [SoundEffect::update] or [SoundEffect::update_secs] with accurate values and often otherwise playback may stutter or jump
9///
10/// # Usage
11///
12/// ```no_run
13///# const BYTES: [u8; 6] = [0,0,0,0,0,0];
14///# fn main() {
15///# use audio_engine::AudioEngine;
16///# use simple_game_utils::sound_effect::NewSoundEffect;
17///# use simple_game_utils::timing::Timing;
18///# let mut  timing = Timing::new(240);
19///# let duration = 1.0;
20/// //this must live as long as `sound` but there's no lifetimes to enforce this
21/// let mut engine = AudioEngine::new().unwrap();
22/// let mut sound = engine.load_from_bytes(&BYTES, duration).unwrap();
23/// sound.play();
24/// loop {
25///     timing.update();
26///     sound.update(&timing);
27/// }
28///# }
29/// ```
30pub struct SoundEffect {
31    //Sound data
32    sound: Sound,
33    //If sound is currently playing
34    is_playing: bool,
35    //Length in seconds
36    duration: f64,
37    // used to prevent bugs
38    next_play_in: f64,
39    //If sound automatically loops
40    loops: bool,
41}
42
43impl Debug for SoundEffect {
44    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45        write!(
46            f,
47            "Sound: is_playing: {}, duration: {:.1}s, loops: {}",
48            self.is_playing, self.duration, self.loops
49        )
50    }
51}
52
53pub trait NewSoundEffect {
54    fn load_from_bytes(
55        &self,
56        bytes: &'static [u8],
57        duration: f64,
58    ) -> Result<SoundEffect, GameUtilError>;
59}
60
61impl NewSoundEffect for AudioEngine {
62    fn load_from_bytes(
63        &self,
64        bytes: &'static [u8],
65        duration: f64,
66    ) -> Result<SoundEffect, GameUtilError> {
67        let decoder =
68            WavDecoder::new(Cursor::new(bytes)).map_err(GameUtilError::SoundEffectInvalid)?;
69        let sound = self
70            .new_sound(decoder)
71            .map_err(GameUtilError::SoundEffectInit)?;
72        Ok(SoundEffect::new(sound, duration))
73    }
74}
75
76impl SoundEffect {
77    pub fn new(sound: Sound, duration: f64) -> Self {
78        Self {
79            sound,
80            is_playing: false,
81            duration,
82            next_play_in: 0.0,
83            loops: false,
84        }
85    }
86
87    /// Play sound effect, won't do anything if sound effect is already playing
88    pub fn play(&mut self) {
89        if !self.is_playing {
90            self.sound.play();
91            self.is_playing = true;
92            self.next_play_in = self.duration;
93        }
94    }
95
96    /// Reset playback position and stop playback
97    pub fn reset(&mut self) {
98        self.sound.stop();
99        self.is_playing = false;
100        self.next_play_in = 0.0;
101    }
102
103    /// Set if the sound loops automatically
104    pub fn set_loop(&mut self, loops: bool) {
105        self.loops = loops;
106        self.sound.set_loop(loops)
107    }
108
109    pub fn set_volume(&mut self, volume: f32) {
110        self.sound.set_volume(volume);
111    }
112
113    /// Returns true if calling [SoundEffect::play] will do anything
114    pub fn can_play(&self) -> bool {
115        !self.is_playing && self.next_play_in < 0.0
116    }
117
118    /// Allows the sound to continue playing
119    pub fn update(&mut self, timing: &Timing) {
120        self.update_secs(timing.fixed_time_step)
121    }
122
123    /// Allows the sound to continue playing
124    pub fn update_secs(&mut self, delta: f64) {
125        if !self.loops && self.is_playing && self.next_play_in < 0.0 {
126            self.reset();
127        }
128        self.next_play_in -= delta;
129    }
130
131    /// If the sound is currently playing
132    pub fn is_playing(&self) -> bool {
133        self.is_playing
134    }
135
136    /// Length in seconds
137    pub fn duration(&self) -> f64 {
138        self.duration
139    }
140
141    /// If sound will automatically loop
142    pub fn loops(&self) -> bool {
143        self.loops
144    }
145}