pointillism/effects/
pan.rs

1//! Structures for panning an audio signal.
2
3use std::marker::PhantomData;
4
5use crate::prelude::*;
6
7/// Represents the way in which gain correlates with panning angle.
8pub trait Law: Copy + Default {
9    /// Initializes a new struct with the specified angle.
10    fn new(angle: f64) -> Self;
11
12    /// Panning angle, between `0.0` and `1.0`.
13    ///
14    /// Hard left is `0.0`, center is `0.5`, hard right is `1.0`.
15    fn angle(&self) -> f64;
16
17    /// Returns a mutable reference to the panning angle.
18    fn angle_mut(&mut self) -> &mut f64;
19
20    /// Returns the left and right gains for a given angle.
21    fn gain(&self) -> (f64, f64);
22}
23
24/// Linear panning.
25///
26/// The left and right channel gains scale linearly with the angle.
27#[derive(Clone, Copy, Debug)]
28pub struct Linear {
29    /// Panning angle. See [`Law::angle`] for more info.
30    pub angle: f64,
31}
32
33impl Default for Linear {
34    fn default() -> Self {
35        Self { angle: 0.5 }
36    }
37}
38
39/// Initializes the fields `new`, `angle`, `angle_mut`.
40macro_rules! pan_boilerplate {
41    () => {
42        fn new(angle: f64) -> Self {
43            Self { angle }
44        }
45
46        fn angle(&self) -> f64 {
47            self.angle
48        }
49
50        fn angle_mut(&mut self) -> &mut f64 {
51            &mut self.angle
52        }
53    };
54}
55
56/// Gain formula for a linear panning law.
57#[must_use]
58pub fn linear_gain(angle: f64) -> (f64, f64) {
59    (1.0 - angle, angle)
60}
61
62impl Law for Linear {
63    pan_boilerplate!();
64
65    fn gain(&self) -> (f64, f64) {
66        linear_gain(self.angle)
67    }
68}
69
70/// Constant power panning.
71///
72/// The left and right channel gains are the cosine and sine of the angle.
73#[derive(Clone, Copy, Debug)]
74pub struct Power {
75    /// Panning angle. See [`Law::angle`] for more info.
76    pub angle: f64,
77}
78
79impl Default for Power {
80    fn default() -> Self {
81        Self { angle: 0.5 }
82    }
83}
84
85/// Gain formula for a power panning law.
86#[must_use]
87pub fn power_gain(angle: f64) -> (f64, f64) {
88    let (r, l) = (std::f64::consts::FRAC_PI_2 * angle).sin_cos();
89    (l, r)
90}
91
92impl Law for Power {
93    pan_boilerplate!();
94
95    fn gain(&self) -> (f64, f64) {
96        power_gain(self.angle)
97    }
98}
99
100/// -4.5 dB panning.
101///
102/// This takes the geometric mean of the gains from the [`Linear`] and
103/// [`Power`].
104#[derive(Clone, Copy, Debug)]
105pub struct Mixed {
106    /// Panning angle. See [`Law::angle`] for more info.
107    pub angle: f64,
108}
109
110impl Default for Mixed {
111    fn default() -> Self {
112        Self { angle: 0.5 }
113    }
114}
115
116/// Gain formula for a mixed panning law.
117#[must_use]
118pub fn mixed_gain(angle: f64) -> (f64, f64) {
119    let linear = linear_gain(angle);
120    let power = power_gain(angle);
121    ((linear.0 * power.0).sqrt(), (linear.1 * power.1).sqrt())
122}
123
124impl Law for Mixed {
125    pan_boilerplate!();
126
127    fn gain(&self) -> (f64, f64) {
128        mixed_gain(self.angle)
129    }
130}
131
132/// A wrapper for a pan [`Law`] which converts it into a [`Map`].
133#[derive(Clone, Copy, Debug)]
134pub struct Wrapper<A: Audio, P: Law> {
135    /// Inner pan law.
136    pub pan_law: P,
137    /// Dummy value.
138    phantom: PhantomData<A>,
139}
140
141impl<A: Audio, P: Law> Wrapper<A, P> {
142    /// Initializes a new [`Wrapper`].
143    pub const fn new(pan_law: P) -> Self {
144        Self {
145            phantom: PhantomData,
146            pan_law,
147        }
148    }
149}
150
151impl<A: Audio, P: Law> Map for Wrapper<A, P> {
152    type Input = A;
153    type Output = smp::Stereo;
154
155    fn eval(&self, sample: A) -> smp::Stereo {
156        let smp::Stereo(sl, sr) = sample.duplicate();
157        let (gl, gr) = self.pan_law.gain();
158        smp::Stereo(sl * gl, sr * gr)
159    }
160}
161
162/// Applies a pan effect, using the specified pan [`Law`].
163pub type Panner<S, P> = eff::MapSgn<S, Wrapper<<S as Signal>::Sample, P>>;
164
165impl<S: Signal, P: Law> Panner<S, P>
166where
167    S::Sample: Audio,
168{
169    /// Initializes a new [`Panner`] for a given signal and pan law.
170    pub const fn new_pan_law(sgn: S, pan_law: P) -> Self {
171        eff::MapSgn::new(sgn, Wrapper::new(pan_law))
172    }
173
174    /// Initializes a new [`Panner`] for a given signal and angle.
175    pub fn new_pan(sgn: S, angle: f64) -> Self {
176        Self::new_pan_law(sgn, P::new(angle))
177    }
178
179    /// Returns the current panning angle.
180    pub fn angle(&self) -> f64 {
181        self.map().pan_law.angle()
182    }
183
184    /// Returns a mutable reference to the current panning angle.
185    pub fn angle_mut(&mut self) -> &mut f64 {
186        self.map_mut().pan_law.angle_mut()
187    }
188}
189
190/// A [`Linear`] panner.
191pub type LinearPanner<S> = Panner<S, Linear>;
192
193impl<S: Signal> LinearPanner<S>
194where
195    S::Sample: Audio,
196{
197    /// Initializes a [`LinearPanner`] with the specified angle.
198    pub const fn linear(sgn: S, angle: f64) -> Self {
199        Self::new_pan_law(sgn, Linear { angle })
200    }
201}
202
203/// A [`Power`] panner.
204pub type PowerPanner<S> = Panner<S, Power>;
205
206impl<S: Signal> Panner<S, Power>
207where
208    S::Sample: Audio,
209{
210    /// Initializes a [`PowerPanner`] with the specified angle.
211    pub const fn power(sgn: S, angle: f64) -> Self {
212        Self::new_pan_law(sgn, Power { angle })
213    }
214}
215
216/// A [`Mixed`] panner.
217pub type MixedPanner<S> = Panner<S, Mixed>;
218
219impl<S: Signal> Panner<S, Mixed>
220where
221    S::Sample: Audio,
222{
223    /// Initializes a [`MixedPanner`] with the specified angle.
224    pub const fn mixed(sgn: S, angle: f64) -> Self {
225        Self::new_pan_law(sgn, Mixed { angle })
226    }
227}