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::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, 1, )?;
97 let id = node.node_id();
98 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, f32::MIN, f32::MAX, );
111 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, f32::MIN, f32::MAX, );
124 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, f32::MIN, f32::MAX, );
137 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, f32::MIN, f32::MAX, );
150 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, f32::MIN, f32::MAX, );
163 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, f32::MIN, f32::MAX, );
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 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 fn PositionX(&self) -> DomRoot<AudioParam> {
236 DomRoot::from_ref(&self.position_x)
237 }
238 fn PositionY(&self) -> DomRoot<AudioParam> {
240 DomRoot::from_ref(&self.position_y)
241 }
242 fn PositionZ(&self) -> DomRoot<AudioParam> {
244 DomRoot::from_ref(&self.position_z)
245 }
246
247 fn OrientationX(&self) -> DomRoot<AudioParam> {
249 DomRoot::from_ref(&self.orientation_x)
250 }
251 fn OrientationY(&self) -> DomRoot<AudioParam> {
253 DomRoot::from_ref(&self.orientation_y)
254 }
255 fn OrientationZ(&self) -> DomRoot<AudioParam> {
257 DomRoot::from_ref(&self.orientation_z)
258 }
259
260 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 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 fn PanningModel(&self) -> PanningModelType {
277 match self.panning_model.get() {
278 PanningModel::EqualPower => PanningModelType::Equalpower,
279 PanningModel::HRTF => PanningModelType::HRTF,
280 }
281 }
282 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 fn RefDistance(&self) -> Finite<f64> {
291 Finite::wrap(self.ref_distance.get())
292 }
293 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 fn MaxDistance(&self) -> Finite<f64> {
306 Finite::wrap(self.max_distance.get())
307 }
308 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 fn RolloffFactor(&self) -> Finite<f64> {
321 Finite::wrap(self.rolloff_factor.get())
322 }
323 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 fn ConeInnerAngle(&self) -> Finite<f64> {
336 Finite::wrap(self.cone_inner_angle.get())
337 }
338 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 fn ConeOuterAngle(&self) -> Finite<f64> {
347 Finite::wrap(self.cone_outer_angle.get())
348 }
349 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 fn ConeOuterGain(&self) -> Finite<f64> {
358 Finite::wrap(self.cone_outer_gain.get())
359 }
360 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 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 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}