[Resolved] When do I release event instances?

Hello!

What is the proper way to manage the lifecycle of audio events? I am integrating FMOD Studio with Cocos2D-x, and I get the following failed assertion:

When I try to clean up resources used by the event instance and audio bank.

void stopAudio(){
    eventInstance.stop(); //error here
    eventInstance.release(); // -- or here, when previous line is commented
    bank.unload(); // -- or here
}

Initialization happens as follows, given I have an initialized FMOD System:

void startAudio(const char *bankPath, const char *eventGUID, FMOD::Studio::Bank* bank,FMOD::Studio::EventInstance* eventInstance){
    
    //Load Bank from bankPath
    pSystem.loadBank(bankPath, bank);
    
    //GUID->ID
    FMOD::Studio::ID eventID = {0};
    FMOD::Studio::parseID(eventGUID, &eventID);
    
    //Load Event Description
    FMOD::Studio::EventDescription eventDescription;
    pSystem.getEvent(&eventID, FMOD_STUDIO_LOAD_BEGIN_NOW, &eventDescription);
    
    //Load Event Instance
    eventDescription.createInstance(eventInstance);

    //Start Playing
    eventInstance.start();

    /* I set some values of parameters and I succesfully hear sounds!! */
}

(start and stop audio are used in “onEnter” and “onExit” of a layer-node on screen)

From my understanding, if you no longer want to update parameters on them, you can release instances immediately after you play them, and the effectively become ‘one offs’ and are managed by studio.

If it won’t let you release them, it’s because the event is invalid to be a ‘one off’ event, e.g. it’s looping or may never stop due to the setup. In that case you must manage the lifetime of the event. If a looping event was hooked up to something that was expected to be a one off, I would display an error somehow.

AFAIK it should always let you call stop() and then release(). They fixed a bug I was having with this a while back, so you should verify you’re using the current version (1.1.3) but there could be more bugs.

If you release() before stop(), it releases on stop. That’s what I found in one of the examples.

I noticed that if I keep starting an event, more an more channels get used, until the max of 32 is reached and errors are generated…! So it does not restart.

How do I know when an event is single-shot or multiple-concurrent-shots?
(Although if I listen, it seems as if there is only a single shot… whereas the used channels increase…)

I assume this is not a direct copy and paste of your code? EventInstance::stop requires a mode parameter. Also, please make sure you’re checking the return value of all your FMOD API calls.

That being said, the most likely cause of a failed assertion in stop() is calling it on an invalid EventInstance handle, probably one that has already been released. You can check if a handle is valid by calling isValid().

Also, what clevijoki says about stop and release is correct - thanks for the explanation!

This is true, but if the event is not going to stop by itself when you call release(), the release() call will fail.

This sounds like a bug. Does the event in question have sounds on the timeline, or on a parameter, or both? How frequently are you calling start on the event?

Indeed, it was pseudo-code closely following actual code.
Even stranger still: sometimes only 2 out of 9 channels start playing!
Would you like me to send you the FMOD Studio project?

This is my FMOD setup:
Add 9 channels of audio tracks on the timeline (3x bass, 3x drum and 3x piano).

Set a tempo marker at t=0.
The track has three sections: A, B and C.
Setup 3 levels of transition markers & regions - jump only on each measure.
First level jumps conditionally to section A, Second layer level conditionally to section B, etc.

Condition is tied to the first game parameter, allowing you to switch (on the measure) to section A,B,C at will.
There three other parameters that automate volume of the tracks (mute/unmute/crossfade), allowing you to mute/unmute/crossfade different versions of each instrument.

So I basically I have single-shot sounds (i.e. you see the wave-form, moving timeline does not trigger new sound) that are in infinite loop using layered transition markers and levels.

This is my game setup:
When entering the scene: bank.load(); eventInstance.start();
When tapping the screen for the first time: eventInstance.start()
When leaving the scene: eventInstance.stop(); eventinstance.release(); bank.unload;

Steps to get bug:
[list]
[]Enter Scene [silent], [/:m:3ata5t6r]
[]Tap [9 channels play], [/:m:3ata5t6r]
[]Leave Scene [silent], [/:m:3ata5t6r]
[]Enter Scene [2 channels play]; [/:m:3ata5t6r]
[]Tap [11 channels play];[/:m:3ata5t6r]
[] Leave [silent];[/:m:3ata5t6r][/list:u:3ata5t6r]

At other times, it stays silent upon entering the scene for the second time, but the used channels add with 9.

I don’t want to start on first tap; but if I start when the audiobank hasn’t finished loading, the event will not start. I have to refactor code to start the event when the FMOD Callback indicates loading has finished.

I also don’t understand why FMOD plays 2 channels, shouldn’t the bank be unloaded? Or is there some secret caching?

That does sound quite broken. Could you try modifying the simple_event example to reproduce this behaviour? It will be much simpler to fix this issue if we can reproduce it in isolation.

I tried isolating the error in the simple event example, and I discovered I forgot to call system.update().

Pretty stupid that I forgot to call system.update() when integrating FMOD in my project, but then again, how was I supposed to know? I embedded FMOD Studio by reverse engineering the examples. A “getting started guide” on the necessary steps to integrate FMOD Studio with your own software would avoid the problem for future users of FMOD Studio. This is not to complain, but just a suggestion to further improve FMOD products and services. I must say I am very happy with my choice for FMOD, because the support is excellent! Thanks!

I have confirmed that NOT calling system.update() causes:
[list]
[] does not automatically start playback when instance has completed loading[/:m:lke27ybp]
[] does not update timeline position[/:m:lke27ybp]
[] keeps incrementing channels when (re)loading an event instance.[/:m:lke27ybp][/list:u:lke27ybp]

I haven’t confirmed that a few channels of a multi-channel event playback instantly on loading for the second time, as the Simple Event example only has a single-channel sound, but I expect this is solved too.

Copy-paste this code in simple_event.cpp and run to reproduce behavior:

#include "fmod_studio.hpp"
#include "fmod.hpp"
#include "common.h"
#include <stdio.h>

FMOD::Studio::System pSystem;
FMOD::Studio::Bank masterBank;
FMOD::Studio::EventInstance eventInstance;

bool started = false;

void startAudio() {
    if(started) return;
    started = true;
    
    //Load Bank
    ERRCHECK( pSystem.loadBank(Common_MediaPath("Master Bank.bank"), &masterBank) );
    
    //Load event
    FMOD::Studio::ID loopingAmbienceID = {0};
    ERRCHECK( FMOD::Studio::parseID("{be4d0587-d91a-42e3-8286-2590da98246e}", &loopingAmbienceID) );
    
    FMOD::Studio::EventDescription loopingAmbienceDescription;
    ERRCHECK( pSystem.getEvent(&loopingAmbienceID, FMOD_STUDIO_LOAD_BEGIN_NOW, &loopingAmbienceDescription) );
    
    ERRCHECK( loopingAmbienceDescription.createInstance(&eventInstance) );
    
    //Start event
    ERRCHECK( eventInstance.start());
}

void stopAudio() {
    started = false;
    if(eventInstance.isValid()){
        //stop
        ERRCHECK(eventInstance.stop(FMOD_STUDIO_STOP_IMMEDIATE));
        //release
        ERRCHECK( eventInstance.release() );
    }
    if(masterBank.isValid()){
        //unload
        ERRCHECK( masterBank.unload() );
    }
}

const char* drawStatus() {
    int position = 0;
    float volume = 0.0;
    bool paused = false;
    const char* playbackeStateStr;
    const char* loadingStateStr;
    
    if(eventInstance.isValid()) {
        eventInstance.getTimelinePosition(&position);
        eventInstance.getVolume(&volume);
        eventInstance.getPaused(&paused);
    
        FMOD_STUDIO_PLAYBACK_STATE playbackState;
        eventInstance.getPlaybackState(&playbackState);
        switch (playbackState) {
            case FMOD_STUDIO_PLAYBACK_IDLE:
                playbackeStateStr = "IDLE"; break;
            case FMOD_STUDIO_PLAYBACK_PLAYING:
                playbackeStateStr = "PLAYING"; break;
            case FMOD_STUDIO_PLAYBACK_STOPPED:
                playbackeStateStr = "STOPPED"; break;
            case FMOD_STUDIO_PLAYBACK_SUSTAINING:
                playbackeStateStr = "SUSTAINING"; break;
        }
    
        FMOD_STUDIO_LOADING_STATE loadingState;
        eventInstance.getLoadingState(&loadingState);
        switch (loadingState) {
            case FMOD_STUDIO_LOADING_STATE_LOADED:
                loadingStateStr = "LOADED"; break;
            case FMOD_STUDIO_LOADING_STATE_LOADING:
                loadingStateStr = "LOADING"; break;
            case FMOD_STUDIO_LOADING_STATE_UNLOADED:
                loadingStateStr = "UNLOADED"; break;
            case FMOD_STUDIO_LOADING_STATE_UNLOADING:
                loadingStateStr = "UNLOADING"; break;
        }
    } else {
        playbackeStateStr = "...";
        loadingStateStr = "...";
    }
    
    FMOD::System* system;
    pSystem.getLowLevelSystem(&system);
    int channels;
    system->getChannelsPlaying(&channels);
    
    Common_Draw("Loading: %s; Pos: %i; Vol: %f; Paused: %s; Playback: %s; Used Channels: %i",loadingStateStr,position,volume,(paused?"Yes":"No"),playbackeStateStr,channels);

}

int FMOD_Main()
{
    // Init FMOD::STUDIO::SYSTEM
    void *extraDriverData = 0;
    Common_Init(&extraDriverData);    
    ERRCHECK( FMOD::Studio::System::create(&pSystem) );
    ERRCHECK( pSystem.initialize(32, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, extraDriverData) );

    do
    {
        Common_Update();
        
        if (Common_BtnPress(BTN_ACTION1))
        {     
            startAudio();
        }
    
        if (Common_BtnPress(BTN_ACTION2))
        {   if(eventInstance.isValid())
                eventInstance.start();
        }

        if (Common_BtnPress(BTN_ACTION3))
        {
            stopAudio();
        }

        
        // Get number of active channels
        FMOD::System* system;
        pSystem.getLowLevelSystem(&system);
        int channels;
        system->getChannelsPlaying(&channels);
        

        /*******************************************************************
         CAUSE OF THE BUG:
         
         Not calling system.update()
         - does automatically start playback when instance has loaded
         - does not update position
         - keeps incrementing channels on startAudio()
         
         With my audio-bank, which has multiple channels "unloading->loading", 
         caused a few channels to be played on loading..
         *******************************************************************/
        //ERRCHECK( pSystem.update() );

        Common_Draw("==================================================");
        Common_Draw("Event loading/unloading doesn't clear channels bug");
        Common_Draw("==================================================");
        Common_Draw("");
        Common_Draw("Press %s to load & start audio", Common_BtnStr(BTN_ACTION1));
        Common_Draw("Press %s to start audio", Common_BtnStr(BTN_ACTION2));
        Common_Draw("Press %s to stop & unload audio", Common_BtnStr(BTN_ACTION3));
        Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
        Common_Draw("", Common_BtnStr(BTN_QUIT));
        drawStatus();

        Common_Sleep(50);
    } while (!Common_BtnPress(BTN_QUIT));

    ERRCHECK( pSystem.release() );

    Common_Close();

    return 0;
}
1 Like

Ah right, not calling System::update() explains it - thanks for letting us know!

Thanks for the suggestion about the getting started guide. This is something we have been planning to provide, but just haven’t had time for so far. It is on our todo list, and we’ll get to it as soon as we can.