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