1use aether_core::{node::DspNode, param::ParamBlock, BUFFER_SIZE, MAX_INPUTS};
8
9pub struct Panner {
10 }
12
13impl Panner {
14 pub fn new() -> Self {
15 Self {}
16 }
17}
18
19impl Default for Panner {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl DspNode for Panner {
26 fn process(
27 &mut self,
28 inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
29 output: &mut [f32; BUFFER_SIZE],
30 params: &mut ParamBlock,
31 _sample_rate: f32,
32 ) {
33 let silence = [0.0f32; BUFFER_SIZE];
34 let input_l = inputs[0].unwrap_or(&silence);
35 let input_r = inputs[1].unwrap_or(&silence);
36
37 let pan = params.get(0).current.clamp(-1.0, 1.0);
38 let width = params.get(1).current.clamp(0.0, 2.0);
39
40 let pan_angle = (pan + 1.0) * 0.5 * std::f32::consts::FRAC_PI_2; let left_gain = pan_angle.cos();
43 let right_gain = pan_angle.sin();
44
45 for i in 0..BUFFER_SIZE {
46 let mono = (input_l[i] + input_r[i]) * 0.5;
48 let side = (input_l[i] - input_r[i]) * 0.5;
49
50 let mid = mono;
52 let side_scaled = side * width;
53
54 let left = (mid + side_scaled) * left_gain;
56 let right = (mid - side_scaled) * right_gain;
57
58 output[i] = (left + right) * 0.5;
60
61 params.tick_all();
62 }
63 }
64
65 fn type_name(&self) -> &'static str {
66 "Panner"
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_panner_center() {
76 let mut panner = Panner::new();
77 let mut params = ParamBlock::new();
78 for &v in &[0.0f32, 1.0] {
80 params.add(v);
81 }
82
83 let input_l = [0.5f32; BUFFER_SIZE];
84 let input_r = [0.5f32; BUFFER_SIZE];
85 let mut inputs = [None; MAX_INPUTS];
86 inputs[0] = Some(&input_l);
87 inputs[1] = Some(&input_r);
88 let mut output = [0.0f32; BUFFER_SIZE];
89
90 panner.process(&inputs, &mut output, &mut params, 48000.0);
91
92 assert!(
94 output[0] > 0.2 && output[0] < 0.8,
95 "center pan should preserve level, got {}",
96 output[0]
97 );
98 }
99
100 #[test]
101 fn test_panner_hard_left() {
102 let mut panner = Panner::new();
103 let mut params = ParamBlock::new();
104 for &v in &[-1.0f32, 1.0] {
106 params.add(v);
107 }
108
109 let input_l = [1.0f32; BUFFER_SIZE];
110 let input_r = [0.0f32; BUFFER_SIZE];
111 let mut inputs = [None; MAX_INPUTS];
112 inputs[0] = Some(&input_l);
113 inputs[1] = Some(&input_r);
114 let mut output = [0.0f32; BUFFER_SIZE];
115
116 panner.process(&inputs, &mut output, &mut params, 48000.0);
117
118 assert!(output[0] > 0.3, "hard left should pass left channel");
120 }
121
122 #[test]
123 fn test_panner_mono_width() {
124 let mut panner = Panner::new();
125 let mut params = ParamBlock::new();
126 for &v in &[0.0f32, 0.0] {
128 params.add(v);
129 }
130
131 let input_l = [0.5f32; BUFFER_SIZE];
132 let input_r = [0.5f32; BUFFER_SIZE];
133 let mut inputs = [None; MAX_INPUTS];
134 inputs[0] = Some(&input_l);
135 inputs[1] = Some(&input_r);
136 let mut output = [0.0f32; BUFFER_SIZE];
137
138 panner.process(&inputs, &mut output, &mut params, 48000.0);
139
140 assert!(
142 output[0] > 0.2 && output[0] < 0.8,
143 "mono width should collapse stereo, got {}",
144 output[0]
145 );
146 }
147}