FMOD Engine User Manual 2.03
The Studio API lets you to interact with the data driven projects created in FMOD Studio at run time. It is built on top of the Core API and provides additional functionality to what the Core API provides.
Studio API wrappers are available for C, C++ and C# as fmod_studio.h, fmod_studio.hpp and fmod_studio.cs respectively. Any includes and libraries required by the Core API are also required for the Studio API.
At the most basic level, this is achieved by creating the Studio::System object and calling Studio::System::initialize on it. You need to do this once when your game starts before you can use the FMOD Engine. Once the system is initialized, you can start loading banks and creating event instances without having to do any other preparations. A more detailed description of initialization can be found in the FMOD Getting Started white paper.
If using the C# wrapper, you must call a Core API function before calling anything else in the Studio API. This is because some runtimes do not perform dependency loading, and because the Studio API depends on the Core API, fmod.dll needs to be loaded before fmod_studio.dll. Calling a Core API function before Studio::System::create ensures that fmod.dll is loaded before fmod_studio.dll.
Instances of Studio::System must be created by calling Studio::System::create. Once created an instance must be initialized with a call to Studio::System::initialize before it can be used. Studio::System::create also creates an FMOD System instance which can be retrieved using Studio::System::getCoreSystem.
Pre-initialization configuration of both the Studio System and the Core System may be performed prior to calling Studio::System::initialize:
The Studio System is shut down and released by calling Studio::System::release, make sure to call this before your game is exited.
FMOD Studio is built on a multithreaded processing model, in which API calls on a game thread try to be fast by only reading and writing shadow data or enqueuing commands to a buffer, while a separate Studio update thread triggered by the mixer asynchronously processes the API commands and performs all the logic required by event playback and automation.
When running in this default asynchronous processing mode, calling Studio::System::update from your game is a fast operation which submits the queued command buffer to the asynchronous thread and performs any asynchronous callbacks due to processing on the Studio update thread.
Studio may also be initialized in synchronous mode with the FMOD_STUDIO_INIT_SYNCHRONOUS_UPDATE flag. When operating in synchronous mode, API calls behave the same but all the processing of queued commands and event playback and automation is performed when your game calls Studio::System::update.
If you do not call Studio::System::update then previous commands will not be executed. While most of the API hides this behavior with use of shadowed variables, it can cause unexpected results if waiting in a loop for Studio::EventDescription::getSampleLoadingState or Studio::Bank::getLoadingState without calling update first.
By default, a bank file created in FMOD Studio contains both event metadata and sound sample data.
Loading a bank loads all its metadata. A bank's metadata contains information about all the events, parameters, and other data other than sample data needed by the events in that bank.
Sound sample data comes in two different types: Normal sample data, and streaming sample data. Normal sample data can be loaded in advance of playback on a per-event basis. Streaming data is streamed in on demand as events are played, and is never fully loaded ahead of time. Whether sample data is streamed or not is set by the sound designer in FMOD Studio, and cannot be changed at runtime. For guidance on which sample data should be set to stream, see the Streaming section of the Core API Guide chapter.
The strings bank is a special bank which contains the string lookup of event path to GUID. The strings bank functions identically to a normal bank, except that it never contains sample data or streaming sample data.
Master banks contain the global mixer and are required for creating instances of events, regardless of which bank the event's metadata and sample data exist in. At least one master bank should remain loaded at all times.
While banks normally contain both sample data and metadata, FMOD Studio also allows for metadata and sample data to be built to separate banks. This is commonly done to lower patch size when patching a project with updated banks, as separating sample data and metadata means that it is possible to update a bank's metadata without updating its sample data, or vice versa.
FMOD Studio optionally allows referenced events to not be automatically included in the same banks as the events by which they are referenced. If this is the case, a referenced event may be assigned to a different bank than the event or events that reference it.
Banks are loaded by calling Studio::System::loadBankFile. They are unloaded by Studio::Bank::unload.
Bank loading can be controlled with the FMOD_STUDIO_LOAD_BANK_FLAGS. When loading banks with FMOD_STUDIO_LOAD_BANK_NORMAL, the function does not return until the bank is finished loading. When using the FMOD_STUDIO_LOAD_BANK_NONBLOCKING flag, the load bank function returns before the bank is finished loading.
When a bank is fully loaded, all metadata in that bank can be accessed. This means that event descriptions can be found with Studio::System::getEvent, and instances created from those descriptions. The bank loading state can be queried with Studio::Bank::getLoadingState.
If a bank's sample data and metadata have been built separately, it is possible to load a bank's metadata and not its sample data, or vice-versa. If a bank's metadata is loaded and its sample data is not, events belonging to that bank can be still instantiated and played. However, doing so causes the FMOD Engine to log a warning, and all instruments whose associated sample data is not loaded are silent during playback.
If referenced events are not included in the same banks as the parent events by which they are referenced, it is possible to play a loaded parent event that references a child event that is not loaded. If this occurs, the FMOD Engine logs a warning, and event instruments that would play a non-loaded child event are silent during playback.
Banks can be unloaded by calling Studio::Bank::unload. Unloading a bank frees all sample data from that bank, invalidates the event descriptions belonging to that bank, and destroys all associated event instances.
If the bank containing sample data is unloaded after being loaded using Studio::System::loadBankMemory, the system immediately unloads that bank's sample data. If any instances of events in a bank are still playing when that bank is unloaded, it may lead to playback errors. This can occur even if multiple copies of the sample data are loaded due to multiple different banks containing that sample data being loaded, and only one of those banks is unloaded.
Sample data is loaded from one of the three actions:
For cases where most or all of the events may play at any time, then loading calling Studio::Bank::loadSampleData to load all data up front may be the best approach. Once the bank sample data has loaded, then all event instances can be created or destroyed and use that existing data immediately. However, it does have the highest memory overhead. Repeated calls to Studio::Bank::loadSampleData are reference counted, and the bank's sample data is only unloaded when Studio::Bank::unloadSampleData has been called an equal number of times.
Sample data can be loaded for a selected event using Studio::EventDescription::loadSampleData. It is best to load the sample data ahead of time, so that the event's sound sample data is ready when needed. For cases of very common events, the sample data could be loaded for the duration of the game or level. For less common events, the sample data may be loaded in or out as needed. Repeated calls to Studio::EventDescription::loadSampleData are reference counted, and the bank's sample data is only unloaded when Studio::EventDescription::unloadSampleData has been called an equal number of times, or if the entire bank is unloaded.
When either of these reference counts is incremented to one the system begins loading the referenced sample data. The sample data is loaded asynchronously and the loading state may be polled by calling Studio::Bank::getSampleLoadingState or Studio::EventDescription::getSampleLoadingState. Alternatively, you can call Studio::System::flushSampleLoading, which will block until all sample loading and unloading has completed.
When an instance of an event is created by calling Studio::EventDescription::createInstance the system begins loading any non-streaming sample data which is not already loaded or loading. Once again the sample data is loaded asynchronously and the loading state may be polled by calling Studio::EventDescription::getSampleLoadingState. If playback of the instance is started by calling Studio::EventInstance::start before the sample data has finished loading then the instance will stay in the FMOD_STUDIO_PLAYBACK_STARTING state until loading of the sampled data has completed.
The automatic loading of sample data is the simplest approach and uses the least amount of memory. However it has the following disadvantages:
For the case of one-shots, this may mean that the sample data is constantly loaded and unloaded whenever a one-shot plays, which is not a good approach. For these sort of common sounds, it is better to call Studio::EventDescription::loadSampleData so the sample data stays in memory rather than constantly unloading then reloading it.
The three approaches to bank loading can be combined. The sample data will stay loaded for as long as at least one of the three conditions are met.
For users who don't explicitly load sample data, sounds will be loaded and unloaded on demand. To help avoid unnecessary file access, there is an idle pool for recently used sounds. When a sound is no longer needed (e.g due to an event instance finishing), its sample data will be placed into the idle pool to be reused later if needed.
By default, the idle pool is set to 256kB in size. This can be customized via the FMOD_STUDIO_ADVANCEDSETTINGS::idleSampleDataPoolSize field.
Start by loading the banks that contain the audio tables. Next, create an instance of an event that contains a programmer instrument. Using this event instance, you can register for event callbacks, specifically FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND. By using these callbacks, you can create and assign sounds from the audio tables.
For localized dialogue, make sure that the required localized bank is loaded. Ensure that any other localized versions of the same bank are unloaded before loading a localized bank.
See the FMOD Studio User Manual for more information.
This is a modified excerpt of the programmer instrument example that is included in the C++ Studio API installation folder. The error checking has been removed for brevity.
struct ProgrammerSoundContext
{
FMOD::System* coreSystem;
FMOD::Studio::System* system;
const char* dialogueString;
};
ProgrammerSoundContext programmerSoundContext;
programmerSoundContext.system = system;
programmerSoundContext.coreSystem = coreSystem;
This section is to set up a struct to contain the various systems required for injecting audio files or loading keys into a programmer instrument.
eventInstance->setUserData(&programmerSoundContext);
eventInstance->setCallback(programmerSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND);
The setUserData()
function allows you to attach any kind of data to the event instance. In this case, the Studio system, Core system, and the dialogue string are being attached to this event instance.
The setCallback()
function attaches the callback to the event instance. This callback will be set up outside the main thread and explained more later on.
// Available banks
// "Dialogue_EN.bank", "Dialogue_JP.bank", "Dialogue_CN.bank"
FMOD::Studio::Bank* localizedBank = NULL;
system->loadBankFile(Common_MediaPath("Dialogue_JP.bank"), FMOD_STUDIO_LOAD_BANK_NORMAL, &localizedBank);
programmerSoundContext.dialogueString = "welcome";
eventInstance->start();
When a localized audio table is added to a bank in FMOD Studio, it builds a different version of that bank file for each locale, each with its own audio table. The programmerSoundContext.dialogueString
variable is the audio table key you wish to use. In this example, "welcome" is used.
With "welcome" as the key, what sound plays depends on the bank loaded. In this example, it will play the Japanese bank's "welcome" audio file.
FMOD_RESULT F_CALL programmerSoundCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters)
This function is to set up what happens when a programmer instrument callback is called.
{
FMOD::Studio::EventInstance* eventInstance = (FMOD::Studio::EventInstance*)event;
if (type == FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND)
{
// Get our context from the event instance user data
ProgrammerSoundContext* context = NULL;
eventInstance->getUserData((void**)&context);
// Find the audio file in the audio table with the key
FMOD_STUDIO_SOUND_INFO info;
context->system->getSoundInfo(context->dialogueString, &info);
FMOD::Sound* sound = NULL;
context->coreSystem->createSound(info.name_or_data, FMOD_LOOP_NORMAL | FMOD_CREATECOMPRESSEDSAMPLE | FMOD_NONBLOCKING | info.mode, &info.exinfo, &sound);
FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props = (FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters;
// Pass the sound to FMOD
props->sound = (FMOD_SOUND*)sound;
props->subsoundIndex = info.subsoundindex;
}
The context
struct set up previously is attached to the event with getUserData()
. As mentioned previously, these are the Core system, the Studio system, and the dialogue string.
The context
struct's dialogue string is passed to the context
struct's Studio system. The system searches all loaded audio tables for the string provided, and passes the corresponding sound info into the info
variable. If multiple audio tables are loaded that contain the same key, the latest-loaded audio table is used.
A Core API FMOD::Sound is then instantiated using the information gathered in the info
variable. The audio table is passed in as info.name_or_data
but the specific audio file to be used isn't specified immediately.
When a programmer instrument is created (triggered), the programmer instrument expects a FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES
to be passed into it. The audio table, in the FMOD::Sound, is provided to the props
properties, and the subsoundIndex
is the actual audio file (subsound of the audio table) chosen with the key string.
else if (type == FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND)
{
FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props = (FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters;
// Obtain the sound
FMOD::Sound* sound = (FMOD::Sound*)props->sound;
// Release the sound
sound->release();
}
}
When the programmer instrument is untriggered, either by the instrument no longer meeting its conditions in the event instance or by the event instance stopping, it fires the FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND
callback. In the above code, when the programmer instrument is destroyed, it finds the FMOD::Sound passed into it and releases it, freeing the memory.
An event is an instanceable unit of sound content that can be triggered, controlled, and stopped from game code. Everything that produces a sound in a game should have a corresponding event.
An event contains and is composed of tracks, instruments, and parameters. The parameters trigger the instruments, which route audio content into the tracks. The tracks route into other tracks, or into the event's master track; The output of the event's master track routes into the project mixer. In addition, the event's parameters can control and manipulate most properties of the event, of the event's instruments and logic markers, and of effects on the event's tracks.
To play an event with the Studio API, typically you do the following:
Generally, best practice is to release event instances immediately after starting them unless you want to continue to act on them in the future. For example, you may wish to play an instance multiple times, explicitly stop an instance and start it again later, or set an instance's parameters while it is still playing. This is because if the released event instance has stopped playing, it will be destroyed by the Studio system while you're still trying to act on it.
FMOD Studio allows you to specify a callback function to call when various state changes occur in an event instance. See FMOD_STUDIO_EVENT_CALLBACK_TYPE for the full list of callbacks available. The callback can be set automatically for all new instances of an event using Studio::EventDescription::setCallback, or it can be set manually for individual event instances using Studio::EventInstance::setCallback.
Some callbacks may be fired asynchronously on a thread owned by FMOD, depending on Studio initialization flags.
When Studio has been initialized in asynchronous mode (the default mode), callbacks are fired from the Studio asynchronous thread as they occur.
If Studio has been initialized with FMOD_STUDIO_INIT_DEFERRED_CALLBACKS then the FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER and
FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT callbacks will be fired from the next call to Studio::System::update.
If Studio has been initialized with FMOD_STUDIO_INIT_SYNCHRONOUS_UPDATE then all callbacks will be fired from the next call to Studio::System::update.
See Also: Callback Behavior
Parameters are used to control the behavior of events, snapshots, and the mixer at run time.
In FMOD Studio, parameters can be used to affect various behaviors, such as automating event, snapshot, and mixer properties, and acting as a trigger condition for instruments and logic markers. Parameter values can then be set at run time using the Studio API, causing automated properties to change, and dependent behaviour to trigger when trigger conditions are met.
Parameters can exist locally or globally. Local parameters exist on a per-event instance basis; each event instance that uses a given parameter has a single instance of that parameter, the value of which is independent from all other instances of the same parameter. A global parameter only ever has a single instance, which is shared between all events that make use of it, as well as the mixer.
Local parameters can be set using the following Studio::EventInstance functions:
Global parameters can be set using the following Studio::System functions:
Parameters can be set by name (case-insensitive), or by ID. A parameter's ID, FMOD_STUDIO_PARAMETER_ID, can be found in its corresponding FMOD_STUDIO_PARAMETER_DESCRIPTION. A parameter's ID is not that same as its GUID, and parameter values cannot be set using GUIDs. For local parameters, parameter descriptions can be retrieved using the following Studio::EventDescription functions:
Likewise, similar functions can be called from Studio::System for global parameters:
For more information about parameters, see the Parameters chapter of the FMOD Studio User Manual.
Audio spatialization is the process of taking an audio file and making it sound "in the world".
See the Studio API 3D Events and Spatial Audio white papers.
Reverb in the Studio API can be handled in two ways. The first is to add reverb effects to the master bus or individual events, and to control the levels sent to those effects using FMOD Studio. The second approach is to use the core reverb system.
The core system has four user configurable 3d reverbs. FMOD Studio event instances can interact with the core reverb system by sending their signal to the core reverbs. The send level can be set with Studio::EventInstance::setReverbLevel and queried with Studio::EventInstance::getReverbLevel.
Each event or bus has a signal path to the master bus or a port bus. The signal path is composed of all buses that receive a signal from the event or bus. This includes any buses on the direct path to the master bus as well as any buses that are targeted by sends.
By default, when an event instance is created, the system ensures that every bus on its signal path has a corresponding ChannelGroup. When an event instance is destroyed, the system destroys any ChannelGroups which are no longer required.
You can override the default behavior by calling Studio::Bus::lockChannelGroup. This forces the system to ensure the ChannelGroup exists for that bus and each bus on its signal path. The system cannot destroy any of these ChannelGroups until you call Studio::Bus::unlockChannelGroup.