fmod/core/
structs.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/.
6use std::{
7    ffi::{c_char, c_float, c_int, c_short, c_uchar, c_uint, c_ushort},
8    marker::PhantomData,
9    mem::MaybeUninit,
10};
11
12use fmod_sys::*;
13use lanyard::{Utf8CStr, Utf8CString};
14
15use crate::{
16    string_from_utf16_be, string_from_utf16_le, ChannelOrder, DspParameterDataType, Mode,
17    SoundFormat, SoundGroup, SoundType, TagType, TimeUnit,
18};
19
20#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
21// force this type to have the exact same layout as FMOD_STUDIO_PARAMETER_ID so we can safely transmute between them.
22#[repr(C)]
23pub struct Guid {
24    pub data_1: c_uint,
25    pub data_2: c_ushort,
26    pub data_3: c_ushort,
27    pub data_4: [c_uchar; 8],
28}
29
30impl Guid {
31    pub fn parse(string: &Utf8CStr) -> Result<Self> {
32        let mut guid = MaybeUninit::uninit();
33        unsafe {
34            FMOD_Studio_ParseID(string.as_ptr(), guid.as_mut_ptr()).to_result()?;
35            Ok(guid.assume_init().into())
36        }
37    }
38}
39
40impl From<FMOD_GUID> for Guid {
41    fn from(value: FMOD_GUID) -> Self {
42        Guid {
43            data_1: value.Data1,
44            data_2: value.Data2,
45            data_3: value.Data3,
46            data_4: value.Data4,
47        }
48    }
49}
50
51impl From<Guid> for FMOD_GUID {
52    fn from(value: Guid) -> Self {
53        FMOD_GUID {
54            Data1: value.data_1,
55            Data2: value.data_2,
56            Data3: value.data_3,
57            Data4: value.data_4,
58        }
59    }
60}
61
62impl std::fmt::Display for Guid {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        let Guid {
65            data_1,
66            data_2,
67            data_3,
68            data_4,
69        } = self;
70
71        f.write_str("{")?;
72        f.write_fmt(format_args!("{data_1:0>8x}-{data_2:0>4x}-{data_3:0>4x}-"))?;
73        f.write_fmt(format_args!("{:0>2x}{:0>2x}-", data_4[0], data_4[1]))?;
74        for b in &data_4[2..] {
75            f.write_fmt(format_args!("{b:0>2x}"))?;
76        }
77        f.write_str("}")
78    }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
82#[repr(C)]
83pub struct Vector {
84    pub x: c_float,
85    pub y: c_float,
86    pub z: c_float,
87}
88
89impl From<Vector> for FMOD_VECTOR {
90    fn from(value: Vector) -> Self {
91        FMOD_VECTOR {
92            x: value.x,
93            y: value.y,
94            z: value.z,
95        }
96    }
97}
98
99impl From<FMOD_VECTOR> for Vector {
100    fn from(value: FMOD_VECTOR) -> Self {
101        Vector {
102            x: value.x,
103            y: value.y,
104            z: value.z,
105        }
106    }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
110#[repr(C)]
111pub struct Attributes3D {
112    pub position: Vector,
113    pub velocity: Vector,
114    pub forward: Vector,
115    pub up: Vector,
116}
117
118impl From<FMOD_3D_ATTRIBUTES> for Attributes3D {
119    fn from(value: FMOD_3D_ATTRIBUTES) -> Self {
120        Attributes3D {
121            position: value.position.into(),
122            velocity: value.velocity.into(),
123            forward: value.forward.into(),
124            up: value.up.into(),
125        }
126    }
127}
128
129impl From<Attributes3D> for FMOD_3D_ATTRIBUTES {
130    fn from(value: Attributes3D) -> Self {
131        FMOD_3D_ATTRIBUTES {
132            position: value.position.into(),
133            velocity: value.velocity.into(),
134            forward: value.forward.into(),
135            up: value.up.into(),
136        }
137    }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
141pub struct CpuUsage {
142    pub dsp: c_float,
143    pub stream: c_float,
144    pub geometry: c_float,
145    pub update: c_float,
146    pub convolution_1: c_float,
147    pub convolution_2: c_float,
148}
149
150impl From<FMOD_CPU_USAGE> for CpuUsage {
151    fn from(value: FMOD_CPU_USAGE) -> Self {
152        CpuUsage {
153            dsp: value.dsp,
154            stream: value.stream,
155            geometry: value.geometry,
156            update: value.update,
157            convolution_1: value.convolution1,
158            convolution_2: value.convolution2,
159        }
160    }
161}
162
163impl From<CpuUsage> for FMOD_CPU_USAGE {
164    fn from(value: CpuUsage) -> Self {
165        FMOD_CPU_USAGE {
166            dsp: value.dsp,
167            stream: value.stream,
168            geometry: value.geometry,
169            update: value.update,
170            convolution1: value.convolution_1,
171            convolution2: value.convolution_2,
172        }
173    }
174}
175
176#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
177#[repr(C)]
178pub struct ReverbProperties {
179    pub decay_time: c_float,
180    pub early_delay: c_float,
181    pub late_delay: c_float,
182    pub hf_reference: c_float,
183    pub hf_decay_ratio: c_float,
184    pub diffusion: c_float,
185    pub density: c_float,
186    pub low_shelf_frequency: c_float,
187    pub low_shelf_gain: c_float,
188    pub high_cut: c_float,
189    pub early_late_mix: c_float,
190    pub wet_level: c_float,
191}
192
193impl From<FMOD_REVERB_PROPERTIES> for ReverbProperties {
194    fn from(value: FMOD_REVERB_PROPERTIES) -> Self {
195        ReverbProperties {
196            decay_time: value.DecayTime,
197            early_delay: value.EarlyDelay,
198            late_delay: value.LateDelay,
199            hf_reference: value.HFReference,
200            hf_decay_ratio: value.HFDecayRatio,
201            diffusion: value.Diffusion,
202            density: value.Density,
203            low_shelf_frequency: value.LowShelfFrequency,
204            low_shelf_gain: value.LowShelfGain,
205            high_cut: value.HighCut,
206            early_late_mix: value.EarlyLateMix,
207            wet_level: value.WetLevel,
208        }
209    }
210}
211
212impl From<ReverbProperties> for FMOD_REVERB_PROPERTIES {
213    fn from(value: ReverbProperties) -> Self {
214        FMOD_REVERB_PROPERTIES {
215            DecayTime: value.decay_time,
216            EarlyDelay: value.early_delay,
217            LateDelay: value.late_delay,
218            HFReference: value.hf_reference,
219            HFDecayRatio: value.hf_decay_ratio,
220            Diffusion: value.diffusion,
221            Density: value.density,
222            LowShelfFrequency: value.low_shelf_frequency,
223            LowShelfGain: value.low_shelf_gain,
224            HighCut: value.high_cut,
225            EarlyLateMix: value.early_late_mix,
226            WetLevel: value.wet_level,
227        }
228    }
229}
230
231pub struct DspParameterDescription {
232    pub kind: DspParameterType,
233    pub name: Utf8CString,
234    pub label: Utf8CString,
235    pub description: Utf8CString,
236}
237
238#[derive(Clone, Debug, PartialEq)]
239pub enum DspParameterType {
240    Float {
241        min: f32,
242        max: f32,
243        default: f32,
244        mapping: FloatMapping,
245    },
246    Int {
247        min: i32,
248        max: i32,
249        default: i32,
250        goes_to_infinity: bool,
251        // TODO names
252    },
253    Bool {
254        default: bool,
255        // TODO names
256    },
257    Data {
258        data_type: DspParameterDataType,
259    },
260}
261
262#[derive(Clone, Copy, Debug, PartialEq, Eq)]
263pub struct FloatMapping {
264    // TODO
265}
266
267impl DspParameterDescription {
268    /// Create a safe [`DspParameterDescription`] struct from the FFI equivalent.
269    ///
270    /// # Safety
271    ///
272    /// [`FMOD_DSP_PARAMETER_DESC::type_`] must match the union value.
273    ///
274    /// The strings [`FMOD_DSP_PARAMETER_DESC`] must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
275    ///
276    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
277    pub unsafe fn from_ffi(value: FMOD_DSP_PARAMETER_DESC) -> Self {
278        // FIXME these array accesses are safe and could be done in a safer way
279        let name = unsafe { Utf8CStr::from_ptr_unchecked(value.name.as_ptr()).to_cstring() };
280        let label = unsafe { Utf8CStr::from_ptr_unchecked(value.label.as_ptr()).to_cstring() };
281        let description = unsafe { Utf8CStr::from_ptr_unchecked(value.description).to_cstring() };
282        let kind = match value.type_ {
283            FMOD_DSP_PARAMETER_TYPE_FLOAT => {
284                let floatdesc = unsafe { value.__bindgen_anon_1.floatdesc };
285                DspParameterType::Float {
286                    min: floatdesc.min,
287                    max: floatdesc.max,
288                    default: floatdesc.defaultval,
289                    mapping: FloatMapping {},
290                }
291            }
292            FMOD_DSP_PARAMETER_TYPE_INT => {
293                let intdesc = unsafe { value.__bindgen_anon_1.intdesc };
294                DspParameterType::Int {
295                    min: intdesc.min,
296                    max: intdesc.max,
297                    default: intdesc.defaultval,
298                    goes_to_infinity: intdesc.goestoinf.into(),
299                }
300            }
301            FMOD_DSP_PARAMETER_TYPE_BOOL => {
302                let booldesc = unsafe { value.__bindgen_anon_1.booldesc };
303                DspParameterType::Bool {
304                    default: booldesc.defaultval.into(),
305                }
306            }
307            FMOD_DSP_PARAMETER_TYPE_DATA => {
308                let datadesc = unsafe { value.__bindgen_anon_1.datadesc };
309                DspParameterType::Data {
310                    data_type: datadesc.datatype.try_into().unwrap(),
311                }
312            }
313            _ => panic!("invalid parameter description type"), // FIXME panic
314        };
315        Self {
316            kind,
317            name,
318            label,
319            description,
320        }
321    }
322
323    // TODO ffi conversion (altho is it even needed?)
324}
325
326#[derive(Clone, Copy, Debug, PartialEq)]
327pub struct DspMeteringInfo {
328    pub sample_count: c_int,
329    pub peak_level: [c_float; 32],
330    pub rms_level: [c_float; 32],
331    pub channel_count: c_short,
332}
333
334impl From<FMOD_DSP_METERING_INFO> for DspMeteringInfo {
335    fn from(value: FMOD_DSP_METERING_INFO) -> Self {
336        Self {
337            sample_count: value.numsamples,
338            peak_level: value.peaklevel,
339            rms_level: value.rmslevel,
340            channel_count: value.numchannels,
341        }
342    }
343}
344
345impl From<DspMeteringInfo> for FMOD_DSP_METERING_INFO {
346    fn from(value: DspMeteringInfo) -> Self {
347        FMOD_DSP_METERING_INFO {
348            numsamples: value.sample_count,
349            peaklevel: value.peak_level,
350            rmslevel: value.rms_level,
351            numchannels: value.channel_count,
352        }
353    }
354}
355
356pub struct Tag {
357    pub kind: TagType,
358    pub name: Utf8CString,
359    pub data: TagData,
360    pub updated: bool,
361}
362
363// FIXME: these strings are most likely null-terminated
364pub enum TagData {
365    Binary(Vec<u8>),
366    Integer(i64),
367    Float(f64),
368    String(String),
369    Utf8String(String),
370    Utf16StringBE(String),
371    Utf16String(String),
372}
373
374impl Tag {
375    /// Create a safe [`Tag`] struct from the FFI equivalent.
376    ///
377    /// # Safety
378    ///
379    /// The string [`FMOD_TAG::name`] must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
380    ///
381    /// This function will read into arbitrary memory! Because of this the tag data type must match the data type of the data pointer.
382    #[allow(clippy::cast_lossless)]
383    pub unsafe fn from_ffi(value: FMOD_TAG) -> Self {
384        let kind = value.type_.try_into().unwrap();
385        let name = unsafe { Utf8CStr::from_ptr_unchecked(value.name).to_cstring() };
386        let updated = value.updated.into();
387        let data = unsafe {
388            // awful union-esquqe code
389            match value.datatype {
390                FMOD_TAGDATATYPE_BINARY => {
391                    let slice =
392                        std::slice::from_raw_parts(value.data as *const u8, value.datalen as usize);
393                    TagData::Binary(slice.to_vec())
394                }
395                FMOD_TAGDATATYPE_INT => match value.datalen {
396                    1 => TagData::Integer(*value.data.cast::<i8>() as i64),
397                    2 => TagData::Integer(*value.data.cast::<i16>() as i64),
398                    4 => TagData::Integer(*value.data.cast::<i32>() as i64),
399                    8 => TagData::Integer(*value.data.cast::<i64>()),
400                    _ => panic!("unrecognized integer data len"),
401                },
402                FMOD_TAGDATATYPE_FLOAT => match value.datalen {
403                    4 => TagData::Float(*value.data.cast::<f32>() as f64),
404                    8 => TagData::Float(*value.data.cast::<f64>()),
405                    _ => panic!("unrecognized float data len"),
406                },
407                FMOD_TAGDATATYPE_STRING => {
408                    let ascii =
409                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
410                    let string = String::from_utf8_lossy(ascii).into_owned();
411                    TagData::String(string)
412                }
413                FMOD_TAGDATATYPE_STRING_UTF8 => {
414                    let utf8 =
415                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
416                    let string = String::from_utf8_lossy(utf8).into_owned();
417                    TagData::Utf8String(string)
418                }
419                // depending on the architecture rust will optimize this to a no-op
420                // we still need to do this to ensure the correct endianness
421                // ideally we could use String::from_utf16_be_lossy but that is nightly only and the tracking issue has basically no activity
422                FMOD_TAGDATATYPE_STRING_UTF16 => {
423                    let slice =
424                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
425                    let string = string_from_utf16_le(slice);
426                    TagData::Utf16String(string)
427                }
428                FMOD_TAGDATATYPE_STRING_UTF16BE => {
429                    let slice =
430                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
431                    let string = string_from_utf16_be(slice);
432                    TagData::Utf16StringBE(string)
433                }
434                _ => panic!("unrecognized tag data type"), // FIXME panic
435            }
436        };
437        Tag {
438            kind,
439            name,
440            data,
441            updated,
442        }
443    }
444}
445
446#[derive(Debug)]
447pub struct SoundBuilder<'a> {
448    pub(crate) mode: FMOD_MODE,
449    pub(crate) create_sound_ex_info: FMOD_CREATESOUNDEXINFO,
450    pub(crate) name_or_data: *const c_char,
451    pub(crate) _phantom: PhantomData<&'a ()>,
452}
453
454const EMPTY_EXINFO: FMOD_CREATESOUNDEXINFO = unsafe {
455    FMOD_CREATESOUNDEXINFO {
456        cbsize: std::mem::size_of::<FMOD_CREATESOUNDEXINFO>() as c_int,
457        ..std::mem::MaybeUninit::zeroed().assume_init()
458    }
459};
460
461// setters
462impl<'a> SoundBuilder<'a> {
463    pub const fn open(filename: &'a Utf8CStr) -> Self {
464        Self {
465            mode: 0,
466            create_sound_ex_info: EMPTY_EXINFO,
467            name_or_data: filename.as_ptr(),
468            _phantom: PhantomData,
469        }
470    }
471
472    // TODO open_user
473
474    /// # Safety
475    ///
476    /// The slice must remain valid until the sound has been loaded.
477    /// See the [`Mode`] docs for more information.
478    pub const unsafe fn open_memory(data: &'a [u8]) -> Self {
479        Self {
480            mode: FMOD_OPENMEMORY,
481            create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
482                length: data.len() as c_uint,
483                ..EMPTY_EXINFO
484            },
485            name_or_data: data.as_ptr().cast(),
486            _phantom: PhantomData,
487        }
488    }
489
490    /// # Safety
491    ///
492    /// The slice must remain valid until the sound has been released.
493    /// Unlike [`Self::open_memory`] this function does not copy the data, so it is even more unsafe!
494    pub const unsafe fn open_memory_point(data: &'a [u8]) -> Self {
495        Self {
496            mode: FMOD_OPENMEMORY_POINT,
497            create_sound_ex_info: FMOD_CREATESOUNDEXINFO {
498                length: data.len() as c_uint,
499                ..EMPTY_EXINFO
500            },
501            name_or_data: data.as_ptr().cast(),
502            _phantom: PhantomData,
503        }
504    }
505
506    /// # Safety
507    ///
508    /// The [`FMOD_CREATESOUNDEXINFO`] must be valid.
509    #[must_use]
510    pub const unsafe fn with_raw_ex_info(mut self, ex_info: FMOD_CREATESOUNDEXINFO) -> Self {
511        self.create_sound_ex_info = ex_info;
512        self
513    }
514
515    #[must_use]
516    pub const fn with_file_offset(mut self, file_offset: c_uint) -> Self {
517        self.create_sound_ex_info.fileoffset = file_offset;
518        self
519    }
520
521    #[must_use]
522    pub const fn with_open_raw(
523        mut self,
524        channel_count: c_int,
525        default_frequency: c_int,
526        format: SoundFormat,
527    ) -> Self {
528        self.mode |= FMOD_OPENRAW;
529        self.create_sound_ex_info.numchannels = channel_count;
530        self.create_sound_ex_info.defaultfrequency = default_frequency;
531        self.create_sound_ex_info.format = format as _;
532        self
533    }
534
535    #[must_use]
536    pub const fn with_mode(mut self, mode: Mode) -> Self {
537        const DISABLE_MODES: Mode = Mode::OPEN_MEMORY
538            .union(Mode::OPEN_MEMORY_POINT)
539            .union(Mode::OPEN_USER)
540            .union(Mode::OPEN_RAW);
541
542        let mode = mode.difference(DISABLE_MODES); // these modes are not allowed to be set by the user, so we unset them
543        let mode: FMOD_MODE = mode.bits();
544        self.mode |= mode;
545        self
546    }
547
548    #[must_use]
549    pub const fn with_decode_buffer_size(mut self, size: c_uint) -> Self {
550        self.create_sound_ex_info.decodebuffersize = size;
551        self
552    }
553
554    #[must_use]
555    pub const fn with_initial_subsound(mut self, initial_subsound: c_int) -> Self {
556        self.create_sound_ex_info.initialsubsound = initial_subsound;
557        self
558    }
559
560    #[must_use]
561    pub const fn with_subsound_count(mut self, count: c_int) -> Self {
562        self.create_sound_ex_info.numsubsounds = count;
563        self
564    }
565
566    // TODO: check if this is safe
567    #[must_use]
568    pub const fn with_inclusion_list(mut self, list: &'a [c_int]) -> Self {
569        self.create_sound_ex_info.inclusionlist = list.as_ptr().cast_mut().cast();
570        self.create_sound_ex_info.inclusionlistnum = list.len() as c_int;
571        self
572    }
573
574    // TODO check safety
575    #[must_use]
576    pub const fn with_dls_name(mut self, dls_name: &'a Utf8CStr) -> Self {
577        self.create_sound_ex_info.dlsname = dls_name.as_ptr();
578        self
579    }
580
581    // TODO check safety
582    #[must_use]
583    pub const fn with_encryption_key(mut self, key: &'a Utf8CStr) -> Self {
584        self.create_sound_ex_info.encryptionkey = key.as_ptr();
585        self
586    }
587
588    #[must_use]
589    pub fn with_max_polyphony(mut self, max_polyphony: c_int) -> Self {
590        self.create_sound_ex_info.maxpolyphony = max_polyphony;
591        self
592    }
593
594    #[must_use]
595    pub const fn with_suggested_sound_type(mut self, sound_type: SoundType) -> Self {
596        self.create_sound_ex_info.suggestedsoundtype = sound_type as _;
597        self
598    }
599
600    #[must_use]
601    pub const fn with_file_buffer_size(mut self, size: c_int) -> Self {
602        self.create_sound_ex_info.filebuffersize = size;
603        self
604    }
605
606    #[must_use]
607    pub const fn with_channel_order(mut self, order: ChannelOrder) -> Self {
608        self.create_sound_ex_info.channelorder = order as _;
609        self
610    }
611
612    #[must_use]
613    pub fn with_initial_sound_group(mut self, group: SoundGroup) -> Self {
614        self.create_sound_ex_info.initialsoundgroup = group.into();
615        self
616    }
617
618    #[must_use]
619    pub const fn with_initial_seek_position(mut self, position: c_uint, unit: TimeUnit) -> Self {
620        self.create_sound_ex_info.initialseekposition = position;
621        self.create_sound_ex_info.initialseekpostype = unit as _;
622        self
623    }
624
625    #[must_use]
626    pub fn with_ignore_set_filesystem(mut self, ignore: bool) -> Self {
627        self.create_sound_ex_info.ignoresetfilesystem = ignore.into();
628        self
629    }
630
631    #[must_use]
632    pub const fn with_min_midi_granularity(mut self, granularity: c_uint) -> Self {
633        self.create_sound_ex_info.minmidigranularity = granularity as _;
634        self
635    }
636
637    #[must_use]
638    pub const fn with_non_block_thread_id(mut self, id: c_int) -> Self {
639        self.create_sound_ex_info.nonblockthreadid = id as _;
640        self
641    }
642
643    // TODO check safety
644    #[must_use]
645    pub const fn with_fsb_guid(mut self, guid: &'a Guid) -> Self {
646        self.create_sound_ex_info.fsbguid = std::ptr::from_ref(guid).cast_mut().cast();
647        self
648    }
649
650    pub(crate) fn ex_info_is_empty(&self) -> bool {
651        self.create_sound_ex_info == EMPTY_EXINFO
652    }
653}
654
655// getters
656impl<'a> SoundBuilder<'a> {
657    pub const fn mode(&self) -> Mode {
658        Mode::from_bits_truncate(self.mode)
659    }
660
661    pub const fn raw_ex_info(&self) -> FMOD_CREATESOUNDEXINFO {
662        self.create_sound_ex_info
663    }
664
665    pub const fn raw_name_or_data(&self) -> *const c_char {
666        self.name_or_data
667    }
668
669    pub fn name_or_url(&self) -> Option<&Utf8CStr> {
670        if self
671            .mode()
672            .intersects(Mode::OPEN_MEMORY | Mode::OPEN_MEMORY_POINT | Mode::OPEN_USER)
673        {
674            None
675        } else {
676            Some(unsafe { Utf8CStr::from_ptr_unchecked(self.name_or_data) })
677        }
678    }
679
680    pub fn data(&self) -> Option<&[u8]> {
681        if self
682            .mode()
683            .intersects(Mode::OPEN_MEMORY | Mode::OPEN_MEMORY_POINT)
684        {
685            Some(unsafe {
686                std::slice::from_raw_parts(
687                    self.name_or_data.cast(),
688                    self.create_sound_ex_info.length as usize,
689                )
690            })
691        } else {
692            None
693        }
694    }
695
696    pub fn length(&self) -> c_uint {
697        self.create_sound_ex_info.length
698    }
699
700    pub fn file_offset(&self) -> c_uint {
701        self.create_sound_ex_info.fileoffset
702    }
703
704    pub fn num_channels(&self) -> c_int {
705        self.create_sound_ex_info.numchannels
706    }
707
708    pub fn default_frequency(&self) -> c_int {
709        self.create_sound_ex_info.defaultfrequency
710    }
711
712    pub fn format(&self) -> SoundFormat {
713        self.create_sound_ex_info.format.try_into().unwrap()
714    }
715
716    pub fn decode_buffer_size(&self) -> c_uint {
717        self.create_sound_ex_info.decodebuffersize
718    }
719
720    pub fn initial_subsound(&self) -> c_int {
721        self.create_sound_ex_info.initialsubsound
722    }
723
724    pub fn subsound_count(&self) -> c_int {
725        self.create_sound_ex_info.numsubsounds
726    }
727
728    pub fn inclusion_list(&self) -> Option<&'a [c_int]> {
729        if self.create_sound_ex_info.inclusionlist.is_null() {
730            None
731        } else {
732            Some(unsafe {
733                std::slice::from_raw_parts(
734                    self.create_sound_ex_info.inclusionlist.cast(),
735                    self.create_sound_ex_info.inclusionlistnum as usize,
736                )
737            })
738        }
739    }
740
741    pub fn dls_name(&self) -> Option<&Utf8CStr> {
742        if self.create_sound_ex_info.dlsname.is_null() {
743            None
744        } else {
745            Some(unsafe { Utf8CStr::from_ptr_unchecked(self.create_sound_ex_info.dlsname) })
746        }
747    }
748
749    pub fn encryption_key(&self) -> Option<&Utf8CStr> {
750        if self.create_sound_ex_info.encryptionkey.is_null() {
751            None
752        } else {
753            Some(unsafe { Utf8CStr::from_ptr_unchecked(self.create_sound_ex_info.encryptionkey) })
754        }
755    }
756
757    pub fn max_polyphony(&self) -> c_int {
758        self.create_sound_ex_info.maxpolyphony
759    }
760
761    pub fn suggested_sound_type(&self) -> SoundType {
762        self.create_sound_ex_info
763            .suggestedsoundtype
764            .try_into()
765            .unwrap()
766    }
767
768    pub fn file_buffer_size(&self) -> c_int {
769        self.create_sound_ex_info.filebuffersize
770    }
771
772    pub fn channel_order(&self) -> ChannelOrder {
773        self.create_sound_ex_info.channelorder.try_into().unwrap()
774    }
775
776    pub fn initial_sound_group(&self) -> SoundGroup {
777        SoundGroup::from(self.create_sound_ex_info.initialsoundgroup)
778    }
779
780    pub fn initial_seek_position(&self) -> (c_uint, TimeUnit) {
781        (
782            self.create_sound_ex_info.initialseekposition,
783            self.create_sound_ex_info
784                .initialseekpostype
785                .try_into()
786                .unwrap(),
787        )
788    }
789
790    pub fn ignore_set_filesystem(&self) -> bool {
791        self.create_sound_ex_info.ignoresetfilesystem > 0
792    }
793
794    pub fn min_midi_granularity(&self) -> c_uint {
795        self.create_sound_ex_info.minmidigranularity
796    }
797
798    pub fn non_block_thread_id(&self) -> c_int {
799        self.create_sound_ex_info.nonblockthreadid
800    }
801
802    pub fn fsb_guid(&self) -> Option<Guid> {
803        if self.create_sound_ex_info.fsbguid.is_null() {
804            None
805        } else {
806            Some(unsafe { *(self.create_sound_ex_info.fsbguid.cast()) })
807        }
808    }
809}
810
811impl<'a> SoundBuilder<'a> {
812    /// # Safety
813    ///
814    /// The mode must match the required fields of the [`FMOD_CREATESOUNDEXINFO`] struct.
815    /// The [`FMOD_CREATESOUNDEXINFO`] struct's cbsize field must be set to the size of the struct.
816    ///
817    /// 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.
818    ///
819    /// 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`].
820    ///
821    /// The lifetime of the builder is unbounded and MUST be constrained!
822    pub unsafe fn from_ffi(
823        name_or_data: *const c_char,
824        mode: FMOD_MODE,
825        create_sound_ex_info: FMOD_CREATESOUNDEXINFO,
826    ) -> Self {
827        Self {
828            mode,
829            create_sound_ex_info,
830            name_or_data,
831            _phantom: PhantomData,
832        }
833    }
834}