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 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
// 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 lanyard::Utf8CStr;
use std::ffi::c_int;
use crate::{
Channel, ChannelGroup, Dsp, DspType, Reverb3D, Sound, SoundBuilder, SoundGroup, System,
};
impl System {
/// Loads a sound into memory, opens it for streaming or sets it up for callback based sounds.
///
/// [`Mode::CREATE_SAMPLE`] will try to load and decompress the whole sound into memory, use [`Mode::CREATE_STREAM`] to open it as a stream and have it play back in realtime from disk or another medium.
/// [`Mode::CREATE_COMPRESSED_SAMPLE`] can also be used for certain formats to play the sound directly in its compressed format from the mixer.
/// - To open a file or URL as a stream, so that it decompresses / reads at runtime, instead of loading / decompressing into memory all at the time of this call, use the [`Mode::CREATE_STREAM`] flag.
/// - To open a file or URL as a compressed sound effect that is not streamed and is not decompressed into memory at load time, use [`Mode::CREATE_COMPRESSED_SAMPLE`].
/// This is supported with MPEG (mp2/mp3), ADPCM/FADPCM, XMA, AT9 and FSB Vorbis files only. This is useful for those who want realtime compressed soundeffects, but not the overhead of disk access.
/// - To open a sound as 2D, so that it is not affected by 3D processing, use the [`Mode::D2`] flag. 3D sound commands will be ignored on these types of sounds.
/// - To open a sound as 3D, so that it is treated as a 3D sound, use the [`Mode::D3`] flag.
///
/// Note that [`Mode::OPEN_RAW`], [`Mode::OPEN_MEMORY`], [`Mode::OPEN_MEMORY_POINT`] and [`Mode::OPEN_USER`] will not work here without the exinfo structure present, as more information is needed.
///
/// Use [`Mode::NONBLOCKING`] to have the sound open or load in the background.
/// You can use Sound::getOpenState to determine if it has finished loading / opening or not. While it is loading (not ready), sound functions are not accessible for that sound.
/// Do not free memory provided with [`Mode::OPEN_MEMORY`] if the sound is not in a ready state, as it will most likely lead to a crash.
///
/// To account for slow media that might cause buffer underrun (skipping / stuttering / repeating blocks of audio) with sounds created with [`FMOD_CREATESTREAM`],
/// use System::setStreamBufferSize to increase read ahead.
///
/// As using [`Mode::OPEN_USER`] causes FMOD to ignore whatever is passed as the first argument `name_or_data`, recommended practice is to pass None.
///
/// Specifying [`Mode::OPEN_MEMORY_POINT`] will POINT to your memory rather allocating its own sound buffers and duplicating it internally,
/// this means you cannot free the memory while FMOD is using it, until after Sound::release is called.
///
/// With [`Mode::OPEN_MEMORY_POINT`], only PCM formats and compressed formats using [`Mode::CREATE_COMPRESSED_SAMPLE`] are supported.
pub fn create_sound(&self, builder: &SoundBuilder<'_>) -> Result<Sound> {
let mut sound = std::ptr::null_mut();
// FIXME is casting to mut correct?
let ex_info = if builder.ex_info_is_empty() {
std::ptr::null_mut()
} else {
std::ptr::addr_of!(builder.create_sound_ex_info).cast_mut()
};
unsafe {
FMOD_System_CreateSound(
self.inner,
builder.name_or_data,
builder.mode,
ex_info,
&mut sound,
)
.to_result()?;
}
Ok(sound.into())
}
/// Opens a sound for streaming.
///
/// This is a convenience function for [`System::create_sound`] with the [`Mode::CREATE_STREAM`] flag added.
///
/// A stream only has one decode buffer and file handle, and therefore can only be played once.
/// It cannot play multiple times at once because it cannot share a stream buffer if the stream is playing at different positions.
/// Open multiple streams to have them play concurrently.
pub fn create_stream(&self, builder: &SoundBuilder<'_>) -> Result<Sound> {
let mut sound = std::ptr::null_mut();
// FIXME is casting to mut correct?
let ex_info = if builder.ex_info_is_empty() {
std::ptr::null_mut()
} else {
std::ptr::addr_of!(builder.create_sound_ex_info).cast_mut()
};
unsafe {
FMOD_System_CreateStream(
self.inner,
builder.name_or_data,
builder.mode,
ex_info,
&mut sound,
)
.to_result()?;
}
Ok(sound.into())
}
/// WARNING: At the moment this function has no guardrails and WILL cause undefined behaviour if used incorrectly.
/// The [`FMOD_DSP_DESCRIPTION`] API is *really* complicated and I felt it was better to provide an (unsafe) way to use it until I can figure out a better way to handle it.
///
/// Create a DSP object given a plugin description structure.
///
/// A DSP object is a module that can be inserted into the mixing graph to allow sound filtering or sound generation.
/// See the DSP architecture guide for more information.
///
/// DSPs must be attached to the DSP graph before they become active, either via ChannelControl::addDSP or DSP::addInput.
pub fn create_dsp(&self, description: &FMOD_DSP_DESCRIPTION) -> Result<Dsp> {
let mut dsp = std::ptr::null_mut();
unsafe {
FMOD_System_CreateDSP(self.inner, description, &mut dsp).to_result()?;
}
Ok(dsp.into())
}
///Create a DSP object given a built in type index.
///
/// A DSP object is a module that can be inserted into the mixing graph to allow sound filtering or sound generation. See the DSP architecture guide for more information.
///
/// DSPs must be attached to the DSP graph before they become active, either via ChannelControl::addDSP or DSP::addInput.
///
/// Using [`DspType::VstPlugin`] or [`DspType::WinampPlugin`] will return the first loaded plugin of this type.
/// To access other plugins of these types, use System::createDSPByPlugin instead.
pub fn create_dsp_by_type(&self, kind: DspType) -> Result<Dsp> {
let mut dsp = std::ptr::null_mut();
unsafe {
FMOD_System_CreateDSPByType(self.inner, kind.into(), &mut dsp).to_result()?;
}
Ok(dsp.into())
}
/// Create a [`ChannelGroup`] object.
///
/// [`ChannelGroup`]s can be used to assign / group [`Channel`]s, for things such as volume scaling.
/// [`ChannelGroup`]s are also used for sub-mixing.
/// Any [`Channel`]s that are assigned to a [`ChannelGroup`] get submixed into that [`ChannelGroup`]'s 'tail' [`Dsp`]. See FMOD_CHANNELCONTROL_DSP_TAIL.
///
/// If a [`ChannelGroup`] has an effect added to it, the effect is processed post-mix from the [`Channel`]s and [`ChannelGroup`]s below it in the mix hierarchy.
/// See the DSP architecture guide for more information.
///
/// All [`ChannelGroup`]s will initially output directly to the master [`ChannelGroup`] (See System::getMasterChannelGroup).
/// [`ChannelGroup`]s can be re-parented this with ChannelGroup::addGroup.
pub fn create_channel_group(&self, name: &Utf8CStr) -> Result<ChannelGroup> {
let mut channel_group = std::ptr::null_mut();
unsafe {
FMOD_System_CreateChannelGroup(self.inner, name.as_ptr(), &mut channel_group)
.to_result()?;
}
Ok(channel_group.into())
}
/// Creates a [`SoundGroup`] object.
///
/// A [`SoundGroup`] is a way to address multiple [`Sound`]s at once with group level commands, such as:
///
/// - Attributes of Sounds that are playing or about to be played, such as volume. See (SoundGroup::setVolume).
/// - Control of playback, such as stopping [`Sound`]s. See (SoundGroup::stop).
/// - Playback behavior such as 'max audible', to limit playback of certain types of [`Sound`]s. See (SoundGroup::setMaxAudible).
///
/// Once a [`SoundGroup`] is created, Sound::setSoundGroup is used to put a [`Sound`] in a [`SoundGroup`].
pub fn create_sound_group(&self, name: &Utf8CStr) -> Result<SoundGroup> {
let mut sound_group = std::ptr::null_mut();
unsafe {
FMOD_System_CreateSoundGroup(self.inner, name.as_ptr(), &mut sound_group)
.to_result()?;
}
Ok(sound_group.into())
}
/// Creates a 'virtual reverb' object.
/// This object reacts to 3D location and morphs the reverb environment based on how close it is to the reverb object's center.
///
/// Multiple reverb objects can be created to achieve a multi-reverb environment.
/// 1 reverb object is used for all 3D reverb objects (slot 0 by default).
///
/// The 3D reverb object is a sphere having 3D attributes (position, minimum distance, maximum distance) and reverb properties.
///
/// The properties and 3D attributes of all reverb objects collectively determine, along with the listener's position,
/// the settings of and input gains into a single 3D reverb [`Dsp`].
///
/// When the listener is within the sphere of effect of one or more 3D reverbs,
/// the listener's 3D reverb properties are a weighted combination of such 3D reverbs.
///
/// When the listener is outside all of the reverbs, no reverb is applied.
///
/// System::setReverbProperties can be used to create an alternative reverb that can be used for 2D and background global reverb.
///
/// To avoid this reverb interfering with the reverb slot used by the 3D reverb, 2D reverb should use a different slot id with System::setReverbProperties,
/// otherwise FMOD_ADVANCEDSETTINGS::reverb3Dinstance can also be used to place 3D reverb on a different reverb slot.
///
/// Use ChannelControl::setReverbProperties to turn off reverb for 2D sounds (ie set wet = 0).
///
/// Creating multiple reverb objects does not impact performance.
/// These are 'virtual reverbs'.
/// There will still be only one reverb [`Dsp`] running that just morphs between the different virtual reverbs.
///
/// Note about reverb [`Dsp`] unit allocation.
/// To remove the [`Dsp`] unit and the associated CPU cost, first make sure all 3D reverb objects are released.
/// Then call System::setReverbProperties with the 3D reverb's slot ID (default is 0) with a property point of 0 or NULL, to signal that the reverb instance should be deleted.
///
/// If a 3D reverb is still present, and System::setReverbProperties function is called to free the reverb,
/// the 3D reverb system will immediately recreate it upon the next System::update call.
///
/// Note that the 3D reverb system will not affect Studio events unless it is explicitly enabled by calling Studio::EventInstance::setReverbLevel on each event instance.
pub fn create_reverb_3d(&self) -> Result<Reverb3D> {
let mut reverb = std::ptr::null_mut();
unsafe {
FMOD_System_CreateReverb3D(self.inner, &mut reverb).to_result()?;
}
Ok(reverb.into())
}
/// Plays a Sound on a Channel.
///
/// When a sound is played, it will use the sound's default frequency and priority. See Sound::setDefaults.
///
/// A sound defined as [`Mode::D3`] will by default play at the 3D position of the listener.
/// To set the 3D position of the Channel before the sound is audible, start the Channel paused by setting the paused parameter to true, and call ChannelControl::set3DAttributes.
///
/// Specifying a channelgroup as part of playSound is more efficient than using Channel::setChannelGroup after playSound, and could avoid audible glitches if the playSound is not in a paused state.
///
/// Channels are reference counted to handle dead or stolen Channel handles.
/// See the white paper on Channel handles for more information.
///
/// Playing more Sounds than physical Channels allow is handled with virtual voices.
/// See the white paper on Virtual Voices for more information.
pub fn play_sound(
&self,
sound: Sound,
channel_group: Option<ChannelGroup>,
paused: bool,
) -> Result<Channel> {
let mut channel = std::ptr::null_mut();
unsafe {
FMOD_System_PlaySound(
self.inner,
sound.into(),
channel_group.map_or(std::ptr::null_mut(), ChannelGroup::into),
paused.into(),
&mut channel,
)
.to_result()?;
}
Ok(channel.into())
}
/// Plays a [`Dsp`] along with any of its inputs on a [`Channel`].
///
/// Specifying a `channel_group` as part of playDSP is more efficient than using Channel::setChannelGroup after playDSP,
/// and could avoid audible glitches if the playDSP is not in a paused state.
///
/// [`Channel`]s are reference counted to handle dead or stolen [`Channel`] handles. See the white paper on [`Channel`] handles for more information.
///
/// Playing more Sounds or [`Dsp`]s than physical [`Channel`]s allowed is handled with virtual voices.
/// See the white paper on Virtual Voices for more information.
pub fn play_dsp(
&self,
dsp: Dsp,
channel_group: Option<ChannelGroup>,
paused: bool,
) -> Result<Channel> {
let mut channel = std::ptr::null_mut();
unsafe {
FMOD_System_PlayDSP(
self.inner,
dsp.into(),
channel_group.map_or(std::ptr::null_mut(), ChannelGroup::into),
paused.into(),
&mut channel,
)
.to_result()?;
}
Ok(channel.into())
}
/// Retrieves a handle to a [`Channel`] by ID.
///
/// This function is mainly for getting handles to existing (playing) [`Channel`]s and setting their attributes.
/// The only way to 'create' an instance of a [`Channel`] for playback is to use [`System::play_sound`] or [`System::play_dsp`].
pub fn get_channel(&self, channel_id: c_int) -> Result<Channel> {
let mut channel = std::ptr::null_mut();
unsafe {
FMOD_System_GetChannel(self.inner, channel_id, &mut channel).to_result()?;
}
Ok(channel.into())
}
/// Retrieve the description structure for a built in DSP plugin.
///
/// FMOD_DSP_TYPE_MIXER not supported.
pub fn get_dsp_info_by_type(&self, dsp_type: DspType) -> Result<*const FMOD_DSP_DESCRIPTION> {
let mut description = std::ptr::null();
unsafe {
FMOD_System_GetDSPInfoByType(self.inner, dsp_type.into(), &mut description)
.to_result()?;
}
Ok(description)
}
/// Retrieves the master [`ChannelGroup`] that all sounds ultimately route to.
///
/// This is the default [`ChannelGroup`] that [`Channel`]s play on,
/// unless a different [`ChannelGroup`] is specified with [`System::play_sound`], [`System::play_dsp`] or Channel::setChannelGroup.
/// A master [`ChannelGroup`] can be used to do things like set the 'master volume' for all playing [`Channel`]s. See ChannelControl::setVolume.
pub fn get_master_channel_group(&self) -> Result<ChannelGroup> {
let mut channel_group = std::ptr::null_mut();
unsafe {
FMOD_System_GetMasterChannelGroup(self.inner, &mut channel_group).to_result()?;
}
Ok(channel_group.into())
}
/// Retrieves the default [`SoundGroup`], where all sounds are placed when they are created.
///
/// If [`SoundGroup`] is released, the [`Sound`]s will be put back into this [`SoundGroup`].
pub fn get_master_sound_group(&self) -> Result<SoundGroup> {
let mut sound_group = std::ptr::null_mut();
unsafe {
FMOD_System_GetMasterSoundGroup(self.inner, &mut sound_group).to_result()?;
}
Ok(sound_group.into())
}
}