web_audio_api/node/
channel_splitter.rs

1use 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
11const DEFAULT_NUMBER_OF_OUTPUTS: usize = 6;
12
13/// Assert that the given number of channels is valid for a ChannelMergerNode
14///
15/// # Panics
16///
17/// This function will panic if:
18/// - the given number of channels is outside the [1, 32] range,
19///   32 being defined by the MAX_CHANNELS constant.
20///
21#[track_caller]
22#[inline(always)]
23pub(crate) fn assert_valid_number_of_channels(number_of_channels: usize) {
24    assert!(
25        number_of_channels > 0 && number_of_channels <= MAX_CHANNELS,
26        "IndexSizeError - Invalid number of channels: {:?} is outside range [1, {:?}]",
27        number_of_channels,
28        MAX_CHANNELS
29    );
30}
31
32/// Assert that the channel count is valid for the ChannelSplitterNode
33/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints>
34///
35/// # Panics
36///
37/// This function panics if given count is not equal to number of outputs
38///
39#[track_caller]
40#[inline(always)]
41fn assert_valid_channel_count(count: usize, number_of_outputs: usize) {
42    assert!(
43        count == number_of_outputs,
44        "InvalidStateError - channel count of ChannelSplitterNode must be equal to number of outputs"
45    );
46}
47
48/// Assert that the channel count mode is valid for the ChannelSplitterNode
49/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints>
50///
51/// # Panics
52///
53/// This function panics if the mode is not equal to Explicit
54///
55#[track_caller]
56#[inline(always)]
57fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
58    assert!(
59        mode == ChannelCountMode::Explicit,
60        "InvalidStateError - channel count of ChannelSplitterNode must be set to Explicit"
61    );
62}
63
64/// Assert that the channel interpretation is valid for the ChannelSplitterNode
65/// see <https://webaudio.github.io/web-audio-api/#audionode-channelinterpretation-constraints>
66///
67/// # Panics
68///
69/// This function panics if the mode is not equal to Explicit
70///
71#[track_caller]
72#[inline(always)]
73fn assert_valid_channel_interpretation(interpretation: ChannelInterpretation) {
74    assert!(
75        interpretation == ChannelInterpretation::Discrete,
76        "InvalidStateError - channel interpretation of ChannelSplitterNode must be set to Discrete"
77    );
78}
79
80/// Options for constructing a [`ChannelSplitterNode`]
81// dictionary ChannelSplitterOptions : AudioNodeOptions {
82//   unsigned long numberOfOutputs = 6;
83// };
84#[derive(Clone, Debug)]
85pub struct ChannelSplitterOptions {
86    pub number_of_outputs: usize,
87    pub audio_node_options: AudioNodeOptions,
88}
89
90impl Default for ChannelSplitterOptions {
91    fn default() -> Self {
92        Self {
93            number_of_outputs: DEFAULT_NUMBER_OF_OUTPUTS,
94            audio_node_options: AudioNodeOptions {
95                channel_count: DEFAULT_NUMBER_OF_OUTPUTS, // must be same as number_of_outputs
96                channel_count_mode: ChannelCountMode::Explicit,
97                channel_interpretation: ChannelInterpretation::Discrete,
98            },
99        }
100    }
101}
102
103/// AudioNode for accessing the individual channels of an audio stream in the routing graph
104#[derive(Debug)]
105pub struct ChannelSplitterNode {
106    registration: AudioContextRegistration,
107    channel_config: ChannelConfig,
108    number_of_outputs: usize,
109}
110
111impl AudioNode for ChannelSplitterNode {
112    fn registration(&self) -> &AudioContextRegistration {
113        &self.registration
114    }
115
116    fn channel_config(&self) -> &ChannelConfig {
117        &self.channel_config
118    }
119
120    fn set_channel_count(&self, count: usize) {
121        assert_valid_channel_count(count, self.number_of_outputs);
122    }
123
124    fn set_channel_count_mode(&self, mode: ChannelCountMode) {
125        assert_valid_channel_count_mode(mode);
126        self.channel_config
127            .set_count_mode(mode, self.registration());
128    }
129
130    fn set_channel_interpretation(&self, interpretation: ChannelInterpretation) {
131        assert_valid_channel_interpretation(interpretation);
132        self.channel_config
133            .set_interpretation(interpretation, self.registration());
134    }
135
136    fn number_of_inputs(&self) -> usize {
137        1
138    }
139
140    fn number_of_outputs(&self) -> usize {
141        self.number_of_outputs
142    }
143}
144
145impl ChannelSplitterNode {
146    pub fn new<C: BaseAudioContext>(context: &C, mut options: ChannelSplitterOptions) -> Self {
147        context.base().register(move |registration| {
148            assert_valid_number_of_channels(options.number_of_outputs);
149
150            // if channel count has been explicitly set, we need to check
151            // its value against number of outputs
152            if options.audio_node_options.channel_count != DEFAULT_NUMBER_OF_OUTPUTS {
153                assert_valid_channel_count(
154                    options.audio_node_options.channel_count,
155                    options.number_of_outputs,
156                );
157            }
158            options.audio_node_options.channel_count = options.number_of_outputs;
159
160            assert_valid_channel_count_mode(options.audio_node_options.channel_count_mode);
161            assert_valid_channel_interpretation(options.audio_node_options.channel_interpretation);
162
163            let node = ChannelSplitterNode {
164                registration,
165                channel_config: options.audio_node_options.into(),
166                number_of_outputs: options.number_of_outputs,
167            };
168
169            let render = ChannelSplitterRenderer {
170                number_of_outputs: options.number_of_outputs,
171            };
172
173            (node, Box::new(render))
174        })
175    }
176}
177
178#[derive(Debug)]
179struct ChannelSplitterRenderer {
180    pub number_of_outputs: usize,
181}
182
183impl AudioProcessor for ChannelSplitterRenderer {
184    fn process(
185        &mut self,
186        inputs: &[AudioRenderQuantum],
187        outputs: &mut [AudioRenderQuantum],
188        _params: AudioParamValues<'_>,
189        _scope: &AudioWorkletGlobalScope,
190    ) -> bool {
191        // single input node
192        let input = &inputs[0];
193
194        // assert number of outputs was correctly set by renderer
195        assert_eq!(self.number_of_outputs, outputs.len());
196
197        for (i, output) in outputs.iter_mut().enumerate() {
198            output.set_number_of_channels(1);
199
200            if i < input.number_of_channels() {
201                *output.channel_data_mut(0) = input.channel_data(i).clone();
202            } else {
203                // input does not have this channel filled, emit silence
204                output.make_silent();
205            }
206        }
207
208        false
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use float_eq::assert_float_eq;
215
216    use crate::context::{BaseAudioContext, OfflineAudioContext};
217    use crate::node::{AudioNode, AudioScheduledSourceNode};
218    use crate::AudioBuffer;
219
220    use super::*;
221
222    #[test]
223    fn test_valid_constructor_options() {
224        let sample_rate = 48000.;
225        let context = OfflineAudioContext::new(1, 128, sample_rate);
226
227        let options = ChannelSplitterOptions {
228            number_of_outputs: 2,
229            ..Default::default()
230        };
231
232        let splitter = ChannelSplitterNode::new(&context, options);
233        assert_eq!(splitter.number_of_outputs(), 2);
234        assert_eq!(splitter.channel_count(), 2);
235    }
236
237    #[test]
238    #[should_panic]
239    fn test_invalid_constructor_options() {
240        let sample_rate = 48000.;
241        let context = OfflineAudioContext::new(1, 128, sample_rate);
242
243        let mut options = ChannelSplitterOptions::default();
244        options.audio_node_options.channel_count = 7;
245
246        let _splitter = ChannelSplitterNode::new(&context, options);
247    }
248
249    #[test]
250    #[should_panic]
251    fn test_set_channel_count() {
252        let sample_rate = 48000.;
253        let context = OfflineAudioContext::new(1, 128, sample_rate);
254
255        let options = ChannelSplitterOptions::default();
256        let splitter = ChannelSplitterNode::new(&context, options);
257        splitter.set_channel_count(3);
258    }
259
260    #[test]
261    fn test_splitter() {
262        let sample_rate = 48000.;
263        let mut context = OfflineAudioContext::new(1, 128, sample_rate);
264
265        let splitter = context.create_channel_splitter(2);
266
267        // connect the 2nd output to the destination
268        splitter.connect_from_output_to_input(&context.destination(), 1, 0);
269
270        // create buffer with sample value 1. left, value -1. right
271        let audio_buffer = AudioBuffer::from(vec![vec![1.], vec![-1.]], 48000.);
272        let mut src = context.create_buffer_source();
273        src.set_buffer(audio_buffer);
274        src.set_loop(true);
275        src.start();
276        src.connect(&splitter);
277
278        let buffer = context.start_rendering_sync();
279
280        let mono = buffer.get_channel_data(0);
281        assert_float_eq!(mono, &[-1.; 128][..], abs_all <= 0.);
282    }
283}