mirror of
https://github.com/lightsoutgames/bevy_openal.git
synced 2024-09-07 20:35:56 +00:00
Initial commit.
This commit is contained in:
commit
2c75e46f1b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
*.dll
|
3963
Cargo.lock
generated
Normal file
3963
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "bevy_openal"
|
||||
version = "0.1.0"
|
||||
authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
alto = "3"
|
||||
anyhow = "1"
|
||||
bevy = { git = "https://github.com/bevyengine/bevy" }
|
||||
hound = "3"
|
||||
lewton = "0.10"
|
BIN
assets/footstep.ogg
Normal file
BIN
assets/footstep.ogg
Normal file
Binary file not shown.
BIN
assets/footstep.wav
Normal file
BIN
assets/footstep.wav
Normal file
Binary file not shown.
52
examples/game.rs
Normal file
52
examples/game.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use bevy::{asset::LoadState, prelude::*};
|
||||
use bevy_openal::{Listener, OpenAlPlugin, Sound, Sounds};
|
||||
|
||||
#[derive(Default)]
|
||||
struct AssetHandles {
|
||||
sounds: Vec<HandleUntyped>,
|
||||
loaded: bool,
|
||||
}
|
||||
|
||||
fn setup(asset_server: Res<AssetServer>, mut handles: ResMut<AssetHandles>) {
|
||||
handles.sounds = asset_server.load_folder(".").expect("Failed to load sfx");
|
||||
}
|
||||
|
||||
fn load_and_create_system(
|
||||
commands: &mut Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut handles: ResMut<AssetHandles>,
|
||||
) {
|
||||
if handles.loaded {
|
||||
return;
|
||||
}
|
||||
handles.loaded = asset_server
|
||||
.get_group_load_state(handles.sounds.iter().map(|handle| handle.id))
|
||||
== LoadState::Loaded;
|
||||
if handles.loaded {
|
||||
commands.spawn((Listener::default(), Transform::default));
|
||||
let handle = handles.sounds[0].clone();
|
||||
let buffer = asset_server.get_handle(handle);
|
||||
let mut sounds = Sounds::default();
|
||||
sounds.insert(
|
||||
"footstep".into(),
|
||||
Sound {
|
||||
buffer,
|
||||
autoplay: true,
|
||||
looping: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
commands.spawn((Transform::from_translation(Vec3::new(15., 0., 0.)), sounds));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_system(bevy::input::system::exit_on_esc_system)
|
||||
.add_plugin(OpenAlPlugin)
|
||||
.init_resource::<AssetHandles>()
|
||||
.add_startup_system(setup)
|
||||
.add_system(load_and_create_system)
|
||||
.run();
|
||||
}
|
225
src/lib.rs
Normal file
225
src/lib.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use alto::Source;
|
||||
use alto::{Alto, Context, Mono, StaticSource, Stereo};
|
||||
use bevy::{
|
||||
asset::{AssetLoader, HandleId, LoadContext, LoadedAsset},
|
||||
prelude::*,
|
||||
reflect::TypeUuid,
|
||||
utils::BoxedFuture,
|
||||
};
|
||||
use lewton::inside_ogg::OggStreamReader;
|
||||
|
||||
#[derive(Clone, Debug, TypeUuid)]
|
||||
#[uuid = "aa22d11e-3bed-11eb-8708-00155dea3db9"]
|
||||
pub struct Buffer {
|
||||
samples: Vec<i16>,
|
||||
sample_rate: i32,
|
||||
channels: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BufferAssetLoader;
|
||||
|
||||
impl AssetLoader for BufferAssetLoader {
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
bytes: &'a [u8],
|
||||
load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
|
||||
Box::pin(async move {
|
||||
let cursor = Cursor::new(bytes.to_vec());
|
||||
let buffer: Option<Buffer> =
|
||||
match load_context.path().extension().unwrap().to_str().unwrap() {
|
||||
"ogg" => {
|
||||
let mut stream = OggStreamReader::new(cursor)?;
|
||||
let mut samples: Vec<i16> = vec![];
|
||||
while let Some(pck_samples) = &mut stream.read_dec_packet_itl()? {
|
||||
samples.append(pck_samples);
|
||||
}
|
||||
Some(Buffer {
|
||||
samples,
|
||||
channels: stream.ident_hdr.audio_channels as u16,
|
||||
sample_rate: stream.ident_hdr.audio_sample_rate as i32,
|
||||
})
|
||||
}
|
||||
"wav" => {
|
||||
let reader = hound::WavReader::new(cursor);
|
||||
if let Ok(mut reader) = reader {
|
||||
let mut samples: Vec<i16> = vec![];
|
||||
for sample in reader.samples::<i16>() {
|
||||
if let Ok(sample) = sample {
|
||||
samples.push(sample);
|
||||
}
|
||||
}
|
||||
Some(Buffer {
|
||||
samples,
|
||||
sample_rate: reader.spec().sample_rate as i32,
|
||||
channels: reader.spec().channels,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(buffer) = buffer {
|
||||
load_context.set_default_asset(LoadedAsset::new(buffer));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["mp3", "ogg", "wav"]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Buffers(HashMap<HandleId, Arc<alto::Buffer>>);
|
||||
|
||||
fn buffer_creation_system(
|
||||
context: Res<Context>,
|
||||
mut buffers: ResMut<Buffers>,
|
||||
mut event_reader: Local<EventReader<AssetEvent<Buffer>>>,
|
||||
events: Res<Events<AssetEvent<Buffer>>>,
|
||||
assets: Res<Assets<Buffer>>,
|
||||
) {
|
||||
for event in event_reader.iter(&events) {
|
||||
match event {
|
||||
AssetEvent::Created { handle } => {
|
||||
if let Some(buffer) = assets.get(handle) {
|
||||
let buffer = match buffer.channels {
|
||||
1 => {
|
||||
context.new_buffer::<Mono<i16>, _>(&buffer.samples, buffer.sample_rate)
|
||||
}
|
||||
2 => context
|
||||
.new_buffer::<Stereo<i16>, _>(&buffer.samples, buffer.sample_rate),
|
||||
_ => {
|
||||
panic!("Unsupported channel count");
|
||||
}
|
||||
};
|
||||
if let Ok(buffer) = buffer {
|
||||
buffers.0.insert(handle.id, Arc::new(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
AssetEvent::Modified { handle: _ } => {}
|
||||
AssetEvent::Removed { handle } => {
|
||||
buffers.0.remove(&handle.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct Sound {
|
||||
pub buffer: Handle<Buffer>,
|
||||
pub autoplay: bool,
|
||||
pub gain: f32,
|
||||
pub looping: bool,
|
||||
#[reflect(ignore)]
|
||||
pub source: Option<StaticSource>,
|
||||
}
|
||||
|
||||
impl Default for Sound {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: Default::default(),
|
||||
autoplay: false,
|
||||
gain: 1.,
|
||||
looping: false,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type Sounds = HashMap<String, Sound>;
|
||||
|
||||
fn source_system(
|
||||
context: Res<Context>,
|
||||
buffers: Res<Buffers>,
|
||||
mut query: Query<(&mut Sounds, Option<&Transform>)>,
|
||||
) {
|
||||
for (mut sounds, transform) in query.iter_mut() {
|
||||
for mut sound in sounds.values_mut() {
|
||||
if sound.source.is_none() {
|
||||
if let Ok(mut source) = context.new_static_source() {
|
||||
if let Some(buffer) = buffers.0.get(&sound.buffer.id) {
|
||||
source.set_buffer(buffer.clone()).unwrap();
|
||||
}
|
||||
if sound.autoplay {
|
||||
source.play();
|
||||
}
|
||||
sound.source = Some(source);
|
||||
}
|
||||
}
|
||||
if let Some(source) = sound.source.as_mut() {
|
||||
source.set_gain(sound.gain).unwrap();
|
||||
source.set_looping(sound.looping);
|
||||
if let Some(transform) = transform {
|
||||
source.set_relative(false);
|
||||
source
|
||||
.set_position([
|
||||
transform.translation.x,
|
||||
transform.translation.y,
|
||||
transform.translation.z,
|
||||
])
|
||||
.unwrap();
|
||||
} else {
|
||||
source.set_relative(true);
|
||||
source.set_position([0., 0., 0.]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct Listener;
|
||||
|
||||
fn update_listener_system(context: ResMut<Context>, query: Query<(&Listener, Option<&Transform>)>) {
|
||||
for (_, transform) in query.iter() {
|
||||
if let Some(transform) = transform {
|
||||
let matrix = transform.compute_matrix().inverse();
|
||||
let look = matrix.x_axis;
|
||||
let up = matrix.z_axis;
|
||||
context
|
||||
.set_position([
|
||||
transform.translation.x,
|
||||
transform.translation.y,
|
||||
transform.translation.z,
|
||||
])
|
||||
.unwrap();
|
||||
context
|
||||
.set_orientation(([look.x, look.y, look.z], [up.x, up.y, up.z]))
|
||||
.unwrap();
|
||||
} else {
|
||||
context.set_position([0., 0., 0.]).unwrap();
|
||||
context
|
||||
.set_orientation(([0., 0., 1.], [0., 1., 0.]))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenAlPlugin;
|
||||
|
||||
impl Plugin for OpenAlPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
let al = Alto::load_default().expect("Could not load alto");
|
||||
let device = al.open(None).expect("Could not open device");
|
||||
let context = device.new_context(None).expect("Could not create context");
|
||||
app.add_asset::<Buffer>()
|
||||
.init_asset_loader::<BufferAssetLoader>()
|
||||
.add_resource(context)
|
||||
.add_resource(Buffers::default())
|
||||
.register_type::<Listener>()
|
||||
.add_system(buffer_creation_system)
|
||||
.add_system(source_system)
|
||||
.add_system(update_listener_system);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user