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