Entrytag VO lines with FMOD

Announcements, support questions, and discussion for the Dialogue System.
User avatar
Tony Li
Posts: 22131
Joined: Thu Jul 18, 2013 1:27 pm

Re: Entrytag VO lines with FMOD

Post by Tony Li »

Your FMODWait() command should wait until it's done. So you can do something like this (for example):

Code: Select all

AnimatorPlay(MouthAnimation);
FMODWait(entrytag)->Message(Done);
required AnimatorPlay(MouthClosed)
fkkcloud
Posts: 298
Joined: Mon Oct 05, 2020 6:00 am

Re: Entrytag VO lines with FMOD

Post by fkkcloud »

I updated the code as there were a memory leak on FMOD code side.

Hopefully its useful for someone who uses both Dialogue System and FMOD Programmer Sound :)

Code: Select all

  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);
                        
            if (voiceData.eventCallbackType != EVENT_CALLBACK_TYPE.SOUND_STOPPED)
            {
                while (IsPlaying())
                {
                    yield return null;
                }
            }
                        
            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;
        }

    }
User avatar
Tony Li
Posts: 22131
Joined: Thu Jul 18, 2013 1:27 pm

Re: Entrytag VO lines with FMOD

Post by Tony Li »

Thank you!

I see the script has a "TODO" item. Is this something that should happen outside of FMODWait()? For example, maybe change the bank only when the player changes languages in a settings/options screen.
Post Reply