Event with Programmer Instrument ending early?

We are on FMOD Studio 2.01.07.

We use an event with a single programmer instrument for playing voice overs. This has worked for years, but after upgrading to FMOD Studio 2.01.07, we are very rarely having the last ~0.5 seconds of the OGG file we supply to the programmer instrument cut off. We get a playback done event too early, and the audio cuts out.

Has anyone else seen this in this version? Did this get fixed?

Can you post a screenshot of your programmer instrument event? Can you confirm the programmer instrument is set to async?

The event itself looks fine. Can you capture a profiler session of this issue happening and see if there are any stop or release type calls being made to these events before they’re fully finished?

As far as I can tell, these events are not being stopped or released while still playing. We’re getting a PLAYBACK DONE event before the file is done playing, which is not something we saw in previous versions of FMOD Studio.

That does sound very strange. Are you able to send the recorded session and the code you are using to use the programmer instruments to me? You can DM me if you worry about privacy.

I would need to rerecord them, but this bug is very difficult to reproduce (Only happens a few times a day).

Another question I have re: Programmer Instruments. The length of the instrument seems to matter even though we check async. Is this expected?

That isn’t to be expected. If an instrument is async it will simply be triggered for as long as it is playing an audio file. In my tests this appears to be true in 2.01. Is this something you are experiencing in the FMOD Studio project or only in-game? Is this something you could also send a profiler session for?

Working on getting a profiler of the Programmer Instrument weirdness. In our experience, when using a programmer instrument, you have to set the length of the instrument in the event to smaller than any Sound we might put in the instrument, or else it will continue playing it out.

That is very strange. That sounds like the instrument is looping but from the screenshot you have shown it is definitely not looping. If you are able to include the code for how this programmer instrument event is called when you get the profiler session recorded, that would be very helpful.

#include "FMODAudioEventFromFile.h"

// Callback from Studio - Remember these callbacks will occur in the Studio update thread, NOT the game thread.
FMOD_RESULT F_CALLBACK FMODAudioEventFromFileCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* e, void *parameters)
{
    FMOD::Studio::EventInstance* pFMODEvent = reinterpret_cast<FMOD::Studio::EventInstance*>(e);

    FMODAudioEventFromFile * pEvent;
    pFMODEvent->getUserData(reinterpret_cast<void**>(&pEvent));

    if (!pEvent)
    {
        return FMOD_OK;
    }

    return pEvent->handleEventCallback(type, parameters);
}

FMOD_RESULT F_CALLBACK FMODAudioEventFromFileCallbackNULL(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* e, void *parameters)
{
    return FMOD_OK;
}

FMODAudioEventFromFile::FMODAudioEventFromFile(std::string dummyEventName, std::string audioFilePath, FMOD::Studio::System * pSystem)
    : mpSystem(pSystem)
    , mpEventDescription(NULL)
    , mpEventInstance(NULL)
    , mStopCalledManually(false)
{
    mDummyEventName = "event:/" + dummyEventName;
    mAudioFilePath = audioFilePath;
}

FMODAudioEventFromFile::~FMODAudioEventFromFile()
{
    if(mpEventInstance)
    { 
        mpEventInstance->setCallback(FMODAudioEventFromFileCallbackNULL, FMOD_STUDIO_EVENT_CALLBACK_ALL);
        mpEventInstance->setUserData(NULL);
        mpEventInstance->stop(FMOD_STUDIO_STOP_IMMEDIATE);
        mpEventInstance->release();
        mpEventInstance = NULL;
    }
}

void FMODAudioEventFromFile::load(SuccessCallback success)
{
    FMOD_RESULT r;

    r = mpSystem->getEvent(mDummyEventName.c_str(), &mpEventDescription);

    if (r != FMOD_OK)
    {
        success(false);
        return;
    }

    r = mpEventDescription->createInstance(&mpEventInstance);

    if (r != FMOD_OK)
    {
        mpEventDescription = NULL;
        success(false);
        return;
    }

    mpEventInstance->setUserData(static_cast<void*>(this));
    mpEventInstance->setCallback(FMODAudioEventFromFileCallback,
        FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND |
        FMOD_STUDIO_EVENT_CALLBACK_STOPPED
    );

    success(true);
}

void FMODAudioEventFromFile::unload(SuccessCallback success)
{
    // TODO: Actually set this, and check this on all the calls below. It doesn't really matter,
    // but it would be cleaner.
    FMOD_RESULT r = FMOD_OK;

    if (mpEventInstance)
    {
        mpEventInstance->setCallback(FMODAudioEventFromFileCallbackNULL, FMOD_STUDIO_EVENT_CALLBACK_ALL);
        mpEventInstance->setUserData(NULL);
        mpEventInstance->stop(FMOD_STUDIO_STOP_IMMEDIATE);
        mpEventInstance->release();
        mpEventInstance = NULL;
    }
    success(r == FMOD_OK);
}

void FMODAudioEventFromFile::play()
{
    mStopCalledManually = false;
    mpEventInstance->start();
}

void FMODAudioEventFromFile::stop()
{
    mStopCalledManually = true;
    mpEventInstance->stop(FMOD_STUDIO_STOP_ALLOWFADEOUT);
}

void FMODAudioEventFromFile::setPaused(bool isPaused)
{
    mpEventInstance->setPaused(isPaused);
}

bool FMODAudioEventFromFile::getPaused()
{
    bool isPaused;

    mpEventInstance->getPaused(&isPaused);
    return isPaused;
}

void FMODAudioEventFromFile::triggerCue()
{
    mpEventInstance->triggerCue();
}

void FMODAudioEventFromFile::setVolume(float volume)
{
    mpEventInstance->setVolume(volume);
}

float FMODAudioEventFromFile::getVolume()
{
    float vol;

    mpEventInstance->getVolume(&vol);

    return vol;
}

void FMODAudioEventFromFile::setPosition(int position)
{
    mpEventInstance->setTimelinePosition(position);
}

int FMODAudioEventFromFile::getPosition()
{
    int pos;

    mpEventInstance->getTimelinePosition(&pos);

    return pos;
}

void FMODAudioEventFromFile::setParameterValue(const std::string & name, float val)
{
    mpEventInstance->setParameterByName(name.c_str(), val);
}

float FMODAudioEventFromFile::getParameterValue(const std::string & name)
{
    float val;
    float finalVal;

    mpEventInstance->getParameterByName(name.c_str(), &val, &finalVal);

    return finalVal;
}

IAudioEvent::PlaybackState FMODAudioEventFromFile::getPlaybackState()
{
    FMOD_STUDIO_PLAYBACK_STATE state;

    mpEventInstance->getPlaybackState(&state);

    switch (state)
    {
    case FMOD_STUDIO_PLAYBACK_PLAYING:
        return IAudioEvent::PlaybackState::PLAYING;
    case FMOD_STUDIO_PLAYBACK_SUSTAINING:
        return IAudioEvent::PlaybackState::SUSTAINING;
    case FMOD_STUDIO_PLAYBACK_STOPPED:
        return IAudioEvent::PlaybackState::STOPPED;
    case FMOD_STUDIO_PLAYBACK_STARTING:
        return IAudioEvent::PlaybackState::STARTING;
    case FMOD_STUDIO_PLAYBACK_STOPPING:
        return IAudioEvent::PlaybackState::STOPPING;
    default:
        return IAudioEvent::PlaybackState::INVALID;
    };
}

FMOD_RESULT FMODAudioEventFromFile::handleEventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, void* parameters)
{
    FMOD_RESULT res;
    
    FMOD::System * pLowLevelSystem;
    mpSystem->getCoreSystem(&pLowLevelSystem);

    if (type == FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND)
    {
        FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props = (FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters;

        FMOD::Sound* sound = NULL;
        res = pLowLevelSystem->createStream(mAudioFilePath.c_str(), FMOD_NONBLOCKING, NULL, &sound);
        
        if (res != FMOD_OK)
        {
            return res;
        }

        // Make the host audio VERY high priority.
        sound->setDefaults(48000, 0);

        props->sound = (FMOD_SOUND*)sound;
    }
    else if (type == FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND)
    {
        FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props = (FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters;

        FMOD::Sound* sound = (FMOD::Sound*)props->sound;
        
        res = sound->release();

        if (res != FMOD_OK)
        {
            return res;
        }
    }
    else if (type == FMOD_STUDIO_EVENT_CALLBACK_STOPPED)
    {
        CALL_DELEGATE(PlaybackDone, mStopCalledManually);
    }

    return FMOD_OK;
}

Every time we play an event with a programmer instrument, it goes through this file which handles loading the dummy event, and injecting the audio file we want (in ogg format).

Here’s what the fmod log has for around this time:

[Platform] ExternalInterface => InitializeNativeOverride
[FMOD] filename = D:\Steam\steamapps\common\The Jackbox Party Pack 8\games/JobGame/content/en/ApplyYourselfInterviewQuestion/77950/question.ogg : mode 00010080

[FMOD] FMOD_NONBLOCKING specified. Putting into queue to be opened asynchronously!

[FMOD] setdata soundi = 000000001A5E1008 : node = 000000000602EA30

[FMOD] add node to async list : head = 00000000122CCA88. list count = 0

[FMOD] Starting Asynchronous operation on sound 000000001A5E1008

[FMOD] Create name=‘D:\Steam\steamapps\common\The Jackbox Party Pack 8\games/JobGame/content/en/ApplyYourselfInterviewQuestion/77950/question.ogg’, mode=0x00010080

[FMOD] Create name=’’, mode=0x02000202

[FMOD] exinfo->cbsize = 224

[FMOD] exinfo->length = 3489440

[FMOD] exinfo->fileoffset = 161024

[FMOD] exinfo->numsubsounds = 1

[FMOD] exinfo->inclusionlist = 000000000672F628

[FMOD] exinfo->inclusionlistnum = 1

[FMOD] exinfo->suggestedsoundtype = 5

[FMOD] exinfo->useropen = 00007FFC1244FFC0

[FMOD] exinfo->userclose = 00007FFC1244FF90

[FMOD] exinfo->userread = 00007FFC12450090

[FMOD] exinfo->userseek = 00007FFC12450150

[FMOD] exinfo->fileuserdata = 000000001F989488

[FMOD] exinfo->initialseekpostype = 1

[FMOD] register codec pool for pool type 5

[FMOD] Sample 0/1: name=‘qu_text_on’, format=5, channels=1, frequency=48000, lengthbytes=2432, lengthpcm=31329, pcmblocksize=0, loopstart=0, loopend=0, mode=0x00000000, channelmask=0x00000000, channelorder=0, peakvolume=0.367310.

[FMOD] Stream: name=’(null)’, format=2, channels=1, frequency=48000, lengthbytes=141180, lengthpcm=338422, pcmblocksize=0, loopstart=0, loopend=0, mode=0x00000000, channelmask=0x00000000, channelorder=0, peakvolume=0.000000.

[FMOD] Finished Asynchronous operation on sound 000000001A5E1008

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

[FMOD] JOB10 (000000001A659AC8)

[FMOD] JOB10 (000000001FFA3C78)

[FMOD] JOB10 (000000001A5E0798)

[FMOD] question.ogg (000000001A5E1008)

[FMOD] (null) (000000001FFA42F8)

I’m going to hand this over to our dev team to investigate, but in the meantime, could you try to change the two mpEventInstance->stop(FMOD_STUDIO_STOP_IMMEDIATE); to use FMOD_STUDIO_STOP_ALLOWFADEOUT and see if that helps?

Sorry for the delay. Our team has been able to replicate this issue of programmer instruments cutting off early and a fix should be in the next release of FMOD Studio.