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