@@ -34,7 +34,7 @@ impl GlobalVolume {
3434#[ derive( Clone , Copy , Debug , Reflect ) ]
3535#[ reflect( Clone , Debug , PartialEq ) ]
3636pub 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.
3838 ///
3939 /// In a linear scale, the value `1.0` represents the "normal" volume,
4040 /// meaning the audio is played at its original level. Values greater than
@@ -144,7 +144,7 @@ impl Volume {
144144
145145 /// Returns the volume in decibels as a float.
146146 ///
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
148148 /// is `0.0`, this method returns negative infinity.
149149 pub fn to_decibels ( & self ) -> f32 {
150150 match self {
@@ -155,57 +155,95 @@ impl Volume {
155155
156156 /// The silent volume. Also known as "off" or "muted".
157157 pub const SILENT : Self = Volume :: Linear ( 0.0 ) ;
158- }
159-
160- impl core:: ops:: Add < Self > for Volume {
161- type Output = Self ;
162158
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)
177178 }
178- }
179179
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)
183199 }
184- }
185-
186- impl core:: ops:: Sub < Self > for Volume {
187- type Output = Self ;
188200
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)
203220 }
204- }
205221
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)
209247 }
210248}
211249
@@ -337,8 +375,9 @@ mod tests {
337375 Linear ( f32 :: NEG_INFINITY ) . to_decibels( ) . is_infinite( ) ,
338376 "Negative infinite linear scale is equivalent to infinite decibels"
339377 ) ;
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 ,
342381 "Negative infinity decibels is equivalent to zero linear scale"
343382 ) ;
344383
@@ -361,6 +400,74 @@ mod tests {
361400 ) ;
362401 }
363402
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+
364471 fn assert_approx_eq ( a : Volume , b : Volume ) {
365472 const EPSILON : f32 = 0.0001 ;
366473
@@ -380,52 +487,6 @@ mod tests {
380487 }
381488 }
382489
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-
429490 #[ test]
430491 fn volume_ops_mul ( ) {
431492 // Linear to Linear.
0 commit comments