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