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