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