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.
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
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);
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.
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?
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);
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.
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:
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)”));
}
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;