Initial commit.

This commit is contained in:
Nolan Darilek 2020-12-12 08:21:55 -06:00
commit 2c75e46f1b
7 changed files with 4256 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
*.dll

3963
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View 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

Binary file not shown.

BIN
assets/footstep.wav Normal file

Binary file not shown.

52
examples/game.rs Normal file
View 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
View 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);
}
}