From ffb1c3f8f864334652b55f3534f4b5ad7f332738 Mon Sep 17 00:00:00 2001 From: dis-da-moe Date: Wed, 16 Nov 2022 17:14:19 +0300 Subject: [PATCH 1/5] add `AddAudioSource` trait and improve `Decodable` docs --- crates/bevy_audio/src/audio_source.rs | 106 ++++++++++++++++++++++++-- crates/bevy_audio/src/lib.rs | 15 +++- 2 files changed, 114 insertions(+), 7 deletions(-) diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 217c9aa868350..d3f7b62244663 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -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}; @@ -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`]. +/// +/// # 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. +/// #[derive(TypeUuid)] +/// #[uuid = "c2090c23-78fd-44f1-8508-c89b1f3cec29"] +/// struct CustomAudio {} +/// // 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, +/// 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 { +/// 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 { +/// Some(self.frames_left) +/// } +/// +/// fn channels(&self) -> u16 { +/// self.channels +/// } +/// +/// fn sample_rate(&self) -> u32 { +/// self.sample_rate +/// } +/// +/// fn total_duration(&self) -> Option { +/// 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 = ::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::(); +/// ``` pub trait Decodable: Send + Sync + 'static { - /// The decoder that can decode the implementing type - type Decoder: rodio::Source + Send + Iterator; - /// 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. 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; + + /// Build and return an iterator [`Self::Decoder`] for the implementing type fn decoder(&self) -> Self::Decoder; } @@ -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. + fn add_audio_source(&mut self) -> &mut Self + where + T: Decodable + Asset; +} diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 7c4db0090c77c..705e1c1d94557 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -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 /// @@ -63,3 +64,15 @@ impl Plugin for AudioPlugin { app.init_asset_loader::(); } } + +impl AddAudioSource for App { + fn add_audio_source(&mut self) -> &mut Self + where + T: Decodable + Asset, + { + self.add_asset::() + .init_resource::>() + .init_non_send_resource::>() + .add_system_to_stage(CoreStage::PostUpdate, play_queued_audio_system::) + } +} From 86c5d4fc872d350a4788578eb6941e0600e3ddec Mon Sep 17 00:00:00 2001 From: moeur Date: Fri, 18 Nov 2022 02:02:40 +0300 Subject: [PATCH 2/5] clean docs and move sine example into own file --- Cargo.toml | 10 +++ crates/bevy_audio/src/audio_source.rs | 111 +++++--------------------- examples/README.md | 1 + examples/audio/decodable.rs | 100 +++++++++++++++++++++++ 4 files changed, 131 insertions(+), 91 deletions(-) create mode 100644 examples/audio/decodable.rs diff --git a/Cargo.toml b/Cargo.toml index 251e47609645d..2bd7ed5817a4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -759,6 +759,16 @@ description = "Shows how to load and play an audio file, and control how it's pl category = "Audio" wasm = true +[[example]] +name = "decodable" +path = "examples/audio/decodable.rs" + +[package.metadata.example.decodable] +name = "Decodable" +description = "Shows how to create and register a custom audio source by implementing the `Decodable` type." +category = "Audio" +wasm = true + # Diagnostics [[example]] name = "log_diagnostics" diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index d3f7b62244663..7642892a6acd9 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -55,100 +55,23 @@ impl AssetLoader for AudioLoader { } } -/// 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`]. -/// -/// # 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. -/// #[derive(TypeUuid)] -/// #[uuid = "c2090c23-78fd-44f1-8508-c89b1f3cec29"] -/// struct CustomAudio {} -/// // 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, -/// 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 { -/// 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 { -/// Some(self.frames_left) -/// } -/// -/// fn channels(&self) -> u16 { -/// self.channels -/// } -/// -/// fn sample_rate(&self) -> u32 { -/// self.sample_rate -/// } -/// -/// fn total_duration(&self) -> Option { -/// 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 = ::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::(); -/// ``` +/// 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`]. pub trait Decodable: Send + Sync + 'static { - /// 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. + /// The type of the audio samples. + /// Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`]. + /// Other types can implement the [`rodio::Sample`] trait as well. type DecoderItem: rodio::Sample + Send + Sync; - /// 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. + /// The type of the iterator of the audio samples, + /// which iterates 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; - /// Build and return an iterator [`Self::Decoder`] for the implementing type + /// Build and return a [`Self::Decoder`] of the implementing type fn decoder(&self) -> Self::Decoder; } @@ -161,9 +84,15 @@ impl Decodable for AudioSource { } } -/// 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. +/// 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. + /// Registers an audio source. + /// The type must implement [`Decodable`], + /// so that it can be converted to a [`rodio::Source`] type, + /// and [`Asset`], so that it can be registered as an asset. + /// To use this method on [`App`][bevy_app::App], + /// the [audio][super::AudioPlugin] and [asset][bevy_asset::AssetPlugin] plugins must be added first. fn add_audio_source(&mut self) -> &mut Self where T: Decodable + Asset; diff --git a/examples/README.md b/examples/README.md index 6b57cf3aec06e..fa34d0116113f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -178,6 +178,7 @@ Example | Description --- | --- [Audio](../examples/audio/audio.rs) | Shows how to load and play an audio file [Audio Control](../examples/audio/audio_control.rs) | Shows how to load and play an audio file, and control how it's played +[Decodable](../examples/audio/decodable.rs) | Shows how to create and register a custom audio source by implementing the `Decodable` type. ## Diagnostics diff --git a/examples/audio/decodable.rs b/examples/audio/decodable.rs new file mode 100644 index 0000000000000..7d38688e2db93 --- /dev/null +++ b/examples/audio/decodable.rs @@ -0,0 +1,100 @@ +//! Shows how to create a custom `Decodable` type by implementing a Sine wave. +use bevy::audio::AddAudioSource; +use bevy::audio::Source; +use bevy::prelude::*; +use bevy::reflect::TypeUuid; +use bevy::utils::Duration; + +// This struct usually contains the data for the audio being played. +// This is where data read from an audio file would be stored, for example. +// Implementing `TypeUuid` will automatically implement `Asset`. +// This allows the type to be registered as an asset. +#[derive(TypeUuid)] +#[uuid = "c2090c23-78fd-44f1-8508-c89b1f3cec29"] +struct SineAudio; + +// This decoder is responsible for playing the audio, +// and so stores data about the audio being played. +struct SineDecoder { + // how far along one period the wave is (between 0 and 1) + current_progress: f32, + // how much we move along the period every frame + progress_per_frame: f32, + // how long a period is + period: f32, + sample_rate: u32, +} + +impl SineDecoder { + fn new() -> Self { + // this is the frequency of A4 + let frequency = 440.; + // standard sample rate for most recordings + let sample_rate = 44_100; + SineDecoder { + current_progress: 0., + progress_per_frame: frequency / sample_rate as f32, + period: std::f32::consts::PI * 2., + sample_rate, + } + } +} + +// The decoder must implement iterator so that it can implement `Decodable`. +impl Iterator for SineDecoder { + type Item = f32; + + fn next(&mut self) -> Option { + self.current_progress += self.progress_per_frame; + // we loop back round to 0 to avoid floating point inaccuracies + if self.current_progress > 1. { + self.current_progress -= 1.; + } + Some(f32::sin(self.period * self.current_progress)) + } +} +// `Source` is what allows the audio source to be played by bevy. +// This trait provides information on the audio. +impl Source for SineDecoder { + fn current_frame_len(&self) -> Option { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + self.sample_rate + } + + fn total_duration(&self) -> Option { + None + } +} + +// Finally `Decodable` can be implemented for our `SineAudio`. +impl Decodable for SineAudio { + type Decoder = SineDecoder; + + type DecoderItem = ::Item; + + fn decoder(&self) -> Self::Decoder { + SineDecoder::new() + } +} + +fn main() { + let mut app = App::new(); + // register the audio source so that it can be used + app.add_plugins(DefaultPlugins) + .add_audio_source::() + .add_startup_system(setup) + .run(); +} + +fn setup(mut assets: ResMut>, audio: Res>) { + // add a `SineAudio` to the asset server so that it can be played + let audio_handle = assets.add(SineAudio {}); + audio.play(audio_handle); +} From 40df1a7d2f290e9ea9f36a19e715ace9871037f0 Mon Sep 17 00:00:00 2001 From: moeur Date: Fri, 18 Nov 2022 02:07:11 +0300 Subject: [PATCH 3/5] mention decodable example in docs --- crates/bevy_audio/src/audio_source.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 7642892a6acd9..38977fa64ca0a 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -60,6 +60,7 @@ impl AssetLoader for AudioLoader { /// 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`]. +/// Check the example `audio/decodable` for how to implement this trait on a custom type. pub trait Decodable: Send + Sync + 'static { /// The type of the audio samples. /// Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`]. From 3e94054a184c34f542e78b63e75a397ce7a891c0 Mon Sep 17 00:00:00 2001 From: moeur Date: Fri, 18 Nov 2022 02:13:56 +0300 Subject: [PATCH 4/5] change manual comparison to modulo --- examples/audio/decodable.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/audio/decodable.rs b/examples/audio/decodable.rs index 7d38688e2db93..417e93e232959 100644 --- a/examples/audio/decodable.rs +++ b/examples/audio/decodable.rs @@ -47,9 +47,7 @@ impl Iterator for SineDecoder { fn next(&mut self) -> Option { self.current_progress += self.progress_per_frame; // we loop back round to 0 to avoid floating point inaccuracies - if self.current_progress > 1. { - self.current_progress -= 1.; - } + self.current_progress %= 1.; Some(f32::sin(self.period * self.current_progress)) } } From 2f5ad36c1015e48fda966755607c8c33ed289509 Mon Sep 17 00:00:00 2001 From: dis-da-moe Date: Fri, 18 Nov 2022 12:38:19 +0300 Subject: [PATCH 5/5] variable frequency --- examples/audio/decodable.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/audio/decodable.rs b/examples/audio/decodable.rs index 417e93e232959..1ac2fdd06db86 100644 --- a/examples/audio/decodable.rs +++ b/examples/audio/decodable.rs @@ -1,4 +1,5 @@ //! Shows how to create a custom `Decodable` type by implementing a Sine wave. +//! ***WARNING THIS EXAMPLE IS VERY LOUD.*** Turn your volume down. use bevy::audio::AddAudioSource; use bevy::audio::Source; use bevy::prelude::*; @@ -11,8 +12,9 @@ use bevy::utils::Duration; // This allows the type to be registered as an asset. #[derive(TypeUuid)] #[uuid = "c2090c23-78fd-44f1-8508-c89b1f3cec29"] -struct SineAudio; - +struct SineAudio { + frequency: f32, +} // This decoder is responsible for playing the audio, // and so stores data about the audio being played. struct SineDecoder { @@ -26,9 +28,7 @@ struct SineDecoder { } impl SineDecoder { - fn new() -> Self { - // this is the frequency of A4 - let frequency = 440.; + fn new(frequency: f32) -> Self { // standard sample rate for most recordings let sample_rate = 44_100; SineDecoder { @@ -78,7 +78,7 @@ impl Decodable for SineAudio { type DecoderItem = ::Item; fn decoder(&self) -> Self::Decoder { - SineDecoder::new() + SineDecoder::new(self.frequency) } } @@ -93,6 +93,8 @@ fn main() { fn setup(mut assets: ResMut>, audio: Res>) { // add a `SineAudio` to the asset server so that it can be played - let audio_handle = assets.add(SineAudio {}); + let audio_handle = assets.add(SineAudio { + frequency: 440., //this is the frequency of A4 + }); audio.play(audio_handle); }