FMOD Engine User Manual 2.03
The Core API has native/built in code to support many special effects out of the box, such as low-pass, compressor, reverb and multiband EQ. A more comprehensive list can be found in the FMOD_DSP_TYPE list.
An effect can be created with System::createDSPByType and added to a Channel or ChannelGroup with ChannelControl::addDSP.
The Core API runs on a modular synth architecture, which allows connections of signal processing nodes (the 'FMOD DSP' concept).
A directed graph processing tree allows the signal to flow from 'generators' at the tail node (a Sound playing through from System::playSound, or a DSP creating sound from System::playDSP for example), to other nodes, mixing together until they reach the head node, where the final result is sent to the sound card.

A visual representation taken directly from the FMOD Profiler.
FMOD typically processes the Sound in the graph, in blocks of 512 samples (10ms) on some platforms, or 1024 on other platforms (21ms). This is the granularity of the system, and affects how smooth parameter changes, such as pitch or volume will heard.
FMOD pre-built DSP effects can be inserted into the graph with functions like DSP::addInput and DSP::disconnectFrom.
For detailed information, see the DSP Architecture and Usage section of the Using DSP Effects in the Core API chapter.
This section will introduce you to the FMOD Engine's DSP system. With this system you can implement custom filters or create complicated signal chains to create high quality and dynamic sounding audio. The DSP system is an incredibly flexible mixing engine that has an emphasis on quality, flexibility and efficiency, and makes it an extremely powerful system when used to its full potential.
The figure below shows a representation of what a very basic FMOD DSP graph looks like.

Audio data flows from the right to the left, tail to head, until it finally arrives at the soundcard, fully mixed and processed.
The above image, excluding the annotation, was taken using the FMOD Profiler. You can profile your own DSP graph as long as you specify FMOD_INIT_PROFILE_ENABLE when initializing the Core API. The tool is located in the /bin directory of the SDK.
This section will describe the units in more detail, from the origin of the data through to the soundcard, from right to left. The following list describes some of the typical DSP units you will see in a graph.
When FMOD plays a PCM sound on a Channel (using System::playSound), it creates a small sub-graph consisting of a fader and a Wavetable Unit. This would also happen if playing a stream, even if the source data is compressed.
When FMOD plays a compressed sound on a Channel (MP3/Vorbis/XMA/ADPCM usually, loaded with FMOD_CREATECOMPRESSEDSAMPLE), it creates a similar small sub-graph consisting of a Fader and a DSPCodec Unit.
When FMOD plays a DSP on a Channel (using System::playDSP), it creates a small sub-graph consisting of a Fader and a standalone Resampler Unit. The DSP that was specified by the user executed by the resampler as a sub-graph to the resampler, and is not visible on the profiler.
In this section we will look at some basic techniques that can be used to manipulate DSP graphs. We shall start with the most basic signal chain (as shown in the image below) and identify the changes that occur to the DSP graph with the provided code.

Note that the graph only exists of 1 unit, the Master ChannelGroup's DSP Fader Unit (FMOD_DSP_TYPE_FADER). This unit can be used to control the mix output of the entire mix if desired.
Now we shall play a PCM sound with System::playSound.

Note that the sub-graph of a DSP Fader unit (FMOD_DSP_TYPE_FADER), and a system level DSP WaveTable unit have been attached to the Master ChannelGroup's DSP Fader unit.
Let's play the sound again, resulting in 2 channels being active.

Note now that the new Channel targets the same Master ChannelGroup DSP Fader unit, and when 2 lines merge into 1 unit, a 'mix' happens. This is just a summing of the 2 signals together.
In this example we shall add an effect to a sound by connecting a DSP effect unit to the Channel. The code below starts by playing a sound, then creates a DSP unit with System::createDSPByType and adds it to the DSP graph using ChannelControl::addDSP.
FMOD::Channel *channel;
FMOD::DSP *dsp_echo;
result = system->playSound(sound, 0, false, &channel);
result = system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp_echo);
result = channel->addDSP(0, dsp_echo);
The figure below shows the FMOD Echo effect inserted at the 'Channel head' or position 0, as specified with the ChannelControl::addDSP command (position = 0). The Channel Fader which used to be the head unit, is now shuffled down to position 1.

If we call ChannelControl::setDSPIndex
result = channel->setDSPIndex(dsp_echo, 1);
We can see below, that the echo has now moved down one, and Channel Fader is back at position 0.

Create a new ChannelGroup and add our Channel to it
In this example we shall introduce ChannelGroups which are effectively used as sub-mix buses. We can add an effect to a ChannelGroup and if Channels are assigned to that ChannelGroup, all Channels will be affected by any DSP inserted into a ChannelGroup.
These ChannelGroups can then be nested and manipulated to create hierarchical mixing.
result = system->createChannelGroup("my channelgroup", &channelgroup);
result = channel->setChannelGroup(channelgroup);

We can now see the newly created ChannelGroup as a stand-alone DSP ChannelGroup Fader between the channel on the right and the Master ChannelGroup Fader on the left.
Adding an effect to a ChannelGroup is the same as adding one to a Channel. Use ChannelControl::addDSP.
FMOD::DSP *dsp_lowpass;
result = system->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &dsp_lowpass);
result = channelgroup->addDSP(1, dsp_lowpass);

We can now see as before, an effect attached to a ChannelGroup Fader, in position 1, the entirety of the ChannelGroup being symbolized by the box around the 2 units.
This example demonstrates a more complex, and somewhat typical scenario, in which we create a new effect, and every time a Sound plays on a Channel, we connect the new channel to the effect.
Important note! Please don't use this example as a standard way to set up reverb. Simply call System::setReverbProperties instead and all connection logic is handled automatically. Note the following logic does not handle what happens when a Channel goes virtual and is removed from the graph, only to return later. You would only normally use this logic if you wanted to control the 'wet' mix levels indivudually for an effect, per channel. Otherwise a simple ChannelControl::addDSP would suffice.
The first step is to add an effect to the master ChannelGroup. We do this by calling System::createDSPByType again, and then using the DSP API to manually add connections.
FMOD::DSP *dsp_reverb;
FMOD::DSP *dsp_tail;
FMOD::ChannelGroup *channelgroup_master;
result = system->createDSPByType(FMOD_DSP_TYPE_SFXREVERB, &dsp_reverb); /* Create the reverb DSP */
result = system->getMasterChannelGroup(&channelgroup_master); /* Grab the master ChannelGroup / master bus */
result = channelgroup_master->getDSP(FMOD_CHANNELCONTROL_DSP_TAIL, &dsp_tail); /* Grab the 'tail' unit for the master ChannelGroup. This is the last DSP unit for the ChannelGroup, in case it has other effects already in it. */
result = dsp_tail->addInput(dsp_reverb);
This will result in

Note that the ChannelGroup from before is still there. This is what the Channels will be playing on. The reason we have a ChannelGroup here for this example is to keep the Channels executing first in the graph, then the reverb second. This raises a topic called 'order of execution' which you can find more information about below and why it may or may not be important to you.
Also note that the reverb is black. This means it is inactive / disabled. All units are inactive by default, so we have to activate them. You can do this with DSP::setActive.
result = dsp_reverb->setActive(true);

Now you can see that the reverb has gone from black/inactive to active.
Now we will play a sound on multiple channels with the following code. The code plays the sound paused, gets its Channel DSP head unit, adds the Channel DSP head unit to the reverb, then unpauses the sound.
FMOD::DSP *channel_dsp_head;
result = system->playSound(sound, channelgroup, true, &gChannel[0]); /* Play the sound. Play it paused so we dont hear the sound play before it is connected to the reverb. */
result = channel->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &channel_dsp_head); /* Grab the 'head' unit for the Channel */
result = dsp_reverb->addInput(channel_dsp_head); /* Manually add a connection from the Channel DSP head to the reverb. */
result = channel->setPaused(false); /* Unpause the channel and let it be audible. */
Note that calling ChannelControl::setPaused internally just calls DSP::setActive on the Channel's head DSP unit.
Here is the result

The interesting parts here are that the Channel DSP head units now have 2 outputs per channel, and each set of outputs mix to the user created ChannelGroup first, before being passed as the 'dry' signal to the output. The second set of outputs can be considered the 'wet' path and similarly mix to the reverb unit, before being processed by the reverb processor.
Each connection between a DSP unit is represented by a DSPConnection object. This is the line between the boxes.
The primary purpose of this object type is to allow the user to control the volume / mix level between 2 processing units, and also to control the speaker / channel mapping between 2 units, so that a signal can be panned, and input signals mapped to any output signal, in any way that is needed.
Lets go back to the example above, but with 1 channel, and change its wet mix from the Channel to the reverb from 1.0 (0db) to 0.0 (-80db)
The code around the playsound would have one difference, and that is that addInput will also take a pointer to the resulting DSPConnection object.
FMOD::DSP *channel_dsp_head;
FMOD::DSPConnection *dsp_connection;
result = system->playSound(sound, channelgroup, true, &gChannel[0]); /* Play the sound. Play it paused so we dont hear the sound play before it is connected to the reverb. */
result = channel->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &channel_dsp_head); /* Grab the 'head' unit for the Channel */
result = dsp_reverb->addInput(channel_dsp_head, &dsp_connection); /* Manually add a connection from the Channel DSP head to the reverb. */
result = channel->setPaused(false); /* Unpause the channel and let it be audible. */
We can then update the volume simply with DSPConnection::setMix.
result = dsp_connection->setMix(0.0f);

You can see there is no signal level in the meter for the reverb, because the only input to it is silent.
In this section we will grab the first output from the channel_dsp_head and apply a pan matrix to it, to allow mapping of input signal to any output speaker within the mix.
The first thing to note, is that the Channel Fader outputs mono to the ChannelGroup Fader. This means there's not much to map from and to here. Any matrix representing this signal will be 1 in and 1 out.
To make it more interesting, we can change the output format of a DSP Unit with DSP::setChannelFormat.
result = channel_dsp_head->setChannelFormat(0, 0, FMOD_SPEAKER_QUAD);
Here is the result

You will notice that the ChannelFader now outputs 4 channels, and this gets propagated through the graph. A Quad to 5.1 pan has a different default upmix than mono to 5.1, so you will see that the fronts are now slightly lower on the final ChannelGroup Fader unit, and there is some signal now introduced into the Surround Left and Surround Right speakers. Now we will use some code to do something interesting, we will put the newly quad ChannelFader signal's front 2 channels into the rear 2 speakers of the quad output.
FMOD::DSPConnection *channel_dsp_head_output_connection;
float matrix[4][4] =
{ /* FL FR SL SR <- Input signal (columns) */
/* row 0 = front left out <- */ { 0, 0, 0, 0 },
/* row 1 = front right out <- */ { 0, 0, 0, 0 },
/* row 2 = surround left out <- */ { 1, 0, 0, 0 },
/* row 3 = surround right out <- */ { 0, 1, 0, 0 }
};
result = channel_dsp_head->getOutput(0, 0, &channel_dsp_head_output_connection);
result = channel_dsp_head_output_connection->setMixMatrix(&matrix[0][0], 4, 4);

We can now see that the first 2 channels are now silent on the output because they have 0s in the matrix where the first 2 input columns map to the first 2 output columns.
Instead the first 2 input columns have 1s where the rows map to the surround left and surround right output speakers.
To disable an effect simply use the setBypass method. The code below plays a sound, adds an effect then bypasses it.
result = dsp_reverb->setBypass(true);
This has the benefit of not disabling all input units like DSP::setActive with false as the parameter would, and allows the signal to pass through the reverb unit untouched (The reverb process function is not called, saving CPU).

The bypassed reverb is represented as greyed out.
Note that many FMOD effects automatically bypass themselves, saving CPU, after no signal, or silence is detected and the effective 'tail' of the effect has played out.
The order of execution for a DSP graph is from right to left, but also top to bottom. Units at the top will get executed before units at the bottom.
Sometimes it is undesirable to have a user created effect execute the DSP units for the channel, rather than the ChannelGroup it belongs to. This typically doesn't matter, but one case where it would matter is if the user called ChannelControl::setDelay on the channel or ChannelControl::setDelay on a parent ChannelGroup, to make the sound delay before starting.
The reverb unit has no concept of the delay because the clock it is delaying against is stored in the ChannelGroup it belongs to.
The result is that the reverb will pull the signal and be audible through the reverb processor, and the dry path will still be silent because it is in a delay state.
The workaround in the above reverb example, is to attach the reverb to the master ChannelGroup after the ChannelGroup the Channels will play on is created, so that the ChannelGroup executes first, and the reverb second.
A second workaround is to stop the reverb pulling data from its inputs. This can be done by using the FMOD_DSPCONNECTION_TYPE 'type' parameter for DSP::addInput. If FMOD_DSPCONNECTION_TYPE_SEND is used instead of FMOD_DSPCONNECTION_TYPE_STANDARD, the inputs are not executed, and all the reverb would do is process whatever is mixed to it from a previous traversal to the inputs.
The delay will then work, but the downside to this method is that if the reverb is first, the signal from the channels will be sent after the reverb has processed. This means it will have to wait until the next mix before it can process that data, therefore 1 mix block of latency is introduced to the reverb.
A plug-in DSP effect must be registered using the Core API before the object referencing the plug-in is loaded in the game.
The following functions can be used to register a plug-in if it is statically linked or compiled with the game code:
FMOD_RESULT FMOD::Studio::System::registerPlugin(const FMOD_DSP_DESCRIPTION* description);
FMOD_RESULT FMOD::System::registerDSP(const FMOD_DSP_DESCRIPTION *description, unsigned int *handle);
If the plug-in library is to be dynamically loaded, it can be registered using:
FMOD_RESULT FMOD::System::loadPlugin(const char *filename, unsigned int *handle, unsigned int priority = 0)
A base plug-in path can be specified using the function:
FMOD_RESULT FMOD::System::setPluginPath(const char *path)
If this is set, the filename parameter of System::loadPlugin is assumed to be relative to this path.
Plug-ins do not normally need to be unregistered, but it is possible with either of the following functions:
FMOD_RESULT FMOD::Studio::System::unregisterPlugin(const char* name)
FMOD_RESULT FMOD::System::unloadPlugin(unsigned int handle)
In these functions, name refers to the name of the plug-in defined in the plug-ins descriptor and handle refers to handle returned by System::loadPlugin.
The plug-in descriptor is a struct, FMOD_DSP_DESCRIPTION defined in fmod_dsp.h, which describes the capabilities of the plug-in and contains function pointers for all callbacks needed to communicate with FMOD. Data in the descriptor cannot change once the plug-in is loaded. The original struct and its data must stay around until the plug-in is unloaded as data inside this struct is referenced directly within FMOD throughout the lifetime of the plug-in.
The first member, FMOD_DSP_DESCRIPTION::pluginsdkversion, must always hold the version number of the plug-in SDK it was complied with. This version is defined as FMOD_PLUGIN_SDK_VERSION. The SDK version is incremented whenever changes to the API occur.
The following two members, FMOD_DSP_DESCRIPTION::name and FMOD_DSP_DESCRIPTION::version, identify the plug-in. Each plug-in must have a unique name, usually the company name followed by the product name. Version numbers should not be included in the name in order to allow for future migration of saved data across different versions. Names should not change across versions for the same reason. The version number should be incremented whenever any changes to the plug-in have been made.
Here is a code snippet from the FMOD Gain example which shows how to initialize the first five members of FMOD_DSP_DESCRIPTION:
FMOD_DSP_DESCRIPTION FMOD_Gain_Desc =
{
FMOD_PLUGIN_SDK_VERSION,
"FMOD Gain", // name
0x00010000, // plug-in version
1, // number of input buffers to process
1, // number of output buffers to process
...
};
The other descriptor members will be discussed in the following sections.
Audio callbacks FMOD_DSP_DESCRIPTION::read, FMOD_DSP_DESCRIPTION::process and FMOD_DSP_DESCRIPTION::shouldiprocess are executed in FMOD's mixer thread whereas all other callbacks are executed in the host's thread (game or Studio UI). It is therefore important to ensure thread safety across parameters and states which are shared between those two types of callbacks.
In the FMOD Gain example, two gains are stored: target gain and current gain. target gain stores the parameter value which is set and queried from the host thread. This value is then assigned to current gain at the start of the audio processing callback and it is current gain that is then applied to the signal. FMOD Gain shows how this method can be used to perform parameter ramping by not directly assigning current gain but interpolating between current gain and target gain over a fixed number of samples so as to minimize audio artefacts during parameter changes.
Plug-in effect and sound modules can have any number of parameters. Once defined, the number of parameters and each of their properties cannot change. Parameters can be one of four types:
Parameters are defined in FMOD_DSP_DESCRIPTION as a list of pointers to parameter descriptors, FMOD_DSP_DESCRIPTION::paramdesc. The FMOD_DSP_DESCRIPTION::numparameters specifies the number of parameters. Each parameter descriptor is of type FMOD_DSP_PARAMETER_DESC. As with the plug-in descriptor, parameter descriptors must stay around until the plug-in is unloaded as the data within these descriptors are directly accessed throughout the lifetime of the plug-in.
Common to each parameter type are the members FMOD_DSP_PARAMETER_DESC::name and units, as well as FMOD_DSP_PARAMETER_DESC::description which should describe the parameter in a sentence or two. The type member will need to be set to one of the four types and either of the FMOD_DSP_PARAMETER_DESC::floatdesc, FMOD_DSP_PARAMETER_DESC::intdesc, FMOD_DSP_PARAMETER_DESC::booldesc or FMOD_DSP_PARAMETER_DESC::datadesc members will need to specified. The different parameter types and their properties are described in more detail in the sections below.
Floating-point parameters have type set to FMOD_DSP_PARAMETER_TYPE_FLOAT. They are continuous, singled-valued parameters and their minimum, maximum and default values are defined by the floatdesc members min, max and defaultval.
The following units should be used where appropriate:
These are preferred over other denominations (such as kHz for cut-off) as they are recognised by Studio therefore allowing values to be displayed in a more readable and consistent manner. Unitless 0-to-1 parameters should be avoided in favour of dB if the parameter describes a gain, % if it describes a multiplier, or a unitless 0-to-10 range is preferred if describing a generic amount.
The FMOD_DSP_DESCRIPTION members FMOD_DSP_DESCRIPTION::setparameterfloat and FMOD_DSP_DESCRIPTION::getparameterfloat will need to point to static functions of type FMOD_DSP_SETPARAM_FLOAT_CALLBACK and FMOD_DSP_GETPARAM_FLOAT_CALLBACK, respectively, if any floating-point parameters are declared.
These will be displayed as dials in FMOD Studio's effect deck.
Integer parameters have type set to FMOD_DSP_PARAMETER_TYPE_INT. They are discrete, singled-valued parameters and their minimum, maximum and default values are defined by the intdesc members min, max and defaultval. The member goestoinf describes whether the maximum value represents infinity as maybe used for parameters representing polyphony, count or ratio.
The FMOD_DSP_DESCRIPTION members FMOD_DSP_DESCRIPTION::setparameterint and FMOD_DSP_DESCRIPTION::getparameterint will need to point to static functions of type FMOD_DSP_SETPARAM_INT_CALLBACK and FMOD_DSP_GETPARAM_INT_CALLBACK, respectively, if any integer parameters are declared.
These will be displayed as dials in FMOD Studio's effect deck.
Boolean parameters have type set to FMOD_DSP_PARAMETER_TYPE_BOOL. They are discrete, singled-valued parameters and their default value is defined by the booldesc member defaultval.
The FMOD_DSP_DESCRIPTION members FMOD_DSP_DESCRIPTION::setparameterbool and FMOD_DSP_DESCRIPTION::getparameterbool will need to point to static functions of type FMOD_DSP_SETPARAM_BOOL_CALLBACK and FMOD_DSP_GETPARAM_BOOL_CALLBACK, respectively, if any boolean parameters are declared.
These will be displayed as buttons in FMOD Studio's effect deck.
Data parameters have type set to FMOD_DSP_PARAMETER_TYPE_DATA. These parameters can represent any type of data including built-in types which serve a special purpose in FMOD. The datadesc member datatype specifies the type of data stored in the parameter. Values 0 and above may be used to describe user types whereas negative values are reserved for special types described in the following sections.
The FMOD_DSP_DESCRIPTION members FMOD_DSP_DESCRIPTION::setparameterdata and FMOD_DSP_DESCRIPTION::getparameterdata will need to point to static functions of type FMOD_DSP_SETPARAM_DATA_CALLBACK and FMOD_DSP_GETPARAM_DATA_CALLBACK, respectively, if any data parameters with datatype 0 and above are declared.
Data parameters with datatype 0 and above will be displayed as drop-zones in FMOD Studio's effect deck. You can drag any file containing the data onto the drop-zone to set the parameter's value. Data is stored with the project just like other parameter types.
Typically each plug-in only has a single definition. If you want to have multiple definitions from within the one plugin file, you can use a plugin list. An example is shown below.
FMOD_DSP_DESCRIPTION My_Gain_Desc = { .. };
FMOD_DSP_DESCRIPTION My_Panner_Desc = { .. };
FMOD_OUTPUT_DESCRIPTION My_Output_Desc = { .. };
static FMOD_PLUGINLIST My_Plugin_List[] =
{
{ FMOD_PLUGINTYPE_DSP, &My_Gain_Desc },
{ FMOD_PLUGINTYPE_DSP, &My_Panner_Desc },
{ FMOD_PLUGINTYPE_OUTPUT, &My_Output_Desc },
{ FMOD_PLUGINTYPE_MAX, NULL }
};
extern "C"
{
F_EXPORT FMOD_PLUGINLIST* F_CALL FMODGetPluginDescriptionList()
{
return &My_Plugin_List;
}
} // end extern "C"
Support for multiple plug-ins via FMODGetPluginDescriptionList was added in 1.08. If the plug-in also implements FMODGetDSPDescription, then older versions of the FMOD Engine load a single DSP effect, whereas newer versions load all effects.
To load plug-ins at runtime, call System::loadPlugin as normal. The handle returned is for the first definition. System::getNumNestedPlugins and System::getNestedPlugin can be used to iterate all plug-ins in the one file.
unsigned int baseHandle;
ERRCHECK(system->loadPlugin("plugin_name.dll", &baseHandle));
int count;
ERRCHECK(system->getNumNestedPlugins(baseHandle, &count));
for (int index=0; index<count; ++index)
{
unsigned int handle;
ERRCHECK(system->getNestedPlugin(baseHandle, index, &handle));
FMOD_PLUGINTYPE type;
ERRCHECK(system->getPluginInfo(handle, &type, 0, 0, 0));
// We have an output plug-in, a DSP plug-in, or a codec plug-in here.
}
The above code also works for plug-ins with a single definition. In that case, the count is always 1 and System::getNestedPlugin returns the same handle as passed in.
A built-in high quality I3DL2 standard compliant reverb, which is used for a fast, configurable environment simulation, and is used for the 3D reverb zone system, described below.
To set an environment simply, use System::setReverbProperties. This lets you set a global environment, or up to 4 different environments, which all Channels are affected by.
Each Channel can have a different reverb wet mix by setting the level in ChannelControl::setReverbProperties.
Read more about the I3DL2 configuration in the Reverb Notes section of the documentation. To avoid confusion when starting out, simply play with the pre-set list of environments in FMOD_REVERB_PRESETS.
There is also an even higher quality Convolution Reverb which allows a user to import an impulse response file (a recording of an impulse in an environment which is used to convolve the signal playing at the time), and have the environment sound like it is in the space the impulse was recorded in.
This is an expensive-to-process effect, so FMOD supports GPU acceleration to offload the processing to the graphics card. This greatly reduces the overhead of the effect, making it almost negligible. GPU acceleration is supported on Xbox One and PS4 platforms. The PS5 and XSX platforms both feature dedicated convolution reverb hardware, which similarly reduce the overhead of the effect.
Convolution reverb can be created with System::createDSPByType with FMOD_DSP_TYPE_CONVOLUTIONREVERB and added to a ChannelGroup with ChannelControl::addDSP. It is recommended to only implement one or a limited number of these effects and place them on a sub-mix/group bus (a ChannelGroup), and not per Channel.