fmod/studio/
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/.
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    core::{Dsp, Sound},
15    Guid, SoundBuilder,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct MemoryUsage {
20    pub exclusive: c_int,
21    pub inclusive: c_int,
22    pub sample_data: c_int,
23}
24
25impl From<FMOD_STUDIO_MEMORY_USAGE> for MemoryUsage {
26    fn from(value: FMOD_STUDIO_MEMORY_USAGE) -> Self {
27        MemoryUsage {
28            exclusive: value.exclusive,
29            inclusive: value.inclusive,
30            sample_data: value.sampledata,
31        }
32    }
33}
34
35impl From<MemoryUsage> for FMOD_STUDIO_MEMORY_USAGE {
36    fn from(value: MemoryUsage) -> Self {
37        FMOD_STUDIO_MEMORY_USAGE {
38            exclusive: value.exclusive,
39            inclusive: value.inclusive,
40            sampledata: value.sample_data,
41        }
42    }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46// force this type to have the exact same layout as FMOD_STUDIO_PARAMETER_ID so we can safely transmute between them.
47#[repr(C)]
48pub struct ParameterID {
49    pub data_1: c_uint,
50    pub data_2: c_uint,
51}
52
53impl From<FMOD_STUDIO_PARAMETER_ID> for ParameterID {
54    fn from(value: FMOD_STUDIO_PARAMETER_ID) -> Self {
55        ParameterID {
56            data_1: value.data1,
57            data_2: value.data2,
58        }
59    }
60}
61
62impl From<ParameterID> for FMOD_STUDIO_PARAMETER_ID {
63    fn from(value: ParameterID) -> Self {
64        FMOD_STUDIO_PARAMETER_ID {
65            data1: value.data_1,
66            data2: value.data_2,
67        }
68    }
69}
70
71// default impl is ok, all values are zero or none.
72#[derive(Clone, Default, PartialEq, Eq, Debug)]
73pub struct AdvancedSettings {
74    pub command_queue_size: c_uint,
75    pub handle_initial_size: c_uint,
76    pub studioupdateperiod: c_int,
77    pub idle_sample_data_pool_size: c_int,
78    pub streaming_schedule_delay: c_uint,
79    pub encryption_key: Option<Utf8CString>, // TODO investigate if FMOD copies this string or if it needs to be kept alive
80}
81
82impl AdvancedSettings {
83    /// Create a safe [`AdvancedSettings`] struct from the FFI equivalent.
84    ///
85    /// # Safety
86    ///
87    /// 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.
88    ///
89    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
90    pub unsafe fn from_ffi(value: FMOD_STUDIO_ADVANCEDSETTINGS) -> Self {
91        let encryption_key = if value.encryptionkey.is_null() {
92            None
93        } else {
94            let cstring = unsafe { Utf8CStr::from_ptr_unchecked(value.encryptionkey) }.to_cstring();
95            Some(cstring)
96        };
97
98        Self {
99            command_queue_size: value.commandqueuesize,
100            handle_initial_size: value.handleinitialsize,
101            studioupdateperiod: value.studioupdateperiod,
102            idle_sample_data_pool_size: value.idlesampledatapoolsize,
103            streaming_schedule_delay: value.streamingscheduledelay,
104            encryption_key,
105        }
106    }
107}
108
109// It's safe to go from AdvancedSettings to FMOD_STUDIO_ADVANCEDSETTINGS because a &Utf8CStr meets all the safety FMOD expects. (aligned, null termienated, etc)
110impl From<&AdvancedSettings> for FMOD_STUDIO_ADVANCEDSETTINGS {
111    fn from(value: &AdvancedSettings) -> Self {
112        let encryption_key = value
113            .encryption_key
114            .as_deref()
115            .map_or(std::ptr::null(), Utf8CStr::as_ptr);
116
117        FMOD_STUDIO_ADVANCEDSETTINGS {
118            cbsize: std::mem::size_of::<Self>() as c_int,
119            commandqueuesize: value.command_queue_size,
120            handleinitialsize: value.handle_initial_size,
121            studioupdateperiod: value.studioupdateperiod,
122            idlesampledatapoolsize: value.idle_sample_data_pool_size,
123            streamingscheduledelay: value.streaming_schedule_delay,
124            encryptionkey: encryption_key,
125        }
126    }
127}
128
129#[derive(Clone, PartialEq, Debug)]
130pub struct ParameterDescription {
131    pub name: Utf8CString,
132    pub id: ParameterID,
133    pub minimum: c_float,
134    pub maximum: c_float,
135    pub default_value: c_float,
136    pub kind: ParameterKind,
137    pub flags: ParameterFlags,
138    pub guid: Guid,
139}
140
141// 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)
142impl From<&ParameterDescription> for FMOD_STUDIO_PARAMETER_DESCRIPTION {
143    fn from(value: &ParameterDescription) -> Self {
144        FMOD_STUDIO_PARAMETER_DESCRIPTION {
145            name: value.name.as_ptr(),
146            id: value.id.into(),
147            minimum: value.minimum,
148            maximum: value.maximum,
149            defaultvalue: value.default_value,
150            type_: value.kind.into(),
151            flags: value.flags.into(),
152            guid: value.guid.into(),
153        }
154    }
155}
156
157impl ParameterDescription {
158    /// Create a safe [`ParameterDescription`] struct from the FFI equivalent.
159    ///
160    /// # Safety
161    ///
162    /// 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.
163    ///
164    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
165    pub unsafe fn from_ffi(value: FMOD_STUDIO_PARAMETER_DESCRIPTION) -> ParameterDescription {
166        unsafe {
167            ParameterDescription {
168                name: Utf8CStr::from_ptr_unchecked(value.name).to_cstring(),
169                id: value.id.into(),
170                minimum: value.minimum,
171                maximum: value.maximum,
172                default_value: value.defaultvalue,
173                kind: ParameterKind::unchecked_transmute_from(value.type_),
174                flags: value.flags.into(),
175                guid: value.guid.into(),
176            }
177        }
178    }
179}
180
181#[derive(Clone, PartialEq, Debug)]
182pub struct UserProperty {
183    pub name: Utf8CString,
184    pub kind: UserPropertyKind,
185}
186
187impl UserProperty {
188    /// Create a safe [`UserPropertyKind`] struct from the FFI equivalent.
189    ///
190    /// # Safety
191    ///
192    /// 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.
193    /// The type field must match the type assigned to the union.
194    ///
195    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
196    pub unsafe fn from_ffi(value: FMOD_STUDIO_USER_PROPERTY) -> Self {
197        unsafe {
198            UserProperty {
199                name: Utf8CStr::from_ptr_unchecked(value.name).to_cstring(),
200                kind: match value.type_ {
201                    FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER => {
202                        UserPropertyKind::Int(value.__bindgen_anon_1.intvalue)
203                    }
204                    FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN => {
205                        UserPropertyKind::Bool(value.__bindgen_anon_1.boolvalue.into())
206                    }
207                    FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT => {
208                        UserPropertyKind::Float(value.__bindgen_anon_1.floatvalue)
209                    }
210                    FMOD_STUDIO_USER_PROPERTY_TYPE_STRING => {
211                        let cstring =
212                            Utf8CStr::from_ptr_unchecked(value.__bindgen_anon_1.stringvalue)
213                                .to_cstring();
214                        UserPropertyKind::String(cstring)
215                    }
216                    v => panic!("invalid user property type {v}"),
217                },
218            }
219        }
220    }
221}
222
223impl From<&UserProperty> for FMOD_STUDIO_USER_PROPERTY {
224    fn from(value: &UserProperty) -> Self {
225        let (kind, union) = match value.kind {
226            UserPropertyKind::Int(v) => (
227                FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER,
228                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 { intvalue: v },
229            ),
230            UserPropertyKind::Bool(v) => (
231                FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN,
232                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 {
233                    boolvalue: v.into(),
234                },
235            ),
236            UserPropertyKind::Float(v) => (
237                FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT,
238                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 { floatvalue: v },
239            ),
240            UserPropertyKind::String(ref v) => (
241                FMOD_STUDIO_USER_PROPERTY_TYPE_STRING,
242                FMOD_STUDIO_USER_PROPERTY__bindgen_ty_1 {
243                    stringvalue: v.as_ptr(),
244                },
245            ),
246        };
247        FMOD_STUDIO_USER_PROPERTY {
248            name: value.name.as_ptr(),
249            type_: kind,
250            __bindgen_anon_1: union,
251        }
252    }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
256pub struct BufferInfo {
257    pub current_usage: c_int,
258    pub peak_usage: c_int,
259    pub capacity: c_int,
260    pub stall_count: c_int,
261    pub stall_time: c_float,
262}
263
264impl From<FMOD_STUDIO_BUFFER_INFO> for BufferInfo {
265    fn from(value: FMOD_STUDIO_BUFFER_INFO) -> Self {
266        BufferInfo {
267            current_usage: value.currentusage,
268            peak_usage: value.peakusage,
269            capacity: value.capacity,
270            stall_count: value.stallcount,
271            stall_time: value.stalltime,
272        }
273    }
274}
275
276impl From<BufferInfo> for FMOD_STUDIO_BUFFER_INFO {
277    fn from(value: BufferInfo) -> Self {
278        FMOD_STUDIO_BUFFER_INFO {
279            currentusage: value.current_usage,
280            peakusage: value.peak_usage,
281            capacity: value.capacity,
282            stallcount: value.stall_count,
283            stalltime: value.stall_time,
284        }
285    }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
289pub struct BufferUsage {
290    pub studio_command_queue: BufferInfo,
291    pub studio_handle: BufferInfo,
292}
293
294impl From<FMOD_STUDIO_BUFFER_USAGE> for BufferUsage {
295    fn from(value: FMOD_STUDIO_BUFFER_USAGE) -> Self {
296        BufferUsage {
297            studio_command_queue: value.studiocommandqueue.into(),
298            studio_handle: value.studiohandle.into(),
299        }
300    }
301}
302
303impl From<BufferUsage> for FMOD_STUDIO_BUFFER_USAGE {
304    fn from(value: BufferUsage) -> Self {
305        FMOD_STUDIO_BUFFER_USAGE {
306            studiocommandqueue: value.studio_command_queue.into(),
307            studiohandle: value.studio_handle.into(),
308        }
309    }
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
313pub struct CpuUsage {
314    pub update: c_float,
315}
316
317impl From<FMOD_STUDIO_CPU_USAGE> for CpuUsage {
318    fn from(value: FMOD_STUDIO_CPU_USAGE) -> Self {
319        CpuUsage {
320            update: value.update,
321        }
322    }
323}
324
325impl From<CpuUsage> for FMOD_STUDIO_CPU_USAGE {
326    fn from(value: CpuUsage) -> Self {
327        FMOD_STUDIO_CPU_USAGE {
328            update: value.update,
329        }
330    }
331}
332
333#[derive(Debug)]
334pub struct SoundInfo<'a> {
335    pub builder: SoundBuilder<'a>,
336    pub subsound_index: c_int,
337}
338
339impl<'a> SoundInfo<'a> {
340    /// Create a safe [`SoundInfo`] struct from the FFI equivalent.
341    ///
342    /// # Safety
343    ///
344    /// See [`SoundBuilder::from_ffi`] for more information.
345    pub unsafe fn from_ffi(value: FMOD_STUDIO_SOUND_INFO) -> Self {
346        SoundInfo {
347            builder: unsafe {
348                SoundBuilder::from_ffi(value.name_or_data, value.mode, value.exinfo)
349            },
350            subsound_index: value.subsoundindex,
351        }
352    }
353}
354
355impl From<&SoundInfo<'_>> for FMOD_STUDIO_SOUND_INFO {
356    fn from(value: &SoundInfo<'_>) -> Self {
357        FMOD_STUDIO_SOUND_INFO {
358            name_or_data: value.builder.name_or_data,
359            mode: value.builder.mode,
360            exinfo: value.builder.create_sound_ex_info,
361            subsoundindex: value.subsound_index,
362        }
363    }
364}
365
366#[derive(Debug, Clone)]
367pub struct CommandInfo {
368    pub command_name: Utf8CString,
369    pub parent_command_index: c_int,
370    pub frame_number: c_int,
371    pub frame_time: c_float,
372    pub instance_type: InstanceType,
373    pub output_type: InstanceType,
374    pub instance_handle: c_uint,
375    pub output_handle: c_uint,
376}
377
378impl From<&CommandInfo> for FMOD_STUDIO_COMMAND_INFO {
379    fn from(value: &CommandInfo) -> Self {
380        FMOD_STUDIO_COMMAND_INFO {
381            commandname: value.command_name.as_ptr(),
382            parentcommandindex: value.parent_command_index,
383            framenumber: value.frame_number,
384            frametime: value.frame_time,
385            instancetype: value.instance_type.into(),
386            outputtype: value.output_type.into(),
387            instancehandle: value.instance_handle,
388            outputhandle: value.output_handle,
389        }
390    }
391}
392
393impl CommandInfo {
394    /// Create a safe [`CommandInfo`] struct from the FFI equivalent.
395    ///
396    /// # Safety
397    ///
398    /// 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.
399    ///
400    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
401    pub unsafe fn from_ffi(value: FMOD_STUDIO_COMMAND_INFO) -> Self {
402        CommandInfo {
403            command_name: unsafe { Utf8CStr::from_ptr_unchecked(value.commandname).to_cstring() },
404            parent_command_index: value.parentcommandindex,
405            frame_number: value.framenumber,
406            frame_time: value.frametime,
407            instance_type: unsafe { InstanceType::unchecked_transmute_from(value.instancetype) },
408            output_type: unsafe { InstanceType::unchecked_transmute_from(value.instancetype) },
409            instance_handle: value.instancehandle,
410            output_handle: value.outputhandle,
411        }
412    }
413}
414
415pub struct ProgrammerSoundProperties<'prop> {
416    pub name: Utf8CString,
417    // FIXME use option for both of these
418    pub sound: &'prop mut Sound,
419    pub subsound_index: &'prop mut c_int,
420}
421
422pub struct PluginInstanceProperties {
423    pub name: Utf8CString,
424    pub dsp: Dsp,
425}
426
427impl From<&PluginInstanceProperties> for FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES {
428    fn from(value: &PluginInstanceProperties) -> Self {
429        FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES {
430            name: value.name.as_ptr(),
431            dsp: value.dsp.inner,
432        }
433    }
434}
435
436impl PluginInstanceProperties {
437    /// Create a safe [`PluginInstanceProperties`] struct from the FFI equivalent.
438    ///
439    /// # Safety
440    ///
441    /// 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.
442    ///
443    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
444    pub unsafe fn from_ffi(value: FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES) -> Self {
445        PluginInstanceProperties {
446            name: unsafe { Utf8CStr::from_ptr_unchecked(value.name) }.to_cstring(),
447            dsp: value.dsp.into(),
448        }
449    }
450}
451
452pub struct TimelineMarkerProperties {
453    pub name: Utf8CString,
454    pub position: c_int,
455}
456
457impl From<&TimelineMarkerProperties> for FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES {
458    fn from(value: &TimelineMarkerProperties) -> Self {
459        FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES {
460            name: value.name.as_ptr(),
461            position: value.position,
462        }
463    }
464}
465
466impl TimelineMarkerProperties {
467    /// Create a safe [`TimelineMarkerProperties`] struct from the FFI equivalent.
468    ///
469    /// # Safety
470    ///
471    /// 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.
472    ///
473    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
474    pub unsafe fn from_ffi(value: FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES) -> Self {
475        TimelineMarkerProperties {
476            name: unsafe { Utf8CStr::from_ptr_unchecked(value.name) }.to_cstring(),
477            position: value.position,
478        }
479    }
480}
481
482pub struct TimelineBeatProperties {
483    pub bar: c_int,
484    pub beat: c_int,
485    pub position: c_int,
486    pub tempo: c_float,
487    pub time_signature_upper: c_int,
488    pub time_signature_lower: c_int,
489}
490
491impl From<TimelineBeatProperties> for FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES {
492    fn from(value: TimelineBeatProperties) -> Self {
493        FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES {
494            bar: value.bar,
495            beat: value.beat,
496            position: value.position,
497            tempo: value.tempo,
498            timesignatureupper: value.time_signature_upper,
499            timesignaturelower: value.time_signature_lower,
500        }
501    }
502}
503
504impl From<FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES> for TimelineBeatProperties {
505    fn from(value: FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES) -> Self {
506        TimelineBeatProperties {
507            bar: value.bar,
508            beat: value.beat,
509            position: value.position,
510            tempo: value.tempo,
511            time_signature_upper: value.timesignatureupper,
512            time_signature_lower: value.timesignaturelower,
513        }
514    }
515}
516
517pub struct TimelineNestedBeatProperties {
518    pub event_guid: Guid,
519    pub properties: TimelineBeatProperties,
520}
521
522impl From<TimelineNestedBeatProperties> for FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES {
523    fn from(value: TimelineNestedBeatProperties) -> Self {
524        FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES {
525            eventid: value.event_guid.into(),
526            properties: value.properties.into(),
527        }
528    }
529}
530
531impl From<FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES> for TimelineNestedBeatProperties {
532    fn from(value: FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES) -> Self {
533        TimelineNestedBeatProperties {
534            event_guid: value.eventid.into(),
535            properties: value.properties.into(),
536        }
537    }
538}