fmod/studio/
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 fmod_sys::*;
8use lanyard::{Utf8CStr, Utf8CString};
9use num_enum::UnsafeFromPrimitive;
10use std::ffi::{c_float, c_int, c_uint};
11
12use super::{InstanceType, ParameterFlags, ParameterKind, UserPropertyKind};
13use crate::{
14    Guid, SoundBuilder,
15    core::{Dsp, Sound},
16};
17
18/// Memory usage statistics.
19///
20/// Memory usage `exclusive` and `inclusive` values do not include sample data loaded in memory because sample data is a shared resource.
21/// Streaming sample data is not a shared resource and is included in the exclusive and `inclusive` values.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct MemoryUsage {
24    /// Size of memory belonging to the bus or event instance.
25    pub exclusive: c_int,
26    /// Size of memory belonging exclusively to the bus or event plus the inclusive memory sizes of all buses and event instances which route into it.
27    pub inclusive: c_int,
28    /// Size of shared sample memory referenced by the bus or event instance,
29    /// inclusive of all sample memory referenced by all buses and event instances which route into it.
30    pub sample_data: c_int,
31}
32
33impl From<FMOD_STUDIO_MEMORY_USAGE> for MemoryUsage {
34    fn from(value: FMOD_STUDIO_MEMORY_USAGE) -> Self {
35        MemoryUsage {
36            exclusive: value.exclusive,
37            inclusive: value.inclusive,
38            sample_data: value.sampledata,
39        }
40    }
41}
42
43impl From<MemoryUsage> for FMOD_STUDIO_MEMORY_USAGE {
44    fn from(value: MemoryUsage) -> Self {
45        FMOD_STUDIO_MEMORY_USAGE {
46            exclusive: value.exclusive,
47            inclusive: value.inclusive,
48            sampledata: value.sample_data,
49        }
50    }
51}
52
53/// Describes an event parameter identifier.
54///
55/// `ParameterID` can be retrieved from the `ParameterDescription`.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57// forces this type to have the exact same layout as FMOD_STUDIO_PARAMETER_ID so we can safely transmute between them.
58#[repr(C)]
59pub struct ParameterID {
60    /// First half of the ID.
61    pub data_1: c_uint,
62    /// Second half of the ID.
63    pub data_2: c_uint,
64}
65
66impl From<FMOD_STUDIO_PARAMETER_ID> for ParameterID {
67    fn from(value: FMOD_STUDIO_PARAMETER_ID) -> Self {
68        ParameterID {
69            data_1: value.data1,
70            data_2: value.data2,
71        }
72    }
73}
74
75impl From<ParameterID> for FMOD_STUDIO_PARAMETER_ID {
76    fn from(value: ParameterID) -> Self {
77        FMOD_STUDIO_PARAMETER_ID {
78            data1: value.data_1,
79            data2: value.data_2,
80        }
81    }
82}
83
84/// Settings for advanced features like configuring memory and cpu usage.
85// default impl is ok, all values are zero or none.
86#[derive(Clone, Default, PartialEq, Eq, Debug)]
87pub struct AdvancedSettings {
88    /// Command queue size for studio async processing.
89    pub command_queue_size: c_uint,
90    /// Initial size to allocate for handles. Memory for handles will grow as needed in pages.
91    pub handle_initial_size: c_uint,
92    /// Update period of Studio when in async mode, in milliseconds. Will be quantized to the nearest multiple of mixer duration.
93    pub studio_update_period: c_int,
94    /// Size in bytes of sample data to retain in memory when no longer used, to avoid repeated disk I/O. Use -1 to disable.
95    pub idle_sample_data_pool_size: c_int,
96    /// Specify the schedule delay for streams, in samples.
97    /// Lower values can reduce latency when scheduling events containing streams but may cause scheduling issues if too small.
98    pub streaming_schedule_delay: c_uint,
99    /// Specify the key for loading sounds from encrypted banks.
100    pub encryption_key: Option<Utf8CString>, // TODO investigate if FMOD copies this string or if it needs to be kept alive
101}
102
103impl AdvancedSettings {
104    /// Create a safe [`AdvancedSettings`] struct from the FFI equivalent.
105    ///
106    /// # Safety
107    ///
108    /// The encryption key from [`FMOD_STUDIO_ADVANCEDSETTINGS`] must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
109    ///
110    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
111    pub unsafe fn from_ffi(value: FMOD_STUDIO_ADVANCEDSETTINGS) -> Self {
112        let encryption_key = if value.encryptionkey.is_null() {
113            None
114        } else {
115            let cstring = unsafe { Utf8CStr::from_ptr_unchecked(value.encryptionkey) }.to_cstring();
116            Some(cstring)
117        };
118
119        Self {
120            command_queue_size: value.commandqueuesize,
121            handle_initial_size: value.handleinitialsize,
122            studio_update_period: value.studioupdateperiod,
123            idle_sample_data_pool_size: value.idlesampledatapoolsize,
124            streaming_schedule_delay: value.streamingscheduledelay,
125            encryption_key,
126        }
127    }
128}
129
130// It's safe to go from AdvancedSettings to FMOD_STUDIO_ADVANCEDSETTINGS because a &Utf8CStr meets all the safety FMOD expects. (aligned, null termienated, etc)
131impl From<&AdvancedSettings> for FMOD_STUDIO_ADVANCEDSETTINGS {
132    fn from(value: &AdvancedSettings) -> Self {
133        let encryption_key = value
134            .encryption_key
135            .as_deref()
136            .map_or(std::ptr::null(), Utf8CStr::as_ptr);
137
138        FMOD_STUDIO_ADVANCEDSETTINGS {
139            cbsize: std::mem::size_of::<Self>() as c_int,
140            commandqueuesize: value.command_queue_size,
141            handleinitialsize: value.handle_initial_size,
142            studioupdateperiod: value.studio_update_period,
143            idlesampledatapoolsize: value.idle_sample_data_pool_size,
144            streamingscheduledelay: value.streaming_schedule_delay,
145            encryptionkey: encryption_key,
146        }
147    }
148}
149
150/// Describes an event parameter.
151#[derive(Clone, PartialEq, Debug)]
152pub struct ParameterDescription {
153    /// The parameter's name.
154    pub name: Utf8CString,
155    /// The parameter's id.
156    pub id: ParameterID,
157    /// The parameter's minimum value.
158    pub minimum: c_float,
159    /// The parameter's maximum value.
160    pub maximum: c_float,
161    /// The parameter's default value.
162    pub default_value: c_float,
163    /// The parameter's type.
164    pub kind: ParameterKind,
165    /// The parameter's behavior flags.
166    pub flags: ParameterFlags,
167    /// The parameter's `Guid`.
168    pub guid: Guid,
169}
170
171// It's safe to go from ParameterDescription to FMOD_STUDIO_PARAMETER_DESCRIPTION because a &Utf8CString meets all the safety FMOD expects. (aligned, null terminated, etc)
172impl From<&ParameterDescription> for FMOD_STUDIO_PARAMETER_DESCRIPTION {
173    fn from(value: &ParameterDescription) -> Self {
174        FMOD_STUDIO_PARAMETER_DESCRIPTION {
175            name: value.name.as_ptr(),
176            id: value.id.into(),
177            minimum: value.minimum,
178            maximum: value.maximum,
179            defaultvalue: value.default_value,
180            type_: value.kind.into(),
181            flags: value.flags.into(),
182            guid: value.guid.into(),
183        }
184    }
185}
186
187impl ParameterDescription {
188    /// Create a safe [`ParameterDescription`] struct from the FFI equivalent.
189    ///
190    /// # Safety
191    ///
192    /// The name from [`FMOD_STUDIO_PARAMETER_DESCRIPTION`] must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
193    ///
194    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
195    pub unsafe fn from_ffi(value: FMOD_STUDIO_PARAMETER_DESCRIPTION) -> ParameterDescription {
196        unsafe {
197            ParameterDescription {
198                name: Utf8CStr::from_ptr_unchecked(value.name).to_cstring(),
199                id: value.id.into(),
200                minimum: value.minimum,
201                maximum: value.maximum,
202                default_value: value.defaultvalue,
203                kind: ParameterKind::unchecked_transmute_from(value.type_),
204                flags: value.flags.into(),
205                guid: value.guid.into(),
206            }
207        }
208    }
209}
210
211/// Describes a user property.
212#[derive(Clone, PartialEq, Debug)]
213pub struct UserProperty {
214    /// Property name.
215    pub name: Utf8CString,
216    /// Property type.
217    pub kind: UserPropertyKind,
218}
219
220impl UserProperty {
221    /// Create a safe [`UserPropertyKind`] struct from the FFI equivalent.
222    ///
223    /// # Safety
224    ///
225    /// All string values from the FFI struct must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
226    /// The type field must match the type assigned to the union.
227    ///
228    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
229    ///
230    /// # Panics
231    ///
232    /// This function will panic if `value` is not a valid user property type.
233    pub unsafe fn from_ffi(value: FMOD_STUDIO_USER_PROPERTY) -> Self {
234        unsafe {
235            UserProperty {
236                name: Utf8CStr::from_ptr_unchecked(value.name).to_cstring(),
237                kind: match value.type_ {
238                    FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER => {
239                        UserPropertyKind::Int(value.__bindgen_anon_1.intvalue)
240                    }
241                    FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN => {
242                        UserPropertyKind::Bool(value.__bindgen_anon_1.boolvalue.into())
243                    }
244                    FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT => {
245                        UserPropertyKind::Float(value.__bindgen_anon_1.floatvalue)
246                    }
247                    FMOD_STUDIO_USER_PROPERTY_TYPE_STRING => {
248                        let cstring =
249                            Utf8CStr::from_ptr_unchecked(value.__bindgen_anon_1.stringvalue)
250                                .to_cstring();
251                        UserPropertyKind::String(cstring)
252                    }
253                    v => panic!("invalid user property type {v}"),
254                },
255            }
256        }
257    }
258}
259
260impl From<&UserProperty> for FMOD_STUDIO_USER_PROPERTY {
261    fn from(value: &UserProperty) -> Self {
262        let (kind, union) = match value.kind {
263            UserPropertyKind::Int(v) => (
264                FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER,
265                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 { intvalue: v },
266            ),
267            UserPropertyKind::Bool(v) => (
268                FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN,
269                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 {
270                    boolvalue: v.into(),
271                },
272            ),
273            UserPropertyKind::Float(v) => (
274                FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT,
275                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 { floatvalue: v },
276            ),
277            UserPropertyKind::String(ref v) => (
278                FMOD_STUDIO_USER_PROPERTY_TYPE_STRING,
279                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 {
280                    stringvalue: v.as_ptr(),
281                },
282            ),
283        };
284        FMOD_STUDIO_USER_PROPERTY {
285            name: value.name.as_ptr(),
286            type_: kind,
287            __bindgen_anon_1: union,
288        }
289    }
290}
291
292/// Information for a single buffer in FMOD Studio.
293#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
294pub struct BufferInfo {
295    /// Current buffer usage in bytes.
296    pub current_usage: c_int,
297    /// Peak buffer usage in bytes.
298    pub peak_usage: c_int,
299    /// Buffer capacity in bytes.
300    pub capacity: c_int,
301    /// Cumulative number of stalls due to buffer overflow.
302    pub stall_count: c_int,
303    /// Cumulative amount of time stalled due to buffer overflow, in seconds.
304    pub stall_time: c_float,
305}
306
307impl From<FMOD_STUDIO_BUFFER_INFO> for BufferInfo {
308    fn from(value: FMOD_STUDIO_BUFFER_INFO) -> Self {
309        BufferInfo {
310            current_usage: value.currentusage,
311            peak_usage: value.peakusage,
312            capacity: value.capacity,
313            stall_count: value.stallcount,
314            stall_time: value.stalltime,
315        }
316    }
317}
318
319impl From<BufferInfo> for FMOD_STUDIO_BUFFER_INFO {
320    fn from(value: BufferInfo) -> Self {
321        FMOD_STUDIO_BUFFER_INFO {
322            currentusage: value.current_usage,
323            peakusage: value.peak_usage,
324            capacity: value.capacity,
325            stallcount: value.stall_count,
326            stalltime: value.stall_time,
327        }
328    }
329}
330
331/// Information for FMOD Studio buffer usage.
332#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
333pub struct BufferUsage {
334    /// Information for the Studio Async Command buffer.
335    pub studio_command_queue: BufferInfo,
336    /// Information for the Studio handle table.
337    pub studio_handle: BufferInfo,
338}
339
340impl From<FMOD_STUDIO_BUFFER_USAGE> for BufferUsage {
341    fn from(value: FMOD_STUDIO_BUFFER_USAGE) -> Self {
342        BufferUsage {
343            studio_command_queue: value.studiocommandqueue.into(),
344            studio_handle: value.studiohandle.into(),
345        }
346    }
347}
348
349impl From<BufferUsage> for FMOD_STUDIO_BUFFER_USAGE {
350    fn from(value: BufferUsage) -> Self {
351        FMOD_STUDIO_BUFFER_USAGE {
352            studiocommandqueue: value.studio_command_queue.into(),
353            studiohandle: value.studio_handle.into(),
354        }
355    }
356}
357
358/// Performance information for Studio API functionality.
359#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
360pub struct CpuUsage {
361    /// `System::update` CPU usage.
362    /// Percentage of main thread, or main thread if the System was created with `SYNCHRONOUS_UPDATE`.
363    pub update: c_float,
364}
365
366impl From<FMOD_STUDIO_CPU_USAGE> for CpuUsage {
367    fn from(value: FMOD_STUDIO_CPU_USAGE) -> Self {
368        CpuUsage {
369            update: value.update,
370        }
371    }
372}
373
374impl From<CpuUsage> for FMOD_STUDIO_CPU_USAGE {
375    fn from(value: CpuUsage) -> Self {
376        FMOD_STUDIO_CPU_USAGE {
377            update: value.update,
378        }
379    }
380}
381
382/// Describes a sound in the audio table.
383#[derive(Debug)]
384pub struct SoundInfo<'a> {
385    /// The Sound's sound builder.
386    pub builder: SoundBuilder<'a>,
387    /// Subsound index for loading the sound.
388    pub subsound_index: c_int,
389}
390
391impl SoundInfo<'_> {
392    /// Create a safe [`SoundInfo`] struct from the FFI equivalent.
393    ///
394    /// # Safety
395    ///
396    /// See [`SoundBuilder::from_ffi`] for more information.
397    pub unsafe fn from_ffi(value: FMOD_STUDIO_SOUND_INFO) -> Self {
398        SoundInfo {
399            builder: unsafe {
400                SoundBuilder::from_ffi(value.name_or_data, value.mode, value.exinfo)
401            },
402            subsound_index: value.subsoundindex,
403        }
404    }
405}
406
407impl From<&SoundInfo<'_>> for FMOD_STUDIO_SOUND_INFO {
408    fn from(value: &SoundInfo<'_>) -> Self {
409        FMOD_STUDIO_SOUND_INFO {
410            name_or_data: value.builder.name_or_data,
411            mode: value.builder.mode,
412            exinfo: value.builder.create_sound_ex_info,
413            subsoundindex: value.subsound_index,
414        }
415    }
416}
417
418/// Describes a command replay command.
419#[derive(Debug, Clone)]
420pub struct CommandInfo {
421    /// Fully qualified C++ name of the API function for this command.
422    pub command_name: Utf8CString,
423    /// Index of the command that created the instance this command operates on, or -1 if the command does not operate on any instance.
424    pub parent_command_index: c_int,
425    /// Frame the command belongs to.
426    pub frame_number: c_int,
427    /// Playback time at which this command will be executed.
428    pub frame_time: c_float,
429    /// Type of object that this command uses as an instance.
430    pub instance_type: InstanceType,
431    /// Type of object that this command outputs.
432    pub output_type: InstanceType,
433    /// Original handle value of the instance.
434    pub instance_handle: c_uint,
435    /// Original handle value of the command output.
436    pub output_handle: c_uint,
437}
438
439impl From<&CommandInfo> for FMOD_STUDIO_COMMAND_INFO {
440    fn from(value: &CommandInfo) -> Self {
441        FMOD_STUDIO_COMMAND_INFO {
442            commandname: value.command_name.as_ptr(),
443            parentcommandindex: value.parent_command_index,
444            framenumber: value.frame_number,
445            frametime: value.frame_time,
446            instancetype: value.instance_type.into(),
447            outputtype: value.output_type.into(),
448            instancehandle: value.instance_handle,
449            outputhandle: value.output_handle,
450        }
451    }
452}
453
454impl CommandInfo {
455    /// Create a safe [`CommandInfo`] struct from the FFI equivalent.
456    ///
457    /// # Safety
458    ///
459    /// All string values from the FFI struct must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
460    ///
461    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
462    pub unsafe fn from_ffi(value: FMOD_STUDIO_COMMAND_INFO) -> Self {
463        CommandInfo {
464            command_name: unsafe { Utf8CStr::from_ptr_unchecked(value.commandname).to_cstring() },
465            parent_command_index: value.parentcommandindex,
466            frame_number: value.framenumber,
467            frame_time: value.frametime,
468            instance_type: unsafe { InstanceType::unchecked_transmute_from(value.instancetype) },
469            output_type: unsafe { InstanceType::unchecked_transmute_from(value.instancetype) },
470            instance_handle: value.instancehandle,
471            output_handle: value.outputhandle,
472        }
473    }
474}
475
476/// Describes a programmer sound.
477#[derive(Debug)]
478pub struct ProgrammerSoundProperties<'prop> {
479    /// Name of the programmer instrument (set in FMOD Studio).
480    pub name: Utf8CString,
481    /// Programmer-created sound.
482    // FIXME investigate if these can be null
483    pub sound: &'prop mut Sound,
484    /// Subsound index.
485    pub subsound_index: &'prop mut c_int,
486}
487
488/// Describes a DSP plug-in instance.
489#[derive(Debug)]
490pub struct PluginInstanceProperties {
491    /// Name of the plug-in effect or sound (set in FMOD Studio).
492    pub name: Utf8CString,
493    /// DSP plug-in instance. (DSP)
494    pub dsp: Dsp,
495}
496
497impl From<&PluginInstanceProperties> for FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES {
498    fn from(value: &PluginInstanceProperties) -> Self {
499        FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES {
500            name: value.name.as_ptr(),
501            dsp: value.dsp.into(),
502        }
503    }
504}
505
506impl PluginInstanceProperties {
507    /// Create a safe [`PluginInstanceProperties`] struct from the FFI equivalent.
508    ///
509    /// # Safety
510    ///
511    /// All string values from the FFI struct must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
512    ///
513    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
514    pub unsafe fn from_ffi(value: FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES) -> Self {
515        PluginInstanceProperties {
516            name: unsafe { Utf8CStr::from_ptr_unchecked(value.name) }.to_cstring(),
517            dsp: unsafe { Dsp::from_ffi(value.dsp) },
518        }
519    }
520}
521
522/// Describes a marker on the timeline.
523#[derive(Debug, Clone)]
524pub struct TimelineMarkerProperties {
525    /// Marker name.
526    pub name: Utf8CString,
527    /// Position of the marker on the timeline in milliseconds.
528    pub position: c_int,
529}
530
531impl From<&TimelineMarkerProperties> for FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES {
532    fn from(value: &TimelineMarkerProperties) -> Self {
533        FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES {
534            name: value.name.as_ptr(),
535            position: value.position,
536        }
537    }
538}
539
540impl TimelineMarkerProperties {
541    /// Create a safe [`TimelineMarkerProperties`] struct from the FFI equivalent.
542    ///
543    /// # Safety
544    ///
545    /// All string values from the FFI struct must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
546    ///
547    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
548    pub unsafe fn from_ffi(value: FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES) -> Self {
549        TimelineMarkerProperties {
550            name: unsafe { Utf8CStr::from_ptr_unchecked(value.name) }.to_cstring(),
551            position: value.position,
552        }
553    }
554}
555
556/// Describes a beat on the timeline.
557#[derive(Clone, Copy, Debug)]
558pub struct TimelineBeatProperties {
559    /// Bar number (starting from 1).
560    pub bar: c_int,
561    /// Beat number within bar (starting from 1).
562    pub beat: c_int,
563    /// Position of the beat on the timeline in milliseconds.
564    pub position: c_int,
565    /// Current tempo in beats per minute.
566    pub tempo: c_float,
567    /// Current time signature upper number (beats per bar).
568    pub time_signature_upper: c_int,
569    /// Current time signature lower number (beat unit).
570    pub time_signature_lower: c_int,
571}
572
573impl From<TimelineBeatProperties> for FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES {
574    fn from(value: TimelineBeatProperties) -> Self {
575        FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES {
576            bar: value.bar,
577            beat: value.beat,
578            position: value.position,
579            tempo: value.tempo,
580            timesignatureupper: value.time_signature_upper,
581            timesignaturelower: value.time_signature_lower,
582        }
583    }
584}
585
586impl From<FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES> for TimelineBeatProperties {
587    fn from(value: FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES) -> Self {
588        TimelineBeatProperties {
589            bar: value.bar,
590            beat: value.beat,
591            position: value.position,
592            tempo: value.tempo,
593            time_signature_upper: value.timesignatureupper,
594            time_signature_lower: value.timesignaturelower,
595        }
596    }
597}
598
599/// Describes a beat on the timeline from a nested event.
600#[derive(Debug, Clone, Copy)]
601pub struct TimelineNestedBeatProperties {
602    /// Event description GUID.
603    pub event_guid: Guid,
604    /// Beat properties.
605    pub properties: TimelineBeatProperties,
606}
607
608impl From<TimelineNestedBeatProperties> for FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES {
609    fn from(value: TimelineNestedBeatProperties) -> Self {
610        FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES {
611            eventid: value.event_guid.into(),
612            properties: value.properties.into(),
613        }
614    }
615}
616
617impl From<FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES> for TimelineNestedBeatProperties {
618    fn from(value: FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES) -> Self {
619        TimelineNestedBeatProperties {
620            event_guid: value.eventid.into(),
621            properties: value.properties.into(),
622        }
623    }
624}