1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::error::GameUtilError;
use crate::timing::Timing;
use audio_engine::{AudioEngine, Sound, WavDecoder};
use std::io::Cursor;

/// Sound effect (although it can also be used for music)
/// You must call [SoundEffect::update] or [SoundEffect::update_secs] with accurate values and often otherwise playback may stutter or jump
///
/// # Usage
///
/// ```
///# use audio_engine::AudioEngine;
///# use simple_game_utils::sound_effect::NewSoundEffect;
///# use simple_game_utils::timing::Timing;
///# let some_sound_bytes = [0,0,0,0,0,0];
///# let mut  timing = Timing::new(240);
///# let duration = 1.0;
/// //this must live as long as `sound` but there's no lifetimes to enforce this
/// let mut engine = AudioEngine::new().unwrap();
/// let mut sound = engine.load_from_bytes(&some_sound_bytes, duration).unwrap();
/// sound.play();
/// loop {
///     timing.update();
///     sound.update(&timing);
/// }
/// ```
pub struct SoundEffect {
    //Sound data
    sound: Sound,
    //If sound is currently playing
    is_playing: bool,
    //Length in seconds
    duration: f64,
    // used to prevent bugs
    next_play_in: f64,
    //If sound automatically loops
    loops: bool,
}

pub trait NewSoundEffect {
    fn load_from_bytes(
        &self,
        bytes: &'static [u8],
        duration: f64,
    ) -> Result<SoundEffect, GameUtilError>;
}

impl NewSoundEffect for AudioEngine {
    fn load_from_bytes(
        &self,
        bytes: &'static [u8],
        duration: f64,
    ) -> Result<SoundEffect, GameUtilError> {
        let decoder =
            WavDecoder::new(Cursor::new(bytes)).map_err(GameUtilError::SoundEffectInvalid)?;
        let sound = self
            .new_sound(decoder)
            .map_err(GameUtilError::SoundEffectInit)?;
        Ok(SoundEffect::new(sound, duration))
    }
}

impl SoundEffect {
    pub fn new(sound: Sound, duration: f64) -> Self {
        Self {
            sound,
            is_playing: false,
            duration,
            next_play_in: 0.0,
            loops: false,
        }
    }

    /// Play sound effect, won't do anything if sound effect is already playing
    pub fn play(&mut self) {
        if !self.is_playing {
            self.sound.play();
            self.is_playing = true;
            self.next_play_in = self.duration;
        }
    }

    /// Reset playback position and stop playback
    pub fn reset(&mut self) {
        self.sound.stop();
        self.is_playing = false;
        self.next_play_in = 0.0;
    }

    /// Set if the sound loops automatically
    pub fn set_loop(&mut self, loops: bool) {
        self.loops = loops;
        self.sound.set_loop(loops)
    }

    pub fn set_volume(&mut self, volume: f32) {
        self.sound.set_volume(volume);
    }

    /// Returns true if calling [SoundEffect::play] will do anything
    pub fn can_play(&self) -> bool {
        !self.is_playing && self.next_play_in < 0.0
    }

    /// Allows the sound to continue playing
    pub fn update(&mut self, timing: &Timing) {
        self.update_secs(timing.fixed_time_step)
    }

    /// Allows the sound to continue playing
    pub fn update_secs(&mut self, delta: f64) {
        if !self.loops && self.is_playing && self.next_play_in < 0.0 {
            self.reset();
        }
        self.next_play_in -= delta;
    }

    /// If the sound is currently playing
    pub fn is_playing(&self) -> bool {
        self.is_playing
    }

    /// Length in seconds
    pub fn duration(&self) -> f64 {
        self.duration
    }

    /// If sound will automatically loop
    pub fn loops(&self) -> bool {
        self.loops
    }
}