bevy/examples/audio/decodable.rs
mgi388 2660ddc4c5
Support decibels in bevy_audio::Volume (#17605)
# Objective

- Allow users to configure volume using decibels by changing the
`Volume` type from newtyping an `f32` to an enum with `Linear` and
`Decibels` variants.
- Fixes #9507.
- Alternative reworked version of closed #9582.

## Solution

Compared to https://github.com/bevyengine/bevy/pull/9582, this PR has
the following main differences:

1. It uses the term "linear scale" instead of "amplitude" per
https://github.com/bevyengine/bevy/pull/9582/files#r1513529491.
2. Supports `ops` for doing `Volume` arithmetic. Can add two volumes,
e.g. to increase/decrease the current volume. Can multiply two volumes,
e.g. to get the “effective” volume of an audio source considering global
volume.

[requested and blessed on Discord]:
https://discord.com/channels/691052431525675048/749430447326625812/1318272597003341867

## Testing

- Ran `cargo run --example soundtrack`.
- Ran `cargo run --example audio_control`.
- Ran `cargo run --example spatial_audio_2d`.
- Ran `cargo run --example spatial_audio_3d`.
- Ran `cargo run --example pitch`.
- Ran `cargo run --example decodable`.
- Ran `cargo run --example audio`.

---

## Migration Guide

Audio volume can now be configured using decibel values, as well as
using linear scale values. To enable this, some types and functions in
`bevy_audio` have changed.

- `Volume` is now an enum with `Linear` and `Decibels` variants.

Before:

```rust
let v = Volume(1.0);
```

After:

```rust
let volume = Volume::Linear(1.0);
let volume = Volume::Decibels(0.0); // or now you can deal with decibels if you prefer
```

- `Volume::ZERO` has been renamed to the more semantically correct
`Volume::SILENT` because `Volume` now supports decibels and "zero
volume" in decibels actually means "normal volume".
- The `AudioSinkPlayback` trait's volume-related methods now deal with
`Volume` types rather than `f32`s. `AudioSinkPlayback::volume()` now
returns a `Volume` rather than an `f32`. `AudioSinkPlayback::set_volume`
now receives a `Volume` rather than an `f32`. This affects the
`AudioSink` and `SpatialAudioSink` implementations of the trait. The
previous `f32` values are equivalent to the volume converted to linear
scale so the `Volume:: Linear` variant should be used to migrate between
`f32`s and `Volume`.
- The `GlobalVolume::new` function now receives a `Volume` instead of an
`f32`.

---------

Co-authored-by: Zachary Harrold <zac@harrold.com.au>
2025-02-10 21:26:43 +00:00

104 lines
2.9 KiB
Rust

//! Shows how to create a custom [`Decodable`] type by implementing a Sine wave.
use bevy::{
audio::{AddAudioSource, AudioPlugin, Source, Volume},
math::ops,
prelude::*,
reflect::TypePath,
};
use core::time::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.
// This allows the type to be registered as an asset.
#[derive(Asset, TypePath)]
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(ops::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 DecoderItem = <SineDecoder as Iterator>::Item;
type Decoder = SineDecoder;
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.set(AudioPlugin {
global_volume: Volume::Linear(0.2).into(),
..default()
}))
.add_audio_source::<SineAudio>()
.add_systems(Startup, setup)
.run();
}
fn setup(mut assets: ResMut<Assets<SineAudio>>, mut commands: Commands) {
// 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
});
commands.spawn(AudioPlayer(audio_handle));
}