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;
12use servo_media::audio::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 #[no_trace]
47 panning_model: Cell<PanningModel>,
48 #[no_trace]
49 distance_model: Cell<DistanceModel>,
50 ref_distance: Cell<f64>,
51 max_distance: Cell<f64>,
52 rolloff_factor: Cell<f64>,
53 cone_inner_angle: Cell<f64>,
54 cone_outer_angle: Cell<f64>,
55 cone_outer_gain: Cell<f64>,
56}
57
58impl PannerNode {
59 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
60 pub(crate) fn new_inherited(
61 cx: &mut JSContext,
62 window: &Window,
63 context: &BaseAudioContext,
64 options: &PannerOptions,
65 ) -> Fallible<PannerNode> {
66 let node_options = options.parent.unwrap_or(
67 2,
68 ChannelCountMode::Clamped_max,
69 ChannelInterpretation::Speakers,
70 );
71 if node_options.mode == ChannelCountMode::Max {
72 return Err(Error::NotSupported(None));
73 }
74 if node_options.count > 2 || node_options.count == 0 {
75 return Err(Error::NotSupported(None));
76 }
77 if *options.maxDistance <= 0. {
78 return Err(Error::Range(c"maxDistance should be positive".into()));
79 }
80 if *options.refDistance < 0. {
81 return Err(Error::Range(c"refDistance should be non-negative".into()));
82 }
83 if *options.rolloffFactor < 0. {
84 return Err(Error::Range(c"rolloffFactor should be non-negative".into()));
85 }
86 if *options.coneOuterGain < 0. || *options.coneOuterGain > 1. {
87 return Err(Error::InvalidState(None));
88 }
89 let options = options.convert();
90 let node = AudioNode::new_inherited(
91 cx,
92 AudioNodeInit::PannerNode(options),
93 context,
94 node_options,
95 1, 1, )?;
98 let id = node.node_id();
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, f32::MIN, f32::MAX, );
111 let position_y = AudioParam::new(
112 cx,
113 window,
114 context,
115 id,
116 AudioNodeType::PannerNode,
117 ParamType::Position(ParamDir::Y),
118 AutomationRate::A_rate,
119 options.position_y, f32::MIN, f32::MAX, );
123 let position_z = AudioParam::new(
124 cx,
125 window,
126 context,
127 id,
128 AudioNodeType::PannerNode,
129 ParamType::Position(ParamDir::Z),
130 AutomationRate::A_rate,
131 options.position_z, f32::MIN, f32::MAX, );
135 let orientation_x = AudioParam::new(
136 cx,
137 window,
138 context,
139 id,
140 AudioNodeType::PannerNode,
141 ParamType::Orientation(ParamDir::X),
142 AutomationRate::A_rate,
143 options.orientation_x, f32::MIN, f32::MAX, );
147 let orientation_y = AudioParam::new(
148 cx,
149 window,
150 context,
151 id,
152 AudioNodeType::PannerNode,
153 ParamType::Orientation(ParamDir::Y),
154 AutomationRate::A_rate,
155 options.orientation_y, f32::MIN, f32::MAX, );
159 let orientation_z = AudioParam::new(
160 cx,
161 window,
162 context,
163 id,
164 AudioNodeType::PannerNode,
165 ParamType::Orientation(ParamDir::Z),
166 AutomationRate::A_rate,
167 options.orientation_z, f32::MIN, f32::MAX, );
171 Ok(PannerNode {
172 node,
173 position_x: Dom::from_ref(&position_x),
174 position_y: Dom::from_ref(&position_y),
175 position_z: Dom::from_ref(&position_z),
176 orientation_x: Dom::from_ref(&orientation_x),
177 orientation_y: Dom::from_ref(&orientation_y),
178 orientation_z: Dom::from_ref(&orientation_z),
179 panning_model: Cell::new(options.panning_model),
180 distance_model: Cell::new(options.distance_model),
181 ref_distance: Cell::new(options.ref_distance),
182 max_distance: Cell::new(options.max_distance),
183 rolloff_factor: Cell::new(options.rolloff_factor),
184 cone_inner_angle: Cell::new(options.cone_inner_angle),
185 cone_outer_angle: Cell::new(options.cone_outer_angle),
186 cone_outer_gain: Cell::new(options.cone_outer_gain),
187 })
188 }
189
190 pub(crate) fn new(
191 cx: &mut JSContext,
192 window: &Window,
193 context: &BaseAudioContext,
194 options: &PannerOptions,
195 ) -> Fallible<DomRoot<PannerNode>> {
196 Self::new_with_proto(cx, window, None, context, options)
197 }
198
199 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
200 fn new_with_proto(
201 cx: &mut JSContext,
202 window: &Window,
203 proto: Option<HandleObject>,
204 context: &BaseAudioContext,
205 options: &PannerOptions,
206 ) -> Fallible<DomRoot<PannerNode>> {
207 let node = PannerNode::new_inherited(cx, window, context, options)?;
208 Ok(reflect_dom_object_with_proto(
209 cx,
210 Box::new(node),
211 window,
212 proto,
213 ))
214 }
215}
216
217impl PannerNodeMethods<crate::DomTypeHolder> for PannerNode {
218 fn Constructor(
220 cx: &mut JSContext,
221 window: &Window,
222 proto: Option<HandleObject>,
223 context: &BaseAudioContext,
224 options: &PannerOptions,
225 ) -> Fallible<DomRoot<PannerNode>> {
226 PannerNode::new_with_proto(cx, window, proto, context, options)
227 }
228
229 fn PositionX(&self) -> DomRoot<AudioParam> {
231 DomRoot::from_ref(&self.position_x)
232 }
233 fn PositionY(&self) -> DomRoot<AudioParam> {
235 DomRoot::from_ref(&self.position_y)
236 }
237 fn PositionZ(&self) -> DomRoot<AudioParam> {
239 DomRoot::from_ref(&self.position_z)
240 }
241
242 fn OrientationX(&self) -> DomRoot<AudioParam> {
244 DomRoot::from_ref(&self.orientation_x)
245 }
246 fn OrientationY(&self) -> DomRoot<AudioParam> {
248 DomRoot::from_ref(&self.orientation_y)
249 }
250 fn OrientationZ(&self) -> DomRoot<AudioParam> {
252 DomRoot::from_ref(&self.orientation_z)
253 }
254
255 fn DistanceModel(&self) -> DistanceModelType {
257 match self.distance_model.get() {
258 DistanceModel::Linear => DistanceModelType::Linear,
259 DistanceModel::Inverse => DistanceModelType::Inverse,
260 DistanceModel::Exponential => DistanceModelType::Exponential,
261 }
262 }
263 fn SetDistanceModel(&self, model: DistanceModelType) {
265 self.distance_model.set(model.convert());
266 let msg = PannerNodeMessage::SetDistanceModel(self.distance_model.get());
267 self.upcast::<AudioNode>()
268 .message(AudioNodeMessage::PannerNode(msg));
269 }
270 fn PanningModel(&self) -> PanningModelType {
272 match self.panning_model.get() {
273 PanningModel::EqualPower => PanningModelType::Equalpower,
274 PanningModel::HRTF => PanningModelType::HRTF,
275 }
276 }
277 fn SetPanningModel(&self, model: PanningModelType) {
279 self.panning_model.set(model.convert());
280 let msg = PannerNodeMessage::SetPanningModel(self.panning_model.get());
281 self.upcast::<AudioNode>()
282 .message(AudioNodeMessage::PannerNode(msg));
283 }
284 fn RefDistance(&self) -> Finite<f64> {
286 Finite::wrap(self.ref_distance.get())
287 }
288 fn SetRefDistance(&self, val: Finite<f64>) -> Fallible<()> {
290 if *val < 0. {
291 return Err(Error::Range(c"value should be non-negative".into()));
292 }
293 self.ref_distance.set(*val);
294 let msg = PannerNodeMessage::SetRefDistance(self.ref_distance.get());
295 self.upcast::<AudioNode>()
296 .message(AudioNodeMessage::PannerNode(msg));
297 Ok(())
298 }
299 fn MaxDistance(&self) -> Finite<f64> {
301 Finite::wrap(self.max_distance.get())
302 }
303 fn SetMaxDistance(&self, val: Finite<f64>) -> Fallible<()> {
305 if *val <= 0. {
306 return Err(Error::Range(c"value should be positive".into()));
307 }
308 self.max_distance.set(*val);
309 let msg = PannerNodeMessage::SetMaxDistance(self.max_distance.get());
310 self.upcast::<AudioNode>()
311 .message(AudioNodeMessage::PannerNode(msg));
312 Ok(())
313 }
314 fn RolloffFactor(&self) -> Finite<f64> {
316 Finite::wrap(self.rolloff_factor.get())
317 }
318 fn SetRolloffFactor(&self, val: Finite<f64>) -> Fallible<()> {
320 if *val < 0. {
321 return Err(Error::Range(c"value should be non-negative".into()));
322 }
323 self.rolloff_factor.set(*val);
324 let msg = PannerNodeMessage::SetRolloff(self.rolloff_factor.get());
325 self.upcast::<AudioNode>()
326 .message(AudioNodeMessage::PannerNode(msg));
327 Ok(())
328 }
329 fn ConeInnerAngle(&self) -> Finite<f64> {
331 Finite::wrap(self.cone_inner_angle.get())
332 }
333 fn SetConeInnerAngle(&self, val: Finite<f64>) {
335 self.cone_inner_angle.set(*val);
336 let msg = PannerNodeMessage::SetConeInner(self.cone_inner_angle.get());
337 self.upcast::<AudioNode>()
338 .message(AudioNodeMessage::PannerNode(msg));
339 }
340 fn ConeOuterAngle(&self) -> Finite<f64> {
342 Finite::wrap(self.cone_outer_angle.get())
343 }
344 fn SetConeOuterAngle(&self, val: Finite<f64>) {
346 self.cone_outer_angle.set(*val);
347 let msg = PannerNodeMessage::SetConeOuter(self.cone_outer_angle.get());
348 self.upcast::<AudioNode>()
349 .message(AudioNodeMessage::PannerNode(msg));
350 }
351 fn ConeOuterGain(&self) -> Finite<f64> {
353 Finite::wrap(self.cone_outer_gain.get())
354 }
355 fn SetConeOuterGain(&self, val: Finite<f64>) -> Fallible<()> {
357 if *val < 0. || *val > 1. {
358 return Err(Error::InvalidState(None));
359 }
360 self.cone_outer_gain.set(*val);
361 let msg = PannerNodeMessage::SetConeGain(self.cone_outer_gain.get());
362 self.upcast::<AudioNode>()
363 .message(AudioNodeMessage::PannerNode(msg));
364 Ok(())
365 }
366
367 fn SetPosition(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) {
369 self.position_x.SetValue(x);
370 self.position_y.SetValue(y);
371 self.position_z.SetValue(z);
372 }
373
374 fn SetOrientation(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) {
376 self.orientation_x.SetValue(x);
377 self.orientation_y.SetValue(y);
378 self.orientation_z.SetValue(z);
379 }
380}
381
382impl Convert<PannerNodeOptions> for &PannerOptions {
383 fn convert(self) -> PannerNodeOptions {
384 PannerNodeOptions {
385 panning_model: self.panningModel.convert(),
386 distance_model: self.distanceModel.convert(),
387 position_x: *self.positionX,
388 position_y: *self.positionY,
389 position_z: *self.positionZ,
390 orientation_x: *self.orientationX,
391 orientation_y: *self.orientationY,
392 orientation_z: *self.orientationZ,
393 ref_distance: *self.refDistance,
394 max_distance: *self.maxDistance,
395 rolloff_factor: *self.rolloffFactor,
396 cone_inner_angle: *self.coneInnerAngle,
397 cone_outer_angle: *self.coneOuterAngle,
398 cone_outer_gain: *self.coneOuterGain,
399 }
400 }
401}
402
403impl Convert<DistanceModel> for DistanceModelType {
404 fn convert(self) -> DistanceModel {
405 match self {
406 DistanceModelType::Linear => DistanceModel::Linear,
407 DistanceModelType::Inverse => DistanceModel::Inverse,
408 DistanceModelType::Exponential => DistanceModel::Exponential,
409 }
410 }
411}
412
413impl Convert<PanningModel> for PanningModelType {
414 fn convert(self) -> PanningModel {
415 match self {
416 PanningModelType::Equalpower => PanningModel::EqualPower,
417 PanningModelType::HRTF => PanningModel::HRTF,
418 }
419 }
420}