Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add AddAudioSource trait and improve Decodable docs #6649

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions crates/bevy_audio/src/audio_source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_asset::{Asset, AssetLoader, LoadContext, LoadedAsset};
use bevy_reflect::TypeUuid;
use bevy_utils::BoxedFuture;
use std::{io::Cursor, sync::Arc};
Expand Down Expand Up @@ -55,14 +55,100 @@ impl AssetLoader for AudioLoader {
}
}

/// A type implementing this trait can be decoded as a rodio source
/// A type implementing this trait can be converted to a [`rodio::Source`] type. It must be [`Send`] and [`Sync`], and usually implements [`Asset`] so needs to be [`TypeUuid`], in order to be registered. Types that implement this trait usually contain raw sound data that can be converted into an iterator of samples. This trait is implemented for [`AudioSource`].
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Examples
/// Basic implementation:
/// ```
/// use bevy_app::App;
/// use bevy_reflect::TypeUuid;
/// use bevy_asset::AssetPlugin;
/// use bevy_audio::{AddAudioSource, Decodable, AudioPlugin};
///
///
/// // This struct contains the raw data for the audio being played. This is where data read from an audio file would be stored, for example.
/// // `TypeUuid` is derived for it so that `Asset` can be implemented for it, which allows it to be registered in the App.
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
/// #[derive(TypeUuid)]
/// #[uuid = "c2090c23-78fd-44f1-8508-c89b1f3cec29"]
/// struct CustomAudio {}
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
/// // This decoder is responsible for playing the audio, and so stores data about the audio being played.
/// struct CustomDecoder {
/// number_frames: u64,
/// channels: u16,
/// sample_rate: u32,
/// iter: std::vec::IntoIter<f32>,
/// frames_left: usize,
/// }
///
/// // The decoder must implement iterator so that it can implement `Decodable`. In this implementation it simply returns the next frame and decrements the frame count.
/// impl Iterator for CustomDecoder {
/// type Item = f32;
///
/// fn next(&mut self) -> Option<Self::Item> {
/// if let Some(frame) = self.iter.next() {
/// self.frames_left -= 1;
/// Some(frame)
/// } else {
/// None
/// }
/// }
/// }
/// // `rodio::Source` is what allows the audio source to be played by bevy. This trait provides information on the audio.
/// impl rodio::Source for CustomDecoder {
/// fn current_frame_len(&self) -> Option<usize> {
/// Some(self.frames_left)
/// }
///
/// fn channels(&self) -> u16 {
/// self.channels
/// }
///
/// fn sample_rate(&self) -> u32 {
/// self.sample_rate
/// }
///
/// fn total_duration(&self) -> Option<bevy_utils::Duration> {
/// Some(bevy_utils::Duration::from_secs(
/// self.number_frames / (self.sample_rate as u64 * self.channels as u64),
/// ))
/// }
/// }
///
/// // Finally `Decodable` can be implemented for our `CustomAudio`.
/// impl Decodable for CustomAudio {
/// type Decoder = CustomDecoder;
///
/// type DecoderItem = <CustomDecoder as Iterator>::Item;
///
/// fn decoder(&self) -> Self::Decoder {
/// // in reality the data would be read from a file then stored in `CustomAudio`, but for simplicity it is created here.
/// let frames = vec![0., 1., 2.];
/// CustomDecoder {
/// number_frames: frames.len() as u64,
/// channels: 1,
/// sample_rate: 1000,
/// iter: frames.clone().into_iter(),
/// frames_left: frames.len(),
/// }
/// }
/// }
///
///
/// let mut app = App::new();
/// // register the audio source so that it can be used
/// app
/// .add_plugin(AssetPlugin::default())
/// .add_plugin(AudioPlugin)
/// .add_audio_source::<CustomAudio>();
/// ```
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
pub trait Decodable: Send + Sync + 'static {
/// The decoder that can decode the implementing type
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>;
/// A single value given by the decoder
/// The type of the audio samples. Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`], but other types can implement [`rodio::Sample`] as well.
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
type DecoderItem: rodio::Sample + Send + Sync;

/// Build and return a [`Self::Decoder`] for the implementing type
/// The type of the iterator of the audio samples, which iterators over samples of type [`Self::DecoderItem`]. Must be a [`rodio::Source`] so that it can provide information on the audio it is iterating over.
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>;

/// Build and return an iterator [`Self::Decoder`] for the implementing type
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
fn decoder(&self) -> Self::Decoder;
}

Expand All @@ -74,3 +160,11 @@ impl Decodable for AudioSource {
rodio::Decoder::new(Cursor::new(self.clone())).unwrap()
}
}

/// A trait that allows adding a custom audio source to the object. This is implemented for [`App`][bevy_app::App] to allow registering custom [`Decodable`] types.
pub trait AddAudioSource {
/// Registers an audio source. The type must implement [`Decodable`], so that it can be converted to [`rodio::Source`], and [`Asset`], so that it can be registered. To use this method on [`App`][bevy_app::App] the [`Audio`][super::AudioPlugin] [`Asset`][bevy_asset::AssetPlugin] plugins must be added to the app.
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
fn add_audio_source<T>(&mut self) -> &mut Self
where
T: Decodable + Asset;
}
15 changes: 14 additions & 1 deletion crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ pub mod prelude {
pub use audio::*;
pub use audio_output::*;
pub use audio_source::*;

pub use rodio::cpal::Sample as CpalSample;
pub use rodio::source::Source;
pub use rodio::Sample;

use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_asset::{AddAsset, Asset};

/// Adds support for audio playback to a Bevy Application
///
Expand All @@ -63,3 +64,15 @@ impl Plugin for AudioPlugin {
app.init_asset_loader::<AudioLoader>();
}
}

impl AddAudioSource for App {
fn add_audio_source<T>(&mut self) -> &mut Self
where
T: Decodable + Asset,
{
self.add_asset::<T>()
.init_resource::<Audio<T>>()
.init_non_send_resource::<AudioOutput<T>>()
.add_system_to_stage(CoreStage::PostUpdate, play_queued_audio_system::<T>)
}
}