use std::marker::PhantomData; use bevy::ecs::system::SystemParam; use bevy::prelude::{Assets, Commands, DetectChanges, Handle, Res, ResMut, Resource}; use bevy_kira_audio::{AudioChannel, AudioControl, AudioInstance, AudioSource, AudioTween}; use crate::utilities::{AudioSettings, SuppliesAudio, TrackType}; use crate::{AmbianceAudioChannel, MusicAudioChannel, SfxAudioChannel, UiSfxAudioChannel}; /// A wrapper for each of the audio channels created and controlled by MusicBox #[derive(SystemParam)] pub struct AudioChannels<'w, 's> { pub music_channel: Res<'w, AudioChannel<MusicAudioChannel>>, pub ambiance_channel: Res<'w, AudioChannel<AmbianceAudioChannel>>, pub sfx_channel: Res<'w, AudioChannel<SfxAudioChannel>>, pub ui_sfx_channel: Res<'w, AudioChannel<UiSfxAudioChannel>>, #[system_param(ignore)] _p: PhantomData<&'s ()>, } /// The service object used to control your game's audio /// /// ## `T: SuppliesAudio` /// /// The particular implementation of `SuppliesAudio` will be used to verify that a music track exists, /// and then to retrieve the associated `AudioSource`. #[derive(SystemParam)] pub struct MusicBox<'w, 's, T: SuppliesAudio> { channels: AudioChannels<'w, 's>, handles: Res<'w, T>, settings: ResMut<'w, AudioSettings>, state: ResMut<'w, MusicBoxState>, audio_instances: ResMut<'w, Assets<AudioInstance>>, } /// Tracks the currently active audio instance singleton channels, to allow /// for transitions #[derive(Debug, Default, Resource)] pub struct MusicBoxState { pub active_music: Option<Handle<AudioInstance>>, pub active_ambiance: Option<Handle<AudioInstance>>, } impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> { /// Start playing a new audio track on the Music channel. The provided tween will be used /// to fade in the new track, and to fade out the old track /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn cross_fade_music<Name: ToString>( &mut self, name: Name, fade: AudioTween, ) -> Option<Handle<AudioInstance>> { self.fade_out_music(fade.clone()); self.fade_in_music(name, fade) } /// Start playing a new audio track on the Music channel. The "in_tween" will be used to fade /// in the new audio track, while the "out_tween" will be used to fade out the old audio /// track /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn in_out_fade_music<Name: ToString>( &mut self, name: Name, in_tween: AudioTween, out_tween: AudioTween, ) -> Option<Handle<AudioInstance>> { self.fade_out_music(out_tween); self.fade_in_music(name, in_tween) } /// Start playing a new audio track on the Music channel. If another track is playing, it will /// be immediately stopped /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_music<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { self.stop_music(); self.fade_in_music(name, AudioTween::default()) } /// Stop playing music on the Music channel pub fn stop_music(&mut self) { self.fade_out_music(AudioTween::default()); } /// Stop playing music on the music channel. The supplied tween will be used to fade out the track /// before it ends pub fn fade_out_music(&mut self, fade: AudioTween) { let handle = std::mem::replace(&mut self.state.active_music, None) .and_then(|handle| self.audio_instances.get_mut(&handle)); if let Some(current) = handle { current.stop(fade); } } /// Start playing a new track on the music channel. If another track is playing, it will be /// immediately stopped. The provided tween will be used to fade in the new track /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn fade_in_music<Name: ToString>( &mut self, name: Name, fade: AudioTween, ) -> Option<Handle<AudioInstance>> { match self.map_tracks(name) { TrackType::WithIntro(_, track) | TrackType::Single(track) => { let next = self .channels .music_channel .play(track) .fade_in(fade) .looped() .handle(); self.state.active_music = Some(next.clone()); Some(next) } TrackType::Missing => None, } } /// Start playing a new audio track on the Music channel. The provided tween will be used /// to fade in the new track, and to fade out the old track /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn cross_fade_ambiance<Name: ToString>( &mut self, name: Name, fade: AudioTween, ) -> Option<Handle<AudioInstance>> { self.fade_out_ambiance(fade.clone()); self.fade_in_ambiance(name, fade) } /// Start playing a new audio track on the Music channel. The "in_tween" will be used to fade /// in the new audio track, while the "out_tween" will be used to fade out the old audio /// track /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn in_out_fade_ambiance<Name: ToString>( &mut self, name: Name, in_tween: AudioTween, out_tween: AudioTween, ) -> Option<Handle<AudioInstance>> { self.fade_out_ambiance(out_tween); self.fade_in_ambiance(name, in_tween) } /// Start playing a new audio track on the Music channel. If another track is playing, it will /// be immediately stopped /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_ambiance<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { self.stop_ambiance(); self.fade_in_ambiance(name, AudioTween::default()) } /// Stop playing ambiance on the Music channel pub fn stop_ambiance(&mut self) { self.fade_out_ambiance(AudioTween::default()); } /// Stop playing ambiance on the ambiance channel. The supplied tween will be used to fade out the track /// before it ends pub fn fade_out_ambiance(&mut self, fade: AudioTween) { let handle = std::mem::replace(&mut self.state.active_ambiance, None) .and_then(|handle| self.audio_instances.get_mut(&handle)); if let Some(current) = handle { current.stop(fade); } } /// Start playing a new track on the ambiance channel. If another track is playing, it will be /// immediately stopped. The provided tween will be used to fade in the new track /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn fade_in_ambiance<Name: ToString>( &mut self, name: Name, fade: AudioTween, ) -> Option<Handle<AudioInstance>> { match self.map_tracks(name) { TrackType::WithIntro(_, track) | TrackType::Single(track) => { let next = self .channels .ambiance_channel .play(track) .fade_in(fade) .looped() .handle(); self.state.active_ambiance = Some(next.clone()); Some(next) } TrackType::Missing => None, } } /// Play a new sound effect on the SFX channel /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_sfx<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { match self.map_tracks(name) { TrackType::WithIntro(_, track) | TrackType::Single(track) => { let instance = self.channels.sfx_channel.play(track).handle(); Some(instance) } TrackType::Missing => None, } } /// Play a new sound effect on the UI SFX channel /// /// # Returns /// /// A handle for the newly started audio instance, or `None` if the track was not found pub fn play_ui_sfx<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> { match self.map_tracks(name) { TrackType::WithIntro(_, track) | TrackType::Single(track) => { let instance = self.channels.ui_sfx_channel.play(track).handle(); Some(instance) } TrackType::Missing => None, } } /// Sync the actual volumes of each track to their values defined in the `AudioSettings` resource, /// modulated by the master volume setting pub fn sync_settings(&self) { if self.settings.is_changed() { self.channels .music_channel .set_volume((self.settings.music_volume * self.settings.master_volume) as f64); self.channels .ambiance_channel .set_volume((self.settings.ambiance_volume * self.settings.master_volume) as f64); self.channels .sfx_channel .set_volume((self.settings.sfx_volume * self.settings.master_volume) as f64); self.channels .ui_sfx_channel .set_volume((self.settings.ui_volume * self.settings.master_volume) as f64); } } /// Get a reference to the settings object pub fn settings(&self) -> &AudioSettings { &self.settings } /// Get a mutable reference to the settings object pub fn settings_mut(&mut self) -> &mut AudioSettings { &mut self.settings } /// Sets the master volume level, as a percentage between 0-1. /// Master volume is used to adjust all of the other volume levels pub fn set_master_volume(&mut self, level: f32) { self.settings.master_volume = level; } /// Sets the music volume level, as a percentage between 0-1. pub fn set_music_volume(&mut self, level: f32) { self.settings.music_volume = level; } /// Sets the ambiance volume level, as a percentage between 0-1. pub fn set_ambiance_volume(&mut self, level: f32) { self.settings.ambiance_volume = level; } /// Sets the sfx volume level, as a percentage between 0-1. pub fn set_sfx_volume(&mut self, level: f32) { self.settings.sfx_volume = level; } /// Sets the ui sfx volume level, as a percentage between 0-1. pub fn set_ui_sfx_volume(&mut self, level: f32) { self.settings.ui_volume = level; } fn map_tracks<Name: ToString>(&'w self, name: Name) -> TrackType<Handle<AudioSource>> { match self.handles.resolve_track_name(name) { TrackType::Single(name) => match self.handles.get_audio_track(name) { Some(handle) => TrackType::Single(handle), None => TrackType::Missing, }, TrackType::WithIntro(intro, looper) => match ( self.handles.get_audio_track(intro), self.handles.get_audio_track(looper), ) { (Some(intro), Some(looper)) => TrackType::WithIntro(intro, looper), _ => TrackType::Missing, }, TrackType::Missing => TrackType::Missing, } } }