Newer
Older
use std::marker::PhantomData;
use bevy::ecs::system::SystemParam;
use bevy::prelude::{Assets, Commands, Handle, Res, ResMut};
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)]
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())
}
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())
}
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) {
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;
}
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,