Optional override for global spatial scale (#10419)
# Objective Fixes #10414. That issue and its comments do a great job of laying out the case for this. ## Solution Added an optional `spatial_scale` field to `PlaybackSettings`, which overrides the default value set on `AudioPlugin`. ## Changelog - `AudioPlugin::spatial_scale` has been renamed to `default_spatial_scale`. - `SpatialScale` is no longer a resource and is wrapped by `DefaultSpatialScale`. - Added an optional `spatial_scale` to `PlaybackSettings`. ## Migration Guide `AudioPlugin::spatial_scale` has been renamed to `default_spatial_scale` and the default spatial scale can now be overridden on individual audio sources with `PlaybackSettings::spatial_scale`. If you were modifying or reading `SpatialScale` at run time, use `DefaultSpatialScale` instead. ```rust // before app.add_plugins(DefaultPlugins.set(AudioPlugin { spatial_scale: SpatialScale::new(AUDIO_SCALE), ..default() })); // after app.add_plugins(DefaultPlugins.set(AudioPlugin { default_spatial_scale: SpatialScale::new(AUDIO_SCALE), ..default() })); ```
This commit is contained in:
parent
2ebf5a303e
commit
29224768e4
@ -67,6 +67,9 @@ pub struct PlaybackSettings {
|
|||||||
/// Note: Bevy does not currently support HRTF or any other high-quality 3D sound rendering
|
/// Note: Bevy does not currently support HRTF or any other high-quality 3D sound rendering
|
||||||
/// features. Spatial audio is implemented via simple left-right stereo panning.
|
/// features. Spatial audio is implemented via simple left-right stereo panning.
|
||||||
pub spatial: bool,
|
pub spatial: bool,
|
||||||
|
/// Optional scale factor applied to the positions of this audio source and the listener,
|
||||||
|
/// overriding the default value configured on [`AudioPlugin::default_spatial_scale`](crate::AudioPlugin::default_spatial_scale).
|
||||||
|
pub spatial_scale: Option<SpatialScale>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlaybackSettings {
|
impl Default for PlaybackSettings {
|
||||||
@ -84,6 +87,7 @@ impl PlaybackSettings {
|
|||||||
speed: 1.0,
|
speed: 1.0,
|
||||||
paused: false,
|
paused: false,
|
||||||
spatial: false,
|
spatial: false,
|
||||||
|
spatial_scale: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Will play the associated audio source in a loop.
|
/// Will play the associated audio source in a loop.
|
||||||
@ -127,6 +131,12 @@ impl PlaybackSettings {
|
|||||||
self.spatial = spatial;
|
self.spatial = spatial;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to use a custom spatial scale.
|
||||||
|
pub const fn with_spatial_scale(mut self, spatial_scale: SpatialScale) -> Self {
|
||||||
|
self.spatial_scale = Some(spatial_scale);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings for the listener for spatial audio sources.
|
/// Settings for the listener for spatial audio sources.
|
||||||
@ -180,25 +190,22 @@ impl GlobalVolume {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The scale factor applied to the positions of audio sources and listeners for
|
/// A scale factor applied to the positions of audio sources and listeners for
|
||||||
/// spatial audio.
|
/// spatial audio.
|
||||||
///
|
///
|
||||||
/// You may need to adjust this scale to fit your world's units.
|
|
||||||
///
|
|
||||||
/// Default is `Vec3::ONE`.
|
/// Default is `Vec3::ONE`.
|
||||||
#[derive(Resource, Clone, Copy, Reflect)]
|
#[derive(Clone, Copy, Debug, Reflect)]
|
||||||
#[reflect(Resource)]
|
|
||||||
pub struct SpatialScale(pub Vec3);
|
pub struct SpatialScale(pub Vec3);
|
||||||
|
|
||||||
impl SpatialScale {
|
impl SpatialScale {
|
||||||
/// Create a new `SpatialScale` with the same value for all 3 dimensions.
|
/// Create a new `SpatialScale` with the same value for all 3 dimensions.
|
||||||
pub fn new(scale: f32) -> Self {
|
pub const fn new(scale: f32) -> Self {
|
||||||
Self(Vec3::splat(scale))
|
Self(Vec3::splat(scale))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0`
|
/// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0`
|
||||||
/// for `z`.
|
/// for `z`.
|
||||||
pub fn new_2d(scale: f32) -> Self {
|
pub const fn new_2d(scale: f32) -> Self {
|
||||||
Self(Vec3::new(scale, scale, 0.0))
|
Self(Vec3::new(scale, scale, 0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,6 +216,16 @@ impl Default for SpatialScale {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The default scale factor applied to the positions of audio sources and listeners for
|
||||||
|
/// spatial audio. Can be overridden for individual sounds in [`PlaybackSettings`].
|
||||||
|
///
|
||||||
|
/// You may need to adjust this scale to fit your world's units.
|
||||||
|
///
|
||||||
|
/// Default is `Vec3::ONE`.
|
||||||
|
#[derive(Resource, Default, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Resource)]
|
||||||
|
pub struct DefaultSpatialScale(pub SpatialScale);
|
||||||
|
|
||||||
/// Bundle for playing a standard bevy audio asset
|
/// Bundle for playing a standard bevy audio asset
|
||||||
pub type AudioBundle = AudioSourceBundle<AudioSource>;
|
pub type AudioBundle = AudioSourceBundle<AudioSource>;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AudioSourceBundle, Decodable, GlobalVolume, PlaybackMode, PlaybackSettings, SpatialAudioSink,
|
AudioSourceBundle, Decodable, DefaultSpatialScale, GlobalVolume, PlaybackMode,
|
||||||
SpatialListener, SpatialScale,
|
PlaybackSettings, SpatialAudioSink, SpatialListener,
|
||||||
};
|
};
|
||||||
use bevy_asset::{Asset, Assets, Handle};
|
use bevy_asset::{Asset, Assets, Handle};
|
||||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||||
@ -56,10 +56,9 @@ pub struct PlaybackRemoveMarker;
|
|||||||
#[derive(SystemParam)]
|
#[derive(SystemParam)]
|
||||||
pub(crate) struct EarPositions<'w, 's> {
|
pub(crate) struct EarPositions<'w, 's> {
|
||||||
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
|
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
|
||||||
pub(crate) scale: Res<'w, SpatialScale>,
|
|
||||||
}
|
}
|
||||||
impl<'w, 's> EarPositions<'w, 's> {
|
impl<'w, 's> EarPositions<'w, 's> {
|
||||||
/// Gets a set of transformed and scaled ear positions.
|
/// Gets a set of transformed ear positions.
|
||||||
///
|
///
|
||||||
/// If there are no listeners, use the default values. If a user has added multiple
|
/// If there are no listeners, use the default values. If a user has added multiple
|
||||||
/// listeners for whatever reason, we will return the first value.
|
/// listeners for whatever reason, we will return the first value.
|
||||||
@ -70,16 +69,13 @@ impl<'w, 's> EarPositions<'w, 's> {
|
|||||||
.next()
|
.next()
|
||||||
.map(|(_, transform, settings)| {
|
.map(|(_, transform, settings)| {
|
||||||
(
|
(
|
||||||
transform.transform_point(settings.left_ear_offset) * self.scale.0,
|
transform.transform_point(settings.left_ear_offset),
|
||||||
transform.transform_point(settings.right_ear_offset) * self.scale.0,
|
transform.transform_point(settings.right_ear_offset),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
let settings = SpatialListener::default();
|
let settings = SpatialListener::default();
|
||||||
(
|
(settings.left_ear_offset, settings.right_ear_offset)
|
||||||
settings.left_ear_offset * self.scale.0,
|
|
||||||
settings.right_ear_offset * self.scale.0,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
(left_ear, right_ear)
|
(left_ear, right_ear)
|
||||||
@ -112,6 +108,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
|||||||
(Without<AudioSink>, Without<SpatialAudioSink>),
|
(Without<AudioSink>, Without<SpatialAudioSink>),
|
||||||
>,
|
>,
|
||||||
ear_positions: EarPositions,
|
ear_positions: EarPositions,
|
||||||
|
default_spatial_scale: Res<DefaultSpatialScale>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) where
|
) where
|
||||||
f32: rodio::cpal::FromSample<Source::DecoderItem>,
|
f32: rodio::cpal::FromSample<Source::DecoderItem>,
|
||||||
@ -138,8 +135,10 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
|
||||||
|
|
||||||
let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform {
|
let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform {
|
||||||
(emitter_transform.translation() * ear_positions.scale.0).into()
|
(emitter_transform.translation() * scale).into()
|
||||||
} else {
|
} else {
|
||||||
warn!("Spatial AudioBundle with no GlobalTransform component. Using zero.");
|
warn!("Spatial AudioBundle with no GlobalTransform component. Using zero.");
|
||||||
Vec3::ZERO.into()
|
Vec3::ZERO.into()
|
||||||
@ -148,8 +147,8 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
|||||||
let sink = match SpatialSink::try_new(
|
let sink = match SpatialSink::try_new(
|
||||||
stream_handle,
|
stream_handle,
|
||||||
emitter_translation,
|
emitter_translation,
|
||||||
left_ear.into(),
|
(left_ear * scale).into(),
|
||||||
right_ear.into(),
|
(right_ear * scale).into(),
|
||||||
) {
|
) {
|
||||||
Ok(sink) => sink,
|
Ok(sink) => sink,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -285,34 +284,46 @@ pub(crate) fn audio_output_available(audio_output: Res<AudioOutput>) -> bool {
|
|||||||
|
|
||||||
/// Updates spatial audio sinks when emitter positions change.
|
/// Updates spatial audio sinks when emitter positions change.
|
||||||
pub(crate) fn update_emitter_positions(
|
pub(crate) fn update_emitter_positions(
|
||||||
mut emitters: Query<(&GlobalTransform, &SpatialAudioSink), Changed<GlobalTransform>>,
|
mut emitters: Query<
|
||||||
spatial_scale: Res<SpatialScale>,
|
(&GlobalTransform, &SpatialAudioSink, &PlaybackSettings),
|
||||||
|
Or<(Changed<GlobalTransform>, Changed<PlaybackSettings>)>,
|
||||||
|
>,
|
||||||
|
default_spatial_scale: Res<DefaultSpatialScale>,
|
||||||
) {
|
) {
|
||||||
for (transform, sink) in emitters.iter_mut() {
|
for (transform, sink, settings) in emitters.iter_mut() {
|
||||||
let translation = transform.translation() * spatial_scale.0;
|
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
|
||||||
|
|
||||||
|
let translation = transform.translation() * scale;
|
||||||
sink.set_emitter_position(translation);
|
sink.set_emitter_position(translation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates spatial audio sink ear positions when spatial listeners change.
|
/// Updates spatial audio sink ear positions when spatial listeners change.
|
||||||
pub(crate) fn update_listener_positions(
|
pub(crate) fn update_listener_positions(
|
||||||
mut emitters: Query<&SpatialAudioSink>,
|
mut emitters: Query<(&SpatialAudioSink, &PlaybackSettings)>,
|
||||||
changed_listener: Query<
|
changed_listener: Query<
|
||||||
(),
|
(),
|
||||||
(
|
(
|
||||||
Or<(Changed<SpatialListener>, Changed<GlobalTransform>)>,
|
Or<(
|
||||||
|
Changed<SpatialListener>,
|
||||||
|
Changed<GlobalTransform>,
|
||||||
|
Changed<PlaybackSettings>,
|
||||||
|
)>,
|
||||||
With<SpatialListener>,
|
With<SpatialListener>,
|
||||||
),
|
),
|
||||||
>,
|
>,
|
||||||
ear_positions: EarPositions,
|
ear_positions: EarPositions,
|
||||||
|
default_spatial_scale: Res<DefaultSpatialScale>,
|
||||||
) {
|
) {
|
||||||
if !ear_positions.scale.is_changed() && changed_listener.is_empty() {
|
if !default_spatial_scale.is_changed() && changed_listener.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (left_ear, right_ear) = ear_positions.get();
|
let (left_ear, right_ear) = ear_positions.get();
|
||||||
|
|
||||||
for sink in emitters.iter_mut() {
|
for (sink, settings) in emitters.iter_mut() {
|
||||||
sink.set_ears_position(left_ear, right_ear);
|
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
|
||||||
|
|
||||||
|
sink.set_ears_position(left_ear * scale, right_ear * scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ pub struct AudioPlugin {
|
|||||||
pub global_volume: GlobalVolume,
|
pub global_volume: GlobalVolume,
|
||||||
/// The scale factor applied to the positions of audio sources and listeners for
|
/// The scale factor applied to the positions of audio sources and listeners for
|
||||||
/// spatial audio.
|
/// spatial audio.
|
||||||
pub spatial_scale: SpatialScale,
|
pub default_spatial_scale: SpatialScale,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for AudioPlugin {
|
impl Plugin for AudioPlugin {
|
||||||
@ -75,11 +75,11 @@ impl Plugin for AudioPlugin {
|
|||||||
app.register_type::<Volume>()
|
app.register_type::<Volume>()
|
||||||
.register_type::<GlobalVolume>()
|
.register_type::<GlobalVolume>()
|
||||||
.register_type::<SpatialListener>()
|
.register_type::<SpatialListener>()
|
||||||
.register_type::<SpatialScale>()
|
.register_type::<DefaultSpatialScale>()
|
||||||
.register_type::<PlaybackMode>()
|
.register_type::<PlaybackMode>()
|
||||||
.register_type::<PlaybackSettings>()
|
.register_type::<PlaybackSettings>()
|
||||||
.insert_resource(self.global_volume)
|
.insert_resource(self.global_volume)
|
||||||
.insert_resource(self.spatial_scale)
|
.insert_resource(DefaultSpatialScale(self.default_spatial_scale))
|
||||||
.configure_sets(
|
.configure_sets(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
AudioPlaySet
|
AudioPlaySet
|
||||||
|
@ -13,7 +13,7 @@ const AUDIO_SCALE: f32 = 1. / 100.0;
|
|||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(AudioPlugin {
|
.add_plugins(DefaultPlugins.set(AudioPlugin {
|
||||||
spatial_scale: SpatialScale::new_2d(AUDIO_SCALE),
|
default_spatial_scale: SpatialScale::new_2d(AUDIO_SCALE),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
|
Loading…
Reference in New Issue
Block a user