
# Objective - Allow users to configure volume using decibels by changing the `Volume` type from newtyping an `f32` to an enum with `Linear` and `Decibels` variants. - Fixes #9507. - Alternative reworked version of closed #9582. ## Solution Compared to https://github.com/bevyengine/bevy/pull/9582, this PR has the following main differences: 1. It uses the term "linear scale" instead of "amplitude" per https://github.com/bevyengine/bevy/pull/9582/files#r1513529491. 2. Supports `ops` for doing `Volume` arithmetic. Can add two volumes, e.g. to increase/decrease the current volume. Can multiply two volumes, e.g. to get the “effective” volume of an audio source considering global volume. [requested and blessed on Discord]: https://discord.com/channels/691052431525675048/749430447326625812/1318272597003341867 ## Testing - Ran `cargo run --example soundtrack`. - Ran `cargo run --example audio_control`. - Ran `cargo run --example spatial_audio_2d`. - Ran `cargo run --example spatial_audio_3d`. - Ran `cargo run --example pitch`. - Ran `cargo run --example decodable`. - Ran `cargo run --example audio`. --- ## Migration Guide Audio volume can now be configured using decibel values, as well as using linear scale values. To enable this, some types and functions in `bevy_audio` have changed. - `Volume` is now an enum with `Linear` and `Decibels` variants. Before: ```rust let v = Volume(1.0); ``` After: ```rust let volume = Volume::Linear(1.0); let volume = Volume::Decibels(0.0); // or now you can deal with decibels if you prefer ``` - `Volume::ZERO` has been renamed to the more semantically correct `Volume::SILENT` because `Volume` now supports decibels and "zero volume" in decibels actually means "normal volume". - The `AudioSinkPlayback` trait's volume-related methods now deal with `Volume` types rather than `f32`s. `AudioSinkPlayback::volume()` now returns a `Volume` rather than an `f32`. `AudioSinkPlayback::set_volume` now receives a `Volume` rather than an `f32`. This affects the `AudioSink` and `SpatialAudioSink` implementations of the trait. The previous `f32` values are equivalent to the volume converted to linear scale so the `Volume:: Linear` variant should be used to migrate between `f32`s and `Volume`. - The `GlobalVolume::new` function now receives a `Volume` instead of an `f32`. --------- Co-authored-by: Zachary Harrold <zac@harrold.com.au>
156 lines
4.9 KiB
Rust
156 lines
4.9 KiB
Rust
//! This example illustrates how to load and play different soundtracks,
|
|
//! transitioning between them as the game state changes.
|
|
|
|
use bevy::{audio::Volume, prelude::*};
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, (cycle_game_state, fade_in, fade_out))
|
|
.add_systems(Update, change_track)
|
|
.run();
|
|
}
|
|
|
|
// This resource simulates game states
|
|
#[derive(Resource, Default)]
|
|
enum GameState {
|
|
#[default]
|
|
Peaceful,
|
|
Battle,
|
|
}
|
|
|
|
// This timer simulates game state changes
|
|
#[derive(Resource)]
|
|
struct GameStateTimer(Timer);
|
|
|
|
// This resource will hold the track list for your soundtrack
|
|
#[derive(Resource)]
|
|
struct SoundtrackPlayer {
|
|
track_list: Vec<Handle<AudioSource>>,
|
|
}
|
|
|
|
impl SoundtrackPlayer {
|
|
fn new(track_list: Vec<Handle<AudioSource>>) -> Self {
|
|
Self { track_list }
|
|
}
|
|
}
|
|
|
|
// This component will be attached to an entity to fade the audio in
|
|
#[derive(Component)]
|
|
struct FadeIn;
|
|
|
|
// This component will be attached to an entity to fade the audio out
|
|
#[derive(Component)]
|
|
struct FadeOut;
|
|
|
|
fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
|
|
// Instantiate the game state resources
|
|
commands.insert_resource(GameState::default());
|
|
commands.insert_resource(GameStateTimer(Timer::from_seconds(
|
|
10.0,
|
|
TimerMode::Repeating,
|
|
)));
|
|
|
|
// Create the track list
|
|
let track_1 = asset_server.load::<AudioSource>("sounds/Mysterious acoustic guitar.ogg");
|
|
let track_2 = asset_server.load::<AudioSource>("sounds/Epic orchestra music.ogg");
|
|
let track_list = vec![track_1, track_2];
|
|
commands.insert_resource(SoundtrackPlayer::new(track_list));
|
|
}
|
|
|
|
// Every time the GameState resource changes, this system is run to trigger the song change.
|
|
fn change_track(
|
|
mut commands: Commands,
|
|
soundtrack_player: Res<SoundtrackPlayer>,
|
|
soundtrack: Query<Entity, With<AudioSink>>,
|
|
game_state: Res<GameState>,
|
|
) {
|
|
if game_state.is_changed() {
|
|
// Fade out all currently running tracks
|
|
for track in soundtrack.iter() {
|
|
commands.entity(track).insert(FadeOut);
|
|
}
|
|
|
|
// Spawn a new `AudioPlayer` with the appropriate soundtrack based on
|
|
// the game state.
|
|
//
|
|
// Volume is set to start at zero and is then increased by the fade_in system.
|
|
match game_state.as_ref() {
|
|
GameState::Peaceful => {
|
|
commands.spawn((
|
|
AudioPlayer(soundtrack_player.track_list.first().unwrap().clone()),
|
|
PlaybackSettings {
|
|
mode: bevy::audio::PlaybackMode::Loop,
|
|
volume: Volume::SILENT,
|
|
..default()
|
|
},
|
|
FadeIn,
|
|
));
|
|
}
|
|
GameState::Battle => {
|
|
commands.spawn((
|
|
AudioPlayer(soundtrack_player.track_list.get(1).unwrap().clone()),
|
|
PlaybackSettings {
|
|
mode: bevy::audio::PlaybackMode::Loop,
|
|
volume: Volume::SILENT,
|
|
..default()
|
|
},
|
|
FadeIn,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fade effect duration
|
|
const FADE_TIME: f32 = 2.0;
|
|
|
|
// Fades in the audio of entities that has the FadeIn component. Removes the FadeIn component once
|
|
// full volume is reached.
|
|
fn fade_in(
|
|
mut commands: Commands,
|
|
mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeIn>>,
|
|
time: Res<Time>,
|
|
) {
|
|
for (mut audio, entity) in audio_sink.iter_mut() {
|
|
let current_volume = audio.volume();
|
|
audio.set_volume(current_volume + Volume::Linear(time.delta_secs() / FADE_TIME));
|
|
if audio.volume().to_linear() >= 1.0 {
|
|
audio.set_volume(Volume::Linear(1.0));
|
|
commands.entity(entity).remove::<FadeIn>();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fades out the audio of entities that has the FadeOut component. Despawns the entities once audio
|
|
// volume reaches zero.
|
|
fn fade_out(
|
|
mut commands: Commands,
|
|
mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeOut>>,
|
|
time: Res<Time>,
|
|
) {
|
|
for (mut audio, entity) in audio_sink.iter_mut() {
|
|
let current_volume = audio.volume();
|
|
audio.set_volume(current_volume - Volume::Linear(time.delta_secs() / FADE_TIME));
|
|
if audio.volume().to_linear() <= 0.0 {
|
|
commands.entity(entity).despawn();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every time the timer ends, switches between the "Peaceful" and "Battle" state.
|
|
fn cycle_game_state(
|
|
mut timer: ResMut<GameStateTimer>,
|
|
mut game_state: ResMut<GameState>,
|
|
time: Res<Time>,
|
|
) {
|
|
timer.0.tick(time.delta());
|
|
if timer.0.just_finished() {
|
|
match game_state.as_ref() {
|
|
GameState::Battle => *game_state = GameState::Peaceful,
|
|
GameState::Peaceful => *game_state = GameState::Battle,
|
|
}
|
|
}
|
|
}
|