mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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>
This commit is contained in:
parent
cc8f023b3a
commit
dff071c2a8
5 changed files with 114 additions and 21 deletions
|
@ -16,6 +16,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev" }
|
|||
bevy_math = { path = "../bevy_math", version = "0.11.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev", features = ["bevy"] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.11.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.11.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
|
||||
|
||||
# other
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{AudioSink, AudioSource, Decodable, SpatialAudioSink};
|
||||
use bevy_asset::{Asset, Handle, HandleId};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::system::Resource;
|
||||
use bevy_math::Vec3;
|
||||
use bevy_transform::prelude::Transform;
|
||||
|
@ -97,12 +98,12 @@ where
|
|||
/// ```
|
||||
/// # use bevy_ecs::system::Res;
|
||||
/// # use bevy_asset::AssetServer;
|
||||
/// # use bevy_audio::Audio;
|
||||
/// # use bevy_audio::{Audio, Volume};
|
||||
/// # use bevy_audio::PlaybackSettings;
|
||||
/// fn play_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
|
||||
/// audio.play_with_settings(
|
||||
/// asset_server.load("my_sound.ogg"),
|
||||
/// PlaybackSettings::LOOP.with_volume(0.75),
|
||||
/// PlaybackSettings::LOOP.with_volume(Volume::new_relative(0.75)),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -210,14 +211,14 @@ where
|
|||
/// ```
|
||||
/// # use bevy_ecs::system::Res;
|
||||
/// # use bevy_asset::AssetServer;
|
||||
/// # use bevy_audio::Audio;
|
||||
/// # use bevy_audio::{Audio, Volume};
|
||||
/// # use bevy_audio::PlaybackSettings;
|
||||
/// # use bevy_math::Vec3;
|
||||
/// # use bevy_transform::prelude::Transform;
|
||||
/// fn play_spatial_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
|
||||
/// audio.play_spatial_with_settings(
|
||||
/// asset_server.load("my_sound.ogg"),
|
||||
/// PlaybackSettings::LOOP.with_volume(0.75),
|
||||
/// PlaybackSettings::LOOP.with_volume(Volume::new_relative(0.75)),
|
||||
/// Transform::IDENTITY,
|
||||
/// 1.0,
|
||||
/// Vec3::new(-2.0, 0.0, 1.0),
|
||||
|
@ -251,13 +252,61 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Defines the volume to play an audio source at.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Volume {
|
||||
/// A volume level relative to the global volume.
|
||||
Relative(VolumeLevel),
|
||||
/// A volume level that ignores the global volume.
|
||||
Absolute(VolumeLevel),
|
||||
}
|
||||
|
||||
impl Default for Volume {
|
||||
fn default() -> Self {
|
||||
Self::Relative(VolumeLevel::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Volume {
|
||||
/// Create a new volume level relative to the global volume.
|
||||
pub fn new_relative(volume: f32) -> Self {
|
||||
Self::Relative(VolumeLevel::new(volume))
|
||||
}
|
||||
/// Create a new volume level that ignores the global volume.
|
||||
pub fn new_absolute(volume: f32) -> Self {
|
||||
Self::Absolute(VolumeLevel::new(volume))
|
||||
}
|
||||
}
|
||||
|
||||
/// A volume level equivalent to a positive only float.
|
||||
#[derive(Clone, Copy, Deref, DerefMut, Debug)]
|
||||
pub struct VolumeLevel(pub(crate) f32);
|
||||
|
||||
impl Default for VolumeLevel {
|
||||
fn default() -> Self {
|
||||
Self(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl VolumeLevel {
|
||||
/// Create a new volume level.
|
||||
pub fn new(volume: f32) -> Self {
|
||||
debug_assert!(volume >= 0.0);
|
||||
Self(volume)
|
||||
}
|
||||
/// Get the value of the volume level.
|
||||
pub fn get(&self) -> f32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings to control playback from the start.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PlaybackSettings {
|
||||
/// Play in repeat
|
||||
pub repeat: bool,
|
||||
/// Volume to play at.
|
||||
pub volume: f32,
|
||||
pub volume: Volume,
|
||||
/// Speed to play at.
|
||||
pub speed: f32,
|
||||
}
|
||||
|
@ -272,19 +321,19 @@ impl PlaybackSettings {
|
|||
/// Will play the associate audio source once.
|
||||
pub const ONCE: PlaybackSettings = PlaybackSettings {
|
||||
repeat: false,
|
||||
volume: 1.0,
|
||||
volume: Volume::Relative(VolumeLevel(1.0)),
|
||||
speed: 1.0,
|
||||
};
|
||||
|
||||
/// Will play the associate audio source in a loop.
|
||||
pub const LOOP: PlaybackSettings = PlaybackSettings {
|
||||
repeat: true,
|
||||
volume: 1.0,
|
||||
volume: Volume::Relative(VolumeLevel(1.0)),
|
||||
speed: 1.0,
|
||||
};
|
||||
|
||||
/// Helper to set the volume from start of playback.
|
||||
pub const fn with_volume(mut self, volume: f32) -> Self {
|
||||
pub const fn with_volume(mut self, volume: Volume) -> Self {
|
||||
self.volume = volume;
|
||||
self
|
||||
}
|
||||
|
@ -326,3 +375,21 @@ where
|
|||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this [`Resource`] to control the global volume of all audio with a [`Volume::Relative`] volume.
|
||||
///
|
||||
/// Keep in mind that changing this value will not affect already playing audio.
|
||||
#[derive(Resource, Default, Clone, Copy)]
|
||||
pub struct GlobalVolume {
|
||||
/// The global volume of all audio.
|
||||
pub volume: VolumeLevel,
|
||||
}
|
||||
|
||||
impl GlobalVolume {
|
||||
/// Create a new [`GlobalVolume`] with the given volume.
|
||||
pub fn new(volume: f32) -> Self {
|
||||
Self {
|
||||
volume: VolumeLevel::new(volume),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::{Audio, AudioSource, Decodable, SpatialAudioSink, SpatialSettings};
|
||||
use crate::{
|
||||
Audio, AudioSource, Decodable, GlobalVolume, SpatialAudioSink, SpatialSettings, Volume,
|
||||
};
|
||||
use bevy_asset::{Asset, Assets};
|
||||
use bevy_ecs::system::{Res, ResMut, Resource};
|
||||
use bevy_utils::tracing::warn;
|
||||
|
@ -109,6 +111,7 @@ where
|
|||
audio: &mut Audio<Source>,
|
||||
sinks: &mut Assets<AudioSink>,
|
||||
spatial_sinks: &mut Assets<SpatialAudioSink>,
|
||||
global_volume: &GlobalVolume,
|
||||
) {
|
||||
let mut queue = audio.queue.write();
|
||||
let len = queue.len();
|
||||
|
@ -121,7 +124,12 @@ where
|
|||
self.play_spatial_source(audio_source, config.settings.repeat, spatial)
|
||||
{
|
||||
sink.set_speed(config.settings.speed);
|
||||
sink.set_volume(config.settings.volume);
|
||||
match config.settings.volume {
|
||||
Volume::Relative(vol) => {
|
||||
sink.set_volume(vol.0 * global_volume.volume.0);
|
||||
}
|
||||
Volume::Absolute(vol) => sink.set_volume(vol.0),
|
||||
}
|
||||
|
||||
// don't keep the strong handle. there is no way to return it to the user here as it is async
|
||||
let _ = spatial_sinks
|
||||
|
@ -129,7 +137,11 @@ where
|
|||
}
|
||||
} else if let Some(sink) = self.play_source(audio_source, config.settings.repeat) {
|
||||
sink.set_speed(config.settings.speed);
|
||||
sink.set_volume(config.settings.volume);
|
||||
|
||||
match config.settings.volume {
|
||||
Volume::Relative(vol) => sink.set_volume(vol.0 * global_volume.volume.0),
|
||||
Volume::Absolute(vol) => sink.set_volume(vol.0),
|
||||
}
|
||||
|
||||
// don't keep the strong handle. there is no way to return it to the user here as it is async
|
||||
let _ = sinks.set(config.sink_handle, AudioSink { sink: Some(sink) });
|
||||
|
@ -147,6 +159,7 @@ where
|
|||
pub fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
audio_output: Res<AudioOutput<Source>>,
|
||||
audio_sources: Option<Res<Assets<Source>>>,
|
||||
global_volume: Res<GlobalVolume>,
|
||||
mut audio: ResMut<Audio<Source>>,
|
||||
mut sinks: ResMut<Assets<AudioSink>>,
|
||||
mut spatial_sinks: ResMut<Assets<SpatialAudioSink>>,
|
||||
|
@ -154,6 +167,12 @@ pub fn play_queued_audio_system<Source: Asset + Decodable>(
|
|||
f32: rodio::cpal::FromSample<Source::DecoderItem>,
|
||||
{
|
||||
if let Some(audio_sources) = audio_sources {
|
||||
audio_output.try_play_queued(&*audio_sources, &mut *audio, &mut sinks, &mut spatial_sinks);
|
||||
audio_output.try_play_queued(
|
||||
&*audio_sources,
|
||||
&mut *audio,
|
||||
&mut sinks,
|
||||
&mut spatial_sinks,
|
||||
&global_volume,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//! App::new()
|
||||
//! .add_plugins(MinimalPlugins)
|
||||
//! .add_plugin(AssetPlugin::default())
|
||||
//! .add_plugin(AudioPlugin)
|
||||
//! .add_plugin(AudioPlugin::default())
|
||||
//! .add_systems(Startup, play_background_audio)
|
||||
//! .run();
|
||||
//! }
|
||||
|
@ -32,8 +32,8 @@ mod sinks;
|
|||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
Audio, AudioOutput, AudioSink, AudioSinkPlayback, AudioSource, Decodable, PlaybackSettings,
|
||||
SpatialAudioSink,
|
||||
Audio, AudioOutput, AudioSink, AudioSinkPlayback, AudioSource, Decodable, GlobalVolume,
|
||||
PlaybackSettings, SpatialAudioSink,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,10 @@ use bevy_asset::{AddAsset, Asset};
|
|||
///
|
||||
/// Use the [`Audio`] resource to play audio.
|
||||
#[derive(Default)]
|
||||
pub struct AudioPlugin;
|
||||
pub struct AudioPlugin {
|
||||
/// The global volume for all audio sources with a [`Volume::Relative`] volume.
|
||||
pub global_volume: GlobalVolume,
|
||||
}
|
||||
|
||||
impl Plugin for AudioPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
|
@ -62,6 +65,7 @@ impl Plugin for AudioPlugin {
|
|||
.add_asset::<AudioSink>()
|
||||
.add_asset::<SpatialAudioSink>()
|
||||
.init_resource::<Audio<AudioSource>>()
|
||||
.insert_resource(self.global_volume)
|
||||
.add_systems(PostUpdate, play_queued_audio_system::<AudioSource>);
|
||||
|
||||
#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Shows how to create a custom `Decodable` type by implementing a Sine wave.
|
||||
//! ***WARNING THIS EXAMPLE IS VERY LOUD.*** Turn your volume down.
|
||||
use bevy::audio::AddAudioSource;
|
||||
use bevy::audio::AudioPlugin;
|
||||
use bevy::audio::Source;
|
||||
use bevy::prelude::*;
|
||||
use bevy::reflect::TypeUuid;
|
||||
|
@ -85,10 +85,12 @@ impl Decodable for SineAudio {
|
|||
fn main() {
|
||||
let mut app = App::new();
|
||||
// register the audio source so that it can be used
|
||||
app.add_plugins(DefaultPlugins)
|
||||
.add_audio_source::<SineAudio>()
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
app.add_plugins(DefaultPlugins.set(AudioPlugin {
|
||||
global_volume: GlobalVolume::new(0.2),
|
||||
}))
|
||||
.add_audio_source::<SineAudio>()
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut assets: ResMut<Assets<SineAudio>>, audio: Res<Audio<SineAudio>>) {
|
||||
|
|
Loading…
Reference in a new issue