fmod/studio/event_instance/
callback.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 crate::{FmodResultExt, Result};
8use fmod_sys::*;
9use lanyard::Utf8CStr;
10use std::ffi::c_void;
11
12use crate::{
13    Sound, panic_wrapper,
14    studio::{
15        EventCallbackMask, EventInstance, PluginInstanceProperties, ProgrammerSoundProperties,
16        TimelineBeatProperties, TimelineMarkerProperties, TimelineNestedBeatProperties,
17    },
18};
19
20/// Trait for this particular FMOD callback.
21///
22/// No `self` parameter is passed to the callback!
23#[allow(unused_variables)]
24pub trait EventInstanceCallback {
25    /// Called when an instance is fully created.
26    fn created(event: EventInstance) -> Result<()> {
27        Ok(())
28    }
29
30    /// Called when an instance is just about to be destroyed.
31    fn destroyed(event: EventInstance) -> Result<()> {
32        Ok(())
33    }
34
35    /// [`EventInstance::start`] has been called on an event which was not already playing.
36    /// The event will remain in this state until its sample data has been loaded.
37    fn starting(event: EventInstance) -> Result<()> {
38        Ok(())
39    }
40
41    /// The event has commenced playing.
42    /// Normally this callback will be issued immediately after [`EventInstanceCallback::starting`], but may be delayed until sample data has loaded.
43    fn started(event: EventInstance) -> Result<()> {
44        Ok(())
45    }
46
47    /// [`EventInstance::start`] has been called on an event which was already playing.
48    fn restarted(event: EventInstance) -> Result<()> {
49        Ok(())
50    }
51
52    /// The event has stopped.
53    fn stopped(event: EventInstance) -> Result<()> {
54        Ok(())
55    }
56
57    /// [`EventInstance::start`] has been called but the polyphony settings did not allow the event to start.
58    ///
59    /// In this case none of [`EventInstanceCallback::starting`], [`EventInstanceCallback::started`] and [`EventInstanceCallback::stopped`] will be called.
60    fn start_failed(event: EventInstance) -> Result<()> {
61        Ok(())
62    }
63
64    /// A programmer sound is about to play. FMOD expects the callback to provide an [`Sound`] object for it to use.
65    fn create_programmer_sound(
66        event: EventInstance,
67        sound_props: ProgrammerSoundProperties<'_>,
68    ) -> Result<()> {
69        Ok(())
70    }
71
72    /// A programmer sound has stopped playing. At this point it is safe to release the [`Sound`] object that was used.
73    fn destroy_programmer_sound(
74        event: EventInstance,
75        sound_props: ProgrammerSoundProperties<'_>,
76    ) -> Result<()> {
77        Ok(())
78    }
79
80    /// Called when a DSP plug-in instance has just been created.
81    fn plugin_created(event: EventInstance, plugin_props: PluginInstanceProperties) -> Result<()> {
82        Ok(())
83    }
84
85    /// Called when a DSP plug-in instance is about to be destroyed.
86    fn plugin_destroyed(
87        event: EventInstance,
88        plugin_props: PluginInstanceProperties,
89    ) -> Result<()> {
90        Ok(())
91    }
92
93    /// Called when the timeline passes a named marker.
94    fn timeline_marker(
95        event: EventInstance,
96        timeline_props: TimelineMarkerProperties,
97    ) -> Result<()> {
98        Ok(())
99    }
100
101    /// Called when the timeline hits a beat in a tempo section.
102    fn timeline_beat(event: EventInstance, timeline_beat: TimelineBeatProperties) -> Result<()> {
103        Ok(())
104    }
105
106    /// Called when the event plays a sound.
107    fn sound_played(event: EventInstance, sound: Sound) -> Result<()> {
108        Ok(())
109    }
110
111    /// Called when the event finishes playing a sound.
112    fn sound_stopped(event: EventInstance, sound: Sound) -> Result<()> {
113        Ok(())
114    }
115
116    /// Called when the event becomes virtual.
117    fn real_to_virtual(event: EventInstance) -> Result<()> {
118        Ok(())
119    }
120
121    /// Called when the event becomes real.
122    fn virtual_to_real(event: EventInstance) -> Result<()> {
123        Ok(())
124    }
125
126    /// Called when a new event is started by a start event command.
127    fn start_event_command(event: EventInstance, new_event: EventInstance) -> Result<()> {
128        Ok(())
129    }
130
131    /// Called when the timeline hits a beat in a tempo section of a nested event.
132    fn nested_timeline_beat(
133        event: EventInstance,
134        timeline_props: TimelineNestedBeatProperties,
135    ) -> Result<()> {
136        Ok(())
137    }
138}
139
140pub(crate) unsafe extern "C" fn event_callback_impl<C: EventInstanceCallback>(
141    kind: FMOD_STUDIO_EVENT_CALLBACK_TYPE,
142    event: *mut FMOD_STUDIO_EVENTINSTANCE,
143    parameters: *mut c_void,
144) -> FMOD_RESULT {
145    panic_wrapper(|| {
146        let event = unsafe { EventInstance::from_ffi(event) };
147        let result = match kind {
148            FMOD_STUDIO_EVENT_CALLBACK_CREATED => C::created(event),
149            FMOD_STUDIO_EVENT_CALLBACK_DESTROYED => C::destroyed(event),
150            FMOD_STUDIO_EVENT_CALLBACK_STARTING => C::starting(event),
151            FMOD_STUDIO_EVENT_CALLBACK_STARTED => C::started(event),
152            FMOD_STUDIO_EVENT_CALLBACK_RESTARTED => C::restarted(event),
153            FMOD_STUDIO_EVENT_CALLBACK_STOPPED => C::stopped(event),
154            FMOD_STUDIO_EVENT_CALLBACK_START_FAILED => C::start_failed(event),
155            FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND => {
156                let props = unsafe {
157                    let props = &mut *parameters.cast::<FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES>();
158                    ProgrammerSoundProperties {
159                        name: Utf8CStr::from_ptr_unchecked(props.name).to_cstring(),
160                        sound: &mut *std::ptr::addr_of_mut!(props.sound).cast(),
161                        subsound_index: &mut props.subsoundIndex,
162                    }
163                };
164                C::create_programmer_sound(event, props)
165            }
166            FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND => {
167                let props = unsafe {
168                    let props = &mut *parameters.cast::<FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES>();
169                    ProgrammerSoundProperties {
170                        name: Utf8CStr::from_ptr_unchecked(props.name).to_cstring(),
171                        sound: &mut *std::ptr::addr_of_mut!(props.sound).cast(),
172                        subsound_index: &mut props.subsoundIndex,
173                    }
174                };
175                C::destroy_programmer_sound(event, props)
176            }
177            FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_CREATED => {
178                let props = unsafe { PluginInstanceProperties::from_ffi(*parameters.cast()) };
179                C::plugin_created(event, props)
180            }
181            FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_DESTROYED => {
182                let props = unsafe { PluginInstanceProperties::from_ffi(*parameters.cast()) };
183                C::plugin_destroyed(event, props)
184            }
185            FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER => {
186                let props = unsafe { TimelineMarkerProperties::from_ffi(*parameters.cast()) };
187                C::timeline_marker(event, props)
188            }
189            FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT => {
190                let props = unsafe {
191                    TimelineBeatProperties::from(
192                        *parameters.cast::<FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES>(),
193                    )
194                };
195                C::timeline_beat(event, props)
196            }
197            FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED => {
198                let sound = unsafe { Sound::from_ffi(parameters.cast()) };
199                C::sound_played(event, sound)
200            }
201            FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED => {
202                let sound = unsafe { Sound::from_ffi(parameters.cast()) };
203                C::sound_stopped(event, sound)
204            }
205            FMOD_STUDIO_EVENT_CALLBACK_REAL_TO_VIRTUAL => C::real_to_virtual(event),
206            FMOD_STUDIO_EVENT_CALLBACK_VIRTUAL_TO_REAL => C::virtual_to_real(event),
207            FMOD_STUDIO_EVENT_CALLBACK_START_EVENT_COMMAND => {
208                let new_event = unsafe { EventInstance::from_ffi(parameters.cast()) };
209                C::start_event_command(event, new_event)
210            }
211            FMOD_STUDIO_EVENT_CALLBACK_NESTED_TIMELINE_BEAT => {
212                let props = unsafe {
213                    TimelineNestedBeatProperties::from(
214                        *parameters.cast::<FMOD_STUDIO_TIMELINE_NESTED_BEAT_PROPERTIES>(),
215                    )
216                };
217                C::nested_timeline_beat(event, props)
218            }
219            _ => {
220                eprintln!("warning: unknown event callback type {kind}");
221                return FMOD_RESULT::FMOD_OK;
222            }
223        };
224        FMOD_RESULT::from_result(result)
225    })
226}
227
228impl EventInstance {
229    /// Sets the event instance user data.
230    #[allow(clippy::not_unsafe_ptr_arg_deref)] // fmod doesn't dereference the passed in pointer, and the user dereferencing it is unsafe anyway
231    pub fn set_userdata(&self, userdata: *mut c_void) -> Result<()> {
232        unsafe { FMOD_Studio_EventInstance_SetUserData(self.inner.as_ptr(), userdata).to_result() }
233    }
234
235    /// Retrieves the event instance user data.
236    pub fn get_userdata(&self) -> Result<*mut c_void> {
237        let mut userdata = std::ptr::null_mut();
238        unsafe {
239            FMOD_Studio_EventInstance_GetUserData(self.inner.as_ptr(), &raw mut userdata)
240                .to_result()?;
241        }
242        Ok(userdata)
243    }
244
245    /// Sets the user callback.
246    pub fn set_callback<C: EventInstanceCallback>(&self, mask: EventCallbackMask) -> Result<()> {
247        unsafe {
248            FMOD_Studio_EventInstance_SetCallback(
249                self.inner.as_ptr(),
250                Some(event_callback_impl::<C>),
251                mask.into(),
252            )
253            .to_result()
254        }
255    }
256}