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>
This commit is contained in:
parent
eee7fd5b3e
commit
2660ddc4c5
@ -74,7 +74,7 @@ impl PlaybackSettings {
|
||||
/// added again.
|
||||
pub const ONCE: PlaybackSettings = PlaybackSettings {
|
||||
mode: PlaybackMode::Once,
|
||||
volume: Volume(1.0),
|
||||
volume: Volume::Linear(1.0),
|
||||
speed: 1.0,
|
||||
paused: false,
|
||||
muted: false,
|
||||
|
@ -170,7 +170,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
}
|
||||
|
||||
sink.set_speed(settings.speed);
|
||||
sink.set_volume(settings.volume.0 * global_volume.volume.0);
|
||||
sink.set_volume(settings.volume * global_volume.volume);
|
||||
|
||||
if settings.paused {
|
||||
sink.pause();
|
||||
@ -210,7 +210,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
}
|
||||
|
||||
sink.set_speed(settings.speed);
|
||||
sink.set_volume(settings.volume.0 * global_volume.volume.0);
|
||||
sink.set_volume(settings.volume * global_volume.volume);
|
||||
|
||||
if settings.paused {
|
||||
sink.pause();
|
||||
|
@ -3,37 +3,26 @@ use bevy_math::Vec3;
|
||||
use bevy_transform::prelude::Transform;
|
||||
use rodio::{Sink, SpatialSink};
|
||||
|
||||
use crate::Volume;
|
||||
|
||||
/// Common interactions with an audio sink.
|
||||
pub trait AudioSinkPlayback {
|
||||
/// Gets the volume of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value
|
||||
/// other than `1.0` will multiply each sample by this value.
|
||||
/// Gets the volume of the sound as a [`Volume`].
|
||||
///
|
||||
/// If the sink is muted, this returns the managed volume rather than the
|
||||
/// sink's actual volume. This allows you to use the volume as if the sink
|
||||
/// were not muted, because a muted sink has a volume of 0.
|
||||
fn volume(&self) -> f32;
|
||||
/// sink's actual volume. This allows you to use the returned volume as if
|
||||
/// the sink were not muted, because a muted sink has a physical volume of
|
||||
/// 0.
|
||||
fn volume(&self) -> Volume;
|
||||
|
||||
/// Changes the volume of the sound.
|
||||
///
|
||||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0`
|
||||
/// will multiply each sample by this value.
|
||||
/// Changes the volume of the sound to the given [`Volume`].
|
||||
///
|
||||
/// If the sink is muted, changing the volume won't unmute it, i.e. the
|
||||
/// sink's volume will remain at `0.0`. However, the sink will remember the
|
||||
/// volume change and it will be used when [`unmute`](Self::unmute) is
|
||||
/// called. This allows you to control the volume even when the sink is
|
||||
/// muted.
|
||||
///
|
||||
/// # Note on Audio Volume
|
||||
///
|
||||
/// An increase of 10 decibels (dB) roughly corresponds to the perceived volume doubling in intensity.
|
||||
/// As this function scales not the volume but the amplitude, a conversion might be necessary.
|
||||
/// For example, to halve the perceived volume you need to decrease the volume by 10 dB.
|
||||
/// This corresponds to 20log(x) = -10dB, solving x = 10^(-10/20) = 0.316.
|
||||
/// Multiply the current volume by 0.316 to halve the perceived volume.
|
||||
fn set_volume(&mut self, volume: f32);
|
||||
/// sink's volume will remain "off" / "muted". However, the sink will
|
||||
/// remember the volume change and it will be used when
|
||||
/// [`unmute`](Self::unmute) is called. This allows you to control the
|
||||
/// volume even when the sink is muted.
|
||||
fn set_volume(&mut self, volume: Volume);
|
||||
|
||||
/// Gets the speed of the sound.
|
||||
///
|
||||
@ -132,7 +121,7 @@ pub struct AudioSink {
|
||||
/// If the sink is muted, this is `Some(volume)` where `volume` is the
|
||||
/// user's intended volume setting, even if the underlying sink's volume is
|
||||
/// 0.
|
||||
pub(crate) managed_volume: Option<f32>,
|
||||
pub(crate) managed_volume: Option<Volume>,
|
||||
}
|
||||
|
||||
impl AudioSink {
|
||||
@ -146,15 +135,16 @@ impl AudioSink {
|
||||
}
|
||||
|
||||
impl AudioSinkPlayback for AudioSink {
|
||||
fn volume(&self) -> f32 {
|
||||
self.managed_volume.unwrap_or_else(|| self.sink.volume())
|
||||
fn volume(&self) -> Volume {
|
||||
self.managed_volume
|
||||
.unwrap_or_else(|| Volume::Linear(self.sink.volume()))
|
||||
}
|
||||
|
||||
fn set_volume(&mut self, volume: f32) {
|
||||
fn set_volume(&mut self, volume: Volume) {
|
||||
if self.is_muted() {
|
||||
self.managed_volume = Some(volume);
|
||||
} else {
|
||||
self.sink.set_volume(volume);
|
||||
self.sink.set_volume(volume.to_linear());
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +187,7 @@ impl AudioSinkPlayback for AudioSink {
|
||||
|
||||
fn unmute(&mut self) {
|
||||
if let Some(volume) = self.managed_volume.take() {
|
||||
self.sink.set_volume(volume);
|
||||
self.sink.set_volume(volume.to_linear());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,7 +217,7 @@ pub struct SpatialAudioSink {
|
||||
/// If the sink is muted, this is `Some(volume)` where `volume` is the
|
||||
/// user's intended volume setting, even if the underlying sink's volume is
|
||||
/// 0.
|
||||
pub(crate) managed_volume: Option<f32>,
|
||||
pub(crate) managed_volume: Option<Volume>,
|
||||
}
|
||||
|
||||
impl SpatialAudioSink {
|
||||
@ -241,15 +231,16 @@ impl SpatialAudioSink {
|
||||
}
|
||||
|
||||
impl AudioSinkPlayback for SpatialAudioSink {
|
||||
fn volume(&self) -> f32 {
|
||||
self.managed_volume.unwrap_or_else(|| self.sink.volume())
|
||||
fn volume(&self) -> Volume {
|
||||
self.managed_volume
|
||||
.unwrap_or_else(|| Volume::Linear(self.sink.volume()))
|
||||
}
|
||||
|
||||
fn set_volume(&mut self, volume: f32) {
|
||||
fn set_volume(&mut self, volume: Volume) {
|
||||
if self.is_muted() {
|
||||
self.managed_volume = Some(volume);
|
||||
} else {
|
||||
self.sink.set_volume(volume);
|
||||
self.sink.set_volume(volume.to_linear());
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,7 +283,7 @@ impl AudioSinkPlayback for SpatialAudioSink {
|
||||
|
||||
fn unmute(&mut self) {
|
||||
if let Some(volume) = self.managed_volume.take() {
|
||||
self.sink.set_volume(volume);
|
||||
self.sink.set_volume(volume.to_linear());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -326,11 +317,11 @@ mod tests {
|
||||
|
||||
fn test_audio_sink_playback<T: AudioSinkPlayback>(mut audio_sink: T) {
|
||||
// Test volume
|
||||
assert_eq!(audio_sink.volume(), 1.0); // default volume
|
||||
audio_sink.set_volume(0.5);
|
||||
assert_eq!(audio_sink.volume(), 0.5);
|
||||
audio_sink.set_volume(1.0);
|
||||
assert_eq!(audio_sink.volume(), 1.0);
|
||||
assert_eq!(audio_sink.volume(), Volume::Linear(1.0)); // default volume
|
||||
audio_sink.set_volume(Volume::Linear(0.5));
|
||||
assert_eq!(audio_sink.volume(), Volume::Linear(0.5));
|
||||
audio_sink.set_volume(Volume::Linear(1.0));
|
||||
assert_eq!(audio_sink.volume(), Volume::Linear(1.0));
|
||||
|
||||
// Test speed
|
||||
assert_eq!(audio_sink.speed(), 1.0); // default speed
|
||||
@ -361,11 +352,11 @@ mod tests {
|
||||
assert!(!audio_sink.is_muted());
|
||||
|
||||
// Test volume with mute
|
||||
audio_sink.set_volume(0.5);
|
||||
audio_sink.set_volume(Volume::Linear(0.5));
|
||||
audio_sink.mute();
|
||||
assert_eq!(audio_sink.volume(), 0.5); // returns managed volume even though sink volume is 0
|
||||
assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); // returns managed volume even though sink volume is 0
|
||||
audio_sink.unmute();
|
||||
assert_eq!(audio_sink.volume(), 0.5); // managed volume is restored
|
||||
assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); // managed volume is restored
|
||||
|
||||
// Test toggle mute
|
||||
audio_sink.toggle_mute();
|
||||
|
@ -1,48 +1,504 @@
|
||||
use bevy_derive::Deref;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::ops;
|
||||
use bevy_reflect::prelude::*;
|
||||
|
||||
/// Use this [`Resource`] to control the global volume of all audio.
|
||||
///
|
||||
/// Note: changing this value will not affect already playing audio.
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Resource, Default)]
|
||||
/// Note: Changing [`GlobalVolume`] does not affect already playing audio.
|
||||
#[derive(Resource, Debug, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Resource, Debug, Default)]
|
||||
pub struct GlobalVolume {
|
||||
/// The global volume of all audio.
|
||||
pub volume: Volume,
|
||||
}
|
||||
|
||||
impl From<Volume> for GlobalVolume {
|
||||
fn from(volume: Volume) -> Self {
|
||||
Self { volume }
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalVolume {
|
||||
/// Create a new [`GlobalVolume`] with the given volume.
|
||||
pub fn new(volume: f32) -> Self {
|
||||
Self {
|
||||
volume: Volume::new(volume),
|
||||
pub fn new(volume: Volume) -> Self {
|
||||
Self { volume }
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Volume`] represents an audio source's volume level.
|
||||
///
|
||||
/// To create a new [`Volume`] from a linear scale value, use
|
||||
/// [`Volume::Linear`].
|
||||
///
|
||||
/// To create a new [`Volume`] from decibels, use [`Volume::Decibels`].
|
||||
#[derive(Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Debug, PartialEq)]
|
||||
pub enum Volume {
|
||||
/// Create a new [`Volume`] from the given volume in linear scale.
|
||||
///
|
||||
/// In a linear scale, the value `1.0` represents the "normal" volume,
|
||||
/// meaning the audio is played at its original level. Values greater than
|
||||
/// `1.0` increase the volume, while values between `0.0` and `1.0` decrease
|
||||
/// the volume. A value of `0.0` effectively mutes the audio.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_audio::Volume;
|
||||
/// # use bevy_math::ops;
|
||||
/// #
|
||||
/// # const EPSILON: f32 = 0.01;
|
||||
///
|
||||
/// let volume = Volume::Linear(0.5);
|
||||
/// assert_eq!(volume.to_linear(), 0.5);
|
||||
/// assert!(ops::abs(volume.to_decibels() - -6.0206) < EPSILON);
|
||||
///
|
||||
/// let volume = Volume::Linear(0.0);
|
||||
/// assert_eq!(volume.to_linear(), 0.0);
|
||||
/// assert_eq!(volume.to_decibels(), f32::NEG_INFINITY);
|
||||
///
|
||||
/// let volume = Volume::Linear(1.0);
|
||||
/// assert_eq!(volume.to_linear(), 1.0);
|
||||
/// assert!(ops::abs(volume.to_decibels() - 0.0) < EPSILON);
|
||||
/// ```
|
||||
Linear(f32),
|
||||
/// Create a new [`Volume`] from the given volume in decibels.
|
||||
///
|
||||
/// In a decibel scale, the value `0.0` represents the "normal" volume,
|
||||
/// meaning the audio is played at its original level. Values greater than
|
||||
/// `0.0` increase the volume, while values less than `0.0` decrease the
|
||||
/// volume. A value of [`f32::NEG_INFINITY`] decibels effectively mutes the
|
||||
/// audio.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_audio::Volume;
|
||||
/// # use bevy_math::ops;
|
||||
/// #
|
||||
/// # const EPSILON: f32 = 0.01;
|
||||
///
|
||||
/// let volume = Volume::Decibels(-5.998);
|
||||
/// assert!(ops::abs(volume.to_linear() - 0.5) < EPSILON);
|
||||
///
|
||||
/// let volume = Volume::Decibels(f32::NEG_INFINITY);
|
||||
/// assert_eq!(volume.to_linear(), 0.0);
|
||||
///
|
||||
/// let volume = Volume::Decibels(0.0);
|
||||
/// assert_eq!(volume.to_linear(), 1.0);
|
||||
///
|
||||
/// let volume = Volume::Decibels(20.0);
|
||||
/// assert_eq!(volume.to_linear(), 10.0);
|
||||
/// ```
|
||||
Decibels(f32),
|
||||
}
|
||||
|
||||
impl Default for Volume {
|
||||
fn default() -> Self {
|
||||
Self::Linear(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Volume {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Volume::{Decibels, Linear};
|
||||
|
||||
match (self, other) {
|
||||
(Linear(a), Linear(b)) => a.abs() == b.abs(),
|
||||
(Decibels(a), Decibels(b)) => a == b,
|
||||
(a, b) => a.to_decibels() == b.to_decibels(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A volume level equivalent to a non-negative float.
|
||||
#[derive(Clone, Copy, Deref, Debug, Reflect)]
|
||||
#[reflect(Debug)]
|
||||
pub struct Volume(pub(crate) f32);
|
||||
impl PartialOrd for Volume {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
use Volume::{Decibels, Linear};
|
||||
|
||||
impl Default for Volume {
|
||||
fn default() -> Self {
|
||||
Self(1.0)
|
||||
Some(match (self, other) {
|
||||
(Linear(a), Linear(b)) => a.abs().total_cmp(&b.abs()),
|
||||
(Decibels(a), Decibels(b)) => a.total_cmp(b),
|
||||
(a, b) => a.to_decibels().total_cmp(&b.to_decibels()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decibels_to_linear(decibels: f32) -> f32 {
|
||||
ops::powf(10.0f32, decibels / 20.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn linear_to_decibels(linear: f32) -> f32 {
|
||||
20.0 * ops::log10(linear.abs())
|
||||
}
|
||||
|
||||
impl Volume {
|
||||
/// Create a new volume level.
|
||||
pub fn new(volume: f32) -> Self {
|
||||
debug_assert!(volume >= 0.0);
|
||||
Self(f32::max(volume, 0.))
|
||||
}
|
||||
/// Get the value of the volume level.
|
||||
pub fn get(&self) -> f32 {
|
||||
self.0
|
||||
/// Returns the volume in linear scale as a float.
|
||||
pub fn to_linear(&self) -> f32 {
|
||||
match self {
|
||||
Self::Linear(v) => v.abs(),
|
||||
Self::Decibels(v) => decibels_to_linear(*v),
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero (silent) volume level
|
||||
pub const ZERO: Self = Volume(0.0);
|
||||
/// Returns the volume in decibels as a float.
|
||||
///
|
||||
/// If the volume is silent / off / muted, i.e. it's underlying linear scale
|
||||
/// is `0.0`, this method returns negative infinity.
|
||||
pub fn to_decibels(&self) -> f32 {
|
||||
match self {
|
||||
Self::Linear(v) => linear_to_decibels(*v),
|
||||
Self::Decibels(v) => *v,
|
||||
}
|
||||
}
|
||||
|
||||
/// The silent volume. Also known as "off" or "muted".
|
||||
pub const SILENT: Self = Volume::Linear(0.0);
|
||||
}
|
||||
|
||||
impl core::ops::Add<Self> for Volume {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
use Volume::{Decibels, Linear};
|
||||
|
||||
match (self, rhs) {
|
||||
(Linear(a), Linear(b)) => Linear(a + b),
|
||||
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
|
||||
decibels_to_linear(a) + decibels_to_linear(b),
|
||||
)),
|
||||
// {Linear, Decibels} favors the left hand side of the operation by
|
||||
// first converting the right hand side to the same type as the left
|
||||
// hand side and then performing the operation.
|
||||
(Linear(..), Decibels(db)) => self + Linear(decibels_to_linear(db)),
|
||||
(Decibels(..), Linear(l)) => self + Decibels(linear_to_decibels(l)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::AddAssign<Self> for Volume {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Sub<Self> for Volume {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
use Volume::{Decibels, Linear};
|
||||
|
||||
match (self, rhs) {
|
||||
(Linear(a), Linear(b)) => Linear(a - b),
|
||||
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
|
||||
decibels_to_linear(a) - decibels_to_linear(b),
|
||||
)),
|
||||
// {Linear, Decibels} favors the left hand side of the operation by
|
||||
// first converting the right hand side to the same type as the left
|
||||
// hand side and then performing the operation.
|
||||
(Linear(..), Decibels(db)) => self - Linear(decibels_to_linear(db)),
|
||||
(Decibels(..), Linear(l)) => self - Decibels(linear_to_decibels(l)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::SubAssign<Self> for Volume {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul<Self> for Volume {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
use Volume::{Decibels, Linear};
|
||||
|
||||
match (self, rhs) {
|
||||
(Linear(a), Linear(b)) => Linear(a * b),
|
||||
(Decibels(a), Decibels(b)) => Decibels(a + b),
|
||||
// {Linear, Decibels} favors the left hand side of the operation by
|
||||
// first converting the right hand side to the same type as the left
|
||||
// hand side and then performing the operation.
|
||||
(Linear(..), Decibels(db)) => self * Linear(decibels_to_linear(db)),
|
||||
(Decibels(..), Linear(l)) => self * Decibels(linear_to_decibels(l)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::MulAssign<Self> for Volume {
|
||||
fn mul_assign(&mut self, rhs: Self) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Div<Self> for Volume {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: Self) -> Self {
|
||||
use Volume::{Decibels, Linear};
|
||||
|
||||
match (self, rhs) {
|
||||
(Linear(a), Linear(b)) => Linear(a / b),
|
||||
(Decibels(a), Decibels(b)) => Decibels(a - b),
|
||||
// {Linear, Decibels} favors the left hand side of the operation by
|
||||
// first converting the right hand side to the same type as the left
|
||||
// hand side and then performing the operation.
|
||||
(Linear(..), Decibels(db)) => self / Linear(decibels_to_linear(db)),
|
||||
(Decibels(..), Linear(l)) => self / Decibels(linear_to_decibels(l)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::DivAssign<Self> for Volume {
|
||||
fn div_assign(&mut self, rhs: Self) {
|
||||
*self = *self / rhs;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Volume::{self, Decibels, Linear};
|
||||
|
||||
/// Based on [Wikipedia's Decibel article].
|
||||
///
|
||||
/// [Wikipedia's Decibel article]: https://web.archive.org/web/20230810185300/https://en.wikipedia.org/wiki/Decibel
|
||||
const DECIBELS_LINEAR_TABLE: [(f32, f32); 27] = [
|
||||
(100., 100000.),
|
||||
(90., 31623.),
|
||||
(80., 10000.),
|
||||
(70., 3162.),
|
||||
(60., 1000.),
|
||||
(50., 316.2),
|
||||
(40., 100.),
|
||||
(30., 31.62),
|
||||
(20., 10.),
|
||||
(10., 3.162),
|
||||
(5.998, 1.995),
|
||||
(3.003, 1.413),
|
||||
(1.002, 1.122),
|
||||
(0., 1.),
|
||||
(-1.002, 0.891),
|
||||
(-3.003, 0.708),
|
||||
(-5.998, 0.501),
|
||||
(-10., 0.3162),
|
||||
(-20., 0.1),
|
||||
(-30., 0.03162),
|
||||
(-40., 0.01),
|
||||
(-50., 0.003162),
|
||||
(-60., 0.001),
|
||||
(-70., 0.0003162),
|
||||
(-80., 0.0001),
|
||||
(-90., 0.00003162),
|
||||
(-100., 0.00001),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn volume_conversion() {
|
||||
for (db, linear) in DECIBELS_LINEAR_TABLE {
|
||||
for volume in [Linear(linear), Decibels(db), Linear(-linear)] {
|
||||
let db_test = volume.to_decibels();
|
||||
let linear_test = volume.to_linear();
|
||||
|
||||
let db_delta = db_test - db;
|
||||
let linear_relative_delta = (linear_test - linear) / linear;
|
||||
|
||||
assert!(
|
||||
db_delta.abs() < 1e-2,
|
||||
"Expected ~{}dB, got {}dB (delta {})",
|
||||
db,
|
||||
db_test,
|
||||
db_delta
|
||||
);
|
||||
assert!(
|
||||
linear_relative_delta.abs() < 1e-3,
|
||||
"Expected ~{}, got {} (relative delta {})",
|
||||
linear,
|
||||
linear_test,
|
||||
linear_relative_delta
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_conversion_special() {
|
||||
assert!(
|
||||
Decibels(f32::INFINITY).to_linear().is_infinite(),
|
||||
"Infinite decibels is equivalent to infinite linear scale"
|
||||
);
|
||||
assert!(
|
||||
Linear(f32::INFINITY).to_decibels().is_infinite(),
|
||||
"Infinite linear scale is equivalent to infinite decibels"
|
||||
);
|
||||
|
||||
assert!(
|
||||
Linear(f32::NEG_INFINITY).to_decibels().is_infinite(),
|
||||
"Negative infinite linear scale is equivalent to infinite decibels"
|
||||
);
|
||||
assert!(
|
||||
Decibels(f32::NEG_INFINITY).to_linear().abs() == 0.0,
|
||||
"Negative infinity decibels is equivalent to zero linear scale"
|
||||
);
|
||||
|
||||
assert!(
|
||||
Linear(0.0).to_decibels().is_infinite(),
|
||||
"Zero linear scale is equivalent to negative infinity decibels"
|
||||
);
|
||||
assert!(
|
||||
Linear(-0.0).to_decibels().is_infinite(),
|
||||
"Negative zero linear scale is equivalent to negative infinity decibels"
|
||||
);
|
||||
|
||||
assert!(
|
||||
Decibels(f32::NAN).to_linear().is_nan(),
|
||||
"NaN decibels is equivalent to NaN linear scale"
|
||||
);
|
||||
assert!(
|
||||
Linear(f32::NAN).to_decibels().is_nan(),
|
||||
"NaN linear scale is equivalent to NaN decibels"
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_approx_eq(a: Volume, b: Volume) {
|
||||
const EPSILON: f32 = 0.0001;
|
||||
|
||||
match (a, b) {
|
||||
(Decibels(a), Decibels(b)) | (Linear(a), Linear(b)) => assert!(
|
||||
(a - b).abs() < EPSILON,
|
||||
"Expected {:?} to be approximately equal to {:?}",
|
||||
a,
|
||||
b
|
||||
),
|
||||
(a, b) => assert!(
|
||||
(a.to_decibels() - b.to_decibels()).abs() < EPSILON,
|
||||
"Expected {:?} to be approximately equal to {:?}",
|
||||
a,
|
||||
b
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_add() {
|
||||
// Linear to Linear.
|
||||
assert_approx_eq(Linear(0.5) + Linear(0.5), Linear(1.0));
|
||||
assert_approx_eq(Linear(0.5) + Linear(0.1), Linear(0.6));
|
||||
assert_approx_eq(Linear(0.5) + Linear(-0.5), Linear(0.0));
|
||||
|
||||
// Decibels to Decibels.
|
||||
assert_approx_eq(Decibels(0.0) + Decibels(0.0), Decibels(6.0206003));
|
||||
assert_approx_eq(Decibels(6.0) + Decibels(6.0), Decibels(12.020599));
|
||||
assert_approx_eq(Decibels(-6.0) + Decibels(-6.0), Decibels(0.020599423));
|
||||
|
||||
// {Linear, Decibels} favors the left hand side of the operation.
|
||||
assert_approx_eq(Linear(0.5) + Decibels(0.0), Linear(1.5));
|
||||
assert_approx_eq(Decibels(0.0) + Linear(0.5), Decibels(3.521825));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_add_assign() {
|
||||
// Linear to Linear.
|
||||
let mut volume = Linear(0.5);
|
||||
volume += Linear(0.5);
|
||||
assert_approx_eq(volume, Linear(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_sub() {
|
||||
// Linear to Linear.
|
||||
assert_approx_eq(Linear(0.5) - Linear(0.5), Linear(0.0));
|
||||
assert_approx_eq(Linear(0.5) - Linear(0.1), Linear(0.4));
|
||||
assert_approx_eq(Linear(0.5) - Linear(-0.5), Linear(1.0));
|
||||
|
||||
// Decibels to Decibels.
|
||||
assert_eq!(Decibels(0.0) - Decibels(0.0), Decibels(f32::NEG_INFINITY));
|
||||
assert_approx_eq(Decibels(6.0) - Decibels(4.0), Decibels(-7.736506));
|
||||
assert_eq!(Decibels(-6.0) - Decibels(-6.0), Decibels(f32::NEG_INFINITY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_sub_assign() {
|
||||
// Linear to Linear.
|
||||
let mut volume = Linear(0.5);
|
||||
volume -= Linear(0.5);
|
||||
assert_approx_eq(volume, Linear(0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_mul() {
|
||||
// Linear to Linear.
|
||||
assert_approx_eq(Linear(0.5) * Linear(0.5), Linear(0.25));
|
||||
assert_approx_eq(Linear(0.5) * Linear(0.1), Linear(0.05));
|
||||
assert_approx_eq(Linear(0.5) * Linear(-0.5), Linear(-0.25));
|
||||
|
||||
// Decibels to Decibels.
|
||||
assert_approx_eq(Decibels(0.0) * Decibels(0.0), Decibels(0.0));
|
||||
assert_approx_eq(Decibels(6.0) * Decibels(6.0), Decibels(12.0));
|
||||
assert_approx_eq(Decibels(-6.0) * Decibels(-6.0), Decibels(-12.0));
|
||||
|
||||
// {Linear, Decibels} favors the left hand side of the operation.
|
||||
assert_approx_eq(Linear(0.5) * Decibels(0.0), Linear(0.5));
|
||||
assert_approx_eq(Decibels(0.0) * Linear(0.501), Decibels(-6.003246));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_mul_assign() {
|
||||
// Linear to Linear.
|
||||
let mut volume = Linear(0.5);
|
||||
volume *= Linear(0.5);
|
||||
assert_approx_eq(volume, Linear(0.25));
|
||||
|
||||
// Decibels to Decibels.
|
||||
let mut volume = Decibels(6.0);
|
||||
volume *= Decibels(6.0);
|
||||
assert_approx_eq(volume, Decibels(12.0));
|
||||
|
||||
// {Linear, Decibels} favors the left hand side of the operation.
|
||||
let mut volume = Linear(0.5);
|
||||
volume *= Decibels(0.0);
|
||||
assert_approx_eq(volume, Linear(0.5));
|
||||
let mut volume = Decibels(0.0);
|
||||
volume *= Linear(0.501);
|
||||
assert_approx_eq(volume, Decibels(-6.003246));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_div() {
|
||||
// Linear to Linear.
|
||||
assert_approx_eq(Linear(0.5) / Linear(0.5), Linear(1.0));
|
||||
assert_approx_eq(Linear(0.5) / Linear(0.1), Linear(5.0));
|
||||
assert_approx_eq(Linear(0.5) / Linear(-0.5), Linear(-1.0));
|
||||
|
||||
// Decibels to Decibels.
|
||||
assert_approx_eq(Decibels(0.0) / Decibels(0.0), Decibels(0.0));
|
||||
assert_approx_eq(Decibels(6.0) / Decibels(6.0), Decibels(0.0));
|
||||
assert_approx_eq(Decibels(-6.0) / Decibels(-6.0), Decibels(0.0));
|
||||
|
||||
// {Linear, Decibels} favors the left hand side of the operation.
|
||||
assert_approx_eq(Linear(0.5) / Decibels(0.0), Linear(0.5));
|
||||
assert_approx_eq(Decibels(0.0) / Linear(0.501), Decibels(6.003246));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_ops_div_assign() {
|
||||
// Linear to Linear.
|
||||
let mut volume = Linear(0.5);
|
||||
volume /= Linear(0.5);
|
||||
assert_approx_eq(volume, Linear(1.0));
|
||||
|
||||
// Decibels to Decibels.
|
||||
let mut volume = Decibels(6.0);
|
||||
volume /= Decibels(6.0);
|
||||
assert_approx_eq(volume, Decibels(0.0));
|
||||
|
||||
// {Linear, Decibels} favors the left hand side of the operation.
|
||||
let mut volume = Linear(0.5);
|
||||
volume /= Decibels(0.0);
|
||||
assert_approx_eq(volume, Linear(0.5));
|
||||
let mut volume = Decibels(0.0);
|
||||
volume /= Linear(0.501);
|
||||
assert_approx_eq(volume, Decibels(6.003246));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! This example illustrates how to load and play an audio file, and control how it's played.
|
||||
|
||||
use bevy::{math::ops, prelude::*};
|
||||
use bevy::{audio::Volume, math::ops, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -78,9 +78,9 @@ fn volume(
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::Equal) {
|
||||
let current_volume = sink.volume();
|
||||
sink.set_volume(current_volume + 0.1);
|
||||
sink.set_volume(current_volume + Volume::Linear(0.1));
|
||||
} else if keyboard_input.just_pressed(KeyCode::Minus) {
|
||||
let current_volume = sink.volume();
|
||||
sink.set_volume(current_volume - 0.1);
|
||||
sink.set_volume(current_volume - Volume::Linear(0.1));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Shows how to create a custom [`Decodable`] type by implementing a Sine wave.
|
||||
|
||||
use bevy::{
|
||||
audio::{AddAudioSource, AudioPlugin, Source},
|
||||
audio::{AddAudioSource, AudioPlugin, Source, Volume},
|
||||
math::ops,
|
||||
prelude::*,
|
||||
reflect::TypePath,
|
||||
@ -86,7 +86,7 @@ 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: GlobalVolume::new(0.2),
|
||||
global_volume: Volume::Linear(0.2).into(),
|
||||
..default()
|
||||
}))
|
||||
.add_audio_source::<SineAudio>()
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! This example illustrates how to load and play different soundtracks,
|
||||
//! transitioning between them as the game state changes.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{audio::Volume, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -82,7 +82,7 @@ fn change_track(
|
||||
AudioPlayer(soundtrack_player.track_list.first().unwrap().clone()),
|
||||
PlaybackSettings {
|
||||
mode: bevy::audio::PlaybackMode::Loop,
|
||||
volume: bevy::audio::Volume::ZERO,
|
||||
volume: Volume::SILENT,
|
||||
..default()
|
||||
},
|
||||
FadeIn,
|
||||
@ -93,7 +93,7 @@ fn change_track(
|
||||
AudioPlayer(soundtrack_player.track_list.get(1).unwrap().clone()),
|
||||
PlaybackSettings {
|
||||
mode: bevy::audio::PlaybackMode::Loop,
|
||||
volume: bevy::audio::Volume::ZERO,
|
||||
volume: Volume::SILENT,
|
||||
..default()
|
||||
},
|
||||
FadeIn,
|
||||
@ -115,9 +115,9 @@ fn fade_in(
|
||||
) {
|
||||
for (mut audio, entity) in audio_sink.iter_mut() {
|
||||
let current_volume = audio.volume();
|
||||
audio.set_volume(current_volume + time.delta_secs() / FADE_TIME);
|
||||
if audio.volume() >= 1.0 {
|
||||
audio.set_volume(1.0);
|
||||
audio.set_volume(current_volume + Volume::Linear(time.delta_secs() / FADE_TIME));
|
||||
if audio.volume().to_linear() >= 1.0 {
|
||||
audio.set_volume(Volume::Linear(1.0));
|
||||
commands.entity(entity).remove::<FadeIn>();
|
||||
}
|
||||
}
|
||||
@ -132,8 +132,8 @@ fn fade_out(
|
||||
) {
|
||||
for (mut audio, entity) in audio_sink.iter_mut() {
|
||||
let current_volume = audio.volume();
|
||||
audio.set_volume(current_volume - time.delta_secs() / FADE_TIME);
|
||||
if audio.volume() <= 0.0 {
|
||||
audio.set_volume(current_volume - Volume::Linear(time.delta_secs() / FADE_TIME));
|
||||
if audio.volume().to_linear() <= 0.0 {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user