Skip to main content

script/dom/audio/
convolvernode.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/. */
4use std::cell::Cell;
5
6use dom_struct::dom_struct;
7use js::context::JSContext;
8use js::rust::HandleObject;
9use script_bindings::codegen::GenericBindings::AudioBufferBinding::AudioBufferMethods;
10use script_bindings::codegen::GenericBindings::AudioNodeBinding::{
11    AudioNodeMethods, ChannelCountMode, ChannelInterpretation,
12};
13use script_bindings::codegen::GenericBindings::BaseAudioContextBinding::BaseAudioContextMethods;
14use script_bindings::codegen::GenericBindings::ConvolverNodeBinding::ConvolverNodeMethods;
15use script_bindings::reflector::reflect_dom_object_with_proto;
16use servo_media::audio::audio_node::{AudioNodeInit, AudioNodeMessage};
17use servo_media::audio::convolver_node::{ConvolverNodeMessage, ConvolverNodeOptions};
18use servo_media::audio::graph::NodeId;
19
20use crate::conversions::ConvertWithCx;
21use crate::dom::audio::audionode::AudioNodeOptionsHelper;
22use crate::dom::audio::baseaudiocontext::BaseAudioContext;
23use crate::dom::audionode::UnwrappedAudioNodeOptions;
24use crate::dom::bindings::codegen::Bindings::ConvolverNodeBinding::ConvolverOptions;
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::root::{DomRoot, MutNullableDom};
27use crate::dom::types::{AudioBuffer, AudioNode};
28use crate::dom::window::Window;
29
30#[dom_struct]
31pub(crate) struct ConvolverNode {
32    source_node: AudioNode,
33    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-buffer>
34    buffer: MutNullableDom<AudioBuffer>,
35    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-normalize>
36    normalize: Cell<bool>,
37}
38
39impl ConvolverNode {
40    fn new_inherited(
41        node_id: Option<NodeId>,
42        context: &BaseAudioContext,
43        node_options: UnwrappedAudioNodeOptions,
44        normalize: bool,
45    ) -> Self {
46        ConvolverNode {
47            source_node: AudioNode::new_inherited(node_id, context, node_options, 1, 1),
48            buffer: Default::default(),
49            normalize: Cell::new(normalize),
50        }
51    }
52}
53
54impl ConvolverNodeMethods<crate::DomTypeHolder> for ConvolverNode {
55    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-convolvernode>
56    fn Constructor(
57        cx: &mut JSContext,
58        window: &Window,
59        proto: Option<HandleObject>,
60        context: &BaseAudioContext,
61        options: &ConvolverOptions,
62    ) -> Fallible<DomRoot<ConvolverNode>> {
63        let node_options = options.parent.unwrap_or(
64            2,
65            ChannelCountMode::Clamped_max,
66            ChannelInterpretation::Speakers,
67        );
68        if AudioNode::channel_count_check(node_options.count) {
69            return Err(Error::NotSupported(Some(String::from(
70                "Channel count not supported",
71            ))));
72        }
73        // <https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints>
74        // > The channel count cannot be greater than two, and a NotSupportedError exception
75        // > MUST be thrown for any attempt to change it to a value greater than two.
76        if node_options.count > 2 {
77            return Err(Error::NotSupported(Some(String::from(
78                "Channel count greater than 2 is not supported",
79            ))));
80        }
81        // <https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints>
82        // > The channel count mode cannot be set to "max", and a NotSupportedError exception
83        // > MUST be thrown for any attempt to set it to "max".
84        if let ChannelCountMode::Max = node_options.mode {
85            return Err(Error::NotSupported(Some(String::from(
86                "Channel count mode Max is not supported",
87            ))));
88        }
89        let convolver_options = AudioNodeInit::ConvolverNode(options.convert(cx));
90        let node_id =
91            AudioNode::generate_audio_node_id(cx, &node_options, convolver_options, context);
92
93        // Step 1. Set the attributes normalize to the inverse of the value of disableNormalization.
94        let node = reflect_dom_object_with_proto(
95            cx,
96            Box::new(ConvolverNode::new_inherited(
97                node_id,
98                context,
99                node_options,
100                !options.disableNormalization,
101            )),
102            window,
103            proto,
104        );
105        // Step 2. If buffer exists, set the buffer attribute to its value.
106        if let Some(Some(ref buffer)) = options.buffer {
107            node.SetBuffer(cx, Some(buffer))?;
108        }
109        Ok(node)
110    }
111
112    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-buffer>
113    fn GetBuffer(&self) -> Fallible<Option<DomRoot<AudioBuffer>>> {
114        Ok(self.buffer.get())
115    }
116
117    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-buffer>
118    fn SetBuffer(&self, cx: &mut JSContext, new_buffer: Option<&AudioBuffer>) -> Fallible<()> {
119        let Some(new_buffer) = new_buffer else {
120            debug!("Setting buffer to null");
121            self.buffer.set(new_buffer);
122            return Ok(());
123        };
124        // Step 1. If the buffer number of channels is not 1, 2, 4,
125        // or if the sample-rate of the buffer is not the same as the sample-rate of its associated BaseAudioContext,
126        // a NotSupportedError MUST be thrown.
127        if ![1u32, 2, 4].contains(&new_buffer.NumberOfChannels()) {
128            return Err(Error::NotSupported(Some(String::from(
129                "Buffer number of channels is not 1, 2 or 4",
130            ))));
131        }
132        if new_buffer.SampleRate() != self.source_node.Context().SampleRate() {
133            return Err(Error::NotSupported(Some(String::from(
134                "Buffer sample rate not equal to sample rate of convolver node's associated base audio context",
135            ))));
136        }
137        self.buffer.set(Some(new_buffer));
138
139        // Step 2. Acquire the AudioBuffer content.
140        if let Some(buffer) = self.buffer.get() {
141            let buffer = buffer.get_channels(cx);
142            if buffer.is_some() {
143                self.source_node.message(AudioNodeMessage::ConvolverNode(
144                    ConvolverNodeMessage::SetBuffer(buffer),
145                ));
146            }
147        }
148
149        Ok(())
150    }
151
152    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-normalize>
153    fn Normalize(&self) -> bool {
154        self.normalize.get()
155    }
156
157    /// <https://webaudio.github.io/web-audio-api/#dom-convolvernode-normalize>
158    fn SetNormalize(&self, value: bool) {
159        self.normalize.set(value);
160        self.source_node.message(AudioNodeMessage::ConvolverNode(
161            ConvolverNodeMessage::SetNormalize(value),
162        ));
163    }
164}
165
166impl ConvertWithCx<ConvolverNodeOptions> for ConvolverOptions {
167    fn convert(&self, cx: &mut JSContext) -> ConvolverNodeOptions {
168        ConvolverNodeOptions {
169            buffer: self
170                .buffer
171                .as_ref()
172                .and_then(|b| b.as_ref()?.get_channels(cx).map(|buffer| (*buffer).clone())),
173            normalize: !self.disableNormalization,
174        }
175    }
176}