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
// 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 fmod_sys::*;
use std::ffi::{c_float, c_int, c_uint};
use crate::{
AdvancedSettings, ChannelControl, Speaker, SpeakerMode, System, TimeUnit, print_panic_msg,
};
use crate::{FmodResultExt, Result};
/// Trait for this particular FMOD callback.
///
/// No `self` parameter is passed to the callback!
pub trait RolloffCallback {
/// Callback to allow custom calculation of distance attenuation.
fn rolloff(channel_control: ChannelControl, distance: c_float) -> c_float;
}
unsafe extern "C" fn rolloff_callback_impl<C: RolloffCallback>(
channel_control: *mut FMOD_CHANNELCONTROL,
distance: c_float,
) -> c_float {
let result = std::panic::catch_unwind(|| {
let channel_control = unsafe { ChannelControl::from_ffi(channel_control) };
C::rolloff(channel_control, distance)
});
match result {
Ok(f) => f,
Err(e) => {
print_panic_msg(&e);
0.0
}
}
}
#[cfg(doc)]
use crate::{Channel, Mode, SystemBuilder};
impl System {
/// Retrieves the maximum number of software mixed Channels possible.
///
/// Software [`Channel`]s refers to real voices that will play,
/// with the return value being the maximum number of voices before successive voices start becoming virtual.
/// For differences between real and virtual voices see the Virtual Voices guide.
pub fn get_software_channels(&self) -> Result<c_int> {
let mut channels = 0;
unsafe {
FMOD_System_GetSoftwareChannels(self.inner.as_ptr(), &raw mut channels).to_result()?;
}
Ok(channels)
}
/// Retrieves the output format for the software mixer.
pub fn get_software_format(&self) -> Result<(c_int, SpeakerMode, c_int)> {
let mut sample_rate = 0;
let mut speaker_mode = 0;
let mut raw_speakers = 0;
unsafe {
FMOD_System_GetSoftwareFormat(
self.inner.as_ptr(),
&raw mut sample_rate,
&raw mut speaker_mode,
&raw mut raw_speakers,
)
.to_result()?;
}
let speaker_mode = speaker_mode.try_into()?;
Ok((sample_rate, speaker_mode, raw_speakers))
}
/// Retrieves the buffer size settings for the FMOD software mixing engine.
///
/// To get the buffer length in milliseconds, divide it by the output rate and multiply the result by 1000.
/// For a buffer length of 1024 and an output rate of 48khz (see [`SystemBuilder::software_format`]), milliseconds = 1024 / 48000 * 1000 = 21.33ms.
/// This means the mixer updates every 21.33ms.
///
/// To get the total buffer size multiply the buffer length by the buffer count value.
/// By default this would be 41024 = 4096 samples, or 421.33ms = 85.33ms.
/// This would generally be the total latency of the software mixer, but in reality due to one of the buffers being written to constantly,
/// and the cursor position of the buffer that is audible, the latency is typically more like the (number of buffers - 1.5) multiplied by the buffer length.
///
/// To convert from milliseconds back to 'samples', simply multiply the value in milliseconds by the sample rate of the output
/// (ie 48000 if that is what it is set to), then divide by 1000.
pub fn get_dsp_buffer_size(&self) -> Result<(c_uint, c_int)> {
let mut buffer_length = 0;
let mut buffer_count = 0;
unsafe {
FMOD_System_GetDSPBufferSize(
self.inner.as_ptr(),
&raw mut buffer_length,
&raw mut buffer_count,
)
.to_result()?;
}
Ok((buffer_length, buffer_count))
}
/// Sets the default file buffer size for newly opened streams.
///
/// Larger values will consume more memory, whereas smaller values may cause buffer under-run / starvation / stuttering caused by large delays in disk access (ie netstream),
/// or CPU usage in slow machines, or by trying to play too many streams at once.
///
/// Does not affect streams created with [`Mode::OPEN_USER`], as the buffer size is specified in [`System::create_sound`].
///
/// Does not affect latency of playback. All streams are pre-buffered (unless opened with [`Mode::OPEN_ONLY`]), so they will always start immediately.
///
/// Seek and Play operations can sometimes cause a reflush of this buffer.
///
/// If [`TimeUnit::RawBytes`] is used, the memory allocated is two times the size passed in, because fmod allocates a double buffer.
///
/// If [`TimeUnit::MS`], [`TimeUnit::PCM`] or [`TimeUnit::PCMBytes`] is used, and the stream is infinite (such as a shoutcast netstream),
/// or VBR, then FMOD cannot calculate an accurate compression ratio to work with when the file is opened.
/// This means it will then base the buffersize on [`TimeUnit::PCMBytes`], or in other words the number of PCM bytes,
/// but this will be incorrect for some compressed formats. Use [`TimeUnit::RawBytes`] for these type (infinite / undetermined length) of streams for more accurate read sizes.
///
/// To determine the actual memory usage of a stream, including sound buffer and other overhead, use [`crate::memory::get_stats`] before and after creating a sound.
///
/// Stream may still stutter if the codec uses a large amount of cpu time, which impacts the smaller, internal 'decode' buffer.
/// The decode buffer size is changeable via `FMOD_CREATESOUNDEXINFO`.
pub fn set_stream_buffer_size(&self, file_buffer_size: c_uint, kind: TimeUnit) -> Result<()> {
unsafe {
FMOD_System_SetStreamBufferSize(self.inner.as_ptr(), file_buffer_size, kind.into())
.to_result()
}
}
/// Retrieves the default file buffer size for newly opened streams.
pub fn get_stream_buffer_size(&self) -> Result<(c_uint, TimeUnit)> {
let mut file_buffer_size = 0;
let mut time_unit = 0;
unsafe {
FMOD_System_GetStreamBufferSize(
self.inner.as_ptr(),
&raw mut file_buffer_size,
&raw mut time_unit,
)
.to_result()?;
}
let time_unit = time_unit.try_into()?;
Ok((file_buffer_size, time_unit))
}
/// Sets the position of the specified speaker for the current speaker mode.
///
/// This function allows the user to specify the position of their speaker to account for non standard setups.
/// It also allows the user to disable speakers from 3D consideration in a game.
///
/// This allows you to customize the position of the speakers for the current [`SpeakerMode`] by giving X (left to right) and Y (front to back) coordinates.
/// When disabling a speaker, 3D spatialization will be redistributed around the missing speaker so signal isn't lost.
///
/// Stereo setup would look like this:
///
/// ```rs
/// system.set_speaker_position(fmod::Speaker::FrontLeft, -1.0, 0.0, true);
/// system.set_speaker_position(system, fmod::Speaker::FrontRight, 1.0f, 0.0f, true);
/// ```
///
/// 7.1 setup would look like this:
/// ```rs
/// system.set_speaker_position(fmod::Speaker::FrontLeft, -30_f32.to_radians().sin(), -30_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::FrontRight, 30_f32.to_radians().sin(), 30_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::FrontCenter, 0_f32.to_radians().sin(), 0_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::LowFrequency, 0_f32.to_radians().sin(), 0_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::SurroundLeft, -90_f32.to_radians().sin(), -90_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::SurroundRight, 90_f32.to_radians().sin(), 90_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::BackLeft, -150_f32.to_radians().sin(), -150_f32.to_radians().cos(), true);
/// system.set_speaker_position(fmod::Speaker::BackRight, 150_f32.to_radians().sin(), 150_f32.to_radians().cos(), true);
/// ```
///
/// Calling [`SystemBuilder::software_format`] will override any customization made with this function.
///
/// Users of the Studio API should be aware this function does not affect the speaker positions used by the Spatializer DSPs,
/// it is purely for Core API spatialization via `ChannelControl::set3DAttributes`.
pub fn set_speaker_position(
&self,
speaker: Speaker,
x: c_float,
y: c_float,
active: bool,
) -> Result<()> {
unsafe {
FMOD_System_SetSpeakerPosition(self.inner.as_ptr(), speaker.into(), x, y, active.into())
.to_result()
}
}
/// Retrieves the position of the specified speaker for the current speaker mode.
pub fn get_speaker_position(&self, speaker: Speaker) -> Result<(c_float, c_float, bool)> {
let mut x = 0.0;
let mut y = 0.0;
let mut active = FMOD_BOOL::FALSE;
unsafe {
FMOD_System_GetSpeakerPosition(
self.inner.as_ptr(),
speaker.into(),
&raw mut x,
&raw mut y,
&raw mut active,
)
.to_result()?;
}
Ok((x, y, active.into()))
}
/// Sets the global doppler scale, distance factor and log roll-off scale for all 3D sound in FMOD.
///
///
///
/// The `doppler_scale` is a general scaling factor for how much the pitch varies due to doppler shifting in 3D sound.
/// Doppler is the pitch bending effect when a sound comes towards the listener or moves away from it, much like the effect you hear when a train goes past you with its horn sounding.
/// With "`doppler_scale`" you can exaggerate or diminish the effect.
/// FMOD's effective speed of sound at a doppler factor of 1.0 is 340 m/s.
///
/// The `distance_factor` is the FMOD 3D engine relative distance factor, compared to 1.0 meters.
/// Another way to put it is that it equates to "how many units per meter does your engine have".
/// For example, if you are using feet then "scale" would equal 3.28.
/// This only affects doppler. If you keep your min/max distance, custom roll-off curves, and positions in scale relative to each other, the volume roll-off will not change.
/// If you set this, the `min_distance` of a sound will automatically set itself to this value when it is created in case the user forgets to set the `min_distance` to match the new `distance_factor`.
///
/// The `rolloff_scale` is a global factor applied to the roll-off of sounds using roll-off modes other than `FMOD_3D_CUSTOMROLLOFF`.
/// When a sound uses a roll-off mode other than `FMOD_3D_CUSTOMROLLOFF` and the distance is greater than the sound's minimum distance,
/// the distance for the purposes of distance attenuation is calculated according to the formula `distance = (distance - min_distance) * rolloff_scale + min_distance`.
pub fn set_3d_settings(
&self,
doppler_scale: c_float,
distance_factor: c_float,
rollof_scale: c_float,
) -> Result<()> {
unsafe {
FMOD_System_Set3DSettings(
self.inner.as_ptr(),
doppler_scale,
distance_factor,
rollof_scale,
)
.to_result()
}
}
/// Retrieves the global doppler scale, distance factor and roll-off scale for all 3D sounds.
pub fn get_3d_settings(&self) -> Result<(c_float, c_float, c_float)> {
let mut doppler_scale = 0.0;
let mut distance_factor = 0.0;
let mut rolloff_scale = 0.0;
unsafe {
FMOD_System_Get3DSettings(
self.inner.as_ptr(),
&raw mut doppler_scale,
&raw mut distance_factor,
&raw mut rolloff_scale,
)
.to_result()?;
}
Ok((doppler_scale, distance_factor, rolloff_scale))
}
/// Sets the number of 3D 'listeners' in the 3D sound scene.
///
/// This function is useful mainly for split-screen game purposes.
///
/// If the number of listeners is set to more than 1, then panning and doppler are turned off. All sound effects will be mono.
/// FMOD uses a 'closest sound to the listener' method to determine what should be heard in this case.
///
/// Users of the Studio API should call [`crate::studio::System::set_listener_count`] instead of this function.
pub fn set_3d_listener_count(&self, count: c_int) -> Result<()> {
unsafe { FMOD_System_Set3DNumListeners(self.inner.as_ptr(), count).to_result() }
}
/// Retrieves the number of 3D listeners.
pub fn get_3d_listener_count(&self) -> Result<c_int> {
let mut count = 0;
unsafe {
FMOD_System_Get3DNumListeners(self.inner.as_ptr(), &raw mut count).to_result()?;
}
Ok(count)
}
/// Sets a callback to allow custom calculation of distance attenuation.
///
/// This function overrides `FMOD_3D_INVERSEROLLOFF`, `FMOD_3D_LINEARROLLOFF`, `FMOD_3D_LINEARSQUAREROLLOFF`, `FMOD_3D_INVERSETAPEREDROLLOFF` and `FMOD_3D_CUSTOMROLLOFF`.
pub fn set_3d_rolloff_callback<C: RolloffCallback>(&self) -> Result<()> {
unsafe {
FMOD_System_Set3DRolloffCallback(self.inner.as_ptr(), Some(rolloff_callback_impl::<C>))
.to_result()
}
}
/// Unset the 3d rolloff callback, returning control of distance attenuation to FMOD.
pub fn unset_3d_rolloff_callback(&self) -> Result<()> {
unsafe { FMOD_System_Set3DRolloffCallback(self.inner.as_ptr(), None).to_result() }
}
/// Sets advanced settings for the system object, typically to allow adjusting of settings related to resource usage or audio quality.
pub fn set_advanced_settings(&self, settings: &AdvancedSettings) -> Result<()> {
let mut settings = settings.into();
unsafe {
FMOD_System_SetAdvancedSettings(self.inner.as_ptr(), &raw mut settings).to_result()
}
}
/// Retrieves the advanced settings for the system object.
pub fn get_advanced_settings(&self) -> Result<AdvancedSettings> {
unsafe {
// this structure is designed to be zeroed, so this should be ok
let mut settings: FMOD_ADVANCEDSETTINGS = std::mem::MaybeUninit::zeroed().assume_init();
settings.cbSize = size_of::<FMOD_ADVANCEDSETTINGS>() as _;
FMOD_System_GetAdvancedSettings(self.inner.as_ptr(), &raw mut settings).to_result()?;
Ok(AdvancedSettings::from_ffi(settings))
}
}
}