Skip to main content

web_audio_api/node/
convolver.rs

1use std::any::Any;
2
3use fft_convolver::FFTConvolver;
4
5use crate::buffer::AudioBuffer;
6use crate::context::{AudioContextRegistration, BaseAudioContext};
7use crate::render::{
8    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
9};
10use crate::RENDER_QUANTUM_SIZE;
11
12use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelCountMode, ChannelInterpretation};
13
14/// Scale buffer by an equal-power normalization
15// see - <https://webaudio.github.io/web-audio-api/#dom-convolvernode-normalize>
16fn normalize_buffer(buffer: &AudioBuffer) -> f32 {
17    let gain_calibration = 0.00125;
18    let gain_calibration_sample_rate = 44100.;
19    let min_power = 0.000125;
20
21    // Normalize by RMS power.
22    let number_of_channels = buffer.number_of_channels();
23    let length = buffer.length();
24    let sample_rate = buffer.sample_rate();
25
26    let mut power: f32 = buffer
27        .channels()
28        .iter()
29        .map(|c| c.as_slice().iter().map(|&s| s * s).sum::<f32>())
30        .sum();
31
32    power = (power / (number_of_channels * length) as f32).sqrt();
33
34    // Protect against accidental overload.
35    if !power.is_finite() || power.is_nan() || power < min_power {
36        power = min_power;
37    }
38
39    let mut scale = 1. / power;
40
41    // Calibrate to make perceived volume same as unprocessed.
42    scale *= gain_calibration;
43
44    // Scale depends on sample-rate.
45    scale *= gain_calibration_sample_rate / sample_rate;
46
47    // True-stereo compensation.
48    if number_of_channels == 4 {
49        scale *= 0.5;
50    }
51
52    scale
53}
54
55/// `ConvolverNode` options
56//dictionary ConvolverOptions : AudioNodeOptions {
57//  AudioBuffer? buffer;
58//  boolean disableNormalization = false;
59//};
60#[derive(Clone, Debug)]
61pub struct ConvolverOptions {
62    /// The desired buffer for the ConvolverNode
63    pub buffer: Option<AudioBuffer>,
64    /// The opposite of the desired initial value for the normalize attribute
65    pub disable_normalization: bool,
66    /// AudioNode options
67    pub audio_node_options: AudioNodeOptions,
68}
69
70impl Default for ConvolverOptions {
71    fn default() -> Self {
72        Self {
73            buffer: None,
74            disable_normalization: false,
75            audio_node_options: AudioNodeOptions {
76                channel_count: 2,
77                channel_count_mode: ChannelCountMode::ClampedMax,
78                channel_interpretation: ChannelInterpretation::Speakers,
79            },
80        }
81    }
82}
83
84/// Assert that the channel count is valid for the ConvolverNode
85/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints>
86///
87/// # Panics
88///
89/// This function panics if given count is greater than 2
90///
91#[track_caller]
92#[inline(always)]
93fn assert_valid_channel_count(count: usize) {
94    assert!(
95        count <= 2,
96        "NotSupportedError - ConvolverNode channel count cannot be greater than two"
97    );
98}
99
100/// Assert that the channel count mode is valid for the ConvolverNode
101/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints>
102///
103/// # Panics
104///
105/// This function panics if given count mode is [`ChannelCountMode::Max`]
106///
107#[track_caller]
108#[inline(always)]
109fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
110    assert_ne!(
111        mode,
112        ChannelCountMode::Max,
113        "NotSupportedError - ConvolverNode channel count mode cannot be set to max"
114    );
115}
116
117/// Processing node which applies a linear convolution effect given an impulse response.
118///
119/// - MDN documentation: <https://developer.mozilla.org/en-US/docs/Web/API/ConvolverNode>
120/// - specification: <https://webaudio.github.io/web-audio-api/#ConvolverNode>
121/// - see also: [`BaseAudioContext::create_convolver`]
122///
123/// The current implementation only handles mono-to-mono convolutions. The provided impulse
124/// response buffer and the input signal will be downmixed appropriately.
125///
126/// # Usage
127///
128/// ```no_run
129/// use std::fs::File;
130///
131/// use web_audio_api::context::{AudioContext, BaseAudioContext};
132/// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode, ConvolverNode, ConvolverOptions};
133///
134/// let context = AudioContext::default();
135/// let file = File::open("samples/vocals-dry.wav").unwrap();
136/// let audio_buffer = context.decode_audio_data_sync(file).unwrap();
137///
138/// let impulse_file = File::open("samples/small-room-response.wav").unwrap();
139/// let impulse_buffer = context.decode_audio_data_sync(impulse_file).unwrap();
140///
141/// let mut src = context.create_buffer_source();
142/// src.set_buffer(audio_buffer);
143///
144/// let mut convolve = ConvolverNode::new(&context, ConvolverOptions::default());
145/// convolve.set_buffer(impulse_buffer);
146///
147/// src.connect(&convolve);
148/// convolve.connect(&context.destination());
149/// src.start();
150/// std::thread::sleep(std::time::Duration::from_millis(4_000));
151/// ```
152///
153/// # Examples
154///
155/// - `cargo run --release --example convolution`
156///
157#[derive(Debug)]
158pub struct ConvolverNode {
159    /// Represents the node instance and its associated audio context
160    registration: AudioContextRegistration,
161    /// Info about audio node channel configuration
162    channel_config: ChannelConfig,
163    /// Perform equal power normalization on response buffer
164    normalize: bool,
165    /// The response buffer, nullable
166    buffer: Option<AudioBuffer>,
167}
168
169impl AudioNode for ConvolverNode {
170    fn registration(&self) -> &AudioContextRegistration {
171        &self.registration
172    }
173
174    fn channel_config(&self) -> &ChannelConfig {
175        &self.channel_config
176    }
177
178    fn number_of_inputs(&self) -> usize {
179        1
180    }
181
182    fn number_of_outputs(&self) -> usize {
183        1
184    }
185
186    // see <https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints>
187    fn set_channel_count(&self, count: usize) {
188        assert_valid_channel_count(count);
189        self.channel_config.set_count(count, self.registration());
190    }
191
192    // see <https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints>
193    fn set_channel_count_mode(&self, mode: ChannelCountMode) {
194        assert_valid_channel_count_mode(mode);
195        self.channel_config
196            .set_count_mode(mode, self.registration());
197    }
198}
199
200impl ConvolverNode {
201    /// returns a `ConvolverNode` instance
202    ///
203    /// # Arguments
204    ///
205    /// * `context` - audio context in which the audio node will live.
206    /// * `options` - convolver options
207    ///
208    /// # Panics
209    ///
210    /// Panics when an AudioBuffer is provided via the `ConvolverOptions` with a sample rate
211    /// different from the audio context sample rate.
212    pub fn new<C: BaseAudioContext>(context: &C, options: ConvolverOptions) -> Self {
213        let ConvolverOptions {
214            buffer,
215            disable_normalization,
216            audio_node_options,
217        } = options;
218
219        assert_valid_channel_count(audio_node_options.channel_count);
220        assert_valid_channel_count_mode(audio_node_options.channel_count_mode);
221
222        let mut node = context.base().register(move |registration| {
223            let renderer = ConvolverRenderer {
224                convolvers: None,
225                impulse_length: 0,
226                impulse_number_of_channels: 0,
227                tail_count: 0,
228            };
229
230            let node = Self {
231                registration,
232                channel_config: audio_node_options.into(),
233                normalize: !disable_normalization,
234                buffer: None,
235            };
236
237            (node, Box::new(renderer))
238        });
239
240        // renderer has been sent to render thread, we can send it messages
241        if let Some(buffer) = buffer {
242            node.set_buffer(buffer);
243        }
244
245        node
246    }
247
248    /// Get the current impulse response buffer
249    pub fn buffer(&self) -> Option<&AudioBuffer> {
250        self.buffer.as_ref()
251    }
252
253    /// Set or update the impulse response buffer
254    ///
255    /// # Panics
256    ///
257    /// Panics when the sample rate of the provided AudioBuffer differs from the audio context
258    /// sample rate.
259    pub fn set_buffer(&mut self, buffer: AudioBuffer) {
260        // If the buffer number of channels is not 1, 2, 4, or if the sample-rate of the buffer is
261        // not the same as the sample-rate of its associated BaseAudioContext, a NotSupportedError
262        // MUST be thrown.
263
264        let sample_rate = buffer.sample_rate();
265        assert_eq!(
266            sample_rate,
267            self.context().sample_rate(),
268            "NotSupportedError - sample rate of the convolution buffer must match the audio context"
269        );
270
271        let number_of_channels = buffer.number_of_channels();
272        assert!(
273            [1, 2, 4].contains(&number_of_channels),
274            "NotSupportedError - the convolution buffer must consist of 1, 2 or 4 channels"
275        );
276
277        // normalize before padding because the length of the buffer affects the scale
278        let scale = if self.normalize {
279            normalize_buffer(&buffer)
280        } else {
281            1.
282        };
283
284        let mut convolvers = Vec::<FFTConvolver<f32>>::new();
285        // @note - value defined by "rule of thumb", to be explored further
286        let partition_size = RENDER_QUANTUM_SIZE * 8;
287
288        // Handle multichannel IR
289        // cf. https://webaudio.github.io/web-audio-api/#Convolution-channel-configurations
290        // Note that in case of mono IR we create 2 convolvers to properly handle stereo input
291        for index in 0..number_of_channels.max(2) {
292            // make sure we don't try to access an inexisting channel, cf. note above
293            let channel = index.min(number_of_channels - 1);
294
295            let mut scaled_channel = vec![0.; buffer.length()];
296            scaled_channel
297                .iter_mut()
298                .zip(buffer.get_channel_data(channel))
299                .for_each(|(o, i)| *o = *i * scale);
300
301            let mut convolver = FFTConvolver::<f32>::default();
302            convolver
303                .init(partition_size, &scaled_channel)
304                .expect("Unable to initialize convolution engine");
305
306            convolvers.push(convolver);
307        }
308
309        let msg = ConvolverInfosMessage {
310            convolvers: Some(convolvers),
311            impulse_length: buffer.length(),
312            impulse_number_of_channels: number_of_channels,
313        };
314
315        self.registration.post_message(msg);
316        self.buffer = Some(buffer);
317    }
318
319    /// Denotes if the response buffer will be scaled with an equal-power normalization
320    pub fn normalize(&self) -> bool {
321        self.normalize
322    }
323
324    /// Update the `normalize` setting. This will only have an effect when `set_buffer` is called.
325    pub fn set_normalize(&mut self, value: bool) {
326        self.normalize = value;
327    }
328}
329
330struct ConvolverInfosMessage {
331    convolvers: Option<Vec<FFTConvolver<f32>>>,
332    impulse_length: usize,
333    impulse_number_of_channels: usize,
334}
335
336struct ConvolverRenderer {
337    convolvers: Option<Vec<FFTConvolver<f32>>>,
338    impulse_length: usize,
339    impulse_number_of_channels: usize,
340    tail_count: usize,
341}
342
343impl AudioProcessor for ConvolverRenderer {
344    fn process(
345        &mut self,
346        inputs: &[AudioRenderQuantum],
347        outputs: &mut [AudioRenderQuantum],
348        _params: AudioParamValues<'_>,
349        _scope: &AudioWorkletGlobalScope,
350    ) -> bool {
351        // single input/output node
352        let input = &inputs[0];
353        let output = &mut outputs[0];
354
355        // handle tail time and active processing
356        // return early if input is silent and we reached the end of the convolution
357        if input.is_silent() {
358            if self.tail_count >= self.impulse_length {
359                output.make_silent();
360                return false;
361            }
362
363            self.tail_count += RENDER_QUANTUM_SIZE;
364        } else {
365            self.tail_count = 0;
366        }
367
368        let convolvers = match &mut self.convolvers {
369            None => {
370                // no convolution buffer set, passthrough
371                *output = input.clone();
372                return !input.is_silent();
373            }
374            Some(convolvers) => convolvers,
375        };
376
377        // https://webaudio.github.io/web-audio-api/#Convolution-channel-configurations
378        match (input.number_of_channels(), self.impulse_number_of_channels) {
379            (1, 1) => {
380                output.set_number_of_channels(1);
381
382                let i = &input.channel_data(0)[..];
383                let o = &mut output.channel_data_mut(0)[..];
384                let _ = convolvers[0].process(i, o);
385            }
386            (1, 2) => {
387                output.set_number_of_channels(2);
388
389                let i = &input.channel_data(0)[..];
390
391                let o_left = &mut output.channel_data_mut(0)[..];
392                let _ = convolvers[0].process(i, o_left);
393
394                let o_right = &mut output.channel_data_mut(1)[..];
395                let _ = convolvers[1].process(i, o_right);
396            }
397            (2, 1) => {
398                output.set_number_of_channels(2);
399
400                let i_left = &input.channel_data(0)[..];
401                let o_left = &mut output.channel_data_mut(0)[..];
402                let _ = convolvers[0].process(i_left, o_left);
403
404                let i_right = &input.channel_data(1)[..];
405                let o_right = &mut output.channel_data_mut(1)[..];
406                let _ = convolvers[1].process(i_right, o_right);
407            }
408            (2, 2) => {
409                output.set_number_of_channels(2);
410
411                let i_left = &input.channel_data(0)[..];
412                let o_left = &mut output.channel_data_mut(0)[..];
413                let _ = convolvers[0].process(i_left, o_left);
414
415                let i_right = &input.channel_data(1)[..];
416                let o_right = &mut output.channel_data_mut(1)[..];
417                let _ = convolvers[1].process(i_right, o_right);
418            }
419            (2, 4) => {
420                output.set_number_of_channels(4);
421
422                let i_left = &input.channel_data(0)[..];
423
424                let o_0 = &mut output.channel_data_mut(0)[..];
425                let _ = convolvers[0].process(i_left, o_0);
426                let o_1 = &mut output.channel_data_mut(1)[..];
427                let _ = convolvers[1].process(i_left, o_1);
428
429                let i_right = &input.channel_data(1)[..];
430
431                let o_2 = &mut output.channel_data_mut(2)[..];
432                let _ = convolvers[2].process(i_right, o_2);
433                let o_3 = &mut output.channel_data_mut(3)[..];
434                let _ = convolvers[3].process(i_right, o_3);
435
436                // mix output back to stereo
437                let o_2 = output.channel_data(2).clone();
438                let o_3 = output.channel_data(3).clone();
439
440                output
441                    .channel_data_mut(0)
442                    .iter_mut()
443                    .zip(o_2.iter())
444                    .for_each(|(l, sl)| *l += *sl);
445
446                output
447                    .channel_data_mut(1)
448                    .iter_mut()
449                    .zip(o_3.iter())
450                    .for_each(|(r, sr)| *r += *sr);
451
452                output.set_number_of_channels(2);
453            }
454            (1, 4) => {
455                output.set_number_of_channels(4);
456
457                let i = &input.channel_data(0)[..];
458
459                let o_0 = &mut output.channel_data_mut(0)[..];
460                let _ = convolvers[0].process(i, o_0);
461                let o_1 = &mut output.channel_data_mut(1)[..];
462                let _ = convolvers[1].process(i, o_1);
463                let o_2 = &mut output.channel_data_mut(2)[..];
464                let _ = convolvers[2].process(i, o_2);
465                let o_3 = &mut output.channel_data_mut(3)[..];
466                let _ = convolvers[3].process(i, o_3);
467
468                // mix output back to stereo
469                let o_2 = output.channel_data(2).clone();
470                let o_3 = output.channel_data(3).clone();
471
472                output
473                    .channel_data_mut(0)
474                    .iter_mut()
475                    .zip(o_2.iter())
476                    .for_each(|(l, sl)| *l += *sl);
477
478                output
479                    .channel_data_mut(1)
480                    .iter_mut()
481                    .zip(o_3.iter())
482                    .for_each(|(r, sr)| *r += *sr);
483
484                output.set_number_of_channels(2);
485            }
486            _ => unreachable!(),
487        }
488
489        true
490    }
491
492    fn onmessage(&mut self, msg: &mut dyn Any) {
493        if let Some(msg) = msg.downcast_mut::<ConvolverInfosMessage>() {
494            let ConvolverInfosMessage {
495                convolvers,
496                impulse_length,
497                impulse_number_of_channels,
498            } = msg;
499            // Avoid deallocation in the render thread by swapping the convolver.
500            std::mem::swap(&mut self.convolvers, convolvers);
501            self.impulse_length = *impulse_length;
502            self.impulse_number_of_channels = *impulse_number_of_channels;
503
504            return;
505        }
506
507        log::warn!("ConvolverRenderer: Dropping incoming message {msg:?}");
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use float_eq::assert_float_eq;
514
515    use crate::context::{BaseAudioContext, OfflineAudioContext};
516    use crate::node::{AudioBufferSourceNode, AudioBufferSourceOptions, AudioScheduledSourceNode};
517
518    use super::*;
519
520    #[test]
521    #[should_panic]
522    fn test_buffer_sample_rate_matches() {
523        let context = OfflineAudioContext::new(1, 128, 44100.);
524
525        let ir = vec![1.];
526        let ir = AudioBuffer::from(vec![ir; 1], 48000.); // sample_rate differs
527        let options = ConvolverOptions {
528            buffer: Some(ir),
529            ..ConvolverOptions::default()
530        };
531
532        let _ = ConvolverNode::new(&context, options);
533    }
534
535    #[test]
536    #[should_panic]
537    fn test_buffer_must_have_1_2_4_channels() {
538        let context = OfflineAudioContext::new(1, 128, 48000.);
539
540        let ir = vec![1.];
541        let ir = AudioBuffer::from(vec![ir; 3], 48000.); // three channels
542        let options = ConvolverOptions {
543            buffer: Some(ir),
544            ..ConvolverOptions::default()
545        };
546
547        let _ = ConvolverNode::new(&context, options);
548    }
549
550    #[test]
551    fn test_constructor_options_buffer() {
552        let sample_rate = 44100.;
553        let mut context = OfflineAudioContext::new(1, 10, sample_rate);
554
555        let ir = vec![1.];
556        let calibration = 0.00125;
557        let channel_data = vec![0., 1., 0., -1., 0.];
558        let expected = [0., calibration, 0., -calibration, 0., 0., 0., 0., 0., 0.];
559
560        // identity ir
561        let ir = AudioBuffer::from(vec![ir; 1], sample_rate);
562        let options = ConvolverOptions {
563            buffer: Some(ir),
564            ..ConvolverOptions::default()
565        };
566        let conv = ConvolverNode::new(&context, options);
567        conv.connect(&context.destination());
568
569        let buffer = AudioBuffer::from(vec![channel_data; 1], sample_rate);
570        let mut src = context.create_buffer_source();
571        src.connect(&conv);
572        src.set_buffer(buffer);
573        src.start();
574
575        let output = context.start_rendering_sync();
576
577        assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
578    }
579
580    fn test_convolve(signal: &[f32], impulse_resp: Option<Vec<f32>>, length: usize) -> AudioBuffer {
581        let sample_rate = 44100.;
582        let mut context = OfflineAudioContext::new(1, length, sample_rate);
583
584        let input = AudioBuffer::from(vec![signal.to_vec()], sample_rate);
585        let mut src = AudioBufferSourceNode::new(&context, AudioBufferSourceOptions::default());
586        src.set_buffer(input);
587        src.start();
588
589        let mut conv = ConvolverNode::new(&context, ConvolverOptions::default());
590        if let Some(ir) = impulse_resp {
591            conv.set_buffer(AudioBuffer::from(vec![ir.to_vec()], sample_rate));
592        }
593
594        src.connect(&conv);
595        conv.connect(&context.destination());
596
597        context.start_rendering_sync()
598    }
599
600    #[test]
601    fn test_passthrough() {
602        let output = test_convolve(&[0., 1., 0., -1., 0.], None, 10);
603        let expected = [0., 1., 0., -1., 0., 0., 0., 0., 0., 0.];
604        assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
605    }
606
607    #[test]
608    fn test_empty() {
609        let ir = vec![];
610        let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
611        let expected = [0.; 10];
612        assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
613    }
614
615    #[test]
616    fn test_zeroed() {
617        let ir = vec![0., 0., 0., 0., 0., 0.];
618        let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
619        let expected = [0.; 10];
620        assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
621    }
622
623    #[test]
624    fn test_identity() {
625        let ir = vec![1.];
626        let calibration = 0.00125;
627        let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
628        let expected = [0., calibration, 0., -calibration, 0., 0., 0., 0., 0., 0.];
629        assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
630    }
631
632    #[test]
633    fn test_two_id() {
634        let ir = vec![1., 1.];
635        let calibration = 0.00125;
636        let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
637        let expected = [
638            0.,
639            calibration,
640            calibration,
641            -calibration,
642            -calibration,
643            0.,
644            0.,
645            0.,
646            0.,
647            0.,
648        ];
649        assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
650    }
651
652    #[test]
653    fn test_should_have_tail_time() {
654        // impulse response of length 256
655        const IR_LEN: usize = 256;
656        let ir = vec![1.; IR_LEN];
657
658        // unity input signal
659        let input = &[1.];
660
661        // render into a buffer of size 512
662        let output = test_convolve(input, Some(ir), 512);
663
664        // we expect non-zero output in the range 0 to IR_LEN
665        let output = output.channel_data(0).as_slice();
666        assert!(!output[..IR_LEN].iter().any(|v| *v <= 1E-6));
667        assert_float_eq!(&output[IR_LEN..], &[0.; 512 - IR_LEN][..], abs_all <= 1E-6);
668    }
669
670    #[test]
671    fn test_channel_config_1_chan_in_1_chan_ir() {
672        let number_of_channels = 1;
673        let length = 128;
674        let sample_rate = 44100.;
675        let mut context = OfflineAudioContext::new(number_of_channels, length, sample_rate);
676
677        let input = AudioBuffer::from(vec![vec![1.]], sample_rate);
678        let ir = AudioBuffer::from(vec![vec![0., 1.]], sample_rate);
679
680        let mut src = AudioBufferSourceNode::new(
681            &context,
682            AudioBufferSourceOptions {
683                buffer: Some(input),
684                ..AudioBufferSourceOptions::default()
685            },
686        );
687
688        let conv = ConvolverNode::new(
689            &context,
690            ConvolverOptions {
691                buffer: Some(ir),
692                disable_normalization: true,
693                ..ConvolverOptions::default()
694            },
695        );
696
697        src.connect(&conv);
698        conv.connect(&context.destination());
699        src.start();
700
701        let result = context.start_rendering_sync();
702
703        let mut expected = [0.; 128];
704        expected[1] = 1.;
705
706        assert_float_eq!(
707            result.get_channel_data(0)[..],
708            expected[..],
709            abs_all <= 1e-7
710        );
711    }
712
713    #[test]
714    fn test_channel_config_1_chan_in_2_chan_ir() {
715        let number_of_channels = 2;
716        let length = 128;
717        let sample_rate = 44100.;
718        let mut context = OfflineAudioContext::new(number_of_channels, length, sample_rate);
719
720        let input = AudioBuffer::from(vec![vec![1.]], sample_rate);
721        let ir = AudioBuffer::from(vec![vec![0., 1., 0.], vec![0., 0., 1.]], sample_rate);
722
723        let mut src = AudioBufferSourceNode::new(
724            &context,
725            AudioBufferSourceOptions {
726                buffer: Some(input),
727                ..AudioBufferSourceOptions::default()
728            },
729        );
730
731        let conv = ConvolverNode::new(
732            &context,
733            ConvolverOptions {
734                buffer: Some(ir),
735                disable_normalization: true,
736                ..ConvolverOptions::default()
737            },
738        );
739
740        src.connect(&conv);
741        conv.connect(&context.destination());
742        src.start();
743
744        let result = context.start_rendering_sync();
745
746        let mut expected_left = [0.; 128];
747        expected_left[1] = 1.;
748
749        let mut expected_right = [0.; 128];
750        expected_right[2] = 1.;
751
752        assert_eq!(result.number_of_channels(), 2);
753        assert_float_eq!(
754            result.get_channel_data(0)[..],
755            expected_left[..],
756            abs_all <= 1e-7
757        );
758        assert_float_eq!(
759            result.get_channel_data(1)[..],
760            expected_right[..],
761            abs_all <= 1e-7
762        );
763    }
764
765    #[test]
766    fn test_channel_config_2_chan_in_1_chan_ir() {
767        let number_of_channels = 2;
768        let length = 128;
769        let sample_rate = 44100.;
770        let mut context = OfflineAudioContext::new(number_of_channels, length, sample_rate);
771
772        let input = AudioBuffer::from(vec![vec![1., 0.], vec![0., 1.]], sample_rate);
773        let ir = AudioBuffer::from(vec![vec![0., 1.]], sample_rate);
774
775        let mut src = AudioBufferSourceNode::new(
776            &context,
777            AudioBufferSourceOptions {
778                buffer: Some(input),
779                ..AudioBufferSourceOptions::default()
780            },
781        );
782
783        let conv = ConvolverNode::new(
784            &context,
785            ConvolverOptions {
786                buffer: Some(ir),
787                disable_normalization: true,
788                ..ConvolverOptions::default()
789            },
790        );
791
792        src.connect(&conv);
793        conv.connect(&context.destination());
794        src.start();
795
796        let result = context.start_rendering_sync();
797
798        let mut expected_left = [0.; 128];
799        expected_left[1] = 1.;
800
801        let mut expected_right = [0.; 128];
802        expected_right[2] = 1.;
803
804        assert_eq!(result.number_of_channels(), 2);
805        assert_float_eq!(
806            result.get_channel_data(0)[..],
807            expected_left[..],
808            abs_all <= 1e-7
809        );
810        assert_float_eq!(
811            result.get_channel_data(1)[..],
812            expected_right[..],
813            abs_all <= 1e-7
814        );
815    }
816
817    #[test]
818    fn test_channel_config_2_chan_in_2_chan_ir() {
819        let number_of_channels = 2;
820        let length = 128;
821        let sample_rate = 44100.;
822        let mut context = OfflineAudioContext::new(number_of_channels, length, sample_rate);
823
824        let input = AudioBuffer::from(vec![vec![1., 0.], vec![0., 1.]], sample_rate);
825        let ir = AudioBuffer::from(vec![vec![0., 1., 0.], vec![0., 0., 1.]], sample_rate);
826
827        let mut src = AudioBufferSourceNode::new(
828            &context,
829            AudioBufferSourceOptions {
830                buffer: Some(input),
831                ..AudioBufferSourceOptions::default()
832            },
833        );
834
835        let conv = ConvolverNode::new(
836            &context,
837            ConvolverOptions {
838                buffer: Some(ir),
839                disable_normalization: true,
840                ..ConvolverOptions::default()
841            },
842        );
843
844        src.connect(&conv);
845        conv.connect(&context.destination());
846        src.start();
847
848        let result = context.start_rendering_sync();
849
850        let mut expected_left = [0.; 128];
851        expected_left[1] = 1.;
852
853        let mut expected_right = [0.; 128];
854        expected_right[3] = 1.;
855
856        assert_eq!(result.number_of_channels(), 2);
857        assert_float_eq!(
858            result.get_channel_data(0)[..],
859            expected_left[..],
860            abs_all <= 1e-7
861        );
862        assert_float_eq!(
863            result.get_channel_data(1)[..],
864            expected_right[..],
865            abs_all <= 1e-7
866        );
867    }
868
869    #[test]
870    fn test_channel_config_2_chan_in_4_chan_ir() {
871        let number_of_channels = 2;
872        let length = 128;
873        let sample_rate = 44100.;
874        let mut context = OfflineAudioContext::new(number_of_channels, length, sample_rate);
875
876        let input = AudioBuffer::from(vec![vec![1., 0.], vec![0., 1.]], sample_rate);
877        let ir = AudioBuffer::from(
878            vec![
879                vec![0., 1., 0., 0., 0.], // in 0 -> out 0
880                vec![0., 0., 1., 0., 0.], // in 0 -> out 1
881                vec![0., 0., 0., 1., 0.], // in 1 -> out 0
882                vec![0., 0., 0., 0., 1.], // in 1 -> out 1
883            ],
884            sample_rate,
885        );
886
887        let mut src = AudioBufferSourceNode::new(
888            &context,
889            AudioBufferSourceOptions {
890                buffer: Some(input),
891                ..AudioBufferSourceOptions::default()
892            },
893        );
894
895        let conv = ConvolverNode::new(
896            &context,
897            ConvolverOptions {
898                buffer: Some(ir),
899                disable_normalization: true,
900                ..ConvolverOptions::default()
901            },
902        );
903
904        src.connect(&conv);
905        conv.connect(&context.destination());
906        src.start();
907
908        let result = context.start_rendering_sync();
909
910        let mut expected_left = [0.; 128];
911        expected_left[1] = 1.;
912        expected_left[4] = 1.;
913
914        let mut expected_right = [0.; 128];
915        expected_right[2] = 1.;
916        expected_right[5] = 1.;
917
918        assert_eq!(result.number_of_channels(), 2);
919        assert_float_eq!(
920            result.get_channel_data(0)[..],
921            expected_left[..],
922            abs_all <= 1e-7
923        );
924        assert_float_eq!(
925            result.get_channel_data(1)[..],
926            expected_right[..],
927            abs_all <= 1e-7
928        );
929    }
930
931    #[test]
932    fn test_channel_config_1_chan_in_4_chan_ir() {
933        let number_of_channels = 2;
934        let length = 128;
935        let sample_rate = 44100.;
936        let mut context = OfflineAudioContext::new(number_of_channels, length, sample_rate);
937
938        let input = AudioBuffer::from(vec![vec![1., 0.]], sample_rate);
939        let ir = AudioBuffer::from(
940            vec![
941                vec![0., 1., 0., 0., 0.], // in 0 -> out 0
942                vec![0., 0., 1., 0., 0.], // in 0 -> out 1
943                vec![0., 0., 0., 1., 0.], // in 0 -> out 0
944                vec![0., 0., 0., 0., 1.], // in 0 -> out 1
945            ],
946            sample_rate,
947        );
948
949        let mut src = AudioBufferSourceNode::new(
950            &context,
951            AudioBufferSourceOptions {
952                buffer: Some(input),
953                ..AudioBufferSourceOptions::default()
954            },
955        );
956
957        let conv = ConvolverNode::new(
958            &context,
959            ConvolverOptions {
960                buffer: Some(ir),
961                disable_normalization: true,
962                ..ConvolverOptions::default()
963            },
964        );
965
966        src.connect(&conv);
967        conv.connect(&context.destination());
968        src.start();
969
970        let result = context.start_rendering_sync();
971
972        let mut expected_left = [0.; 128];
973        expected_left[1] = 1.;
974        expected_left[3] = 1.;
975
976        let mut expected_right = [0.; 128];
977        expected_right[2] = 1.;
978        expected_right[4] = 1.;
979
980        assert_eq!(result.number_of_channels(), 2);
981        assert_float_eq!(
982            result.get_channel_data(0)[..],
983            expected_left[..],
984            abs_all <= 1e-7
985        );
986        assert_float_eq!(
987            result.get_channel_data(1)[..],
988            expected_right[..],
989            abs_all <= 1e-7
990        );
991    }
992}