Skip to content

Commit 0df67cd

Browse files
committed
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 <[email protected]>
1 parent 7d0edbc commit 0df67cd

File tree

5 files changed

+155
-7
lines changed

5 files changed

+155
-7
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,16 @@ description = "Shows how to load and play an audio file, and control how it's pl
763763
category = "Audio"
764764
wasm = true
765765

766+
[[example]]
767+
name = "decodable"
768+
path = "examples/audio/decodable.rs"
769+
770+
[package.metadata.example.decodable]
771+
name = "Decodable"
772+
description = "Shows how to create and register a custom audio source by implementing the `Decodable` type."
773+
category = "Audio"
774+
wasm = true
775+
766776
# Diagnostics
767777
[[example]]
768778
name = "log_diagnostics"

crates/bevy_audio/src/audio_source.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
2+
use bevy_asset::{Asset, AssetLoader, LoadContext, LoadedAsset};
33
use bevy_reflect::TypeUuid;
44
use bevy_utils::BoxedFuture;
55
use std::{io::Cursor, sync::Arc};
@@ -63,14 +63,24 @@ impl AssetLoader for AudioLoader {
6363
}
6464
}
6565

66-
/// A type implementing this trait can be decoded as a rodio source
66+
/// A type implementing this trait can be converted to a [`rodio::Source`] type.
67+
/// It must be [`Send`] and [`Sync`], and usually implements [`Asset`] so needs to be [`TypeUuid`],
68+
/// in order to be registered.
69+
/// Types that implement this trait usually contain raw sound data that can be converted into an iterator of samples.
70+
/// This trait is implemented for [`AudioSource`].
71+
/// Check the example `audio/decodable` for how to implement this trait on a custom type.
6772
pub trait Decodable: Send + Sync + 'static {
68-
/// The decoder that can decode the implementing type
69-
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>;
70-
/// A single value given by the decoder
73+
/// The type of the audio samples.
74+
/// Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`].
75+
/// Other types can implement the [`rodio::Sample`] trait as well.
7176
type DecoderItem: rodio::Sample + Send + Sync;
7277

73-
/// Build and return a [`Self::Decoder`] for the implementing type
78+
/// The type of the iterator of the audio samples,
79+
/// which iterates over samples of type [`Self::DecoderItem`].
80+
/// Must be a [`rodio::Source`] so that it can provide information on the audio it is iterating over.
81+
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>;
82+
83+
/// Build and return a [`Self::Decoder`] of the implementing type
7484
fn decoder(&self) -> Self::Decoder;
7585
}
7686

@@ -82,3 +92,17 @@ impl Decodable for AudioSource {
8292
rodio::Decoder::new(Cursor::new(self.clone())).unwrap()
8393
}
8494
}
95+
96+
/// A trait that allows adding a custom audio source to the object.
97+
/// This is implemented for [`App`][bevy_app::App] to allow registering custom [`Decodable`] types.
98+
pub trait AddAudioSource {
99+
/// Registers an audio source.
100+
/// The type must implement [`Decodable`],
101+
/// so that it can be converted to a [`rodio::Source`] type,
102+
/// and [`Asset`], so that it can be registered as an asset.
103+
/// To use this method on [`App`][bevy_app::App],
104+
/// the [audio][super::AudioPlugin] and [asset][bevy_asset::AssetPlugin] plugins must be added first.
105+
fn add_audio_source<T>(&mut self) -> &mut Self
106+
where
107+
T: Decodable + Asset;
108+
}

crates/bevy_audio/src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ pub mod prelude {
3535
pub use audio::*;
3636
pub use audio_output::*;
3737
pub use audio_source::*;
38+
3839
pub use rodio::cpal::Sample as CpalSample;
3940
pub use rodio::source::Source;
4041
pub use rodio::Sample;
4142

4243
use bevy_app::prelude::*;
43-
use bevy_asset::AddAsset;
44+
use bevy_asset::{AddAsset, Asset};
4445

4546
/// Adds support for audio playback to a Bevy Application
4647
///
@@ -63,3 +64,15 @@ impl Plugin for AudioPlugin {
6364
app.init_asset_loader::<AudioLoader>();
6465
}
6566
}
67+
68+
impl AddAudioSource for App {
69+
fn add_audio_source<T>(&mut self) -> &mut Self
70+
where
71+
T: Decodable + Asset,
72+
{
73+
self.add_asset::<T>()
74+
.init_resource::<Audio<T>>()
75+
.init_non_send_resource::<AudioOutput<T>>()
76+
.add_system_to_stage(CoreStage::PostUpdate, play_queued_audio_system::<T>)
77+
}
78+
}

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ Example | Description
178178
--- | ---
179179
[Audio](../examples/audio/audio.rs) | Shows how to load and play an audio file
180180
[Audio Control](../examples/audio/audio_control.rs) | Shows how to load and play an audio file, and control how it's played
181+
[Decodable](../examples/audio/decodable.rs) | Shows how to create and register a custom audio source by implementing the `Decodable` type.
181182

182183
## Diagnostics
183184

examples/audio/decodable.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//! Shows how to create a custom `Decodable` type by implementing a Sine wave.
2+
//! ***WARNING THIS EXAMPLE IS VERY LOUD.*** Turn your volume down.
3+
use bevy::audio::AddAudioSource;
4+
use bevy::audio::Source;
5+
use bevy::prelude::*;
6+
use bevy::reflect::TypeUuid;
7+
use bevy::utils::Duration;
8+
9+
// This struct usually contains the data for the audio being played.
10+
// This is where data read from an audio file would be stored, for example.
11+
// Implementing `TypeUuid` will automatically implement `Asset`.
12+
// This allows the type to be registered as an asset.
13+
#[derive(TypeUuid)]
14+
#[uuid = "c2090c23-78fd-44f1-8508-c89b1f3cec29"]
15+
struct SineAudio {
16+
frequency: f32,
17+
}
18+
// This decoder is responsible for playing the audio,
19+
// and so stores data about the audio being played.
20+
struct SineDecoder {
21+
// how far along one period the wave is (between 0 and 1)
22+
current_progress: f32,
23+
// how much we move along the period every frame
24+
progress_per_frame: f32,
25+
// how long a period is
26+
period: f32,
27+
sample_rate: u32,
28+
}
29+
30+
impl SineDecoder {
31+
fn new(frequency: f32) -> Self {
32+
// standard sample rate for most recordings
33+
let sample_rate = 44_100;
34+
SineDecoder {
35+
current_progress: 0.,
36+
progress_per_frame: frequency / sample_rate as f32,
37+
period: std::f32::consts::PI * 2.,
38+
sample_rate,
39+
}
40+
}
41+
}
42+
43+
// The decoder must implement iterator so that it can implement `Decodable`.
44+
impl Iterator for SineDecoder {
45+
type Item = f32;
46+
47+
fn next(&mut self) -> Option<Self::Item> {
48+
self.current_progress += self.progress_per_frame;
49+
// we loop back round to 0 to avoid floating point inaccuracies
50+
self.current_progress %= 1.;
51+
Some(f32::sin(self.period * self.current_progress))
52+
}
53+
}
54+
// `Source` is what allows the audio source to be played by bevy.
55+
// This trait provides information on the audio.
56+
impl Source for SineDecoder {
57+
fn current_frame_len(&self) -> Option<usize> {
58+
None
59+
}
60+
61+
fn channels(&self) -> u16 {
62+
1
63+
}
64+
65+
fn sample_rate(&self) -> u32 {
66+
self.sample_rate
67+
}
68+
69+
fn total_duration(&self) -> Option<Duration> {
70+
None
71+
}
72+
}
73+
74+
// Finally `Decodable` can be implemented for our `SineAudio`.
75+
impl Decodable for SineAudio {
76+
type Decoder = SineDecoder;
77+
78+
type DecoderItem = <SineDecoder as Iterator>::Item;
79+
80+
fn decoder(&self) -> Self::Decoder {
81+
SineDecoder::new(self.frequency)
82+
}
83+
}
84+
85+
fn main() {
86+
let mut app = App::new();
87+
// register the audio source so that it can be used
88+
app.add_plugins(DefaultPlugins)
89+
.add_audio_source::<SineAudio>()
90+
.add_startup_system(setup)
91+
.run();
92+
}
93+
94+
fn setup(mut assets: ResMut<Assets<SineAudio>>, audio: Res<Audio<SineAudio>>) {
95+
// add a `SineAudio` to the asset server so that it can be played
96+
let audio_handle = assets.add(SineAudio {
97+
frequency: 440., //this is the frequency of A4
98+
});
99+
audio.play(audio_handle);
100+
}

0 commit comments

Comments
 (0)