Stopping an event after fade completion?

I’m currently working on trying to get our game to transition more smoothly between music, rather than abruptly stopping and then starting the next track. I’ve been able to get the current track fading out nicely using fade points, but the issue I’m having is that I’m not sure exactly when to actually call stop & delete the old event and start the next queued one. The event remains in the “playing” playback state even when the volume hits 0, which I would expect, but is there any way of knowing that the fade has completed?

Snippet from fading out the music via the fade points:

            ulong dspClock;
            ulong parentClock;
            result = eventSet.FMODEvent.getChannelGroup(out group);
            result = group.getDSPClock(out dspClock, out parentClock);

            FMOD.System system;
            FMOD.SPEAKERMODE mode;
            int numSpeakers;
            studioSystem.getLowLevelSystem(out system);
            system.getSoftwareFormat(out _SampleRate, out mode, out numSpeakers);

            const float fadeTime = 1.0f;
            ulong fadedTime = dspClock + Convert.ToUInt64(fadeTime * _SampleRate);

            group.addFadePoint(dspClock, fadeTime);
            group.addFadePoint(fadedTime, 0.0f);

After an event gets a volume of 0 it will go “virtual”, which should trigger a REAL_TO_VIRTUAL callback, and from there you should be able to stop and release the event.
We have a C# example that shows how to set a callback on an event if you are unsure how to go about doing that. You will need to set the VOL0_BECOMES_VIRTUAL flag when initializing the Studio system.
EDIT: On a side note, you shouldn’t need to set fade outs if using FMOD Studio API. Instead you can add an AHDSR to the event master track volume, that way when the event is stopped the release time will fade out when the track automatically.

Thanks for the response! I’ll pass the AHDSR information along to our audio guy to take a look there and we can hopefully go that route. Judging from your response and the documentation, the fadeout should happen automatically when calling stop on the event instance as long as STOP_MODE.IMMEDIATE is not passed, correct?

As far as the REAL_TO_VIRTUAL, I don’t seem to be getting that callback. I’m passing the flag as follows during initialization:

const int maxChannels = 1000;
studioSystem.initialize( maxChannels, FMOD.Studio.INITFLAGS.SYNCHRONOUS_UPDATE, FMOD.INITFLAGS.VOL0_BECOMES_VIRTUAL, IntPtr.Zero );

And I’m registering for callbacks when I create an event instance as follows:

FMOD.Studio.EventInstance gameEvent;

result = _event.createInstance( out gameEvent );
Debug.Assert( result == FMOD.RESULT.OK );

result = gameEvent.setCallback( _EventCallback, EVENT_CALLBACK_TYPE.ALL );

I’m getting other EVENT_CALLBACK_TYPE just as started, stopped, etc. However, I’m not gettingthe REAL_TO_VIRTUAL one. Is there anything else that needs set besides the initialization flag and the callback on the event instance?

Correct, as long as it has been initialized correctly, which it looks like it has.

That should be triggering the callback, what version of FMOD are you using?

Apologies for reviving this old thread. We’ve been working with the AHDSR solution since this topic, but are running into this issue once again. Since this topic is the first thing to come back when searching Google or Bing for “fmod not getting REAL_TO_VIRTUAL callback” it seems like it’s the best place to keep the information in the hopes of helping someone else as well.

Unfortunately, we are still not getting the REAL_TO_VIRTUAL callback. I just recently updated to version 2.01.23 to see if it helps, but we still seem to be getting most other callbacks except for this one. Still using more or less the same code examples shown earlier in the thread for testing.

@jeff_fmod Can you (or someone else on the dev team) confirm you’re getting the REAL_TO_VIRTUAL callback when using the C# (non-Unity) API?

EDIT: For additional information, I added this check in my update function:

bool isVirtual = false;
				result = eventSet.FMODEvent.isVirtual( out isVirtual );
				if ( isVirtual )
				{
					eventSet.FMODEvent.release();
					_AudioEvents[index] = null;
				}

I added a break point and isVirtual is never true. I’ve tested this both using the FadePoint code in the original post, as well as just by calling the setVolume directly on the event and setting it to 0.0f. So it seems like the issue may not be with the callback itself, but the fact that the event is not being set virtual

Some possibilities I can think of that could prevent the event from being virtualized are:

  • The event or a bus above an event is set to the “Highest” Priority setting, meaning it will never be virtualized.
  • The event is being stopped by something else, instead of silenced. This would give you a FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED callback instead.

The best way to debug this would be to connect the FMOD Profiler to your game and send us a profiler capture to go through. For C#, you will need to initialize the FMOD.Studio.System with the LIVEUPDATE init flag to connect the profiler.

Can you please collect a profiler capture while reproducing this issue in game, then package your profiler session including banks (ticking the “Banks” option when exporting), and upload it to your FMOD Profile?

Sorry for the delay - I was out of office and just saw your reply upon returning.

I’ve uploaded solari_audio.fspackage to my profile. Everything was caputed using 2.01.23. Hopefully I captured everything correctly. I booted up the game, started the recording via th eprofiler, played an event, and then called the fade out code posted above.

Please let me know if there are any problems with the capture, or if anything doesn’t make sense about the way I’m trying to play and fade the audio.

Thanks for uploading the profiler capture. Re-reading this years later I realize I made a mistake when I first answered you 3 years ago, and I didn’t catch it when responding last month, so apologies for the misinformation. FMOD actually uses two virtualization systems:

  • The Core API virtualization system for optimizing away “voices”, which are the internal sound resources that events use
  • The Studio API virtualization system for optimizing away event instances, including voices

While Studio API virtualization system optimizes away voices, the Core API virtualization system does not optimize away events. The situation is described in more detail here: Interaction with Core API Virtual Voice System.
By using fade points to fade out events, you are virtualizing voices in the Core API virtualization system, which unfortunately won’t trigger any Studio API virtualization changes, so you won’t get the Studio API REAL_TO_VIRTUAL callback as I originally stated (sorry again for the misinformation).

If you want to use fade points, the only way I can think of to stop the event after the fadepoint completes would be to poll the channel group’s audibility state with ChannelGroup.getAudibility, and stop the event if it hits 0. For example:

//Add a map of channel groups and EventInstances
Dictionary<FMOD.ChannelGroup, FMOD.Studio.EventInstance> stopMap = new Dictionary<FMOD.ChannelGroup, FMOD.Studio.EventInstance>();

...

ulong dspClock;
ulong parentClock;
result = eventSet.FMODEvent.getChannelGroup(out group);
result = group.getDSPClock(out dspClock, out parentClock);

FMOD.System system;
FMOD.SPEAKERMODE mode;
int numSpeakers;
studioSystem.getLowLevelSystem(out system);
system.getSoftwareFormat(out _SampleRate, out mode, out numSpeakers);

const float fadeTime = 1.0f;
ulong fadedTime = dspClock + Convert.ToUInt64(fadeTime * _SampleRate);

group.addFadePoint(dspClock, fadeTime);
group.addFadePoint(fadedTime, 0.0f);

//After adding the fade point, add it to your map
stopMap.add(group, eventSet.FMODEvent);

...

//In your main loop, poll the map and see if the channel is audible. If it is, stop the event.
foreach(KeyValuePair<FMOD.ChannelGroup, FMOD.Studio.EventInstance> entry in stopMap)
{
    entry.Key.getAudibility(out float aud);
    if(aud <= 0.0f)
    {
        entry.Value.stop(FMOD_STUDIO_STOP_IMMEDIATE);
    }
}