Creating a custom plugin

Thanks for uploading your code!

I can see what you mean by having issues with the plugin when traversing the DSP graph. My expectation that you could call setParameter functions on the “FMOD Resampler” was incorrect, so you have my apology for that - it seems that the actual custom DSP is stored as an independent subgraph that is connected to the resampler, and cannot be accessed by the normal means of traversing the DSP graph.

That said, using an event callback will give you direct access to your Plugin DSP fairly simply. I have created a struct as follows in the UE header to pass to the event as user data:

    struct UserData {
        FMOD::DSP* pluginDSP;
    };

    const UserData userData;

As well as the following static event callback, which retrieves the DSP when it is created, and passes it to the user data struct:

FMOD_RESULT F_CALLBACK UEngineTest::EventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters) {
	if (type == FMOD_STUDIO_EVENT_CALLBACK_PLUGIN_CREATED) {
		UserData* userData;
		((FMOD::Studio::EventInstance*)event)->getUserData((void**) &userData);
		userData->pluginDSP = (FMOD::DSP*)(((FMOD_STUDIO_PLUGIN_INSTANCE_PROPERTIES*)parameters)->dsp);
	}
	return FMOD_OK;
}

I have appended the following code to the end of your code in the if (AudioComp == nullptr) check in UEngineTest::TickComponent(), which assigns the User Data struct and Callback to the AudioComponent’s underlying Event Instance:

if (AudioComp == nullptr) {
    //....
    AudioComp->StudioInstance->setCallback(EventCallback);
    void* userDataPointer = (void*)&userData;
    AudioComp->StudioInstance->setUserData(userDataPointer);
}

And finally, I’ve set the RPM parameter on the retrieved plugin DSP at the end of UEngineTest::TickComponent() with the following code:

//...
if (userData.pluginDSP != nullptr) {
    userData.pluginDSP->setParameterFloat(0, 5000);
}

With this code, I am successfully able to retrieve the plugin DSP and set all parameters on it without any issue. Please give a shot on your and see whether you can access your DSP plugin in code - if so, you can omit traversing the DSP graph entirely.

This worked like a charm. I must distinguish the plugins now by a boolean I guess as there are 2 per engine, one for acceleration and one for deceleration.

Is it possible to have a callback on the dsp which is called after the dsp “process” callback, not Unreal Engines tick? Because I have to set the looped samples rpm value directly after the dsp is finished processing.

Best regards.

Edit.: I noticed something strange: The Unreal Engine Event is lower pitched than in FMOD. I don’t change the sound at all afterwards in this test project, it is purely the DSP that outputs the audio file at 48khz.

IGNORE THE PREVIOUS EDIT, THAT WAS MY FAULT.

Edit.: With your help I am now able to have a very smooth blending between the granular synth and the looped samples. In this examples you can hear the 2 granular synths for on- and offload and also 6 looped samples for on- and offload.

One problem is here which is due to the way i update the looped samples rpm. On Unreal Engines Tick I get the dsp’s output rpm and set the looped samples rpm. so the looped samples rpm is always set 1 mixer tick later than the dsp outputs its samples. so whenever you change the rpm too fast you hear a slight phasing as the looped samples update 1 buffer later and it is again out of sync. That is the reason I ask wether I can register eg. a callback in the dsp that is called at the end of the process function so i can update the event float parameter.

The easiest way to handle this would probably be to pass a function pointer to your DSP as user data, which you then call from within the DSP at the end of the process call to set the looped samples RPM. This should have the effect of “cutting out the middleman” so to speak, in that you shouldn’t have to wait for an additional mixer tick.

Here’s a rough code snippet demonstrating creating a struct and assigning a function pointer to it:

struct Data
{
   void (*postmix)();
};

Data data = {0};
data.postProcess = MyCallback;
dsp->setUserData(data);

And the calling the function pointer from within your DSP’s process callback:

FMOD_RESULT process(FMOD_DSP_STATE *state, ...)
{
    //...
    //existing process callback code here
    //...
   getUserData()->postProcess();
   return FMOD_OK;
}

It sounds great! Due to the somewhat complex requirement of passing the RPM around, I suspect that some spaghetti-ness unavoidable. Just using a function pointer may be a little cleaner than passing the whole Event Instance, but other than that, I don’t have any further suggestions.

Hi Apfelbaum,
I hope you are ok. I am also big fan of older driving games, in my case Richard Burns Rally.
I really need your help with FMOD please.
I am currently creating rally game mod of RBR (main focus realistic physics see here https://www.youtube.com/watch?v=3HhC_Heb1Gc&t=40s) and I would love to implement the FMOD to it as it would be awesome. Please email me at przemek-guminski@wp,pl Thank you in advance

Generally speaking, specific support for modding falls outside the scope of support beyond assisting with general API questions/issues - if there’s a modding community for RBR, I would recommend getting in touch with them for assistance. If you do have any specific questions, feel free to create a thread for them or reply to existing threads if relevant, but we heavily discourage spamming similar messages in multiple unrelated threads, so please refrain from doing so.

I have another question regarding the inputbuffer: Do I have to fill the buffer (iirc 1024 samples) with unique data or just half of it and then duplicate that to the second half of the buffer?

ok sorry

Can I get you to elaborate on the context of your use of the input buffer? I was under the impression you were creating a sound module DSP, which should only have an output buffer, and no input buffer.

Sorry sorry, outputbuffer. My mistake.

When using the FMOD DSP API, the output buffer of your DSP should be entirely filled with sample data - if you’re outputting multi-channel sample data, the data should be interleaved. Is there a specific reason why you were thinking of only filling half of the buffer and then duplicating?

Thank you for the answer. I read this somewhere in a forum post I don’t find anymore and was confused. Best regards I am done now with awkward questions :slight_smile:

Heyo, have you considered selling your plugin, or licensing it out? (Or open sourcing it) I’m very interested in something like this, as AudioMotors is pretty much dead…