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}