Skip to content

Commit

Permalink
audio: initial (very minimal) audio plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
cart committed Jul 16, 2020
1 parent af10917 commit 3eb3935
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Expand Up @@ -17,6 +17,7 @@ members = [

[dependencies]
# bevy
bevy_audio = { path = "crates/bevy_audio" }
bevy_app = { path = "crates/bevy_app" }
bevy_asset = { path = "crates/bevy_asset" }
bevy_type_registry = { path = "crates/bevy_type_registry" }
Expand Down Expand Up @@ -115,6 +116,10 @@ path = "examples/asset/hot_asset_reloading.rs"
name = "asset_loading"
path = "examples/asset/asset_loading.rs"

[[example]]
name = "audio"
path = "examples/audio/audio.rs"

[[example]]
name = "custom_diagnostic"
path = "examples/diagnostics/custom_diagnostic.rs"
Expand Down
Binary file added assets/sounds/Windless Slopes.mp3
Binary file not shown.
14 changes: 14 additions & 0 deletions crates/bevy_audio/Cargo.toml
@@ -0,0 +1,14 @@
[package]
authors = ["Carter Anderson <mcanders1@gmail.com>"]
edition = "2018"
name = "bevy_audio"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy_app = {path = "../bevy_app"}
bevy_asset = {path = "../bevy_asset"}
bevy_ecs = {path = "../bevy_ecs"}
anyhow = "1.0"
rodio = {version = "0.11", default-features = false, features = ["mp3"]}
54 changes: 54 additions & 0 deletions crates/bevy_audio/src/audio_output.rs
@@ -0,0 +1,54 @@
use crate::AudioSource;
use bevy_asset::{Assets, Handle};
use bevy_ecs::Res;
use rodio::{Decoder, Device, Sink};
use std::{collections::VecDeque, io::Cursor, sync::RwLock};

pub struct AudioOutput {
device: Device,
queue: RwLock<VecDeque<Handle<AudioSource>>>,
}

impl Default for AudioOutput {
fn default() -> Self {
Self {
device: rodio::default_output_device().unwrap(),
queue: Default::default(),
}
}
}

impl AudioOutput {
pub fn play(&self, audio_source: &AudioSource) {
let sink = Sink::new(&self.device);
sink.append(Decoder::new(Cursor::new(audio_source.clone())).unwrap());
sink.detach();
}

pub fn queue_play(&self, audio_source: Handle<AudioSource>) {
self.queue.write().unwrap().push_front(audio_source);
}

pub fn try_play_queued(&self, audio_sources: &Assets<AudioSource>) {
let mut queue = self.queue.write().unwrap();
let len = queue.len();
let mut i = 0;
while i < len {
let audio_source_handle = queue.pop_back().unwrap();
if let Some(audio_source) = audio_sources.get(&audio_source_handle) {
self.play(audio_source);
} else {
// audio source hasn't loaded yet. add it back to the queue
queue.push_front(audio_source_handle);
}
i += 1;
}
}
}

pub fn play_queued_audio_system(
audio_sources: Res<Assets<AudioSource>>,
audio_output: Res<AudioOutput>,
) {
audio_output.try_play_queued(&audio_sources);
}
27 changes: 27 additions & 0 deletions crates/bevy_audio/src/audio_source.rs
@@ -0,0 +1,27 @@
use anyhow::Result;
use bevy_asset::AssetLoader;
use std::{sync::Arc, path::Path};

#[derive(Clone)]
pub struct AudioSource {
pub bytes: Arc<Vec<u8>>,
}

impl AsRef<[u8]> for AudioSource {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}

#[derive(Default)]
pub struct Mp3Loader;

impl AssetLoader<AudioSource> for Mp3Loader {
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<AudioSource> {
Ok(AudioSource { bytes: Arc::new(bytes) })
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["mp3"];
EXTENSIONS
}
}
21 changes: 21 additions & 0 deletions crates/bevy_audio/src/lib.rs
@@ -0,0 +1,21 @@
mod audio_output;
mod audio_source;

pub use audio_output::*;
pub use audio_source::*;

use bevy_app::{stage, AppBuilder, AppPlugin};
use bevy_asset::AddAsset;
use bevy_ecs::IntoQuerySystem;

#[derive(Default)]
pub struct AudioPlugin;

impl AppPlugin for AudioPlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<AudioOutput>()
.add_asset::<AudioSource>()
.add_asset_loader::<AudioSource, Mp3Loader>()
.add_system_to_stage(stage::POST_UPDATE, play_queued_audio_system.system());
}
}
15 changes: 15 additions & 0 deletions examples/audio/audio.rs
@@ -0,0 +1,15 @@
use bevy::prelude::*;

fn main() {
App::build()
.add_default_plugins()
.add_startup_system(setup.system())
.run();
}

fn setup(asset_server: Res<AssetServer>, audio_output: Res<AudioOutput>) {
let music = asset_server
.load("assets/sounds/Windless Slopes.mp3")
.unwrap();
audio_output.queue_play(music);
}
1 change: 1 addition & 0 deletions src/add_default_plugins.rs
Expand Up @@ -19,6 +19,7 @@ impl AddDefaultPlugins for AppBuilder {
self.add_plugin(bevy_ui::UiPlugin::default());
self.add_plugin(bevy_gltf::GltfPlugin::default());
self.add_plugin(bevy_text::TextPlugin::default());
self.add_plugin(bevy_audio::AudioPlugin::default());

#[cfg(feature = "bevy_winit")]
self.add_plugin(bevy_winit::WinitPlugin::default());
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -46,6 +46,7 @@ pub use bevy_app as app;
pub use glam as math;

pub use bevy_asset as asset;
pub use bevy_audio as audio;
pub use bevy_core as core;
pub use bevy_diagnostic as diagnostic;
pub use bevy_ecs as ecs;
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Expand Up @@ -4,6 +4,7 @@ pub use crate::{
EventReader, Events,
},
asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle},
audio::{AudioOutput, AudioSource},
core::{
time::{Time, Timer},
transform::FaceToward,
Expand Down

0 comments on commit 3eb3935

Please sign in to comment.