1use 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, 1, ),
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 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 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, f32::MIN, f32::MAX, );
154 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, f32::MIN, f32::MAX, );
167 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, f32::MIN, f32::MAX, );
180 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, f32::MIN, f32::MAX, );
193 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, f32::MIN, f32::MAX, );
206 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, f32::MIN, f32::MAX, );
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 fn PositionX(&self) -> DomRoot<AudioParam> {
242 DomRoot::from_ref(&self.position_x)
243 }
244 fn PositionY(&self) -> DomRoot<AudioParam> {
246 DomRoot::from_ref(&self.position_y)
247 }
248 fn PositionZ(&self) -> DomRoot<AudioParam> {
250 DomRoot::from_ref(&self.position_z)
251 }
252
253 fn OrientationX(&self) -> DomRoot<AudioParam> {
255 DomRoot::from_ref(&self.orientation_x)
256 }
257 fn OrientationY(&self) -> DomRoot<AudioParam> {
259 DomRoot::from_ref(&self.orientation_y)
260 }
261 fn OrientationZ(&self) -> DomRoot<AudioParam> {
263 DomRoot::from_ref(&self.orientation_z)
264 }
265
266 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 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 fn PanningModel(&self) -> PanningModelType {
283 match self.panning_model.get() {
284 PanningModel::EqualPower => PanningModelType::Equalpower,
285 PanningModel::HRTF => PanningModelType::HRTF,
286 }
287 }
288 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 fn RefDistance(&self) -> Finite<f64> {
297 Finite::wrap(self.ref_distance.get())
298 }
299 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 fn MaxDistance(&self) -> Finite<f64> {
312 Finite::wrap(self.max_distance.get())
313 }
314 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 fn RolloffFactor(&self) -> Finite<f64> {
327 Finite::wrap(self.rolloff_factor.get())
328 }
329 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 fn ConeInnerAngle(&self) -> Finite<f64> {
342 Finite::wrap(self.cone_inner_angle.get())
343 }
344 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 fn ConeOuterAngle(&self) -> Finite<f64> {
353 Finite::wrap(self.cone_outer_angle.get())
354 }
355 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 fn ConeOuterGain(&self) -> Finite<f64> {
364 Finite::wrap(self.cone_outer_gain.get())
365 }
366 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 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 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}