Skip to content

Commit dff071c

Browse files
Ability to set a Global Volume (#7706)
# Objective Adds a new resource to control a global volume. Fixes #7690 --- ## Solution Added a new resource to control global volume, this is then multiplied with an audio sources volume to get the output volume, individual audio sources can opt out of this my enabling the `absolute_volume` field in `PlaybackSettings`. --- ## Changelog ### Added - `GlobalVolume` a resource to control global volume (in prelude). - `global_volume` field to `AudioPlugin` or setting the initial value of `GlobalVolume`. - `Volume` enum that can be `Relative` or `Absolute`. - `VolumeLevel` struct for defining a volume level. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent cc8f023 commit dff071c

File tree

5 files changed

+114
-21
lines changed

5 files changed

+114
-21
lines changed

crates/bevy_audio/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev" }
1616
bevy_math = { path = "../bevy_math", version = "0.11.0-dev" }
1717
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev", features = ["bevy"] }
1818
bevy_transform = { path = "../bevy_transform", version = "0.11.0-dev" }
19+
bevy_derive = { path = "../bevy_derive", version = "0.11.0-dev" }
1920
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
2021

2122
# other

crates/bevy_audio/src/audio.rs

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{AudioSink, AudioSource, Decodable, SpatialAudioSink};
22
use bevy_asset::{Asset, Handle, HandleId};
3+
use bevy_derive::{Deref, DerefMut};
34
use bevy_ecs::system::Resource;
45
use bevy_math::Vec3;
56
use bevy_transform::prelude::Transform;
@@ -97,12 +98,12 @@ where
9798
/// ```
9899
/// # use bevy_ecs::system::Res;
99100
/// # use bevy_asset::AssetServer;
100-
/// # use bevy_audio::Audio;
101+
/// # use bevy_audio::{Audio, Volume};
101102
/// # use bevy_audio::PlaybackSettings;
102103
/// fn play_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
103104
/// audio.play_with_settings(
104105
/// asset_server.load("my_sound.ogg"),
105-
/// PlaybackSettings::LOOP.with_volume(0.75),
106+
/// PlaybackSettings::LOOP.with_volume(Volume::new_relative(0.75)),
106107
/// );
107108
/// }
108109
/// ```
@@ -210,14 +211,14 @@ where
210211
/// ```
211212
/// # use bevy_ecs::system::Res;
212213
/// # use bevy_asset::AssetServer;
213-
/// # use bevy_audio::Audio;
214+
/// # use bevy_audio::{Audio, Volume};
214215
/// # use bevy_audio::PlaybackSettings;
215216
/// # use bevy_math::Vec3;
216217
/// # use bevy_transform::prelude::Transform;
217218
/// fn play_spatial_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
218219
/// audio.play_spatial_with_settings(
219220
/// asset_server.load("my_sound.ogg"),
220-
/// PlaybackSettings::LOOP.with_volume(0.75),
221+
/// PlaybackSettings::LOOP.with_volume(Volume::new_relative(0.75)),
221222
/// Transform::IDENTITY,
222223
/// 1.0,
223224
/// Vec3::new(-2.0, 0.0, 1.0),
@@ -251,13 +252,61 @@ where
251252
}
252253
}
253254

255+
/// Defines the volume to play an audio source at.
256+
#[derive(Clone, Copy, Debug)]
257+
pub enum Volume {
258+
/// A volume level relative to the global volume.
259+
Relative(VolumeLevel),
260+
/// A volume level that ignores the global volume.
261+
Absolute(VolumeLevel),
262+
}
263+
264+
impl Default for Volume {
265+
fn default() -> Self {
266+
Self::Relative(VolumeLevel::default())
267+
}
268+
}
269+
270+
impl Volume {
271+
/// Create a new volume level relative to the global volume.
272+
pub fn new_relative(volume: f32) -> Self {
273+
Self::Relative(VolumeLevel::new(volume))
274+
}
275+
/// Create a new volume level that ignores the global volume.
276+
pub fn new_absolute(volume: f32) -> Self {
277+
Self::Absolute(VolumeLevel::new(volume))
278+
}
279+
}
280+
281+
/// A volume level equivalent to a positive only float.
282+
#[derive(Clone, Copy, Deref, DerefMut, Debug)]
283+
pub struct VolumeLevel(pub(crate) f32);
284+
285+
impl Default for VolumeLevel {
286+
fn default() -> Self {
287+
Self(1.0)
288+
}
289+
}
290+
291+
impl VolumeLevel {
292+
/// Create a new volume level.
293+
pub fn new(volume: f32) -> Self {
294+
debug_assert!(volume >= 0.0);
295+
Self(volume)
296+
}
297+
/// Get the value of the volume level.
298+
pub fn get(&self) -> f32 {
299+
self.0
300+
}
301+
}
302+
254303
/// Settings to control playback from the start.
255304
#[derive(Clone, Copy, Debug)]
256305
pub struct PlaybackSettings {
257306
/// Play in repeat
258307
pub repeat: bool,
259308
/// Volume to play at.
260-
pub volume: f32,
309+
pub volume: Volume,
261310
/// Speed to play at.
262311
pub speed: f32,
263312
}
@@ -272,19 +321,19 @@ impl PlaybackSettings {
272321
/// Will play the associate audio source once.
273322
pub const ONCE: PlaybackSettings = PlaybackSettings {
274323
repeat: false,
275-
volume: 1.0,
324+
volume: Volume::Relative(VolumeLevel(1.0)),
276325
speed: 1.0,
277326
};
278327

279328
/// Will play the associate audio source in a loop.
280329
pub const LOOP: PlaybackSettings = PlaybackSettings {
281330
repeat: true,
282-
volume: 1.0,
331+
volume: Volume::Relative(VolumeLevel(1.0)),
283332
speed: 1.0,
284333
};
285334

286335
/// Helper to set the volume from start of playback.
287-
pub const fn with_volume(mut self, volume: f32) -> Self {
336+
pub const fn with_volume(mut self, volume: Volume) -> Self {
288337
self.volume = volume;
289338
self
290339
}
@@ -326,3 +375,21 @@ where
326375
.finish()
327376
}
328377
}
378+
379+
/// Use this [`Resource`] to control the global volume of all audio with a [`Volume::Relative`] volume.
380+
///
381+
/// Keep in mind that changing this value will not affect already playing audio.
382+
#[derive(Resource, Default, Clone, Copy)]
383+
pub struct GlobalVolume {
384+
/// The global volume of all audio.
385+
pub volume: VolumeLevel,
386+
}
387+
388+
impl GlobalVolume {
389+
/// Create a new [`GlobalVolume`] with the given volume.
390+
pub fn new(volume: f32) -> Self {
391+
Self {
392+
volume: VolumeLevel::new(volume),
393+
}
394+
}
395+
}

crates/bevy_audio/src/audio_output.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{Audio, AudioSource, Decodable, SpatialAudioSink, SpatialSettings};
1+
use crate::{
2+
Audio, AudioSource, Decodable, GlobalVolume, SpatialAudioSink, SpatialSettings, Volume,
3+
};
24
use bevy_asset::{Asset, Assets};
35
use bevy_ecs::system::{Res, ResMut, Resource};
46
use bevy_utils::tracing::warn;
@@ -109,6 +111,7 @@ where
109111
audio: &mut Audio<Source>,
110112
sinks: &mut Assets<AudioSink>,
111113
spatial_sinks: &mut Assets<SpatialAudioSink>,
114+
global_volume: &GlobalVolume,
112115
) {
113116
let mut queue = audio.queue.write();
114117
let len = queue.len();
@@ -121,15 +124,24 @@ where
121124
self.play_spatial_source(audio_source, config.settings.repeat, spatial)
122125
{
123126
sink.set_speed(config.settings.speed);
124-
sink.set_volume(config.settings.volume);
127+
match config.settings.volume {
128+
Volume::Relative(vol) => {
129+
sink.set_volume(vol.0 * global_volume.volume.0);
130+
}
131+
Volume::Absolute(vol) => sink.set_volume(vol.0),
132+
}
125133

126134
// don't keep the strong handle. there is no way to return it to the user here as it is async
127135
let _ = spatial_sinks
128136
.set(config.sink_handle, SpatialAudioSink { sink: Some(sink) });
129137
}
130138
} else if let Some(sink) = self.play_source(audio_source, config.settings.repeat) {
131139
sink.set_speed(config.settings.speed);
132-
sink.set_volume(config.settings.volume);
140+
141+
match config.settings.volume {
142+
Volume::Relative(vol) => sink.set_volume(vol.0 * global_volume.volume.0),
143+
Volume::Absolute(vol) => sink.set_volume(vol.0),
144+
}
133145

134146
// don't keep the strong handle. there is no way to return it to the user here as it is async
135147
let _ = sinks.set(config.sink_handle, AudioSink { sink: Some(sink) });
@@ -147,13 +159,20 @@ where
147159
pub fn play_queued_audio_system<Source: Asset + Decodable>(
148160
audio_output: Res<AudioOutput<Source>>,
149161
audio_sources: Option<Res<Assets<Source>>>,
162+
global_volume: Res<GlobalVolume>,
150163
mut audio: ResMut<Audio<Source>>,
151164
mut sinks: ResMut<Assets<AudioSink>>,
152165
mut spatial_sinks: ResMut<Assets<SpatialAudioSink>>,
153166
) where
154167
f32: rodio::cpal::FromSample<Source::DecoderItem>,
155168
{
156169
if let Some(audio_sources) = audio_sources {
157-
audio_output.try_play_queued(&*audio_sources, &mut *audio, &mut sinks, &mut spatial_sinks);
170+
audio_output.try_play_queued(
171+
&*audio_sources,
172+
&mut *audio,
173+
&mut sinks,
174+
&mut spatial_sinks,
175+
&global_volume,
176+
);
158177
};
159178
}

crates/bevy_audio/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! App::new()
1010
//! .add_plugins(MinimalPlugins)
1111
//! .add_plugin(AssetPlugin::default())
12-
//! .add_plugin(AudioPlugin)
12+
//! .add_plugin(AudioPlugin::default())
1313
//! .add_systems(Startup, play_background_audio)
1414
//! .run();
1515
//! }
@@ -32,8 +32,8 @@ mod sinks;
3232
pub mod prelude {
3333
#[doc(hidden)]
3434
pub use crate::{
35-
Audio, AudioOutput, AudioSink, AudioSinkPlayback, AudioSource, Decodable, PlaybackSettings,
36-
SpatialAudioSink,
35+
Audio, AudioOutput, AudioSink, AudioSinkPlayback, AudioSource, Decodable, GlobalVolume,
36+
PlaybackSettings, SpatialAudioSink,
3737
};
3838
}
3939

@@ -53,7 +53,10 @@ use bevy_asset::{AddAsset, Asset};
5353
///
5454
/// Use the [`Audio`] resource to play audio.
5555
#[derive(Default)]
56-
pub struct AudioPlugin;
56+
pub struct AudioPlugin {
57+
/// The global volume for all audio sources with a [`Volume::Relative`] volume.
58+
pub global_volume: GlobalVolume,
59+
}
5760

5861
impl Plugin for AudioPlugin {
5962
fn build(&self, app: &mut App) {
@@ -62,6 +65,7 @@ impl Plugin for AudioPlugin {
6265
.add_asset::<AudioSink>()
6366
.add_asset::<SpatialAudioSink>()
6467
.init_resource::<Audio<AudioSource>>()
68+
.insert_resource(self.global_volume)
6569
.add_systems(PostUpdate, play_queued_audio_system::<AudioSource>);
6670

6771
#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]

examples/audio/decodable.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Shows how to create a custom `Decodable` type by implementing a Sine wave.
2-
//! ***WARNING THIS EXAMPLE IS VERY LOUD.*** Turn your volume down.
32
use bevy::audio::AddAudioSource;
3+
use bevy::audio::AudioPlugin;
44
use bevy::audio::Source;
55
use bevy::prelude::*;
66
use bevy::reflect::TypeUuid;
@@ -85,10 +85,12 @@ impl Decodable for SineAudio {
8585
fn main() {
8686
let mut app = App::new();
8787
// register the audio source so that it can be used
88-
app.add_plugins(DefaultPlugins)
89-
.add_audio_source::<SineAudio>()
90-
.add_systems(Startup, setup)
91-
.run();
88+
app.add_plugins(DefaultPlugins.set(AudioPlugin {
89+
global_volume: GlobalVolume::new(0.2),
90+
}))
91+
.add_audio_source::<SineAudio>()
92+
.add_systems(Startup, setup)
93+
.run();
9294
}
9395

9496
fn setup(mut assets: ResMut<Assets<SineAudio>>, audio: Res<Audio<SineAudio>>) {

0 commit comments

Comments
 (0)