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