@@ -34,7 +34,7 @@ impl GlobalVolume {
34
34
#[ derive( Clone , Copy , Debug , Reflect ) ]
35
35
#[ reflect( Clone , Debug , PartialEq ) ]
36
36
pub enum Volume {
37
- /// Create a new [`Volume`] from the given volume in linear scale.
37
+ /// Create a new [`Volume`] from the given volume in the linear scale.
38
38
///
39
39
/// In a linear scale, the value `1.0` represents the "normal" volume,
40
40
/// meaning the audio is played at its original level. Values greater than
@@ -144,7 +144,7 @@ impl Volume {
144
144
145
145
/// Returns the volume in decibels as a float.
146
146
///
147
- /// If the volume is silent / off / muted, i.e. its underlying linear scale
147
+ /// If the volume is silent / off / muted, i.e., its underlying linear scale
148
148
/// is `0.0`, this method returns negative infinity.
149
149
pub fn to_decibels ( & self ) -> f32 {
150
150
match self {
@@ -155,57 +155,95 @@ impl Volume {
155
155
156
156
/// The silent volume. Also known as "off" or "muted".
157
157
pub const SILENT : Self = Volume :: Linear ( 0.0 ) ;
158
- }
159
-
160
- impl core:: ops:: Add < Self > for Volume {
161
- type Output = Self ;
162
158
163
- fn add ( self , rhs : Self ) -> Self {
164
- use Volume :: { Decibels , Linear } ;
165
-
166
- match ( self , rhs) {
167
- ( Linear ( a) , Linear ( b) ) => Linear ( a + b) ,
168
- ( Decibels ( a) , Decibels ( b) ) => Decibels ( linear_to_decibels (
169
- decibels_to_linear ( a) + decibels_to_linear ( b) ,
170
- ) ) ,
171
- // {Linear, Decibels} favors the left hand side of the operation by
172
- // first converting the right hand side to the same type as the left
173
- // hand side and then performing the operation.
174
- ( Linear ( ..) , Decibels ( db) ) => self + Linear ( decibels_to_linear ( db) ) ,
175
- ( Decibels ( ..) , Linear ( l) ) => self + Decibels ( linear_to_decibels ( l) ) ,
176
- }
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)
177
178
}
178
- }
179
179
180
- impl core:: ops:: AddAssign < Self > for Volume {
181
- fn add_assign ( & mut self , rhs : Self ) {
182
- * self = * self + rhs;
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)
183
199
}
184
- }
185
-
186
- impl core:: ops:: Sub < Self > for Volume {
187
- type Output = Self ;
188
200
189
- fn sub ( self , rhs : Self ) -> Self {
190
- use Volume :: { Decibels , Linear } ;
191
-
192
- match ( self , rhs) {
193
- ( Linear ( a) , Linear ( b) ) => Linear ( a - b) ,
194
- ( Decibels ( a) , Decibels ( b) ) => Decibels ( linear_to_decibels (
195
- decibels_to_linear ( a) - decibels_to_linear ( b) ,
196
- ) ) ,
197
- // {Linear, Decibels} favors the left hand side of the operation by
198
- // first converting the right hand side to the same type as the left
199
- // hand side and then performing the operation.
200
- ( Linear ( ..) , Decibels ( db) ) => self - Linear ( decibels_to_linear ( db) ) ,
201
- ( Decibels ( ..) , Linear ( l) ) => self - Decibels ( linear_to_decibels ( l) ) ,
202
- }
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)
203
220
}
204
- }
205
221
206
- impl core:: ops:: SubAssign < Self > for Volume {
207
- fn sub_assign ( & mut self , rhs : Self ) {
208
- * self = * self - rhs;
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)
209
247
}
210
248
}
211
249
@@ -337,8 +375,9 @@ mod tests {
337
375
Linear ( f32 :: NEG_INFINITY ) . to_decibels( ) . is_infinite( ) ,
338
376
"Negative infinite linear scale is equivalent to infinite decibels"
339
377
) ;
340
- assert ! (
341
- Decibels ( f32 :: NEG_INFINITY ) . to_linear( ) . abs( ) == 0.0 ,
378
+ assert_eq ! (
379
+ Decibels ( f32 :: NEG_INFINITY ) . to_linear( ) . abs( ) ,
380
+ 0.0 ,
342
381
"Negative infinity decibels is equivalent to zero linear scale"
343
382
) ;
344
383
@@ -361,6 +400,74 @@ mod tests {
361
400
) ;
362
401
}
363
402
403
+ #[ test]
404
+ fn test_increase_by_percentage ( ) {
405
+ let volume = Linear ( 1.0 ) ;
406
+
407
+ // 100% increase should double the volume
408
+ let increased = volume. increase_by_percentage ( 100.0 ) ;
409
+ assert_eq ! ( increased. to_linear( ) , 2.0 ) ;
410
+
411
+ // 50% increase
412
+ let increased = volume. increase_by_percentage ( 50.0 ) ;
413
+ assert_eq ! ( increased. to_linear( ) , 1.5 ) ;
414
+ }
415
+
416
+ #[ test]
417
+ fn test_decrease_by_percentage ( ) {
418
+ let volume = Linear ( 1.0 ) ;
419
+
420
+ // 50% decrease should halve the volume
421
+ let decreased = volume. decrease_by_percentage ( 50.0 ) ;
422
+ assert_eq ! ( decreased. to_linear( ) , 0.5 ) ;
423
+
424
+ // 25% decrease
425
+ let decreased = volume. decrease_by_percentage ( 25.0 ) ;
426
+ assert_eq ! ( decreased. to_linear( ) , 0.75 ) ;
427
+
428
+ // 100% decrease should result in silence
429
+ let decreased = volume. decrease_by_percentage ( 100.0 ) ;
430
+ assert_eq ! ( decreased. to_linear( ) , 0.0 ) ;
431
+ }
432
+
433
+ #[ test]
434
+ fn test_scale_to_factor ( ) {
435
+ let volume = Linear ( 0.8 ) ;
436
+ let scaled = volume. scale_to_factor ( 1.25 ) ;
437
+ assert_eq ! ( scaled. to_linear( ) , 1.0 ) ;
438
+ }
439
+
440
+ #[ test]
441
+ fn test_fade_towards ( ) {
442
+ let current = Linear ( 1.0 ) ;
443
+ let target = Linear ( 0.0 ) ;
444
+
445
+ // 50% fade should result in 0.5 linear volume
446
+ let faded = current. fade_towards ( target, 0.5 ) ;
447
+ assert_eq ! ( faded. to_linear( ) , 0.5 ) ;
448
+
449
+ // 0% fade should keep current volume
450
+ let faded = current. fade_towards ( target, 0.0 ) ;
451
+ assert_eq ! ( faded. to_linear( ) , 1.0 ) ;
452
+
453
+ // 100% fade should reach target volume
454
+ let faded = current. fade_towards ( target, 1.0 ) ;
455
+ assert_eq ! ( faded. to_linear( ) , 0.0 ) ;
456
+ }
457
+
458
+ #[ test]
459
+ fn test_decibel_math_properties ( ) {
460
+ let volume = Linear ( 1.0 ) ;
461
+
462
+ // Adding 20dB should multiply linear volume by 10
463
+ let adjusted = volume * Decibels ( 20.0 ) ;
464
+ assert_approx_eq ( adjusted, Linear ( 10.0 ) ) ;
465
+
466
+ // Subtracting 20dB should divide linear volume by 10
467
+ let adjusted = volume / Decibels ( 20.0 ) ;
468
+ assert_approx_eq ( adjusted, Linear ( 0.1 ) ) ;
469
+ }
470
+
364
471
fn assert_approx_eq ( a : Volume , b : Volume ) {
365
472
const EPSILON : f32 = 0.0001 ;
366
473
@@ -380,52 +487,6 @@ mod tests {
380
487
}
381
488
}
382
489
383
- #[ test]
384
- fn volume_ops_add ( ) {
385
- // Linear to Linear.
386
- assert_approx_eq ( Linear ( 0.5 ) + Linear ( 0.5 ) , Linear ( 1.0 ) ) ;
387
- assert_approx_eq ( Linear ( 0.5 ) + Linear ( 0.1 ) , Linear ( 0.6 ) ) ;
388
- assert_approx_eq ( Linear ( 0.5 ) + Linear ( -0.5 ) , Linear ( 0.0 ) ) ;
389
-
390
- // Decibels to Decibels.
391
- assert_approx_eq ( Decibels ( 0.0 ) + Decibels ( 0.0 ) , Decibels ( 6.0206003 ) ) ;
392
- assert_approx_eq ( Decibels ( 6.0 ) + Decibels ( 6.0 ) , Decibels ( 12.020599 ) ) ;
393
- assert_approx_eq ( Decibels ( -6.0 ) + Decibels ( -6.0 ) , Decibels ( 0.020599423 ) ) ;
394
-
395
- // {Linear, Decibels} favors the left hand side of the operation.
396
- assert_approx_eq ( Linear ( 0.5 ) + Decibels ( 0.0 ) , Linear ( 1.5 ) ) ;
397
- assert_approx_eq ( Decibels ( 0.0 ) + Linear ( 0.5 ) , Decibels ( 3.521825 ) ) ;
398
- }
399
-
400
- #[ test]
401
- fn volume_ops_add_assign ( ) {
402
- // Linear to Linear.
403
- let mut volume = Linear ( 0.5 ) ;
404
- volume += Linear ( 0.5 ) ;
405
- assert_approx_eq ( volume, Linear ( 1.0 ) ) ;
406
- }
407
-
408
- #[ test]
409
- fn volume_ops_sub ( ) {
410
- // Linear to Linear.
411
- assert_approx_eq ( Linear ( 0.5 ) - Linear ( 0.5 ) , Linear ( 0.0 ) ) ;
412
- assert_approx_eq ( Linear ( 0.5 ) - Linear ( 0.1 ) , Linear ( 0.4 ) ) ;
413
- assert_approx_eq ( Linear ( 0.5 ) - Linear ( -0.5 ) , Linear ( 1.0 ) ) ;
414
-
415
- // Decibels to Decibels.
416
- assert_eq ! ( Decibels ( 0.0 ) - Decibels ( 0.0 ) , Decibels ( f32 :: NEG_INFINITY ) ) ;
417
- assert_approx_eq ( Decibels ( 6.0 ) - Decibels ( 4.0 ) , Decibels ( -7.736506 ) ) ;
418
- assert_eq ! ( Decibels ( -6.0 ) - Decibels ( -6.0 ) , Decibels ( f32 :: NEG_INFINITY ) ) ;
419
- }
420
-
421
- #[ test]
422
- fn volume_ops_sub_assign ( ) {
423
- // Linear to Linear.
424
- let mut volume = Linear ( 0.5 ) ;
425
- volume -= Linear ( 0.5 ) ;
426
- assert_approx_eq ( volume, Linear ( 0.0 ) ) ;
427
- }
428
-
429
490
#[ test]
430
491
fn volume_ops_mul ( ) {
431
492
// Linear to Linear.
0 commit comments