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: Option<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: Option<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: Option<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        if let Some(node_id) = self.node {
98            self.context
99                .audio_context_impl()
100                .lock()
101                .unwrap()
102                .message_node(node_id, message);
103        }
104    }
105
106    pub(crate) fn context(&self) -> &BaseAudioContext {
107        &self.context
108    }
109
110    pub(crate) fn node_id(&self) -> Option<NodeId> {
111        self.node
112    }
113
114    pub(crate) fn param_type(&self) -> ParamType {
115        self.param
116    }
117}
118
119impl AudioParamMethods<crate::DomTypeHolder> for AudioParam {
120    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate>
121    fn AutomationRate(&self) -> AutomationRate {
122        self.automation_rate.get()
123    }
124
125    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate>
126    fn SetAutomationRate(&self, automation_rate: AutomationRate) -> Fallible<()> {
127        // > AudioBufferSourceNode
128        // > The AudioParams playbackRate and detune MUST be "k-rate". An InvalidStateError must be
129        // > thrown if the rate is changed to "a-rate".
130        if automation_rate == AutomationRate::A_rate &&
131            self.node_type == AudioNodeType::AudioBufferSourceNode &&
132            (self.param == ParamType::Detune || self.param == ParamType::PlaybackRate)
133        {
134            return Err(Error::InvalidState(None));
135        }
136
137        self.automation_rate.set(automation_rate);
138        self.message_node(AudioNodeMessage::SetParamRate(
139            self.param,
140            automation_rate.convert(),
141        ));
142
143        Ok(())
144    }
145
146    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-value>
147    fn Value(&self) -> Finite<f32> {
148        if self.node.is_none() {
149            return Finite::wrap(self.default_value);
150        }
151        let (tx, rx) = mpsc::channel();
152        self.message_node(AudioNodeMessage::GetParamValue(self.param, tx));
153        Finite::wrap(rx.recv().unwrap())
154    }
155
156    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-value>
157    fn SetValue(&self, value: Finite<f32>) {
158        self.message_node(AudioNodeMessage::SetParam(
159            self.param,
160            UserAutomationEvent::SetValue(*value),
161        ));
162    }
163
164    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-defaultvalue>
165    fn DefaultValue(&self) -> Finite<f32> {
166        Finite::wrap(self.default_value)
167    }
168
169    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-minvalue>
170    fn MinValue(&self) -> Finite<f32> {
171        Finite::wrap(self.min_value)
172    }
173
174    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-maxvalue>
175    fn MaxValue(&self) -> Finite<f32> {
176        Finite::wrap(self.max_value)
177    }
178
179    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-setvalueattime>
180    fn SetValueAtTime(
181        &self,
182        value: Finite<f32>,
183        start_time: Finite<f64>,
184    ) -> Fallible<DomRoot<AudioParam>> {
185        if *start_time < 0. {
186            return Err(Error::Range(format!(
187                "start time {} should not be negative",
188                *start_time
189            )));
190        }
191        self.message_node(AudioNodeMessage::SetParam(
192            self.param,
193            UserAutomationEvent::SetValueAtTime(*value, *start_time),
194        ));
195        Ok(DomRoot::from_ref(self))
196    }
197
198    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-linearramptovalueattime>
199    fn LinearRampToValueAtTime(
200        &self,
201        value: Finite<f32>,
202        end_time: Finite<f64>,
203    ) -> Fallible<DomRoot<AudioParam>> {
204        if *end_time < 0. {
205            return Err(Error::Range(format!(
206                "end time {} should not be negative",
207                *end_time
208            )));
209        }
210        self.message_node(AudioNodeMessage::SetParam(
211            self.param,
212            UserAutomationEvent::RampToValueAtTime(RampKind::Linear, *value, *end_time),
213        ));
214        Ok(DomRoot::from_ref(self))
215    }
216
217    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-exponentialramptovalueattime>
218    fn ExponentialRampToValueAtTime(
219        &self,
220        value: Finite<f32>,
221        end_time: Finite<f64>,
222    ) -> Fallible<DomRoot<AudioParam>> {
223        if *end_time < 0. {
224            return Err(Error::Range(format!(
225                "end time {} should not be negative",
226                *end_time
227            )));
228        }
229        if *value == 0. {
230            return Err(Error::Range(format!(
231                "target value {} should not be 0",
232                *value
233            )));
234        }
235        self.message_node(AudioNodeMessage::SetParam(
236            self.param,
237            UserAutomationEvent::RampToValueAtTime(RampKind::Exponential, *value, *end_time),
238        ));
239        Ok(DomRoot::from_ref(self))
240    }
241
242    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-settargetattime>
243    fn SetTargetAtTime(
244        &self,
245        target: Finite<f32>,
246        start_time: Finite<f64>,
247        time_constant: Finite<f32>,
248    ) -> Fallible<DomRoot<AudioParam>> {
249        if *start_time < 0. {
250            return Err(Error::Range(format!(
251                "start time {} should not be negative",
252                *start_time
253            )));
254        }
255        if *time_constant < 0. {
256            return Err(Error::Range(format!(
257                "time constant {} should not be negative",
258                *time_constant
259            )));
260        }
261        self.message_node(AudioNodeMessage::SetParam(
262            self.param,
263            UserAutomationEvent::SetTargetAtTime(*target, *start_time, (*time_constant).into()),
264        ));
265        Ok(DomRoot::from_ref(self))
266    }
267
268    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-setvaluecurveattime>
269    fn SetValueCurveAtTime(
270        &self,
271        values: Vec<Finite<f32>>,
272        start_time: Finite<f64>,
273        end_time: Finite<f64>,
274    ) -> Fallible<DomRoot<AudioParam>> {
275        if *start_time < 0. {
276            return Err(Error::Range(format!(
277                "start time {} should not be negative",
278                *start_time
279            )));
280        }
281        if values.len() < 2. as usize {
282            return Err(Error::InvalidState(None));
283        }
284
285        if *end_time < 0. {
286            return Err(Error::Range(format!(
287                "end time {} should not be negative",
288                *end_time
289            )));
290        }
291        self.message_node(AudioNodeMessage::SetParam(
292            self.param,
293            UserAutomationEvent::SetValueCurveAtTime(
294                values.into_iter().map(|v| *v).collect(),
295                *start_time,
296                *end_time,
297            ),
298        ));
299        Ok(DomRoot::from_ref(self))
300    }
301
302    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelscheduledvalues>
303    fn CancelScheduledValues(&self, cancel_time: Finite<f64>) -> Fallible<DomRoot<AudioParam>> {
304        if *cancel_time < 0. {
305            return Err(Error::Range(format!(
306                "cancel time {} should not be negative",
307                *cancel_time
308            )));
309        }
310        self.message_node(AudioNodeMessage::SetParam(
311            self.param,
312            UserAutomationEvent::CancelScheduledValues(*cancel_time),
313        ));
314        Ok(DomRoot::from_ref(self))
315    }
316
317    /// <https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelandholdattime>
318    fn CancelAndHoldAtTime(&self, cancel_time: Finite<f64>) -> Fallible<DomRoot<AudioParam>> {
319        if *cancel_time < 0. {
320            return Err(Error::Range(format!(
321                "cancel time {} should not be negative",
322                *cancel_time
323            )));
324        }
325        self.message_node(AudioNodeMessage::SetParam(
326            self.param,
327            UserAutomationEvent::CancelAndHoldAtTime(*cancel_time),
328        ));
329        Ok(DomRoot::from_ref(self))
330    }
331}
332
333// https://webaudio.github.io/web-audio-api/#enumdef-automationrate
334impl Convert<ParamRate> for AutomationRate {
335    fn convert(self) -> ParamRate {
336        match self {
337            AutomationRate::A_rate => ParamRate::ARate,
338            AutomationRate::K_rate => ParamRate::KRate,
339        }
340    }
341}