Newer
Older
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
pub struct MusicBoxState {
pub active_music: Option<Handle<AudioInstance>>,
/// The name used to start the currently active music track
pub active_music_name: Option<String>,
pub active_ambiance: Option<Handle<AudioInstance>>,
/// The name used to start the currently active ambiance track
pub active_ambiance_name: Option<String>,
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>> {
if self.state.active_music_name == Some(name.to_string()) {
return self.state.active_music.as_ref().map(|f| f.clone_weak());
}
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>> {
if self.state.active_music_name == Some(name.to_string()) {
return self.state.active_music.as_ref().map(|f| f.clone_weak());
}
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>> {
if self.state.active_music_name == Some(name.to_string()) {
return self.state.active_music.as_ref().map(|f| f.clone_weak());
}
self.stop_music();
self.fade_in_music(name, AudioTween::default())
}
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) {
self.state.active_music_name = None;
let handle = self
.state
.active_music
.take()
.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>> {
let name = name.to_string();
if self.state.active_music_name.as_ref() == Some(&name) {
return self.state.active_music.as_ref().map(|f| f.clone_weak());
}
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_name = Some(name.clone());
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())
}
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 = self
.state
.active_ambiance
.take()
.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 SFX channel in a loop. Ideal for ambient spatial sounds
/// like a crackling fireplace
///
/// # Returns
///
/// A handle for the newly started audio instance, or `None` if the track was not found
pub fn start_looped_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).looped().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
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);
}
pub fn settings(&self) -> &AudioSettings {
&self.settings
}
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;
}
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/// Stop an audio instance handle from playing immediately
pub fn stop_handle(&mut self, handle: &Handle<AudioInstance>) {
if let Some(instance) = self.audio_instances.get_mut(handle) {
instance.stop(AudioTween::default());
}
}
/// Stop an audio instance handle from playing with a given tween
pub fn stop_handle_with_tween(&mut self, handle: &Handle<AudioInstance>, tween: AudioTween) {
if let Some(instance) = self.audio_instances.get_mut(handle) {
instance.stop(tween);
}
}
/// Pause an audio instance handle immediately, in a way that can be resumed later
pub fn pause_handle(&mut self, handle: &Handle<AudioInstance>) {
if let Some(instance) = self.audio_instances.get_mut(handle) {
instance.pause(AudioTween::default());
}
}
/// Pause an audio instance with a given tween in a way that can be resumed later
pub fn pause_handle_with_tween(&mut self, handle: &Handle<AudioInstance>, tween: AudioTween) {
if let Some(instance) = self.audio_instances.get_mut(handle) {
instance.pause(tween);
}
}
/// Stop an audio instance handle from playing immediately
pub fn resume_handle(&mut self, handle: &Handle<AudioInstance>) {
if let Some(instance) = self.audio_instances.get_mut(handle) {
instance.resume(AudioTween::default());
}
}
/// Stop an audio instance handle from playing with a given tween
pub fn resume_handle_with_tween(&mut self, handle: &Handle<AudioInstance>, tween: AudioTween) {
if let Some(instance) = self.audio_instances.get_mut(handle) {
instance.resume(tween);
}
}
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,