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