servo_media_audio/
stereo_panner.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::f32::consts::PI;
6
7use crate::block::{Chunk, FRAMES_PER_BLOCK, Tick};
8use crate::node::{AudioNodeEngine, AudioNodeType, BlockInfo, ChannelInfo};
9use crate::param::{Param, ParamType};
10
11#[derive(Copy, Clone, Debug)]
12pub struct StereoPannerOptions {
13    pub pan: f32,
14}
15
16impl Default for StereoPannerOptions {
17    fn default() -> Self {
18        StereoPannerOptions { pan: 0. }
19    }
20}
21
22#[derive(AudioNodeCommon)]
23pub(crate) struct StereoPannerNode {
24    channel_info: ChannelInfo,
25    pan: Param,
26}
27
28impl StereoPannerNode {
29    pub fn new(options: StereoPannerOptions, channel_info: ChannelInfo) -> Self {
30        Self {
31            channel_info,
32            pan: Param::new(options.pan),
33        }
34    }
35
36    pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool {
37        self.pan.update(info, tick)
38    }
39}
40
41impl AudioNodeEngine for StereoPannerNode {
42    fn node_type(&self) -> AudioNodeType {
43        AudioNodeType::StereoPannerNode
44    }
45
46    fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk {
47        debug_assert!(inputs.len() == 1);
48
49        {
50            let block = &mut inputs.blocks[0];
51
52            block.explicit_repeat();
53
54            let mono = if block.chan_count() == 1 {
55                block.resize_silence(2);
56                true
57            } else {
58                debug_assert!(block.chan_count() == 2);
59                false
60            };
61
62            let (l, r) = block.data_mut().split_at_mut(FRAMES_PER_BLOCK.0 as usize);
63            let mut pan = self.pan.value();
64            for frame in 0..FRAMES_PER_BLOCK.0 {
65                let frame = Tick(frame);
66                if self.update_parameters(info, frame) {
67                    pan = self.pan.value();
68                }
69
70                // https://webaudio.github.io/web-audio-api/#stereopanner-algorithm
71
72                // clamp pan to [-1, 1]
73                pan = pan.clamp(-1., 1.);
74
75                let x = if mono {
76                    (pan + 1.) / 2.
77                } else if pan <= 0. {
78                    pan + 1.
79                } else {
80                    pan
81                };
82                let x = x * PI / 2.;
83
84                let mut gain_l = x.cos();
85                let mut gain_r = x.sin();
86                // 9. * PI / 2 is often slightly negative, clamp
87                if gain_l <= 0. {
88                    gain_l = 0.
89                }
90                if gain_r <= 0. {
91                    gain_r = 0.;
92                }
93
94                let index = frame.0 as usize;
95                if mono {
96                    let input = l[index];
97                    l[index] = input * gain_l;
98                    r[index] = input * gain_r;
99                } else if pan <= 0. {
100                    l[index] += r[index] * gain_l;
101                    r[index] *= gain_r;
102                } else {
103                    r[index] += l[index] * gain_r;
104                    l[index] *= gain_l;
105                }
106            }
107        }
108
109        inputs
110    }
111
112    fn input_count(&self) -> u32 {
113        1
114    }
115
116    fn get_param(&mut self, id: ParamType) -> &mut Param {
117        match id {
118            ParamType::Pan => &mut self.pan,
119            _ => panic!("Unknown param {:?} for PannerNode", id),
120        }
121    }
122}