firewheel_core/dsp/
pan_law.rs

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