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:
Rob Parrett 2024-01-25 09:29:35 -07:00 committed by GitHub
parent 2ebf5a303e
commit 29224768e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 33 deletions

View file

@ -67,6 +67,9 @@ pub struct PlaybackSettings {
/// 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.
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 {
@ -84,6 +87,7 @@ impl PlaybackSettings {
speed: 1.0,
paused: false,
spatial: false,
spatial_scale: None,
};
/// Will play the associated audio source in a loop.
@ -127,6 +131,12 @@ impl PlaybackSettings {
self.spatial = spatial;
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.
@ -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.
///
/// You may need to adjust this scale to fit your world's units.
///
/// Default is `Vec3::ONE`.
#[derive(Resource, Clone, Copy, Reflect)]
#[reflect(Resource)]
#[derive(Clone, Copy, Debug, Reflect)]
pub struct SpatialScale(pub Vec3);
impl SpatialScale {
/// 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))
}
/// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0`
/// for `z`.
pub fn new_2d(scale: f32) -> Self {
pub const fn new_2d(scale: f32) -> Self {
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
pub type AudioBundle = AudioSourceBundle<AudioSource>;

View file

@ -1,6 +1,6 @@
use crate::{
AudioSourceBundle, Decodable, GlobalVolume, PlaybackMode, PlaybackSettings, SpatialAudioSink,
SpatialListener, SpatialScale,
AudioSourceBundle, Decodable, DefaultSpatialScale, GlobalVolume, PlaybackMode,
PlaybackSettings, SpatialAudioSink, SpatialListener,
};
use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::{prelude::*, system::SystemParam};
@ -56,10 +56,9 @@ pub struct PlaybackRemoveMarker;
#[derive(SystemParam)]
pub(crate) struct EarPositions<'w, 's> {
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
pub(crate) scale: Res<'w, SpatialScale>,
}
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
/// listeners for whatever reason, we will return the first value.
@ -70,16 +69,13 @@ impl<'w, 's> EarPositions<'w, 's> {
.next()
.map(|(_, transform, settings)| {
(
transform.transform_point(settings.left_ear_offset) * self.scale.0,
transform.transform_point(settings.right_ear_offset) * self.scale.0,
transform.transform_point(settings.left_ear_offset),
transform.transform_point(settings.right_ear_offset),
)
})
.unwrap_or_else(|| {
let settings = SpatialListener::default();
(
settings.left_ear_offset * self.scale.0,
settings.right_ear_offset * self.scale.0,
)
(settings.left_ear_offset, settings.right_ear_offset)
});
(left_ear, right_ear)
@ -112,6 +108,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
(Without<AudioSink>, Without<SpatialAudioSink>),
>,
ear_positions: EarPositions,
default_spatial_scale: Res<DefaultSpatialScale>,
mut commands: Commands,
) where
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 {
(emitter_transform.translation() * ear_positions.scale.0).into()
(emitter_transform.translation() * scale).into()
} else {
warn!("Spatial AudioBundle with no GlobalTransform component. Using zero.");
Vec3::ZERO.into()
@ -148,8 +147,8 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
let sink = match SpatialSink::try_new(
stream_handle,
emitter_translation,
left_ear.into(),
right_ear.into(),
(left_ear * scale).into(),
(right_ear * scale).into(),
) {
Ok(sink) => sink,
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.
pub(crate) fn update_emitter_positions(
mut emitters: Query<(&GlobalTransform, &SpatialAudioSink), Changed<GlobalTransform>>,
spatial_scale: Res<SpatialScale>,
mut emitters: Query<
(&GlobalTransform, &SpatialAudioSink, &PlaybackSettings),
Or<(Changed<GlobalTransform>, Changed<PlaybackSettings>)>,
>,
default_spatial_scale: Res<DefaultSpatialScale>,
) {
for (transform, sink) in emitters.iter_mut() {
let translation = transform.translation() * spatial_scale.0;
for (transform, sink, settings) in emitters.iter_mut() {
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
let translation = transform.translation() * scale;
sink.set_emitter_position(translation);
}
}
/// Updates spatial audio sink ear positions when spatial listeners change.
pub(crate) fn update_listener_positions(
mut emitters: Query<&SpatialAudioSink>,
mut emitters: Query<(&SpatialAudioSink, &PlaybackSettings)>,
changed_listener: Query<
(),
(
Or<(Changed<SpatialListener>, Changed<GlobalTransform>)>,
Or<(
Changed<SpatialListener>,
Changed<GlobalTransform>,
Changed<PlaybackSettings>,
)>,
With<SpatialListener>,
),
>,
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;
}
let (left_ear, right_ear) = ear_positions.get();
for sink in emitters.iter_mut() {
sink.set_ears_position(left_ear, right_ear);
for (sink, settings) in emitters.iter_mut() {
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
sink.set_ears_position(left_ear * scale, right_ear * scale);
}
}

View file

@ -67,7 +67,7 @@ pub struct AudioPlugin {
pub global_volume: GlobalVolume,
/// The scale factor applied to the positions of audio sources and listeners for
/// spatial audio.
pub spatial_scale: SpatialScale,
pub default_spatial_scale: SpatialScale,
}
impl Plugin for AudioPlugin {
@ -75,11 +75,11 @@ impl Plugin for AudioPlugin {
app.register_type::<Volume>()
.register_type::<GlobalVolume>()
.register_type::<SpatialListener>()
.register_type::<SpatialScale>()
.register_type::<DefaultSpatialScale>()
.register_type::<PlaybackMode>()
.register_type::<PlaybackSettings>()
.insert_resource(self.global_volume)
.insert_resource(self.spatial_scale)
.insert_resource(DefaultSpatialScale(self.default_spatial_scale))
.configure_sets(
PostUpdate,
AudioPlaySet

View file

@ -13,7 +13,7 @@ const AUDIO_SCALE: f32 = 1. / 100.0;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AudioPlugin {
spatial_scale: SpatialScale::new_2d(AUDIO_SCALE),
default_spatial_scale: SpatialScale::new_2d(AUDIO_SCALE),
..default()
}))
.add_systems(Startup, setup)