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}