public class FMODCallbackHandler : MonoBehaviour { public static event Action OnBeat; // Variables that are modified in the callback need to be part of a seperate class. // This class needs to be 'blittable' otherwise it can't be pinned in memory. [StructLayout(LayoutKind.Sequential)] class TimelineInfo { public int currentMusicBar = 0; public FMOD.StringWrapper lastMarker = new FMOD.StringWrapper(); } TimelineInfo timelineInfo; GCHandle timelineHandle; EVENT_CALLBACK beatCallback; EventInstance evtInstance; public FMODUnity.StudioEventEmitter studioEvent; private void Start() { init(studioEvent.EventInstance); } void init(EventInstance instance) { timelineInfo = new TimelineInfo(); // Explicitly create the delegate object and assign it to a member so it doesn't get freed // by the garbage collected while it's being used beatCallback = new EVENT_CALLBACK(BeatEventCallback); evtInstance = instance; // Pin the class that will store the data modified during the callback timelineHandle = GCHandle.Alloc(timelineInfo, GCHandleType.Pinned); // Pass the object through the userdata of the instance evtInstance.setUserData(GCHandle.ToIntPtr(timelineHandle)); evtInstance.setCallback(beatCallback, EVENT_CALLBACK_TYPE.TIMELINE_BEAT); //evtInstance.setCallback(beatCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT | FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER); Debug.Log("callback is setup for " + instance, transform); } void OnDestroy() { evtInstance.setUserData(IntPtr.Zero); evtInstance.release(); if (timelineHandle != null && timelineHandle.IsAllocated) timelineHandle.Free(); //if (studioEvent != null) studioEvent.onCreateInstance -= init; } [AOT.MonoPInvokeCallback(typeof(EVENT_CALLBACK))] static FMOD.RESULT BeatEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr) { EventInstance instance = new EventInstance(instancePtr); // Retrieve the user data IntPtr timelineInfoPtr; FMOD.RESULT result = instance.getUserData(out timelineInfoPtr); if (result != FMOD.RESULT.OK) { Debug.LogError("Timeline Callback error: " + result); } else if (timelineInfoPtr != IntPtr.Zero) { // Get the object to store beat and marker details GCHandle timelineHandle = GCHandle.FromIntPtr(timelineInfoPtr); TimelineInfo timelineInfo = (TimelineInfo)timelineHandle.Target; switch (type) { case EVENT_CALLBACK_TYPE.TIMELINE_BEAT: { var parameter = (FMOD.Studio.TIMELINE_BEAT_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_BEAT_PROPERTIES)); timelineInfo.currentMusicBar = parameter.bar; OnBeat?.Invoke(parameter.tempo); } break; case EVENT_CALLBACK_TYPE.TIMELINE_MARKER: { var parameter = (FMOD.Studio.TIMELINE_MARKER_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_MARKER_PROPERTIES)); timelineInfo.lastMarker = parameter.name; } break; } } return FMOD.RESULT.OK; } }