Skip to content

Commit

Permalink
Complete inline documentation for bevy_audio (#3510)
Browse files Browse the repository at this point in the history
# Objective
Part of #3492 

- Complete inline documentation of `bevy_audio`

## Solution

- Added inline documentation to all public parts of `bevy_audio`
- Added a few inline examples at important places
- Some renaming for clarity (e.g. `AudioLoader` and generics)
- added `#![warn(missing_docs)]` and `#![forbid(unsafe_code)]` to `bevy_audio`

I also tried adding support for the other vorbis file endings `.oga` and `.spx` to the `AudioLoader` (see `file endings` at https://tools.ietf.org/html/rfc5334#section-10.3), but the `rodio` decoder does not seem to support those.
  • Loading branch information
NiklasEi committed Jan 5, 2022
1 parent 2515140 commit f9b51ca
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 37 deletions.
4 changes: 4 additions & 0 deletions crates/bevy_audio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ parking_lot = "0.11.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
rodio = { version = "0.14", default-features = false, features = ["wasm-bindgen"] }

[dev-dependencies]
# bevy
bevy_internal = { path = "../bevy_internal", version = "0.5.0" }

[features]
mp3 = ["rodio/mp3"]
flac = ["rodio/flac"]
Expand Down
44 changes: 31 additions & 13 deletions crates/bevy_audio/src/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,36 @@ use bevy_asset::{Asset, Handle};
use parking_lot::RwLock;
use std::{collections::VecDeque, fmt};

/// The external struct used to play audio
pub struct Audio<P = AudioSource>
/// Use this resource to play audio
///
/// ```
/// # use bevy_ecs::system::Res;
/// # use bevy_asset::AssetServer;
/// # use bevy_audio::Audio;
/// fn play_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
/// audio.play(asset_server.load("my_sound.ogg"));
/// }
/// ```
pub struct Audio<Source = AudioSource>
where
P: Asset + Decodable,
Source: Asset + Decodable,
{
pub queue: RwLock<VecDeque<Handle<P>>>,
/// Queue for playing audio from asset handles
pub queue: RwLock<VecDeque<Handle<Source>>>,
}

impl<P: Asset> fmt::Debug for Audio<P>
impl<Source: Asset> fmt::Debug for Audio<Source>
where
P: Decodable,
Source: Decodable,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Audio").field("queue", &self.queue).finish()
}
}

impl<P> Default for Audio<P>
impl<Source> Default for Audio<Source>
where
P: Asset + Decodable,
Source: Asset + Decodable,
{
fn default() -> Self {
Self {
Expand All @@ -31,13 +41,21 @@ where
}
}

impl<P> Audio<P>
impl<Source> Audio<Source>
where
P: Asset + Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
Source: Asset + Decodable,
{
pub fn play(&self, audio_source: Handle<P>) {
/// Play audio from a [`Handle`] to the audio source
///
/// ```
/// # use bevy_ecs::system::Res;
/// # use bevy_asset::AssetServer;
/// # use bevy_audio::Audio;
/// fn play_audio_system(asset_server: Res<AssetServer>, audio: Res<Audio>) {
/// audio.play(asset_server.load("my_sound.ogg"));
/// }
/// ```
pub fn play(&self, audio_source: Handle<Source>) {
self.queue.write().push_front(audio_source);
}
}
32 changes: 14 additions & 18 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ use rodio::{OutputStream, OutputStreamHandle, Sink};
use std::marker::PhantomData;

/// Used internally to play audio on the current "audio device"
pub struct AudioOutput<P = AudioSource>
pub struct AudioOutput<Source = AudioSource>
where
P: Decodable,
Source: Decodable,
{
_stream: Option<OutputStream>,
stream_handle: Option<OutputStreamHandle>,
phantom: PhantomData<P>,
phantom: PhantomData<Source>,
}

impl<P> Default for AudioOutput<P>
impl<Source> Default for AudioOutput<Source>
where
P: Decodable,
Source: Decodable,
{
fn default() -> Self {
if let Ok((stream, stream_handle)) = OutputStream::try_default() {
Expand All @@ -37,21 +37,19 @@ where
}
}

impl<P> AudioOutput<P>
impl<Source> AudioOutput<Source>
where
P: Asset + Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
Source: Asset + Decodable,
{
fn play_source(&self, audio_source: &P) {
fn play_source(&self, audio_source: &Source) {
if let Some(stream_handle) = &self.stream_handle {
let sink = Sink::try_new(stream_handle).unwrap();
sink.append(audio_source.decoder());
sink.detach();
}
}

fn try_play_queued(&self, audio_sources: &Assets<P>, audio: &mut Audio<P>) {
fn try_play_queued(&self, audio_sources: &Assets<Source>, audio: &mut Audio<Source>) {
let mut queue = audio.queue.write();
let len = queue.len();
let mut i = 0;
Expand All @@ -69,17 +67,15 @@ where
}

/// Plays audio currently queued in the [`Audio`] resource through the [`AudioOutput`] resource
pub fn play_queued_audio_system<P: Asset>(world: &mut World)
pub fn play_queued_audio_system<Source: Asset>(world: &mut World)
where
P: Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
Source: Decodable,
{
let world = world.cell();
let audio_output = world.get_non_send::<AudioOutput<P>>().unwrap();
let mut audio = world.get_resource_mut::<Audio<P>>().unwrap();
let audio_output = world.get_non_send::<AudioOutput<Source>>().unwrap();
let mut audio = world.get_resource_mut::<Audio<Source>>().unwrap();

if let Some(audio_sources) = world.get_resource::<Assets<P>>() {
if let Some(audio_sources) = world.get_resource::<Assets<Source>>() {
audio_output.try_play_queued(&*audio_sources, &mut *audio);
};
}
22 changes: 18 additions & 4 deletions crates/bevy_audio/src/audio_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{io::Cursor, sync::Arc};
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "7a14806a-672b-443b-8d16-4f18afefa463"]
pub struct AudioSource {
/// Raw data of the audio source
pub bytes: Arc<[u8]>,
}

Expand All @@ -17,11 +18,18 @@ impl AsRef<[u8]> for AudioSource {
}
}

/// Loads mp3 files as [`AudioSource`] [`Assets`](bevy_asset::Assets)
/// Loads files as [`AudioSource`] [`Assets`](bevy_asset::Assets)
///
/// This asset loader supports different audio formats based on the enable Bevy features.
/// The feature `bevy/vorbis` enables loading from `.ogg` files and is enabled by default.
/// Other file endings can be loaded from with additional features:
/// `.mp3` with `bevy/mp3`
/// `.flac` with `bevy/flac`
/// `.wav` with `bevy/wav`
#[derive(Default)]
pub struct Mp3Loader;
pub struct AudioLoader;

impl AssetLoader for Mp3Loader {
impl AssetLoader for AudioLoader {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> BoxedFuture<Result<()>> {
load_context.set_default_asset(LoadedAsset::new(AudioSource {
bytes: bytes.into(),
Expand All @@ -43,14 +51,20 @@ impl AssetLoader for Mp3Loader {
}
}

/// A type implementing this trait can be decoded as a rodio source
pub trait Decodable: Send + Sync + 'static {
type Decoder;
/// The decoder that can decode the implemeting type
type Decoder: rodio::Source + Send + Sync + Iterator<Item = Self::DecoderItem>;
/// A single value given by the decoder
type DecoderItem: rodio::Sample + Send + Sync;

/// Build and return a [`Self::Decoder`] for the implementing type
fn decoder(&self) -> Self::Decoder;
}

impl Decodable for AudioSource {
type Decoder = rodio::Decoder<Cursor<AudioSource>>;
type DecoderItem = <rodio::Decoder<Cursor<AudioSource>> as Iterator>::Item;

fn decoder(&self) -> Self::Decoder {
rodio::Decoder::new(Cursor::new(self.clone())).unwrap()
Expand Down
37 changes: 35 additions & 2 deletions crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
//! Audio support for the game engine Bevy
//!
//! ```
//! # use bevy_ecs::{system::Res, event::EventWriter};
//! # use bevy_audio::{Audio, AudioPlugin};
//! # use bevy_asset::{AssetPlugin, AssetServer};
//! # use bevy_app::{App, AppExit};
//! # use bevy_internal::MinimalPlugins;
//! fn main() {
//! App::new()
//! .add_plugins(MinimalPlugins)
//! .add_plugin(AssetPlugin)
//! .add_plugin(AudioPlugin)
//! # .add_system(stop)
//! .add_startup_system(play_background_audio)
//! .run();
//! }
//!
//! fn play_background_audio(asset_server: Res<AssetServer>, audio: Res<Audio>) {
//! audio.play(asset_server.load("background_audio.ogg"));
//! }
//!
//! # fn stop(mut events: EventWriter<AppExit>) {
//! # events.send(AppExit)
//! # }
//! ```

#![forbid(unsafe_code)]
#![warn(missing_docs)]

mod audio;
mod audio_output;
mod audio_source;

#[allow(missing_docs)]
pub mod prelude {
#[doc(hidden)]
pub use crate::{Audio, AudioOutput, AudioSource, Decodable};
Expand All @@ -15,7 +46,9 @@ use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::system::IntoExclusiveSystem;

/// Adds support for audio playback to an App
/// Adds support for audio playback to a Bevy Application
///
/// Use the [`Audio`] resource to play audio.
#[derive(Default)]
pub struct AudioPlugin;

Expand All @@ -30,6 +63,6 @@ impl Plugin for AudioPlugin {
);

#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]
app.init_asset_loader::<Mp3Loader>();
app.init_asset_loader::<AudioLoader>();
}
}

0 comments on commit f9b51ca

Please sign in to comment.