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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
use crate::*;

use rodio::{decoder::Decoder, source::Source, Sink};
use std::{io::Cursor, sync::Arc, time::Duration};

const QUICK_FADE_DURATION_SECONDS: f32 = 0.2;

/// Handles playback of a [`Clip`] with support for pausing, resuming, volume adjustment.
///
/// Instances can be built using a [`ClipPlayerBuilder`].
///
/// # Example
///
/// ```no_run
/// # use riddle_audio::*; doctest::simple(|audio_system| {
/// let bytes = include_bytes!("../../example_assets/boop.wav");
/// let clip = Clip::load(&bytes[..], ClipFormat::Wav)?;
///
/// // Play the clip
/// let mut player = ClipPlayerBuilder::new(&audio_system).play(&clip)?;
/// player.set_volume(0.5);
/// # Ok(player) });
/// ```
pub struct ClipPlayer {
    audio: AudioSystemHandle,
    clip: Clip,
    sink: Option<Arc<Sink>>,

    volume: f32,
}

impl ClipPlayer {
    pub(crate) fn new(audio: &AudioSystem, clip: &Clip, volume: f32) -> Self {
        Self {
            audio: audio.clone_handle(),
            clip: clip.clone(),
            sink: None,
            volume,
        }
    }

    fn play(&mut self, mode: PlayMode, paused: bool) -> Result<()> {
        let sink: Arc<Sink> = Sink::try_new(&self.audio.stream_handle)
            .map_err(|_| AudioError::PlayError {
                cause: "Error making rodio Sink",
            })?
            .into();

        if paused {
            sink.pause();
            sink.set_volume(0.0);
        } else {
            sink.set_volume(self.volume);
        }

        let source = Decoder::new(Cursor::new(self.clip.data.clone()))
            .map_err(|_| AudioError::ClipDecodeError)?;

        match mode {
            PlayMode::OneShot => sink.append(source),
            PlayMode::Loop => sink.append(source.repeat_infinite()),
        }

        self.sink = Some(sink);

        Ok(())
    }

    /// Fade the volume from the current volume to the targat volume over time.
    ///
    /// Once the volume has been changed the nominal volume will be immediately set to the new
    /// goal volume, as that is the volume that the player will be set to if it gets paused
    /// and resumed.
    ///
    /// The observed volume will change over time as the `AudioSubsystem` progresses the fade.
    ///
    /// If another volume fade is started while one is in progress the existing one is replaced
    /// by the new one, starting from whatever the current volume is.
    ///
    /// Since [`ClipPlayer::set_volume`], [`ClipPlayer::stop`], [`ClipPlayer::pause`]
    /// and [`ClipPlayer::resume`] calls also trigger a fade to avoid popping,
    /// calling any of those methods will also replace any current fade.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use riddle_audio::*; doctest::simple(|audio_system| {
    /// # let bytes = include_bytes!("../../example_assets/boop.wav");
    /// # let clip = Clip::load(&bytes[..], ClipFormat::Wav)?;
    /// // The player starts with all volumes at 1.0
    /// let mut player = ClipPlayerBuilder::new(&audio_system).play(&clip)?;
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(1.0, player.get_observed_volume());
    ///
    /// // The nominal volume changes immediately, the observed volume hasn't changed
    /// player.fade_volume(0.0, std::time::Duration::from_secs(1));
    /// assert_eq!(0.0, player.get_nominal_volume());
    /// assert_eq!(1.0, player.get_observed_volume());
    ///
    /// // A few seconds later
    /// # doctest::pump_for_secs(audio_system, 2);
    /// // The fade has completed and the nominal and observed volumes agree again
    /// assert_eq!(0.0, player.get_nominal_volume());
    /// assert_eq!(0.0, player.get_observed_volume());
    /// # Ok(player) });
    /// ```
    pub fn fade_volume(&mut self, volume: f32, duration: Duration) {
        self.volume = volume;
        self.fade_volume_with_type(self.volume, duration, FadeType::AlterVolume);
    }

    /// Set the volume of playback immediately.
    ///
    /// This performs a very quick fade to the destination volume, to avoid popping
    /// audio artefacts.
    ///
    /// See the example in [`ClipPlayer::fade_volume`] for more details of how volume
    /// changes over time.
    pub fn set_volume(&mut self, volume: f32) {
        self.fade_volume(volume, Duration::from_secs_f32(QUICK_FADE_DURATION_SECONDS))
    }

    /// Get the nominal volume of the player, which may not match the volume the player is currently
    /// playing at this second.
    ///
    /// This is the volume last set via [`ClipPlayer::set_volume`] or [`ClipPlayer::fade_volume`].
    /// This is the volume the player will be at if it is paused and resumed.
    ///
    /// See the example in [`ClipPlayer::fade_volume`] for more details of how volume
    /// changes over time.
    pub fn get_nominal_volume(&self) -> f32 {
        self.volume
    }

    /// Get the observed volume of the player, which is always equal to exactly the volume of playback.
    ///
    /// This is the volume of playback at this moment in time, which could be either equal to the
    /// nominal volume, or another volume if there is a fade running or if the player has been
    /// paused (which causes an observed fade to 0 volume).
    ///
    /// See the example in [`ClipPlayer::fade_volume`] for more details of how volume
    /// changes over time.
    pub fn get_observed_volume(&self) -> f32 {
        self.sink
            .as_ref()
            .map(|sink| sink.volume())
            .unwrap_or(self.volume)
    }

    /// Pauses playback of the clip.
    ///
    /// This performs a very quick fade to zero volume, to avoid popping
    /// audio artefacts, and then pauses playback.
    ///
    /// The nominal volume of the player won't change, but the observed volume will drop to zero.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use riddle_audio::*; doctest::simple(|audio_system| {
    /// # let bytes = include_bytes!("../../example_assets/boop.wav");
    /// # let clip = Clip::load(&bytes[..], ClipFormat::Wav)?;
    /// // The player starts with all volumes at 1.0
    /// let mut player = ClipPlayerBuilder::new(&audio_system).play(&clip)?;
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(1.0, player.get_observed_volume());
    ///
    /// // Pausing doesn't change the nominal volume
    /// player.pause();
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(1.0, player.get_observed_volume());
    ///
    /// // A short moment later
    /// # doctest::pump_for_secs(audio_system, 1);
    /// // The pause has completed and the observed volume is now 0.0
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(0.0, player.get_observed_volume());
    /// # Ok(player) });
    /// ```
    pub fn pause(&mut self) {
        self.fade_volume_with_type(
            0.0,
            Duration::from_secs_f32(QUICK_FADE_DURATION_SECONDS),
            FadeType::Pause,
        );
    }

    // Resumes playback if paused.
    //
    // Immediately starts playback and performs a quick fade back up to the players
    // nominal volume.
    //
    // The nominal volume of the player won't change, but the observed volume with fade
    // from 0 to the nominal value over time.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use riddle_audio::*; doctest::simple(|audio_system| {
    /// # let bytes = include_bytes!("../../example_assets/boop.wav");
    /// # let clip = Clip::load(&bytes[..], ClipFormat::Wav)?;
    /// // The paused player starts with an observed volume of 0.0
    /// let mut player = ClipPlayerBuilder::new(&audio_system).paused(&clip)?;
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(0.0, player.get_observed_volume());
    ///
    /// // Resuming doesn't change the nominal volume
    /// player.resume();
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(0.0, player.get_observed_volume());
    ///
    /// // A short moment later
    /// # doctest::pump_for_secs(audio_system, 1);
    /// // The resume has completed and the observed volume is now 1.0
    /// assert_eq!(1.0, player.get_nominal_volume());
    /// assert_eq!(1.0, player.get_observed_volume());
    /// # Ok(player) });
    /// ```
    pub fn resume(&mut self) {
        if let Some(sink) = &self.sink {
            if sink.is_paused() {
                sink.play();
                self.fade_volume_with_type(
                    self.volume,
                    Duration::from_secs_f32(QUICK_FADE_DURATION_SECONDS),
                    FadeType::Resume,
                );
            }
        }
    }

    /// Stops playback.
    ///
    /// This is equivalent to calling [`ClipPlayer::pause`] and then dropping the player
    /// after the fade is complete
    pub fn stop(mut self) {
        self.pause();
    }

    fn fade_volume_with_type(&mut self, volume: f32, duration: Duration, fade_type: FadeType) {
        if let Some(sink) = &self.sink {
            let fade = Fade::new(sink.clone(), volume, duration, fade_type);
            self.audio.register_fade(fade);
        }
    }
}

/// Enum describing what the player should do at the end of the clip
#[derive(Copy, Clone)]
pub enum PlayMode {
    /// Stop playing at the end of the clip
    OneShot,

    /// Return to the beginning of the clip and play it again
    Loop,
}

/// Builder for [`ClipPlayer`]
///
/// A builder instance may be used to construct multiple players.
///
/// # Example
///
/// ```no_run
/// # use riddle_audio::*; doctest::simple(|audio_system| {
/// let bytes = include_bytes!("../../example_assets/boop.wav");
/// let clip = Clip::load(&bytes[..], ClipFormat::Wav)?;
///
/// // Play the clip
/// let player = ClipPlayerBuilder::new(&audio_system)
///     .with_volume(0.5)
///     .with_mode(PlayMode::Loop)
///     .play(&clip)?;
/// # Ok(player) });
/// ```
pub struct ClipPlayerBuilder {
    mode: PlayMode,
    audio: AudioSystemHandle,
    volume: f32,
}

impl ClipPlayerBuilder {
    /// Make a new builder.
    ///
    /// Defaults:
    ///
    /// * mode: [`PlayMode::OneShot`]
    /// * volume: 1.0.
    pub fn new(audio: &AudioSystem) -> Self {
        Self {
            mode: PlayMode::OneShot,
            audio: audio.clone_handle(),
            volume: 1.0,
        }
    }

    /// Set the playback mode of the player. Defaults to [`PlayMode::OneShot`].
    pub fn with_mode(&mut self, mode: PlayMode) -> &mut Self {
        self.mode = mode;
        self
    }

    /// Set the playback volume of the player. Defaults to 1.0.
    pub fn with_volume(&mut self, volume: f32) -> &mut Self {
        self.volume = volume;
        self
    }

    /// Build the ClipPlayer, and start playing the clip immediately.
    pub fn play(&self, clip: &Clip) -> Result<ClipPlayer> {
        let mut player = ClipPlayer::new(&self.audio, clip, self.volume);
        player.play(self.mode, false)?;
        Ok(player)
    }

    /// Build the ClipPlayer in the paused state. [`ClipPlayer::resume`] will need
    /// to be called on the player to start playback.
    pub fn paused(&self, clip: &Clip) -> Result<ClipPlayer> {
        let mut player = ClipPlayer::new(&self.audio, clip, self.volume);
        player.play(self.mode, true)?;
        Ok(player)
    }
}