melody_bay/webaudio/nodes/
panner.rs1#[derive(Debug, Clone)]
2pub struct PannerNode {
3 id: NodeId,
4 graph: Arc<Mutex<GraphInner>>,
5}
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PanningModel {
9 EqualPower,
10 Hrtf,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum DistanceModel {
15 Linear,
16 Inverse,
17 Exponential,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct PannerOptions {
22 pub panning_model: PanningModel,
23 pub distance_model: DistanceModel,
24 pub position_x: f32,
25 pub position_y: f32,
26 pub position_z: f32,
27 pub orientation_x: f32,
28 pub orientation_y: f32,
29 pub orientation_z: f32,
30 pub ref_distance: f32,
31 pub max_distance: f32,
32 pub rolloff_factor: f32,
33 pub cone_inner_angle: f32,
34 pub cone_outer_angle: f32,
35 pub cone_outer_gain: f32,
36}
37
38impl Default for PannerOptions {
39 fn default() -> Self {
40 Self {
41 panning_model: PanningModel::EqualPower,
42 distance_model: DistanceModel::Inverse,
43 position_x: 0.0,
44 position_y: 0.0,
45 position_z: 0.0,
46 orientation_x: 1.0,
47 orientation_y: 0.0,
48 orientation_z: 0.0,
49 ref_distance: 1.0,
50 max_distance: 10_000.0,
51 rolloff_factor: 1.0,
52 cone_inner_angle: 360.0,
53 cone_outer_angle: 360.0,
54 cone_outer_gain: 0.0,
55 }
56 }
57}
58
59impl PannerNode {
60 #[must_use]
61 pub fn position_x(&self) -> AudioParamHandle {
62 AudioParamHandle {
63 graph: Arc::clone(&self.graph),
64 id: self.position_x_param(),
65 }
66 }
67
68 #[must_use]
69 pub fn position_y(&self) -> AudioParamHandle {
70 AudioParamHandle {
71 graph: Arc::clone(&self.graph),
72 id: self.position_y_param(),
73 }
74 }
75
76 #[must_use]
77 pub fn position_z(&self) -> AudioParamHandle {
78 AudioParamHandle {
79 graph: Arc::clone(&self.graph),
80 id: self.position_z_param(),
81 }
82 }
83
84 #[must_use]
85 pub fn orientation_x(&self) -> AudioParamHandle {
86 AudioParamHandle {
87 graph: Arc::clone(&self.graph),
88 id: self.orientation_x_param(),
89 }
90 }
91
92 #[must_use]
93 pub fn orientation_y(&self) -> AudioParamHandle {
94 AudioParamHandle {
95 graph: Arc::clone(&self.graph),
96 id: self.orientation_y_param(),
97 }
98 }
99
100 #[must_use]
101 pub fn orientation_z(&self) -> AudioParamHandle {
102 AudioParamHandle {
103 graph: Arc::clone(&self.graph),
104 id: self.orientation_z_param(),
105 }
106 }
107
108 pub fn set_distance_model(&self, model: DistanceModel) {
109 self.try_distance_model(model)
110 .expect("distance model is always valid");
111 }
112
113 pub fn set_panning_model(&self, model: PanningModel) -> Result<(), GraphError> {
114 if model == PanningModel::Hrtf {
115 return Err(GraphError::UnsupportedPanningModel);
116 }
117 self.set_panner(|kind| {
118 if let NodeKind::Panner { panning_model, .. } = kind {
119 *panning_model = model;
120 }
121 Ok(())
122 })
123 }
124
125 pub fn try_distance_model(&self, model: DistanceModel) -> Result<(), GraphError> {
126 self.set_panner(|kind| {
127 if let NodeKind::Panner { distance_model, .. } = kind {
128 *distance_model = model;
129 }
130 Ok(())
131 })
132 }
133
134 pub fn try_cone_inner_angle(&self, degrees: f32) -> Result<(), GraphError> {
135 validate_panner_angle(degrees)?;
136 self.set_panner(|kind| {
137 if let NodeKind::Panner {
138 cone_inner_angle, ..
139 } = kind
140 {
141 *cone_inner_angle = degrees;
142 }
143 Ok(())
144 })
145 }
146
147 pub fn try_cone_outer_angle(&self, degrees: f32) -> Result<(), GraphError> {
148 validate_panner_angle(degrees)?;
149 self.set_panner(|kind| {
150 if let NodeKind::Panner {
151 cone_outer_angle, ..
152 } = kind
153 {
154 *cone_outer_angle = degrees;
155 }
156 Ok(())
157 })
158 }
159
160 pub fn try_cone_outer_gain(&self, gain: f32) -> Result<(), GraphError> {
161 if !gain.is_finite() || !(0.0..=1.0).contains(&gain) {
162 return Err(GraphError::InvalidPannerConfig);
163 }
164 self.set_panner(|kind| {
165 if let NodeKind::Panner {
166 cone_outer_gain, ..
167 } = kind
168 {
169 *cone_outer_gain = gain;
170 }
171 Ok(())
172 })
173 }
174
175 pub fn try_ref_distance(&self, distance: f32) -> Result<(), GraphError> {
176 validate_non_negative_panner_value(distance)?;
177 self.set_panner(|kind| {
178 if let NodeKind::Panner { ref_distance, .. } = kind {
179 *ref_distance = distance;
180 }
181 Ok(())
182 })
183 }
184
185 pub fn try_max_distance(&self, distance: f32) -> Result<(), GraphError> {
186 validate_positive_panner_value(distance)?;
187 self.set_panner(|kind| {
188 if let NodeKind::Panner { max_distance, .. } = kind {
189 *max_distance = distance;
190 }
191 Ok(())
192 })
193 }
194
195 pub fn try_rolloff_factor(&self, factor: f32) -> Result<(), GraphError> {
196 if !factor.is_finite() || factor < 0.0 {
197 return Err(GraphError::InvalidPannerConfig);
198 }
199 self.set_panner(|kind| {
200 if let NodeKind::Panner { rolloff_factor, .. } = kind {
201 *rolloff_factor = factor;
202 }
203 Ok(())
204 })
205 }
206
207 #[must_use]
208 pub fn panning_model_value(&self) -> PanningModel {
209 let inner = self.graph.lock().expect("graph mutex poisoned");
210 if let NodeKind::Panner { panning_model, .. } = &inner.nodes[self.id.0].kind {
211 *panning_model
212 } else {
213 PanningModel::EqualPower
214 }
215 }
216
217 #[must_use]
218 pub fn distance_model_value(&self) -> DistanceModel {
219 let inner = self.graph.lock().expect("graph mutex poisoned");
220 if let NodeKind::Panner { distance_model, .. } = &inner.nodes[self.id.0].kind {
221 *distance_model
222 } else {
223 DistanceModel::Inverse
224 }
225 }
226
227 #[must_use]
228 pub fn cone_inner_angle_value(&self) -> f32 {
229 self.panner_value(|kind| match kind {
230 NodeKind::Panner {
231 cone_inner_angle, ..
232 } => *cone_inner_angle,
233 _ => 360.0,
234 })
235 }
236
237 #[must_use]
238 pub fn cone_outer_angle_value(&self) -> f32 {
239 self.panner_value(|kind| match kind {
240 NodeKind::Panner {
241 cone_outer_angle, ..
242 } => *cone_outer_angle,
243 _ => 360.0,
244 })
245 }
246
247 #[must_use]
248 pub fn cone_outer_gain_value(&self) -> f32 {
249 self.panner_value(|kind| match kind {
250 NodeKind::Panner {
251 cone_outer_gain, ..
252 } => *cone_outer_gain,
253 _ => 0.0,
254 })
255 }
256
257 #[must_use]
258 pub fn ref_distance_value(&self) -> f32 {
259 self.panner_value(|kind| match kind {
260 NodeKind::Panner { ref_distance, .. } => *ref_distance,
261 _ => 1.0,
262 })
263 }
264
265 #[must_use]
266 pub fn max_distance_value(&self) -> f32 {
267 self.panner_value(|kind| match kind {
268 NodeKind::Panner { max_distance, .. } => *max_distance,
269 _ => 10_000.0,
270 })
271 }
272
273 #[must_use]
274 pub fn rolloff_factor_value(&self) -> f32 {
275 self.panner_value(|kind| match kind {
276 NodeKind::Panner { rolloff_factor, .. } => *rolloff_factor,
277 _ => 1.0,
278 })
279 }
280
281 fn set_panner(
282 &self,
283 update: impl FnOnce(&mut NodeKind) -> Result<(), GraphError>,
284 ) -> Result<(), GraphError> {
285 let mut inner = self.graph.lock().expect("graph mutex poisoned");
286 update(&mut inner.nodes[self.id.0].kind)
287 }
288
289 fn panner_value(&self, read: impl FnOnce(&NodeKind) -> f32) -> f32 {
290 let inner = self.graph.lock().expect("graph mutex poisoned");
291 read(&inner.nodes[self.id.0].kind)
292 }
293
294 #[must_use]
295 fn position_x_param(&self) -> ParamId {
296 ParamId {
297 node: self.id,
298 param: ParamKind::PositionX,
299 }
300 }
301
302 #[must_use]
303 fn position_y_param(&self) -> ParamId {
304 ParamId {
305 node: self.id,
306 param: ParamKind::PositionY,
307 }
308 }
309
310 #[must_use]
311 fn position_z_param(&self) -> ParamId {
312 ParamId {
313 node: self.id,
314 param: ParamKind::PositionZ,
315 }
316 }
317
318 #[must_use]
319 fn orientation_x_param(&self) -> ParamId {
320 ParamId {
321 node: self.id,
322 param: ParamKind::OrientationX,
323 }
324 }
325
326 #[must_use]
327 fn orientation_y_param(&self) -> ParamId {
328 ParamId {
329 node: self.id,
330 param: ParamKind::OrientationY,
331 }
332 }
333
334 #[must_use]
335 fn orientation_z_param(&self) -> ParamId {
336 ParamId {
337 node: self.id,
338 param: ParamKind::OrientationZ,
339 }
340 }
341
342 #[must_use]
343 pub fn param(&self, name: &str) -> Option<AudioParamHandle> {
344 self.parameter(name)
345 }
346
347 #[must_use]
348 pub fn parameter(&self, name: &str) -> Option<AudioParamHandle> {
349 match name {
350 "positionX" | "position_x" => Some(self.position_x()),
351 "positionY" | "position_y" => Some(self.position_y()),
352 "positionZ" | "position_z" => Some(self.position_z()),
353 "orientationX" | "orientation_x" => Some(self.orientation_x()),
354 "orientationY" | "orientation_y" => Some(self.orientation_y()),
355 "orientationZ" | "orientation_z" => Some(self.orientation_z()),
356 _ => None,
357 }
358 }
359}
360
361impl From<PannerNode> for NodeId {
362 fn from(value: PannerNode) -> Self {
363 value.id
364 }
365}
366
367impl From<&PannerNode> for NodeId {
368 fn from(value: &PannerNode) -> Self {
369 value.id
370 }
371}
372
373impl_node_channel_config!(PannerNode);
374
375macro_rules! impl_audio_node_handle_with_graph {
376 ($($node:ty),* $(,)?) => {
377 $(
378 impl private::AudioNodeHandlePrivate for $node {
379 fn node_id(&self) -> NodeId {
380 self.id
381 }
382
383 fn context_identity(&self) -> Option<usize> {
384 Some(Arc::as_ptr(&self.graph) as usize)
385 }
386 }
387 )*
388 };
389}
390
391impl_audio_node_handle_with_graph!(
392 OscillatorNode,
393 ConstantSourceNode,
394 AudioBufferSourceNode,
395 SoundDataSourceNode,
396 GainNode,
397 StereoPannerNode,
398 BiquadFilterHandle,
399 IirFilterNode,
400 WaveShaperNode,
401 ConvolverNode,
402 DelayNodeHandle,
403 DynamicsCompressorNode,
404 AnalyserNode,
405 AudioWorkletNode,
406 PannerNode,
407);
408
409impl_node_channel_config!(AudioDestinationNode);