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