Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional override for global spatial scale #10419

Merged
merged 4 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions crates/bevy_audio/src/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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))
}
}
Expand All @@ -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>;

Expand Down
55 changes: 33 additions & 22 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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>,
Expand All @@ -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()
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
}
}
6 changes: 3 additions & 3 deletions crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,19 @@ 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 {
fn build(&self, app: &mut App) {
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
Expand Down
2 changes: 1 addition & 1 deletion examples/audio/spatial_audio_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down