fmod/core/channel_control/spatialization.rs
1// Copyright (c) 2024 Lily Lyons
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7use std::{ffi::c_float, mem::MaybeUninit};
8
9use fmod_sys::*;
10
11use crate::{ChannelControl, Vector};
12
13impl ChannelControl {
14 /// Sets the 3D position and velocity used to apply panning, attenuation and doppler.
15 ///
16 /// The [`Mode::D3`] flag must be set on this object otherwise [`FMOD_RESULT::FMOD_ERR_NEEDS3D`] is returned.
17 ///
18 /// Vectors must be provided in the correct handedness.
19 ///
20 /// For a stereo 3D sound, you can set the spread of the left/right parts in speaker space by using [`ChannelControl::set_3d_spread`].
21 pub fn set_3d_attributes(
22 &self,
23 position: Option<Vector>,
24 velocity: Option<Vector>,
25 ) -> Result<()> {
26 // vector is layout compatible with FMOD_VECTOR
27 let position = position
28 .as_ref()
29 .map_or(std::ptr::null(), std::ptr::from_ref)
30 .cast();
31 let velocity = velocity
32 .as_ref()
33 .map_or(std::ptr::null(), std::ptr::from_ref)
34 .cast();
35 unsafe { FMOD_ChannelControl_Set3DAttributes(self.inner, position, velocity).to_result() }
36 }
37
38 /// Retrieves the 3D position and velocity used to apply panning, attenuation and doppler.
39 pub fn get_3d_attributes(&self) -> Result<(Vector, Vector)> {
40 let mut position = MaybeUninit::zeroed();
41 let mut velocity = MaybeUninit::zeroed();
42 unsafe {
43 FMOD_ChannelControl_Get3DAttributes(
44 self.inner,
45 position.as_mut_ptr(),
46 velocity.as_mut_ptr(),
47 )
48 .to_result()?;
49
50 let position = position.assume_init().into();
51 let velocity = velocity.assume_init().into();
52
53 Ok((position, velocity))
54 }
55 }
56
57 /// Sets the orientation of a 3D cone shape, used for simulated occlusion.
58 ///
59 /// The [`Mode::D3`] flag must be set on this object otherwise [`FMOD_RESULT::FMOD_ERR_NEEDS3D`] is returned.
60 ///
61 /// This function has no effect unless [`ChannelControl::set_3d_cone_settings`] has been used to change the cone inside/outside angles from the default.
62 ///
63 /// Vectors must be provided in the correct handedness.
64 pub fn set_3d_cone_orientation(&self, mut orientation: Vector) -> Result<()> {
65 // this probably doesn't need to be mutable? more safe to be mutable just in case
66 let orientation = std::ptr::from_mut(&mut orientation).cast();
67 unsafe { FMOD_ChannelControl_Set3DConeOrientation(self.inner, orientation).to_result() }
68 }
69
70 /// Retrieves the orientation of a 3D cone shape, used for simulated occlusion.
71 pub fn get_3d_cone_orientation(&self) -> Result<Vector> {
72 let mut orientation = MaybeUninit::zeroed();
73 unsafe {
74 FMOD_ChannelControl_Get3DConeOrientation(self.inner, orientation.as_mut_ptr())
75 .to_result()?;
76
77 let orientation = orientation.assume_init().into();
78
79 Ok(orientation)
80 }
81 }
82
83 /// Sets the angles and attenuation levels of a 3D cone shape, for simulated occlusion which is based on direction.
84 ///
85 /// The [`Mode::D3`] flag must be set on this object otherwise [`FMOD_RESULT::FMOD_ERR_NEEDS3D`] is returned.
86 ///
87 /// When [`ChannelControl::set_3d_cone_orientation`] is used and a 3D 'cone' is set up,
88 /// attenuation will automatically occur for a sound based on the relative angle of the direction the cone is facing,
89 /// vs the angle between the sound and the listener.
90 /// - If the relative angle is within the `inside_angle`, the sound will not have any attenuation applied.
91 /// - If the relative angle is between the `inside_angle` and `outside_angle`,
92 /// linear volume attenuation (between 1 and `outside_volume`) is applied between the two angles until it reaches the `outside_angle`.
93 /// - If the relative angle is outside of the `outside_angle` the volume does not attenuate any further.
94 pub fn set_3d_cone_settings(
95 &self,
96 inside_angle: c_float,
97 outside_angle: c_float,
98 outside_volume: c_float,
99 ) -> Result<()> {
100 unsafe {
101 FMOD_ChannelControl_Set3DConeSettings(
102 self.inner,
103 inside_angle,
104 outside_angle,
105 outside_volume,
106 )
107 .to_result()
108 }
109 }
110
111 /// Retrieves the angles and attenuation levels of a 3D cone shape, for simulated occlusion which is based on direction.
112 ///
113 /// When [`ChannelControl::set_3d_cone_orientation`] is used and a 3D 'cone' is set up,
114 /// attenuation will automatically occur for a sound based on the relative angle of the direction the cone is facing,
115 /// vs the angle between the sound and the listener.
116 /// - If the relative angle is within the `inside_angle`, the sound will not have any attenuation applied.
117 /// - If the relative angle is between the `inside_angle` and `outside_angle`,
118 /// linear volume attenuation (between 1 and `outside_volume`) is applied between the two angles until it reaches the `outside_angle`.
119 /// - If the relative angle is outside of the `outside_angle` the volume does not attenuate any further.
120 pub fn get_3d_cone_settings(&self) -> Result<(c_float, c_float, c_float)> {
121 let mut inside_angle = 0.0;
122 let mut outside_angle = 0.0;
123 let mut outside_volume = 0.0;
124 unsafe {
125 FMOD_ChannelControl_Get3DConeSettings(
126 self.inner,
127 &mut inside_angle,
128 &mut outside_angle,
129 &mut outside_volume,
130 )
131 .to_result()?;
132 }
133 Ok((inside_angle, outside_angle, outside_volume))
134 }
135
136 /// Sets a custom roll-off shape for 3D distance attenuation.
137 ///
138 /// Must be used in conjunction with [`Mode::CUSTOM_ROLLOFF`] flag to be activated.
139 ///
140 /// If [`Mode::CUSTOM_ROLLOFF`] is set and the roll-off shape is not set, FMOD will revert to [`Mode::INVERSE_ROLLOFF`] roll-off mode.
141 ///
142 /// When a custom roll-off is specified a [`Channel`] or [`ChannelGroup`]'s 3D 'minimum' and 'maximum' distances are ignored.
143 ///
144 /// The distance in-between point values is linearly interpolated until the final point where the last value is held.
145 ///
146 /// If the points are not sorted by distance, an error will result.
147 ///
148 /// # Safety
149 ///
150 /// This function does not duplicate the memory for the points internally.
151 /// The memory you pass to FMOD must remain valid while in use.
152 pub unsafe fn set_3d_custom_rolloff(&self, points: &mut [Vector]) -> Result<()> {
153 // probably doesn't need to be mutable, but more safe to be mutable just in case
154 unsafe {
155 FMOD_ChannelControl_Set3DCustomRolloff(
156 self.inner,
157 points.as_mut_ptr().cast(),
158 points.len() as i32,
159 )
160 .to_result()
161 }
162 }
163
164 /// Retrieves the current custom roll-off shape for 3D distance attenuation.
165 pub fn get_3d_custom_rolloff(&self) -> Result<Vec<Vector>> {
166 let mut points = std::ptr::null_mut();
167 let mut num_points = 0;
168 unsafe {
169 FMOD_ChannelControl_Get3DCustomRolloff(self.inner, &mut points, &mut num_points)
170 .to_result()?;
171
172 let points = std::slice::from_raw_parts(points.cast(), num_points as usize).to_vec();
173
174 Ok(points)
175 }
176 }
177
178 /// Sets an override value for the 3D distance filter.
179 ///
180 /// If distance filtering is enabled, by default the 3D engine will automatically attenuate frequencies using a lowpass and a highpass filter, based on 3D distance.
181 /// This function allows the distance filter effect to be set manually, or to be set back to 'automatic' mode.
182 ///
183 /// The FMOD_3D flag must be set on this object otherwise FMOD_ERR_NEEDS3D is returned.
184 ///
185 /// The System must be initialized with FMOD_INIT_CHANNEL_DISTANCEFILTER for this feature to work.
186 ///
187 /// #### NOTE: Currently only supported for [`Channel`], not [`ChannelGroup`].
188 pub fn set_3d_distance_filter(
189 &self,
190 custom: bool,
191 custom_level: c_float,
192 center_freq: c_float,
193 ) -> Result<()> {
194 unsafe {
195 FMOD_ChannelControl_Set3DDistanceFilter(self.inner, custom, custom_level, center_freq)
196 .to_result()
197 }
198 }
199
200 /// Retrieves the override values for the 3D distance filter.
201 pub fn get_3d_distance_filter(&self) -> Result<(bool, c_float, c_float)> {
202 let mut custom = false;
203 let mut custom_level = 0.0;
204 let mut center_freq = 0.0;
205 unsafe {
206 FMOD_ChannelControl_Get3DDistanceFilter(
207 self.inner,
208 &mut custom,
209 &mut custom_level,
210 &mut center_freq,
211 )
212 .to_result()?;
213 }
214 Ok((custom, custom_level, center_freq))
215 }
216
217 /// Sets the amount by which doppler is scaled.
218 ///
219 /// The FMOD_3D flag must be set on this object otherwise FMOD_ERR_NEEDS3D is returned.
220 ///
221 /// The doppler effect will disabled if System::set3DNumListeners is given a value greater than 1.
222 ///
223 /// #### NOTE: Currently only supported for [`Channel`], not [`ChannelGroup`].
224 pub fn set_3d_doppler_level(&self, level: c_float) -> Result<()> {
225 unsafe { FMOD_ChannelControl_Set3DDopplerLevel(self.inner, level).to_result() }
226 }
227
228 /// Retrieves the amount by which doppler is scaled.
229 pub fn get_3d_doppler_level(&self) -> Result<c_float> {
230 let mut level = 0.0;
231 unsafe { FMOD_ChannelControl_Get3DDopplerLevel(self.inner, &mut level).to_result()? }
232 Ok(level)
233 }
234
235 /// Sets the blend between 3D panning and 2D panning.
236 ///
237 /// The FMOD_3D flag must be set on this object otherwise FMOD_ERR_NEEDS3D is returned.
238 ///
239 /// 2D functions include:
240 ///
241 /// - ChannelControl::setPan
242 /// - ChannelControl::setMixLevelsOutput
243 /// - ChannelControl::setMixLevelsInput
244 /// - ChannelControl::setMixMatrix
245 ///
246 /// 3D functions include:
247 ///
248 /// - ChannelControl::set3DAttributes
249 /// - ChannelControl::set3DConeOrientation
250 /// - ChannelControl::set3DCustomRolloff
251 pub fn set_3d_level(&self, level: c_float) -> Result<()> {
252 unsafe { FMOD_ChannelControl_Set3DLevel(self.inner, level).to_result() }
253 }
254
255 /// Retrieves the blend between 3D panning and 2D panning.
256 ///
257 /// The FMOD_3D flag must be set on this object otherwise FMOD_ERR_NEEDS3D is returned.
258 ///
259 /// 2D functions include:
260 ///
261 /// - ChannelControl::setPan
262 /// - ChannelControl::setMixLevelsOutput
263 /// - ChannelControl::setMixLevelsInput
264 /// - ChannelControl::setMixMatrix
265 ///
266 /// 3D functions include:
267 ///
268 /// - ChannelControl::set3DAttributes
269 /// - ChannelControl::set3DConeOrientation
270 /// - ChannelControl::set3DCustomRolloff
271 pub fn get_3d_level(&self) -> Result<c_float> {
272 let mut level = 0.0;
273 unsafe { FMOD_ChannelControl_Get3DLevel(self.inner, &mut level).to_result()? }
274 Ok(level)
275 }
276
277 /// Sets the minimum and maximum distances used to calculate the 3D roll-off attenuation.
278 ///
279 /// When the listener is within the minimum distance of the sound source the 3D volume will be at its maximum. As the listener moves from the minimum distance to the maximum distance the sound will attenuate following the roll-off curve set. When outside the maximum distance the sound will no longer attenuate.
280 ///
281 /// Attenuation in 3D space is controlled by the roll-off mode, these are FMOD_3D_INVERSEROLLOFF, FMOD_3D_LINEARROLLOFF, FMOD_3D_LINEARSQUAREROLLOFF, FMOD_3D_INVERSETAPEREDROLLOFF, FMOD_3D_CUSTOMROLLOFF.
282 ///
283 /// Minimum distance is useful to give the impression that the sound is loud or soft in 3D space.
284 /// A sound with a small 3D minimum distance in a typical (non custom) roll-off mode will make the sound appear small, and the sound will attenuate quickly.
285 /// A sound with a large minimum distance will make the sound appear larger.
286 ///
287 /// The FMOD_3D flag must be set on this object otherwise FMOD_ERR_NEEDS3D is returned.
288 ///
289 /// To define the min and max distance per Sound instead of Channel or ChannelGroup use Sound::set3DMinMaxDistance.
290 ///
291 /// If FMOD_3D_CUSTOMROLLOFF has been set on this object these values are stored, but ignored in 3D processing.
292 pub fn set_3d_min_max_distance(&self, min: c_float, max: c_float) -> Result<()> {
293 unsafe { FMOD_ChannelControl_Set3DMinMaxDistance(self.inner, min, max).to_result() }
294 }
295
296 /// Retrieves the minimum and maximum distances used to calculate the 3D roll-off attenuation.
297 pub fn get_3d_min_max_distance(&self) -> Result<(c_float, c_float)> {
298 let mut min = 0.0;
299 let mut max = 0.0;
300 unsafe {
301 FMOD_ChannelControl_Get3DMinMaxDistance(self.inner, &mut min, &mut max).to_result()?;
302 }
303 Ok((min, max))
304 }
305
306 /// Sets the 3D attenuation factors for the direct and reverb paths.
307 ///
308 /// There is a reverb path/send when ChannelControl::setReverbProperties has been used, reverbocclusion controls its attenuation.
309 ///
310 /// If the System has been initialized with FMOD_INIT_CHANNEL_DISTANCEFILTER or
311 /// FMOD_INIT_CHANNEL_LOWPASS the directocclusion is applied as frequency filtering rather than volume attenuation.
312 pub fn set_3d_occlusion(&self, direct: c_float, reverb: c_float) -> Result<()> {
313 unsafe { FMOD_ChannelControl_Set3DOcclusion(self.inner, direct, reverb).to_result() }
314 }
315
316 /// Retrieves the 3D attenuation factors for the direct and reverb paths.
317 pub fn get_3d_occlusion(&self) -> Result<(c_float, c_float)> {
318 let mut direct = 0.0;
319 let mut reverb = 0.0;
320 unsafe {
321 FMOD_ChannelControl_Get3DOcclusion(self.inner, &mut direct, &mut reverb).to_result()?;
322 }
323 Ok((direct, reverb))
324 }
325
326 /// Sets the spread of a 3D sound in speaker space.
327 ///
328 /// When the spread angle is 0 (default) a multi-channel signal will collapse to mono and be spatialized to a single point based on ChannelControl::set3DAttributes calculations.
329 /// As the angle is increased, each channel within a multi-channel signal will be rotated away from that point.
330 /// For 2, 4, 6, 8, and 12 channel signals, the spread is arranged from leftmost speaker to rightmost speaker intelligently,
331 /// for example in 5.1 the leftmost speaker is rear left, followed by front left, center, front right then finally rear right as the rightmost speaker (LFE is not spread).
332 /// For other channel counts the individual channels are spread evenly in the order of the signal.
333 /// As the signal is spread the power will be preserved.
334 ///
335 /// For a stereo signal given different spread angles:
336 /// - 0: Sound is collapsed to mono and spatialized to a single point.
337 /// - 90: Left channel is rotated 45 degrees to the left compared with angle=0 and the right channel 45 degrees to the right.
338 /// - 180: Left channel is rotated 90 degrees to the left compared with angle=0 and the right channel 90 degrees to the right.
339 /// - 360: Left channel is rotated 180 degrees to the left and the right channel 180 degrees to the right. This means the sound is collapsed to mono and spatialized to a single point in the opposite direction compared with (angle=0).
340 pub fn set_3d_spread(&self, angle: c_float) -> Result<()> {
341 unsafe { FMOD_ChannelControl_Set3DSpread(self.inner, angle).to_result() }
342 }
343
344 /// Retrieves the spread of a 3D sound in speaker space.
345 pub fn get_3d_spread(&self) -> Result<c_float> {
346 let mut angle = 0.0;
347 unsafe { FMOD_ChannelControl_Get3DSpread(self.inner, &mut angle).to_result()? }
348 Ok(angle)
349 }
350}