mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
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:
parent
7d0edbc4d6
commit
0df67cdaae
5 changed files with 155 additions and 7 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
100
examples/audio/decodable.rs
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue