# PreviewEvent works for custom programmer events, but only sometimes

**URL:** <https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908>\
**Category:** Unity\
**Tags:** unity, csharp\
**Created:** [November 25, 2023, 2:43pm UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908 "2023-11-25T14:43:30Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![lgarczyn](https://avatars.discourse-cdn.com/v4/letter/l/6f9a4e/32.png) [@lgarczyn](https://qa.fmod.com/u/lgarczyn)\
**Post date:** [November 25, 2023, 2:43pm UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/1 "2023-11-25T14:43:30Z")

</div>

So I have custom AI voiceover tools in the editor for prototyping, and it requires previewing them from the editor:

```auto
      if (Application.isPlaying)
      {
        _eventInstance = RuntimeManager.CreateInstance(eventPath);
      }
      #if UNITY_EDITOR
      else
      {
        EditorEventRef eventRef = EventManager.EventFromPath(eventPath);
        EditorUtils.LoadPreviewBanks();
        EditorUtils.PreviewEvent(eventRef, new Dictionary<string, float>());
      }
      #endif

      // Pin the key string in memory and pass a pointer through the user data
      _voiceDataHandle = GCHandle.Alloc(_voiceData);
      Log(_eventInstance.setUserData(GCHandle.ToIntPtr(_voiceDataHandle)), "setUserData");

      Log(_eventInstance.setCallback(DialogueEventCallback), "setCallback");
      Log(_eventInstance.start(), "start");
      Log(_eventInstance.release(), "release");

```

```auto

    [MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
    static RESULT DialogueEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
      EventInstance instance = new(instancePtr);

      // Retrieve the user data
      if (!StaticLog(instance.getUserData(out IntPtr voiceDataInstancePtr))) return RESULT.OK;
      if (voiceDataInstancePtr == IntPtr.Zero) return RESULT.OK;

      // Get the object to store beat and marker details
      GCHandle voiceDataHandle = GCHandle.FromIntPtr(voiceDataInstancePtr);
      VoiceData voiceData = (VoiceData)voiceDataHandle.Target;

      voiceData.EventCallbackType = type;
      switch (type)
      {
        case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
        {
          MODE soundMode = MODE.CREATESTREAM;
          PROGRAMMER_SOUND_PROPERTIES parameter = (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
            typeof(PROGRAMMER_SOUND_PROPERTIES));

          RESULT soundResult = RuntimeManager.CoreSystem.createSound(
            voiceData.Path.FullPath,
            soundMode,
            out Sound dialogueSound);
          if (soundResult == RESULT.OK)
          {
            parameter.sound = dialogueSound.handle;
            parameter.subsoundIndex = -1;
            Marshal.StructureToPtr(parameter, parameterPtr, false);
          }

          break;
        }
        case EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
        {
          PROGRAMMER_SOUND_PROPERTIES parameter = (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
            typeof(PROGRAMMER_SOUND_PROPERTIES));
          Sound sound = new(parameter.sound);
          sound.release();

          break;
        }
        case EVENT_CALLBACK_TYPE.DESTROYED:
        {
          // Now the event has been destroyed, unpin the string memory so it can be garbage collected
          voiceDataHandle.Free();

          break;
        }
      }

      return RESULT.OK;
    }

```

This works fine in the editor, except when it doesn’t. Sometimes PreviewEvent just returns a null pointer.

---

<div class="post-metadata">

**Author:** ![Leah\_FMOD](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/leah_fmod/32/3685_2.png) [@Leah\_FMOD](https://qa.fmod.com/u/Leah_FMOD)\
**Post date:** [November 30, 2023, 12:47am UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/2 "2023-11-30T00:47:24Z")

</div>

Hi,

A few questions:

- What version of FMOD for Unity are you using?
- Do you find that your run into this issue immediately, or only after PreviewEvent works without issue several times?
- Correct me if I’m wrong, but are you missing an assignment of the EventInstance returned by `PreviewEvent()` to `_eventInstance`?

---

<div class="post-metadata">

**Author:** ![lgarczyn](https://avatars.discourse-cdn.com/v4/letter/l/6f9a4e/32.png) [@lgarczyn](https://qa.fmod.com/u/lgarczyn)\
**Post date:** [November 30, 2023, 7:11am UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/3 "2023-11-30T07:11:48Z")

</div>

Okay now that’s pretty hilarious. Sorry for the bother, I was indeed missing an assignment. Not sure how that ever worked.

---

<div class="post-metadata">

**Author:** ![Leah\_FMOD](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/leah_fmod/32/3685_2.png) [@Leah\_FMOD](https://qa.fmod.com/u/Leah_FMOD)\
**Post date:** [November 30, 2023, 10:36pm UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/4 "2023-11-30T22:36:15Z")

</div>

No problem, happy to hear that it’s now working.

---

<div class="post-metadata">

**Author:** ![lgarczyn](https://avatars.discourse-cdn.com/v4/letter/l/6f9a4e/32.png) [@lgarczyn](https://qa.fmod.com/u/lgarczyn)\
**Post date:** [December 2, 2023, 11:30pm UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/5 "2023-12-02T23:30:55Z")

</div>

So now I’m getting a new hilarious bug.

When I play the event in the editor, nothing happens.  
But when I try in playmode, the editor freezes on SetListenerLocation of all places

New code:

```auto
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using AOT;
using FMOD;
using FMOD.Studio;
using FMODUnity;
using UnityEngine;
using Debug = UnityEngine.Debug;

#if UNITY_EDITOR
using System.Collections.Generic;
#endif

namespace Dialogs.VoiceOver.Utils
{
  public class VoiceOverPlayer
  {
    EventInstance _eventInstance;
    PLAYBACK_STATE _state;
    VoiceData _voiceData;
    GCHandle _voiceDataHandle;
    static FMOD.Studio.System _system;

    #if UNITY_EDITOR
    static FMOD.System CoreSystem
    {
      get
      {
        if (StaticLog(EditorUtils.System.getCoreSystem(out FMOD.System core))) return core;
        throw new Exception("GetCoreSystem failed");
      }
    }
    #else
    static FMOD.System CoreSystem => RuntimeManager.CoreSystem;
    #endif

    public bool IsVoicePlaying()
    {
      if (_voiceData == null ||
          _voiceData.EventCallbackType == EVENT_CALLBACK_TYPE.SOUND_STOPPED ||
          _voiceData.EventCallbackType == EVENT_CALLBACK_TYPE.STOPPED ||
          _voiceData.EventCallbackType == EVENT_CALLBACK_TYPE.DESTROYED) return false;
      _eventInstance.getPlaybackState(out _state);
      return _voiceData.EventCallbackType != EVENT_CALLBACK_TYPE.SOUND_STOPPED;
    }

    public bool HasAlreadyStopped()
    {
      return _state == PLAYBACK_STATE.STOPPED;
    }

    public void PlayDialogue(VoiceOverPath path)
    {
      _voiceData = new VoiceData
      {
        Path = path,
      };
      string eventPath = "event:/Dialogue/Dialogue";

      if (Application.isPlaying)
      {
        _eventInstance = RuntimeManager.CreateInstance(eventPath);
      }
      #if UNITY_EDITOR
      else
      {
        EditorEventRef eventRef = EventManager.EventFromPath(eventPath);
        EditorUtils.LoadPreviewBanks();
        EditorUtils.PreviewEvent(eventRef, new Dictionary<string, float>());
        _eventInstance = EditorUtils.PreviewEvent(eventRef, new Dictionary<string, float>());
      }
      #endif

      // Pin the key string in memory and pass a pointer through the user data
      _voiceDataHandle = GCHandle.Alloc(_voiceData);
      Log(_eventInstance.setUserData(GCHandle.ToIntPtr(_voiceDataHandle)), "setUserData");

      Log(_eventInstance.setCallback(DialogueEventCallback), "setCallback");
      Log(_eventInstance.start(), "start");
      Log(_eventInstance.release(), "release");
    }

    [MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
    static RESULT DialogueEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
      EventInstance instance = new(instancePtr);

      // Retrieve the user data
      if (!StaticLog(instance.getUserData(out IntPtr voiceDataInstancePtr))) return RESULT.OK;
      if (voiceDataInstancePtr == IntPtr.Zero) return RESULT.OK;

      // Get the object to store beat and marker details
      GCHandle voiceDataHandle = GCHandle.FromIntPtr(voiceDataInstancePtr);
      VoiceData voiceData = (VoiceData)voiceDataHandle.Target;

      voiceData.EventCallbackType = type;
      switch (type)
      {
        case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
        {
          MODE soundMode = MODE.CREATESTREAM;
          PROGRAMMER_SOUND_PROPERTIES parameter = (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
            typeof(PROGRAMMER_SOUND_PROPERTIES));

          RESULT soundResult = RuntimeManager.CoreSystem.createSound(
          RESULT soundResult = CoreSystem.createSound(
            voiceData.Path.FullPath,
            soundMode,
            out Sound dialogueSound);
            out FMOD.Sound dialogueSound);
          if (soundResult == RESULT.OK)
          {
            parameter.sound = dialogueSound.handle;
            parameter.subsoundIndex = -1;
            Marshal.StructureToPtr(parameter, parameterPtr, false);
          }

          break;
        }
        case EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
        {
          PROGRAMMER_SOUND_PROPERTIES parameter = (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
            typeof(PROGRAMMER_SOUND_PROPERTIES));
          Sound sound = new(parameter.sound);
          FMOD.Sound sound = new(parameter.sound);
          sound.release();

          break;
        }
        case EVENT_CALLBACK_TYPE.DESTROYED:
        {
          // Now the event has been destroyed, unpin the string memory so it can be garbage collected
          voiceDataHandle.Free();

          break;
        }
      }

      return RESULT.OK;
    }

    bool Log(RESULT result, string label = "")
    {
      if (result == RESULT.OK) return true;
      Debug.Log($"Dialogue System: Sequencer: TTS({_voiceData.Path}): FMOD failed at {label} : {result}");
      return false;
    }

    static bool StaticLog(RESULT result, string label = "")
    {
      if (result == RESULT.OK) return true;
      Debug.Log($"Dialogue System: Sequencer: TTS(): FMOD failed at {label} : {result}");
      return false;
    }

    class VoiceData
    {
      public EVENT_CALLBACK_TYPE EventCallbackType;
      public VoiceOverPath Path;
    }

    public void Destroy(bool stopSound = false)
    {
      if (HasAlreadyStopped()) return;
      _eventInstance.release();
      if (stopSound && IsVoicePlaying()) _eventInstance.stop(STOP_MODE.ALLOWFADEOUT);
      _eventInstance.clearHandle();
    }
  }
}

```

This is probably caused by accessing EditorUtils.System during play mode, which was not what I intended, but the freeze is probably not intended behavior.

---

<div class="post-metadata">

**Author:** ![lgarczyn](https://avatars.discourse-cdn.com/v4/letter/l/6f9a4e/32.png) [@lgarczyn](https://qa.fmod.com/u/lgarczyn)\
**Post date:** [December 5, 2023, 9:46am UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/6 "2023-12-05T09:46:23Z")

</div>

Well turns out throwing an exception in a FMOD callback is a very bad idea, and Application.isPlaying is not accessible outside of the main thread.

It’s crazy the thing you take for granted when you’re used to a single thread.

---

<div class="post-metadata">

**Author:** ![lgarczyn](https://avatars.discourse-cdn.com/v4/letter/l/6f9a4e/32.png) [@lgarczyn](https://qa.fmod.com/u/lgarczyn)\
**Post date:** [December 5, 2023, 9:54am UTC](https://qa.fmod.com/t/previewevent-works-for-custom-programmer-events-but-only-sometimes/20908/7 "2023-12-05T09:54:34Z")

</div>

The final code if anyone is interested:

```auto
using System;
using System.Runtime.InteropServices;
using AOT;
using FMOD;
using FMOD.Studio;
using FMODUnity;
using UnityEngine;
using Debug = UnityEngine.Debug;

#if UNITY_EDITOR
using System.Collections.Generic;
#endif

namespace Dialogs.VoiceOver.Utils
{
  public class VoiceOverPlayer
  {
    EventInstance _eventInstance;
    PLAYBACK_STATE _state;
    VoiceData _voiceData;
    GCHandle _voiceDataHandle;

    #if UNITY_EDITOR
    static FMOD.System CoreSystem
    {
      get
      {
        if (RuntimeManager.IsInitialized) return RuntimeManager.CoreSystem;
        if (StaticLog(EditorUtils.System.getCoreSystem(out FMOD.System core))) return core;
        throw new Exception("GetCoreSystem failed");
      }
    }
    #else
    static FMOD.System CoreSystem => RuntimeManager.CoreSystem;
    #endif

    public bool IsVoicePlaying()
    {
      if (_voiceData == null ||
          _voiceData.EventCallbackType == EVENT_CALLBACK_TYPE.SOUND_STOPPED ||
          _voiceData.EventCallbackType == EVENT_CALLBACK_TYPE.STOPPED ||
          _voiceData.EventCallbackType == EVENT_CALLBACK_TYPE.DESTROYED) return false;
      _eventInstance.getPlaybackState(out _state);
      return _voiceData.EventCallbackType != EVENT_CALLBACK_TYPE.SOUND_STOPPED;
    }

    public bool HasAlreadyStopped()
    {
      return _state == PLAYBACK_STATE.STOPPED;
    }

    public void PlayDialogue(VoiceOverPath path)
    {
      _voiceData = new VoiceData
      {
        Path = path,
      };
      string eventPath = "event:/Dialogue/Dialogue";

      if (Application.isPlaying)
      {
        _eventInstance = RuntimeManager.CreateInstance(eventPath);
      }
      #if UNITY_EDITOR
      else
      {
        EditorEventRef eventRef = EventManager.EventFromPath(eventPath);
        EditorUtils.LoadPreviewBanks();
        _eventInstance = EditorUtils.PreviewEvent(eventRef, new Dictionary<string, float>());
      }
      #endif

      // Pin the key string in memory and pass a pointer through the user data
      _voiceDataHandle = GCHandle.Alloc(_voiceData);
      Log(_eventInstance.setUserData(GCHandle.ToIntPtr(_voiceDataHandle)), "setUserData");

      Log(_eventInstance.setCallback(DialogueEventCallback), "setCallback");
      Log(_eventInstance.start(), "start");
      Log(_eventInstance.release(), "release");
    }

    [MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
    static RESULT DialogueEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
      EventInstance instance = new(instancePtr);

      // Retrieve the user data
      if (!StaticLog(instance.getUserData(out IntPtr voiceDataInstancePtr))) return RESULT.OK;
      if (voiceDataInstancePtr == IntPtr.Zero) return RESULT.OK;

      // Get the object to store beat and marker details
      GCHandle voiceDataHandle = GCHandle.FromIntPtr(voiceDataInstancePtr);
      VoiceData voiceData = (VoiceData)voiceDataHandle.Target;

      voiceData.EventCallbackType = type;
      switch (type)
      {
        case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
        {
          MODE soundMode = MODE.CREATESTREAM;
          PROGRAMMER_SOUND_PROPERTIES parameter = (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
            typeof(PROGRAMMER_SOUND_PROPERTIES));

          try
          {

            RESULT soundResult = CoreSystem.createSound(
              voiceData.Path.FullPath,
              soundMode,
              out Sound dialogueSound);
            if (soundResult == RESULT.OK)
            {
              parameter.sound = dialogueSound.handle;
              parameter.subsoundIndex = -1;
              Marshal.StructureToPtr(parameter, parameterPtr, false);
            }
          }
          catch (Exception e)
          {
            Debug.LogError(e);
            return RESULT.ERR_INITIALIZED;
          }

          break;
        }
        case EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
        {
          PROGRAMMER_SOUND_PROPERTIES parameter = (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
            typeof(PROGRAMMER_SOUND_PROPERTIES));
          Sound sound = new(parameter.sound);
          sound.release();

          break;
        }
        case EVENT_CALLBACK_TYPE.DESTROYED:
        {
          // Now the event has been destroyed, unpin the string memory so it can be garbage collected
          voiceDataHandle.Free();

          break;
        }
      }

      return RESULT.OK;
    }

    bool Log(RESULT result, string label = "")
    {
      if (result == RESULT.OK) return true;
      Debug.Log($"Dialogue System: Sequencer: TTS({_voiceData.Path}): FMOD failed at {label} : {result}");
      return false;
    }

    static bool StaticLog(RESULT result, string label = "")
    {
      if (result == RESULT.OK) return true;
      Debug.Log($"Dialogue System: Sequencer: TTS(): FMOD failed at {label} : {result}");
      return false;
    }

    class VoiceData
    {
      public EVENT_CALLBACK_TYPE EventCallbackType;
      public VoiceOverPath Path;
    }

    public void Destroy(bool stopSound = false)
    {
      if (_eventInstance.isValid()) _eventInstance.release();
      if (stopSound && !HasAlreadyStopped() && IsVoicePlaying()) _eventInstance.stop(STOP_MODE.ALLOWFADEOUT);
      _eventInstance.clearHandle();
    }
  }
}

```
