Listening to markers from events placed in a parent event's timeline

Hey there,

(working with unity/fmod 2.00.07)

I managed to receive marker label from an event within Unity through a StudioEventEmitter ( https://fmod.com/resources/documentation-unity?version=2.0&page=examples-timeline-callbacks.html )

I now have an event that embed others in its timeline and I need to be able to receive markers from those embedded events but the callback solution linked above doesn’t react to “nested” events.

I’m sure I just misunderstood something about how FMOD works with event/instance.
Is it achievable ? or do I need to rethink my workflow ?

Thanks for the help,

Starting with 2.00.02 we implemented forwarding of FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER from nested events so you should be seeing them come through. You shouldn’t need to do anything different on the implementation side either.

Can you double check your event setup and ensure there are no errors printing to the log?

Hey mathew,
Thanks for the insight !

After some reasearch I found that I needed to transform all events instance within an event into “nested” through right click menus) Nested Event Question

I managed to toggle all instance of events within my main event timeline into real nested event.
But I still don’t receive any marker from the nested ones.

I also don’t see my nested events within unity’s fmod event window ( someone already asked for that while ago Accessing nested events in unity )

I couldn’t find anything regarding "FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER "

I’m using code from the topic I linked in my original question :

    case FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER:
      {
        //Debug.Log(parameterPtr);
        var parameter = (FMOD.Studio.TIMELINE_MARKER_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_MARKER_PROPERTIES));

I don’t have any issue other than that (logs/errors …)

I tried to update fmod unity integration to the lastest version (2.00.09) and I’m not receiving any marker anymore (event from normal events).

Tried to tweak the callback subscription script but nothing works. The callback is not called.

Here is my full script (slight custom version of original logic) in case I’m doing something obviously wrong.

//--------------------------------------------------------------------
//
// This is a Unity behaviour script that demonstrates how to use
// timeline markers in your game code. 
//
// Timeline markers can be implicit - such as beats and bars. Or they 
// can be explicity placed by sound designers, in which case they have 
// a sound designer specified name attached to them.
//
// Timeline markers can be useful for syncing game events to sound
// events.
//
// The script starts a piece of music and then displays on the screen
// the current bar and the last marker encountered.
//
// This document assumes familiarity with Unity scripting. See
// https://unity3d.com/learn/tutorials/topics/scripting for resources
// on learning Unity scripting. 
//
//--------------------------------------------------------------------

using System;
using System.Runtime.InteropServices;
using UnityEngine;
using FMOD.Studio;

public abstract class FMODCallbackHandler : MonoBehaviour
{
  // 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;

  FMOD.Studio.EVENT_CALLBACK beatCallback;
  FMOD.Studio.EventInstance evtInstance;
  public FMODUnity.StudioEventEmitter studioEvent;

  private void Start()
  {
    init(studioEvent.EventInstance);
  }

  void init(FMOD.Studio.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 FMOD.Studio.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_MARKER);
    //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(FMOD.Studio.EVENT_CALLBACK))]
  FMOD.RESULT BeatEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, FMOD.Studio.EventInstance instance, IntPtr parameterPtr)
  {
    // Retrieve the user data
    IntPtr timelineInfoPtr;
    FMOD.RESULT result = instance.getUserData(out timelineInfoPtr);

    //Debug.Log(timelineInfoPtr);
    Debug.Log("beat callback : "+type);

    if (result != FMOD.RESULT.OK)
    {
      Debug.LogError("Timeline Callback error: " + result);
    }
    else if (timelineInfoPtr != IntPtr.Zero)
    {

      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));
            onBeatReceived(parameter);
          }
          break;
        case FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER:
          {
            //Debug.Log(parameterPtr);
            var parameter = (FMOD.Studio.TIMELINE_MARKER_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_MARKER_PROPERTIES));
            
            //Debug.Log(parameter);
            //Debug.Log(parameter.name);

            onMarkerReceived(parameter);
          }
          break;
        default:
          Debug.Log(type);
          break;
      }
    }
    return FMOD.RESULT.OK;
  }

  protected virtual void onBeatReceived(FMOD.Studio.TIMELINE_BEAT_PROPERTIES beat)
  {
    Debug.Log("[FMODCallBack] Beat received. "+beat);
  }

  protected virtual void onMarkerReceived(FMOD.Studio.TIMELINE_MARKER_PROPERTIES marker)
  {
    Debug.Log("[FMODCallBack] Marker received. "+marker);
  }
}

Oh, you need to wait for instance to be valid (previous version of the script was using the onCreateInstance callback and was removed)

  IEnumerator Start()
  {

    while (!studioEvent.EventInstance.isValid()) yield return null;

    //studioEvent.onCreateInstance += init;
    init();
  }

but still don’t have marker from nested (in 2.00.09)

Hey devDarjeeling,

It looks like there’s a bug in how we trigger TIMELINE_MARKER callbacks from nested events. We will address this in a future version.

Unfortunately due to the nature of the issue, there’s no real workaround.