Skip to main content

script/dom/audio/
audiobuffersourcenode.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/. */
4
5use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::rust::HandleObject;
10use script_bindings::reflector::reflect_dom_object_with_proto;
11use servo_media::audio::audio_node::{AudioNodeInit, AudioNodeMessage, AudioNodeType};
12use servo_media::audio::buffer_source_node::{
13    AudioBufferSourceNodeMessage, AudioBufferSourceNodeOptions,
14};
15use servo_media::audio::param::ParamType;
16
17use crate::conversions::ConvertWithCx;
18use crate::dom::audio::audiobuffer::AudioBuffer;
19use crate::dom::audio::audioparam::AudioParam;
20use crate::dom::audio::audioscheduledsourcenode::AudioScheduledSourceNode;
21use crate::dom::audio::baseaudiocontext::BaseAudioContext;
22use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::{
23    AudioBufferSourceNodeMethods, AudioBufferSourceOptions,
24};
25use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate;
26use crate::dom::bindings::codegen::Bindings::AudioScheduledSourceNodeBinding::AudioScheduledSourceNodeMethods;
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::num::Finite;
30use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
31use crate::dom::window::Window;
32
33#[dom_struct]
34pub(crate) struct AudioBufferSourceNode {
35    source_node: AudioScheduledSourceNode,
36    buffer: MutNullableDom<AudioBuffer>,
37    buffer_set: Cell<bool>,
38    playback_rate: Dom<AudioParam>,
39    detune: Dom<AudioParam>,
40    loop_enabled: Cell<bool>,
41    loop_start: Cell<f64>,
42    loop_end: Cell<f64>,
43}
44
45impl AudioBufferSourceNode {
46    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
47    fn new_inherited(
48        cx: &mut JSContext,
49        window: &Window,
50        context: &BaseAudioContext,
51        options: &AudioBufferSourceOptions,
52    ) -> Fallible<AudioBufferSourceNode> {
53        let node_options = Default::default();
54        let source_options = AudioNodeInit::AudioBufferSourceNode(options.convert(cx));
55        let source_node = AudioScheduledSourceNode::new_inherited(
56            cx,
57            source_options,
58            context,
59            node_options,
60            0, /* inputs */
61            1, /* outputs */
62        )?;
63        let node_id = source_node.node().node_id();
64        // <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-playbackrate>
65        let playback_rate = AudioParam::new(
66            cx,
67            window,
68            context,
69            node_id,
70            AudioNodeType::AudioBufferSourceNode,
71            ParamType::PlaybackRate,
72            AutomationRate::K_rate,
73            *options.playbackRate,
74            f32::MIN,
75            f32::MAX,
76        );
77        // <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-detune>
78        let detune = AudioParam::new(
79            cx,
80            window,
81            context,
82            node_id,
83            AudioNodeType::AudioBufferSourceNode,
84            ParamType::Detune,
85            AutomationRate::K_rate,
86            *options.detune,
87            f32::MIN,
88            f32::MAX,
89        );
90        let node = AudioBufferSourceNode {
91            source_node,
92            buffer: Default::default(),
93            buffer_set: Cell::new(false),
94            playback_rate: Dom::from_ref(&playback_rate),
95            detune: Dom::from_ref(&detune),
96            loop_enabled: Cell::new(options.loop_),
97            loop_start: Cell::new(*options.loopStart),
98            loop_end: Cell::new(*options.loopEnd),
99        };
100        if let Some(Some(ref buffer)) = options.buffer {
101            node.SetBuffer(cx, Some(buffer))?;
102        }
103        Ok(node)
104    }
105
106    pub(crate) fn new(
107        cx: &mut JSContext,
108        window: &Window,
109        context: &BaseAudioContext,
110        options: &AudioBufferSourceOptions,
111    ) -> Fallible<DomRoot<AudioBufferSourceNode>> {
112        Self::new_with_proto(cx, window, None, context, options)
113    }
114
115    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
116    fn new_with_proto(
117        cx: &mut JSContext,
118        window: &Window,
119        proto: Option<HandleObject>,
120        context: &BaseAudioContext,
121        options: &AudioBufferSourceOptions,
122    ) -> Fallible<DomRoot<AudioBufferSourceNode>> {
123        let node = AudioBufferSourceNode::new_inherited(cx, window, context, options)?;
124        Ok(reflect_dom_object_with_proto(
125            cx,
126            Box::new(node),
127            window,
128            proto,
129        ))
130    }
131}
132
133impl AudioBufferSourceNodeMethods<crate::DomTypeHolder> for AudioBufferSourceNode {
134    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-audiobuffersourcenode>
135    fn Constructor(
136        cx: &mut JSContext,
137        window: &Window,
138        proto: Option<HandleObject>,
139        context: &BaseAudioContext,
140        options: &AudioBufferSourceOptions,
141    ) -> Fallible<DomRoot<AudioBufferSourceNode>> {
142        AudioBufferSourceNode::new_with_proto(cx, window, proto, context, options)
143    }
144
145    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-buffer>
146    fn GetBuffer(&self) -> Fallible<Option<DomRoot<AudioBuffer>>> {
147        Ok(self.buffer.get())
148    }
149
150    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-buffer>
151    fn SetBuffer(&self, cx: &mut JSContext, new_buffer: Option<&AudioBuffer>) -> Fallible<()> {
152        if new_buffer.is_some() {
153            if self.buffer_set.get() {
154                // Step 2.
155                return Err(Error::InvalidState(None));
156            }
157            // Step 3.
158            self.buffer_set.set(true);
159        }
160
161        // Step 4.
162        self.buffer.set(new_buffer);
163
164        // Step 5.
165        if self.source_node.has_start() &&
166            let Some(buffer) = self.buffer.get()
167        {
168            let buffer = buffer.get_channels(cx);
169            if buffer.is_some() {
170                self.source_node
171                    .node()
172                    .message(AudioNodeMessage::AudioBufferSourceNode(
173                        AudioBufferSourceNodeMessage::SetBuffer(buffer),
174                    ));
175            }
176        }
177
178        Ok(())
179    }
180
181    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-playbackrate>
182    fn PlaybackRate(&self) -> DomRoot<AudioParam> {
183        DomRoot::from_ref(&self.playback_rate)
184    }
185
186    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-detune>
187    fn Detune(&self) -> DomRoot<AudioParam> {
188        DomRoot::from_ref(&self.detune)
189    }
190
191    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loop>
192    fn Loop(&self) -> bool {
193        self.loop_enabled.get()
194    }
195
196    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loop>
197    fn SetLoop(&self, should_loop: bool) {
198        self.loop_enabled.set(should_loop);
199        let msg = AudioNodeMessage::AudioBufferSourceNode(
200            AudioBufferSourceNodeMessage::SetLoopEnabled(should_loop),
201        );
202        self.source_node.node().message(msg);
203    }
204
205    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopstart>
206    fn LoopStart(&self) -> Finite<f64> {
207        Finite::wrap(self.loop_start.get())
208    }
209
210    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopstart>
211    fn SetLoopStart(&self, loop_start: Finite<f64>) {
212        self.loop_start.set(*loop_start);
213        let msg = AudioNodeMessage::AudioBufferSourceNode(
214            AudioBufferSourceNodeMessage::SetLoopStart(*loop_start),
215        );
216        self.source_node.node().message(msg);
217    }
218
219    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopend>
220    fn LoopEnd(&self) -> Finite<f64> {
221        Finite::wrap(self.loop_end.get())
222    }
223
224    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopend>
225    fn SetLoopEnd(&self, loop_end: Finite<f64>) {
226        self.loop_end.set(*loop_end);
227        let msg = AudioNodeMessage::AudioBufferSourceNode(
228            AudioBufferSourceNodeMessage::SetLoopEnd(*loop_end),
229        );
230        self.source_node.node().message(msg);
231    }
232
233    /// <https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-start>
234    fn Start(
235        &self,
236        cx: &mut JSContext,
237        when: Finite<f64>,
238        offset: Option<Finite<f64>>,
239        duration: Option<Finite<f64>>,
240    ) -> Fallible<()> {
241        if let Some(offset) = offset &&
242            *offset < 0.
243        {
244            return Err(Error::Range(
245                c"'offset' must be a positive value".to_owned(),
246            ));
247        }
248
249        if let Some(duration) = duration &&
250            *duration < 0.
251        {
252            return Err(Error::Range(
253                c"'duration' must be a positive value".to_owned(),
254            ));
255        }
256
257        if let Some(buffer) = self.buffer.get() {
258            let buffer = buffer.get_channels(cx);
259            if buffer.is_some() {
260                self.source_node
261                    .node()
262                    .message(AudioNodeMessage::AudioBufferSourceNode(
263                        AudioBufferSourceNodeMessage::SetBuffer(buffer),
264                    ));
265            }
266        }
267
268        self.source_node
269            .node()
270            .message(AudioNodeMessage::AudioBufferSourceNode(
271                AudioBufferSourceNodeMessage::SetStartParams(
272                    *when,
273                    offset.map(|f| *f),
274                    duration.map(|f| *f),
275                ),
276            ));
277
278        self.source_node
279            .upcast::<AudioScheduledSourceNode>()
280            .Start(when)
281    }
282}
283
284impl ConvertWithCx<AudioBufferSourceNodeOptions> for AudioBufferSourceOptions {
285    fn convert(&self, cx: &mut JSContext) -> AudioBufferSourceNodeOptions {
286        AudioBufferSourceNodeOptions {
287            buffer: self
288                .buffer
289                .as_ref()
290                .and_then(|b| b.as_ref()?.get_channels(cx).map(|buffer| (*buffer).clone())),
291            detune: *self.detune,
292            loop_enabled: self.loop_,
293            loop_end: Some(*self.loopEnd),
294            loop_start: Some(*self.loopStart),
295            playback_rate: *self.playbackRate,
296        }
297    }
298}