1use std::cell::Cell;
6use std::f32;
7
8use dom_struct::dom_struct;
9use js::rust::HandleObject;
10use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage, AudioNodeType};
11use servo_media::audio::panner_node::{
12 DistanceModel, PannerNodeMessage, PannerNodeOptions, PanningModel,
13};
14use servo_media::audio::param::{ParamDir, ParamType};
15
16use crate::conversions::Convert;
17use crate::dom::audio::audionode::{AudioNode, AudioNodeOptionsHelper};
18use crate::dom::audio::audioparam::AudioParam;
19use crate::dom::audio::baseaudiocontext::BaseAudioContext;
20use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
21 ChannelCountMode, ChannelInterpretation,
22};
23use crate::dom::bindings::codegen::Bindings::AudioParamBinding::{
24 AudioParamMethods, AutomationRate,
25};
26use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::{
27 DistanceModelType, PannerNodeMethods, PannerOptions, PanningModelType,
28};
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::inheritance::Castable;
31use crate::dom::bindings::num::Finite;
32use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
33use crate::dom::bindings::root::{Dom, DomRoot};
34use crate::dom::window::Window;
35use crate::script_runtime::CanGc;
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, allow(crown::unrooted_must_root))]
62 pub(crate) fn new_inherited(
63 window: &Window,
64 context: &BaseAudioContext,
65 options: &PannerOptions,
66 can_gc: CanGc,
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);
75 }
76 if node_options.count > 2 || node_options.count == 0 {
77 return Err(Error::NotSupported);
78 }
79 if *options.maxDistance <= 0. {
80 return Err(Error::Range("maxDistance should be positive".into()));
81 }
82 if *options.refDistance < 0. {
83 return Err(Error::Range("refDistance should be non-negative".into()));
84 }
85 if *options.rolloffFactor < 0. {
86 return Err(Error::Range("rolloffFactor should be non-negative".into()));
87 }
88 if *options.coneOuterGain < 0. || *options.coneOuterGain > 1. {
89 return Err(Error::InvalidState);
90 }
91 let options = options.convert();
92 let node = AudioNode::new_inherited(
93 AudioNodeInit::PannerNode(options),
94 context,
95 node_options,
96 1, 1, )?;
99 let id = node.node_id();
100 let position_x = AudioParam::new(
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, can_gc,
111 );
112 let position_y = AudioParam::new(
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, can_gc,
123 );
124 let position_z = AudioParam::new(
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, can_gc,
135 );
136 let orientation_x = AudioParam::new(
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, can_gc,
147 );
148 let orientation_y = AudioParam::new(
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, can_gc,
159 );
160 let orientation_z = AudioParam::new(
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, can_gc,
171 );
172 Ok(PannerNode {
173 node,
174 position_x: Dom::from_ref(&position_x),
175 position_y: Dom::from_ref(&position_y),
176 position_z: Dom::from_ref(&position_z),
177 orientation_x: Dom::from_ref(&orientation_x),
178 orientation_y: Dom::from_ref(&orientation_y),
179 orientation_z: Dom::from_ref(&orientation_z),
180 panning_model: Cell::new(options.panning_model),
181 distance_model: Cell::new(options.distance_model),
182 ref_distance: Cell::new(options.ref_distance),
183 max_distance: Cell::new(options.max_distance),
184 rolloff_factor: Cell::new(options.rolloff_factor),
185 cone_inner_angle: Cell::new(options.cone_inner_angle),
186 cone_outer_angle: Cell::new(options.cone_outer_angle),
187 cone_outer_gain: Cell::new(options.cone_outer_gain),
188 })
189 }
190
191 pub(crate) fn new(
192 window: &Window,
193 context: &BaseAudioContext,
194 options: &PannerOptions,
195 can_gc: CanGc,
196 ) -> Fallible<DomRoot<PannerNode>> {
197 Self::new_with_proto(window, None, context, options, can_gc)
198 }
199
200 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
201 fn new_with_proto(
202 window: &Window,
203 proto: Option<HandleObject>,
204 context: &BaseAudioContext,
205 options: &PannerOptions,
206 can_gc: CanGc,
207 ) -> Fallible<DomRoot<PannerNode>> {
208 let node = PannerNode::new_inherited(window, context, options, can_gc)?;
209 Ok(reflect_dom_object_with_proto(
210 Box::new(node),
211 window,
212 proto,
213 can_gc,
214 ))
215 }
216}
217
218impl PannerNodeMethods<crate::DomTypeHolder> for PannerNode {
219 fn Constructor(
221 window: &Window,
222 proto: Option<HandleObject>,
223 can_gc: CanGc,
224 context: &BaseAudioContext,
225 options: &PannerOptions,
226 ) -> Fallible<DomRoot<PannerNode>> {
227 PannerNode::new_with_proto(window, proto, context, options, can_gc)
228 }
229
230 fn PositionX(&self) -> DomRoot<AudioParam> {
232 DomRoot::from_ref(&self.position_x)
233 }
234 fn PositionY(&self) -> DomRoot<AudioParam> {
236 DomRoot::from_ref(&self.position_y)
237 }
238 fn PositionZ(&self) -> DomRoot<AudioParam> {
240 DomRoot::from_ref(&self.position_z)
241 }
242
243 fn OrientationX(&self) -> DomRoot<AudioParam> {
245 DomRoot::from_ref(&self.orientation_x)
246 }
247 fn OrientationY(&self) -> DomRoot<AudioParam> {
249 DomRoot::from_ref(&self.orientation_y)
250 }
251 fn OrientationZ(&self) -> DomRoot<AudioParam> {
253 DomRoot::from_ref(&self.orientation_z)
254 }
255
256 fn DistanceModel(&self) -> DistanceModelType {
258 match self.distance_model.get() {
259 DistanceModel::Linear => DistanceModelType::Linear,
260 DistanceModel::Inverse => DistanceModelType::Inverse,
261 DistanceModel::Exponential => DistanceModelType::Exponential,
262 }
263 }
264 fn SetDistanceModel(&self, model: DistanceModelType) {
266 self.distance_model.set(model.convert());
267 let msg = PannerNodeMessage::SetDistanceModel(self.distance_model.get());
268 self.upcast::<AudioNode>()
269 .message(AudioNodeMessage::PannerNode(msg));
270 }
271 fn PanningModel(&self) -> PanningModelType {
273 match self.panning_model.get() {
274 PanningModel::EqualPower => PanningModelType::Equalpower,
275 PanningModel::HRTF => PanningModelType::HRTF,
276 }
277 }
278 fn SetPanningModel(&self, model: PanningModelType) {
280 self.panning_model.set(model.convert());
281 let msg = PannerNodeMessage::SetPanningModel(self.panning_model.get());
282 self.upcast::<AudioNode>()
283 .message(AudioNodeMessage::PannerNode(msg));
284 }
285 fn RefDistance(&self) -> Finite<f64> {
287 Finite::wrap(self.ref_distance.get())
288 }
289 fn SetRefDistance(&self, val: Finite<f64>) -> Fallible<()> {
291 if *val < 0. {
292 return Err(Error::Range("value should be non-negative".into()));
293 }
294 self.ref_distance.set(*val);
295 let msg = PannerNodeMessage::SetRefDistance(self.ref_distance.get());
296 self.upcast::<AudioNode>()
297 .message(AudioNodeMessage::PannerNode(msg));
298 Ok(())
299 }
300 fn MaxDistance(&self) -> Finite<f64> {
302 Finite::wrap(self.max_distance.get())
303 }
304 fn SetMaxDistance(&self, val: Finite<f64>) -> Fallible<()> {
306 if *val <= 0. {
307 return Err(Error::Range("value should be positive".into()));
308 }
309 self.max_distance.set(*val);
310 let msg = PannerNodeMessage::SetMaxDistance(self.max_distance.get());
311 self.upcast::<AudioNode>()
312 .message(AudioNodeMessage::PannerNode(msg));
313 Ok(())
314 }
315 fn RolloffFactor(&self) -> Finite<f64> {
317 Finite::wrap(self.rolloff_factor.get())
318 }
319 fn SetRolloffFactor(&self, val: Finite<f64>) -> Fallible<()> {
321 if *val < 0. {
322 return Err(Error::Range("value should be non-negative".into()));
323 }
324 self.rolloff_factor.set(*val);
325 let msg = PannerNodeMessage::SetRolloff(self.rolloff_factor.get());
326 self.upcast::<AudioNode>()
327 .message(AudioNodeMessage::PannerNode(msg));
328 Ok(())
329 }
330 fn ConeInnerAngle(&self) -> Finite<f64> {
332 Finite::wrap(self.cone_inner_angle.get())
333 }
334 fn SetConeInnerAngle(&self, val: Finite<f64>) {
336 self.cone_inner_angle.set(*val);
337 let msg = PannerNodeMessage::SetConeInner(self.cone_inner_angle.get());
338 self.upcast::<AudioNode>()
339 .message(AudioNodeMessage::PannerNode(msg));
340 }
341 fn ConeOuterAngle(&self) -> Finite<f64> {
343 Finite::wrap(self.cone_outer_angle.get())
344 }
345 fn SetConeOuterAngle(&self, val: Finite<f64>) {
347 self.cone_outer_angle.set(*val);
348 let msg = PannerNodeMessage::SetConeOuter(self.cone_outer_angle.get());
349 self.upcast::<AudioNode>()
350 .message(AudioNodeMessage::PannerNode(msg));
351 }
352 fn ConeOuterGain(&self) -> Finite<f64> {
354 Finite::wrap(self.cone_outer_gain.get())
355 }
356 fn SetConeOuterGain(&self, val: Finite<f64>) -> Fallible<()> {
358 if *val < 0. || *val > 1. {
359 return Err(Error::InvalidState);
360 }
361 self.cone_outer_gain.set(*val);
362 let msg = PannerNodeMessage::SetConeGain(self.cone_outer_gain.get());
363 self.upcast::<AudioNode>()
364 .message(AudioNodeMessage::PannerNode(msg));
365 Ok(())
366 }
367
368 fn SetPosition(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) {
370 self.position_x.SetValue(x);
371 self.position_y.SetValue(y);
372 self.position_z.SetValue(z);
373 }
374
375 fn SetOrientation(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) {
377 self.orientation_x.SetValue(x);
378 self.orientation_y.SetValue(y);
379 self.orientation_z.SetValue(z);
380 }
381}
382
383impl Convert<PannerNodeOptions> for &PannerOptions {
384 fn convert(self) -> PannerNodeOptions {
385 PannerNodeOptions {
386 panning_model: self.panningModel.convert(),
387 distance_model: self.distanceModel.convert(),
388 position_x: *self.positionX,
389 position_y: *self.positionY,
390 position_z: *self.positionZ,
391 orientation_x: *self.orientationX,
392 orientation_y: *self.orientationY,
393 orientation_z: *self.orientationZ,
394 ref_distance: *self.refDistance,
395 max_distance: *self.maxDistance,
396 rolloff_factor: *self.rolloffFactor,
397 cone_inner_angle: *self.coneInnerAngle,
398 cone_outer_angle: *self.coneOuterAngle,
399 cone_outer_gain: *self.coneOuterGain,
400 }
401 }
402}
403
404impl Convert<DistanceModel> for DistanceModelType {
405 fn convert(self) -> DistanceModel {
406 match self {
407 DistanceModelType::Linear => DistanceModel::Linear,
408 DistanceModelType::Inverse => DistanceModel::Inverse,
409 DistanceModelType::Exponential => DistanceModel::Exponential,
410 }
411 }
412}
413
414impl Convert<PanningModel> for PanningModelType {
415 fn convert(self) -> PanningModel {
416 match self {
417 PanningModelType::Equalpower => PanningModel::EqualPower,
418 PanningModelType::HRTF => PanningModel::HRTF,
419 }
420 }
421}