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}