firewheel_core/dsp/fade.rs
1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4use core::f32::consts::FRAC_PI_2;
5
6use crate::{
7 diff::{Diff, Patch},
8 dsp::volume::{amp_to_db, Volume},
9};
10
11/// The algorithm used to map a normalized crossfade/panning value in the
12/// range `[0.0, 1.0]` or `[-1.0, 1.0]` to the corresponding gain values
13/// for two inputs.
14#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Diff, Patch)]
15#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[repr(u32)]
18pub enum FadeCurve {
19 /// This curve makes the combined signal appear to play at a constant volume
20 /// across the entire fade range for most signals.
21 ///
22 /// More specifically this a circular curve with each input at -3dB at
23 /// center.
24 #[default]
25 EqualPower3dB = 0,
26 /// Same as [`FadeCurve::EqualPower3dB`], but each input will be at -6dB
27 /// at center which may be better for some signals.
28 EqualPower6dB,
29 /// This is cheaper to compute than [`FadeCurve::EqualPower3dB`], but is less
30 /// accurate in its perception of constant volume.
31 SquareRoot,
32 /// The cheapest to compute, but is the least accurate in its perception of
33 /// constant volume for some signals (though if the signals are highly
34 /// correlated such as a wet/dry mix, then this mode may actually provide
35 /// better results.)
36 Linear,
37}
38
39impl FadeCurve {
40 /// Compute the raw gain values for both inputs.
41 ///
42 /// * `fade` - The fade amount, where `0.5` is center, `0.0` is fully the
43 /// first input, and `1.0` is fully the second input.
44 pub fn compute_gains_0_to_1(&self, fade: f32) -> (f32, f32) {
45 if fade <= 0.00001 {
46 (1.0, 0.0)
47 } else if fade >= 0.99999 {
48 (0.0, 1.0)
49 } else {
50 match self {
51 Self::EqualPower3dB => {
52 let fade = FRAC_PI_2 * fade;
53 let fade_cos = fade.cos();
54 let fade_sin = fade.sin();
55
56 (fade_cos, fade_sin)
57 }
58 Self::EqualPower6dB => {
59 let fade = FRAC_PI_2 * fade;
60 let fade_cos = fade.cos();
61 let fade_sin = fade.sin();
62
63 (fade_cos * fade_cos, fade_sin * fade_sin)
64 }
65 Self::SquareRoot => ((1.0 - fade).sqrt(), fade.sqrt()),
66 Self::Linear => ((1.0 - fade), fade),
67 }
68 }
69 }
70
71 /// Compute the raw gain values for both inputs.
72 ///
73 /// * `fade` - The fade amount, where `0.0` is center, `-1.0` is fully the
74 /// first input, and `1.0` is fully the second input.
75 pub fn compute_gains_neg1_to_1(&self, fade: f32) -> (f32, f32) {
76 if fade <= -0.99999 {
77 (1.0, 0.0)
78 } else if fade >= 0.99999 {
79 (0.0, 1.0)
80 } else {
81 let fade = (fade + 1.0) * 0.5;
82
83 match self {
84 Self::EqualPower3dB => {
85 let fade = FRAC_PI_2 * fade;
86 let fade_cos = fade.cos();
87 let fade_sin = fade.sin();
88
89 (fade_cos, fade_sin)
90 }
91 Self::EqualPower6dB => {
92 let fade = FRAC_PI_2 * fade;
93 let fade_cos = fade.cos();
94 let fade_sin = fade.sin();
95
96 (fade_cos * fade_cos, fade_sin * fade_sin)
97 }
98 Self::SquareRoot => ((1.0 - fade).sqrt(), fade.sqrt()),
99 Self::Linear => ((1.0 - fade), fade),
100 }
101 }
102 }
103
104 /// Compute the decibel values for both inputs for crossfading.
105 ///
106 /// * `fade` - The fade amount, where `0.5` is center, `0.0` is fully the
107 /// first input, and `1.0` is fully the second input.
108 pub fn crossfade_decibels(&self, fade: f32) -> (f32, f32) {
109 let (a1, a2) = self.compute_gains_0_to_1(fade);
110
111 (amp_to_db(a1), amp_to_db(a2))
112 }
113
114 /// Compute the [`Volume`]s for both inputs for crossfading.
115 ///
116 /// (Both volumes will be of type [`Volume::Decibels`]).
117 ///
118 /// * `fade` - The fade amount, where `0.5` is center, `0.0` is fully the
119 /// first input, and `1.0` is fully the second input.
120 pub fn crossfade_volumes(&self, fade: f32) -> (Volume, Volume) {
121 let (d1, d2) = self.crossfade_decibels(fade);
122
123 (Volume::Decibels(d1), Volume::Decibels(d2))
124 }
125
126 pub fn from_u32(val: u32) -> Self {
127 match val {
128 1 => Self::EqualPower6dB,
129 2 => Self::SquareRoot,
130 3 => Self::Linear,
131 _ => Self::EqualPower3dB,
132 }
133 }
134}