firewheel_core/dsp/
pan_law.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 to use to map a normalized panning value in the range `[-1.0, 1.0]`
9/// to the corresponding gain values for the left and right channels.
10#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Diff, Patch)]
11#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
12#[repr(u32)]
13pub enum PanLaw {
14    /// This pan law makes the signal appear to play at a constant volume across
15    /// the entire panning range.
16    ///
17    /// More specifically this a circular pan law with each channel at -3dB when
18    /// panned center.
19    #[default]
20    EqualPower3dB = 0,
21    /// Same as [`PanLaw::EqualPower3dB`], but each channel will be at -6dB when
22    /// panned center which may be better for some signals.
23    EqualPower6dB,
24    /// This is cheaper to compute than `EqualPower3dB`, but is less accurate in
25    /// its perception of constant volume.
26    SquareRoot,
27    /// The cheapest to compute, but is the least accurate in its perception of
28    /// constant volume.
29    Linear,
30}
31
32impl PanLaw {
33    /// Compute the raw gain values for the `(left, right)` channels.
34    ///
35    /// * `pan` - The pan amount, where `0.0` is center, `-1.0` is fully left, and
36    /// `1.0` is fully right.
37    pub fn compute_gains(&self, pan: f32) -> (f32, f32) {
38        if pan <= -1.0 {
39            (1.0, 0.0)
40        } else if pan >= 1.0 {
41            (0.0, 1.0)
42        } else {
43            let pan = (pan + 1.0) * 0.5;
44
45            match self {
46                Self::EqualPower3dB => {
47                    let pan = FRAC_PI_2 * pan;
48                    let pan_cos = pan.cos();
49                    let pan_sin = pan.sin();
50
51                    (pan_cos, pan_sin)
52                }
53                Self::EqualPower6dB => {
54                    let pan = FRAC_PI_2 * pan;
55                    let pan_cos = pan.cos();
56                    let pan_sin = pan.sin();
57
58                    (pan_cos * pan_cos, pan_sin * pan_sin)
59                }
60                Self::SquareRoot => ((1.0 - pan).sqrt(), pan.sqrt()),
61                Self::Linear => ((1.0 - pan), pan),
62            }
63        }
64    }
65
66    pub fn from_u32(val: u32) -> Self {
67        match val {
68            1 => Self::EqualPower6dB,
69            2 => Self::SquareRoot,
70            3 => Self::Linear,
71            _ => Self::EqualPower3dB,
72        }
73    }
74}