I have an event instance that is playing between scenes, music starts in scene A, in scene B I want to listen to the music’s beat callback. Is this possible?
I don’t want to stop the music in scene A and then start it again in scene B, it abrupts the flow.
Context:
I have a section in my game where the player is outside a rave, when the player enters this scene I start playing the rave music but with a low pass filter controlled by a parameter.
So I have managed to hook up the callback to the already playing event by doing this:
void ListenForCallback()
{
var playingAmbience = AudioManager.GetAmbienceByName(ambienceToListenFor.fmodName);
// 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
playingAmbience.eventInstance.setUserData(GCHandle.ToIntPtr(timelineHandle));
var result = playingAmbience.eventInstance.setCallback(beatCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT | FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER);
if(result != FMOD.RESULT.OK) { Debug.Log("Error setting callback."); }
}
When I then changed scene I got this
ArgumentException: GCHandle value belongs to a different domain
at System.Runtime.InteropServices.GCHandle.op_Explicit (System.IntPtr value) [0x00025] in <fb001e01371b4adca20013e0ac763896>:0
at System.Runtime.InteropServices.GCHandle.FromIntPtr (System.IntPtr value) [0x00000] in <fb001e01371b4adca20013e0ac763896>:0
at AmbienceCallbackResponse.BeatEventCallback (FMOD.Studio.EVENT_CALLBACK_TYPE type, System.IntPtr instancePtr, System.IntPtr parameterPtr) [0x0004d] in D:\Github\TOEM\Assets\AmbienceCallbackResponse.cs:88
at (wrapper managed-to-native) FMOD.Studio.System.FMOD_Studio_System_Update(intptr)
at FMOD.Studio.System.update () [0x00001] in D:\Github\TOEM\Assets\Plugins\FMOD\src\Runtime\wrapper\fmod_studio.cs:418
at FMODUnity.RuntimeManager.Update () [0x00318] in D:\Github\TOEM\Assets\Plugins\FMOD\src\Runtime\RuntimeManager.cs:493
I fixed that by doing this on my callback listener script on scene B
So now I’m my the point where I’ve ended up with this error…
NullReferenceException: Object reference not set to an instance of an object
at (wrapper dynamic-method) System.Object.UnityEditor.AudioFilterGUI.call_DrawAudioFilterGUI(object,UnityEngine.MonoBehaviour)
at (wrapper managed-to-native) FMOD.Studio.System.FMOD_Studio_System_Update(intptr)
at FMOD.Studio.System.update () [0x00001] in D:\Github\TOEM\Assets\Plugins\FMOD\src\Runtime\wrapper\fmod_studio.cs:418
at FMODUnity.RuntimeManager.Update () [0x00318] in D:\Github\TOEM\Assets\Plugins\FMOD\src\Runtime\RuntimeManager.cs:493
(Filename: Assets/Plugins/FMOD/src/Runtime/wrapper/fmod_studio.cs Line: 418)
I’m on my phone right now but from reading this I’d say you at least need to set the callback of your event to null in the OnDestroy function as well. Not sure that will solve your current error but it will at least avoid additional buggy behaviour!
Yeah, I’ve gotten everything to work, cheers guys!
The event is playing using our AudioManager which is using DontDestroyOnLoad, I’m just hooking into its callback like this.
public class AmbienceCallbackResponse : MonoBehaviour
{
[SerializeField] AudioReference ambienceToListenFor;
[SerializeField] UnityEvent onBeat;
[SerializeField] UnityEvent onMarker;
FMOD.Studio.EVENT_CALLBACK beatCallback;
// 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;
bool shouldTriggerOnBeat;
bool shouldTriggerOnMarker;
private void OnDestroy()
{
var playingAmbience = AudioManager.GetAmbienceByName(ambienceToListenFor.fmodName);
var result = playingAmbience.eventInstance.setUserData(IntPtr.Zero);
if (result != RESULT.OK)
{
Debug.LogError(result.ToString());
}
result = playingAmbience.eventInstance.setCallback(null);
if (result != RESULT.OK)
{
Debug.LogError(result.ToString());
}
if(timelineHandle.IsAllocated)
timelineHandle.Free();
}
void Start()
{
// 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 FMOD.Studio.EVENT_CALLBACK(BeatEventCallback);
timelineInfo = new TimelineInfo();
ListenForCallback();
}
private void LateUpdate()
{
if (shouldTriggerOnBeat)
{
shouldTriggerOnBeat = false;
onBeat?.Invoke();
}
if (shouldTriggerOnMarker)
{
shouldTriggerOnMarker = false;
onMarker?.Invoke();
}
}
void ListenForCallback()
{
var playingAmbience = AudioManager.GetAmbienceByName(ambienceToListenFor.fmodName);
// 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
playingAmbience.eventInstance.setUserData(GCHandle.ToIntPtr(timelineHandle));
var result = playingAmbience.eventInstance.setCallback(beatCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT | FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER);
if(result != FMOD.RESULT.OK) { Debug.Log("Error setting callback."); }
}
/* THIS EVENT USED TO BE STATIC! MIGHT BE MESSING WITH THINGS! */
[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
FMOD.RESULT BeatEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
{
FMOD.Studio.EventInstance instance = new FMOD.Studio.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 FMOD.Studio.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;
shouldTriggerOnBeat = true;
break;
}
case FMOD.Studio.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;
shouldTriggerOnMarker = true;
break;
}
}
}
return FMOD.RESULT.OK;
}