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
// Copyright (c) 2024 Lily 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::{ChannelControl, Speaker, SpeakerMode, System, TimeUnit};
pub trait RolloffCallback {
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 channel_control = channel_control.into();
C::rolloff(channel_control, distance)
}
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, &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,
&mut sample_rate,
&mut speaker_mode,
&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, &mut buffer_length, &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 [`SoundMoude::OpenUser`], as the buffer size is specified in [`System::create_sound`].
///
/// Does not affect latency of playback. All streams are pre-buffered (unless opened with [`SoundMode::OpenOnly`]), 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::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, 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, &mut file_buffer_size, &mut time_unit)
.to_result()?;
}
let time_unit = time_unit.try_into()?;
Ok((file_buffer_size, time_unit))
}
// TODO advanced settings
/// 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, 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, speaker.into(), &mut x, &mut y, &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, 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,
&mut doppler_scale,
&mut distance_factor,
&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, 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, &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, 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, None).to_result() }
}
}