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 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 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 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
//! Audio Center resource and systems.
use super::{Audio, AudioManager, AudioSource};
use crate::prelude::*;
use kira;
use kira::{
sound::{static_sound::StaticSoundSettings, PlaybackState},
tween,
tween::Tween,
Volume,
};
use std::collections::VecDeque;
use std::time::Duration;
use tracing::warn;
/// A resource that can be used to control game audios.
#[derive(HasSchema)]
#[schema(no_clone)]
pub struct AudioCenter {
/// Buffer for audio events that have not yet been processed.
events: VecDeque<AudioEvent>,
/// The handle to the current music.
music: Option<Audio>,
/// The volume scale for main audio.
main_volume_scale: f32,
/// The volume scale for music.
music_volume_scale: f32,
/// The volume scale for sound effects.
effects_volume_scale: f32,
/// How long music should fade out for when stopping.
music_fade_duration: Duration,
/// How long music should fade out for when stopping.
sounds_fade_duration: Duration,
}
impl Default for AudioCenter {
fn default() -> Self {
Self {
events: VecDeque::with_capacity(16),
music: None,
main_volume_scale: 1.0,
music_volume_scale: 1.0,
effects_volume_scale: 1.0,
music_fade_duration: Duration::from_millis(500),
sounds_fade_duration: Duration::from_millis(500),
}
}
}
impl AudioCenter {
/// Push an audio event to the queue for later processing.
pub fn push_event(&mut self, event: AudioEvent) {
self.events.push_back(event);
}
/// Returns the currently played music.
pub fn music(&self) -> Option<&Audio> {
self.music.as_ref()
}
/// Get the playback state of the current music.
pub fn music_state(&self) -> Option<PlaybackState> {
self.music().map(|m| m.handle.state())
}
/// Play a sound. These are usually short audios that indicate something
/// happened in game, e.g. a player jump, an explosion, etc.
/// Volume is scaled by both main_volume_scale, and effects_volume_scale.
pub fn play_sound(&mut self, sound_source: Handle<AudioSource>, volume: f64) {
self.events.push_back(AudioEvent::PlaySound {
sound_source,
volume,
})
}
/// Plays music, forcibly stopping any current music.
/// Volume is scaled by both main_volume_scale and music_volume_scale.
pub fn play_music(&mut self, sound_source: Handle<AudioSource>, volume: f64, loop_music: bool) {
let clamped_volume = volume.clamp(0.0, 1.0);
let mut settings = StaticSoundSettings::new().volume(Volume::Amplitude(clamped_volume));
if loop_music {
settings = settings.loop_region(kira::sound::Region {
start: 0.0.into(),
end: kira::sound::EndPosition::EndOfAudio,
});
}
self.events.push_back(AudioEvent::PlayMusic {
sound_source,
sound_settings: Box::new(settings),
force_restart: true,
});
}
/// Plays music with advanced settings.
/// Volume is scaled by both main_volume_scale and music_volume_scale.
///
/// # Parameters
///
/// * `sound_source` - The handle for the audio source to play
/// * `volume` - The volume to play the music at (0.0 to 1.0)
/// * `loop_music` - Whether the music should loop indefinitely
/// * `reverse` - Whether to play the audio in reverse
/// * `start_position` - The position in seconds to start playback from
/// * `playback_rate` - The playback rate (1.0 is normal speed, 0.5 is half speed, 2.0 is double speed)
/// * `skip_restart` - If true, won't restart the music if it's already playing the same track
#[allow(clippy::too_many_arguments)]
pub fn play_music_advanced(
&mut self,
sound_source: Handle<AudioSource>,
volume: f64,
loop_music: bool,
reverse: bool,
start_position: f64,
playback_rate: f64,
skip_restart: bool,
) {
let clamped_volume = volume.clamp(0.0, 1.0);
let mut settings = StaticSoundSettings::new()
.volume(Volume::Amplitude(clamped_volume))
.start_position(kira::sound::PlaybackPosition::Seconds(start_position))
.reverse(reverse)
.playback_rate(playback_rate);
if loop_music {
settings = settings.loop_region(kira::sound::Region {
start: 0.0.into(),
end: kira::sound::EndPosition::EndOfAudio,
});
}
self.events.push_back(AudioEvent::PlayMusic {
sound_source,
sound_settings: Box::new(settings),
force_restart: !skip_restart,
});
}
/// Plays music with custom StaticSoundSettings.
/// Volume is scaled by both main_volume_scale and music_volume_scale.
pub fn play_music_custom(
&mut self,
sound_source: Handle<AudioSource>,
sound_settings: StaticSoundSettings,
skip_restart: bool,
) {
self.events.push_back(AudioEvent::PlayMusic {
sound_source,
sound_settings: Box::new(sound_settings),
force_restart: !skip_restart,
});
}
/// Sets the volume scale for main audio within the range of 0.0 to 1.0.
/// The main volume scale impacts all other volume scales.
pub fn set_main_volume_scale(&mut self, main: f32) {
self.main_volume_scale = main.clamp(0.0, 1.0);
}
/// Sets the volume scale for music within the range of 0.0 to 1.0.
pub fn set_music_volume_scale(&mut self, music: f32) {
self.music_volume_scale = music.clamp(0.0, 1.0);
}
/// Sets the volume scale for effects within the range of 0.0 to 1.0.
pub fn set_effects_volume_scale(&mut self, effects: f32) {
self.effects_volume_scale = effects.clamp(0.0, 1.0);
}
/// Sets the volume scales for main, music, and effects within the range of 0.0 to 1.0.
pub fn set_volume_scales(&mut self, main: f32, music: f32, effects: f32) {
self.set_main_volume_scale(main);
self.set_music_volume_scale(music);
self.set_effects_volume_scale(effects);
self.events.push_back(AudioEvent::VolumeScaleUpdate {
main_volume_scale: self.main_volume_scale,
music_volume_scale: self.music_volume_scale,
effects_volume_scale: self.effects_volume_scale,
});
}
/// Returns the main volume scale (which impacts all other volume scales).
pub fn main_volume_scale(&self) -> f32 {
self.main_volume_scale
}
/// Returns the music volume scale.
pub fn music_volume_scale(&self) -> f32 {
self.music_volume_scale
}
/// Returns the volume scale for sound effects.
pub fn effects_volume_scale(&self) -> f32 {
self.effects_volume_scale
}
/// Returns the duration for music fade out.
pub fn music_fade_duration(&self) -> Duration {
self.music_fade_duration
}
/// Sets the duration for music fade out.
pub fn set_music_fade_duration(&mut self, duration: Duration) {
self.music_fade_duration = duration;
}
/// Returns the duration for audio fade out.
pub fn sounds_fade_duration(&self) -> Duration {
self.sounds_fade_duration
}
/// Sets the duration for audio fade out.
pub fn set_sounds_fade_duration(&mut self, duration: Duration) {
self.sounds_fade_duration = duration;
}
/// Stops the currently playing music
/// * `fade_out` - If true, fades out the music using the Audio Center's music fade duration. If false, stops the sounds instantly.
pub fn stop_music(&mut self, fade_out: bool) {
self.events.push_back(AudioEvent::StopMusic { fade_out });
}
/// Stops all currently playing sounds.
/// * `fade_out` - If true, fades out the sounds using the Audio Center's sounds fade duration. If false, stops the sounds instantly.
pub fn stop_all_sounds(&mut self, fade_out: bool) {
self.events
.push_back(AudioEvent::StopAllSounds { fade_out });
}
}
/// An audio event that may be sent to the [`AudioCenter`] resource for
/// processing.
#[derive(Clone, Debug)]
pub enum AudioEvent {
/// Update the volume of all audios using the new scale values.
/// This event is used to adjust the overall volume of the application.
VolumeScaleUpdate {
/// The main volume scale factor.
main_volume_scale: f32,
/// The music volume scale factor.
music_volume_scale: f32,
/// The effects volume scale factor.
effects_volume_scale: f32,
},
/// Play some music.
///
/// Any current music is stopped if force_restart is true or if the new music is different.
PlayMusic {
/// The handle for the music.
sound_source: Handle<AudioSource>,
/// The settings for the music.
sound_settings: Box<StaticSoundSettings>,
/// Whether to force restart the music even if it's the same as the current music.
force_restart: bool,
},
/// Stop the currently playing music.
StopMusic {
/// Whether to fade out the sounds or stop them instantly.
fade_out: bool,
},
/// Play a sound.
PlaySound {
/// The handle to the sound to play.
sound_source: Handle<AudioSource>,
/// The volume to play the sound at.
volume: f64,
},
/// Stop all currently playing sounds.
StopAllSounds {
/// Whether to fade out the sounds or stop them instantly.
fade_out: bool,
},
}
/// Internally used sytem for processing audio events in the bones audio session.
pub fn _process_audio_events(
mut audio_manager: ResMut<AudioManager>,
mut audio_center: ResMut<AudioCenter>,
assets: ResInit<AssetServer>,
mut entities: ResMut<Entities>,
mut audios: CompMut<Audio>,
) {
for event in audio_center.events.drain(..).collect::<Vec<_>>() {
match event {
AudioEvent::VolumeScaleUpdate {
main_volume_scale,
music_volume_scale,
effects_volume_scale,
} => {
let tween = Tween::default();
// Update music volume
if let Some(music) = &mut audio_center.music {
let volume =
(main_volume_scale as f64) * (music_volume_scale as f64) * music.volume;
music.handle.set_volume(volume, tween);
}
// Update sound volumes
for audio in audios.iter_mut() {
let volume =
(main_volume_scale as f64) * (effects_volume_scale as f64) * audio.volume;
audio.handle.set_volume(volume, tween);
}
}
AudioEvent::PlayMusic {
sound_source,
mut sound_settings,
force_restart,
} => {
let should_play = force_restart
|| audio_center.music.as_ref().map_or(true, |current_music| {
sound_source != current_music.bones_handle
});
if should_play {
// Stop the current music
if let Some(mut music) = audio_center.music.take() {
let tween = Tween {
start_time: kira::StartTime::Immediate,
duration: audio_center.music_fade_duration,
easing: tween::Easing::Linear,
};
music.handle.stop(tween);
}
// Scale the requested volume by the volume scales
let volume = match sound_settings.volume {
tween::Value::Fixed(vol) => vol.as_amplitude(),
_ => 1.0,
};
let scaled_volume = (audio_center.main_volume_scale as f64)
* (audio_center.music_volume_scale as f64)
* volume;
sound_settings.volume = tween::Value::Fixed(Volume::Amplitude(scaled_volume));
// Play the new music
let sound_data = assets.get(sound_source).with_settings(*sound_settings);
match audio_manager.play(sound_data) {
Err(err) => warn!("Error playing music: {err}"),
Ok(handle) => {
audio_center.music = Some(Audio {
handle,
volume,
bones_handle: sound_source,
})
}
}
}
}
AudioEvent::StopMusic { fade_out } => {
if let Some(mut music) = audio_center.music.take() {
if fade_out {
let tween = Tween {
start_time: kira::StartTime::Immediate,
duration: audio_center.music_fade_duration,
easing: tween::Easing::Linear,
};
music.handle.stop(tween);
} else {
music.handle.stop(Tween::default());
}
}
}
AudioEvent::StopAllSounds { fade_out } => {
let tween = if fade_out {
Tween {
start_time: kira::StartTime::Immediate,
duration: audio_center.sounds_fade_duration,
easing: tween::Easing::Linear,
}
} else {
Tween::default()
};
for (_, audio) in entities.iter_with(&mut audios) {
audio.handle.stop(tween);
}
}
AudioEvent::PlaySound {
sound_source,
volume,
} => {
let scaled_volume = (audio_center.main_volume_scale as f64)
* (audio_center.effects_volume_scale as f64)
* volume;
let sound_data = assets
.get(sound_source)
.with_settings(StaticSoundSettings::default().volume(scaled_volume));
match audio_manager.play(sound_data) {
Err(err) => warn!("Error playing sound: {err}"),
Ok(handle) => {
let audio_ent = entities.create();
audios.insert(
audio_ent,
Audio {
handle,
volume,
bones_handle: sound_source,
},
);
}
}
}
}
}
}
/// Internally used sytem for killing finished audios (generally sounds) which were emitted as separate entities.
/// Used in the bones audio session.
pub fn _kill_finished_audios(entities: Res<Entities>, audios: Comp<Audio>, mut commands: Commands) {
for (audio_ent, audio) in entities.iter_with(&audios) {
if audio.handle.state() == PlaybackState::Stopped {
commands.add(move |mut entities: ResMut<Entities>| entities.kill(audio_ent));
}
}
}