fmod/core/
structs.rs

1// Copyright (c) 2024 Melody Madeline 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 std::{
8    ffi::{c_float, c_int, c_short, c_uchar, c_uint, c_ushort},
9    mem::MaybeUninit,
10};
11
12use crate::{FmodResultExt, Result};
13use fmod_sys::*;
14use lanyard::{Utf8CStr, Utf8CString};
15
16use super::{FloatMappingType, Resampler, Speaker};
17use crate::{DspParameterDataType, TagType, string_from_utf16_be, string_from_utf16_le};
18
19#[cfg(doc)]
20use crate::{Channel, Geometry, Reverb3D, Sound, System, SystemBuilder};
21
22/// Structure describing a globally unique identifier.
23#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
24// force this type to have the exact same layout as FMOD_STUDIO_PARAMETER_ID so we can safely transmute between them.
25#[repr(C)]
26pub struct Guid {
27    /// Specifies the first 8 hexadecimal digits of the GUID.
28    pub data_1: c_uint,
29    /// Specifies the first group of 4 hexadecimal digits.
30    pub data_2: c_ushort,
31    /// Specifies the second group of 4 hexadecimal digits.
32    pub data_3: c_ushort,
33    /// Specifies the second group of 4 hexadecimal digits.
34    pub data_4: [c_uchar; 8],
35}
36
37impl Guid {
38    /// Parse a GUID from a string.
39    #[cfg(feature = "studio")]
40    pub fn parse(string: &Utf8CStr) -> Result<Self> {
41        let mut guid = MaybeUninit::uninit();
42        unsafe {
43            FMOD_Studio_ParseID(string.as_ptr(), guid.as_mut_ptr()).to_result()?;
44            Ok(guid.assume_init().into())
45        }
46    }
47}
48
49impl From<FMOD_GUID> for Guid {
50    fn from(value: FMOD_GUID) -> Self {
51        Guid {
52            data_1: value.Data1,
53            data_2: value.Data2,
54            data_3: value.Data3,
55            data_4: value.Data4,
56        }
57    }
58}
59
60impl From<Guid> for FMOD_GUID {
61    fn from(value: Guid) -> Self {
62        FMOD_GUID {
63            Data1: value.data_1,
64            Data2: value.data_2,
65            Data3: value.data_3,
66            Data4: value.data_4,
67        }
68    }
69}
70
71impl std::fmt::Display for Guid {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        let Guid {
74            data_1,
75            data_2,
76            data_3,
77            data_4,
78        } = self;
79
80        f.write_str("{")?;
81        f.write_fmt(format_args!("{data_1:0>8x}-{data_2:0>4x}-{data_3:0>4x}-"))?;
82        f.write_fmt(format_args!("{:0>2x}{:0>2x}-", data_4[0], data_4[1]))?;
83        for b in &data_4[2..] {
84            f.write_fmt(format_args!("{b:0>2x}"))?;
85        }
86        f.write_str("}")
87    }
88}
89
90/// Structure describing a point in 3D space.
91///
92/// FMOD uses a left handed coordinate system by default.
93///
94/// To use a right handed coordinate system specify [`FMOD_INIT_3D_RIGHTHANDED`] from [`FMOD_INITFLAGS`] in [`SystemBuilder::build`].
95#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
96#[repr(C)]
97pub struct Vector {
98    /// X coordinate in 3D space.
99    pub x: c_float,
100    /// Y coordinate in 3D space.
101    pub y: c_float,
102    /// Z coordinate in 3D space.
103    pub z: c_float,
104}
105
106impl From<Vector> for FMOD_VECTOR {
107    fn from(value: Vector) -> Self {
108        FMOD_VECTOR {
109            x: value.x,
110            y: value.y,
111            z: value.z,
112        }
113    }
114}
115
116impl From<FMOD_VECTOR> for Vector {
117    fn from(value: FMOD_VECTOR) -> Self {
118        Vector {
119            x: value.x,
120            y: value.y,
121            z: value.z,
122        }
123    }
124}
125
126/// Structure describing a position, velocity and orientation.
127#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
128#[repr(C)]
129pub struct Attributes3D {
130    /// Position in world space used for panning and attenuation.
131    pub position: Vector,
132    /// Velocity in world space used for doppler.
133    pub velocity: Vector,
134    /// Forwards orientation, must be of unit length (1.0) and perpendicular to up.
135    pub forward: Vector,
136    /// Upwards orientation, must be of unit length (1.0) and perpendicular to forward.
137    pub up: Vector,
138}
139
140impl From<FMOD_3D_ATTRIBUTES> for Attributes3D {
141    fn from(value: FMOD_3D_ATTRIBUTES) -> Self {
142        Attributes3D {
143            position: value.position.into(),
144            velocity: value.velocity.into(),
145            forward: value.forward.into(),
146            up: value.up.into(),
147        }
148    }
149}
150
151impl From<Attributes3D> for FMOD_3D_ATTRIBUTES {
152    fn from(value: Attributes3D) -> Self {
153        FMOD_3D_ATTRIBUTES {
154            position: value.position.into(),
155            velocity: value.velocity.into(),
156            forward: value.forward.into(),
157            up: value.up.into(),
158        }
159    }
160}
161
162/// Performance information for Core API functionality.
163#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
164pub struct CpuUsage {
165    /// DSP mixing engine CPU usage.
166    ///
167    /// Percentage of [`FMOD_THREAD_TYPE_MIXER`], or main thread if [`FMOD_INIT_MIX_FROM_UPDATE`] flag is used with [`SystemBuilder::build`].
168    pub dsp: c_float,
169    /// Streaming engine CPU usage.
170    ///
171    /// Percentage of [`FMOD_THREAD_TYPE_STREAM`], or main thread if [`FMOD_INIT_STREAM_FROM_UPDATE`] flag is used with [`SystemBuilder::build`].
172    pub stream: c_float,
173    /// Geometry engine CPU usage.
174    ///
175    /// Percentage of [`FMOD_THREAD_TYPE_GEOMETRY`].
176    pub geometry: c_float,
177    /// [`System::update`] CPU usage. Percentage of main thread.
178    pub update: c_float,
179    /// Convolution reverb processing thread #1 CPU usage.
180    ///
181    /// Percentage of [`FMOD_THREAD_TYPE_CONVOLUTION1`].
182    pub convolution_1: c_float,
183    /// Convolution reverb processing thread #2 CPU usage.
184    ///
185    /// Percentage of [`FMOD_THREAD_TYPE_CONVOLUTION2`].
186    pub convolution_2: c_float,
187}
188
189impl From<FMOD_CPU_USAGE> for CpuUsage {
190    fn from(value: FMOD_CPU_USAGE) -> Self {
191        CpuUsage {
192            dsp: value.dsp,
193            stream: value.stream,
194            geometry: value.geometry,
195            update: value.update,
196            convolution_1: value.convolution1,
197            convolution_2: value.convolution2,
198        }
199    }
200}
201
202impl From<CpuUsage> for FMOD_CPU_USAGE {
203    fn from(value: CpuUsage) -> Self {
204        FMOD_CPU_USAGE {
205            dsp: value.dsp,
206            stream: value.stream,
207            geometry: value.geometry,
208            update: value.update,
209            convolution1: value.convolution_1,
210            convolution2: value.convolution_2,
211        }
212    }
213}
214
215/// Structure defining a reverb environment.
216#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
217#[repr(C)]
218pub struct ReverbProperties {
219    /// Reverberation decay time.
220    pub decay_time: c_float,
221    /// Initial reflection delay time.
222    pub early_delay: c_float,
223    /// Late reverberation delay time relative to initial reflection.
224    pub late_delay: c_float,
225    /// Reference high frequency.
226    pub hf_reference: c_float,
227    /// High-frequency to mid-frequency decay time ratio.
228    pub hf_decay_ratio: c_float,
229    /// Value that controls the echo density in the late reverberation decay.
230    pub diffusion: c_float,
231    /// Value that controls the modal density in the late reverberation decay.
232    pub density: c_float,
233    /// Reference low frequency
234    pub low_shelf_frequency: c_float,
235    /// Relative room effect level at low frequencies.
236    pub low_shelf_gain: c_float,
237    /// Relative room effect level at high frequencies.
238    pub high_cut: c_float,
239    /// Early reflections level relative to room effect.
240    pub early_late_mix: c_float,
241    /// Room effect level at mid frequencies.
242    pub wet_level: c_float,
243}
244
245impl From<FMOD_REVERB_PROPERTIES> for ReverbProperties {
246    fn from(value: FMOD_REVERB_PROPERTIES) -> Self {
247        ReverbProperties {
248            decay_time: value.DecayTime,
249            early_delay: value.EarlyDelay,
250            late_delay: value.LateDelay,
251            hf_reference: value.HFReference,
252            hf_decay_ratio: value.HFDecayRatio,
253            diffusion: value.Diffusion,
254            density: value.Density,
255            low_shelf_frequency: value.LowShelfFrequency,
256            low_shelf_gain: value.LowShelfGain,
257            high_cut: value.HighCut,
258            early_late_mix: value.EarlyLateMix,
259            wet_level: value.WetLevel,
260        }
261    }
262}
263
264impl From<ReverbProperties> for FMOD_REVERB_PROPERTIES {
265    fn from(value: ReverbProperties) -> Self {
266        FMOD_REVERB_PROPERTIES {
267            DecayTime: value.decay_time,
268            EarlyDelay: value.early_delay,
269            LateDelay: value.late_delay,
270            HFReference: value.hf_reference,
271            HFDecayRatio: value.hf_decay_ratio,
272            Diffusion: value.diffusion,
273            Density: value.density,
274            LowShelfFrequency: value.low_shelf_frequency,
275            LowShelfGain: value.low_shelf_gain,
276            HighCut: value.high_cut,
277            EarlyLateMix: value.early_late_mix,
278            WetLevel: value.wet_level,
279        }
280    }
281}
282
283/// Base structure for DSP parameter descriptions.
284#[derive(Debug)]
285pub struct DspParameterDescription {
286    /// Parameter type.
287    pub kind: DspParameterType,
288    /// Parameter name.
289    pub name: Utf8CString,
290    /// Parameter label.
291    pub label: Utf8CString,
292    /// Parameter description.
293    pub description: Utf8CString,
294}
295
296/// DSP parameter types.
297#[derive(Clone, Debug, PartialEq)]
298pub enum DspParameterType {
299    /// Float parameter description.
300    Float {
301        /// Minimum value.
302        min: f32,
303        /// Maximum value.
304        max: f32,
305        /// Default value.
306        default: f32,
307        /// How the values are distributed across dials and automation curves.
308        mapping: FloatMapping,
309    },
310    /// Integer parameter description.
311    Int {
312        /// Minimum value.
313        min: i32,
314        /// Maximum value.
315        max: i32,
316        /// Default value.
317        default: i32,
318        /// Whether the last value represents infinity.
319        goes_to_infinity: bool,
320        /// Names for each value (UTF-8 string).
321        ///
322        /// There should be as many strings as there are possible values (max - min + 1).
323        names: Option<Vec<Utf8CString>>,
324    },
325    /// Boolean parameter description.
326    Bool {
327        /// Default parameter value.
328        default: bool,
329        /// Names for false and true, respectively (UTF-8 string).
330        names: Option<[Utf8CString; 2]>,
331    },
332    /// Data parameter description.
333    Data {
334        /// Type of data.
335        data_type: DspParameterDataType,
336    },
337}
338
339/// Structure to define a mapping for a DSP unit's float parameter.
340#[derive(Clone, Debug, PartialEq)]
341pub struct FloatMapping {
342    /// Float mapping type.
343    pub kind: FloatMappingType,
344    /// Piecewise linear mapping type.
345    pub piecewise_linear_mapping: Option<PiecewiseLinearMapping>,
346}
347
348/// Structure to define a piecewise linear mapping.
349#[derive(Clone, Debug, PartialEq)]
350pub struct PiecewiseLinearMapping {
351    /// Values in the parameter's units for each point .
352    pub point_param_values: Vec<c_float>,
353    /// Positions along the control's scale (e.g. dial angle) corresponding to each parameter value.
354    ///
355    /// The range of this scale is arbitrary and all positions will be relative to the minimum and maximum values
356    /// (e.g. `[0,1,3]` is equivalent to `[1,2,4]` and `[2,4,8]`).
357    ///
358    /// If this array is `None`, `point_param_values` will be distributed with equal spacing.
359    pub point_positions: Option<Vec<c_float>>,
360}
361
362impl FloatMapping {
363    unsafe fn from_ffi(value: FMOD_DSP_PARAMETER_FLOAT_MAPPING) -> Self {
364        let kind = value.type_.try_into().unwrap();
365
366        let piecewise_linear_mapping = if kind == FloatMappingType::PiecewiceLinear {
367            let point_param_values = unsafe {
368                std::slice::from_raw_parts(
369                    value.piecewiselinearmapping.pointparamvalues,
370                    value.piecewiselinearmapping.numpoints as _,
371                )
372                .to_vec()
373            };
374            let point_positions = if value.piecewiselinearmapping.pointpositions.is_null() {
375                None
376            } else {
377                Some(unsafe {
378                    std::slice::from_raw_parts(
379                        value.piecewiselinearmapping.pointpositions,
380                        value.piecewiselinearmapping.numpoints as _,
381                    )
382                    .to_vec()
383                })
384            };
385            Some(PiecewiseLinearMapping {
386                point_param_values,
387                point_positions,
388            })
389        } else {
390            None
391        };
392
393        Self {
394            kind,
395            piecewise_linear_mapping,
396        }
397    }
398}
399
400impl DspParameterDescription {
401    /// Create a safe [`DspParameterDescription`] struct from the FFI equivalent.
402    ///
403    /// # Safety
404    ///
405    /// [`FMOD_DSP_PARAMETER_DESC::type_`] must match the union value.
406    ///
407    /// 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.
408    ///
409    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
410    ///
411    /// # Panics
412    ///
413    /// This function will panic if the description type is not valid.
414    pub unsafe fn from_ffi(value: FMOD_DSP_PARAMETER_DESC) -> Self {
415        // FIXME these array accesses are safe and could be done in a safer way
416        let name = unsafe { Utf8CStr::from_ptr_unchecked(value.name.as_ptr()).to_cstring() };
417        let label = unsafe { Utf8CStr::from_ptr_unchecked(value.label.as_ptr()).to_cstring() };
418        let description = unsafe { Utf8CStr::from_ptr_unchecked(value.description).to_cstring() };
419        let kind = match value.type_ {
420            FMOD_DSP_PARAMETER_TYPE_FLOAT => {
421                let floatdesc = unsafe { value.__bindgen_anon_1.floatdesc };
422                let mapping = unsafe { FloatMapping::from_ffi(floatdesc.mapping) };
423
424                DspParameterType::Float {
425                    min: floatdesc.min,
426                    max: floatdesc.max,
427                    default: floatdesc.defaultval,
428                    mapping,
429                }
430            }
431            FMOD_DSP_PARAMETER_TYPE_INT => {
432                let intdesc = unsafe { value.__bindgen_anon_1.intdesc };
433                let names = if intdesc.valuenames.is_null() {
434                    None
435                } else {
436                    let pointers = unsafe {
437                        std::slice::from_raw_parts(
438                            intdesc.valuenames,
439                            intdesc.max as usize - intdesc.min as usize + 1,
440                        )
441                    };
442                    Some(
443                        pointers
444                            .iter()
445                            .map(|p| unsafe { Utf8CStr::from_ptr_unchecked(*p).to_cstring() })
446                            .collect(),
447                    )
448                };
449
450                DspParameterType::Int {
451                    min: intdesc.min,
452                    max: intdesc.max,
453                    default: intdesc.defaultval,
454                    goes_to_infinity: intdesc.goestoinf.into(),
455                    names,
456                }
457            }
458            FMOD_DSP_PARAMETER_TYPE_BOOL => {
459                let booldesc = unsafe { value.__bindgen_anon_1.booldesc };
460                let names = if booldesc.valuenames.is_null() {
461                    None
462                } else {
463                    let [p1, p2] =
464                        unsafe { *std::ptr::from_ref(&booldesc.valuenames).cast::<[_; 2]>() };
465                    Some([
466                        unsafe { Utf8CStr::from_ptr_unchecked(p1).to_cstring() },
467                        unsafe { Utf8CStr::from_ptr_unchecked(p2).to_cstring() },
468                    ])
469                };
470
471                DspParameterType::Bool {
472                    default: booldesc.defaultval.into(),
473                    names,
474                }
475            }
476            FMOD_DSP_PARAMETER_TYPE_DATA => {
477                let datadesc = unsafe { value.__bindgen_anon_1.datadesc };
478                DspParameterType::Data {
479                    data_type: datadesc.datatype.into(),
480                }
481            }
482            _ => panic!("invalid parameter description type"), // FIXME panic
483        };
484        Self {
485            kind,
486            name,
487            label,
488            description,
489        }
490    }
491
492    // No FFI conversion is provided because we don't support writing dsps in rust yet
493}
494
495/// DSP metering info.
496#[derive(Clone, Copy, Debug, PartialEq)]
497pub struct DspMeteringInfo {
498    /// Number of samples considered for this metering info.
499    pub sample_count: c_int,
500    /// Peak level per channel.
501    pub peak_level: [c_float; 32],
502    /// Rms level per channel.
503    pub rms_level: [c_float; 32],
504    /// Number of channels.
505    pub channel_count: c_short,
506}
507
508impl From<FMOD_DSP_METERING_INFO> for DspMeteringInfo {
509    fn from(value: FMOD_DSP_METERING_INFO) -> Self {
510        Self {
511            sample_count: value.numsamples,
512            peak_level: value.peaklevel,
513            rms_level: value.rmslevel,
514            channel_count: value.numchannels,
515        }
516    }
517}
518
519impl From<DspMeteringInfo> for FMOD_DSP_METERING_INFO {
520    fn from(value: DspMeteringInfo) -> Self {
521        FMOD_DSP_METERING_INFO {
522            numsamples: value.sample_count,
523            peaklevel: value.peak_level,
524            rmslevel: value.rms_level,
525            numchannels: value.channel_count,
526        }
527    }
528}
529
530/// Tag data / metadata description.
531#[derive(Debug)]
532pub struct Tag {
533    /// Tag type.
534    pub kind: TagType,
535    /// Name.
536    pub name: Utf8CString,
537    /// Tag data type.
538    pub data: TagData,
539    /// True if this tag has been updated since last being accessed with [`Sound::get_tag`]
540    pub updated: bool,
541}
542
543/// List of tag data / metadata types.
544#[derive(Debug)]
545// FIXME: these strings are most likely null-terminated
546pub enum TagData {
547    /// Raw binary data.
548    Binary(Vec<u8>),
549    /// Integer.
550    Integer(i64),
551    /// IEEE floating point number.
552    Float(f64),
553    /// 8bit ASCII char string.
554    String(String),
555    /// 8 bit UTF string.
556    Utf8String(String),
557    /// 16bit UTF string Big endian byte order.
558    Utf16StringBE(String),
559    /// 16bit UTF string. Assume little endian byte order.
560    Utf16String(String),
561}
562
563impl Tag {
564    /// Create a safe [`Tag`] struct from the FFI equivalent.
565    ///
566    /// # Safety
567    ///
568    /// 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.
569    ///
570    /// This function will read into arbitrary memory! Because of this the tag data type must match the data type of the data pointer.
571    ///
572    /// # Panics
573    ///
574    /// This function will panic if `value` is not valid (Invalid type, wrong data length, etc)
575    #[allow(clippy::cast_lossless)]
576    pub unsafe fn from_ffi(value: FMOD_TAG) -> Self {
577        let kind = value.type_.try_into().unwrap();
578        let name = unsafe { Utf8CStr::from_ptr_unchecked(value.name).to_cstring() };
579        let updated = value.updated.into();
580        let data = unsafe {
581            // awful union-esquqe code
582            match value.datatype {
583                FMOD_TAGDATATYPE_BINARY => {
584                    let slice =
585                        std::slice::from_raw_parts(value.data as *const u8, value.datalen as usize);
586                    TagData::Binary(slice.to_vec())
587                }
588                FMOD_TAGDATATYPE_INT => match value.datalen {
589                    1 => TagData::Integer(*value.data.cast::<i8>() as i64),
590                    2 => TagData::Integer(*value.data.cast::<i16>() as i64),
591                    4 => TagData::Integer(*value.data.cast::<i32>() as i64),
592                    8 => TagData::Integer(*value.data.cast::<i64>()),
593                    _ => panic!("unrecognized integer data len"),
594                },
595                FMOD_TAGDATATYPE_FLOAT => match value.datalen {
596                    4 => TagData::Float(*value.data.cast::<f32>() as f64),
597                    8 => TagData::Float(*value.data.cast::<f64>()),
598                    _ => panic!("unrecognized float data len"),
599                },
600                FMOD_TAGDATATYPE_STRING => {
601                    let ascii =
602                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
603                    let string = String::from_utf8_lossy(ascii).into_owned();
604                    TagData::String(string)
605                }
606                FMOD_TAGDATATYPE_STRING_UTF8 => {
607                    let utf8 =
608                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
609                    let string = String::from_utf8_lossy(utf8).into_owned();
610                    TagData::Utf8String(string)
611                }
612                // depending on the architecture rust will optimize this to a no-op
613                // we still need to do this to ensure the correct endianness
614                // ideally we could use String::from_utf16_be_lossy but that is nightly only and the tracking issue has basically no activity
615                FMOD_TAGDATATYPE_STRING_UTF16 => {
616                    let slice =
617                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
618                    let string = string_from_utf16_le(slice);
619                    TagData::Utf16String(string)
620                }
621                FMOD_TAGDATATYPE_STRING_UTF16BE => {
622                    let slice =
623                        std::slice::from_raw_parts(value.data.cast(), value.datalen as usize);
624                    let string = string_from_utf16_be(slice);
625                    TagData::Utf16StringBE(string)
626                }
627                _ => panic!("unrecognized tag data type"), // FIXME panic
628            }
629        };
630        Tag {
631            kind,
632            name,
633            data,
634            updated,
635        }
636    }
637}
638
639/// Advanced configuration settings.
640///
641/// Structure to allow configuration of lesser used system level settings.
642/// These tweaks generally allow the user to set resource limits and customize settings to better fit their application.
643#[derive(Debug, Default)]
644pub struct AdvancedSettings {
645    /// Maximum MPEG Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
646    pub max_mpeg_codecs: c_int,
647    /// Maximum IMA-ADPCM Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
648    pub max_adpcm_codecs: c_int,
649    /// Maximum XMA Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
650    pub max_xma_codecs: c_int,
651    /// Maximum Vorbis Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
652    pub max_vorbis_codecs: c_int,
653    /// Maximum AT9 Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
654    pub max_at9_codecs: c_int,
655    /// Maximum FADPCM Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
656    pub max_fadpcm_codecs: c_int,
657    /// Maximum Opus Sounds created as [`FMOD_CREATECOMPRESSEDSAMPLE`].
658    pub max_opus_codecs: c_int,
659    #[cfg(fmod_lt_2_3)]
660    pub max_pcm_codecs: c_int,
661
662    // The docs mention something about this "not being valid before System::init"
663    // No idea what that means. I don't think it's anything we need to worry about?
664    // This is also not used when calling `SetAdvancedSettings` so we don't need to worry about asio_speaker_list matching the same length.
665    // I *think*.
666    // Should this be an enum?
667    /// Read only list of strings representing ASIO channel names (UTF-8 string).
668    pub asio_channel_list: Option<Vec<Utf8CString>>,
669    /// List of speakers that represent each ASIO channel used for remapping.
670    ///
671    /// Use [`FMOD_SPEAKER_NONE`] to indicate no output for a given speaker.
672    pub asio_speaker_list: Option<Vec<Speaker>>, // FIXME: validate this is copied
673    /// For use with [`FMOD_INIT_VOL0_BECOMES_VIRTUAL`],
674    ///
675    /// [`Channel`]s with audibility below this will become virtual.
676    ///
677    /// See the Virtual Voices guide for more information.
678    pub vol0_virtual_vol: c_float,
679    /// For use with Streams, the default size of the double buffer.
680    pub default_decode_buffer_size: c_uint,
681    /// For use with [`FMOD_INIT_PROFILE_ENABLE`],
682    /// specify the port to listen on for connections by FMOD Studio or FMOD Profiler.
683    pub profile_port: c_ushort,
684    /// For use with [`Geometry`],
685    /// the maximum time it takes for a [`Channel`] to fade to the new volume level when its occlusion changes.
686    pub geometry_max_fade_time: c_uint,
687    /// For use with [`FMOD_INIT_CHANNEL_DISTANCEFILTER`],
688    /// the default center frequency for the distance filter.
689    pub distance_filter_center_freq: c_float,
690    /// For use with [`Reverb3D`], selects which global reverb instance to use.
691    pub reverb_3d_instance: c_int,
692    /// Number of intermediate mixing buffers in the DSP buffer pool.
693    /// Each buffer in bytes is `dsp_buffer_pool_size` (See [`System::get_dsp_buffer_size`]) * sizeof(float) * output mode speaker count.
694    ///
695    /// ie 7.1 @ 1024 DSP block size = 1024 * 4 * 8 = 32KB.
696    pub dsp_buffer_pool_size: c_int,
697    /// Resampling method used by [`Channel`]s.
698    pub resampler_method: Resampler,
699    /// Seed value to initialize the internal random number generator.
700    pub random_seed: c_uint,
701    /// Maximum number of CPU threads to use for [`FMOD_DSP_TYPE_CONVOLUTIONREVERB`] effect.
702    ///
703    /// 1 = effect is entirely processed inside the [`FMOD_THREAD_TYPE_MIXER`] thread.
704    ///
705    /// 2 and 3 offloads different parts of the convolution processing into different threads
706    /// ([`FMOD_THREAD_TYPE_CONVOLUTION1`] and [`FMOD_THREAD_TYPE_CONVOLUTION2`]) to increase throughput.
707    pub max_convolution_threads: c_int,
708    /// Maximum Spatial Objects that can be reserved per FMOD system.
709    ///
710    /// [`FMOD_OUTPUTTYPE_AUDIO3D`] is a special case where multiple FMOD systems are not allowed.
711    ///
712    /// See the Object based approach section of the Spatial Audio white paper.
713    /// - A value of -1 means no Spatial Objects will be reserved.
714    /// - A value of 0 means all available Spatial Objects will be reserved.
715    /// - Any other value means it will reserve that many Spatial Objects.
716    pub max_spatial_objects: c_int,
717}
718
719impl From<&AdvancedSettings> for FMOD_ADVANCEDSETTINGS {
720    fn from(value: &AdvancedSettings) -> Self {
721        let speaker_count = value.asio_speaker_list.as_ref().map_or(0, Vec::len);
722        let speaker_ptr: *const Speaker = value
723            .asio_speaker_list
724            .as_ref()
725            .map_or(std::ptr::null_mut(), Vec::as_ptr);
726
727        Self {
728            cbSize: size_of::<FMOD_ADVANCEDSETTINGS>() as c_int,
729            maxMPEGCodecs: value.max_mpeg_codecs,
730            maxADPCMCodecs: value.max_adpcm_codecs,
731            maxXMACodecs: value.max_xma_codecs,
732            maxVorbisCodecs: value.max_vorbis_codecs,
733            maxAT9Codecs: value.max_at9_codecs,
734            maxFADPCMCodecs: value.max_fadpcm_codecs,
735            maxOpusCodecs: value.max_opus_codecs,
736            #[cfg(fmod_lt_2_3)]
737            maxPCMCodecs: value.max_pcm_codecs,
738            ASIONumChannels: speaker_count as i32,
739            ASIOChannelList: std::ptr::null_mut(),
740            // Speaker has the same repr() as i32
741            // So this SHOULD be ok
742            ASIOSpeakerList: speaker_ptr.cast_mut().cast(),
743            vol0virtualvol: value.vol0_virtual_vol,
744            defaultDecodeBufferSize: value.default_decode_buffer_size,
745            profilePort: value.profile_port,
746            geometryMaxFadeTime: value.geometry_max_fade_time,
747            distanceFilterCenterFreq: value.distance_filter_center_freq,
748            reverb3Dinstance: value.reverb_3d_instance,
749            DSPBufferPoolSize: value.dsp_buffer_pool_size,
750            resamplerMethod: value.resampler_method.into(),
751            randomSeed: value.random_seed,
752            maxConvolutionThreads: value.max_convolution_threads,
753            maxSpatialObjects: value.max_spatial_objects,
754        }
755    }
756}
757
758impl AdvancedSettings {
759    /// Due to how [`FMOD_ADVANCEDSETTINGS`] interacts with `FMOD_System_GetAdvancedSettings` this won't read `ASIOSpeakerList`.
760    /// Usually `ASIOSpeakerList` won't be filled out. If you're 100% certain that's not the case, you will have to convert it yourself.
761    ///
762    /// ```ignore
763    /// let slice = unsafe { std::slice::from_raw_parts(value.ASIOSpeakerList, value.ASIONumChannels) };
764    /// let speakers: Result<Speaker, _> = slice.iter().copied().map(Speaker::try_from).collect();
765    /// let speakers = speakers.expect("invalid speaker value");
766    /// ```
767    ///
768    /// # Safety
769    ///
770    /// `ASIOChannelList` must be valid for reads up to `ASIONumChannels`.
771    /// Every pointer inside `ASIOChannelList` must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
772    ///
773    ///
774    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
775    ///
776    /// # Panics
777    ///
778    /// This function will panic if `resamplerMethod` is not a valid user resampler.
779    pub unsafe fn from_ffi(value: FMOD_ADVANCEDSETTINGS) -> Self {
780        let channels = if value.ASIONumChannels > 0 {
781            let slice = unsafe {
782                std::slice::from_raw_parts(value.ASIOChannelList, value.ASIONumChannels as _)
783            };
784            let vec = slice
785                .iter()
786                .map(|&ptr| unsafe { Utf8CStr::from_ptr_unchecked(ptr) }.to_cstring())
787                .collect();
788            Some(vec)
789        } else {
790            None
791        };
792
793        Self {
794            max_mpeg_codecs: value.maxMPEGCodecs,
795            max_adpcm_codecs: value.maxADPCMCodecs,
796            max_xma_codecs: value.maxXMACodecs,
797            max_vorbis_codecs: value.maxVorbisCodecs,
798            max_at9_codecs: value.maxAT9Codecs,
799            max_fadpcm_codecs: value.maxFADPCMCodecs,
800            max_opus_codecs: value.maxOpusCodecs,
801            #[cfg(fmod_lt_2_3)]
802            max_pcm_codecs: value.maxPCMCodecs,
803
804            asio_channel_list: channels,
805            asio_speaker_list: None,
806
807            vol0_virtual_vol: value.vol0virtualvol,
808            default_decode_buffer_size: value.defaultDecodeBufferSize,
809            profile_port: value.profilePort,
810            geometry_max_fade_time: value.geometryMaxFadeTime,
811            distance_filter_center_freq: value.distanceFilterCenterFreq,
812            reverb_3d_instance: value.reverb3Dinstance,
813            dsp_buffer_pool_size: value.DSPBufferPoolSize,
814            resampler_method: value.resamplerMethod.try_into().unwrap(),
815            random_seed: value.randomSeed,
816            max_convolution_threads: value.maxConvolutionThreads,
817            max_spatial_objects: value.maxSpatialObjects,
818        }
819    }
820}