fmod/core/system/setup.rs
1// Copyright (c) 2024 Melody Madeline 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 fmod_sys::*;
8use std::ffi::{c_float, c_int, c_uint};
9
10use crate::{
11 AdvancedSettings, ChannelControl, Speaker, SpeakerMode, System, TimeUnit, print_panic_msg,
12};
13use crate::{FmodResultExt, Result};
14
15/// Trait for this particular FMOD callback.
16///
17/// No `self` parameter is passed to the callback!
18pub trait RolloffCallback {
19 /// Callback to allow custom calculation of distance attenuation.
20 fn rolloff(channel_control: ChannelControl, distance: c_float) -> c_float;
21}
22unsafe extern "C" fn rolloff_callback_impl<C: RolloffCallback>(
23 channel_control: *mut FMOD_CHANNELCONTROL,
24 distance: c_float,
25) -> c_float {
26 let result = std::panic::catch_unwind(|| {
27 let channel_control = unsafe { ChannelControl::from_ffi(channel_control) };
28 C::rolloff(channel_control, distance)
29 });
30 match result {
31 Ok(f) => f,
32 Err(e) => {
33 print_panic_msg(&e);
34 0.0
35 }
36 }
37}
38
39#[cfg(doc)]
40use crate::{Channel, Mode, SystemBuilder};
41
42impl System {
43 /// Retrieves the maximum number of software mixed Channels possible.
44 ///
45 /// Software [`Channel`]s refers to real voices that will play,
46 /// with the return value being the maximum number of voices before successive voices start becoming virtual.
47 /// For differences between real and virtual voices see the Virtual Voices guide.
48 pub fn get_software_channels(&self) -> Result<c_int> {
49 let mut channels = 0;
50 unsafe {
51 FMOD_System_GetSoftwareChannels(self.inner.as_ptr(), &raw mut channels).to_result()?;
52 }
53 Ok(channels)
54 }
55
56 /// Retrieves the output format for the software mixer.
57 pub fn get_software_format(&self) -> Result<(c_int, SpeakerMode, c_int)> {
58 let mut sample_rate = 0;
59 let mut speaker_mode = 0;
60 let mut raw_speakers = 0;
61 unsafe {
62 FMOD_System_GetSoftwareFormat(
63 self.inner.as_ptr(),
64 &raw mut sample_rate,
65 &raw mut speaker_mode,
66 &raw mut raw_speakers,
67 )
68 .to_result()?;
69 }
70 let speaker_mode = speaker_mode.try_into()?;
71 Ok((sample_rate, speaker_mode, raw_speakers))
72 }
73
74 /// Retrieves the buffer size settings for the FMOD software mixing engine.
75 ///
76 /// To get the buffer length in milliseconds, divide it by the output rate and multiply the result by 1000.
77 /// For a buffer length of 1024 and an output rate of 48khz (see [`SystemBuilder::software_format`]), milliseconds = 1024 / 48000 * 1000 = 21.33ms.
78 /// This means the mixer updates every 21.33ms.
79 ///
80 /// To get the total buffer size multiply the buffer length by the buffer count value.
81 /// By default this would be 41024 = 4096 samples, or 421.33ms = 85.33ms.
82 /// This would generally be the total latency of the software mixer, but in reality due to one of the buffers being written to constantly,
83 /// 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.
84 ///
85 /// To convert from milliseconds back to 'samples', simply multiply the value in milliseconds by the sample rate of the output
86 /// (ie 48000 if that is what it is set to), then divide by 1000.
87 pub fn get_dsp_buffer_size(&self) -> Result<(c_uint, c_int)> {
88 let mut buffer_length = 0;
89 let mut buffer_count = 0;
90 unsafe {
91 FMOD_System_GetDSPBufferSize(
92 self.inner.as_ptr(),
93 &raw mut buffer_length,
94 &raw mut buffer_count,
95 )
96 .to_result()?;
97 }
98 Ok((buffer_length, buffer_count))
99 }
100
101 /// Sets the default file buffer size for newly opened streams.
102 ///
103 /// 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),
104 /// or CPU usage in slow machines, or by trying to play too many streams at once.
105 ///
106 /// Does not affect streams created with [`Mode::OPEN_USER`], as the buffer size is specified in [`System::create_sound`].
107 ///
108 /// Does not affect latency of playback. All streams are pre-buffered (unless opened with [`Mode::OPEN_ONLY`]), so they will always start immediately.
109 ///
110 /// Seek and Play operations can sometimes cause a reflush of this buffer.
111 ///
112 /// If [`TimeUnit::RawBytes`] is used, the memory allocated is two times the size passed in, because fmod allocates a double buffer.
113 ///
114 /// If [`TimeUnit::MS`], [`TimeUnit::PCM`] or [`TimeUnit::PCMBytes`] is used, and the stream is infinite (such as a shoutcast netstream),
115 /// or VBR, then FMOD cannot calculate an accurate compression ratio to work with when the file is opened.
116 /// This means it will then base the buffersize on [`TimeUnit::PCMBytes`], or in other words the number of PCM bytes,
117 /// 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.
118 ///
119 /// 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.
120 ///
121 /// Stream may still stutter if the codec uses a large amount of cpu time, which impacts the smaller, internal 'decode' buffer.
122 /// The decode buffer size is changeable via `FMOD_CREATESOUNDEXINFO`.
123 pub fn set_stream_buffer_size(&self, file_buffer_size: c_uint, kind: TimeUnit) -> Result<()> {
124 unsafe {
125 FMOD_System_SetStreamBufferSize(self.inner.as_ptr(), file_buffer_size, kind.into())
126 .to_result()
127 }
128 }
129
130 /// Retrieves the default file buffer size for newly opened streams.
131 pub fn get_stream_buffer_size(&self) -> Result<(c_uint, TimeUnit)> {
132 let mut file_buffer_size = 0;
133 let mut time_unit = 0;
134 unsafe {
135 FMOD_System_GetStreamBufferSize(
136 self.inner.as_ptr(),
137 &raw mut file_buffer_size,
138 &raw mut time_unit,
139 )
140 .to_result()?;
141 }
142 let time_unit = time_unit.try_into()?;
143 Ok((file_buffer_size, time_unit))
144 }
145
146 /// Sets the position of the specified speaker for the current speaker mode.
147 ///
148 /// This function allows the user to specify the position of their speaker to account for non standard setups.
149 /// It also allows the user to disable speakers from 3D consideration in a game.
150 ///
151 /// 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.
152 /// When disabling a speaker, 3D spatialization will be redistributed around the missing speaker so signal isn't lost.
153 ///
154 /// Stereo setup would look like this:
155 ///
156 /// ```rs
157 /// system.set_speaker_position(fmod::Speaker::FrontLeft, -1.0, 0.0, true);
158 /// system.set_speaker_position(system, fmod::Speaker::FrontRight, 1.0f, 0.0f, true);
159 /// ```
160 ///
161 /// 7.1 setup would look like this:
162 /// ```rs
163 /// system.set_speaker_position(fmod::Speaker::FrontLeft, -30_f32.to_radians().sin(), -30_f32.to_radians().cos(), true);
164 /// system.set_speaker_position(fmod::Speaker::FrontRight, 30_f32.to_radians().sin(), 30_f32.to_radians().cos(), true);
165 /// system.set_speaker_position(fmod::Speaker::FrontCenter, 0_f32.to_radians().sin(), 0_f32.to_radians().cos(), true);
166 /// system.set_speaker_position(fmod::Speaker::LowFrequency, 0_f32.to_radians().sin(), 0_f32.to_radians().cos(), true);
167 /// system.set_speaker_position(fmod::Speaker::SurroundLeft, -90_f32.to_radians().sin(), -90_f32.to_radians().cos(), true);
168 /// system.set_speaker_position(fmod::Speaker::SurroundRight, 90_f32.to_radians().sin(), 90_f32.to_radians().cos(), true);
169 /// system.set_speaker_position(fmod::Speaker::BackLeft, -150_f32.to_radians().sin(), -150_f32.to_radians().cos(), true);
170 /// system.set_speaker_position(fmod::Speaker::BackRight, 150_f32.to_radians().sin(), 150_f32.to_radians().cos(), true);
171 /// ```
172 ///
173 /// Calling [`SystemBuilder::software_format`] will override any customization made with this function.
174 ///
175 /// Users of the Studio API should be aware this function does not affect the speaker positions used by the Spatializer DSPs,
176 /// it is purely for Core API spatialization via `ChannelControl::set3DAttributes`.
177 pub fn set_speaker_position(
178 &self,
179 speaker: Speaker,
180 x: c_float,
181 y: c_float,
182 active: bool,
183 ) -> Result<()> {
184 unsafe {
185 FMOD_System_SetSpeakerPosition(self.inner.as_ptr(), speaker.into(), x, y, active.into())
186 .to_result()
187 }
188 }
189
190 /// Retrieves the position of the specified speaker for the current speaker mode.
191 pub fn get_speaker_position(&self, speaker: Speaker) -> Result<(c_float, c_float, bool)> {
192 let mut x = 0.0;
193 let mut y = 0.0;
194 let mut active = FMOD_BOOL::FALSE;
195 unsafe {
196 FMOD_System_GetSpeakerPosition(
197 self.inner.as_ptr(),
198 speaker.into(),
199 &raw mut x,
200 &raw mut y,
201 &raw mut active,
202 )
203 .to_result()?;
204 }
205 Ok((x, y, active.into()))
206 }
207
208 /// Sets the global doppler scale, distance factor and log roll-off scale for all 3D sound in FMOD.
209 ///
210 ///
211 ///
212 /// The `doppler_scale` is a general scaling factor for how much the pitch varies due to doppler shifting in 3D sound.
213 /// 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.
214 /// With "`doppler_scale`" you can exaggerate or diminish the effect.
215 /// FMOD's effective speed of sound at a doppler factor of 1.0 is 340 m/s.
216 ///
217 /// The `distance_factor` is the FMOD 3D engine relative distance factor, compared to 1.0 meters.
218 /// Another way to put it is that it equates to "how many units per meter does your engine have".
219 /// For example, if you are using feet then "scale" would equal 3.28.
220 /// 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.
221 /// 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`.
222 ///
223 /// The `rolloff_scale` is a global factor applied to the roll-off of sounds using roll-off modes other than `FMOD_3D_CUSTOMROLLOFF`.
224 /// When a sound uses a roll-off mode other than `FMOD_3D_CUSTOMROLLOFF` and the distance is greater than the sound's minimum distance,
225 /// the distance for the purposes of distance attenuation is calculated according to the formula `distance = (distance - min_distance) * rolloff_scale + min_distance`.
226 pub fn set_3d_settings(
227 &self,
228 doppler_scale: c_float,
229 distance_factor: c_float,
230 rollof_scale: c_float,
231 ) -> Result<()> {
232 unsafe {
233 FMOD_System_Set3DSettings(
234 self.inner.as_ptr(),
235 doppler_scale,
236 distance_factor,
237 rollof_scale,
238 )
239 .to_result()
240 }
241 }
242
243 /// Retrieves the global doppler scale, distance factor and roll-off scale for all 3D sounds.
244 pub fn get_3d_settings(&self) -> Result<(c_float, c_float, c_float)> {
245 let mut doppler_scale = 0.0;
246 let mut distance_factor = 0.0;
247 let mut rolloff_scale = 0.0;
248 unsafe {
249 FMOD_System_Get3DSettings(
250 self.inner.as_ptr(),
251 &raw mut doppler_scale,
252 &raw mut distance_factor,
253 &raw mut rolloff_scale,
254 )
255 .to_result()?;
256 }
257 Ok((doppler_scale, distance_factor, rolloff_scale))
258 }
259
260 /// Sets the number of 3D 'listeners' in the 3D sound scene.
261 ///
262 /// This function is useful mainly for split-screen game purposes.
263 ///
264 /// If the number of listeners is set to more than 1, then panning and doppler are turned off. All sound effects will be mono.
265 /// FMOD uses a 'closest sound to the listener' method to determine what should be heard in this case.
266 ///
267 /// Users of the Studio API should call [`crate::studio::System::set_listener_count`] instead of this function.
268 pub fn set_3d_listener_count(&self, count: c_int) -> Result<()> {
269 unsafe { FMOD_System_Set3DNumListeners(self.inner.as_ptr(), count).to_result() }
270 }
271
272 /// Retrieves the number of 3D listeners.
273 pub fn get_3d_listener_count(&self) -> Result<c_int> {
274 let mut count = 0;
275 unsafe {
276 FMOD_System_Get3DNumListeners(self.inner.as_ptr(), &raw mut count).to_result()?;
277 }
278 Ok(count)
279 }
280
281 /// Sets a callback to allow custom calculation of distance attenuation.
282 ///
283 /// This function overrides `FMOD_3D_INVERSEROLLOFF`, `FMOD_3D_LINEARROLLOFF`, `FMOD_3D_LINEARSQUAREROLLOFF`, `FMOD_3D_INVERSETAPEREDROLLOFF` and `FMOD_3D_CUSTOMROLLOFF`.
284 pub fn set_3d_rolloff_callback<C: RolloffCallback>(&self) -> Result<()> {
285 unsafe {
286 FMOD_System_Set3DRolloffCallback(self.inner.as_ptr(), Some(rolloff_callback_impl::<C>))
287 .to_result()
288 }
289 }
290
291 /// Unset the 3d rolloff callback, returning control of distance attenuation to FMOD.
292 pub fn unset_3d_rolloff_callback(&self) -> Result<()> {
293 unsafe { FMOD_System_Set3DRolloffCallback(self.inner.as_ptr(), None).to_result() }
294 }
295
296 /// Sets advanced settings for the system object, typically to allow adjusting of settings related to resource usage or audio quality.
297 pub fn set_advanced_settings(&self, settings: &AdvancedSettings) -> Result<()> {
298 let mut settings = settings.into();
299 unsafe {
300 FMOD_System_SetAdvancedSettings(self.inner.as_ptr(), &raw mut settings).to_result()
301 }
302 }
303
304 /// Retrieves the advanced settings for the system object.
305 pub fn get_advanced_settings(&self) -> Result<AdvancedSettings> {
306 unsafe {
307 // this structure is designed to be zeroed, so this should be ok
308 let mut settings: FMOD_ADVANCEDSETTINGS = std::mem::MaybeUninit::zeroed().assume_init();
309 settings.cbSize = size_of::<FMOD_ADVANCEDSETTINGS>() as _;
310 FMOD_System_GetAdvancedSettings(self.inner.as_ptr(), &raw mut settings).to_result()?;
311
312 Ok(AdvancedSettings::from_ffi(settings))
313 }
314 }
315}