Setting Channel Callback (SyncPoint) after ProgrammerSound creation

Hi there,

I’m using FMOD Studio on UE4 combined with the Core API in order to implement SyncPoint functionality for Programmer Sounds.

We are extending UFMODAudioComponent with our own class I’ll refer to as UMyFMODAudioComponent.

I have the following scenario:

  • SetProgrammerSoundName()
  • Receive EventCallbackCreateProgrammerSound
  • Notify app that ProgrammerSound has been created using FThreadSafeBool set to true
  • Check for our FThreadSafeBool in UMyFMODAudioComponent::TickComponent
  • If true, then call function to traverse the Instance’s channel ground heierarchy in order to find a channel to bind the callback to

In this case, it does work but it takes a few ticks before the Channel we need to bind to seems to exist. After this we do receive our Channel callbacks (including SyncPoints) just as we expect!

However, if I have SyncPoints at the 0:00 position (or early enough) in the asset, we do not end up having bound the callback in time for these SyncPoints to fire a callback.

In an attempt to more promptly assign the callback, I have tried to bind various Event callbacks (FMOD_STUDIO_EVENT_CALLBACK_STARTED & FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED) and look for the required Channel to set the callback on, but in both cases the Channel does not seem to exist yet in the hierarchy.

Is there a better way? What is the correct/most precise way to locate and find the channel before it proceeds into playback?

Thanks so much!

We don’t really have an interface to retrieve a channel the moment it starts, and I can see why this is limiting in the case of SyncPoints. I feel like there should be a callback when a channel is created, or perhaps some way to assign channel control callbacks to Sounds- I will raise a suggestion for the Dev team to consider.

In your case, I think you could address the 0:00 SyncPoint case specifically by calling Sound::getSyncPoint inside the SOUND_PLAYED callback, checking whether it’s at 0:00 using Sound::getSyncPointInfo,
and manually calling your SyncPoint logic from there. For example:

static FMOD_RESULT F_CALLBACK OnSyncPoint(FMOD_CHANNELCONTROL *channelcontrol,  FMOD_CHANNELCONTROL_TYPE controltype,  FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype,  void *commanddata1,  void *commanddata2)
{
    ... // Other syncpoint stuff
    return FMOD_OK;
}

FMOD_RESULT F_CALLBACK OnEvent(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters)
{
    ... // Other programmer sound stuff

    FMOD::Sound* sound = nullptr;
    result = context->coreSystem->createSound(path, FMOD_DEFAULT, nullptr, &sound);

    if (result == FMOD_OK)
    {
        FMOD_SYNCPOINT *point = nullptr;
        sound->getSyncPoint(0, &point);
        unsigned int time = -1;
        sound->getSyncPointInfo(point, nullptr, 0, &time, FMOD_TIMEUNIT_MS);
        if (time == 0)
        {
            OnSyncPoint(nullptr, FMOD_CHANNELCONTROL_CHANNEL, FMOD_CHANNELCONTROL_CALLBACK_SYNCPOINT, 0, nullptr);
        }

        props->sound = (FMOD_SOUND*)sound;
        props->subsoundIndex = info.subsoundindex;
    }
...
    return FMOD_OK;
}
1 Like

Hi Jeff,

Thanks so much, and indeed this is what I ended up doing as a workaround in any case!

For now, I ignore 0:00 SyncPoints even if they do arrive via the channel callback and I enumerate and manually process syncpoints via getSyncPointInfo when I receive notification that a ProgrammerSound is triggered (via FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED).

A nicer interface for this would be great, STARTED/PLAYED/STOPPED callbacks declared as virtual functions on UMyFMODAudioComponent would make for less plugin modification mess on our side too :slight_smile:

A question regarding enumerating the channel hierarchy, what is the relationship with the studio mix groups and where do the names from? What I am doing now is very naiive (I look for StudioInstance->Group->Group[0]->Channel[0] but aside from MasterBus at the top level the names do not match what I would expect from FMOD Studio)

Thanks!

That is a good question- the relationship is not very obvious, and not very documented. When you call Studio::EventInstance::getChannelGroup, you get the event instance’s Master Track, which doesn’t have any Instruments on it, and therefore doesn’t have any actual FMOD::Channels on it. Instead it has a collection of FMOD::ChannelGroups, which are the Audio Tracks in your event. These ChannelGroups do have Instruments on them, so they can have one or more Channels playing on them. Confusingly, the name we give the Master Tracks in the API is “Master Bus”, and the name of each audio track is “Group Bus”. To summarize:

Studio: Event Master Track Audio Tracks Instruments
API: EventInstance ChannelGroup ChannelGroup[] Channel[]
Pseudo code: StudioInstance ->Group ->Group[0] ->Channel[0]
Name: event name “Master Bus” “Group Bus” sound name

Also worth noting, an Instrument may contain other channel groups if it is a Multi Instrument, or entire other submix structures if it is an Event Instrument.

I have created a task to make a better explanation for these concepts somewhere. Please let me know if you have any other questions!

1 Like