Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_audio/src/volume.rs
6598 views
1
use bevy_ecs::prelude::*;
2
use bevy_math::ops;
3
use bevy_reflect::prelude::*;
4
5
/// Use this [`Resource`] to control the global volume of all audio.
6
///
7
/// Note: Changing [`GlobalVolume`] does not affect already playing audio.
8
#[derive(Resource, Debug, Default, Clone, Copy, Reflect)]
9
#[reflect(Resource, Debug, Default, Clone)]
10
pub struct GlobalVolume {
11
/// The global volume of all audio.
12
pub volume: Volume,
13
}
14
15
impl From<Volume> for GlobalVolume {
16
fn from(volume: Volume) -> Self {
17
Self { volume }
18
}
19
}
20
21
impl GlobalVolume {
22
/// Create a new [`GlobalVolume`] with the given volume.
23
pub fn new(volume: Volume) -> Self {
24
Self { volume }
25
}
26
}
27
28
/// A [`Volume`] represents an audio source's volume level.
29
///
30
/// To create a new [`Volume`] from a linear scale value, use
31
/// [`Volume::Linear`].
32
///
33
/// To create a new [`Volume`] from decibels, use [`Volume::Decibels`].
34
#[derive(Clone, Copy, Debug, Reflect)]
35
#[reflect(Clone, Debug, PartialEq)]
36
pub enum Volume {
37
/// Create a new [`Volume`] from the given volume in the linear scale.
38
///
39
/// In a linear scale, the value `1.0` represents the "normal" volume,
40
/// meaning the audio is played at its original level. Values greater than
41
/// `1.0` increase the volume, while values between `0.0` and `1.0` decrease
42
/// the volume. A value of `0.0` effectively mutes the audio.
43
///
44
/// # Examples
45
///
46
/// ```
47
/// # use bevy_audio::Volume;
48
/// # use bevy_math::ops;
49
/// #
50
/// # const EPSILON: f32 = 0.01;
51
///
52
/// let volume = Volume::Linear(0.5);
53
/// assert_eq!(volume.to_linear(), 0.5);
54
/// assert!(ops::abs(volume.to_decibels() - -6.0206) < EPSILON);
55
///
56
/// let volume = Volume::Linear(0.0);
57
/// assert_eq!(volume.to_linear(), 0.0);
58
/// assert_eq!(volume.to_decibels(), f32::NEG_INFINITY);
59
///
60
/// let volume = Volume::Linear(1.0);
61
/// assert_eq!(volume.to_linear(), 1.0);
62
/// assert!(ops::abs(volume.to_decibels() - 0.0) < EPSILON);
63
/// ```
64
Linear(f32),
65
/// Create a new [`Volume`] from the given volume in decibels.
66
///
67
/// In a decibel scale, the value `0.0` represents the "normal" volume,
68
/// meaning the audio is played at its original level. Values greater than
69
/// `0.0` increase the volume, while values less than `0.0` decrease the
70
/// volume. A value of [`f32::NEG_INFINITY`] decibels effectively mutes the
71
/// audio.
72
///
73
/// # Examples
74
///
75
/// ```
76
/// # use bevy_audio::Volume;
77
/// # use bevy_math::ops;
78
/// #
79
/// # const EPSILON: f32 = 0.01;
80
///
81
/// let volume = Volume::Decibels(-5.998);
82
/// assert!(ops::abs(volume.to_linear() - 0.5) < EPSILON);
83
///
84
/// let volume = Volume::Decibels(f32::NEG_INFINITY);
85
/// assert_eq!(volume.to_linear(), 0.0);
86
///
87
/// let volume = Volume::Decibels(0.0);
88
/// assert_eq!(volume.to_linear(), 1.0);
89
///
90
/// let volume = Volume::Decibels(20.0);
91
/// assert_eq!(volume.to_linear(), 10.0);
92
/// ```
93
Decibels(f32),
94
}
95
96
impl Default for Volume {
97
fn default() -> Self {
98
Self::Linear(1.0)
99
}
100
}
101
102
impl PartialEq for Volume {
103
fn eq(&self, other: &Self) -> bool {
104
use Volume::{Decibels, Linear};
105
106
match (self, other) {
107
(Linear(a), Linear(b)) => a.abs() == b.abs(),
108
(Decibels(a), Decibels(b)) => a == b,
109
(a, b) => a.to_decibels() == b.to_decibels(),
110
}
111
}
112
}
113
114
impl PartialOrd for Volume {
115
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
116
use Volume::{Decibels, Linear};
117
118
Some(match (self, other) {
119
(Linear(a), Linear(b)) => a.abs().total_cmp(&b.abs()),
120
(Decibels(a), Decibels(b)) => a.total_cmp(b),
121
(a, b) => a.to_decibels().total_cmp(&b.to_decibels()),
122
})
123
}
124
}
125
126
#[inline]
127
fn decibels_to_linear(decibels: f32) -> f32 {
128
ops::powf(10.0f32, decibels / 20.0)
129
}
130
131
#[inline]
132
fn linear_to_decibels(linear: f32) -> f32 {
133
20.0 * ops::log10(linear.abs())
134
}
135
136
impl Volume {
137
/// Returns the volume in linear scale as a float.
138
pub fn to_linear(&self) -> f32 {
139
match self {
140
Self::Linear(v) => v.abs(),
141
Self::Decibels(v) => decibels_to_linear(*v),
142
}
143
}
144
145
/// Returns the volume in decibels as a float.
146
///
147
/// If the volume is silent / off / muted, i.e., its underlying linear scale
148
/// is `0.0`, this method returns negative infinity.
149
pub fn to_decibels(&self) -> f32 {
150
match self {
151
Self::Linear(v) => linear_to_decibels(*v),
152
Self::Decibels(v) => *v,
153
}
154
}
155
156
/// The silent volume. Also known as "off" or "muted".
157
pub const SILENT: Self = Volume::Linear(0.0);
158
159
/// Increases the volume by the specified percentage.
160
///
161
/// This method works in the linear domain, where a 100% increase
162
/// means doubling the volume (equivalent to +6.02dB).
163
///
164
/// # Arguments
165
/// * `percentage` - The percentage to increase (50.0 means 50% increase)
166
///
167
/// # Examples
168
/// ```
169
/// use bevy_audio::Volume;
170
///
171
/// let volume = Volume::Linear(1.0);
172
/// let increased = volume.increase_by_percentage(100.0);
173
/// assert_eq!(increased.to_linear(), 2.0);
174
/// ```
175
pub fn increase_by_percentage(&self, percentage: f32) -> Self {
176
let factor = 1.0 + (percentage / 100.0);
177
Volume::Linear(self.to_linear() * factor)
178
}
179
180
/// Decreases the volume by the specified percentage.
181
///
182
/// This method works in the linear domain, where a 50% decrease
183
/// means halving the volume (equivalent to -6.02dB).
184
///
185
/// # Arguments
186
/// * `percentage` - The percentage to decrease (50.0 means 50% decrease)
187
///
188
/// # Examples
189
/// ```
190
/// use bevy_audio::Volume;
191
///
192
/// let volume = Volume::Linear(1.0);
193
/// let decreased = volume.decrease_by_percentage(50.0);
194
/// assert_eq!(decreased.to_linear(), 0.5);
195
/// ```
196
pub fn decrease_by_percentage(&self, percentage: f32) -> Self {
197
let factor = 1.0 - (percentage / 100.0).clamp(0.0, 1.0);
198
Volume::Linear(self.to_linear() * factor)
199
}
200
201
/// Scales the volume to a specific linear factor relative to the current volume.
202
///
203
/// This is different from `adjust_by_linear` as it sets the volume to be
204
/// exactly the factor times the original volume, rather than applying
205
/// the factor to the current volume.
206
///
207
/// # Arguments
208
/// * `factor` - The scaling factor (2.0 = twice as loud, 0.5 = half as loud)
209
///
210
/// # Examples
211
/// ```
212
/// use bevy_audio::Volume;
213
///
214
/// let volume = Volume::Linear(0.8);
215
/// let scaled = volume.scale_to_factor(1.25);
216
/// assert_eq!(scaled.to_linear(), 1.0);
217
/// ```
218
pub fn scale_to_factor(&self, factor: f32) -> Self {
219
Volume::Linear(self.to_linear() * factor)
220
}
221
222
/// Creates a fade effect by interpolating between current volume and target volume.
223
///
224
/// This method performs linear interpolation in the linear domain, which
225
/// provides a more natural-sounding fade effect.
226
///
227
/// # Arguments
228
/// * `target` - The target volume to fade towards
229
/// * `factor` - The interpolation factor (0.0 = current volume, 1.0 = target volume)
230
///
231
/// # Examples
232
/// ```
233
/// use bevy_audio::Volume;
234
///
235
/// let current = Volume::Linear(1.0);
236
/// let target = Volume::Linear(0.0);
237
/// let faded = current.fade_towards(target, 0.5);
238
/// assert_eq!(faded.to_linear(), 0.5);
239
/// ```
240
pub fn fade_towards(&self, target: Volume, factor: f32) -> Self {
241
let current_linear = self.to_linear();
242
let target_linear = target.to_linear();
243
let factor_clamped = factor.clamp(0.0, 1.0);
244
245
let interpolated = current_linear + (target_linear - current_linear) * factor_clamped;
246
Volume::Linear(interpolated)
247
}
248
}
249
250
impl core::ops::Mul<Self> for Volume {
251
type Output = Self;
252
253
fn mul(self, rhs: Self) -> Self {
254
use Volume::{Decibels, Linear};
255
256
match (self, rhs) {
257
(Linear(a), Linear(b)) => Linear(a * b),
258
(Decibels(a), Decibels(b)) => Decibels(a + b),
259
// {Linear, Decibels} favors the left hand side of the operation by
260
// first converting the right hand side to the same type as the left
261
// hand side and then performing the operation.
262
(Linear(..), Decibels(db)) => self * Linear(decibels_to_linear(db)),
263
(Decibels(..), Linear(l)) => self * Decibels(linear_to_decibels(l)),
264
}
265
}
266
}
267
268
impl core::ops::MulAssign<Self> for Volume {
269
fn mul_assign(&mut self, rhs: Self) {
270
*self = *self * rhs;
271
}
272
}
273
274
impl core::ops::Div<Self> for Volume {
275
type Output = Self;
276
277
fn div(self, rhs: Self) -> Self {
278
use Volume::{Decibels, Linear};
279
280
match (self, rhs) {
281
(Linear(a), Linear(b)) => Linear(a / b),
282
(Decibels(a), Decibels(b)) => Decibels(a - b),
283
// {Linear, Decibels} favors the left hand side of the operation by
284
// first converting the right hand side to the same type as the left
285
// hand side and then performing the operation.
286
(Linear(..), Decibels(db)) => self / Linear(decibels_to_linear(db)),
287
(Decibels(..), Linear(l)) => self / Decibels(linear_to_decibels(l)),
288
}
289
}
290
}
291
292
impl core::ops::DivAssign<Self> for Volume {
293
fn div_assign(&mut self, rhs: Self) {
294
*self = *self / rhs;
295
}
296
}
297
298
#[cfg(test)]
299
mod tests {
300
use super::Volume::{self, Decibels, Linear};
301
302
/// Based on [Wikipedia's Decibel article].
303
///
304
/// [Wikipedia's Decibel article]: https://web.archive.org/web/20230810185300/https://en.wikipedia.org/wiki/Decibel
305
const DECIBELS_LINEAR_TABLE: [(f32, f32); 27] = [
306
(100., 100000.),
307
(90., 31623.),
308
(80., 10000.),
309
(70., 3162.),
310
(60., 1000.),
311
(50., 316.2),
312
(40., 100.),
313
(30., 31.62),
314
(20., 10.),
315
(10., 3.162),
316
(5.998, 1.995),
317
(3.003, 1.413),
318
(1.002, 1.122),
319
(0., 1.),
320
(-1.002, 0.891),
321
(-3.003, 0.708),
322
(-5.998, 0.501),
323
(-10., 0.3162),
324
(-20., 0.1),
325
(-30., 0.03162),
326
(-40., 0.01),
327
(-50., 0.003162),
328
(-60., 0.001),
329
(-70., 0.0003162),
330
(-80., 0.0001),
331
(-90., 0.00003162),
332
(-100., 0.00001),
333
];
334
335
#[test]
336
fn volume_conversion() {
337
for (db, linear) in DECIBELS_LINEAR_TABLE {
338
for volume in [Linear(linear), Decibels(db), Linear(-linear)] {
339
let db_test = volume.to_decibels();
340
let linear_test = volume.to_linear();
341
342
let db_delta = db_test - db;
343
let linear_relative_delta = (linear_test - linear) / linear;
344
345
assert!(
346
db_delta.abs() < 1e-2,
347
"Expected ~{db}dB, got {db_test}dB (delta {db_delta})",
348
);
349
assert!(
350
linear_relative_delta.abs() < 1e-3,
351
"Expected ~{linear}, got {linear_test} (relative delta {linear_relative_delta})",
352
);
353
}
354
}
355
}
356
357
#[test]
358
fn volume_conversion_special() {
359
assert!(
360
Decibels(f32::INFINITY).to_linear().is_infinite(),
361
"Infinite decibels is equivalent to infinite linear scale"
362
);
363
assert!(
364
Linear(f32::INFINITY).to_decibels().is_infinite(),
365
"Infinite linear scale is equivalent to infinite decibels"
366
);
367
368
assert!(
369
Linear(f32::NEG_INFINITY).to_decibels().is_infinite(),
370
"Negative infinite linear scale is equivalent to infinite decibels"
371
);
372
assert_eq!(
373
Decibels(f32::NEG_INFINITY).to_linear().abs(),
374
0.0,
375
"Negative infinity decibels is equivalent to zero linear scale"
376
);
377
378
assert!(
379
Linear(0.0).to_decibels().is_infinite(),
380
"Zero linear scale is equivalent to negative infinity decibels"
381
);
382
assert!(
383
Linear(-0.0).to_decibels().is_infinite(),
384
"Negative zero linear scale is equivalent to negative infinity decibels"
385
);
386
387
assert!(
388
Decibels(f32::NAN).to_linear().is_nan(),
389
"NaN decibels is equivalent to NaN linear scale"
390
);
391
assert!(
392
Linear(f32::NAN).to_decibels().is_nan(),
393
"NaN linear scale is equivalent to NaN decibels"
394
);
395
}
396
397
#[test]
398
fn test_increase_by_percentage() {
399
let volume = Linear(1.0);
400
401
// 100% increase should double the volume
402
let increased = volume.increase_by_percentage(100.0);
403
assert_eq!(increased.to_linear(), 2.0);
404
405
// 50% increase
406
let increased = volume.increase_by_percentage(50.0);
407
assert_eq!(increased.to_linear(), 1.5);
408
}
409
410
#[test]
411
fn test_decrease_by_percentage() {
412
let volume = Linear(1.0);
413
414
// 50% decrease should halve the volume
415
let decreased = volume.decrease_by_percentage(50.0);
416
assert_eq!(decreased.to_linear(), 0.5);
417
418
// 25% decrease
419
let decreased = volume.decrease_by_percentage(25.0);
420
assert_eq!(decreased.to_linear(), 0.75);
421
422
// 100% decrease should result in silence
423
let decreased = volume.decrease_by_percentage(100.0);
424
assert_eq!(decreased.to_linear(), 0.0);
425
}
426
427
#[test]
428
fn test_scale_to_factor() {
429
let volume = Linear(0.8);
430
let scaled = volume.scale_to_factor(1.25);
431
assert_eq!(scaled.to_linear(), 1.0);
432
}
433
434
#[test]
435
fn test_fade_towards() {
436
let current = Linear(1.0);
437
let target = Linear(0.0);
438
439
// 50% fade should result in 0.5 linear volume
440
let faded = current.fade_towards(target, 0.5);
441
assert_eq!(faded.to_linear(), 0.5);
442
443
// 0% fade should keep current volume
444
let faded = current.fade_towards(target, 0.0);
445
assert_eq!(faded.to_linear(), 1.0);
446
447
// 100% fade should reach target volume
448
let faded = current.fade_towards(target, 1.0);
449
assert_eq!(faded.to_linear(), 0.0);
450
}
451
452
#[test]
453
fn test_decibel_math_properties() {
454
let volume = Linear(1.0);
455
456
// Adding 20dB should multiply linear volume by 10
457
let adjusted = volume * Decibels(20.0);
458
assert_approx_eq(adjusted, Linear(10.0));
459
460
// Subtracting 20dB should divide linear volume by 10
461
let adjusted = volume / Decibels(20.0);
462
assert_approx_eq(adjusted, Linear(0.1));
463
}
464
465
fn assert_approx_eq(a: Volume, b: Volume) {
466
const EPSILON: f32 = 0.0001;
467
468
match (a, b) {
469
(Decibels(a), Decibels(b)) | (Linear(a), Linear(b)) => assert!(
470
(a - b).abs() < EPSILON,
471
"Expected {a:?} to be approximately equal to {b:?}",
472
),
473
(a, b) => assert!(
474
(a.to_decibels() - b.to_decibels()).abs() < EPSILON,
475
"Expected {a:?} to be approximately equal to {b:?}",
476
),
477
}
478
}
479
480
#[test]
481
fn volume_ops_mul() {
482
// Linear to Linear.
483
assert_approx_eq(Linear(0.5) * Linear(0.5), Linear(0.25));
484
assert_approx_eq(Linear(0.5) * Linear(0.1), Linear(0.05));
485
assert_approx_eq(Linear(0.5) * Linear(-0.5), Linear(-0.25));
486
487
// Decibels to Decibels.
488
assert_approx_eq(Decibels(0.0) * Decibels(0.0), Decibels(0.0));
489
assert_approx_eq(Decibels(6.0) * Decibels(6.0), Decibels(12.0));
490
assert_approx_eq(Decibels(-6.0) * Decibels(-6.0), Decibels(-12.0));
491
492
// {Linear, Decibels} favors the left hand side of the operation.
493
assert_approx_eq(Linear(0.5) * Decibels(0.0), Linear(0.5));
494
assert_approx_eq(Decibels(0.0) * Linear(0.501), Decibels(-6.003246));
495
}
496
497
#[test]
498
fn volume_ops_mul_assign() {
499
// Linear to Linear.
500
let mut volume = Linear(0.5);
501
volume *= Linear(0.5);
502
assert_approx_eq(volume, Linear(0.25));
503
504
// Decibels to Decibels.
505
let mut volume = Decibels(6.0);
506
volume *= Decibels(6.0);
507
assert_approx_eq(volume, Decibels(12.0));
508
509
// {Linear, Decibels} favors the left hand side of the operation.
510
let mut volume = Linear(0.5);
511
volume *= Decibels(0.0);
512
assert_approx_eq(volume, Linear(0.5));
513
let mut volume = Decibels(0.0);
514
volume *= Linear(0.501);
515
assert_approx_eq(volume, Decibels(-6.003246));
516
}
517
518
#[test]
519
fn volume_ops_div() {
520
// Linear to Linear.
521
assert_approx_eq(Linear(0.5) / Linear(0.5), Linear(1.0));
522
assert_approx_eq(Linear(0.5) / Linear(0.1), Linear(5.0));
523
assert_approx_eq(Linear(0.5) / Linear(-0.5), Linear(-1.0));
524
525
// Decibels to Decibels.
526
assert_approx_eq(Decibels(0.0) / Decibels(0.0), Decibels(0.0));
527
assert_approx_eq(Decibels(6.0) / Decibels(6.0), Decibels(0.0));
528
assert_approx_eq(Decibels(-6.0) / Decibels(-6.0), Decibels(0.0));
529
530
// {Linear, Decibels} favors the left hand side of the operation.
531
assert_approx_eq(Linear(0.5) / Decibels(0.0), Linear(0.5));
532
assert_approx_eq(Decibels(0.0) / Linear(0.501), Decibels(6.003246));
533
}
534
535
#[test]
536
fn volume_ops_div_assign() {
537
// Linear to Linear.
538
let mut volume = Linear(0.5);
539
volume /= Linear(0.5);
540
assert_approx_eq(volume, Linear(1.0));
541
542
// Decibels to Decibels.
543
let mut volume = Decibels(6.0);
544
volume /= Decibels(6.0);
545
assert_approx_eq(volume, Decibels(0.0));
546
547
// {Linear, Decibels} favors the left hand side of the operation.
548
let mut volume = Linear(0.5);
549
volume /= Decibels(0.0);
550
assert_approx_eq(volume, Linear(0.5));
551
let mut volume = Decibels(0.0);
552
volume /= Linear(0.501);
553
assert_approx_eq(volume, Decibels(6.003246));
554
}
555
}
556
557