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 all commits
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
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,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"
Expand Down
36 changes: 30 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 @@ -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`],
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
/// 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 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`].
/// Other types can implement the [`rodio::Sample`] trait 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 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;
}

Expand All @@ -82,3 +92,17 @@ 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 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;
}
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>)
}
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
100 changes: 100 additions & 0 deletions examples/audio/decodable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Shows how to create a custom `Decodable` type by implementing a Sine wave.
dis-da-mor marked this conversation as resolved.
Show resolved Hide resolved
//! ***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);
}