Skip to content

Commit

Permalink
Add support for spatial audio (#44)
Browse files Browse the repository at this point in the history
# Objective

- Add basic support for spatial audio
- Update to 0.9
- Unblocks #22

## Solution

- Added `play_spatial`, `play_spatial_buffered` and `set_listener_rotation`.
  • Loading branch information
harudagondi authored Nov 13, 2022
1 parent 17fbcd9 commit 2c4cb3a
Show file tree
Hide file tree
Showing 9 changed files with 670 additions and 33 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ hound = { version = "3.4", optional = true }
lewton = { version = "0.10", optional = true }
claxon = { version = "0.4", optional = true }
minimp3 = { version = "0.5", optional = true }
bevy_math = { version = "0.9", features = ["mint"] }

[features]
wav = ["hound"]
Expand All @@ -28,8 +29,7 @@ ogg = ["lewton"]
flac = ["claxon"]

[dependencies.bevy]
# git = "https://github.com/bevyengine/bevy.git"
version = "0.8"
version = "0.9"
default-features = false
features = ["bevy_asset"]

Expand All @@ -38,7 +38,7 @@ fastrand = "1.8"

[dev-dependencies.bevy]
# git = "https://github.com/bevyengine/bevy.git"
version = "0.8"
version = "0.9"
default-features = false
features = [
"render",
Expand Down
8 changes: 4 additions & 4 deletions examples/gain.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy::{
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, StartupStage},
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, Resource, StartupStage},
reflect::TypeUuid,
time::Time,
DefaultPlugins,
Expand Down Expand Up @@ -31,9 +31,9 @@ fn main() {
.run();
}

#[derive(Deref)]
#[derive(Resource, Deref)]
struct SineWithGainHandle(Handle<SineWithGain>);

#[derive(Resource)]
struct SineWithGainSink(Handle<AudioSink<SineWithGain>>);

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

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

sink.control::<oddio::Gain<_>, _>()
.set_amplitude_ratio(factor as f32);
Expand Down
7 changes: 4 additions & 3 deletions examples/noise.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy::{
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, StartupStage},
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, Resource, StartupStage},
reflect::TypeUuid,
DefaultPlugins,
};
Expand All @@ -9,6 +9,7 @@ use oddio::Signal;
#[derive(TypeUuid)]
#[uuid = "7cc24057-b499-4f7a-8f8a-e37dfa64be32"]
struct Noise;
#[derive(Resource)]
struct NoiseSignal;

impl Signal for NoiseSignal {
Expand Down Expand Up @@ -42,9 +43,9 @@ fn main() {
.run();
}

#[derive(Deref)]
#[derive(Resource, Deref)]
struct NoiseHandle(Handle<Noise>);

#[derive(Resource)]
struct NoiseSink(Handle<AudioSink<Noise>>);

fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Noise>>) {
Expand Down
6 changes: 3 additions & 3 deletions examples/sine.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy::{
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, StartupStage},
prelude::{App, Assets, Commands, Deref, Handle, Res, ResMut, Resource, StartupStage},
DefaultPlugins,
};
use bevy_oddio::{
Expand All @@ -18,9 +18,9 @@ fn main() {
.run();
}

#[derive(Deref)]
#[derive(Resource, Deref)]
struct SineHandle(Handle<Sine>);

#[derive(Resource)]
struct SineSink(Handle<AudioSink<Sine>>);

fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Sine>>) {
Expand Down
100 changes: 100 additions & 0 deletions examples/spatial_2d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use bevy::{
prelude::{
default, App, Assets, BuildChildren, Camera2dBundle, Color, Commands, Component, Deref,
Handle, Query, Res, ResMut, Resource, SpatialBundle, StartupStage, Transform, Vec2, Vec3,
With,
},
sprite::{Sprite, SpriteBundle},
time::Time,
DefaultPlugins,
};
use bevy_oddio::{
builtins::sine::{self, Sine},
output::spatial::SpatialAudioSink,
Audio, AudioPlugin,
};
use oddio::{Sample, Spatial, SpatialOptions};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(AudioPlugin)
.add_startup_system(init_assets)
.add_startup_system_to_stage(StartupStage::PostStartup, setup)
.add_system(change_velocity)
.run();
}

#[derive(Component)]
struct Emitter;

#[derive(Resource, Deref)]
struct SineHandle(Handle<Sine>);
#[derive(Resource)]
struct SineSink(Handle<SpatialAudioSink<Sine>>);

fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Sine>>) {
let handle = assets.add(Sine);
commands.insert_resource(SineHandle(handle));
}

fn setup(mut commands: Commands, mut audio: ResMut<Audio<Sample, Sine>>, noise: Res<SineHandle>) {
// Note is in A4.
let handle = audio.play_spatial(
noise.clone(),
sine::Settings::new(0.0, 440.0),
SpatialOptions {
position: (Vec3::Y * 0.4).into(),
velocity: Vec3::ZERO.into(),
radius: 0.5,
},
);
commands.insert_resource(SineSink(handle));

commands
.spawn(SpatialBundle {
transform: Transform::from_scale(Vec3::splat(100.0)),
..default()
})
.with_children(|parent| {
parent
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::GREEN,
custom_size: Some(Vec2::new(0.3, 0.3)),
..default()
},
transform: Transform::from_xyz(0.0, 0.4, 0.0),
..default()
})
.insert(Emitter);
});

commands.spawn(Camera2dBundle::default());
}

fn change_velocity(
sink: Res<SineSink>,
mut sinks: ResMut<Assets<SpatialAudioSink<Sine>>>,
time: Res<Time>,
mut query: Query<&mut Transform, With<Emitter>>,
) {
let mut emitter = query.single_mut();

let normalized_time = time.elapsed_seconds_wrapped().sin() as f32 * 5.0;
let delta = time.delta_seconds();

let sink = match sinks.get_mut(&sink.0) {
Some(sink) => sink,
None => return,
};

let prev_pos = emitter.translation;
let position = Vec3::new(normalized_time, prev_pos.y, prev_pos.z);
let velocity = Vec3::X * ((prev_pos.x - position.x) / delta);

emitter.translation = Vec3::new(position.x, position.y, position.z);

sink.control::<Spatial<_>, _>()
.set_motion(position.into(), velocity.into(), false);
}
129 changes: 129 additions & 0 deletions examples/spatial_3d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use bevy::{
prelude::{
default, shape, App, Assets, Camera3dBundle, Color, Commands, Component, Deref, Handle,
Mesh, PbrBundle, PointLight, PointLightBundle, Query, Res, ResMut, Resource,
StandardMaterial, StartupStage, Transform, Vec3, With,
},
time::Time,
DefaultPlugins,
};
use bevy_oddio::{
builtins::sine::{self, Sine},
output::spatial::SpatialAudioSink,
Audio, AudioPlugin,
};
use oddio::{Sample, Spatial, SpatialOptions};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(AudioPlugin)
.add_startup_system(init_assets)
.add_startup_system_to_stage(StartupStage::PostStartup, setup)
.add_system(change_velocity)
.run();
}

#[derive(Component)]
struct Emitter;

#[derive(Resource, Deref)]
struct SineHandle(Handle<Sine>);
#[derive(Resource)]
struct SineSink(Handle<SpatialAudioSink<Sine>>);

fn init_assets(mut commands: Commands, mut assets: ResMut<Assets<Sine>>) {
let handle = assets.add(Sine);
commands.insert_resource(SineHandle(handle));
}

fn setup(
mut commands: Commands,
mut audio: ResMut<Audio<Sample, Sine>>,
noise: Res<SineHandle>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Note is in A4.
let handle = audio.play_spatial(
noise.clone(),
sine::Settings::new(0.0, 440.0),
SpatialOptions {
position: (Vec3::Y * 0.4).into(),
velocity: Vec3::ZERO.into(),
radius: 0.5,
},
);
commands.insert_resource(SineSink(handle));

// Listener
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.2,
..default()
})),
material: materials.add(Color::GREEN.into()),
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
});

// Emitter
commands
.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.2,
..default()
})),
material: materials.add(Color::BLUE.into()),
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
})
.insert(Emitter);

// light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});

commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}

fn change_velocity(
sink: Res<SineSink>,
mut sinks: ResMut<Assets<SpatialAudioSink<Sine>>>,
time: Res<Time>,
mut query: Query<&mut Transform, With<Emitter>>,
) {
let mut emitter = query.single_mut();

let x = time.elapsed_seconds_wrapped().sin() as f32 * 3.0;
let z = time.elapsed_seconds_wrapped().cos() as f32 * 3.0;
let delta = time.delta_seconds();

let sink = match sinks.get_mut(&sink.0) {
Some(sink) => sink,
None => return,
};

let prev_pos = emitter.translation;
let position = Vec3::new(x, prev_pos.y, z);
let velocity = Vec3::new(
(prev_pos.x - position.x) / delta,
0.0,
(prev_pos.z - position.z) / delta,
);

emitter.translation = Vec3::new(position.x, position.y, position.z);

sink.control::<Spatial<_>, _>()
.set_motion(position.into(), velocity.into(), false);
}
Loading

0 comments on commit 2c4cb3a

Please sign in to comment.