firewheel_core/dsp/pan_law.rs
1use std::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}