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