1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use block::{Chunk, Tick, FRAMES_PER_BLOCK};
use node::{AudioNodeEngine, BlockInfo};
use node::{AudioNodeType, ChannelInfo};
use param::{Param, ParamType};
use std::f32::consts::PI;

#[derive(Copy, Clone, Debug)]
pub struct StereoPannerOptions {
    pub pan: f32,
}

impl Default for StereoPannerOptions {
    fn default() -> Self {
        StereoPannerOptions { pan: 0. }
    }
}

#[derive(AudioNodeCommon)]
pub(crate) struct StereoPannerNode {
    channel_info: ChannelInfo,
    pan: Param,
}

impl StereoPannerNode {
    pub fn new(options: StereoPannerOptions, channel_info: ChannelInfo) -> Self {
        Self {
            channel_info,
            pan: Param::new(options.pan),
        }
    }

    pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool {
        self.pan.update(info, tick)
    }
}

impl AudioNodeEngine for StereoPannerNode {
    fn node_type(&self) -> AudioNodeType {
        AudioNodeType::StereoPannerNode
    }

    fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk {
        debug_assert!(inputs.len() == 1);

        {
            let block = &mut inputs.blocks[0];

            block.explicit_repeat();

            let mono = if block.chan_count() == 1 {
                block.resize_silence(2);
                true
            } else {
                debug_assert!(block.chan_count() == 2);
                false
            };

            let (l, r) = block.data_mut().split_at_mut(FRAMES_PER_BLOCK.0 as usize);
            let mut pan = self.pan.value();
            for frame in 0..FRAMES_PER_BLOCK.0 {
                let frame = Tick(frame);
                if self.update_parameters(info, frame) {
                    pan = self.pan.value();
                }

                // https://webaudio.github.io/web-audio-api/#stereopanner-algorithm

                // clamp pan to [-1, 1]
                pan = if pan < -1. {
                    -1.
                } else if pan > 1. {
                    1.
                } else {
                    pan
                };

                let x = if mono {
                    (pan + 1.) / 2.
                } else if pan <= 0. {
                    pan + 1.
                } else {
                    pan
                };
                let x = x * PI / 2.;

                let mut gain_l = x.cos();
                let mut gain_r = x.sin();
                // 9. * PI / 2 is often slightly negative, clamp
                if gain_l <= 0. {
                    gain_l = 0.
                }
                if gain_r <= 0. {
                    gain_r = 0.;
                }

                let index = frame.0 as usize;
                if mono {
                    let input = l[index];
                    l[index] = input * gain_l;
                    r[index] = input * gain_r;
                } else if pan <= 0. {
                    l[index] = l[index] + r[index] * gain_l;
                    r[index] = r[index] * gain_r;
                } else {
                    r[index] = r[index] + l[index] * gain_r;
                    l[index] = l[index] * gain_l;
                }
            }
        }

        inputs
    }

    fn input_count(&self) -> u32 {
        1
    }

    fn get_param(&mut self, id: ParamType) -> &mut Param {
        match id {
            ParamType::Pan => &mut self.pan,
            _ => panic!("Unknown param {:?} for PannerNode", id),
        }
    }
}