fmod/core/system/
callback.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 std::ffi::{c_int, c_void};
8
9use fmod_sys::*;
10use lanyard::Utf8CStr;
11
12use crate::{
13    studio, Channel, ChannelControl, ChannelGroup, Dsp, DspConnection, Geometry, OutputType,
14    Reverb3D, Sound, SoundGroup, System,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ErrorCallbackInfo<'a> {
19    pub error: Error,
20    pub instance: Instance,
21    pub function_name: &'a Utf8CStr,
22    pub function_params: &'a Utf8CStr,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Instance {
27    None,
28    System(System),
29    Channel(Channel),
30    ChannelGroup(ChannelGroup),
31    ChannelControl(ChannelControl),
32    Sound(Sound),
33    SoundGroup(SoundGroup),
34    Dsp(Dsp),
35    DspConnection(DspConnection),
36    Geometry(Geometry),
37    Reverb3D(Reverb3D),
38    StudioSystem(studio::System),
39    StudioEventDescription(studio::EventDescription),
40    StudioEventInstance(studio::EventInstance),
41    StudioParameterInstance,
42    StudioBus(studio::Bus),
43    StudioVCA(studio::Vca),
44    StudioBank(studio::Bank),
45    StudioCommandReplay(studio::CommandReplay),
46}
47
48impl ErrorCallbackInfo<'_> {
49    /// # Safety
50    ///
51    /// The function name and function params fields of [`FMOD_ERRORCALLBACK_INFO`] must be a null-terminated and must be valid for reads of bytes up to and including the nul terminator.
52    ///
53    /// See [`Utf8CStr::from_ptr_unchecked`] for more information.
54    pub unsafe fn from_ffi(value: FMOD_ERRORCALLBACK_INFO) -> Self {
55        Self {
56            error: value.result.into(),
57            instance: match value.instancetype {
58                FMOD_ERRORCALLBACK_INSTANCETYPE_NONE => Instance::None,
59                FMOD_ERRORCALLBACK_INSTANCETYPE_SYSTEM => {
60                    Instance::System(System::from(value.instance.cast()))
61                }
62                FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNEL => {
63                    Instance::Channel(Channel::from(value.instance.cast()))
64                }
65                FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNELGROUP => {
66                    Instance::ChannelGroup(ChannelGroup::from(value.instance.cast()))
67                }
68                FMOD_ERRORCALLBACK_INSTANCETYPE_CHANNELCONTROL => {
69                    Instance::ChannelControl(ChannelControl::from(value.instance.cast()))
70                }
71                FMOD_ERRORCALLBACK_INSTANCETYPE_SOUND => {
72                    Instance::Sound(Sound::from(value.instance.cast()))
73                }
74                FMOD_ERRORCALLBACK_INSTANCETYPE_SOUNDGROUP => {
75                    Instance::SoundGroup(SoundGroup::from(value.instance.cast()))
76                }
77                FMOD_ERRORCALLBACK_INSTANCETYPE_DSP => {
78                    Instance::Dsp(Dsp::from(value.instance.cast()))
79                }
80                FMOD_ERRORCALLBACK_INSTANCETYPE_DSPCONNECTION => {
81                    Instance::DspConnection(DspConnection::from(value.instance.cast()))
82                }
83                FMOD_ERRORCALLBACK_INSTANCETYPE_GEOMETRY => {
84                    Instance::Geometry(Geometry::from(value.instance.cast()))
85                }
86                FMOD_ERRORCALLBACK_INSTANCETYPE_REVERB3D => {
87                    Instance::Reverb3D(Reverb3D::from(value.instance.cast()))
88                }
89                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_SYSTEM => {
90                    Instance::StudioSystem(studio::System::from(value.instance.cast()))
91                }
92                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTDESCRIPTION => {
93                    Instance::StudioEventDescription(studio::EventDescription::from(
94                        value.instance.cast(),
95                    ))
96                }
97                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_EVENTINSTANCE => {
98                    Instance::StudioEventInstance(studio::EventInstance::from(
99                        value.instance.cast(),
100                    ))
101                }
102                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_PARAMETERINSTANCE => {
103                    Instance::StudioParameterInstance
104                }
105                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BUS => {
106                    Instance::StudioBus(studio::Bus::from(value.instance.cast()))
107                }
108                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_VCA => {
109                    Instance::StudioVCA(studio::Vca::from(value.instance.cast()))
110                }
111                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_BANK => {
112                    Instance::StudioBank(studio::Bank::from(value.instance.cast()))
113                }
114                FMOD_ERRORCALLBACK_INSTANCETYPE_STUDIO_COMMANDREPLAY => {
115                    Instance::StudioCommandReplay(studio::CommandReplay::from(
116                        value.instance.cast(),
117                    ))
118                }
119                _ => {
120                    eprintln!("warning: unknown instance type {}", value.instancetype);
121                    Instance::None
122                }
123            },
124            function_name: unsafe { Utf8CStr::from_ptr_unchecked(value.functionname) },
125            function_params: unsafe { Utf8CStr::from_ptr_unchecked(value.functionparams) },
126        }
127    }
128}
129
130bitflags::bitflags! {
131  #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
132  pub struct SystemCallbackMask: FMOD_SYSTEM_CALLBACK_TYPE {
133      const DEVICELISTCHANGED     = FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED;
134      const DEVICELOST            = FMOD_SYSTEM_CALLBACK_DEVICELOST;
135      const MEMORYALLOCATIONFAILED= FMOD_SYSTEM_CALLBACK_MEMORYALLOCATIONFAILED;
136      const THREADCREATED         = FMOD_SYSTEM_CALLBACK_THREADCREATED;
137      const BADDSPCONNECTION      = FMOD_SYSTEM_CALLBACK_BADDSPCONNECTION;
138      const PREMIX                = FMOD_SYSTEM_CALLBACK_PREMIX;
139      const POSTMIX               = FMOD_SYSTEM_CALLBACK_POSTMIX;
140      const ERROR                 = FMOD_SYSTEM_CALLBACK_ERROR;
141      const MIDMIX                = FMOD_SYSTEM_CALLBACK_MIDMIX;
142      const THREADDESTROYED       = FMOD_SYSTEM_CALLBACK_THREADDESTROYED;
143      const PREUPDATE             = FMOD_SYSTEM_CALLBACK_PREUPDATE;
144      const POSTUPDATE            = FMOD_SYSTEM_CALLBACK_POSTUPDATE;
145      const RECORDLISTCHANGED     = FMOD_SYSTEM_CALLBACK_RECORDLISTCHANGED;
146      const BUFFEREDNOMIX         = FMOD_SYSTEM_CALLBACK_BUFFEREDNOMIX;
147      const DEVICEREINITIALIZE    = FMOD_SYSTEM_CALLBACK_DEVICEREINITIALIZE;
148      const OUTPUTUNDERRUN        = FMOD_SYSTEM_CALLBACK_OUTPUTUNDERRUN;
149      const RECORDPOSITIONCHANGED = FMOD_SYSTEM_CALLBACK_RECORDPOSITIONCHANGED ;
150      const ALL                   = FMOD_SYSTEM_CALLBACK_ALL;
151  }
152}
153
154impl From<SystemCallbackMask> for FMOD_SYSTEM_CALLBACK_TYPE {
155    fn from(mask: SystemCallbackMask) -> Self {
156        mask.bits()
157    }
158}
159
160impl From<FMOD_SYSTEM_CALLBACK_TYPE> for SystemCallbackMask {
161    fn from(mask: FMOD_SYSTEM_CALLBACK_TYPE) -> Self {
162        Self::from_bits_truncate(mask)
163    }
164}
165
166#[allow(unused_variables)]
167pub trait SystemCallback {
168    fn device_list_changed(system: System, userdata: *mut c_void) -> Result<()> {
169        Ok(())
170    }
171
172    fn device_lost(system: System, userdata: *mut c_void) -> Result<()> {
173        Ok(())
174    }
175
176    fn memory_allocation_failed(
177        system: System,
178        file: &Utf8CStr,
179        size: c_int,
180        userdata: *mut c_void,
181    ) -> Result<()> {
182        Ok(())
183    }
184
185    fn thread_created(
186        system: System,
187        handle: *mut c_void,
188        thread_name: &Utf8CStr,
189        userdata: *mut c_void,
190    ) -> Result<()> {
191        Ok(())
192    }
193
194    fn bad_dsp_connection(system: System, userdata: *mut c_void) -> Result<()> {
195        Ok(())
196    }
197
198    fn premix(system: System, userdata: *mut c_void) -> Result<()> {
199        Ok(())
200    }
201
202    fn postmix(system: System, userdata: *mut c_void) -> Result<()> {
203        Ok(())
204    }
205
206    fn error(
207        system: System,
208        error_info: ErrorCallbackInfo<'_>,
209        userdata: *mut c_void,
210    ) -> Result<()> {
211        Ok(())
212    }
213
214    fn mid_mix(system: System, userdata: *mut c_void) -> Result<()> {
215        Ok(())
216    }
217
218    fn thread_destroyed(
219        system: System,
220        handle: *mut c_void,
221        thread_name: &Utf8CStr,
222        userdata: *mut c_void,
223    ) -> Result<()> {
224        Ok(())
225    }
226
227    fn pre_update(system: System, userdata: *mut c_void) -> Result<()> {
228        Ok(())
229    }
230
231    fn post_update(system: System, userdata: *mut c_void) -> Result<()> {
232        Ok(())
233    }
234
235    fn record_list_changed(system: System, userdata: *mut c_void) -> Result<()> {
236        Ok(())
237    }
238
239    fn buffered_no_mix(system: System, userdata: *mut c_void) -> Result<()> {
240        Ok(())
241    }
242
243    fn device_reinitialize(
244        system: System,
245        output_type: OutputType,
246        driver_index: c_int,
247        userdata: *mut c_void,
248    ) -> Result<()> {
249        Ok(())
250    }
251
252    fn output_underrun(system: System, userdata: *mut c_void) -> Result<()> {
253        Ok(())
254    }
255
256    fn record_position_changed(
257        system: System,
258        sound: Sound,
259        record_position: c_int,
260        userdata: *mut c_void,
261    ) -> Result<()> {
262        Ok(())
263    }
264}
265
266unsafe extern "C" fn callback_impl<C: SystemCallback>(
267    system: *mut FMOD_SYSTEM,
268    callback_type: FMOD_SYSTEM_CALLBACK_TYPE,
269    command_data_1: *mut c_void,
270    command_data_2: *mut c_void,
271    userdata: *mut c_void,
272) -> FMOD_RESULT {
273    let system = System::from(system);
274    match callback_type {
275        FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED => C::device_list_changed(system, userdata).into(),
276        FMOD_SYSTEM_CALLBACK_DEVICELOST => C::device_lost(system, userdata).into(),
277        FMOD_SYSTEM_CALLBACK_MEMORYALLOCATIONFAILED => {
278            let file = unsafe { Utf8CStr::from_ptr_unchecked(command_data_1.cast()) };
279            C::memory_allocation_failed(system, file, command_data_2 as c_int, userdata).into()
280        }
281        FMOD_SYSTEM_CALLBACK_THREADCREATED => {
282            let thread_name = unsafe { Utf8CStr::from_ptr_unchecked(command_data_2.cast()) };
283            C::thread_created(system, command_data_1, thread_name, userdata).into()
284        }
285        FMOD_SYSTEM_CALLBACK_BADDSPCONNECTION => C::bad_dsp_connection(system, userdata).into(),
286        FMOD_SYSTEM_CALLBACK_PREMIX => C::premix(system, userdata).into(),
287        FMOD_SYSTEM_CALLBACK_POSTMIX => C::postmix(system, userdata).into(),
288        FMOD_SYSTEM_CALLBACK_ERROR => {
289            let error_info = unsafe { ErrorCallbackInfo::from_ffi(*command_data_1.cast()) };
290            C::error(system, error_info, userdata).into()
291        }
292        FMOD_SYSTEM_CALLBACK_MIDMIX => C::mid_mix(system, userdata).into(),
293        FMOD_SYSTEM_CALLBACK_THREADDESTROYED => {
294            let thread_name = unsafe { Utf8CStr::from_ptr_unchecked(command_data_2.cast()) };
295            C::thread_destroyed(system, command_data_1, thread_name, userdata).into()
296        }
297        FMOD_SYSTEM_CALLBACK_PREUPDATE => C::pre_update(system, userdata).into(),
298        FMOD_SYSTEM_CALLBACK_POSTUPDATE => C::post_update(system, userdata).into(),
299        FMOD_SYSTEM_CALLBACK_RECORDLISTCHANGED => C::record_list_changed(system, userdata).into(),
300        FMOD_SYSTEM_CALLBACK_BUFFEREDNOMIX => C::buffered_no_mix(system, userdata).into(),
301        FMOD_SYSTEM_CALLBACK_DEVICEREINITIALIZE => {
302            let output_type = OutputType::try_from(command_data_1 as FMOD_OUTPUTTYPE)
303                .expect("invalid output type");
304            C::device_reinitialize(system, output_type, command_data_2 as c_int, userdata).into()
305        }
306        FMOD_SYSTEM_CALLBACK_OUTPUTUNDERRUN => C::output_underrun(system, userdata).into(),
307        FMOD_SYSTEM_CALLBACK_RECORDPOSITIONCHANGED => {
308            let sound = Sound::from(command_data_1.cast());
309            C::record_position_changed(system, sound, command_data_2 as c_int, userdata).into()
310        }
311        _ => {
312            eprintln!("warning: unknown callback type {callback_type}");
313            FMOD_RESULT::FMOD_OK
314        }
315    }
316}
317
318impl System {
319    pub fn set_callback<C: SystemCallback>(&self, mask: SystemCallbackMask) -> Result<()> {
320        unsafe { FMOD_System_SetCallback(self.inner, Some(callback_impl::<C>), mask.into()).into() }
321    }
322}