fmod/core/system/creation.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 lanyard::Utf8CStr;
9use std::ffi::c_int;
10
11use crate::{
12 Channel, ChannelGroup, Dsp, DspType, Reverb3D, Sound, SoundBuilder, SoundGroup, System,
13};
14
15impl System {
16 /// Loads a sound into memory, opens it for streaming or sets it up for callback based sounds.
17 ///
18 /// [`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.
19 /// [`Mode::CREATE_COMPRESSED_SAMPLE`] can also be used for certain formats to play the sound directly in its compressed format from the mixer.
20 /// - 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.
21 /// - 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`].
22 /// 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.
23 /// - 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.
24 /// - To open a sound as 3D, so that it is treated as a 3D sound, use the [`Mode::D3`] flag.
25 ///
26 /// 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.
27 ///
28 /// Use [`Mode::NONBLOCKING`] to have the sound open or load in the background.
29 /// 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.
30 /// 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.
31 ///
32 /// To account for slow media that might cause buffer underrun (skipping / stuttering / repeating blocks of audio) with sounds created with [`FMOD_CREATESTREAM`],
33 /// use System::setStreamBufferSize to increase read ahead.
34 ///
35 /// 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.
36 ///
37 /// Specifying [`Mode::OPEN_MEMORY_POINT`] will POINT to your memory rather allocating its own sound buffers and duplicating it internally,
38 /// this means you cannot free the memory while FMOD is using it, until after Sound::release is called.
39 ///
40 /// With [`Mode::OPEN_MEMORY_POINT`], only PCM formats and compressed formats using [`Mode::CREATE_COMPRESSED_SAMPLE`] are supported.
41 pub fn create_sound(&self, builder: &SoundBuilder<'_>) -> Result<Sound> {
42 let mut sound = std::ptr::null_mut();
43 // FIXME is casting to mut correct?
44 let ex_info = if builder.ex_info_is_empty() {
45 std::ptr::null_mut()
46 } else {
47 std::ptr::addr_of!(builder.create_sound_ex_info).cast_mut()
48 };
49 unsafe {
50 FMOD_System_CreateSound(
51 self.inner,
52 builder.name_or_data,
53 builder.mode,
54 ex_info,
55 &mut sound,
56 )
57 .to_result()?;
58 }
59 Ok(sound.into())
60 }
61
62 /// Opens a sound for streaming.
63 ///
64 /// This is a convenience function for [`System::create_sound`] with the [`Mode::CREATE_STREAM`] flag added.
65 ///
66 /// A stream only has one decode buffer and file handle, and therefore can only be played once.
67 /// It cannot play multiple times at once because it cannot share a stream buffer if the stream is playing at different positions.
68 /// Open multiple streams to have them play concurrently.
69 pub fn create_stream(&self, builder: &SoundBuilder<'_>) -> Result<Sound> {
70 let mut sound = std::ptr::null_mut();
71 // FIXME is casting to mut correct?
72 let ex_info = if builder.ex_info_is_empty() {
73 std::ptr::null_mut()
74 } else {
75 std::ptr::addr_of!(builder.create_sound_ex_info).cast_mut()
76 };
77 unsafe {
78 FMOD_System_CreateStream(
79 self.inner,
80 builder.name_or_data,
81 builder.mode,
82 ex_info,
83 &mut sound,
84 )
85 .to_result()?;
86 }
87 Ok(sound.into())
88 }
89
90 /// WARNING: At the moment this function has no guardrails and WILL cause undefined behaviour if used incorrectly.
91 /// 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.
92 ///
93 /// Create a DSP object given a plugin description structure.
94 ///
95 /// A DSP object is a module that can be inserted into the mixing graph to allow sound filtering or sound generation.
96 /// See the DSP architecture guide for more information.
97 ///
98 /// DSPs must be attached to the DSP graph before they become active, either via ChannelControl::addDSP or DSP::addInput.
99 pub fn create_dsp(&self, description: &FMOD_DSP_DESCRIPTION) -> Result<Dsp> {
100 let mut dsp = std::ptr::null_mut();
101 unsafe {
102 FMOD_System_CreateDSP(self.inner, description, &mut dsp).to_result()?;
103 }
104 Ok(dsp.into())
105 }
106
107 ///Create a DSP object given a built in type index.
108 ///
109 /// 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.
110 ///
111 /// DSPs must be attached to the DSP graph before they become active, either via ChannelControl::addDSP or DSP::addInput.
112 ///
113 /// Using [`DspType::VstPlugin`] or [`DspType::WinampPlugin`] will return the first loaded plugin of this type.
114 /// To access other plugins of these types, use System::createDSPByPlugin instead.
115 pub fn create_dsp_by_type(&self, kind: DspType) -> Result<Dsp> {
116 let mut dsp = std::ptr::null_mut();
117 unsafe {
118 FMOD_System_CreateDSPByType(self.inner, kind.into(), &mut dsp).to_result()?;
119 }
120 Ok(dsp.into())
121 }
122
123 /// Create a [`ChannelGroup`] object.
124 ///
125 /// [`ChannelGroup`]s can be used to assign / group [`Channel`]s, for things such as volume scaling.
126 /// [`ChannelGroup`]s are also used for sub-mixing.
127 /// Any [`Channel`]s that are assigned to a [`ChannelGroup`] get submixed into that [`ChannelGroup`]'s 'tail' [`Dsp`]. See FMOD_CHANNELCONTROL_DSP_TAIL.
128 ///
129 /// 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.
130 /// See the DSP architecture guide for more information.
131 ///
132 /// All [`ChannelGroup`]s will initially output directly to the master [`ChannelGroup`] (See System::getMasterChannelGroup).
133 /// [`ChannelGroup`]s can be re-parented this with ChannelGroup::addGroup.
134 pub fn create_channel_group(&self, name: &Utf8CStr) -> Result<ChannelGroup> {
135 let mut channel_group = std::ptr::null_mut();
136 unsafe {
137 FMOD_System_CreateChannelGroup(self.inner, name.as_ptr(), &mut channel_group)
138 .to_result()?;
139 }
140 Ok(channel_group.into())
141 }
142
143 /// Creates a [`SoundGroup`] object.
144 ///
145 /// A [`SoundGroup`] is a way to address multiple [`Sound`]s at once with group level commands, such as:
146 ///
147 /// - Attributes of Sounds that are playing or about to be played, such as volume. See (SoundGroup::setVolume).
148 /// - Control of playback, such as stopping [`Sound`]s. See (SoundGroup::stop).
149 /// - Playback behavior such as 'max audible', to limit playback of certain types of [`Sound`]s. See (SoundGroup::setMaxAudible).
150 ///
151 /// Once a [`SoundGroup`] is created, Sound::setSoundGroup is used to put a [`Sound`] in a [`SoundGroup`].
152 pub fn create_sound_group(&self, name: &Utf8CStr) -> Result<SoundGroup> {
153 let mut sound_group = std::ptr::null_mut();
154 unsafe {
155 FMOD_System_CreateSoundGroup(self.inner, name.as_ptr(), &mut sound_group)
156 .to_result()?;
157 }
158 Ok(sound_group.into())
159 }
160
161 /// Creates a 'virtual reverb' object.
162 /// This object reacts to 3D location and morphs the reverb environment based on how close it is to the reverb object's center.
163 ///
164 /// Multiple reverb objects can be created to achieve a multi-reverb environment.
165 /// 1 reverb object is used for all 3D reverb objects (slot 0 by default).
166 ///
167 /// The 3D reverb object is a sphere having 3D attributes (position, minimum distance, maximum distance) and reverb properties.
168 ///
169 /// The properties and 3D attributes of all reverb objects collectively determine, along with the listener's position,
170 /// the settings of and input gains into a single 3D reverb [`Dsp`].
171 ///
172 /// When the listener is within the sphere of effect of one or more 3D reverbs,
173 /// the listener's 3D reverb properties are a weighted combination of such 3D reverbs.
174 ///
175 /// When the listener is outside all of the reverbs, no reverb is applied.
176 ///
177 /// System::setReverbProperties can be used to create an alternative reverb that can be used for 2D and background global reverb.
178 ///
179 /// 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,
180 /// otherwise FMOD_ADVANCEDSETTINGS::reverb3Dinstance can also be used to place 3D reverb on a different reverb slot.
181 ///
182 /// Use ChannelControl::setReverbProperties to turn off reverb for 2D sounds (ie set wet = 0).
183 ///
184 /// Creating multiple reverb objects does not impact performance.
185 /// These are 'virtual reverbs'.
186 /// There will still be only one reverb [`Dsp`] running that just morphs between the different virtual reverbs.
187 ///
188 /// Note about reverb [`Dsp`] unit allocation.
189 /// To remove the [`Dsp`] unit and the associated CPU cost, first make sure all 3D reverb objects are released.
190 /// 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.
191 ///
192 /// If a 3D reverb is still present, and System::setReverbProperties function is called to free the reverb,
193 /// the 3D reverb system will immediately recreate it upon the next System::update call.
194 ///
195 /// 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.
196 pub fn create_reverb_3d(&self) -> Result<Reverb3D> {
197 let mut reverb = std::ptr::null_mut();
198 unsafe {
199 FMOD_System_CreateReverb3D(self.inner, &mut reverb).to_result()?;
200 }
201 Ok(reverb.into())
202 }
203
204 /// Plays a Sound on a Channel.
205 ///
206 /// When a sound is played, it will use the sound's default frequency and priority. See Sound::setDefaults.
207 ///
208 /// A sound defined as [`Mode::D3`] will by default play at the 3D position of the listener.
209 /// 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.
210 ///
211 /// 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.
212 ///
213 /// Channels are reference counted to handle dead or stolen Channel handles.
214 /// See the white paper on Channel handles for more information.
215 ///
216 /// Playing more Sounds than physical Channels allow is handled with virtual voices.
217 /// See the white paper on Virtual Voices for more information.
218 pub fn play_sound(
219 &self,
220 sound: Sound,
221 channel_group: Option<ChannelGroup>,
222 paused: bool,
223 ) -> Result<Channel> {
224 let mut channel = std::ptr::null_mut();
225 unsafe {
226 FMOD_System_PlaySound(
227 self.inner,
228 sound.into(),
229 channel_group.map_or(std::ptr::null_mut(), ChannelGroup::into),
230 paused.into(),
231 &mut channel,
232 )
233 .to_result()?;
234 }
235 Ok(channel.into())
236 }
237
238 /// Plays a [`Dsp`] along with any of its inputs on a [`Channel`].
239 ///
240 /// Specifying a `channel_group` as part of playDSP is more efficient than using Channel::setChannelGroup after playDSP,
241 /// and could avoid audible glitches if the playDSP is not in a paused state.
242 ///
243 /// [`Channel`]s are reference counted to handle dead or stolen [`Channel`] handles. See the white paper on [`Channel`] handles for more information.
244 ///
245 /// Playing more Sounds or [`Dsp`]s than physical [`Channel`]s allowed is handled with virtual voices.
246 /// See the white paper on Virtual Voices for more information.
247 pub fn play_dsp(
248 &self,
249 dsp: Dsp,
250 channel_group: Option<ChannelGroup>,
251 paused: bool,
252 ) -> Result<Channel> {
253 let mut channel = std::ptr::null_mut();
254 unsafe {
255 FMOD_System_PlayDSP(
256 self.inner,
257 dsp.into(),
258 channel_group.map_or(std::ptr::null_mut(), ChannelGroup::into),
259 paused.into(),
260 &mut channel,
261 )
262 .to_result()?;
263 }
264 Ok(channel.into())
265 }
266
267 /// Retrieves a handle to a [`Channel`] by ID.
268 ///
269 /// This function is mainly for getting handles to existing (playing) [`Channel`]s and setting their attributes.
270 /// The only way to 'create' an instance of a [`Channel`] for playback is to use [`System::play_sound`] or [`System::play_dsp`].
271 pub fn get_channel(&self, channel_id: c_int) -> Result<Channel> {
272 let mut channel = std::ptr::null_mut();
273 unsafe {
274 FMOD_System_GetChannel(self.inner, channel_id, &mut channel).to_result()?;
275 }
276 Ok(channel.into())
277 }
278
279 /// Retrieve the description structure for a built in DSP plugin.
280 ///
281 /// FMOD_DSP_TYPE_MIXER not supported.
282 pub fn get_dsp_info_by_type(&self, dsp_type: DspType) -> Result<*const FMOD_DSP_DESCRIPTION> {
283 let mut description = std::ptr::null();
284 unsafe {
285 FMOD_System_GetDSPInfoByType(self.inner, dsp_type.into(), &mut description)
286 .to_result()?;
287 }
288 Ok(description)
289 }
290
291 /// Retrieves the master [`ChannelGroup`] that all sounds ultimately route to.
292 ///
293 /// This is the default [`ChannelGroup`] that [`Channel`]s play on,
294 /// unless a different [`ChannelGroup`] is specified with [`System::play_sound`], [`System::play_dsp`] or Channel::setChannelGroup.
295 /// A master [`ChannelGroup`] can be used to do things like set the 'master volume' for all playing [`Channel`]s. See ChannelControl::setVolume.
296 pub fn get_master_channel_group(&self) -> Result<ChannelGroup> {
297 let mut channel_group = std::ptr::null_mut();
298 unsafe {
299 FMOD_System_GetMasterChannelGroup(self.inner, &mut channel_group).to_result()?;
300 }
301 Ok(channel_group.into())
302 }
303
304 /// Retrieves the default [`SoundGroup`], where all sounds are placed when they are created.
305 ///
306 /// If [`SoundGroup`] is released, the [`Sound`]s will be put back into this [`SoundGroup`].
307 pub fn get_master_sound_group(&self) -> Result<SoundGroup> {
308 let mut sound_group = std::ptr::null_mut();
309 unsafe {
310 FMOD_System_GetMasterSoundGroup(self.inner, &mut sound_group).to_result()?;
311 }
312 Ok(sound_group.into())
313 }
314}