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