fmod

Loading delay exceeded warning - Programmer Instrument - Unity


I'm getting an warning that says ": Loading delay exceeded, sound may play at incorrect time" whenever I try to use the example code from FMOD for Programmer Instrument

I thought maybe I need to wait for the FMOD.Sound openState == READY, but that doesn't seem to have worked. Am I missing something? Here's my code:

    public static void PlayDialogue(string key, Action callback = null)
    {
        // Create Instance
        FMOD.Studio.EventInstance dialogueInstance = FMODUnity.RuntimeManager.CreateInstance(instance.dialogueEvent);
        // Pin the key string in memory and pass a pointer through the user data
        GCHandle stringHandle = GCHandle.Alloc(key, GCHandleType.Pinned);
        dialogueInstance.setUserData(GCHandle.ToIntPtr(stringHandle));
        dialogueInstance.setCallback(instance.dialogueCallback);
    }
    
    [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
    static FMOD.RESULT DialogueCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
        // Get Instance
        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;

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

                    if (key.Contains("."))
                    {
                        // Load Sound by Given Path
                        FMOD.RESULT soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                            Application.streamingAssetsPath + "/" + key, soundMode, out dialogueSound);
                        if (soundResult == FMOD.RESULT.OK)
                        {
                            programmerSoundProperties.sound = dialogueSound.handle;
                            programmerSoundProperties.subsoundIndex = -1;
                            Marshal.StructureToPtr(programmerSoundProperties, parameterPtr, false);
                            // Wait To Play
                            WaitForLoadThenPlay(instancePtr, parameterPtr);
                        }
                    }
                    else
                    {
                        // Load Sound Path
                        FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                        FMOD.RESULT keyResult = FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
                        if (keyResult != FMOD.RESULT.OK) break;
                        // Load Sound
                        FMOD.RESULT soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                            dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode, ref dialogueSoundInfo.exinfo, out dialogueSound);
                        if (soundResult == FMOD.RESULT.OK)
                        {
                            programmerSoundProperties.sound = dialogueSound.handle;
                            programmerSoundProperties.subsoundIndex = dialogueSoundInfo.subsoundindex;
                            Marshal.StructureToPtr(programmerSoundProperties, parameterPtr, false);
                            // Wait To Play
                            WaitForLoadThenPlay(instancePtr, parameterPtr);
                        }
                    }
                    break;
                }
            case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
                {
                    FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES parameter =
                        (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(
                            parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                    FMOD.Sound 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;
    }

    private static void WaitForLoadThenPlay(IntPtr instancePtr, IntPtr parameterPtr)
        => instance.StartCoroutine(instance.WaitForLoadThenPlayRoutine(instancePtr, parameterPtr));
    private IEnumerator WaitForLoadThenPlayRoutine(IntPtr instancePtr, IntPtr parameterPtr)
    {
        // Get Instance
        FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
        // Grab Sound Reference
        FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES parameter =
            (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(
                parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
        FMOD.Sound sound = new FMOD.Sound(parameter.sound);

        // Wait for Load
        FMOD.OPENSTATE state = FMOD.OPENSTATE.BUFFERING;
        uint percentbuffered;
        bool starving;
        bool diskbusy;
        while (state != FMOD.OPENSTATE.READY)
        {
            yield return null;
            sound.getOpenState(out state, out percentbuffered, out starving, out diskbusy);
        }
        instance.start();
        instance.release();
    }

Solution

  • Here's what I ended up doing. I don't know if it makes sense or not, but it does get rid of the warnings. I really wish they'd fix the documentation. The example script doesn't work properly.

        public static void PlayDialogue(string key, Action callback = null) => instance.StartCoroutine(PlayDialogueRoutine(key, callback));
        public static IEnumerator PlayDialogueRoutine(string key, Action callback = null)
        {
            // Check if Already Playing
            if (instance.activeDialogue.ContainsKey(key))
            {
                Debug.LogError("Tried to play already playing dialogue");
                callback?.Invoke();
                yield break;
            }
    
            // Load Sound Path
            FMOD.Studio.SOUND_INFO dialogueSoundInfo;
            FMOD.RESULT keyResult = FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
            if (keyResult != FMOD.RESULT.OK)
            {
                Debug.LogError("Couldn't find dialogue with key: " + key);
                callback?.Invoke();
                yield break;
            }
    
            // Load Sound
            FMOD.Sound dialogueSound;
            FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL
                | FMOD.MODE.CREATECOMPRESSEDSAMPLE
                | FMOD.MODE.NONBLOCKING;
            FMOD.RESULT soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode,
                ref dialogueSoundInfo.exinfo, out dialogueSound);
            if (soundResult != FMOD.RESULT.OK)
            {
                Debug.LogError("Couldn't load sound: " + key);
                callback?.Invoke();
                yield break;
            }
    
            // Wait to Load
            int maxFrameWait = 120;
            FMOD.OPENSTATE openstate = FMOD.OPENSTATE.BUFFERING;
            uint percentbuffered;
            bool starving;
            bool diskbusy;
            while (openstate != FMOD.OPENSTATE.READY)
            {
                yield return null;
                dialogueSound.getOpenState(out openstate, out percentbuffered, out starving, out diskbusy);
                if (--maxFrameWait <= 0)
                {
                    dialogueSound.release();
                    Debug.LogError("Failed to load dialogue sound " + key);
                    yield break;
                }
            }
    
            // Create Instance 
            FMOD.Studio.EventInstance dialogueInstance = FMODUnity.RuntimeManager.CreateInstance(instance.dialogueEvent);
    
            // Store Reference (and remove in play stopped callback) 
            instance.activeDialogue[key] = dialogueInstance;
    
            // Pin Memory
            FMODDialogueParameterWrapper soundWrapper = new FMODDialogueParameterWrapper(dialogueSound, dialogueSoundInfo, () =>
            {
                instance.activeDialogue.Remove(key);
                callback?.Invoke();
            });
            GCHandle soundWrapperHandle = GCHandle.Alloc(soundWrapper, GCHandleType.Pinned);
            dialogueInstance.setUserData(GCHandle.ToIntPtr(soundWrapperHandle));
            // Set Callback
            dialogueInstance.setCallback(instance.dialogueCallback,
                FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND
                | FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND
                | FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED
                | FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED);
    
            // Play One Shot
            dialogueInstance.start();
            dialogueInstance.release();
        }
    
        [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
        static FMOD.RESULT DialogueCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
        {
            // Get Instance
            FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
    
            // Retrieve the user data FMODDialogueParameterWrapper
            IntPtr dialogueParameterWrapperPtr;
            FMOD.RESULT result = instance.getUserData(out dialogueParameterWrapperPtr);
            if (result != FMOD.RESULT.OK)
            {
                Debug.LogError("Failed to fetch user data for dialogue callback: " + result);
            }
            else if (dialogueParameterWrapperPtr != IntPtr.Zero)
            {
                GCHandle dialogueParameterWrapperHandle = GCHandle.FromIntPtr(dialogueParameterWrapperPtr);
                FMODDialogueParameterWrapper dialogueParameterWrapper =
                    (FMODDialogueParameterWrapper)dialogueParameterWrapperHandle.Target;
                switch (type)
                {
                    case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                        {
                            FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES programmerSoundProperties =
                                (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(
                                    parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                            programmerSoundProperties.sound = dialogueParameterWrapper.sound.handle;
                            programmerSoundProperties.subsoundIndex = dialogueParameterWrapper.soundInfo.subsoundindex;
                            Marshal.StructureToPtr(programmerSoundProperties, parameterPtr, false);
                            break;
                        }
                    case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
                        {
                            FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES parameter =
                                (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(
                                    parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                            FMOD.Sound 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
                            Debug.Log("Freeing");
                            dialogueParameterWrapperHandle.Free();
                            break;
                        }
                    case FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED:
                        {
                            dialogueParameterWrapper.onStopCallback?.Invoke();
                            break;
                        }
                }
            }
            return FMOD.RESULT.OK;
        }
    
            [StructLayout(LayoutKind.Sequential)]
    class FMODDialogueParameterWrapper
    {
        public FMOD.Sound sound;
        public FMOD.Studio.SOUND_INFO soundInfo;
        public Action onStopCallback;
    
        public FMODDialogueParameterWrapper(FMOD.Sound sound, FMOD.Studio.SOUND_INFO soundInfo, Action onStopCallback)
        {
            this.sound = sound;
            this.soundInfo = soundInfo;
            this.onStopCallback = onStopCallback;
        }
    }