web_audio_api/node/
channel_merger.rs1use std::fmt::Debug;
2
3use crate::context::{AudioContextRegistration, BaseAudioContext};
4use crate::render::{
5    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
6};
7use crate::MAX_CHANNELS;
8
9use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelCountMode, ChannelInterpretation};
10
11#[track_caller]
20#[inline(always)]
21pub(crate) fn assert_valid_number_of_channels(number_of_channels: usize) {
22    assert!(
23        number_of_channels > 0 && number_of_channels <= MAX_CHANNELS,
24        "IndexSizeError - Invalid number of channels: {:?} is outside range [1, {:?}]",
25        number_of_channels,
26        MAX_CHANNELS
27    );
28}
29
30#[track_caller]
38#[inline(always)]
39fn assert_valid_channel_count(count: usize) {
40    assert!(
41        count == 1,
42        "InvalidStateError - channel count of ChannelMergerNode must be equal to 1"
43    );
44}
45
46#[track_caller]
54#[inline(always)]
55fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
56    assert!(
57        mode == ChannelCountMode::Explicit,
58        "InvalidStateError - channel count of ChannelMergerNode must be set to Explicit"
59    );
60}
61
62#[derive(Clone, Debug)]
67pub struct ChannelMergerOptions {
68    pub number_of_inputs: usize,
69    pub audio_node_options: AudioNodeOptions,
70}
71
72impl Default for ChannelMergerOptions {
73    fn default() -> Self {
74        Self {
75            number_of_inputs: 6,
76            audio_node_options: AudioNodeOptions {
77                channel_count: 1,
78                channel_count_mode: ChannelCountMode::Explicit,
79                channel_interpretation: ChannelInterpretation::Speakers,
80            },
81        }
82    }
83}
84
85#[derive(Debug)]
87pub struct ChannelMergerNode {
88    registration: AudioContextRegistration,
89    channel_config: ChannelConfig,
90    number_of_inputs: usize,
91}
92
93impl AudioNode for ChannelMergerNode {
94    fn registration(&self) -> &AudioContextRegistration {
95        &self.registration
96    }
97
98    fn channel_config(&self) -> &ChannelConfig {
99        &self.channel_config
100    }
101
102    fn set_channel_count(&self, count: usize) {
103        assert_valid_channel_count(count);
104    }
105
106    fn set_channel_count_mode(&self, mode: ChannelCountMode) {
107        assert_valid_channel_count_mode(mode);
108        self.channel_config
109            .set_count_mode(mode, self.registration());
110    }
111
112    fn number_of_inputs(&self) -> usize {
113        self.number_of_inputs
114    }
115
116    fn number_of_outputs(&self) -> usize {
117        1
118    }
119}
120
121impl ChannelMergerNode {
122    pub fn new<C: BaseAudioContext>(context: &C, options: ChannelMergerOptions) -> Self {
123        context.base().register(move |registration| {
124            assert_valid_number_of_channels(options.number_of_inputs);
125
126            assert_valid_channel_count(options.audio_node_options.channel_count);
127            assert_valid_channel_count_mode(options.audio_node_options.channel_count_mode);
128
129            let node = ChannelMergerNode {
130                registration,
131                channel_config: options.audio_node_options.into(),
132                number_of_inputs: options.number_of_inputs,
133            };
134
135            let render = ChannelMergerRenderer {};
136
137            (node, Box::new(render))
138        })
139    }
140}
141
142#[derive(Debug)]
143struct ChannelMergerRenderer {}
144
145impl AudioProcessor for ChannelMergerRenderer {
146    fn process(
147        &mut self,
148        inputs: &[AudioRenderQuantum],
149        outputs: &mut [AudioRenderQuantum],
150        _params: AudioParamValues<'_>,
151        _scope: &AudioWorkletGlobalScope,
152    ) -> bool {
153        let output = &mut outputs[0];
155
156        if inputs.iter().any(|input| !input.is_silent()) {
161            output.set_number_of_channels(inputs.len());
162
163            inputs.iter().enumerate().for_each(|(i, input)| {
164                *output.channel_data_mut(i) = input.channel_data(0).clone();
165            });
166        } else {
167            output.make_silent();
168        }
169
170        false
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use float_eq::assert_float_eq;
177
178    use crate::context::{BaseAudioContext, OfflineAudioContext};
179    use crate::node::{AudioNode, AudioScheduledSourceNode};
180
181    use super::*;
182
183    #[test]
184    #[should_panic]
185    fn test_invalid_constructor_options() {
186        let sample_rate = 48000.;
187        let context = OfflineAudioContext::new(1, 128, sample_rate);
188
189        let mut options = ChannelMergerOptions::default();
190        options.audio_node_options.channel_count = 2;
191
192        let _merger = ChannelMergerNode::new(&context, options);
193    }
194
195    #[test]
196    #[should_panic]
197    fn test_invalid_set_channel_count() {
198        let sample_rate = 48000.;
199        let context = OfflineAudioContext::new(1, 128, sample_rate);
200
201        let options = ChannelMergerOptions::default();
202        let merger = ChannelMergerNode::new(&context, options);
203        merger.set_channel_count(3);
204    }
205
206    #[test]
207    fn test_merge() {
208        let sample_rate = 48000.;
209        let mut context = OfflineAudioContext::new(2, 128, sample_rate);
210
211        let merger = context.create_channel_merger(2);
212        merger.connect(&context.destination());
213
214        let mut src1 = context.create_constant_source();
215        src1.offset().set_value(2.);
216        src1.connect_from_output_to_input(&merger, 0, 0);
217        src1.start();
218
219        let mut src2 = context.create_constant_source();
220        src2.offset().set_value(3.);
221        src2.connect_from_output_to_input(&merger, 0, 1);
222        src2.start();
223
224        let buffer = context.start_rendering_sync();
225
226        let left = buffer.get_channel_data(0);
227        assert_float_eq!(left, &[2.; 128][..], abs_all <= 0.);
228
229        let right = buffer.get_channel_data(1);
230        assert_float_eq!(right, &[3.; 128][..], abs_all <= 0.);
231    }
232
233    #[test]
234    fn test_merge_disconnect() {
235        let sample_rate = 48000.;
236        let length = 4 * 128;
237        let disconnect_at = length as f64 / sample_rate as f64 / 2.;
238        let mut context = OfflineAudioContext::new(2, length, sample_rate);
239
240        let merger = context.create_channel_merger(2);
241        merger.connect(&context.destination());
242
243        let mut src1 = context.create_constant_source();
244        src1.offset().set_value(2.);
245        src1.connect_from_output_to_input(&merger, 0, 0);
246        src1.start();
247
248        let mut src2 = context.create_constant_source();
249        src2.offset().set_value(3.);
250        src2.connect_from_output_to_input(&merger, 0, 1);
251        src2.start();
252
253        context.suspend_sync(disconnect_at, move |_| src2.disconnect());
254
255        let buffer = context.start_rendering_sync();
256
257        let left = buffer.get_channel_data(0);
258        assert_float_eq!(left, &vec![2.; length][..], abs_all <= 0.);
259
260        let right = buffer.get_channel_data(1);
261        assert_float_eq!(
262            &right[0..length / 2],
263            &vec![3.; length / 2][..],
264            abs_all <= 0.
265        );
266        assert_float_eq!(
267            &right[length / 2..],
268            &vec![0.; length / 2][..],
269            abs_all <= 0.
270        );
271    }
272}