Skip to content

Commit 2c4cb3a

Browse files
authored
Add support for spatial audio (#44)
# Objective - Add basic support for spatial audio - Update to 0.9 - Unblocks #22 ## Solution - Added `play_spatial`, `play_spatial_buffered` and `set_listener_rotation`.
1 parent 17fbcd9 commit 2c4cb3a

File tree

9 files changed

+670
-33
lines changed

9 files changed

+670
-33
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ hound = { version = "3.4", optional = true }
2020
lewton = { version = "0.10", optional = true }
2121
claxon = { version = "0.4", optional = true }
2222
minimp3 = { version = "0.5", optional = true }
23+
bevy_math = { version = "0.9", features = ["mint"] }
2324

2425
[features]
2526
wav = ["hound"]
@@ -28,8 +29,7 @@ ogg = ["lewton"]
2829
flac = ["claxon"]
2930

3031
[dependencies.bevy]
31-
# git = "https://github.com/bevyengine/bevy.git"
32-
version = "0.8"
32+
version = "0.9"
3333
default-features = false
3434
features = ["bevy_asset"]
3535

@@ -38,7 +38,7 @@ fastrand = "1.8"
3838

3939
[dev-dependencies.bevy]
4040
# git = "https://github.com/bevyengine/bevy.git"
41-
version = "0.8"
41+
version = "0.9"
4242
default-features = false
4343
features = [
4444
"render",

examples/gain.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy::{
2-
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, StartupStage},
2+
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, Resource, StartupStage},
33
reflect::TypeUuid,
44
time::Time,
55
DefaultPlugins,
@@ -31,9 +31,9 @@ fn main() {
3131
.run();
3232
}
3333

34-
#[derive(Deref)]
34+
#[derive(Resource, Deref)]
3535
struct SineWithGainHandle(Handle<SineWithGain>);
36-
36+
#[derive(Resource)]
3737
struct SineWithGainSink(Handle<AudioSink<SineWithGain>>);
3838

3939
fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<SineWithGain>>) {
@@ -60,7 +60,7 @@ fn change_volume(
6060
None => return,
6161
};
6262

63-
let factor = (time.seconds_since_startup().sin() + 1.0) / 2.0;
63+
let factor = (time.elapsed_seconds_wrapped().sin() + 1.0) / 2.0;
6464

6565
sink.control::<oddio::Gain<_>, _>()
6666
.set_amplitude_ratio(factor as f32);

examples/noise.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy::{
2-
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, StartupStage},
2+
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, Resource, StartupStage},
33
reflect::TypeUuid,
44
DefaultPlugins,
55
};
@@ -9,6 +9,7 @@ use oddio::Signal;
99
#[derive(TypeUuid)]
1010
#[uuid = "7cc24057-b499-4f7a-8f8a-e37dfa64be32"]
1111
struct Noise;
12+
#[derive(Resource)]
1213
struct NoiseSignal;
1314

1415
impl Signal for NoiseSignal {
@@ -42,9 +43,9 @@ fn main() {
4243
.run();
4344
}
4445

45-
#[derive(Deref)]
46+
#[derive(Resource, Deref)]
4647
struct NoiseHandle(Handle<Noise>);
47-
48+
#[derive(Resource)]
4849
struct NoiseSink(Handle<AudioSink<Noise>>);
4950

5051
fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Noise>>) {

examples/sine.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy::{
2-
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, StartupStage},
2+
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, Resource, StartupStage},
33
DefaultPlugins,
44
};
55
use bevy_oddio::{
@@ -18,9 +18,9 @@ fn main() {
1818
.run();
1919
}
2020

21-
#[derive(Deref)]
21+
#[derive(Resource, Deref)]
2222
struct SineHandle(Handle<Sine>);
23-
23+
#[derive(Resource)]
2424
struct SineSink(Handle<AudioSink<Sine>>);
2525

2626
fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Sine>>) {

examples/spatial_2d.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use bevy::{
2+
prelude::{
3+
default, App, Assets, BuildChildren, Camera2dBundle, Color, Commands, Component, Deref,
4+
Handle, Query, Res, ResMut, Resource, SpatialBundle, StartupStage, Transform, Vec2, Vec3,
5+
With,
6+
},
7+
sprite::{Sprite, SpriteBundle},
8+
time::Time,
9+
DefaultPlugins,
10+
};
11+
use bevy_oddio::{
12+
builtins::sine::{self, Sine},
13+
output::spatial::SpatialAudioSink,
14+
Audio, AudioPlugin,
15+
};
16+
use oddio::{Sample, Spatial, SpatialOptions};
17+
18+
fn main() {
19+
App::new()
20+
.add_plugins(DefaultPlugins)
21+
.add_plugin(AudioPlugin)
22+
.add_startup_system(init_assets)
23+
.add_startup_system_to_stage(StartupStage::PostStartup, setup)
24+
.add_system(change_velocity)
25+
.run();
26+
}
27+
28+
#[derive(Component)]
29+
struct Emitter;
30+
31+
#[derive(Resource, Deref)]
32+
struct SineHandle(Handle<Sine>);
33+
#[derive(Resource)]
34+
struct SineSink(Handle<SpatialAudioSink<Sine>>);
35+
36+
fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Sine>>) {
37+
let handle = assets.add(Sine);
38+
commands.insert_resource(SineHandle(handle));
39+
}
40+
41+
fn setup(mut commands: Commands, mut audio: ResMut<Audio<Sample, Sine>>, noise: Res<SineHandle>) {
42+
// Note is in A4.
43+
let handle = audio.play_spatial(
44+
noise.clone(),
45+
sine::Settings::new(0.0, 440.0),
46+
SpatialOptions {
47+
position: (Vec3::Y * 0.4).into(),
48+
velocity: Vec3::ZERO.into(),
49+
radius: 0.5,
50+
},
51+
);
52+
commands.insert_resource(SineSink(handle));
53+
54+
commands
55+
.spawn(SpatialBundle {
56+
transform: Transform::from_scale(Vec3::splat(100.0)),
57+
..default()
58+
})
59+
.with_children(|parent| {
60+
parent
61+
.spawn(SpriteBundle {
62+
sprite: Sprite {
63+
color: Color::GREEN,
64+
custom_size: Some(Vec2::new(0.3, 0.3)),
65+
..default()
66+
},
67+
transform: Transform::from_xyz(0.0, 0.4, 0.0),
68+
..default()
69+
})
70+
.insert(Emitter);
71+
});
72+
73+
commands.spawn(Camera2dBundle::default());
74+
}
75+
76+
fn change_velocity(
77+
sink: Res<SineSink>,
78+
mut sinks: ResMut<Assets<SpatialAudioSink<Sine>>>,
79+
time: Res<Time>,
80+
mut query: Query<&mut Transform, With<Emitter>>,
81+
) {
82+
let mut emitter = query.single_mut();
83+
84+
let normalized_time = time.elapsed_seconds_wrapped().sin() as f32 * 5.0;
85+
let delta = time.delta_seconds();
86+
87+
let sink = match sinks.get_mut(&sink.0) {
88+
Some(sink) => sink,
89+
None => return,
90+
};
91+
92+
let prev_pos = emitter.translation;
93+
let position = Vec3::new(normalized_time, prev_pos.y, prev_pos.z);
94+
let velocity = Vec3::X * ((prev_pos.x - position.x) / delta);
95+
96+
emitter.translation = Vec3::new(position.x, position.y, position.z);
97+
98+
sink.control::<Spatial<_>, _>()
99+
.set_motion(position.into(), velocity.into(), false);
100+
}

examples/spatial_3d.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use bevy::{
2+
prelude::{
3+
default, shape, App, Assets, Camera3dBundle, Color, Commands, Component, Deref, Handle,
4+
Mesh, PbrBundle, PointLight, PointLightBundle, Query, Res, ResMut, Resource,
5+
StandardMaterial, StartupStage, Transform, Vec3, With,
6+
},
7+
time::Time,
8+
DefaultPlugins,
9+
};
10+
use bevy_oddio::{
11+
builtins::sine::{self, Sine},
12+
output::spatial::SpatialAudioSink,
13+
Audio, AudioPlugin,
14+
};
15+
use oddio::{Sample, Spatial, SpatialOptions};
16+
17+
fn main() {
18+
App::new()
19+
.add_plugins(DefaultPlugins)
20+
.add_plugin(AudioPlugin)
21+
.add_startup_system(init_assets)
22+
.add_startup_system_to_stage(StartupStage::PostStartup, setup)
23+
.add_system(change_velocity)
24+
.run();
25+
}
26+
27+
#[derive(Component)]
28+
struct Emitter;
29+
30+
#[derive(Resource, Deref)]
31+
struct SineHandle(Handle<Sine>);
32+
#[derive(Resource)]
33+
struct SineSink(Handle<SpatialAudioSink<Sine>>);
34+
35+
fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Sine>>) {
36+
let handle = assets.add(Sine);
37+
commands.insert_resource(SineHandle(handle));
38+
}
39+
40+
fn setup(
41+
mut commands: Commands,
42+
mut audio: ResMut<Audio<Sample, Sine>>,
43+
noise: Res<SineHandle>,
44+
mut meshes: ResMut<Assets<Mesh>>,
45+
mut materials: ResMut<Assets<StandardMaterial>>,
46+
) {
47+
// Note is in A4.
48+
let handle = audio.play_spatial(
49+
noise.clone(),
50+
sine::Settings::new(0.0, 440.0),
51+
SpatialOptions {
52+
position: (Vec3::Y * 0.4).into(),
53+
velocity: Vec3::ZERO.into(),
54+
radius: 0.5,
55+
},
56+
);
57+
commands.insert_resource(SineSink(handle));
58+
59+
// Listener
60+
commands.spawn(PbrBundle {
61+
mesh: meshes.add(Mesh::from(shape::UVSphere {
62+
radius: 0.2,
63+
..default()
64+
})),
65+
material: materials.add(Color::GREEN.into()),
66+
transform: Transform::from_xyz(0.0, 0.0, 0.0),
67+
..default()
68+
});
69+
70+
// Emitter
71+
commands
72+
.spawn(PbrBundle {
73+
mesh: meshes.add(Mesh::from(shape::UVSphere {
74+
radius: 0.2,
75+
..default()
76+
})),
77+
material: materials.add(Color::BLUE.into()),
78+
transform: Transform::from_xyz(0.0, 0.0, 0.0),
79+
..default()
80+
})
81+
.insert(Emitter);
82+
83+
// light
84+
commands.spawn(PointLightBundle {
85+
point_light: PointLight {
86+
intensity: 1500.0,
87+
shadows_enabled: true,
88+
..default()
89+
},
90+
transform: Transform::from_xyz(4.0, 8.0, 4.0),
91+
..default()
92+
});
93+
94+
commands.spawn(Camera3dBundle {
95+
transform: Transform::from_xyz(0.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
96+
..default()
97+
});
98+
}
99+
100+
fn change_velocity(
101+
sink: Res<SineSink>,
102+
mut sinks: ResMut<Assets<SpatialAudioSink<Sine>>>,
103+
time: Res<Time>,
104+
mut query: Query<&mut Transform, With<Emitter>>,
105+
) {
106+
let mut emitter = query.single_mut();
107+
108+
let x = time.elapsed_seconds_wrapped().sin() as f32 * 3.0;
109+
let z = time.elapsed_seconds_wrapped().cos() as f32 * 3.0;
110+
let delta = time.delta_seconds();
111+
112+
let sink = match sinks.get_mut(&sink.0) {
113+
Some(sink) => sink,
114+
None => return,
115+
};
116+
117+
let prev_pos = emitter.translation;
118+
let position = Vec3::new(x, prev_pos.y, z);
119+
let velocity = Vec3::new(
120+
(prev_pos.x - position.x) / delta,
121+
0.0,
122+
(prev_pos.z - position.z) / delta,
123+
);
124+
125+
emitter.translation = Vec3::new(position.x, position.y, position.z);
126+
127+
sink.control::<Spatial<_>, _>()
128+
.set_motion(position.into(), velocity.into(), false);
129+
}

0 commit comments

Comments
 (0)