Loading delay exceeded warning when using Programmer Instrument and Audio Table

Hi!

I’m currently working on a project with several thousand voice lines, so the logical conclusion was to use the Programmer Instrument in combination with the Audio Table. Using the Unity Integration documentation I was able to get it to work and everything seems to be in order, but I keep getting the following error message:

[FMOD] SoundSourceInstrumentInstance::startChannelIfReady : Loading delay exceeded, sound may play at incorrect time

So far I’ve tried the following:

  • Preload the bank that contains the event containing the programmer instrument
  • Preload the bank containing the audiotable
  • Use the createStream function instead of the createSound function

None of these things have had any impact. I’m hoping someone can give me the golden tip that I need.

NB: I don’t actually hear any significant delay, but before I implement this on our development branch I would like to make sure that nothing is wrong and/or avoid (superfluous) warnings.

Thanks!
Ruben

The “Loading delay exceeded” message indicates that the sampledata was not finished loading when you tried to start the event instance.

Loading a bank just loads the metadata for the events in that bank, not the sampledata. To load the sampledata of an event prior to playing an instance of the event, call Studio::EventDescription::loadSampleData. It is best to do this a short period of time before you create an instance of the event, so that the sampledata has time to load.

It is also possible to load all the sampledata for an entire bank by calling Studio::Bank::loadSampleData.

If you call Studio::EventDescription::createInstance without having loaded the sampledata ahead of time, FMOD will automatically load the required sampledata required by that event instance.

In all cases, if you do not allow enough time for the sampledata to load before starting an instance of an event that uses that sampledata, you will see the “Loading delay exceeded” message you described.

You can read more about sampledata loading in the Sample Data Loading section of the Studio API Guide chapter of the FMOD API User Manual.

Hi Joseph, thanks for your answer!

I’ve tried loading the event containing the programmer event using loadSampleData, but that did not amount to anything. I am assuming that is because the event itself does not contain any sampledata until I put it there during the callback.

Loading the bank containing the Audio Table and passing true for the loadSamples parameter did not seem to do the trick either. For as far as I can see, only sampledata that has been implemented in an event can be preloaded, so it would make sense that the separate files in the Audio Table would not be preloaded in that case.

Is there a way to preload sampledata of a file referenced in an Audio Table?

Oh! You’re using programmer instruments. Yes, loading the event or bank’s sampledata won’t help in that case, as the sampledata isn’t specifically associated with any event.

The only way to preload the sampledata of assets in a bank’s audio table is to create them as sounds via the Core API. You can call System::createSound to load an individual asset.

1 Like

Ah yes perfect, that seems to make it work!

Just to make sure I am using this correctly, I’m currently following these steps:

  • load the asset by calling createSound
  • wait for the getOpenState to return READY
  • use the programmer instrument callback to pass the sound.handle IntPtr value and subindex

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

// Get the sound info object
soundInfoHandle = GCHandle.FromIntPtr(soundInfoPtr);
soundInfo = soundInfoHandle.Target as SoundInfo;

var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

parameter.sound = soundInfo.SoundHandle;
parameter.subsoundIndex = soundInfo.SubIndex;
Marshal.StructureToPtr(parameter, parameterPtr, false);

1 Like

That looks reasonable, assuming you’re storing the sound object created by createSound in the event instance’s user data - and if it’s working, you must be.

1 Like

Hi @joseph , hi @rubenbergshoeff

I am having the same issue and tried to solve it based on this conversation. But I am confused: Is SoundInfo in the code quoted by @rubenbergshoeff a custom data struct that contains the Sound and SOUND_INFO? We need to store both in the user data, right? I tried that and it seems to work, at least…

Can we not tell the system somehow that we don’t care about timing and it should suppress the warning?

Should the sample code here maybe be updated? It will always print the warning.
https://www.fmod.com/docs/2.02/unity/examples-programmer-sounds.html

Won’t everyone who is working with Audio Tables have to do this, so it should be integrated into the plugin?

Assuming you’re using Unity, you can suppress warnings by setting the logging level to “Error” or “None.”

Is this the only way? I would love to be able to suppress only this warning in particular. Also, there can be sound where timing is critical, and the warning is needed. An optional logging-level parameter would be ideal.
I follow a strict no-warnings policy and would rather to things properly than just deactivate warnings all together.

I’m just bringing this up, because I find it confusing that the suggested way of playing back a sound from an audio table is so involved. The warning tells me that the correct way is to preload the sample like discussed here and that requires quite a lot of boilerplate that needs to be maintained.

I think that the provided example code will always trigger warnings is a strong signal that maintenance is an issue. I know you are working on improving on this, so I just want to voice my interest in this :slight_smile:

Thank you for your support!

Yes, that’s the only way. Suppressing specific warnings often leads to people habitually suppressing and then not remembering that they have done so, which we would prefer to avoid.

Thanks for the feedback. We’ll take that on board as we continue to improve FMOD in future.

This is the top hit on Google for this problem and it took me a bit to figure out what was meant in the accepted answer.

Here’s the full, commented script that appears to work for me. If I’m doing something silly, please let me know.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using FMOD;
using FMOD.Studio;
using FMODUnity;

public class FModProgrammerSoundPlayer : IDisposable
{
    private EVENT_CALLBACK dialogueCallback;
    private Dictionary<string, EventInstance> _loadedInstances = new();

    public struct SoundAndInfo
    {
        public Sound Sound;
        public SOUND_INFO SoundInfo;
    }

    public FModProgrammerSoundPlayer()
    {
        // Need to make sure the callback doesn't get garbage collected by keeping a reference.
        dialogueCallback = DialogueEventCallback;
    }

    public void LoadSound(EventReference eventReference, string key)
    {
        if (_loadedInstances.ContainsKey(key)) return;

        // First get the sound info for this key.
        SOUND_INFO dialogueSoundInfo;
        var keyResult = RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
        if (keyResult != RESULT.OK)
        {
            UnityEngine.Debug.LogError($"Error when loading {key}: {keyResult}");
            return;
        }

        MODE soundMode =
            MODE.LOOP_NORMAL | // Was set like this in example code. Perhaps should be changed to FMOD_LOOP_OFF
            MODE.CREATECOMPRESSEDSAMPLE | // This loads the sound compressed into memory (uncompressed if it isn't compressed). FMOD_CREATESTREAM could load directly from file. // FMOD_CREATESAMPLE decompresses on load, optimized for playback
            MODE.NONBLOCKING; // Don't block the main thread. 

        // Create the sound with the createSound method.
        Sound dialogueSound;
        var soundResult = RuntimeManager.CoreSystem.createSound(
            dialogueSoundInfo.name_or_data,
            soundMode | dialogueSoundInfo.mode,
            ref dialogueSoundInfo.exinfo,
            out dialogueSound);

        if (soundResult != RESULT.OK)
        {
            UnityEngine.Debug.LogError($"Trying to load sound with key {key} returned error {soundResult}");
            return;
        }

        // We'll create a struct to hold both the Sound itself and SoundInfo data.
        var soundAndInfo = new SoundAndInfo()
        {
            Sound = dialogueSound,
            SoundInfo = dialogueSoundInfo
        };
        // Pin the struct to memory with GCHandle.Alloc
        GCHandle soundInfoHandle = GCHandle.Alloc(soundAndInfo, GCHandleType.Pinned);

        // Finally create an instance for the Event and set the userdata to point to the struct we just pinned.
        var dialogueInstance = RuntimeManager.CreateInstance(eventReference);
        dialogueInstance.setUserData(GCHandle.ToIntPtr(soundInfoHandle));
        // Set the callback.
        dialogueInstance.setCallback(dialogueCallback);
        // Store the EventInstance for when we're going to play the file.
        _loadedInstances.Add(key, dialogueInstance);
    }

    public void PlayDialogue(string key)
    {
        // When we want to play, we'll just find the event instance again and start it.
        var inst = _loadedInstances[key];
        inst.start();
    }


    public void Dispose()
    {
        // Don't forget to release the saved instances. The sounds themselves will be destroyed with the callback.
        foreach (var inst in _loadedInstances.Values)
        {
            inst.release();
        }

        _loadedInstances.Clear();
    }

    [AOT.MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
    static RESULT DialogueEventCallback(EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
        //UnityEngine.Debug.Log("Dialogue Callback with type " + type);
        try
        {
            // Get the EventInstance from the provided pointer
            var instance = new EventInstance(instancePtr);
            instance.getUserData(out IntPtr soundInfoPtr);
            // Get the pointer to the SoundAndInfo struct from the instance.
            var soundInfoHandle = GCHandle.FromIntPtr(soundInfoPtr);
            // Dereference it.
            var soundInfo = (SoundAndInfo)soundInfoHandle.Target;
            switch (type)
            {
                // This is called before playing the programmer sound.
                // FMOD expects us to fill the parameterPtr with the sound and soundInfo during this call.
                case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                {
                    // Get the struct from the callback via the pointer.
                    var parameter =
                        (PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,
                            typeof(PROGRAMMER_SOUND_PROPERTIES));
                    // Put in the sound and soundInfo
                    parameter.sound = soundInfo.Sound.handle;
                    parameter.subsoundIndex = soundInfo.SoundInfo.subsoundindex;
                    // Put the modified data back where we found it.
                    Marshal.StructureToPtr(parameter, parameterPtr, false);
                    break;
                }
                // When the event is destroyed
                case EVENT_CALLBACK_TYPE.DESTROYED:
                {
                    // Release the sound
                    soundInfo.Sound.release();
                    // Free the handle to our info struct
                    soundInfoHandle.Free();
                    break;
                }
            }
        }
        catch (Exception e) // Getting exceptions in this callback WILL hang Unity, so we'll use a try/catch block
        {
            if (e is ArgumentException || e is InvalidOperationException)
            {
                return RESULT.OK;
            }

            throw e;
        }

        return RESULT.OK;
    }
}
1 Like

Hello! We are having the exact same issue, and it’s quite a confusing one. Has there been any improvements on the warnings since?

As of the time of writing (March of 2024), we have not altered the warnings surrounding playing programmer sounds without first loading the required sampledata.