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