use aether_core::{node::DspNode, param::ParamBlock, BUFFER_SIZE, MAX_INPUTS};
pub struct Panner {
}
impl Panner {
pub fn new() -> Self {
Self {}
}
}
impl Default for Panner {
fn default() -> Self {
Self::new()
}
}
impl DspNode for Panner {
fn process(
&mut self,
inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
output: &mut [f32; BUFFER_SIZE],
params: &mut ParamBlock,
_sample_rate: f32,
) {
let silence = [0.0f32; BUFFER_SIZE];
let input_l = inputs[0].unwrap_or(&silence);
let input_r = inputs[1].unwrap_or(&silence);
let pan = params.get(0).current.clamp(-1.0, 1.0);
let width = params.get(1).current.clamp(0.0, 2.0);
let pan_angle = (pan + 1.0) * 0.5 * std::f32::consts::FRAC_PI_2; let left_gain = pan_angle.cos();
let right_gain = pan_angle.sin();
for i in 0..BUFFER_SIZE {
let mono = (input_l[i] + input_r[i]) * 0.5;
let side = (input_l[i] - input_r[i]) * 0.5;
let mid = mono;
let side_scaled = side * width;
let left = (mid + side_scaled) * left_gain;
let right = (mid - side_scaled) * right_gain;
output[i] = (left + right) * 0.5;
params.tick_all();
}
}
fn type_name(&self) -> &'static str {
"Panner"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_panner_center() {
let mut panner = Panner::new();
let mut params = ParamBlock::new();
for &v in &[0.0f32, 1.0] {
params.add(v);
}
let input_l = [0.5f32; BUFFER_SIZE];
let input_r = [0.5f32; BUFFER_SIZE];
let mut inputs = [None; MAX_INPUTS];
inputs[0] = Some(&input_l);
inputs[1] = Some(&input_r);
let mut output = [0.0f32; BUFFER_SIZE];
panner.process(&inputs, &mut output, &mut params, 48000.0);
assert!(
output[0] > 0.2 && output[0] < 0.8,
"center pan should preserve level, got {}",
output[0]
);
}
#[test]
fn test_panner_hard_left() {
let mut panner = Panner::new();
let mut params = ParamBlock::new();
for &v in &[-1.0f32, 1.0] {
params.add(v);
}
let input_l = [1.0f32; BUFFER_SIZE];
let input_r = [0.0f32; BUFFER_SIZE];
let mut inputs = [None; MAX_INPUTS];
inputs[0] = Some(&input_l);
inputs[1] = Some(&input_r);
let mut output = [0.0f32; BUFFER_SIZE];
panner.process(&inputs, &mut output, &mut params, 48000.0);
assert!(output[0] > 0.3, "hard left should pass left channel");
}
#[test]
fn test_panner_mono_width() {
let mut panner = Panner::new();
let mut params = ParamBlock::new();
for &v in &[0.0f32, 0.0] {
params.add(v);
}
let input_l = [0.5f32; BUFFER_SIZE];
let input_r = [0.5f32; BUFFER_SIZE];
let mut inputs = [None; MAX_INPUTS];
inputs[0] = Some(&input_l);
inputs[1] = Some(&input_r);
let mut output = [0.0f32; BUFFER_SIZE];
panner.process(&inputs, &mut output, &mut params, 48000.0);
assert!(
output[0] > 0.2 && output[0] < 0.8,
"mono width should collapse stereo, got {}",
output[0]
);
}
}