use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PanLaw {
EqualPower,
Linear,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct PanGains {
pub left: f32,
pub right: f32,
}
#[inline]
#[must_use]
pub fn pan_gains(pan: f32, law: PanLaw) -> PanGains {
let p = pan.clamp(-1.0, 1.0);
let t = (p + 1.0) * 0.5;
match law {
PanLaw::EqualPower => {
let angle = t * std::f32::consts::FRAC_PI_2;
PanGains {
left: angle.cos(),
right: angle.sin(),
}
}
PanLaw::Linear => PanGains {
left: 1.0 - t,
right: t,
},
}
}
#[inline]
#[must_use]
pub fn pan_mono(sample: f32, pan: f32, law: PanLaw) -> (f32, f32) {
let g = pan_gains(pan, law);
(sample * g.left, sample * g.right)
}
#[inline]
#[must_use]
pub fn stereo_balance(left: f32, right: f32, balance: f32) -> (f32, f32) {
let b = balance.clamp(-1.0, 1.0);
if b < 0.0 {
(left, right * (1.0 + b))
} else {
(left * (1.0 - b), right)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_center_equal_power() {
let g = pan_gains(0.0, PanLaw::EqualPower);
assert!((g.left - g.right).abs() < 0.01);
assert!((g.left * g.left + g.right * g.right - 1.0).abs() < 0.01);
}
#[test]
fn test_full_left() {
let g = pan_gains(-1.0, PanLaw::EqualPower);
assert!((g.left - 1.0).abs() < 0.01);
assert!(g.right.abs() < 0.01);
}
#[test]
fn test_full_right() {
let g = pan_gains(1.0, PanLaw::EqualPower);
assert!(g.left.abs() < 0.01);
assert!((g.right - 1.0).abs() < 0.01);
}
#[test]
fn test_linear_center() {
let g = pan_gains(0.0, PanLaw::Linear);
assert!((g.left - 0.5).abs() < f32::EPSILON);
assert!((g.right - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_pan_mono() {
let (l, r) = pan_mono(1.0, -1.0, PanLaw::EqualPower);
assert!((l - 1.0).abs() < 0.01);
assert!(r.abs() < 0.01);
}
#[test]
fn test_stereo_balance() {
let (l, r) = stereo_balance(1.0, 1.0, -1.0);
assert!((l - 1.0).abs() < f32::EPSILON);
assert!(r.abs() < f32::EPSILON);
}
#[test]
fn test_serde_roundtrip() {
let g = pan_gains(0.3, PanLaw::EqualPower);
let json = serde_json::to_string(&g).unwrap();
let back: PanGains = serde_json::from_str(&json).unwrap();
assert!((g.left - back.left).abs() < f32::EPSILON);
}
}