Skip to main content

script/dom/audio/
pannernode.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;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::rust::HandleObject;
10use script_bindings::reflector::reflect_dom_object_with_proto;
11use servo_media::audio::audio_node::{AudioNodeInit, AudioNodeMessage, AudioNodeType};
12use servo_media::audio::panner_node::{
13    DistanceModel, PannerNodeMessage, PannerNodeOptions, PanningModel,
14};
15use servo_media::audio::param::{ParamDir, ParamType};
16
17use crate::conversions::Convert;
18use crate::dom::audio::audionode::{AudioNode, AudioNodeOptionsHelper};
19use crate::dom::audio::audioparam::AudioParam;
20use crate::dom::audio::baseaudiocontext::BaseAudioContext;
21use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
22    ChannelCountMode, ChannelInterpretation,
23};
24use crate::dom::bindings::codegen::Bindings::AudioParamBinding::{
25    AudioParamMethods, AutomationRate,
26};
27use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::{
28    DistanceModelType, PannerNodeMethods, PannerOptions, PanningModelType,
29};
30use crate::dom::bindings::error::{Error, Fallible};
31use crate::dom::bindings::inheritance::Castable;
32use crate::dom::bindings::num::Finite;
33use crate::dom::bindings::root::{Dom, DomRoot};
34use crate::dom::window::Window;
35
36#[dom_struct]
37pub(crate) struct PannerNode {
38    node: AudioNode,
39    position_x: Dom<AudioParam>,
40    position_y: Dom<AudioParam>,
41    position_z: Dom<AudioParam>,
42    orientation_x: Dom<AudioParam>,
43    orientation_y: Dom<AudioParam>,
44    orientation_z: Dom<AudioParam>,
45    #[no_trace]
46    panning_model: Cell<PanningModel>,
47    #[no_trace]
48    distance_model: Cell<DistanceModel>,
49    ref_distance: Cell<f64>,
50    max_distance: Cell<f64>,
51    rolloff_factor: Cell<f64>,
52    cone_inner_angle: Cell<f64>,
53    cone_outer_angle: Cell<f64>,
54    cone_outer_gain: Cell<f64>,
55}
56
57impl PannerNode {
58    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
59    pub(crate) fn new_inherited(
60        cx: &mut JSContext,
61        window: &Window,
62        context: &BaseAudioContext,
63        options: &PannerOptions,
64    ) -> Fallible<PannerNode> {
65        let node_options = options.parent.unwrap_or(
66            2,
67            ChannelCountMode::Clamped_max,
68            ChannelInterpretation::Speakers,
69        );
70        if node_options.mode == ChannelCountMode::Max {
71            return Err(Error::NotSupported(None));
72        }
73        if node_options.count > 2 || node_options.count == 0 {
74            return Err(Error::NotSupported(None));
75        }
76        if *options.maxDistance <= 0. {
77            return Err(Error::Range(c"maxDistance should be positive".into()));
78        }
79        if *options.refDistance < 0. {
80            return Err(Error::Range(c"refDistance should be non-negative".into()));
81        }
82        if *options.rolloffFactor < 0. {
83            return Err(Error::Range(c"rolloffFactor should be non-negative".into()));
84        }
85        if *options.coneOuterGain < 0. || *options.coneOuterGain > 1. {
86            return Err(Error::InvalidState(None));
87        }
88        let options = options.convert();
89        let node = AudioNode::new_inherited(
90            cx,
91            AudioNodeInit::PannerNode(options),
92            context,
93            node_options,
94            1, // inputs
95            1, // outputs
96        )?;
97        let id = node.node_id();
98        // <https://webaudio.github.io/web-audio-api/#dom-pannernode-positionx>
99        let position_x = AudioParam::new(
100            cx,
101            window,
102            context,
103            id,
104            AudioNodeType::PannerNode,
105            ParamType::Position(ParamDir::X),
106            AutomationRate::A_rate,
107            options.position_x, // default value
108            f32::MIN,           // min value
109            f32::MAX,           // max value
110        );
111        // <https://webaudio.github.io/web-audio-api/#dom-pannernode-positiony>
112        let position_y = AudioParam::new(
113            cx,
114            window,
115            context,
116            id,
117            AudioNodeType::PannerNode,
118            ParamType::Position(ParamDir::Y),
119            AutomationRate::A_rate,
120            options.position_y, // default value
121            f32::MIN,           // min value
122            f32::MAX,           // max value
123        );
124        // <https://webaudio.github.io/web-audio-api/#dom-pannernode-positionz>
125        let position_z = AudioParam::new(
126            cx,
127            window,
128            context,
129            id,
130            AudioNodeType::PannerNode,
131            ParamType::Position(ParamDir::Z),
132            AutomationRate::A_rate,
133            options.position_z, // default value
134            f32::MIN,           // min value
135            f32::MAX,           // max value
136        );
137        // <https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationx>
138        let orientation_x = AudioParam::new(
139            cx,
140            window,
141            context,
142            id,
143            AudioNodeType::PannerNode,
144            ParamType::Orientation(ParamDir::X),
145            AutomationRate::A_rate,
146            options.orientation_x, // default value
147            f32::MIN,              // min value
148            f32::MAX,              // max value
149        );
150        // <https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationy>
151        let orientation_y = AudioParam::new(
152            cx,
153            window,
154            context,
155            id,
156            AudioNodeType::PannerNode,
157            ParamType::Orientation(ParamDir::Y),
158            AutomationRate::A_rate,
159            options.orientation_y, // default value
160            f32::MIN,              // min value
161            f32::MAX,              // max value
162        );
163        // <https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationz>
164        let orientation_z = AudioParam::new(
165            cx,
166            window,
167            context,
168            id,
169            AudioNodeType::PannerNode,
170            ParamType::Orientation(ParamDir::Z),
171            AutomationRate::A_rate,
172            options.orientation_z, // default value
173            f32::MIN,              // min value
174            f32::MAX,              // max value
175        );
176        Ok(PannerNode {
177            node,
178            position_x: Dom::from_ref(&position_x),
179            position_y: Dom::from_ref(&position_y),
180            position_z: Dom::from_ref(&position_z),
181            orientation_x: Dom::from_ref(&orientation_x),
182            orientation_y: Dom::from_ref(&orientation_y),
183            orientation_z: Dom::from_ref(&orientation_z),
184            panning_model: Cell::new(options.panning_model),
185            distance_model: Cell::new(options.distance_model),
186            ref_distance: Cell::new(options.ref_distance),
187            max_distance: Cell::new(options.max_distance),
188            rolloff_factor: Cell::new(options.rolloff_factor),
189            cone_inner_angle: Cell::new(options.cone_inner_angle),
190            cone_outer_angle: Cell::new(options.cone_outer_angle),
191            cone_outer_gain: Cell::new(options.cone_outer_gain),
192        })
193    }
194
195    pub(crate) fn new(
196        cx: &mut JSContext,
197        window: &Window,
198        context: &BaseAudioContext,
199        options: &PannerOptions,
200    ) -> Fallible<DomRoot<PannerNode>> {
201        Self::new_with_proto(cx, window, None, context, options)
202    }
203
204    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
205    fn new_with_proto(
206        cx: &mut JSContext,
207        window: &Window,
208        proto: Option<HandleObject>,
209        context: &BaseAudioContext,
210        options: &PannerOptions,
211    ) -> Fallible<DomRoot<PannerNode>> {
212        let node = PannerNode::new_inherited(cx, window, context, options)?;
213        Ok(reflect_dom_object_with_proto(
214            cx,
215            Box::new(node),
216            window,
217            proto,
218        ))
219    }
220}
221
222impl PannerNodeMethods<crate::DomTypeHolder> for PannerNode {
223    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-pannernode>
224    fn Constructor(
225        cx: &mut JSContext,
226        window: &Window,
227        proto: Option<HandleObject>,
228        context: &BaseAudioContext,
229        options: &PannerOptions,
230    ) -> Fallible<DomRoot<PannerNode>> {
231        PannerNode::new_with_proto(cx, window, proto, context, options)
232    }
233
234    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-positionx>
235    fn PositionX(&self) -> DomRoot<AudioParam> {
236        DomRoot::from_ref(&self.position_x)
237    }
238    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-positiony>
239    fn PositionY(&self) -> DomRoot<AudioParam> {
240        DomRoot::from_ref(&self.position_y)
241    }
242    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-positionz>
243    fn PositionZ(&self) -> DomRoot<AudioParam> {
244        DomRoot::from_ref(&self.position_z)
245    }
246
247    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationx>
248    fn OrientationX(&self) -> DomRoot<AudioParam> {
249        DomRoot::from_ref(&self.orientation_x)
250    }
251    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationy>
252    fn OrientationY(&self) -> DomRoot<AudioParam> {
253        DomRoot::from_ref(&self.orientation_y)
254    }
255    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationz>
256    fn OrientationZ(&self) -> DomRoot<AudioParam> {
257        DomRoot::from_ref(&self.orientation_z)
258    }
259
260    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel>
261    fn DistanceModel(&self) -> DistanceModelType {
262        match self.distance_model.get() {
263            DistanceModel::Linear => DistanceModelType::Linear,
264            DistanceModel::Inverse => DistanceModelType::Inverse,
265            DistanceModel::Exponential => DistanceModelType::Exponential,
266        }
267    }
268    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel>
269    fn SetDistanceModel(&self, model: DistanceModelType) {
270        self.distance_model.set(model.convert());
271        let msg = PannerNodeMessage::SetDistanceModel(self.distance_model.get());
272        self.upcast::<AudioNode>()
273            .message(AudioNodeMessage::PannerNode(msg));
274    }
275    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel>
276    fn PanningModel(&self) -> PanningModelType {
277        match self.panning_model.get() {
278            PanningModel::EqualPower => PanningModelType::Equalpower,
279            PanningModel::HRTF => PanningModelType::HRTF,
280        }
281    }
282    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel>
283    fn SetPanningModel(&self, model: PanningModelType) {
284        self.panning_model.set(model.convert());
285        let msg = PannerNodeMessage::SetPanningModel(self.panning_model.get());
286        self.upcast::<AudioNode>()
287            .message(AudioNodeMessage::PannerNode(msg));
288    }
289    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance>
290    fn RefDistance(&self) -> Finite<f64> {
291        Finite::wrap(self.ref_distance.get())
292    }
293    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance>
294    fn SetRefDistance(&self, val: Finite<f64>) -> Fallible<()> {
295        if *val < 0. {
296            return Err(Error::Range(c"value should be non-negative".into()));
297        }
298        self.ref_distance.set(*val);
299        let msg = PannerNodeMessage::SetRefDistance(self.ref_distance.get());
300        self.upcast::<AudioNode>()
301            .message(AudioNodeMessage::PannerNode(msg));
302        Ok(())
303    }
304    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance>
305    fn MaxDistance(&self) -> Finite<f64> {
306        Finite::wrap(self.max_distance.get())
307    }
308    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance>
309    fn SetMaxDistance(&self, val: Finite<f64>) -> Fallible<()> {
310        if *val <= 0. {
311            return Err(Error::Range(c"value should be positive".into()));
312        }
313        self.max_distance.set(*val);
314        let msg = PannerNodeMessage::SetMaxDistance(self.max_distance.get());
315        self.upcast::<AudioNode>()
316            .message(AudioNodeMessage::PannerNode(msg));
317        Ok(())
318    }
319    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor>
320    fn RolloffFactor(&self) -> Finite<f64> {
321        Finite::wrap(self.rolloff_factor.get())
322    }
323    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor>
324    fn SetRolloffFactor(&self, val: Finite<f64>) -> Fallible<()> {
325        if *val < 0. {
326            return Err(Error::Range(c"value should be non-negative".into()));
327        }
328        self.rolloff_factor.set(*val);
329        let msg = PannerNodeMessage::SetRolloff(self.rolloff_factor.get());
330        self.upcast::<AudioNode>()
331            .message(AudioNodeMessage::PannerNode(msg));
332        Ok(())
333    }
334    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-coneinnerangle>
335    fn ConeInnerAngle(&self) -> Finite<f64> {
336        Finite::wrap(self.cone_inner_angle.get())
337    }
338    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-coneinnerangle>
339    fn SetConeInnerAngle(&self, val: Finite<f64>) {
340        self.cone_inner_angle.set(*val);
341        let msg = PannerNodeMessage::SetConeInner(self.cone_inner_angle.get());
342        self.upcast::<AudioNode>()
343            .message(AudioNodeMessage::PannerNode(msg));
344    }
345    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-coneouterangle>
346    fn ConeOuterAngle(&self) -> Finite<f64> {
347        Finite::wrap(self.cone_outer_angle.get())
348    }
349    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-coneouterangle>
350    fn SetConeOuterAngle(&self, val: Finite<f64>) {
351        self.cone_outer_angle.set(*val);
352        let msg = PannerNodeMessage::SetConeOuter(self.cone_outer_angle.get());
353        self.upcast::<AudioNode>()
354            .message(AudioNodeMessage::PannerNode(msg));
355    }
356    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain>
357    fn ConeOuterGain(&self) -> Finite<f64> {
358        Finite::wrap(self.cone_outer_gain.get())
359    }
360    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain>
361    fn SetConeOuterGain(&self, val: Finite<f64>) -> Fallible<()> {
362        if *val < 0. || *val > 1. {
363            return Err(Error::InvalidState(None));
364        }
365        self.cone_outer_gain.set(*val);
366        let msg = PannerNodeMessage::SetConeGain(self.cone_outer_gain.get());
367        self.upcast::<AudioNode>()
368            .message(AudioNodeMessage::PannerNode(msg));
369        Ok(())
370    }
371
372    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-setposition>
373    fn SetPosition(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) {
374        self.position_x.SetValue(x);
375        self.position_y.SetValue(y);
376        self.position_z.SetValue(z);
377    }
378
379    /// <https://webaudio.github.io/web-audio-api/#dom-pannernode-setorientation>
380    fn SetOrientation(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) {
381        self.orientation_x.SetValue(x);
382        self.orientation_y.SetValue(y);
383        self.orientation_z.SetValue(z);
384    }
385}
386
387impl Convert<PannerNodeOptions> for &PannerOptions {
388    fn convert(self) -> PannerNodeOptions {
389        PannerNodeOptions {
390            panning_model: self.panningModel.convert(),
391            distance_model: self.distanceModel.convert(),
392            position_x: *self.positionX,
393            position_y: *self.positionY,
394            position_z: *self.positionZ,
395            orientation_x: *self.orientationX,
396            orientation_y: *self.orientationY,
397            orientation_z: *self.orientationZ,
398            ref_distance: *self.refDistance,
399            max_distance: *self.maxDistance,
400            rolloff_factor: *self.rolloffFactor,
401            cone_inner_angle: *self.coneInnerAngle,
402            cone_outer_angle: *self.coneOuterAngle,
403            cone_outer_gain: *self.coneOuterGain,
404        }
405    }
406}
407
408impl Convert<DistanceModel> for DistanceModelType {
409    fn convert(self) -> DistanceModel {
410        match self {
411            DistanceModelType::Linear => DistanceModel::Linear,
412            DistanceModelType::Inverse => DistanceModel::Inverse,
413            DistanceModelType::Exponential => DistanceModel::Exponential,
414        }
415    }
416}
417
418impl Convert<PanningModel> for PanningModelType {
419    fn convert(self) -> PanningModel {
420        match self {
421            PanningModelType::Equalpower => PanningModel::EqualPower,
422            PanningModelType::HRTF => PanningModel::HRTF,
423        }
424    }
425}