Skip to main content

melody_bay/webaudio/nodes/
panner.rs

1#[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);