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