Multiple Programmer sounds in one event

For our project we need localization and we use programmer sounds for that. We also want to be able to play multiple programmer instruments in the same event. We tried to learn from the example in the API but we did not get the callback to work.
We use a class that inherits from the FMODAudioComponent. In that class we tried to set up a progammerSound callback.
In the .h file of that SoundComponent class we added following code.

// Programmer sound
struct ProgrammerSoundContext
{
	FMOD::System* coreSystem;
	FMOD::Studio::System* system;
	const char* dialogueString;
};
#define CHECK_RESULT(op) \
    { \
        FMOD_RESULT res = (op); \
        if (res != FMOD_OK) \
        { \
            return res; \
        } \
    }

FMOD_RESULT F_CALLBACK programmerSoundCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters);
// Programmer sound end

In the cpp constuctor we added

FMOD::Studio::System* System = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Max);
FMOD::Studio::EventDescription* eventDescription = NULL;
	System->getEvent("event:/Character/Dialogue", &eventDescription);
	FMOD::Studio::EventInstance* eventInstance = NULL;
	eventDescription->createInstance(&eventInstance);
	
	eventInstance->setUserData(&programmerSoundContext);
	eventInstance->setCallback(programmerSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND);
	
bAutoActivate = false;

In our dialogue class we have a play dialogue function which has this code in the end

Component->SetEvent(Event);
Component->SetProgrammerSoundName(Dialogue);

	FMOD_RESULT Result = Component->StudioInstance->setCallback(programmerSoundCallback);
	if (Result != FMOD_OK)
	{
		UE_LOG(LogTemp, Error, TEXT("Dialog::setcallback  failed"));
	}

	Component->Play();
	CanPlayQueuedDialogues = true;
	OnDialogueStart.Broadcast(Dialogue, Component);


We get error 30 “Invalid handle” for the setcallback function. Also studio instance seems to be Null. There is probably something fundamental we got wrong here.

If we can get the callback to work then we still haven’t found any information about how to play multiple programmer sounds in the same event. So some more information on that would also be nice.

We are using Unreal 5.1 github source code version with FMOD studio 2.0.2.06

Hi,

Apologies for the delayed response.

Could I get a screenshot of how you have the event set up in FMOD Studio?

Hello!

No worries, yes here is a screenshot of the event:

Thanks.

Hi,

Thanks for those.

Where does Event come from? I can’t see it being created using EventDescription->CreateInstance(&Component->StudioInstance).

Would it be possible to share the full code you are using? It can be uploaded to your profile or DM’ed to me.

Hi!

I think we use this BP function that we call in our dialogue class for creating the Event:

UFMODEvent* Event = UFMODBlueprintStatics::FindEventByName(path);

I’ve sent you a DM with the code.
Hopefully we can figure this out.

1 Like

The StudioInstance that you are accessing is part of FMODAudioComponent and is only being created when FMODAudioComponent::Play() is being called. Try adding this to the Dialogue Class

Creating the instance
Component->SetEvent(Event);
Component->SetProgrammerSoundName(Dialogue);
// Creating an Event description from the set event to use to create the instance
FMOD::Studio::EventDescription* dialogueDescription =  IFMODStudioModule::Get().GetEventDescription(Component->Event, EFMODSystemContext::Runtime);
dialogueDescription->createInstance(&Component->StudioInstance);

	FMOD_RESULT Result = Component->StudioInstance->setCallback(programmerSoundCallback);
	if (Result != FMOD_OK)
	{
		UE_LOG(LogTemp, Error, TEXT("Dialog::setcallback  failed"));
	}

	Component->Play();
	CanPlayQueuedDialogues = true;
	OnDialogueStart.Broadcast(Dialogue, Component); 

Give that ago

Thanks! Now I don’t get the any errors but my callback never gets called.
I’ve tried to make the callback included in the SoundComponent class as a static function but it made no difference.
What more than using Component->StudioInstance->setCallback in the Dialogue class do I need to do. Do I need to do something in the constructor of the SoundComponent so it starts to listen to the callback.
I can see that EventCallbackCreateProgrammerSound gets called but my programmerSound callback is not.
My code is still the same as before but just updated with your fix. The things in the Soundcomponents constructor I’ve made are probably completely wrong.
Thanks for your patience.

I just noticed what we were missing, when assigning the callback you have to set the flags you want it to be triggered on. The full list of callback flags can be found under FMOD API | Studio API Reference. So change the line

FMOD_RESULT Result = Component->StudioInstance->setCallback(programmerSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND);

Using these flags the callback should be triggered when the programmer sound is created and destroyed.

Hope this helps!

Unfortunately that did not make a difference. Something is still wrong or missing somewhere.

If you add the programmerSoundCallback function to the Dialogue.cpp script does that help?

Unfortunately no. It still does not get called.

Is it possible to get a stripped-out version of your Unreal project uploaded to your Profile or DM’d to me so I can test the code on my end?

Our project is quite big so it would require a lot of time and work to make a stripped out version, which is not possible at the moment.
I have tried having the callback in our soundcomponent class, in our singleton dialogue class, in our FModController class. I’ve tested calling the setcallback from eventinstance, eventdescription and studio instance.
If I have it in the Dialogue class are these lines still correct?

FMOD::Studio::EventDescription* dialogueDescription = IFMODStudioModule::Get().GetEventDescription(Component->Event, EFMODSystemContext::Runtime);
dialogueDescription->createInstance(&Component->StudioInstance);
FMOD_RESULT Result = dialogueDescription->setCallback(programmerSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND);

If you don’t get error messages from setcallback, but the callback is never called what are the things that could still be wrong?

Here you are setting the callback onto the Event Description (FMOD API | Studio API Reference) rather than the Event Instance (FMOD API | Studio API Reference). This means the callback isn’t actually getting passed onto the Instance
You could try:

FMOD_RESULT Result = dialogueDescription->setCallback(programmerSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND);
dialogueDescription->createInstance(&Component->StudioInstance);

Now any Event Instance created using this description will have the callback assigned to it. Or you can assign the callback directly to the Event Instance like so:

dialogueDescription->createInstance(&Component->StudioInstance);
FMOD_RESULT Result = Component->StudioInstance->setCallback(programmerSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND);

I tried both of those but did not get it to workd. Is there a way to debug so you can see why the callback does not get called?

The first thing to check is that an event is being created successfully and being played. It might be worth checking by replacing the Programmer Instruments with Single Instruments and listening for them to play. As it is hard to tell if the programmer sound is being played correctly as it won’t play any sound unless it is created successfully.

Once you are sure the event is being created a played successfully then a way to debug the callback is to add more flags to its trigger conditions so there is a greater chance of it being triggered.

I would recommend FMOD_STUDIO_EVENT_CALLBACK_CREATED or FMOD_STUDIO_EVENT_CALLBACK_STARTING if neither of these are getting triggered then we know that the event is not being created/started correctly.

Single instruments play. A single Programmer sound plays. With two programmer sounds in one event. Only the last one plays, but that is what we want to fix with the programmersound callback.
I tried the two callbacks you listed and then some more from that page but the callback was never triggered.

I have uploaded a test project which contains only the FMOD parts needed for testing it with the same problem. Press space to trigger the dialogue. The dialogue is triggered from the BP_Player blueprint which calls our PlayDialogue function on the dialogue cpp class.
We play event A891 and the first part of that dialogue will not play, but after a few seconds you will be able to hear the second part (second programmer sound).
Both programmer sounds start at the same time, but the other one has silence in the beginning.

Hopefully testing the project will help with finding the problem.

Hi,

Thank you very much for the project.

The reason the callback is not being triggered is in FMODAudioComponent.cpp line 749 our assigned Callback and UserData is being overwritten.

So a solution might be implementing a new Play() function in the MyFMODAudioComponent class which copies most of the original function, without overwriting the Callback and the UserData. Or create a check inside the PlayInternal() function before line: 747

void* data = NULL;
StudioInstance->getUserData(&data);
if (data != NULL)
// data == NULL - This is a generic function and we can overwrite the Callback
// data != Null - This is  our programmer sound so do not overwrite the Callback

The programmmerSoundSoundContext is also not being assigned to the Component->StudioInstance->setUserData() which is causing issues in the callback. That data can be assigned like so:

programmerSoundContext.dialogueString = TCHAR_TO_ANSI(*path);
programmerSoundContext.system = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime);
programmerSoundContext.system->getCoreSystem(&programmerSoundContext.coreSystem);
Component->StudioInstance->setUserData(programmerSoundContext);

We have recently added a full Programmer Sound Example in our docs here: Unreal Integration | User Guide. Apologies for not linking to this sooner.

I am not sure how the second part is being triggered at all.

Hopefully, now you are able to get into the callback.

Thanks, now the callback is being triggered. Unfortunately when we are inside the callback and try to getuserdata from the eventinstance it’s just garbage characters and GetSoundInfo fails with Invalid handle (30).
I suspect it is because we set user data on the component? (component->StudioInstance->SetUserData). If that is true how can we access the component in the callback or set userdata on the eventinstance? I also tested getting user data immediatly after setting it (not in the callback). Then it contained the correct data.

Blockquote
if (type == FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND)
{
FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props = (FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters;
// Get our context from the event instance user data
ProgrammerSoundContext* context = NULL;
FMOD_RESULT EIRes = eventInstance->getUserData((void**)&context);
if (EIRes != FMOD_OK)
{
UE_LOG(LogTemp, Error, TEXT("Dialogue: ProgrammerSoundCallback Failed to get userdata "));
}
FString debugString = context->key;
UE_LOG(LogTemp, Display, TEXT(“Dialog: ProgSoundCallback Create ProgSound %s”), debugString);
// Find the audio file in the audio table with the key
const char
charKey = TCHAR_TO_ANSI(*context->key);
FMOD_STUDIO_SOUND_INFO info;
FMOD_RESULT result = context->system->getSoundInfo(charKey, &info);
if (result != FMOD_OK)
{
UE_LOG(LogTemp, Error, TEXT(“Dialog:ProgSoundCallback Failed to get soundInfo (Create programmer sound)”));
}

Hi,
Using a const char * in the userData seems to always trash the data so I found using an FString worked instead:
A slight change to your context

struct ProgrammerSoundContext
{
	FMOD::System* coreSystem;
	FMOD::Studio::System* system;
	FString dialogueString;
};

Which allows you to pass in the string

programmerSoundContext.dialogueString = path;
programmerSoundContext.system = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime);
programmerSoundContext.system->getCoreSystem(&programmerSoundContext.coreSystem);
Component->StudioInstance->setUserData(&programmerSoundContext);

Then in the callback function you can convert ir back again

// Get our context from the event instance user data
ProgrammerSoundContext* context = NULL;
CHECK_RESULT(eventInstance->getUserData((void**)&context));

char* path = TCHAR_TO_ANSI(*context->dialogueString);

A further check you could

if (!context->system->isValid())
    return FMOD_INVALID_HANDLE;

Hope this helps!