Add AddAudioSource trait and improve Decodable docs (#6649)

# Objective

- Fixes #6361
- Fixes #6362 
- Fixes #6364 

## Solution
- Added an example for creating a custom `Decodable` type 
- Clarified the documentation on `Decodable`
- Added an `AddAudioSource` trait and implemented it for `App`

Co-authored-by: dis-da-moe <84386186+dis-da-moe@users.noreply.github.com>
This commit is contained in:
dis-da-moe 2023-01-17 22:42:00 +00:00
parent 7d0edbc4d6
commit 0df67cdaae
5 changed files with 155 additions and 7 deletions

View file

@ -763,6 +763,16 @@ description = "Shows how to load and play an audio file, and control how it's pl
category = "Audio" category = "Audio"
wasm = true 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 # Diagnostics
[[example]] [[example]]
name = "log_diagnostics" name = "log_diagnostics"

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_asset::{Asset, AssetLoader, LoadContext, LoadedAsset};
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_utils::BoxedFuture; use bevy_utils::BoxedFuture;
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
@ -63,14 +63,24 @@ 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`].
/// Check the example `audio/decodable` for how to implement this trait on a custom type.
pub trait Decodable: Send + Sync + 'static { pub trait Decodable: Send + Sync + 'static {
/// The decoder that can decode the implementing type /// The type of the audio samples.
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>; /// Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`].
/// A single value given by the decoder /// Other types can implement the [`rodio::Sample`] trait as well.
type DecoderItem: rodio::Sample + Send + Sync; 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 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<Item = Self::DecoderItem>;
/// Build and return a [`Self::Decoder`] of the implementing type
fn decoder(&self) -> Self::Decoder; fn decoder(&self) -> Self::Decoder;
} }
@ -82,3 +92,17 @@ impl Decodable for AudioSource {
rodio::Decoder::new(Cursor::new(self.clone())).unwrap() 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 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<T>(&mut self) -> &mut Self
where
T: Decodable + Asset;
}

View file

@ -35,12 +35,13 @@ pub mod prelude {
pub use audio::*; pub use audio::*;
pub use audio_output::*; pub use audio_output::*;
pub use audio_source::*; pub use audio_source::*;
pub use rodio::cpal::Sample as CpalSample; pub use rodio::cpal::Sample as CpalSample;
pub use rodio::source::Source; pub use rodio::source::Source;
pub use rodio::Sample; pub use rodio::Sample;
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::AddAsset; use bevy_asset::{AddAsset, Asset};
/// Adds support for audio playback to a Bevy Application /// Adds support for audio playback to a Bevy Application
/// ///
@ -63,3 +64,15 @@ impl Plugin for AudioPlugin {
app.init_asset_loader::<AudioLoader>(); 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>)
}
}

View file

@ -178,6 +178,7 @@ Example | Description
--- | --- --- | ---
[Audio](../examples/audio/audio.rs) | Shows how to load and play an audio file [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 [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 ## Diagnostics

100
examples/audio/decodable.rs Normal file
View file

@ -0,0 +1,100 @@
//! 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::*;
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 {
frequency: f32,
}
// 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(frequency: f32) -> Self {
// 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::Item> {
self.current_progress += self.progress_per_frame;
// we loop back round to 0 to avoid floating point inaccuracies
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<usize> {
None
}
fn channels(&self) -> u16 {
1
}
fn sample_rate(&self) -> u32 {
self.sample_rate
}
fn total_duration(&self) -> Option<Duration> {
None
}
}
// Finally `Decodable` can be implemented for our `SineAudio`.
impl Decodable for SineAudio {
type Decoder = SineDecoder;
type DecoderItem = <SineDecoder as Iterator>::Item;
fn decoder(&self) -> Self::Decoder {
SineDecoder::new(self.frequency)
}
}
fn main() {
let mut app = App::new();
// register the audio source so that it can be used
app.add_plugins(DefaultPlugins)
.add_audio_source::<SineAudio>()
.add_startup_system(setup)
.run();
}
fn setup(mut assets: ResMut<Assets<SineAudio>>, audio: Res<Audio<SineAudio>>) {
// add a `SineAudio` to the asset server so that it can be played
let audio_handle = assets.add(SineAudio {
frequency: 440., //this is the frequency of A4
});
audio.play(audio_handle);
}