1use bevy_ecs::prelude::*;
2use bevy_math::ops;
3use bevy_reflect::prelude::*;
4
5#[derive(Resource, Debug, Default, Clone, Copy, Reflect)]
9#[reflect(Resource, Debug, Default, Clone)]
10pub struct GlobalVolume {
11 pub volume: Volume,
13}
14
15impl From<Volume> for GlobalVolume {
16 fn from(volume: Volume) -> Self {
17 Self { volume }
18 }
19}
20
21impl GlobalVolume {
22 pub fn new(volume: Volume) -> Self {
24 Self { volume }
25 }
26}
27
28#[derive(Clone, Copy, Debug, Reflect)]
35#[reflect(Clone, Debug, PartialEq)]
36pub enum Volume {
37 Linear(f32),
65 Decibels(f32),
94}
95
96impl Default for Volume {
97 fn default() -> Self {
98 Self::Linear(1.0)
99 }
100}
101
102impl 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
114impl 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]
127fn decibels_to_linear(decibels: f32) -> f32 {
128 ops::powf(10.0f32, decibels / 20.0)
129}
130
131#[inline]
132fn linear_to_decibels(linear: f32) -> f32 {
133 20.0 * ops::log10(linear.abs())
134}
135
136impl Volume {
137 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 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 pub const SILENT: Self = Volume::Linear(0.0);
158
159 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 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 pub fn scale_to_factor(&self, factor: f32) -> Self {
219 Volume::Linear(self.to_linear() * factor)
220 }
221
222 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
250impl 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(db)) => self * Linear(decibels_to_linear(db)),
263 (Decibels(..), Linear(l)) => self * Decibels(linear_to_decibels(l)),
264 }
265 }
266}
267
268impl core::ops::MulAssign<Self> for Volume {
269 fn mul_assign(&mut self, rhs: Self) {
270 *self = *self * rhs;
271 }
272}
273
274impl 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(db)) => self / Linear(decibels_to_linear(db)),
287 (Decibels(..), Linear(l)) => self / Decibels(linear_to_decibels(l)),
288 }
289 }
290}
291
292impl core::ops::DivAssign<Self> for Volume {
293 fn div_assign(&mut self, rhs: Self) {
294 *self = *self / rhs;
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::Volume::{self, Decibels, Linear};
301
302 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 let increased = volume.increase_by_percentage(100.0);
403 assert_eq!(increased.to_linear(), 2.0);
404
405 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 let decreased = volume.decrease_by_percentage(50.0);
416 assert_eq!(decreased.to_linear(), 0.5);
417
418 let decreased = volume.decrease_by_percentage(25.0);
420 assert_eq!(decreased.to_linear(), 0.75);
421
422 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 let faded = current.fade_towards(target, 0.5);
441 assert_eq!(faded.to_linear(), 0.5);
442
443 let faded = current.fade_towards(target, 0.0);
445 assert_eq!(faded.to_linear(), 1.0);
446
447 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 let adjusted = volume * Decibels(20.0);
458 assert_approx_eq(adjusted, Linear(10.0));
459
460 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 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 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 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 let mut volume = Linear(0.5);
501 volume *= Linear(0.5);
502 assert_approx_eq(volume, Linear(0.25));
503
504 let mut volume = Decibels(6.0);
506 volume *= Decibels(6.0);
507 assert_approx_eq(volume, Decibels(12.0));
508
509 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 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 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 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 let mut volume = Linear(0.5);
539 volume /= Linear(0.5);
540 assert_approx_eq(volume, Linear(1.0));
541
542 let mut volume = Decibels(6.0);
544 volume /= Decibels(6.0);
545 assert_approx_eq(volume, Decibels(0.0));
546
547 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}