rg3d_sound/source/
spatial.rs

1//! Spatial sound source module.
2//!
3//! # Overview
4//!
5//! Spatial sound source are most interesting source in the engine. They can have additional effects such as positioning,
6//! distance attenuation, can be processed via HRTF, etc.
7//!
8//! # Usage
9//!
10//! ```no_run
11//! use std::sync::{Arc, Mutex};
12//! use rg3d_sound::buffer::SoundBufferResource;
13//! use rg3d_sound::pool::Handle;
14//! use rg3d_sound::source::{SoundSource, Status};
15//! use rg3d_sound::source::generic::GenericSourceBuilder;
16//! use rg3d_sound::context::SoundContext;
17//! use rg3d_sound::source::spatial::SpatialSourceBuilder;
18//!
19//! fn make_source(context: &mut SoundContext, buffer: SoundBufferResource) -> Handle<SoundSource> {
20//!     let source = SpatialSourceBuilder::new(GenericSourceBuilder::new()
21//!         .with_buffer(buffer)
22//!         .with_status(Status::Playing)
23//!         .build()
24//!         .unwrap())
25//!         .build_source();
26//!     context.state().add_source(source)
27//! }
28//! ```
29
30use crate::{
31    context::DistanceModel,
32    listener::Listener,
33    source::{generic::GenericSource, SoundSource},
34};
35use rg3d_core::{
36    algebra::Vector3,
37    inspect::{Inspect, PropertyInfo},
38    visitor::{Visit, VisitResult, Visitor},
39};
40use std::ops::{Deref, DerefMut};
41
42/// See module docs.
43#[derive(Debug, Clone, Inspect)]
44pub struct SpatialSource {
45    pub(in crate) generic: GenericSource,
46    #[inspect(min_value = 0.0, step = 0.05)]
47    radius: f32,
48    position: Vector3<f32>,
49    #[inspect(min_value = 0.0, step = 0.05)]
50    max_distance: f32,
51    #[inspect(min_value = 0.0, step = 0.05)]
52    rolloff_factor: f32,
53    // Some data that needed for iterative overlap-save convolution.
54    #[inspect(skip)]
55    pub(in crate) prev_left_samples: Vec<f32>,
56    #[inspect(skip)]
57    pub(in crate) prev_right_samples: Vec<f32>,
58    #[inspect(skip)]
59    pub(in crate) prev_sampling_vector: Vector3<f32>,
60    #[inspect(skip)]
61    pub(in crate) prev_distance_gain: Option<f32>,
62}
63
64impl SpatialSource {
65    /// Sets position of source in world space.
66    pub fn set_position(&mut self, position: Vector3<f32>) -> &mut Self {
67        self.position = position;
68        self
69    }
70
71    /// Returns positions of source.
72    pub fn position(&self) -> Vector3<f32> {
73        self.position
74    }
75
76    /// Sets radius of imaginable sphere around source in which no distance attenuation is applied.
77    pub fn set_radius(&mut self, radius: f32) -> &mut Self {
78        self.radius = radius;
79        self
80    }
81
82    /// Returns radius of source.
83    pub fn radius(&self) -> f32 {
84        self.radius
85    }
86
87    /// Sets rolloff factor. Rolloff factor is used in distance attenuation and has different meaning
88    /// in various distance models. It is applicable only for InverseDistance and ExponentDistance
89    /// distance models. See DistanceModel docs for formulae.
90    pub fn set_rolloff_factor(&mut self, rolloff_factor: f32) -> &mut Self {
91        self.rolloff_factor = rolloff_factor;
92        self
93    }
94
95    /// Returns rolloff factor.
96    pub fn rolloff_factor(&self) -> f32 {
97        self.rolloff_factor
98    }
99
100    /// Sets maximum distance until which distance gain will be applicable. Basically it doing this
101    /// min(max(distance, radius), max_distance) which clamps distance in radius..max_distance range.
102    /// From listener's perspective this will sound like source has stopped decreasing its volume even
103    /// if distance continue to grow.
104    pub fn set_max_distance(&mut self, max_distance: f32) -> &mut Self {
105        self.max_distance = max_distance;
106        self
107    }
108
109    /// Returns max distance.
110    pub fn max_distance(&self) -> f32 {
111        self.max_distance
112    }
113
114    /// Returns shared reference to inner generic source.
115    pub fn generic(&self) -> &GenericSource {
116        &self.generic
117    }
118
119    /// Returns mutable reference to inner generic source.
120    pub fn generic_mut(&mut self) -> &mut GenericSource {
121        &mut self.generic
122    }
123
124    // Distance models were taken from OpenAL Specification because it looks like they're
125    // standard in industry and there is no need to reinvent it.
126    // https://www.openal.org/documentation/openal-1.1-specification.pdf
127    pub(in crate) fn get_distance_gain(
128        &self,
129        listener: &Listener,
130        distance_model: DistanceModel,
131    ) -> f32 {
132        let distance = self
133            .position
134            .metric_distance(&listener.position())
135            .max(self.radius)
136            .min(self.max_distance);
137        match distance_model {
138            DistanceModel::None => 1.0,
139            DistanceModel::InverseDistance => {
140                self.radius / (self.radius + self.rolloff_factor * (distance - self.radius))
141            }
142            DistanceModel::LinearDistance => {
143                1.0 - self.radius * (distance - self.radius) / (self.max_distance - self.radius)
144            }
145            DistanceModel::ExponentDistance => (distance / self.radius).powf(-self.rolloff_factor),
146        }
147    }
148
149    pub(in crate) fn get_panning(&self, listener: &Listener) -> f32 {
150        (self.position - listener.position())
151            .try_normalize(f32::EPSILON)
152            // Fallback to look axis will give zero panning which will result in even
153            // gain in each channels (as if there was no panning at all).
154            .unwrap_or_else(|| listener.look_axis())
155            .dot(&listener.ear_axis())
156    }
157
158    pub(in crate) fn get_sampling_vector(&self, listener: &Listener) -> Vector3<f32> {
159        let to_self = self.position - listener.position();
160
161        (listener.basis() * to_self)
162            .try_normalize(f32::EPSILON)
163            // This is ok to fallback to (0, 0, 1) vector because it's given
164            // in listener coordinate system.
165            .unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0))
166    }
167}
168
169impl Deref for SpatialSource {
170    type Target = GenericSource;
171
172    fn deref(&self) -> &Self::Target {
173        &self.generic
174    }
175}
176
177impl DerefMut for SpatialSource {
178    fn deref_mut(&mut self) -> &mut Self::Target {
179        &mut self.generic
180    }
181}
182
183impl Visit for SpatialSource {
184    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
185        visitor.enter_region(name)?;
186
187        self.radius.visit("Radius", visitor)?;
188        self.position.visit("Position", visitor)?;
189
190        visitor.leave_region()
191    }
192}
193
194impl Default for SpatialSource {
195    fn default() -> Self {
196        Self {
197            generic: Default::default(),
198            radius: 1.0,
199            position: Vector3::new(0.0, 0.0, 0.0),
200            max_distance: f32::MAX,
201            rolloff_factor: 1.0,
202            prev_left_samples: Default::default(),
203            prev_right_samples: Default::default(),
204            prev_sampling_vector: Vector3::new(0.0, 0.0, 1.0),
205            prev_distance_gain: None,
206        }
207    }
208}
209
210/// Spatial source builder allows you to construct new spatial sound source with desired parameters.
211pub struct SpatialSourceBuilder {
212    generic: GenericSource,
213    radius: f32,
214    position: Vector3<f32>,
215    max_distance: f32,
216    rolloff_factor: f32,
217}
218
219impl SpatialSourceBuilder {
220    /// Creates new spatial source builder from given generic source which. Generic source can be created
221    /// using GenericSourceBuilder. See module docs for example.
222    pub fn new(generic: GenericSource) -> Self {
223        Self {
224            generic,
225            radius: 1.0,
226            position: Vector3::new(0.0, 0.0, 0.0),
227            max_distance: f32::MAX,
228            rolloff_factor: 1.0,
229        }
230    }
231
232    /// See `set_position` of SpatialSource.
233    pub fn with_position(mut self, position: Vector3<f32>) -> Self {
234        self.position = position;
235        self
236    }
237
238    /// See `set_radius` of SpatialSource.
239    pub fn with_radius(mut self, radius: f32) -> Self {
240        self.radius = radius;
241        self
242    }
243
244    /// See `set_max_distance` of SpatialSource.
245    pub fn with_max_distance(mut self, max_distance: f32) -> Self {
246        self.max_distance = max_distance;
247        self
248    }
249
250    /// See `set_rolloff_factor` of SpatialSource.
251    pub fn with_rolloff_factor(mut self, rolloff_factor: f32) -> Self {
252        self.rolloff_factor = rolloff_factor;
253        self
254    }
255
256    /// Creates new instance of spatial sound source.
257    pub fn build(self) -> SpatialSource {
258        SpatialSource {
259            generic: self.generic,
260            radius: self.radius,
261            position: self.position,
262            max_distance: self.max_distance,
263            rolloff_factor: self.rolloff_factor,
264            prev_left_samples: Default::default(),
265            prev_right_samples: Default::default(),
266            ..Default::default()
267        }
268    }
269
270    /// Creates new instance of sound source of `Spatial` variant.
271    pub fn build_source(self) -> SoundSource {
272        SoundSource::Spatial(self.build())
273    }
274}