fmod/core/
sound_builder.rs

1use std::ffi::{c_char, c_int, c_uint, c_void};
2use std::marker::PhantomData;
3
4use crate::{FmodResultExt, Guid, Result};
5use fmod_sys::*;
6use lanyard::Utf8CStr;
7
8use crate::{ChannelOrder, Mode, SoundFormat, SoundGroup, SoundType, TimeUnit, panic_wrapper};
9
10use super::{
11    FileSystemAsync, FileSystemSync, Sound, System, async_filesystem_cancel, async_filesystem_read,
12    filesystem_close, filesystem_open, filesystem_read, filesystem_seek,
13};
14
15#[cfg(doc)]
16use crate::Error;
17
18/// A builder for creating a [`Sound`].
19#[derive(Debug)]
20pub struct SoundBuilder<'a> {
21    pub(crate) mode: FMOD_MODE,
22    pub(crate) create_sound_ex_info: FMOD_CREATESOUNDEXINFO,
23    pub(crate) name_or_data: *const c_char,
24    pub(crate) _phantom: PhantomData<&'a ()>,
25}
26
27const EMPTY_EXINFO: FMOD_CREATESOUNDEXINFO = unsafe {
28    FMOD_CREATESOUNDEXINFO {
29        cbsize: std::mem::size_of::<FMOD_CREATESOUNDEXINFO>() as c_int,
30        ..std::mem::MaybeUninit::zeroed().assume_init()
31    }
32};
33
34/// Capture or provide sound data as it is decoded.
35pub trait PcmCallback {
36    /// Callback to provide audio for [`SoundBuilder::open_user`], or capture audio as it is decoded.
37    fn read(sound: Sound, data: &mut [u8]) -> Result<()>;
38
39    /// Callback to perform seeking for [`SoundBuilder::open_user`], or capture seek requests.
40    fn set_position(
41        sound: Sound,
42        subsound: c_int,
43        position: c_uint,
44        position_type: TimeUnit,
45    ) -> Result<()>;
46}
47
48/// Callback to be called when a sound has finished loading, or a non blocking seek is occuring.
49///
50/// Return code currently ignored.
51///
52/// Note that for non blocking streams a seek could occur when restarting the sound after the first playthrough.
53/// This will result in a callback being triggered again.
54///
55/// # Safety
56///
57/// Since this callback can occur from the async thread, there are restrictions about what functions can be called during the callback.
58/// All [`Sound`] functions are safe to call, except for [`Sound::set_sound_group`] and [`Sound::release`].
59/// It is also safe to call [`System::get_userdata`].
60/// The rest of the Core API and the Studio API is not allowed. Calling a non-allowed function will return [`Error::InvalidThread`].
61pub unsafe trait NonBlockCallback {
62    /// Call this particular callback.
63    // "return code is ignored". so do we want to allow returning a result?
64    fn call(sound: Sound, result: Result<()>) -> Result<()>;
65}
66
67// setters
68impl<'a> SoundBuilder<'a> {
69    /// Open a file or url.
70    pub const fn open(filename: &'a Utf8CStr) -> Self {
71        Self {
72            mode: 0,
73            create_sound_ex_info: EMPTY_EXINFO,
74            name_or_data: filename.as_ptr(),
75            _phantom: PhantomData,
76        }
77    }
78
79    /// Open a user-created static sample or stream.
80    pub const fn open_user(
81        length: c_uint,
82        channel_count: c_int,
83        default_frequency: c_int,
84        format: SoundFormat,
85    ) -> Self {
86        Self {
87            mode: FMOD_OPENUSER,
88            create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
89                length,
90                numchannels: channel_count,
91                defaultfrequency: default_frequency,
92                format: format as _,
93                ..EMPTY_EXINFO
94            },
95            name_or_data: std::ptr::null(),
96            _phantom: PhantomData,
97        }
98    }
99
100    /// Open the sound using a byte slice.
101    ///
102    /// # Safety
103    ///
104    /// The slice must remain valid until the sound has been *loaded*.
105    /// See the [`Mode`] docs for more information.
106    pub const unsafe fn open_memory(data: &'a [u8]) -> Self {
107        Self {
108            mode: FMOD_OPENMEMORY,
109            create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
110                length: data.len() as c_uint,
111                ..EMPTY_EXINFO
112            },
113            name_or_data: data.as_ptr().cast(),
114            _phantom: PhantomData,
115        }
116    }
117
118    /// Open the sound using a byte slice.
119    ///
120    /// # Safety
121    ///
122    /// The slice must remain valid until the sound has been *released*.
123    /// Unlike [`Self::open_memory`] this function does not copy the data, so it is even more unsafe!
124    pub const unsafe fn open_memory_point(data: &'a [u8]) -> Self {
125        Self {
126            mode: FMOD_OPENMEMORY_POINT,
127            create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
128                length: data.len() as c_uint,
129                ..EMPTY_EXINFO
130            },
131            name_or_data: data.as_ptr().cast(),
132            _phantom: PhantomData,
133        }
134    }
135
136    /// Specify a custom filesystem to open the [`Sound`].
137    // FIXME is this a valid API?
138    #[must_use]
139    pub const fn with_filesystem<F: FileSystemSync + FileSystemAsync>(
140        mut self,
141        userdata: *mut c_void,
142    ) -> Self {
143        self.create_sound_ex_info.fileuseropen = Some(filesystem_open::<F>);
144        self.create_sound_ex_info.fileuserclose = Some(filesystem_close::<F>);
145        self.create_sound_ex_info.fileuserread = Some(filesystem_read::<F>);
146        self.create_sound_ex_info.fileuserseek = Some(filesystem_seek::<F>);
147        self.create_sound_ex_info.fileuserasyncread = Some(async_filesystem_read::<F>);
148        self.create_sound_ex_info.fileuserasynccancel = Some(async_filesystem_cancel::<F>);
149        self.create_sound_ex_info.fileuserdata = userdata;
150        self
151    }
152
153    /// Specify a custom *sync* filesystem  to open the [`Sound`].
154    #[must_use]
155    pub const fn with_filesystem_sync<F: FileSystemSync>(mut self, userdata: *mut c_void) -> Self {
156        self.create_sound_ex_info.fileuseropen = Some(filesystem_open::<F>);
157        self.create_sound_ex_info.fileuserclose = Some(filesystem_close::<F>);
158        self.create_sound_ex_info.fileuserread = Some(filesystem_read::<F>);
159        self.create_sound_ex_info.fileuserseek = Some(filesystem_seek::<F>);
160        self.create_sound_ex_info.fileuserasyncread = None;
161        self.create_sound_ex_info.fileuserasynccancel = None;
162        self.create_sound_ex_info.fileuserdata = userdata;
163        self
164    }
165
166    /// Specify a custom *async* filesystem  to open the [`Sound`].
167    #[must_use]
168    pub const fn with_filesystem_async<F: FileSystemAsync>(
169        mut self,
170        userdata: *mut c_void,
171    ) -> Self {
172        self.create_sound_ex_info.fileuseropen = Some(filesystem_open::<F>);
173        self.create_sound_ex_info.fileuserclose = Some(filesystem_close::<F>);
174        self.create_sound_ex_info.fileuserasyncread = Some(async_filesystem_read::<F>);
175        self.create_sound_ex_info.fileuserasynccancel = Some(async_filesystem_cancel::<F>);
176        self.create_sound_ex_info.fileuserread = None;
177        self.create_sound_ex_info.fileuserseek = None;
178        self.create_sound_ex_info.fileuserdata = userdata;
179        self
180    }
181
182    /// # Safety
183    ///
184    /// The [`FMOD_CREATESOUNDEXINFO`] must be valid.
185    #[must_use]
186    pub const unsafe fn with_raw_ex_info(mut self, ex_info: FMOD_CREATESOUNDEXINFO) -> Self {
187        self.create_sound_ex_info = ex_info;
188        self
189    }
190
191    /// File offset to start reading from.
192    #[must_use]
193    pub const fn with_file_offset(mut self, file_offset: c_uint) -> Self {
194        self.create_sound_ex_info.fileoffset = file_offset;
195        self
196    }
197
198    /// Ignore the file format and treat as raw PCM.
199    #[must_use]
200    pub const fn with_open_raw(
201        mut self,
202        channel_count: c_int,
203        default_frequency: c_int,
204        format: SoundFormat,
205    ) -> Self {
206        self.mode |= FMOD_OPENRAW;
207        self.create_sound_ex_info.numchannels = channel_count;
208        self.create_sound_ex_info.defaultfrequency = default_frequency;
209        self.create_sound_ex_info.format = format as _;
210        self
211    }
212
213    /// Set the [`Mode`] flags for this builder.
214    ///
215    /// [`Mode::OPEN_MEMORY`], [`Mode::OPEN_MEMORY_POINT`],
216    /// [`Mode::OPEN_USER`], and [`Mode::OPEN_RAW`] cannot be set using this function.
217    ///
218    /// Please use constructors on this type to use those modes.
219    #[must_use]
220    pub const fn with_mode(mut self, mode: Mode) -> Self {
221        const DISABLE_MODES: Mode = Mode::OPEN_MEMORY
222            .union(Mode::OPEN_MEMORY_POINT)
223            .union(Mode::OPEN_USER)
224            .union(Mode::OPEN_RAW);
225
226        let mode = mode.difference(DISABLE_MODES); // these modes are not allowed to be set by the user, so we unset them
227        let mode: FMOD_MODE = mode.bits();
228        self.mode |= mode;
229        self
230    }
231
232    /// Size of the decoded buffer for [`Mode::CREATE_STREAM`], or the block size used with pcmreadcallback for [`SoundBuilder::open_user`].
233    #[must_use]
234    pub const fn with_decode_buffer_size(mut self, size: c_uint) -> Self {
235        self.create_sound_ex_info.decodebuffersize = size;
236        self
237    }
238
239    /// Initial subsound to seek to for [`Mode::CREATE_STREAM`].
240    #[must_use]
241    pub const fn with_initial_subsound(mut self, initial_subsound: c_int) -> Self {
242        self.create_sound_ex_info.initialsubsound = initial_subsound;
243        self
244    }
245
246    /// Number of subsounds available for [`SoundBuilder::open_user`], or maximum subsounds to load from file.
247    #[must_use]
248    pub const fn with_subsound_count(mut self, count: c_int) -> Self {
249        self.create_sound_ex_info.numsubsounds = count;
250        self
251    }
252
253    /// List of subsound indices to load from file.
254    // TODO: check if this is safe
255    #[must_use]
256    pub const fn with_inclusion_list(mut self, list: &'a [c_int]) -> Self {
257        self.create_sound_ex_info.inclusionlist = list.as_ptr().cast_mut().cast();
258        self.create_sound_ex_info.inclusionlistnum = list.len() as c_int;
259        self
260    }
261
262    /// File path for a [`SoundType::DLS`] sample set to use when loading a [`SoundType::MIDI`] file, see below for defaults.
263    // TODO check safety
264    #[must_use]
265    pub const fn with_dls_name(mut self, dls_name: &'a Utf8CStr) -> Self {
266        self.create_sound_ex_info.dlsname = dls_name.as_ptr();
267        self
268    }
269
270    /// Key for encrypted [`SoundType::FSB`] file, cannot be used in conjunction with [`Self::open_memory_point`].
271    // TODO check safety
272    #[must_use]
273    pub const fn with_encryption_key(mut self, key: &'a Utf8CStr) -> Self {
274        self.create_sound_ex_info.encryptionkey = key.as_ptr();
275        self
276    }
277
278    /// Maximum voice count for [`SoundType::MIDI`] / [`SoundType::IT`].
279    #[must_use]
280    pub fn with_max_polyphony(mut self, max_polyphony: c_int) -> Self {
281        self.create_sound_ex_info.maxpolyphony = max_polyphony;
282        self
283    }
284
285    /// Attempt to load using the specified type first instead of loading in codec priority order.
286    #[must_use]
287    pub const fn with_suggested_sound_type(mut self, sound_type: SoundType) -> Self {
288        self.create_sound_ex_info.suggestedsoundtype = sound_type as _;
289        self
290    }
291
292    /// Buffer size for reading the file, -1 to disable buffering.
293    #[must_use]
294    pub const fn with_file_buffer_size(mut self, size: c_int) -> Self {
295        self.create_sound_ex_info.filebuffersize = size;
296        self
297    }
298
299    /// Custom ordering of speakers for this sound data.
300    #[must_use]
301    pub const fn with_channel_order(mut self, order: ChannelOrder) -> Self {
302        self.create_sound_ex_info.channelorder = order as _;
303        self
304    }
305
306    /// [`SoundGroup`] to place the created [`Sound`] in once created.
307    #[must_use]
308    pub fn with_initial_sound_group(mut self, group: SoundGroup) -> Self {
309        self.create_sound_ex_info.initialsoundgroup = group.into();
310        self
311    }
312
313    /// Initial position to seek to for [`Mode::CREATE_STREAM`].
314    #[must_use]
315    pub const fn with_initial_seek_position(mut self, position: c_uint, unit: TimeUnit) -> Self {
316        self.create_sound_ex_info.initialseekposition = position;
317        self.create_sound_ex_info.initialseekpostype = unit as _;
318        self
319    }
320
321    /// Ignore [`System::set_filesystem_sync`] and this [`SoundBuilder`]'s file callbacks.
322    #[must_use]
323    pub const fn with_ignore_set_filesystem(mut self, ignore: bool) -> Self {
324        self.create_sound_ex_info.ignoresetfilesystem = ignore as _;
325        self
326    }
327
328    /// Hardware / software decoding policy for [`SoundType::AudioQueue`].
329    #[must_use]
330    pub const fn with_audioqueue_policy(mut self, policy: c_uint) -> Self {
331        self.create_sound_ex_info.audioqueuepolicy = policy;
332        self
333    }
334
335    /// Mixer granularity for [`SoundType::MIDI`] sounds, smaller numbers give a more accurate reproduction at the cost of higher CPU usage.
336    #[must_use]
337    pub const fn with_min_midi_granularity(mut self, granularity: c_uint) -> Self {
338        self.create_sound_ex_info.minmidigranularity = granularity as _;
339        self
340    }
341
342    /// Thread index to execute [`Mode::NONBLOCKING`] loads on for parallel Sound loading.
343    #[must_use]
344    pub const fn with_non_block_thread_id(mut self, id: c_int) -> Self {
345        self.create_sound_ex_info.nonblockthreadid = id as _;
346        self
347    }
348
349    /// On input, GUID of already loaded [`SoundType::FSB`] file to reduce disk access, on output, GUID of loaded FSB.
350    // TODO check safety
351    #[must_use]
352    pub const fn with_fsb_guid(mut self, guid: &'a Guid) -> Self {
353        self.create_sound_ex_info.fsbguid = std::ptr::from_ref(guid).cast_mut().cast();
354        self
355    }
356
357    /// Specify a PCM callback.
358    #[must_use]
359    pub const fn with_pcm_callback<C: PcmCallback>(mut self) -> Self {
360        unsafe extern "C" fn pcm_read<C: PcmCallback>(
361            sound: *mut FMOD_SOUND,
362            data: *mut c_void,
363            data_len: c_uint,
364        ) -> FMOD_RESULT {
365            panic_wrapper(|| {
366                let result = C::read(unsafe { Sound::from_ffi(sound) }, unsafe {
367                    std::slice::from_raw_parts_mut(data.cast(), data_len as _)
368                });
369                FMOD_RESULT::from_result(result)
370            })
371        }
372        unsafe extern "C" fn pcm_set_pos<C: PcmCallback>(
373            sound: *mut FMOD_SOUND,
374            subsound: c_int,
375            position: c_uint,
376            postype: FMOD_TIMEUNIT,
377        ) -> FMOD_RESULT {
378            panic_wrapper(|| {
379                let result = C::set_position(
380                    unsafe { Sound::from_ffi(sound) },
381                    subsound,
382                    position,
383                    postype.try_into().unwrap(),
384                );
385                FMOD_RESULT::from_result(result)
386            })
387        }
388
389        self.create_sound_ex_info.pcmreadcallback = Some(pcm_read::<C>);
390        self.create_sound_ex_info.pcmsetposcallback = Some(pcm_set_pos::<C>);
391
392        self
393    }
394
395    /// Callback to notify completion for [`Mode::NONBLOCKING`], occurs during creation and seeking / restarting streams.
396    #[must_use]
397    pub const fn with_nonblock_callback<C: NonBlockCallback>(mut self) -> Self {
398        unsafe extern "C" fn nonblock_callback<C: NonBlockCallback>(
399            sound: *mut FMOD_SOUND,
400            result: FMOD_RESULT,
401        ) -> FMOD_RESULT {
402            panic_wrapper(|| {
403                let result = C::call(unsafe { Sound::from_ffi(sound) }, result.to_result());
404                FMOD_RESULT::from_result(result)
405            })
406        }
407
408        self.create_sound_ex_info.nonblockcallback = Some(nonblock_callback::<C>);
409
410        self
411    }
412
413    pub(crate) fn ex_info_is_empty(&self) -> bool {
414        self.create_sound_ex_info == EMPTY_EXINFO
415    }
416
417    /// Helper method that forwards to [`System::create_sound`].
418    pub fn build(&self, system: System) -> Result<Sound> {
419        system.create_sound(self)
420    }
421
422    /// Helper method that forwards to [`System::create_stream`].
423    pub fn build_stream(&self, system: System) -> Result<Sound> {
424        system.create_stream(self)
425    }
426}
427
428// getters
429impl<'a> SoundBuilder<'a> {
430    /// Get the mode of this [`SoundBuilder`].
431    pub const fn mode(&self) -> Mode {
432        Mode::from_bits_truncate(self.mode)
433    }
434
435    /// Get the raw ex info of this [`SoundBuilder`].
436    pub const fn raw_ex_info(&self) -> FMOD_CREATESOUNDEXINFO {
437        self.create_sound_ex_info
438    }
439
440    /// Get the raw name/data/url of this [`SoundBuilder`].
441    pub const fn raw_name_or_data(&self) -> *const c_char {
442        self.name_or_data
443    }
444
445    /// Get the name or url of this [`SoundBuilder`].
446    ///
447    /// Returns `None` if [`Mode::OPEN_MEMORY`] or [`Mode::OPEN_MEMORY_POINT`] or [`Mode::OPEN_USER`] are set.
448    pub fn name_or_url(&self) -> Option<&Utf8CStr> {
449        if self
450            .mode()
451            .intersects(Mode::OPEN_MEMORY | Mode::OPEN_MEMORY_POINT | Mode::OPEN_USER)
452        {
453            None
454        } else {
455            Some(unsafe { Utf8CStr::from_ptr_unchecked(self.name_or_data) })
456        }
457    }
458
459    /// Get the data of this [`SoundBuilder`].
460    ///
461    /// Returns `Some` if [`Mode::OPEN_MEMORY`] or [`Mode::OPEN_MEMORY_POINT`] are set.
462    pub fn data(&self) -> Option<&[u8]> {
463        if self
464            .mode()
465            .intersects(Mode::OPEN_MEMORY | Mode::OPEN_MEMORY_POINT)
466        {
467            Some(unsafe {
468                std::slice::from_raw_parts(
469                    self.name_or_data.cast(),
470                    self.create_sound_ex_info.length as usize,
471                )
472            })
473        } else {
474            None
475        }
476    }
477
478    /// Get the length of data of this [`SoundBuilder`].
479    pub const fn length(&self) -> c_uint {
480        self.create_sound_ex_info.length
481    }
482
483    /// Get the file offset of this [`SoundBuilder`].
484    pub const fn file_offset(&self) -> c_uint {
485        self.create_sound_ex_info.fileoffset
486    }
487
488    /// Get the channel count of this [`SoundBuilder`].
489    pub const fn channel_count(&self) -> c_int {
490        self.create_sound_ex_info.numchannels
491    }
492
493    /// Get the default frequency of this [`SoundBuilder`].
494    pub const fn default_frequency(&self) -> c_int {
495        self.create_sound_ex_info.defaultfrequency
496    }
497
498    /// Get the sound format of this [`SoundBuilder`].
499    #[allow(clippy::missing_panics_doc)] // this function can't panic in practice as we control the sound format
500    pub fn format(&self) -> SoundFormat {
501        self.create_sound_ex_info.format.try_into().unwrap()
502    }
503
504    /// Get the decode buffer size of this [`SoundBuilder`].
505    pub const fn decode_buffer_size(&self) -> c_uint {
506        self.create_sound_ex_info.decodebuffersize
507    }
508
509    /// Get the initial subsound of this [`SoundBuilder`].
510    pub const fn initial_subsound(&self) -> c_int {
511        self.create_sound_ex_info.initialsubsound
512    }
513
514    /// Get the subsound count of this [`SoundBuilder`].
515    pub const fn subsound_count(&self) -> c_int {
516        self.create_sound_ex_info.numsubsounds
517    }
518
519    /// Get the inclusion list of this [`SoundBuilder`].
520    pub fn inclusion_list(&self) -> Option<&'a [c_int]> {
521        if self.create_sound_ex_info.inclusionlist.is_null() {
522            None
523        } else {
524            Some(unsafe {
525                std::slice::from_raw_parts(
526                    self.create_sound_ex_info.inclusionlist.cast(),
527                    self.create_sound_ex_info.inclusionlistnum as usize,
528                )
529            })
530        }
531    }
532
533    /// Get the DLS name of this [`SoundBuilder`].
534    pub fn dls_name(&self) -> Option<&Utf8CStr> {
535        if self.create_sound_ex_info.dlsname.is_null() {
536            None
537        } else {
538            Some(unsafe { Utf8CStr::from_ptr_unchecked(self.create_sound_ex_info.dlsname) })
539        }
540    }
541
542    /// Get the encryption key of this [`SoundBuilder`].
543    pub fn encryption_key(&self) -> Option<&Utf8CStr> {
544        if self.create_sound_ex_info.encryptionkey.is_null() {
545            None
546        } else {
547            Some(unsafe { Utf8CStr::from_ptr_unchecked(self.create_sound_ex_info.encryptionkey) })
548        }
549    }
550
551    /// Get the max polyphony of this [`SoundBuilder`].
552    pub const fn max_polyphony(&self) -> c_int {
553        self.create_sound_ex_info.maxpolyphony
554    }
555
556    /// Get the suggested sound type of this [`SoundBuilder`].
557    #[allow(clippy::missing_panics_doc)] // this function can't panic in practice as we control the sound type
558    pub fn suggested_sound_type(&self) -> SoundType {
559        self.create_sound_ex_info
560            .suggestedsoundtype
561            .try_into()
562            .unwrap()
563    }
564
565    /// Get the file buffer size of this [`SoundBuilder`].
566    pub const fn file_buffer_size(&self) -> c_int {
567        self.create_sound_ex_info.filebuffersize
568    }
569
570    /// Get the channel order of this [`SoundBuilder`].
571    #[allow(clippy::missing_panics_doc)] // this function can't panic in practice as we control the channel order
572    pub fn channel_order(&self) -> ChannelOrder {
573        self.create_sound_ex_info.channelorder.try_into().unwrap()
574    }
575
576    /// Get the initial sound group of this [`SoundBuilder`].
577    pub fn initial_sound_group(&self) -> Option<SoundGroup> {
578        if self.create_sound_ex_info.initialsoundgroup.is_null() {
579            None
580        } else {
581            Some(unsafe { SoundGroup::from_ffi(self.create_sound_ex_info.initialsoundgroup) })
582        }
583    }
584
585    /// Get the initial seek position of this [`SoundBuilder`].
586    #[allow(clippy::missing_panics_doc)] // this function can't panic in practice as we control the seek position
587    pub fn initial_seek_position(&self) -> (c_uint, TimeUnit) {
588        (
589            self.create_sound_ex_info.initialseekposition,
590            self.create_sound_ex_info
591                .initialseekpostype
592                .try_into()
593                .unwrap(),
594        )
595    }
596
597    /// Get the ignore set filesystem flag of this [`SoundBuilder`].
598    pub const fn ignore_set_filesystem(&self) -> bool {
599        self.create_sound_ex_info.ignoresetfilesystem > 0
600    }
601    /// Get the min midi granularity of this [`SoundBuilder`].
602    pub const fn min_midi_granularity(&self) -> c_uint {
603        self.create_sound_ex_info.minmidigranularity
604    }
605
606    /// Get the nonblock thread id of this [`SoundBuilder`].
607    pub const fn non_block_thread_id(&self) -> c_int {
608        self.create_sound_ex_info.nonblockthreadid
609    }
610
611    /// Get the FSB guid of this [`SoundBuilder`].
612    pub const fn fsb_guid(&self) -> Option<Guid> {
613        if self.create_sound_ex_info.fsbguid.is_null() {
614            None
615        } else {
616            Some(unsafe { *(self.create_sound_ex_info.fsbguid.cast()) })
617        }
618    }
619}
620
621impl SoundBuilder<'_> {
622    /// # Safety
623    ///
624    /// The mode must match the required fields of the [`FMOD_CREATESOUNDEXINFO`] struct.
625    /// The [`FMOD_CREATESOUNDEXINFO`] struct's cbsize field must be set to the size of the struct.
626    ///
627    /// If the mode is not [`Mode::OPEN_MEMORY`] or [`Mode::OPEN_MEMORY_POINT`] `name_or_data` pointer must be valid for reads of bytes up to and including the nul terminator.
628    ///
629    /// If the mode is [`Mode::OPEN_MEMORY`] or [`Mode::OPEN_MEMORY_POINT`] the data pointer must be valid for reads of bytes up to [`FMOD_CREATESOUNDEXINFO::length`].
630    ///
631    /// The lifetime of the builder is unbounded and MUST be constrained!
632    pub unsafe fn from_ffi(
633        name_or_data: *const c_char,
634        mode: FMOD_MODE,
635        create_sound_ex_info: FMOD_CREATESOUNDEXINFO,
636    ) -> Self {
637        Self {
638            mode,
639            create_sound_ex_info,
640            name_or_data,
641            _phantom: PhantomData,
642        }
643    }
644}