Skip to main content

script/dom/audio/
audioparam.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::sync::mpsc;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use script_bindings::cformat;
11use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
12use servo_media::audio::audio_node::{AudioNodeMessage, AudioNodeType};
13use servo_media::audio::graph::NodeId;
14use servo_media::audio::param::{ParamRate, ParamType, RampKind, UserAutomationEvent};
15
16use crate::conversions::Convert;
17use crate::dom::audio::baseaudiocontext::BaseAudioContext;
18use crate::dom::bindings::codegen::Bindings::AudioParamBinding::{
19    AudioParamMethods, AutomationRate,
20};
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::num::Finite;
23use crate::dom::bindings::root::{Dom, DomRoot};
24use crate::dom::window::Window;
25
26#[dom_struct]
27pub(crate) struct AudioParam {
28    reflector_: Reflector,
29    context: Dom<BaseAudioContext>,
30    #[no_trace]
31    node: Option<NodeId>,
32    #[no_trace]
33    node_type: AudioNodeType,
34    #[no_trace]
35    param: ParamType,
36    automation_rate: Cell<AutomationRate>,
37    default_value: f32,
38    min_value: f32,
39    max_value: f32,
40}
41
42impl AudioParam {
43    #[allow(clippy::too_many_arguments)]
44    pub(crate) fn new_inherited(
45        context: &BaseAudioContext,
46        node: Option<NodeId>,
47        node_type: AudioNodeType,
48        param: ParamType,
49        automation_rate: AutomationRate,
50        default_value: f32,
51        min_value: f32,
52        max_value: f32,
53    ) -> AudioParam {
54        AudioParam {
55            reflector_: Reflector::new(),
56            context: Dom::from_ref(context),
57            node,
58            node_type,
59            param,
60            automation_rate: Cell::new(automation_rate),
61            default_value,
62            min_value,
63            max_value,
64        }
65    }
66
67    #[allow(clippy::too_many_arguments)]
68    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
69    pub(crate) fn new(
70        cx: &mut JSContext,
71        window: &Window,
72        context: &BaseAudioContext,
73        node: Option<NodeId>,
74        node_type: AudioNodeType,
75        param: ParamType,
76        automation_rate: AutomationRate,
77        default_value: f32,
78        min_value: f32,
79        max_value: f32,
80    ) -> DomRoot<AudioParam> {
81        let audio_param = AudioParam::new_inherited(
82            context,
83            node,
84            node_type,
85            param,
86            automation_rate,
87            default_value,
88            min_value,
89            max_value,
90        );
91        reflect_dom_object_with_cx(Box::new(audio_param), window, cx)
92    }
93
94    fn message_node(&self, message: AudioNodeMessage) {
95        if let Some(node_id) = self.node {
96            self.context
97                .audio_context_impl()
98                .lock()
99                .unwrap()
100                .message_node(node_id, message);
101        }
102    }
103
104    pub(crate) fn context(&self) -> &BaseAudioContext {
105        &self.context
106    }
107
108    pub(crate) fn node_id(&self) -> Option<NodeId> {
109        self.node
110    }
111
112    pub(crate) fn param_type(&self) -> ParamType {
113        self.param
114    }
115}
116
117impl AudioParamMethods<crate::DomTypeHolder> for AudioParam {
118    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate>
119    fn AutomationRate(&self) -> AutomationRate {
120        self.automation_rate.get()
121    }
122
123    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate>
124    fn SetAutomationRate(&self, automation_rate: AutomationRate) -> Fallible<()> {
125        // > AudioBufferSourceNode
126        // > The AudioParams playbackRate and detune MUST be "k-rate". An InvalidStateError must be
127        // > thrown if the rate is changed to "a-rate".
128        if automation_rate == AutomationRate::A_rate &&
129            self.node_type == AudioNodeType::AudioBufferSourceNode &&
130            (self.param == ParamType::Detune || self.param == ParamType::PlaybackRate)
131        {
132            return Err(Error::InvalidState(None));
133        }
134
135        self.automation_rate.set(automation_rate);
136        self.message_node(AudioNodeMessage::SetParamRate(
137            self.param,
138            automation_rate.convert(),
139        ));
140
141        Ok(())
142    }
143
144    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-value>
145    fn Value(&self) -> Finite<f32> {
146        if self.node.is_none() {
147            return Finite::wrap(self.default_value);
148        }
149        let (tx, rx) = mpsc::channel();
150        self.message_node(AudioNodeMessage::GetParamValue(self.param, tx));
151        Finite::wrap(rx.recv().unwrap())
152    }
153
154    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-value>
155    fn SetValue(&self, value: Finite<f32>) {
156        self.message_node(AudioNodeMessage::SetParam(
157            self.param,
158            UserAutomationEvent::SetValue(*value),
159        ));
160    }
161
162    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-defaultvalue>
163    fn DefaultValue(&self) -> Finite<f32> {
164        Finite::wrap(self.default_value)
165    }
166
167    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-minvalue>
168    fn MinValue(&self) -> Finite<f32> {
169        Finite::wrap(self.min_value)
170    }
171
172    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-maxvalue>
173    fn MaxValue(&self) -> Finite<f32> {
174        Finite::wrap(self.max_value)
175    }
176
177    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-setvalueattime>
178    fn SetValueAtTime(
179        &self,
180        value: Finite<f32>,
181        start_time: Finite<f64>,
182    ) -> Fallible<DomRoot<AudioParam>> {
183        if *start_time < 0. {
184            return Err(Error::Range(cformat!(
185                "start time {} should not be negative",
186                *start_time
187            )));
188        }
189        self.message_node(AudioNodeMessage::SetParam(
190            self.param,
191            UserAutomationEvent::SetValueAtTime(*value, *start_time),
192        ));
193        Ok(DomRoot::from_ref(self))
194    }
195
196    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-linearramptovalueattime>
197    fn LinearRampToValueAtTime(
198        &self,
199        value: Finite<f32>,
200        end_time: Finite<f64>,
201    ) -> Fallible<DomRoot<AudioParam>> {
202        if *end_time < 0. {
203            return Err(Error::Range(cformat!(
204                "end time {} should not be negative",
205                *end_time
206            )));
207        }
208        self.message_node(AudioNodeMessage::SetParam(
209            self.param,
210            UserAutomationEvent::RampToValueAtTime(RampKind::Linear, *value, *end_time),
211        ));
212        Ok(DomRoot::from_ref(self))
213    }
214
215    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-exponentialramptovalueattime>
216    fn ExponentialRampToValueAtTime(
217        &self,
218        value: Finite<f32>,
219        end_time: Finite<f64>,
220    ) -> Fallible<DomRoot<AudioParam>> {
221        if *end_time < 0. {
222            return Err(Error::Range(cformat!(
223                "end time {} should not be negative",
224                *end_time
225            )));
226        }
227        if *value == 0. {
228            return Err(Error::Range(cformat!(
229                "target value {} should not be 0",
230                *value
231            )));
232        }
233        self.message_node(AudioNodeMessage::SetParam(
234            self.param,
235            UserAutomationEvent::RampToValueAtTime(RampKind::Exponential, *value, *end_time),
236        ));
237        Ok(DomRoot::from_ref(self))
238    }
239
240    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-settargetattime>
241    fn SetTargetAtTime(
242        &self,
243        target: Finite<f32>,
244        start_time: Finite<f64>,
245        time_constant: Finite<f32>,
246    ) -> Fallible<DomRoot<AudioParam>> {
247        if *start_time < 0. {
248            return Err(Error::Range(cformat!(
249                "start time {} should not be negative",
250                *start_time
251            )));
252        }
253        if *time_constant < 0. {
254            return Err(Error::Range(cformat!(
255                "time constant {} should not be negative",
256                *time_constant
257            )));
258        }
259        self.message_node(AudioNodeMessage::SetParam(
260            self.param,
261            UserAutomationEvent::SetTargetAtTime(*target, *start_time, (*time_constant).into()),
262        ));
263        Ok(DomRoot::from_ref(self))
264    }
265
266    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-setvaluecurveattime>
267    fn SetValueCurveAtTime(
268        &self,
269        values: Vec<Finite<f32>>,
270        start_time: Finite<f64>,
271        end_time: Finite<f64>,
272    ) -> Fallible<DomRoot<AudioParam>> {
273        if *start_time < 0. {
274            return Err(Error::Range(cformat!(
275                "start time {} should not be negative",
276                *start_time
277            )));
278        }
279        if values.len() < 2. as usize {
280            return Err(Error::InvalidState(None));
281        }
282
283        if *end_time < 0. {
284            return Err(Error::Range(cformat!(
285                "end time {} should not be negative",
286                *end_time
287            )));
288        }
289        self.message_node(AudioNodeMessage::SetParam(
290            self.param,
291            UserAutomationEvent::SetValueCurveAtTime(
292                values.into_iter().map(|v| *v).collect(),
293                *start_time,
294                *end_time,
295            ),
296        ));
297        Ok(DomRoot::from_ref(self))
298    }
299
300    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelscheduledvalues>
301    fn CancelScheduledValues(&self, cancel_time: Finite<f64>) -> Fallible<DomRoot<AudioParam>> {
302        if *cancel_time < 0. {
303            return Err(Error::Range(cformat!(
304                "cancel time {} should not be negative",
305                *cancel_time
306            )));
307        }
308        self.message_node(AudioNodeMessage::SetParam(
309            self.param,
310            UserAutomationEvent::CancelScheduledValues(*cancel_time),
311        ));
312        Ok(DomRoot::from_ref(self))
313    }
314
315    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelandholdattime>
316    fn CancelAndHoldAtTime(&self, cancel_time: Finite<f64>) -> Fallible<DomRoot<AudioParam>> {
317        if *cancel_time < 0. {
318            return Err(Error::Range(cformat!(
319                "cancel time {} should not be negative",
320                *cancel_time
321            )));
322        }
323        self.message_node(AudioNodeMessage::SetParam(
324            self.param,
325            UserAutomationEvent::CancelAndHoldAtTime(*cancel_time),
326        ));
327        Ok(DomRoot::from_ref(self))
328    }
329}
330
331// https://webaudio.github.io/web-audio-api/#enumdef-automationrate
332impl Convert<ParamRate> for AutomationRate {
333    fn convert(self) -> ParamRate {
334        match self {
335            AutomationRate::A_rate => ParamRate::ARate,
336            AutomationRate::K_rate => ParamRate::KRate,
337        }
338    }
339}