1use std::f32::consts::PI;
3
4use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
5use crate::param::{AudioParam, AudioParamDescriptor};
6use crate::render::{
7 AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
8};
9
10use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelCountMode, ChannelInterpretation};
11
12#[derive(Clone, Debug)]
17pub struct StereoPannerOptions {
18 pub pan: f32,
20 pub audio_node_options: AudioNodeOptions,
22}
23
24impl Default for StereoPannerOptions {
25 fn default() -> Self {
26 Self {
27 pan: 0.,
28 audio_node_options: AudioNodeOptions {
29 channel_count: 2,
30 channel_count_mode: ChannelCountMode::ClampedMax,
31 channel_interpretation: ChannelInterpretation::Speakers,
32 },
33 }
34 }
35}
36
37#[track_caller]
45#[inline(always)]
46fn assert_valid_channel_count(count: usize) {
47 assert!(
48 count <= 2,
49 "NotSupportedError - StereoPannerNode channel count cannot be greater than two"
50 );
51}
52
53#[track_caller]
61#[inline(always)]
62fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
63 assert_ne!(
64 mode,
65 ChannelCountMode::Max,
66 "NotSupportedError - StereoPannerNode channel count mode cannot be set to max",
67 );
68}
69
70#[inline(always)]
74fn get_stereo_gains(x: f32) -> [f32; 2] {
75 let gain_left = ((1. - x) * PI / 2.).sin(); let gain_right = (x * PI / 2.).sin();
77
78 [gain_left, gain_right]
79}
80
81#[derive(Debug)]
116pub struct StereoPannerNode {
117 registration: AudioContextRegistration,
119 channel_config: ChannelConfig,
121 pan: AudioParam,
124}
125
126impl AudioNode for StereoPannerNode {
127 fn registration(&self) -> &AudioContextRegistration {
128 &self.registration
129 }
130
131 fn channel_config(&self) -> &ChannelConfig {
132 &self.channel_config
133 }
134
135 fn number_of_inputs(&self) -> usize {
136 1
137 }
138
139 fn number_of_outputs(&self) -> usize {
140 1
141 }
142
143 fn set_channel_count_mode(&self, mode: ChannelCountMode) {
144 assert_valid_channel_count_mode(mode);
145 self.channel_config
146 .set_count_mode(mode, self.registration());
147 }
148
149 fn set_channel_count(&self, count: usize) {
150 assert_valid_channel_count(count);
151 self.channel_config.set_count(count, self.registration());
152 }
153}
154
155impl StereoPannerNode {
156 pub fn new<C: BaseAudioContext>(context: &C, options: StereoPannerOptions) -> Self {
171 context.base().register(move |registration| {
172 assert_valid_channel_count_mode(options.audio_node_options.channel_count_mode);
173 assert_valid_channel_count(options.audio_node_options.channel_count);
174
175 let pan_options = AudioParamDescriptor {
176 name: String::new(),
177 min_value: -1.,
178 max_value: 1.,
179 default_value: 0.,
180 automation_rate: crate::param::AutomationRate::A,
181 };
182 let (pan_param, pan_proc) = context.create_audio_param(pan_options, ®istration);
183
184 pan_param.set_value(options.pan);
185
186 let renderer = StereoPannerRenderer::new(pan_proc);
187
188 let node = Self {
189 registration,
190 channel_config: options.audio_node_options.into(),
191 pan: pan_param,
192 };
193
194 (node, Box::new(renderer))
195 })
196 }
197
198 #[must_use]
200 pub fn pan(&self) -> &AudioParam {
201 &self.pan
202 }
203}
204
205struct StereoPannerRenderer {
207 pan: AudioParamId,
210}
211
212impl StereoPannerRenderer {
213 fn new(pan: AudioParamId) -> Self {
214 Self { pan }
215 }
216}
217
218impl AudioProcessor for StereoPannerRenderer {
219 fn process(
220 &mut self,
221 inputs: &[AudioRenderQuantum],
222 outputs: &mut [AudioRenderQuantum],
223 params: AudioParamValues<'_>,
224 _scope: &AudioWorkletGlobalScope,
225 ) -> bool {
226 let input = &inputs[0];
228 let output = &mut outputs[0];
229
230 if input.is_silent() {
231 output.make_silent();
232 return false;
233 }
234
235 output.set_number_of_channels(2);
236
237 let pan_values = params.get(&self.pan);
239
240 let [left, right] = output.stereo_mut();
241
242 match input.number_of_channels() {
243 0 => (),
244 1 => {
245 if pan_values.len() == 1 {
246 let pan = pan_values[0];
247 let x = (pan + 1.) * 0.5;
248 let [gain_left, gain_right] = get_stereo_gains(x);
249
250 left.iter_mut()
251 .zip(right.iter_mut())
252 .zip(input.channel_data(0).iter())
253 .for_each(|((l, r), input)| {
254 *l = input * gain_left;
255 *r = input * gain_right;
256 });
257 } else {
258 left.iter_mut()
259 .zip(right.iter_mut())
260 .zip(pan_values.iter())
261 .zip(input.channel_data(0).iter())
262 .for_each(|(((l, r), pan), input)| {
263 let x = (pan + 1.) * 0.5;
264 let [gain_left, gain_right] = get_stereo_gains(x);
265
266 *l = input * gain_left;
267 *r = input * gain_right;
268 });
269 }
270 }
271 2 => {
272 if pan_values.len() == 1 {
273 let pan = pan_values[0];
274 let x = if pan <= 0. { pan + 1. } else { pan };
275 let [gain_left, gain_right] = get_stereo_gains(x);
276
277 left.iter_mut()
278 .zip(right.iter_mut())
279 .zip(input.channel_data(0).iter())
280 .zip(input.channel_data(1).iter())
281 .for_each(|(((l, r), &input_left), &input_right)| {
282 if pan <= 0. {
283 *l = input_right.mul_add(gain_left, input_left);
284 *r = input_right * gain_right;
285 } else {
286 *l = input_left * gain_left;
287 *r = input_left.mul_add(gain_right, input_right);
288 }
289 });
290 } else {
291 left.iter_mut()
292 .zip(right.iter_mut())
293 .zip(pan_values.iter())
294 .zip(input.channel_data(0).iter())
295 .zip(input.channel_data(1).iter())
296 .for_each(|((((l, r), &pan), &input_left), &input_right)| {
297 if pan <= 0. {
298 let x = pan + 1.;
299 let [gain_left, gain_right] = get_stereo_gains(x);
300
301 *l = input_right.mul_add(gain_left, input_left);
302 *r = input_right * gain_right;
303 } else {
304 let x = pan;
305 let [gain_left, gain_right] = get_stereo_gains(x);
306
307 *l = input_left * gain_left;
308 *r = input_left.mul_add(gain_right, input_right);
309 }
310 });
311 }
312 }
313 _ => panic!("StereoPannerNode should not have more than 2 channels to process"),
314 }
315
316 false
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use float_eq::assert_float_eq;
323
324 use crate::context::{BaseAudioContext, OfflineAudioContext};
325 use crate::node::AudioScheduledSourceNode;
326
327 use super::*;
328
329 #[test]
330 fn test_constructor() {
331 {
332 let context = OfflineAudioContext::new(2, 1, 44_100.);
333 let _panner = StereoPannerNode::new(&context, StereoPannerOptions::default());
334 }
335
336 {
337 let context = OfflineAudioContext::new(2, 1, 44_100.);
338 let _panner = context.create_stereo_panner();
339 }
340
341 {
342 let context = OfflineAudioContext::new(2, 1, 44_100.);
343 let panner = StereoPannerNode::new(&context, StereoPannerOptions::default());
344
345 let default_pan = 0.;
346 let pan = panner.pan.value();
347 assert_float_eq!(pan, default_pan, abs_all <= 0.);
348 }
349 }
350
351 #[test]
352 fn test_init_with_channel_count_mode() {
353 let context = OfflineAudioContext::new(2, 1, 44_100.);
354 let options = StereoPannerOptions {
355 audio_node_options: AudioNodeOptions {
356 channel_count_mode: ChannelCountMode::Explicit,
357 ..AudioNodeOptions::default()
358 },
359 ..StereoPannerOptions::default()
360 };
361 let panner = StereoPannerNode::new(&context, options);
362 assert_eq!(
363 panner.channel_config().count_mode(),
364 ChannelCountMode::Explicit
365 );
366 assert_eq!(panner.channel_count_mode(), ChannelCountMode::Explicit);
367 }
368
369 #[test]
370 fn test_mono_panning() {
371 let sample_rate = 44_100.;
372
373 let context = OfflineAudioContext::new(2, 128, 44_100.);
374
375 let mut buffer = context.create_buffer(1, 128, sample_rate);
376 buffer.copy_to_channel(&[1.; 128], 0);
377
378 {
380 let mut context = OfflineAudioContext::new(2, 128, 44_100.);
381 let panner = StereoPannerNode::new(
383 &context,
384 StereoPannerOptions {
385 audio_node_options: AudioNodeOptions {
386 channel_count: 1,
387 channel_count_mode: ChannelCountMode::ClampedMax,
388 ..AudioNodeOptions::default()
389 },
390 pan: -1.,
391 },
392 );
393 panner.connect(&context.destination());
394
395 let mut src = context.create_buffer_source();
396 src.connect(&panner);
397 src.set_buffer(buffer.clone());
398 src.start();
399
400 let res = context.start_rendering_sync();
401
402 assert_float_eq!(res.get_channel_data(0)[..], [1.; 128], abs_all <= 0.);
403 assert_float_eq!(res.get_channel_data(1)[..], [0.; 128], abs_all <= 0.);
404 }
405
406 {
408 let mut context = OfflineAudioContext::new(2, 128, 44_100.);
409 let panner = StereoPannerNode::new(
411 &context,
412 StereoPannerOptions {
413 audio_node_options: AudioNodeOptions {
414 channel_count: 1,
415 channel_count_mode: ChannelCountMode::ClampedMax,
416 ..AudioNodeOptions::default()
417 },
418 pan: 1.,
419 },
420 );
421 panner.connect(&context.destination());
422
423 let mut src = context.create_buffer_source();
424 src.connect(&panner);
425 src.set_buffer(buffer.clone());
426 src.start();
427
428 let res = context.start_rendering_sync();
429
430 assert_float_eq!(res.get_channel_data(0)[..], [0.; 128], abs_all <= 1e-7);
431 assert_float_eq!(res.get_channel_data(1)[..], [1.; 128], abs_all <= 0.);
432 }
433
434 {
436 let mut context = OfflineAudioContext::new(2, 128, 44_100.);
437 let panner = StereoPannerNode::new(
439 &context,
440 StereoPannerOptions {
441 audio_node_options: AudioNodeOptions {
442 channel_count: 1,
443 channel_count_mode: ChannelCountMode::ClampedMax,
444 ..AudioNodeOptions::default()
445 },
446 pan: 0.,
447 },
448 );
449 panner.connect(&context.destination());
450
451 let mut src = context.create_buffer_source();
452 src.connect(&panner);
453 src.set_buffer(buffer.clone());
454 src.start();
455
456 let res = context.start_rendering_sync();
457
458 let mut power = [0.; 128];
459 power
460 .iter_mut()
461 .zip(res.get_channel_data(0).iter())
462 .zip(res.get_channel_data(1).iter())
463 .for_each(|((p, l), r)| {
464 *p = l * l + r * r;
465 });
466
467 assert_float_eq!(power, [1.; 128], abs_all <= 1.2e-7);
468 }
469 }
470
471 #[test]
472 fn test_stereo_panning() {
473 let sample_rate = 44_100.;
474
475 let context = OfflineAudioContext::new(2, 128, 44_100.);
476
477 let mut buffer = context.create_buffer(2, 128, sample_rate);
478 buffer.copy_to_channel(&[1.; 128], 0);
479 buffer.copy_to_channel(&[1.; 128], 1);
480
481 {
483 let mut context = OfflineAudioContext::new(2, 128, 44_100.);
484 let panner = StereoPannerNode::new(
486 &context,
487 StereoPannerOptions {
488 pan: -1.,
489 ..StereoPannerOptions::default()
490 },
491 );
492 panner.connect(&context.destination());
493
494 let mut src = context.create_buffer_source();
495 src.connect(&panner);
496 src.set_buffer(buffer.clone());
497 src.start();
498
499 let res = context.start_rendering_sync();
500
501 assert_float_eq!(res.get_channel_data(0)[..], [2.; 128], abs_all <= 0.);
502 assert_float_eq!(res.get_channel_data(1)[..], [0.; 128], abs_all <= 0.);
503 }
504
505 {
507 let mut context = OfflineAudioContext::new(2, 128, 44_100.);
508 let panner = StereoPannerNode::new(
510 &context,
511 StereoPannerOptions {
512 pan: 1.,
513 ..StereoPannerOptions::default()
514 },
515 );
516 panner.connect(&context.destination());
517
518 let mut src = context.create_buffer_source();
519 src.connect(&panner);
520 src.set_buffer(buffer.clone());
521 src.start();
522
523 let res = context.start_rendering_sync();
524
525 assert_float_eq!(res.get_channel_data(0)[..], [0.; 128], abs_all <= 1e-7);
526 assert_float_eq!(res.get_channel_data(1)[..], [2.; 128], abs_all <= 0.);
527 }
528
529 {
531 let mut context = OfflineAudioContext::new(2, 128, 44_100.);
532 let panner = StereoPannerNode::new(
534 &context,
535 StereoPannerOptions {
536 pan: 0.,
537 ..StereoPannerOptions::default()
538 },
539 );
540 panner.connect(&context.destination());
541
542 let mut src = context.create_buffer_source();
543 src.connect(&panner);
544 src.set_buffer(buffer.clone());
545 src.start();
546
547 let res = context.start_rendering_sync();
548
549 assert_float_eq!(res.get_channel_data(0)[..], [1.; 128], abs_all <= 1e-7);
550 assert_float_eq!(res.get_channel_data(1)[..], [1.; 128], abs_all <= 0.);
551 }
552 }
553}