use crate::Context;
use crate::audio;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct SoundOptions {
pub volume: f32,
pub fade_in: Duration,
pub fade_out: Option<Duration>,
pub start_paused: bool,
pub looped: bool,
}
impl Default for SoundOptions {
fn default() -> Self {
Self {
volume: 1.0,
fade_in: Duration::ZERO,
fade_out: None,
start_paused: false,
looped: false,
}
}
}
#[derive(Debug, Clone)]
pub struct SoundStart {
pub sound_id: u32,
pub options: SoundOptions,
}
impl SoundStart {
pub fn new(sound_id: u32, options: SoundOptions) -> Self {
Self { sound_id, options }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SoundPlaybackPosition {
pub rendered_frames: u64,
pub output_sample_rate: u32,
pub paused: bool,
}
impl SoundPlaybackPosition {
pub fn elapsed(self) -> Duration {
if self.output_sample_rate == 0 {
return Duration::ZERO;
}
Duration::from_secs_f64(self.rendered_frames as f64 / self.output_sample_rate as f64)
}
}
pub fn register_sound(ctx: &mut Context, bytes: Vec<u8>) -> Option<u32> {
ctx.with_audio(|a| a.register_sound(bytes))
}
pub fn play_sound(ctx: &mut Context, sound_id: u32, options: SoundOptions) -> Option<u64> {
let opts = to_play_options(&options);
ctx.with_audio(|a| a.play_registered_sound_with_options(sound_id, opts))
.flatten()
}
pub fn play_sounds_synced(ctx: &mut Context, starts: &[SoundStart]) -> Option<Vec<u64>> {
let starts = starts
.iter()
.map(|start| (start.sound_id, to_play_options(&start.options)))
.collect::<Vec<_>>();
ctx.with_audio(|audio| audio.play_registered_sounds_synced(&starts))
.flatten()
}
pub fn play_sound_simple(ctx: &mut Context, sound_id: u32) -> Option<u64> {
ctx.with_audio(|a| {
a.play_registered_sound_with_options(sound_id, audio::PlayOptions::default())
})
.flatten()
}
pub fn pause_sound(ctx: &mut Context, play_id: u64) {
ctx.with_audio(|a| a.pause_play_id(play_id));
}
pub fn resume_sound(ctx: &mut Context, play_id: u64) {
ctx.with_audio(|a| a.resume_play_id(play_id));
}
pub fn stop_sound(ctx: &mut Context, play_id: u64) {
ctx.with_audio(|a| a.stop_play_id(play_id));
}
pub fn stop_all_sounds(ctx: &mut Context) {
ctx.with_audio(|a| a.stop_all_sounds());
}
pub fn fade_in_sound(ctx: &mut Context, play_id: u64, duration: Duration) {
ctx.with_audio(|a| a.fade_in_play_id(play_id, duration));
}
pub fn fade_out_sound(ctx: &mut Context, play_id: u64, duration: Duration) {
ctx.with_audio(|a| a.fade_out_play_id(play_id, duration));
}
pub fn set_sound_volume(ctx: &mut Context, play_id: u64, volume: f32) {
ctx.with_audio(|a| a.set_volume_play_id(play_id, volume));
}
pub fn is_sound_playing(ctx: &Context, play_id: u64) -> bool {
ctx.runtime
.audio
.as_ref()
.map(|a| a.is_playing_play_id(play_id))
.unwrap_or(false)
}
pub fn sound_playback_position(ctx: &Context, play_id: u64) -> Option<SoundPlaybackPosition> {
let position = ctx
.runtime
.audio
.as_ref()?
.playback_position_play_id(play_id)?;
Some(SoundPlaybackPosition {
rendered_frames: position.rendered_frames,
output_sample_rate: position.output_sample_rate,
paused: position.paused,
})
}
pub fn unregister_sound(ctx: &mut Context, sound_id: u32) {
ctx.with_audio(|a| a.unregister_sound(sound_id));
}
pub fn play_sine(ctx: &mut Context, freq: f32, volume: f32) -> Option<u64> {
ctx.with_audio(|a| a.play_sine(freq, volume)).flatten()
}
fn to_play_options(options: &SoundOptions) -> audio::PlayOptions {
audio::PlayOptions {
volume: options.volume,
fade_in: options.fade_in,
fade_out: options.fade_out,
start_paused: options.start_paused,
looped: options.looped,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_sound_returns_none_when_audio_is_unavailable() {
let mut ctx = Context::new();
assert_eq!(register_sound(&mut ctx, vec![1, 2, 3]), None);
}
#[test]
fn play_sound_simple_returns_none_when_audio_is_unavailable() {
let mut ctx = Context::new();
assert_eq!(play_sound_simple(&mut ctx, 42), None);
}
#[test]
fn synced_playback_returns_none_when_audio_is_unavailable() {
let mut ctx = Context::new();
let starts = [SoundStart::new(42, SoundOptions::default())];
assert_eq!(play_sounds_synced(&mut ctx, &starts), None);
}
#[test]
fn playback_position_returns_none_when_audio_is_unavailable() {
let ctx = Context::new();
assert_eq!(sound_playback_position(&ctx, 7), None);
}
#[test]
fn playback_position_converts_frames_to_elapsed_time() {
let position = SoundPlaybackPosition {
rendered_frames: 24_000,
output_sample_rate: 48_000,
paused: false,
};
assert_eq!(position.elapsed(), Duration::from_millis(500));
}
}