# FMOD Programmer Sound Freeze on Unity

**URL:** <https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911>\
**Category:** Unity\
**Created:** [February 26, 2023, 7:20am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911 "2023-02-26T07:20:07Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![fkkcloud](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/fkkcloud/32/2134_2.png) [@fkkcloud](https://qa.fmod.com/u/fkkcloud)\
**Post date:** [February 26, 2023, 7:20am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/1 "2023-02-26T07:20:07Z")

</div>

Hello,

Playing Programmer Sound is freezing Unity.  
All the other non-Programmer Sound has been working perfectly for the last 3 years development on this project…

Once I play few programmer sound as each dialogue line plays, it freezes the editor without any error message. It happens roughly about 7~13 Programmer Sound plays later - it’s pretty random at this point.

I am on:  
Unity 2020.3.38f1  
FMOD 2.01.11

Below is my code to use Programmer Sound wichi is based on [https://www.fmod.com/docs/2.01/unity/examples-programmer-sounds.html](https://www.fmod.com/docs/2.01/unity/examples-programmer-sounds.html)

I’ve integrated into Dialogue System (Unity Asset)'s Sequencer Command btw.  
The key string (in the code below that is retreived by a function GetParameter(0)) for search in Programmer Sound will be something like “Yuna\_158\_1” or “Min\_172\_44” and so on which seems to work but wanted to write it if it helps.

```auto
    // FMODWait(entrytag);
    public class SequencerCommandFMODWait : SequencerCommand
    {
        EventInstance eventInstance;
        PLAYBACK_STATE state;
        EventDescription eventCheck;
        FMOD.Studio.EVENT_CALLBACK dialogueCallback;

        private FMOD.Studio.EVENT_CALLBACK_TYPE _currentType;
        public FMOD.Studio.EVENT_CALLBACK_TYPE CurrentType
        {
            set
            {
                _currentType = value;
            }
            get
            {
                return _currentType;
            }
        }
        
        public IEnumerator Start()
        {
            dialogueCallback = new FMOD.Studio.EVENT_CALLBACK(DialogueEventCallback);
            
            PlayDialogue(GetParameter(0));
            
            // Mouth Animation Start
            SequencerCommandStartTalkAnimation.StartTalkAnim(speaker, GetSubject(1), GetParameter(1));
            
            if (CurrentType != EVENT_CALLBACK_TYPE.SOUND_STOPPED)
            {
                while (IsPlaying())
                {
                    yield return null;
                }
            }
            
            // Mouth Animation End
            SequencerCommandStopTalkAnimation.StopTalkAnim(speaker, GetSubject(1), GetParameter(1));
            
            Stop();
        }

        bool IsPlaying()
        {
            if (CurrentType == EVENT_CALLBACK_TYPE.STOPPED || CurrentType == EVENT_CALLBACK_TYPE.SOUND_STOPPED || CurrentType == EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND)
            {
                return false;
            }

            return CurrentType != EVENT_CALLBACK_TYPE.SOUND_STOPPED;
        }

        public void OnDestroy()
        {
            if (state != PLAYBACK_STATE.STOPPED)
            {
                if (IsPlaying())
                {
                    eventInstance.stop(STOP_MODE.IMMEDIATE);
                }
                
                eventInstance.release();
            }
        }
        
        void PlayDialogue(string key)
        {
            eventInstance = FMODUnity.RuntimeManager.CreateInstance("event:/Dialogue/DialogTest");

            // Pin the key string in memory and pass a pointer through the user data
            GCHandle stringHandle = GCHandle.Alloc(key);
            eventInstance.setUserData(GCHandle.ToIntPtr(stringHandle));

            eventInstance.setCallback(dialogueCallback);
            eventInstance.start();
        }
        
        [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
        FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
        {
            FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

            // Retrieve the user data
            IntPtr stringPtr;
            instance.getUserData(out stringPtr);

            // Get the string object
            GCHandle stringHandle = GCHandle.FromIntPtr(stringPtr);
            String key = stringHandle.Target as String;

            CurrentType = type;
            switch (type)
            {
                case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                {
                    FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATECOMPRESSEDSAMPLE | FMOD.MODE.NONBLOCKING;
                    var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                    if (key.Contains("."))
                    {
                        FMOD.Sound dialogueSound;
                        var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(Application.streamingAssetsPath + "/" + key, soundMode, out dialogueSound);
                        if (soundResult == FMOD.RESULT.OK)
                        {
                            parameter.sound = dialogueSound.handle;
                            parameter.subsoundIndex = -1;
                            Marshal.StructureToPtr(parameter, parameterPtr, false);
                        }
                    }
                    else
                    {
                        FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                        var keyResult = FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
                        if (keyResult != FMOD.RESULT.OK)
                        {
                            break;
                        }
                        FMOD.Sound dialogueSound;
                        var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode, ref dialogueSoundInfo.exinfo, out dialogueSound);
                        if (soundResult == FMOD.RESULT.OK)
                        {
                            parameter.sound = dialogueSound.handle;
                            parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
                            Marshal.StructureToPtr(parameter, parameterPtr, false);
                        }
                    }
                    break;
                }
                case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
                {
                    var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                    var sound = new FMOD.Sound(parameter.sound);
                    sound.release();

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

                    break;
                }
            }
            return FMOD.RESULT.OK;
        }

    }

```

---

<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:** [February 27, 2023, 4:56am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/2 "2023-02-27T04:56:16Z")

</div>

Hi,

You seem to be running into two connected issues. You’ll notice that the scripting example in the docs that you’ve linked has a static callback; the main issue is that your `MonoPInvokeCallback` has to be `static`. As a result of adding the `static` keyword to the callback, you’ll also notice that your line `CurrentType = type;` in the callback breaks. If you want to operate on non-static members like `CurrentType`, you need to pass them to the callback as user data, the same as you’ve done with the sound key. You cannot access them directly.

If the purpose of `CurrentType` is just to track the state of an event instance, not the callback type, you may find it more convenient to call `eventInstance.getPlaybackState()` instead. If you do need to tie some kind of behavior specifically to callback types, not just the event state, you can pass `CurrentType` as user data. Since you’re also passing the sound key as user data, you can use a struct or class to bundle multiple variables or method delegates together. The [Timeline Callbacks Scripting Example](https://www.fmod.com/docs/2.01/unity/examples-timeline-callbacks.html) in the Unity docs shows a basic example of using a struct in this way.

Hope this helps!

---

<div class="post-metadata">

**Author:** ![fkkcloud](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/fkkcloud/32/2134_2.png) [@fkkcloud](https://qa.fmod.com/u/fkkcloud)\
**Post date:** [February 27, 2023, 5:51am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/3 "2023-02-27T05:51:37Z")

</div>

Hello,

I changed it back to static and used eventInstance.getPlaybackState() instead of CurrentType.  
It seems to not occur any freeze.  
But it does not change its state when the actual sound is stopped while “EVENT\_CALLBACK\_TYPE.SOUND\_STOPPED” is called right after the file playing length is finished.  
I am using this to move the character mouth move and stop.

With eventInstance.getPlaybackState(), I can not know the exact timing of the sound (file playing length) is stopped like the CurrentType.

What would be the solution here?

---

<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:** [February 27, 2023, 6:07am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/4 "2023-02-27T06:07:21Z")

</div>

In that case, the solution is to pass `CurrentType` to the callback as user data, set its value in the callback, and then act on it outside the callback. As mentioned before, since you’re also passing the sound key as user data, you’ll want to use a struct to contain both variables; the Timeline Callbacks example I linked in my previous reply demonstrates how to do this.

---

<div class="post-metadata">

**Author:** ![fkkcloud](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/fkkcloud/32/2134_2.png) [@fkkcloud](https://qa.fmod.com/u/fkkcloud)\
**Post date:** [February 27, 2023, 6:25am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/5 "2023-02-27T06:25:05Z")

</div>

Hello,

Just implemented and it seems like its working as expected.

However, could you take a look at my implementation and see if there is any problem lurking inside if that is okay?

```auto
    public class SequencerCommandFMODWait : SequencerCommand
    {
        PLAYBACK_STATE state;
        EventDescription eventCheck;
        EVENT_CALLBACK dialogueCallback;
        private EventInstance eventInstance;
        
        class VoiceData
        {
            public string key;
            public EVENT_CALLBACK_TYPE eventCallbackType;
        }

        private VoiceData voiceData;
        private GCHandle voiceDataHandle;
        
        public IEnumerator Start()
        {
            dialogueCallback = new FMOD.Studio.EVENT_CALLBACK(DialogueEventCallback);

            voiceData = new VoiceData();
            voiceData.key = GetParameter(0);
            
            PlayDialogue(voiceData);
            
            // Mouth animation start
            SequencerCommandStartTalkAnimation.StartTalkAnim(speaker, GetSubject(1), GetParameter(1));
            
            if (voiceData.eventCallbackType != EVENT_CALLBACK_TYPE.SOUND_STOPPED)
            {
                while (IsPlaying())
                {
                    yield return null;
                }
            }
            
            // Mouth animation end
            SequencerCommandStopTalkAnimation.StopTalkAnim(speaker, GetSubject(1), GetParameter(1));
            
            Stop();
        }

        bool IsPlaying()
        {
            if (voiceData.eventCallbackType == EVENT_CALLBACK_TYPE.SOUND_STOPPED) return false;
            eventInstance.getPlaybackState(out state);
            return voiceData.eventCallbackType != EVENT_CALLBACK_TYPE.SOUND_STOPPED;
        }

        public void OnDestroy()
        {
            if (state != PLAYBACK_STATE.STOPPED)
            {
                eventInstance.setUserData(IntPtr.Zero);
                if (IsPlaying()) eventInstance.stop(STOP_MODE.IMMEDIATE);
                eventInstance.release();
            }
        }
        
        void PlayDialogue(VoiceData inVoiceData)
        {
            // TODO: check localization and set the bank to EN JP or KR - https://documentation.help/FMOD-Studio-API/programmer_sounds.html
            eventInstance = FMODUnity.RuntimeManager.CreateInstance("event:/Dialogue/DialogTest");

            // Pin the key string in memory and pass a pointer through the user data
            voiceDataHandle = GCHandle.Alloc(inVoiceData);
            eventInstance.setUserData(GCHandle.ToIntPtr(voiceDataHandle));

            eventInstance.setCallback(dialogueCallback);
            eventInstance.start();
            eventInstance.release();
        }
        
        [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
        static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
        {
            FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

            // Retrieve the user data
            IntPtr voiceDataInstancePtr;
            FMOD.RESULT result = instance.getUserData(out voiceDataInstancePtr);
            if (result != FMOD.RESULT.OK)
            {
                Debug.LogError("Timeline Callback error: " + result);
            }
            else if (voiceDataInstancePtr != IntPtr.Zero)
            {
                // Get the object to store beat and marker details
                GCHandle timelineHandle = GCHandle.FromIntPtr(voiceDataInstancePtr);
                VoiceData timelineInfo = (VoiceData) timelineHandle.Target;

                timelineInfo.eventCallbackType = type;
                switch (type)
                {
                    case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                    {
                        FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATECOMPRESSEDSAMPLE |
                                              FMOD.MODE.NONBLOCKING;
                        var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr,
                            typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                        if (timelineInfo.key.Contains("."))
                        {
                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                Application.streamingAssetsPath + "/" + timelineInfo.key, soundMode, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = -1;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }
                        else
                        {
                            FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                            var keyResult =
                                FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(timelineInfo.key, out dialogueSoundInfo);
                            if (keyResult != FMOD.RESULT.OK)
                            {
                                break;
                            }

                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode,
                                ref dialogueSoundInfo.exinfo, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }

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

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

                        break;
                    }
                }
            }

            return FMOD.RESULT.OK;
        }

    }

```

Much appreciated.

---

<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:** [February 27, 2023, 11:02pm UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/6 "2023-02-27T23:02:56Z")

</div>

The only issue I can see here is that `case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:` will not execute if the user data has been freed, which causes a memory leak as your assigned sound is never released, and therefore stays in memory. You can fix this by moving the code for the callback type `FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND` to a separate `if` statement that will always be checked, even when your user data has already been freed:

```auto
        if (result != FMOD.RESULT.OK)
        {
              UnityEngine.Debug.LogError("Callback error: " + result);
        }
        else if (voiceDataInstancePtr != IntPtr.Zero)
        {
              // existing code for CREATE_PROGRAMMER_SOUND and 
              // EVENT_CALLBACK_TYPE.DESTROYED here. Only executes
              // if user data has not been freed
        } 
        
        // separate check for whether the programmer sound is getting destroyed,
        // regardless of whether the user data is valid - sound still needs
        // to be released even if user data already has been
        if (type == FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND)
        {
              var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
              var sound = new FMOD.Sound(parameter.sound);
              sound.release();
        }

```

---

<div class="post-metadata">

**Author:** ![fkkcloud](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/fkkcloud/32/2134_2.png) [@fkkcloud](https://qa.fmod.com/u/fkkcloud)\
**Post date:** [February 27, 2023, 11:26pm UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/7 "2023-02-27T23:26:22Z")

</div>

Is this what you mean by checking the separate " `FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND`" so its always checked whether the user data is freed or not?

```auto
        [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
        static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
        {
            FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

            // Retrieve the user data
            IntPtr voiceDataInstancePtr;
            FMOD.RESULT result = instance.getUserData(out voiceDataInstancePtr);
            if (result != FMOD.RESULT.OK)
            {
                Debug.LogError("Timeline Callback error: " + result);
            }
            else if (voiceDataInstancePtr != IntPtr.Zero)
            {
                // 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 FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                    {
                        FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATECOMPRESSEDSAMPLE |
                                              FMOD.MODE.NONBLOCKING;
                        var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr,
                            typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                        if (voiceData.key.Contains("."))
                        {
                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                Application.streamingAssetsPath + "/" + voiceData.key, soundMode, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = -1;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }
                        else
                        {
                            FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                            var keyResult =
                                FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(voiceData.key, out dialogueSoundInfo);
                            if (keyResult != FMOD.RESULT.OK)
                            {
                                break;
                            }

                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode,
                                ref dialogueSoundInfo.exinfo, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }

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

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

                        break;
                    }
                }
            }
            
            if (type == FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND)
            {
                var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                var sound = new FMOD.Sound(parameter.sound);
                sound.release();
            }

            return FMOD.RESULT.OK;
        }

    }

```

---

<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:** [February 28, 2023, 12:16am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/8 "2023-02-28T00:16:12Z")

</div>

In essence, yes, but there’s some duplicate code that can be cleaned up. However, after a quick look over the rest of the code for your class, you may run into race conditions due to the code you’re executing in `OnDestroy` - for example, if the user data is valid for the `voiceDataInstancePtr != IntPtr.Zero` check, and then freed from outside the callback immediately afterwards, the callback code will try to access memory that has just been freed.

If you remove the code you have in `OnDestroy`, and use the following callback code, it should be fine.

```auto
    [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
    static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
        FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

        // Retrieve the user data
        IntPtr voiceDataInstancePtr;
        FMOD.RESULT result = instance.getUserData(out voiceDataInstancePtr);
        if (result != FMOD.RESULT.OK)
        {
            UnityEngine.Debug.LogError("Callback error: " + result);
        }
        else 
        {
            // Get the voice data struct to access the sound key and assign to the callback type
            GCHandle voiceDataHandle = GCHandle.FromIntPtr(voiceDataInstancePtr);
            VoiceData voiceData = (VoiceData)voiceDataHandle.Target;

            voiceData.eventCallbackType = type;
            switch (type)
            {
                case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                    {
                        FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATECOMPRESSEDSAMPLE |
                                              FMOD.MODE.NONBLOCKING;
                        var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
                            typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                        if (voiceData.key.Contains("."))
                        {
                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                Application.streamingAssetsPath + "/" + voiceData.key, soundMode, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = -1;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }
                        else
                        {
                            FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                            var keyResult =
                                FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(voiceData.key, out dialogueSoundInfo);
                            if (keyResult != FMOD.RESULT.OK)
                            {
                                break;
                            }

                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode,
                                ref dialogueSoundInfo.exinfo, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }

                        break;
                    }
                case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
                    {
                        var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                        var sound = new FMOD.Sound(parameter.sound);
                        sound.release();
                        break;
                    }
                case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED:
                    {
                        //Now the event has been destroyed, unpin the string memory so it can be garbage collected
                        voiceDataHandle.Free();
                        break;
                    }
            }
        }

        return FMOD.RESULT.OK;
    }

```

---

<div class="post-metadata">

**Author:** ![fkkcloud](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/fkkcloud/32/2134_2.png) [@fkkcloud](https://qa.fmod.com/u/fkkcloud)\
**Post date:** [February 28, 2023, 4:25am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/9 "2023-02-28T04:25:06Z")

</div>

I see.

So what I’ve did is:

Removed this specific code from OnDestroy()

```auto
                eventInstance.setUserData(IntPtr.Zero);

```

and also removed checking which was a separate if statement that will always be checked even when your use data has already been freed which was outside of the switch statement.

```auto
       if (type == FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND)
        {
              var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
              var sound = new FMOD.Sound(parameter.sound);
              sound.release();
        }

```

Im sum, below is the complete code:

```auto
  public class SequencerCommandFMODWait : SequencerCommand
    {
        PLAYBACK_STATE state;
        EventDescription eventCheck;
        EVENT_CALLBACK dialogueCallback;
        private EventInstance eventInstance;
        
        class VoiceData
        {
            public string key;
            public EVENT_CALLBACK_TYPE eventCallbackType;
        }

        private VoiceData voiceData;
        private GCHandle voiceDataHandle;
        
        public IEnumerator Start()
        {
            dialogueCallback = new FMOD.Studio.EVENT_CALLBACK(DialogueEventCallback);

            voiceData = new VoiceData();
            voiceData.key = GetParameter(0);

            PlayDialogue(voiceData);
            
            // Mouth animation start
            SequencerCommandStartTalkAnimation.StartTalkAnim(speaker, GetSubject(1), GetParameter(1));
            
            if (voiceData.eventCallbackType != EVENT_CALLBACK_TYPE.SOUND_STOPPED)
            {
                while (IsPlaying())
                {
                    yield return null;
                }
            }
            
            // Mouth animation end
            SequencerCommandStopTalkAnimation.StopTalkAnim(speaker, GetSubject(1), GetParameter(1));
            
            Stop();
        }

        bool IsPlaying()
        {
            if (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 void OnDestroy()
        {
            if (state != PLAYBACK_STATE.STOPPED)
            {
                if (IsPlaying())
                {
                    eventInstance.stop(STOP_MODE.IMMEDIATE);
                }
                eventInstance.release();
            }
        }
        
        void PlayDialogue(VoiceData inVoiceData)
        {
            // TODO: check localization and set the bank to EN JP or KR - https://documentation.help/FMOD-Studio-API/programmer_sounds.html
            eventInstance = FMODUnity.RuntimeManager.CreateInstance("event:/Dialogue/DialogTest");

            // Pin the key string in memory and pass a pointer through the user data
            voiceDataHandle = GCHandle.Alloc(inVoiceData);
            eventInstance.setUserData(GCHandle.ToIntPtr(voiceDataHandle));

            eventInstance.setCallback(dialogueCallback);
            eventInstance.start();
            eventInstance.release();
        }
        
        [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
        static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
        {
            FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

            // Retrieve the user data
            IntPtr voiceDataInstancePtr;
            FMOD.RESULT result = instance.getUserData(out voiceDataInstancePtr);
            if (result != FMOD.RESULT.OK)
            {
                Debug.LogError("Timeline Callback error: " + result);
            }
            else if (voiceDataInstancePtr != IntPtr.Zero)
            {
                // 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 FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                    {
                        FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATECOMPRESSEDSAMPLE |
                                              FMOD.MODE.NONBLOCKING;
                        var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr,
                            typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                        if (voiceData.key.Contains("."))
                        {
                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                Application.streamingAssetsPath + "/" + voiceData.key, soundMode, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = -1;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }
                        else
                        {
                            FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                            var keyResult =
                                FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(voiceData.key, out dialogueSoundInfo);
                            if (keyResult != FMOD.RESULT.OK)
                            {
                                break;
                            }

                            FMOD.Sound dialogueSound;
                            var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                                dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode,
                                ref dialogueSoundInfo.exinfo, out dialogueSound);
                            if (soundResult == FMOD.RESULT.OK)
                            {
                                parameter.sound = dialogueSound.handle;
                                parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
                                Marshal.StructureToPtr(parameter, parameterPtr, false);
                            }
                        }

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

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

                        break;
                    }
                }
            }
            
            return FMOD.RESULT.OK;
        }

    }

```

---

<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:** [February 28, 2023, 4:49am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/10 "2023-02-28T04:49:48Z")

</div>

What you have there should be fine. You can also:

1. Remove the `if (voiceDataInstancePtr != IntPtr.Zero)` line from your callback, leaving just the `else`, as that particular `if` statement isn’t needed now
2. Remove the extra `eventInstance.release()` from `OnDestroy()` since you’re already calling `eventInstance.release()` in `PlayDialogue()`

Neither of the above things will cause errors in your code if you leave them in, but they’re both unneeded.

---

<div class="post-metadata">

**Author:** ![fkkcloud](https://yyz2.discourse-cdn.com/flex036/user_avatar/qa.fmod.com/fkkcloud/32/2134_2.png) [@fkkcloud](https://qa.fmod.com/u/fkkcloud)\
**Post date:** [February 28, 2023, 5:40am UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/11 "2023-02-28T05:40:14Z")

</div>

Thanks for the explanation. This should fix this thread’s issue for sure.

One additional question:  
Rarely a few lines are not playing at all.

I can see that it used the correct Key and the file itself is good. Bank and table also includes the specific sound for sure.

Which line in above code is a good line to put Debug.Log or some sort of msg to show that the Programmer Sound have allocated memory for the sound and played?

Thank you.

---

<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:** [February 28, 2023, 10:25pm UTC](https://qa.fmod.com/t/fmod-programmer-sound-freeze-on-unity/19911/12 "2023-02-28T22:25:03Z")

</div>

I would recommend setting the logging level to “Log” in FMOD Settings, as it will enable you to see when a sound is being created, as well as details like its path, which you can check against your expected path. As for checking what’s happening in the callback, inserting a breakpoint in the `FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND` case so that you can step through it would probably be your best bet.

Since the occurrence is rare, however, if you don’t want to have the game pause every time a correct sound is created as well, a `Debug.Log()` call in the the various switch cases would be a good idea so that you can see which callback types are being used. Logging the various FMOD functions in the callback that return `FMOD.Result` is also a good idea, as you’ll be able to see which functions, if any, aren’t working.
