2020-12-12 14:21:55 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::io::Cursor;
|
2020-12-12 16:26:37 +00:00
|
|
|
use std::ops::{Deref, DerefMut};
|
2020-12-12 14:21:55 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2020-12-12 16:26:37 +00:00
|
|
|
pub use alto::efx;
|
|
|
|
pub use alto::Context;
|
|
|
|
pub use alto::Device;
|
2020-12-12 14:21:55 +00:00
|
|
|
pub use alto::Source;
|
2021-05-13 23:25:40 +00:00
|
|
|
use alto::{efx::AuxEffectSlot, ContextAttrs, SourceState};
|
2020-12-12 16:26:37 +00:00
|
|
|
use alto::{Alto, Mono, StaticSource, Stereo};
|
2020-12-12 14:21:55 +00:00
|
|
|
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() {
|
2021-01-09 20:25:19 +00:00
|
|
|
"flac" => {
|
|
|
|
let reader = claxon::FlacReader::new(cursor);
|
|
|
|
if let Ok(mut reader) = reader {
|
|
|
|
let mut samples: Vec<i16> = vec![];
|
2021-05-21 17:02:45 +00:00
|
|
|
for sample in reader.samples().flatten() {
|
|
|
|
samples.push(sample as i16);
|
2021-01-09 20:25:19 +00:00
|
|
|
}
|
|
|
|
let info = reader.streaminfo();
|
|
|
|
Some(Buffer {
|
|
|
|
samples,
|
|
|
|
sample_rate: info.sample_rate as i32,
|
|
|
|
channels: info.channels as u16,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2020-12-12 14:21:55 +00:00
|
|
|
"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![];
|
2021-05-21 17:02:45 +00:00
|
|
|
for sample in reader.samples::<i16>().flatten() {
|
|
|
|
samples.push(sample);
|
2020-12-12 14:21:55 +00:00
|
|
|
}
|
|
|
|
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] {
|
2021-01-09 20:25:19 +00:00
|
|
|
&["flac", "ogg", "wav"]
|
2020-12-12 14:21:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 18:19:48 +00:00
|
|
|
// TODO: Make non-public when we have multi-stage asset loading.
|
2020-12-12 14:21:55 +00:00
|
|
|
#[derive(Default)]
|
2021-04-09 18:19:48 +00:00
|
|
|
pub struct Buffers(pub HashMap<HandleId, Arc<alto::Buffer>>);
|
2020-12-12 14:21:55 +00:00
|
|
|
|
2020-12-16 09:15:20 +00:00
|
|
|
fn buffer_creation(
|
2020-12-12 14:21:55 +00:00
|
|
|
context: Res<Context>,
|
|
|
|
mut buffers: ResMut<Buffers>,
|
2021-03-01 17:20:33 +00:00
|
|
|
mut events: EventReader<AssetEvent<Buffer>>,
|
2020-12-12 14:21:55 +00:00
|
|
|
assets: Res<Assets<Buffer>>,
|
|
|
|
) {
|
2021-03-01 17:20:33 +00:00
|
|
|
for event in events.iter() {
|
2020-12-12 14:21:55 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-07 22:28:22 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
|
2021-01-07 22:18:26 +00:00
|
|
|
pub enum SoundState {
|
|
|
|
Stopped,
|
|
|
|
Playing,
|
|
|
|
Paused,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SoundState {
|
|
|
|
fn default() -> Self {
|
|
|
|
SoundState::Stopped
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-07 22:28:22 +00:00
|
|
|
#[derive(Reflect)]
|
2020-12-12 14:21:55 +00:00
|
|
|
pub struct Sound {
|
|
|
|
pub buffer: Handle<Buffer>,
|
2021-01-07 22:18:26 +00:00
|
|
|
pub state: SoundState,
|
2020-12-12 14:21:55 +00:00
|
|
|
pub gain: f32,
|
2020-12-12 14:53:20 +00:00
|
|
|
pub pitch: f32,
|
2021-05-21 17:02:45 +00:00
|
|
|
pub looping: bool,
|
2021-04-25 13:28:13 +00:00
|
|
|
pub reference_distance: f32,
|
2021-04-29 16:15:34 +00:00
|
|
|
pub max_distance: f32,
|
2021-04-25 13:28:13 +00:00
|
|
|
pub rolloff_factor: f32,
|
2021-04-28 15:51:58 +00:00
|
|
|
pub bypass_global_effects: bool,
|
2021-01-07 22:28:22 +00:00
|
|
|
#[reflect(ignore)]
|
2020-12-12 14:21:55 +00:00
|
|
|
pub source: Option<StaticSource>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Sound {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
buffer: Default::default(),
|
2021-01-07 22:18:26 +00:00
|
|
|
state: Default::default(),
|
2020-12-12 14:21:55 +00:00
|
|
|
gain: 1.,
|
|
|
|
looping: false,
|
2020-12-12 14:53:20 +00:00
|
|
|
pitch: 1.,
|
2021-04-25 13:28:13 +00:00
|
|
|
reference_distance: 1.,
|
2021-04-29 16:15:34 +00:00
|
|
|
max_distance: f32::MAX,
|
2021-04-25 13:28:13 +00:00
|
|
|
rolloff_factor: 1.,
|
2021-04-28 15:51:58 +00:00
|
|
|
bypass_global_effects: false,
|
2020-12-12 14:21:55 +00:00
|
|
|
source: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-24 01:25:30 +00:00
|
|
|
|
2020-12-12 16:26:37 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct GlobalEffects(Vec<AuxEffectSlot>);
|
|
|
|
|
|
|
|
impl Deref for GlobalEffects {
|
|
|
|
type Target = Vec<AuxEffectSlot>;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DerefMut for GlobalEffects {
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
&mut self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 17:02:45 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn sync_source_and_components(
|
2021-02-03 10:18:37 +00:00
|
|
|
source: &mut StaticSource,
|
|
|
|
transform: Option<&Transform>,
|
|
|
|
global_transform: Option<&GlobalTransform>,
|
2021-05-21 17:02:45 +00:00
|
|
|
gain: f32,
|
|
|
|
pitch: f32,
|
|
|
|
looping: bool,
|
|
|
|
reference_distance: f32,
|
|
|
|
max_distance: f32,
|
|
|
|
rolloff_factor: f32,
|
|
|
|
bypass_global_effects: bool,
|
|
|
|
global_effects: &mut Vec<AuxEffectSlot>,
|
2021-02-03 10:18:37 +00:00
|
|
|
) {
|
|
|
|
let translation = global_transform
|
|
|
|
.map(|v| v.translation)
|
|
|
|
.or_else(|| transform.map(|v| v.translation));
|
|
|
|
if let Some(translation) = translation {
|
|
|
|
source.set_relative(false);
|
|
|
|
source
|
|
|
|
.set_position([translation.x, translation.y, translation.z])
|
|
|
|
.unwrap();
|
|
|
|
} else {
|
|
|
|
source.set_relative(true);
|
|
|
|
source.set_position([0., 0., 0.]).unwrap();
|
|
|
|
}
|
2021-05-21 17:02:45 +00:00
|
|
|
source.set_gain(gain).unwrap();
|
|
|
|
source.set_pitch(pitch).unwrap();
|
|
|
|
source.set_looping(looping);
|
|
|
|
source.set_reference_distance(reference_distance).unwrap();
|
|
|
|
source.set_max_distance(max_distance).unwrap();
|
|
|
|
source.set_rolloff_factor(rolloff_factor).unwrap();
|
|
|
|
if !bypass_global_effects {
|
|
|
|
for (send, effect) in global_effects.iter_mut().enumerate() {
|
|
|
|
source.set_aux_send(send as i32, effect).unwrap();
|
|
|
|
}
|
|
|
|
}
|
2021-02-03 10:18:37 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 09:15:20 +00:00
|
|
|
fn source_update(
|
2020-12-12 14:21:55 +00:00
|
|
|
context: Res<Context>,
|
|
|
|
buffers: Res<Buffers>,
|
2020-12-12 16:26:37 +00:00
|
|
|
mut global_effects: ResMut<GlobalEffects>,
|
2021-01-08 18:56:08 +00:00
|
|
|
mut query: Query<(&mut Sound, Option<&Transform>, Option<&GlobalTransform>)>,
|
2020-12-12 14:21:55 +00:00
|
|
|
) {
|
2021-01-08 18:56:08 +00:00
|
|
|
for (mut sound, transform, global_transform) in query.iter_mut() {
|
2021-05-21 17:02:45 +00:00
|
|
|
let Sound {
|
|
|
|
gain,
|
|
|
|
pitch,
|
|
|
|
looping,
|
|
|
|
reference_distance,
|
|
|
|
max_distance,
|
|
|
|
rolloff_factor,
|
|
|
|
bypass_global_effects,
|
|
|
|
..
|
|
|
|
} = *sound;
|
|
|
|
match &sound.state {
|
2021-01-08 18:56:08 +00:00
|
|
|
SoundState::Stopped => {
|
|
|
|
if let Some(source) = sound.source.as_mut() {
|
|
|
|
source.stop();
|
|
|
|
sound.source = None;
|
2021-01-07 22:18:26 +00:00
|
|
|
}
|
2021-01-08 18:56:08 +00:00
|
|
|
}
|
|
|
|
SoundState::Playing => {
|
2021-05-10 22:23:54 +00:00
|
|
|
if let Some(source) = sound.source.as_mut() {
|
2021-05-21 17:02:45 +00:00
|
|
|
sync_source_and_components(
|
|
|
|
source,
|
|
|
|
transform,
|
|
|
|
global_transform,
|
|
|
|
gain,
|
|
|
|
pitch,
|
|
|
|
looping,
|
|
|
|
reference_distance,
|
|
|
|
max_distance,
|
|
|
|
rolloff_factor,
|
|
|
|
bypass_global_effects,
|
|
|
|
&mut **global_effects,
|
|
|
|
);
|
|
|
|
if ![SourceState::Playing, SourceState::Stopped].contains(&source.state()) {
|
2021-05-10 22:23:54 +00:00
|
|
|
source.play();
|
|
|
|
}
|
|
|
|
} else {
|
2021-01-08 18:56:08 +00:00
|
|
|
let mut source = context.new_static_source().unwrap();
|
|
|
|
if let Some(buffer) = buffers.0.get(&sound.buffer.id) {
|
|
|
|
source.set_buffer(buffer.clone()).unwrap();
|
2021-01-07 22:18:26 +00:00
|
|
|
}
|
2021-05-21 17:02:45 +00:00
|
|
|
sync_source_and_components(
|
|
|
|
&mut source,
|
|
|
|
transform,
|
|
|
|
global_transform,
|
|
|
|
gain,
|
|
|
|
pitch,
|
|
|
|
looping,
|
|
|
|
reference_distance,
|
|
|
|
max_distance,
|
|
|
|
rolloff_factor,
|
|
|
|
bypass_global_effects,
|
|
|
|
&mut **global_effects,
|
|
|
|
);
|
2021-01-08 18:56:08 +00:00
|
|
|
source.play();
|
|
|
|
sound.source = Some(source);
|
2021-01-07 22:18:26 +00:00
|
|
|
}
|
2021-01-08 18:56:08 +00:00
|
|
|
}
|
|
|
|
SoundState::Paused => {
|
|
|
|
if let Some(source) = sound.source.as_mut() {
|
2021-05-21 17:02:45 +00:00
|
|
|
if source.state() != SourceState::Paused {
|
|
|
|
source.pause();
|
|
|
|
sync_source_and_components(
|
|
|
|
source,
|
|
|
|
transform,
|
|
|
|
global_transform,
|
|
|
|
gain,
|
|
|
|
pitch,
|
|
|
|
looping,
|
|
|
|
reference_distance,
|
|
|
|
max_distance,
|
|
|
|
rolloff_factor,
|
|
|
|
bypass_global_effects,
|
|
|
|
&mut **global_effects,
|
|
|
|
);
|
|
|
|
}
|
2021-01-08 18:56:08 +00:00
|
|
|
} else {
|
|
|
|
let mut source = context.new_static_source().unwrap();
|
|
|
|
if let Some(buffer) = buffers.0.get(&sound.buffer.id) {
|
|
|
|
source.set_buffer(buffer.clone()).unwrap();
|
2020-12-12 14:21:55 +00:00
|
|
|
}
|
2021-05-21 17:02:45 +00:00
|
|
|
sync_source_and_components(
|
|
|
|
&mut source,
|
|
|
|
transform,
|
|
|
|
global_transform,
|
|
|
|
gain,
|
|
|
|
pitch,
|
|
|
|
looping,
|
|
|
|
reference_distance,
|
|
|
|
max_distance,
|
|
|
|
rolloff_factor,
|
|
|
|
bypass_global_effects,
|
|
|
|
&mut **global_effects,
|
|
|
|
);
|
2021-01-08 18:56:08 +00:00
|
|
|
source.pause();
|
|
|
|
sound.source = Some(source);
|
2020-12-12 14:21:55 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-08 18:56:08 +00:00
|
|
|
}
|
2021-05-21 17:02:45 +00:00
|
|
|
if let Some(source) = &sound.source {
|
|
|
|
sound.state = match source.state() {
|
2021-01-08 18:56:08 +00:00
|
|
|
SourceState::Initial => SoundState::Stopped,
|
|
|
|
SourceState::Playing => SoundState::Playing,
|
|
|
|
SourceState::Paused => SoundState::Paused,
|
|
|
|
SourceState::Stopped => SoundState::Stopped,
|
|
|
|
SourceState::Unknown(_) => SoundState::Stopped,
|
|
|
|
};
|
2020-12-12 14:21:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-08 18:56:08 +00:00
|
|
|
impl Sound {
|
|
|
|
pub fn stop(&mut self) {
|
|
|
|
if let Some(source) = self.source.as_mut() {
|
|
|
|
source.stop();
|
|
|
|
}
|
|
|
|
self.state = SoundState::Stopped;
|
|
|
|
self.source = None;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn play(&mut self) {
|
|
|
|
if let Some(source) = self.source.as_mut() {
|
|
|
|
source.play();
|
|
|
|
}
|
|
|
|
self.state = SoundState::Playing;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pause(&mut self) {
|
|
|
|
if let Some(source) = self.source.as_mut() {
|
|
|
|
source.pause();
|
|
|
|
}
|
|
|
|
self.state = SoundState::Paused;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 14:21:55 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Default, Reflect)]
|
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct Listener;
|
|
|
|
|
2021-01-08 18:56:08 +00:00
|
|
|
fn listener_update(
|
|
|
|
context: ResMut<Context>,
|
|
|
|
query: Query<(&Listener, Option<&Transform>, Option<&GlobalTransform>)>,
|
|
|
|
) {
|
|
|
|
for (_, transform, global_transform) in query.iter() {
|
2021-01-08 20:06:13 +00:00
|
|
|
let transform: Option<Transform> = global_transform
|
|
|
|
.map(|v| {
|
2021-01-09 00:58:42 +00:00
|
|
|
let transform: Transform = (*v).into();
|
|
|
|
transform
|
2021-01-08 18:56:08 +00:00
|
|
|
})
|
2021-01-08 20:06:13 +00:00
|
|
|
.or_else(|| transform.cloned());
|
2020-12-12 14:21:55 +00:00
|
|
|
if let Some(transform) = transform {
|
2021-04-12 18:09:03 +00:00
|
|
|
let look = transform.local_x();
|
|
|
|
let up = transform.local_z();
|
2020-12-12 14:21:55 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 23:25:40 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
|
|
pub struct OpenAlConfig {
|
|
|
|
pub soft_hrtf: bool,
|
|
|
|
}
|
|
|
|
|
2020-12-12 14:21:55 +00:00
|
|
|
pub struct OpenAlPlugin;
|
|
|
|
|
|
|
|
impl Plugin for OpenAlPlugin {
|
|
|
|
fn build(&self, app: &mut AppBuilder) {
|
2021-05-13 23:25:40 +00:00
|
|
|
if !app.world().contains_resource::<OpenAlConfig>() {
|
|
|
|
app.insert_resource(OpenAlConfig::default());
|
|
|
|
}
|
2021-05-21 17:02:45 +00:00
|
|
|
let config = *app.world().get_resource::<OpenAlConfig>().unwrap();
|
2020-12-12 14:21:55 +00:00
|
|
|
let al = Alto::load_default().expect("Could not load alto");
|
|
|
|
let device = al.open(None).expect("Could not open device");
|
2021-05-13 23:25:40 +00:00
|
|
|
let mut context_attrs = ContextAttrs::default();
|
|
|
|
if config.soft_hrtf {
|
|
|
|
context_attrs.soft_hrtf = Some(true);
|
|
|
|
}
|
|
|
|
let context = device
|
|
|
|
.new_context(Some(context_attrs))
|
|
|
|
.expect("Could not create context");
|
2020-12-12 14:21:55 +00:00
|
|
|
app.add_asset::<Buffer>()
|
|
|
|
.init_asset_loader::<BufferAssetLoader>()
|
2021-03-01 17:20:33 +00:00
|
|
|
.insert_non_send_resource(device)
|
|
|
|
.insert_resource(context)
|
|
|
|
.insert_resource(Buffers::default())
|
|
|
|
.insert_resource(GlobalEffects::default())
|
2020-12-12 14:21:55 +00:00
|
|
|
.register_type::<Listener>()
|
2020-12-16 09:15:20 +00:00
|
|
|
.add_system(buffer_creation.system())
|
|
|
|
.add_system(source_update.system())
|
|
|
|
.add_system(listener_update.system());
|
2020-12-12 14:21:55 +00:00
|
|
|
}
|
|
|
|
}
|