The repo never enforced rustfmt, so formatting had drifted broadly. This is a single mechanical `cargo fmt` pass over the whole crate (no behavioral change; lib suite green, 493 passed). Going forward fmt should be enforced (planned CI fmt --check step). Part of the 0.6.1 hygiene pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
2.8 KiB
Rust
93 lines
2.8 KiB
Rust
//! Listener-side stereo pan law.
|
|
//!
|
|
//! Capture, Opus, and the network stay mono. These helpers are used only after a
|
|
//! peer has been decoded locally, just before the playout mix is written to the
|
|
//! stereo playback bus.
|
|
|
|
/// Clamp and compute constant-power pan gains for `pan` in `[-1.0, 1.0]`.
|
|
///
|
|
/// - `-1.0` is hard left `(1, 0)`
|
|
/// - `0.0` is center `(sqrt(1/2), sqrt(1/2))`
|
|
/// - `1.0` is hard right `(0, 1)`
|
|
pub fn pan_gains(pan: f32) -> (f32, f32) {
|
|
let pan = pan.clamp(-1.0, 1.0);
|
|
let theta = (pan + 1.0) * std::f32::consts::FRAC_PI_4;
|
|
(theta.cos(), theta.sin())
|
|
}
|
|
|
|
/// Gains used by the legacy-compatible playback mixer.
|
|
///
|
|
/// The pure law above is constant-power. The existing application, however, was
|
|
/// mono and users heard the full old mono signal in both ears. Scaling by sqrt(2)
|
|
/// makes `pan = 0` exactly dual-mono `(1, 1)`, preserving the default sound while
|
|
/// still following the same equal-power curve as a peer is moved away from center.
|
|
pub fn playback_pan_gains(pan: f32) -> (f32, f32) {
|
|
let (left, right) = pan_gains(pan);
|
|
(
|
|
left * std::f32::consts::SQRT_2,
|
|
right * std::f32::consts::SQRT_2,
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
const EPS: f32 = 1.0e-6;
|
|
|
|
#[test]
|
|
fn hard_left_and_right_are_endpoints() {
|
|
assert_eq!(pan_gains(-1.0), (1.0, 0.0));
|
|
let (l, r) = pan_gains(1.0);
|
|
assert!(
|
|
l.abs() < EPS,
|
|
"left at hard-right should be zero-ish, got {l}"
|
|
);
|
|
assert!(
|
|
(r - 1.0).abs() < EPS,
|
|
"right at hard-right should be one, got {r}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn center_is_equal_and_power_preserving() {
|
|
let (l, r) = pan_gains(0.0);
|
|
assert!((l - r).abs() < EPS);
|
|
assert!((l - std::f32::consts::FRAC_1_SQRT_2).abs() < EPS);
|
|
assert!(((l * l + r * r) - 1.0).abs() < EPS);
|
|
}
|
|
|
|
#[test]
|
|
fn gains_move_monotonically() {
|
|
let pans = [-1.0, -0.5, 0.0, 0.5, 1.0];
|
|
let mut prev_l = f32::INFINITY;
|
|
let mut prev_r = f32::NEG_INFINITY;
|
|
for pan in pans {
|
|
let (l, r) = pan_gains(pan);
|
|
assert!(
|
|
l <= prev_l + EPS,
|
|
"left gain must not rise as pan moves right"
|
|
);
|
|
assert!(
|
|
r >= prev_r - EPS,
|
|
"right gain must not fall as pan moves right"
|
|
);
|
|
prev_l = l;
|
|
prev_r = r;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn playback_center_preserves_legacy_dual_mono() {
|
|
let (l, r) = playback_pan_gains(0.0);
|
|
assert!((l - 1.0).abs() < EPS);
|
|
assert!((r - 1.0).abs() < EPS);
|
|
}
|
|
|
|
#[test]
|
|
fn input_is_clamped() {
|
|
assert_eq!(pan_gains(-9.0), pan_gains(-1.0));
|
|
assert_eq!(pan_gains(9.0), pan_gains(1.0));
|
|
}
|
|
}
|