1- use crate :: { AudioSink , AudioSource , Decodable } ;
1+ use crate :: { AudioSink , AudioSource , Decodable , SpatialAudioSink } ;
22use bevy_asset:: { Asset , Handle , HandleId } ;
33use bevy_ecs:: system:: Resource ;
4+ use bevy_math:: Vec3 ;
5+ use bevy_transform:: prelude:: Transform ;
46use parking_lot:: RwLock ;
57use std:: { collections:: VecDeque , fmt} ;
68
6062 ///
6163 /// Returns a weak [`Handle`] to the [`AudioSink`]. If this handle isn't changed to a
6264 /// strong one, the sink will be detached and the sound will continue playing. Changing it
63- /// to a strong handle allows for control on the playback through the [`AudioSink`] asset.
65+ /// to a strong handle allows you to control the playback through the [`AudioSink`] asset.
6466 ///
6567 /// ```
6668 /// # use bevy_ecs::system::Res;
8385 settings : PlaybackSettings :: ONCE ,
8486 sink_handle : id,
8587 source_handle : audio_source,
88+ spatial : None ,
8689 } ;
8790 self . queue . write ( ) . push_back ( config) ;
8891 Handle :: < AudioSink > :: weak ( id)
@@ -115,14 +118,141 @@ where
115118 settings,
116119 sink_handle : id,
117120 source_handle : audio_source,
121+ spatial : None ,
118122 } ;
119123 self . queue . write ( ) . push_back ( config) ;
120124 Handle :: < AudioSink > :: weak ( id)
121125 }
126+
127+ /// Play audio from a [`Handle`] to the audio source, placing the listener at the given
128+ /// transform, an ear on each side separated by `gap`. The audio emitter will placed at
129+ /// `emitter`.
130+ ///
131+ /// `bevy_audio` is not using HRTF for spatial audio, but is transforming the sound to a mono
132+ /// track, and then changing the level of each stereo channel according to the distance between
133+ /// the emitter and each ear by amplifying the difference between what the two ears hear.
134+ ///
135+ /// ```
136+ /// # use bevy_ecs::system::Res;
137+ /// # use bevy_asset::AssetServer;
138+ /// # use bevy_audio::Audio;
139+ /// # use bevy_math::Vec3;
140+ /// # use bevy_transform::prelude::Transform;
141+ /// fn play_spatial_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
142+ /// // Sound will be to the left and behind the listener
143+ /// audio.play_spatial(
144+ /// asset_server.load("my_sound.ogg"),
145+ /// Transform::IDENTITY,
146+ /// 1.0,
147+ /// Vec3::new(-2.0, 0.0, 1.0),
148+ /// );
149+ /// }
150+ /// ```
151+ ///
152+ /// Returns a weak [`Handle`] to the [`SpatialAudioSink`]. If this handle isn't changed to a
153+ /// strong one, the sink will be detached and the sound will continue playing. Changing it
154+ /// to a strong handle allows you to control the playback, or move the listener and emitter
155+ /// through the [`SpatialAudioSink`] asset.
156+ ///
157+ /// ```
158+ /// # use bevy_ecs::system::Res;
159+ /// # use bevy_asset::{AssetServer, Assets};
160+ /// # use bevy_audio::{Audio, SpatialAudioSink};
161+ /// # use bevy_math::Vec3;
162+ /// # use bevy_transform::prelude::Transform;
163+ /// fn play_spatial_audio_system(
164+ /// asset_server: Res<AssetServer>,
165+ /// audio: Res<Audio>,
166+ /// spatial_audio_sinks: Res<Assets<SpatialAudioSink>>,
167+ /// ) {
168+ /// // This is a weak handle, and can't be used to control playback.
169+ /// let weak_handle = audio.play_spatial(
170+ /// asset_server.load("my_sound.ogg"),
171+ /// Transform::IDENTITY,
172+ /// 1.0,
173+ /// Vec3::new(-2.0, 0.0, 1.0),
174+ /// );
175+ /// // This is now a strong handle, and can be used to control playback, or move the emitter.
176+ /// let strong_handle = spatial_audio_sinks.get_handle(weak_handle);
177+ /// }
178+ /// ```
179+ pub fn play_spatial (
180+ & self ,
181+ audio_source : Handle < Source > ,
182+ listener : Transform ,
183+ gap : f32 ,
184+ emitter : Vec3 ,
185+ ) -> Handle < SpatialAudioSink > {
186+ let id = HandleId :: random :: < SpatialAudioSink > ( ) ;
187+ let config = AudioToPlay {
188+ settings : PlaybackSettings :: ONCE ,
189+ sink_handle : id,
190+ source_handle : audio_source,
191+ spatial : Some ( SpatialSettings {
192+ left_ear : ( listener. translation + listener. left ( ) * gap / 2.0 ) . to_array ( ) ,
193+ right_ear : ( listener. translation + listener. right ( ) * gap / 2.0 ) . to_array ( ) ,
194+ emitter : emitter. to_array ( ) ,
195+ } ) ,
196+ } ;
197+ self . queue . write ( ) . push_back ( config) ;
198+ Handle :: < SpatialAudioSink > :: weak ( id)
199+ }
200+
201+ /// Play spatial audio from a [`Handle`] to the audio source with [`PlaybackSettings`] that
202+ /// allows looping or changing volume from the start. The listener is placed at the given
203+ /// transform, an ear on each side separated by `gap`. The audio emitter is placed at
204+ /// `emitter`.
205+ ///
206+ /// `bevy_audio` is not using HRTF for spatial audio, but is transforming the sound to a mono
207+ /// track, and then changing the level of each stereo channel according to the distance between
208+ /// the emitter and each ear by amplifying the difference between what the two ears hear.
209+ ///
210+ /// ```
211+ /// # use bevy_ecs::system::Res;
212+ /// # use bevy_asset::AssetServer;
213+ /// # use bevy_audio::Audio;
214+ /// # use bevy_audio::PlaybackSettings;
215+ /// # use bevy_math::Vec3;
216+ /// # use bevy_transform::prelude::Transform;
217+ /// fn play_spatial_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
218+ /// audio.play_spatial_with_settings(
219+ /// asset_server.load("my_sound.ogg"),
220+ /// PlaybackSettings::LOOP.with_volume(0.75),
221+ /// Transform::IDENTITY,
222+ /// 1.0,
223+ /// Vec3::new(-2.0, 0.0, 1.0),
224+ /// );
225+ /// }
226+ /// ```
227+ ///
228+ /// See [`Self::play_spatial`] on how to control playback once it's started, or how to move
229+ /// the listener or the emitter.
230+ pub fn play_spatial_with_settings (
231+ & self ,
232+ audio_source : Handle < Source > ,
233+ settings : PlaybackSettings ,
234+ listener : Transform ,
235+ gap : f32 ,
236+ emitter : Vec3 ,
237+ ) -> Handle < SpatialAudioSink > {
238+ let id = HandleId :: random :: < SpatialAudioSink > ( ) ;
239+ let config = AudioToPlay {
240+ settings,
241+ sink_handle : id,
242+ source_handle : audio_source,
243+ spatial : Some ( SpatialSettings {
244+ left_ear : ( listener. translation + listener. left ( ) * gap / 2.0 ) . to_array ( ) ,
245+ right_ear : ( listener. translation + listener. right ( ) * gap / 2.0 ) . to_array ( ) ,
246+ emitter : emitter. to_array ( ) ,
247+ } ) ,
248+ } ;
249+ self . queue . write ( ) . push_back ( config) ;
250+ Handle :: < SpatialAudioSink > :: weak ( id)
251+ }
122252}
123253
124254/// Settings to control playback from the start.
125- #[ derive( Clone , Debug ) ]
255+ #[ derive( Clone , Copy , Debug ) ]
126256pub struct PlaybackSettings {
127257 /// Play in repeat
128258 pub repeat : bool ,
@@ -166,6 +296,13 @@ impl PlaybackSettings {
166296 }
167297}
168298
299+ #[ derive( Clone ) ]
300+ pub ( crate ) struct SpatialSettings {
301+ pub ( crate ) left_ear : [ f32 ; 3 ] ,
302+ pub ( crate ) right_ear : [ f32 ; 3 ] ,
303+ pub ( crate ) emitter : [ f32 ; 3 ] ,
304+ }
305+
169306#[ derive( Clone ) ]
170307pub ( crate ) struct AudioToPlay < Source >
171308where
@@ -174,6 +311,7 @@ where
174311 pub ( crate ) sink_handle : HandleId ,
175312 pub ( crate ) source_handle : Handle < Source > ,
176313 pub ( crate ) settings : PlaybackSettings ,
314+ pub ( crate ) spatial : Option < SpatialSettings > ,
177315}
178316
179317impl < Source > fmt:: Debug for AudioToPlay < Source >
0 commit comments