Title Different channel behavior when switching from Core API to Studio API: after sound finishes Channel::* calls fail

Windows (x64), C++
FMOD: 2.03.09

I have a working setup with the Core API:

CHECKFMODERR(FMOD::System_Create(&system));

CHECKFMODERR(system->setSoftwareChannels(MAX_SOUNDS_SLOTS));

CHECKFMODERR(system->init(MAX_SOUNDS_SLOTS, FMOD_INIT_NORMAL, nullptr));

CHECKFMODERR(system->set3DSettings(1.0f, DISTANCEFACTOR, 1.0f));

// per frame:

CHECKFMODERR(system->update());

When I switch to the Studio API (but still play sounds via low-level System::playSound):

CHECKFMODERR(FMOD::Studio::System::create(&studiosystem));

CHECKFMODERR(studiosystem->getCoreSystem(&system));

CHECKFMODERR(system->setSoftwareChannels(MAX_SOUNDS_SLOTS));

CHECKFMODERR(studiosystem->initialize(MAX_SOUNDS_SLOTS, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, nullptr));

CHECKFMODERR(system->set3DSettings(1.0f, DISTANCEFACTOR, 1.0f));

// per frame:

CHECKFMODERR(studiosystem->update());

Observed:

  • After a short sound naturally finishes, any calls on the channel (e.g., channel->getPaused, channel->isPlaying) sometimes do not return FMOD_OK. With pure Core init I get FMOD_OK and isPlaying=false in the same situation.
  • Virtualization behaves differently: channels become virtual more often / seem to “disappear” earlier, even though I did not change virtualization settings.

Questions:

  1. Is it expected that the lifetime/validity of low-level channels (FMOD::Channel) differs when initialized via Studio API vs pure Core init? In my case, this is critical (see the code below).
  2. Can Studio API be configured to behave as close as possible to Core (same/near virtualization behavior and the same return codes after a sound ends)?

Additional notes:

  • Channels are created with system->playSound (not Studio events).
  • With Core API the current logic is stable; issues appear immediately after switching Init/Update to Studio.

Small code excerpt where the issue manifests (cleanup after completion):

bool paused = true;
FMOD_RESULT status = PlayingSounds[i].channel->getPaused(&paused);
if (status != FMOD_OK) {
    core.Trace("PlayingSounds[%d].channel 0x%08X %s paused %d status %d",
               i, PlayingSounds[i].channel, PlayingSounds[i].Name.c_str(), paused, status);
    i = FreeSound(i);
    continue;
}

if (paused) continue;

bool is_playing;
status = CHECKFMODERR(PlayingSounds[i].channel->isPlaying(&is_playing));

if (!is_playing || status != FMOD_OK) {
    if (PlayingSounds[i].aliasLoopIdx != -1)
        AliasRandomLoopSound(i);
    else {
        PlayingSounds[i].channel = nullptr;
        if (i <= 1 && OGG_sound[i] != nullptr) {
            CHECKFMODERR(OGG_sound[i]->release());
            OGG_sound[i] = nullptr;
        }
        i = FreeSound(i);
        int32_t soundId = (i + 1) | (PlayingSounds[i].stamp << 16);
        core.Event("SoundEnded", "l", soundId);
    }
}

In Core, checking the paused state worked fine, but in Studio, sounds that have finished playing immediately return FMOD_ERR_INVALID_HANDLE. Even if I rewrite this logic, the channel becomes completely inaccessible, including losing info like its last 3D coordinates, which is important for restarting a random sound from a specific list at the same location.

  1. I rewrote the error handling so that in a couple of places FMOD_ERR_INVALID_HANDLE / FMOD_ERR_CHANNEL_STOLEN are treated not as errors but as the normal scenario after a channel finished playback.
  2. The issue where channel coordinates couldn’t be retrieved via the getter (because with the Studio API the channel becomes invalid immediately after the sound ends) was fixed by adding an FMOD_VECTOR field to the sound structure. Rewriting the crossfade from a delta-time–based implementation to a proper API-based approach also freed up three floats.
  3. Regarding virtualization: FMOD Core by default does not virtualize channels (vol0virtualvol = -1.0f), while FMOD Studio does. Damn, this is nowhere documented. I’m also a bit unhappy that at the start of playback, until the updater runs, a channel that should be virtual will be real for one frame.

In general, I would like more information about what gets overridden when FMOD Studio is enabled if previously only the Core was used.

Apologies for the delayed response!

Unfortunately, I haven’t been able to reproduce the behavior you described, where channels played by a Studio system’s Core system become invalid when finished, but do not when only played by a Core system. Behavior for both cases is the same on my end - channels become invalid once they finish playing.

Can you share a code snippet demonstrating how sounds are being created and played?

I’m also unable to reproduce this. I’m observing that channels that would exceed the software channel count are immediately virtual, even before the system is updated. How is your channel going virtual? Is it exceeding the software channels limit, or falling below vol0virtualvol?

Studio initializes with FMOD_INIT_VOL0_BECOMES_VIRTUAL, while the Core API does not, which I have noticed isn’t documented - I’ve flagged this internally.

However, unless it has been specified in FMOD_ADVANCEDSETTINGS, vol0virtualvol isn’t set at all for both Studio and Core systems, and should be 0. How are you observing the value of vol0virtualvol?