FMOD Engine User Manual 2.03

4. Core API Loading and Playing Sounds

This chapter describes how to load and play sounds using the Core API.

4.1 Loading Samples for Playback

Before an asset may be played, its sample data must be loaded or buffered into memory. The process of loading or buffering the sampledata into memory is referred to as "creating the sound." Sounds are created using System::createStream or System::createSound, which also lets you choose the loading mode to be used for that sound.

4.1.1 Non-blocking Sound Creation

Loading a sound is one of the slowest operations the FMOD Engine can perform. You may optionally place a sound load into the background so that it doesn't affect processing in the main application thread by using the FMOD_NONBLOCKING flag in System::createSound.

FMOD::Sound *sound;
result = system->createStream("../media/wave.mp3", FMOD_NONBLOCKING, 0, &sound); // Creates a handle to a stream then commands the FMOD Async loader to open the stream in the background.
ERRCHECK(result);

This immediately returns a sound handle, which you can use to check the status of the sound being loaded with Sound::getOpenState. When the sound is ready to play, its state is FMOD_OPENSTATE_READY. Wait until the sound is ready before playing it. If a function other than getOpenState is called on a sound that is still loading, it typically returns FMOD_ERR_NOTREADY.

To avoid a stall on a streaming sound when trying to free/release it, check that the state is FMOD_OPENSTATE_READY before calling Sound::release.

You can specify a callback using the nonblockcallback member of the FMOD_CREATESOUNDEXINFO structure, to be called when the sound finished loading or the stream finishes opening. The following are examples of the callback definition and the createSound call.

FMOD_RESULT F_CALLBACK nonblockcallback(FMOD_SOUND *sound, FMOD_RESULT result)
{
    FMOD::Sound *snd = (FMOD::Sound *)sound;

    printf("Sound loaded! (%d) %s\n", result, FMOD_ErrorString(result)); 

    return FMOD_OK;
}
FMOD_RESULT result;
FMOD::Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;

memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
exinfo.nonblockcallback = nonblockcallback;

result = system->createStream("../media/wave.mp3", FMOD_NONBLOCKING, &exinfo, &sound);
ERRCHECK(result);

4.1.2 Loading modes

There are three different ways in which sample data may be loaded for playback.

Decompressed Sounds

The default mode for createSound is FMOD_CREATESAMPLE, which decompresses the sample data into memory in PCM format for playback. (PCM data is raw uncompressed audio data. For more information, see Sample Data.) Decompressed sounds uses little to no CPU time to process, as PCM data is the same format that the FMOD mixing engine uses, as well as the format used by the audio device itself. However, uncompressed PCM data requires a lot more memory than compressed sample data.

Decompressed sounds are desirable on platforms with limited CPU cycles, especially mobile devices.

Compressed Sounds

For shorter sounds, rather than decompressing the audio file into memory, you may wish to load the audio file into memory in its compressed form. Compressed sounds require less loading time and memory than decompressed sounds, as the sample data does not need to be decompressed when it is loaded. However, because compressed samples are more complicated to play, they have larger contexts to deal with (for example vorbis decode information), so there is a constant per voice overhead (up to a fixed limit) for each playing compressed sound.

Codec allocation is incurred at System::init time if you call System::setAdvancedSettings and set a maxCodecs value, or it could happen the first time a sound is loaded with the FMOD_CREATECOMPRESSEDSAMPLE flag. If not configured with System::setAdvancedSettings, this uses the default of 32 codecs for the allocation.

For example, the vorbis codec has an overhead of 16kb per voice, so the default of 32 vorbis codecs would consume 512kb of memory. If you used System::setAdvancedSettings to set the maxVorbisCodecs value to 16 instead, the vorbis codecs would instead consume 256kb of memory, but you could only play 16 compressed sounds encoded in Vorbis format.

Generally, the best cross platform codec to used as a compressed sample is Vorbis (from an FSB file). However, if Vorbis uses too much CPU for your platform (i.e.: mobile), the FADPCM codec is a good second option. It is less compressed, and uses far fewer CPU cycles to decode, while giving good quality and 4:1 compression. For PS4 or Xbox One, it is better to use the AT9 and XMA codec formats respectively, as the decoding of these formats are handled by separate media chips, taking the load off the CPU. See the relevant Platform Details section for details on platform specific audio formats.

To play a sound as compressed, add the FMOD_CREATECOMPRESSEDSAMPLE flag to the System::createSound function.

Streams

Streaming is the ability to take a large audio asset, and read/play it in real time in small chunks at a time, avoiding the need to load the entire asset into memory.

Whether an asset should be streamed is a question of resource availability and allocation. Each currently-playing streaming asset requires constant file I/O (which is to say, access to the disk) and a very small amount of memory for the stream's ring buffer; whereas assets using other loading modes require much more memory but only require file I/O when they’re first loaded, and only require these resources once, no matter how many instances of the asset are playing.

File I/O is a very limited resource on many platforms, and is needed for many things other than audio. Fortunately, most games load much of their content into memory up-front at the start of new levels and areas, and many users do not run applications that constantly access the disk in the background. This means that there are usually periods when the player’s device is not otherwise loading content from disk, meaning that streaming assets that play in those periods can enjoy uncontested and uninterrupted file I/O. By contrast, a streaming asset that plays while the disk is being accessed regularly by anything else may not be able to access to disk frequently enough to refresh its ring buffer, and so may suffer from buffer starvation. Buffer starvation of a streaming asset manifests as the sound stuttering or stopping entirely.

The streaming loading mode is therefore best used for assets that meet the following conditions:

Accordingly, streaming is typically reserved for:

To play a Sound as a stream, add the FMOD_CREATESTREAM flag to the System::createSound function, or use the System::createStream function. These options both equate to the same end behavior.

Streaming behavior can be adjusted in several ways, as streaming a file takes two threads, one for file reading, and one for codec decoding/decompression. File buffer sizes can be adjusted with System::setStreamBufferSize and codec decoding buffer size can be adjusted with FMOD_CREATESOUNDEXINFO decodeBufferSize member, or FMOD_ADVANCEDSETTINGS::defaultDecodeBufferSize.

Internet Streaming

FMOD streaming supports internet addresses. Supplying http or https in the filename will switch FMOD to streaming using native http, shoutcast or icecast.

Playlist files (such as ASX/PLS/M3U/WAX formats) are supported, including redirection.

Proxy specification and authentication are supported, as well as real-time shoutcast stream switching, metadata retrieval and packet loss notification.

4.2 Playing a sound

To execute a simple playSound

  1. Load a sound with System::createSound, using the system object handle as described above. This will return a Sound handle. This is your handle to your loaded sound.
  2. Play the sound with System::playSound, using the Sound handle returned from Step 1. This will return a Channel handle.
  3. Let it play in the background, or monitor its status with ChannelControl::isPlaying, using the Channel handle returned from Step 2. A channel handle will also go immediately invalid when a sound ends, when calling any relevant Channel based function, so that is another way to know a sound has ended. The error code returned will be FMOD_ERR_INVALID_HANDLE.

All functions execute immediately, so you can either fire and forget a sound during main loop execution or poll for a sound to finish. Playing a sound does not block the application.

The following example shows playing a sound loaded with createSound and then playing it.

C
C++
C#
JS

FMOD_RESULT result;
FMOD_SOUND *sound;
FMOD_CHANNEL *channel;

result = FMOD_System_CreateSound(system, "../media/wave.mp3", FMOD_DEFAULT, 0, &sound);
ERRCHECK(result);

result = FMOD_System_PlaySound(system, sound, 0, 0, &channel);
ERRCHECK(result);
FMOD_RESULT result;
FMOD::Sound *sound;
FMOD::Channel *channel;

result = system->createSound("../media/wave.mp3", FMOD_DEFAULT, nullptr, &sound);
ERRCHECK(result);

result = system->playSound(sound, nullptr, false, &channel);
ERRCHECK(result);
FMOD.RESULT result;
FMOD.Sound sound;
FMOD.Channel channel;

result = system.createSound("../media/wave.mp3", FMOD.MODE.DEFAULT, out sound);
ERRCHECK(result);

result = system.playSound(sound, null, false, out channel);
ERRCHECK(result);
var result;
var sound = {};
var outval = {};
var channel = null;

result = system.createSound("../media/wave.mp3", FMOD.MODE_DEFAULT, 0, outval);
ERRCHECK(result);

sound = outval.val;

result = system.playSound(sound, null, false, channel);
ERRCHECK(result);

4.2.1 Getting a Subsound

When a sound contains multiple subsounds, you can get get the number of subsounds by using Sound::getNumSubSounds, and get a pointer to a specific subsound by calling Sound::getSubSound and specifying the index of the subsound you want.

Under most circumstances, getSubSound is a free function call, whether it be called on a sample or a stream, as all it does is return a pointer. However, if getSubSound is called on a blocking stream, it may cause System::playSound to stall for several milliseconds or more while it seeks and reflushes the stream buffer. Time taken can depend on the file format and media.

If the parent sound was opened using FMOD_NONBLOCKING, its current FMOD_OPENSTATE will have to be polled with Sound::getOpenState until it returns FMOD_OPENSTATE_READY. When the stream is ready and System::playSound is called, then the playsound will not stall and will execute immediately because the stream has been flushed.

4.3 Advanced Sound Creation

FMOD has a number of FMOD_MODE modes for Sound creation that require the use of FMOD_CREATESOUNDEXINFO to specify various properties of the sound, such as the data format, frequency, length, callbacks, and so on. The following details how to use these modes, and provides basic examples of creating a sound using each mode.

4.3.1 Creating a Sound from memory

FMOD_OPENMEMORY causes FMOD to interpret the first argument of System::createSound or System::createStream as a pointer to memory instead of a filename. FMOD_CREATESOUNDEXINFO::length is used to specify the length of the sound, specifically the amount of memory in bytes the sound's data occupies. This data is copied into FMOD's buffers and can be freed after the sound is created. If using FMOD_CREATESTREAM, the data is instead streamed from the buffer pointed to by the pointer you passed in, so you should ensure that the memory isn't freed until you have finished with and released the stream.

C
C++
C#
JS

FMOD::Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;
void *buffer = 0;
int length = 0;

//
// Load your audio data to the "buffer" pointer here
//

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);     // Size of the struct.
exinfo.length = length;                             // Length of sound - PCM data in bytes

system->createSound((const char *)buffer, FMOD_OPENMEMORY, &exinfo, &sound);
// The audio data pointed to by "buffer" has been duplicated into FMOD's buffers, and can now be freed
// However, if loading as a stream with FMOD_CREATESTREAM or System::createStream, the memory must stay active, so do not free it!
FMOD_Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;
void *buffer = 0;
int length = 0;

//
// Load your audio data to the "buffer" pointer here
//

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);     // Size of the struct.
exinfo.length = length;                             // Length of sound - PCM data in bytes

FMOD_System_CreateSound(system, (const char *)buffer, FMOD_OPENMEMORY, &exinfo, &sound);
// The audio data pointed to by "buffer" has been duplicated into FMOD's buffers, and can now be freed
// However, if loading as a stream with FMOD_CREATESTREAM or System::createStream, the memory must stay active, so do not free it!
FMOD.Sound sound;
FMOD.CREATESOUNDEXINFO exinfo;
byte[] buffer;

//
// Load your audio data to the "buffer" array here
//

// Create extended sound info struct
exinfo = new FMOD.CREATESOUNDEXINFO();
exinfo.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
exinfo.length = (uint)bytes.Length;

system.createSound(buffer, FMOD.MODE.OPENMEMORY, ref exinfo, out sound);
// The audio data stored by the "buffer" array has been duplicated into FMOD's buffers, and can now be freed
// However, if loading as a stream with FMOD_CREATESTREAM or System::createStream, you must pin "buffer" with GCHandle so that it stays active
var sound = {};
var outval = {};
var buffer;

//
// Load your audio data to a Uint8Array and assign it to "buffer" var here 
//

// Create extended sound info struct
// No need to define cbsize, the struct already knows its own size in JS
var exinfo = FMOD.CREATESOUNDEXINFO();
exinfo.length = buffer.length;            // Length of sound - PCM data in bytes

system.createSound(buffer.buffer, FMOD.OPENMEMORY, exinfo, outval);
sound = outval.val;
// The audio data stored in the "buffer" var has been duplicated into FMOD's buffers, and can now be freed
// However, if loading as a stream with FMOD_CREATESTREAM or System::createStream, the memory must stay active, so do not free it!

FMOD_OPENMEMORY_POINT also causes FMOD to interpret the first argument of System::createSound or System::createStream as a pointer to memory instead of a filename. However, unlike FMOD_OPENMEMORY, FMOD will use the memory as is instead of copying it to its own buffers. As a result, you may only free the memory after Sound::release is called. FMOD_CREATESOUNDEXINFO::length is used to specify the length of the sound, specifically the amount of memory in bytes the sound's data occupies.

C
C++
C#
JS

FMOD::Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;
void *buffer = 0;
int length = 0;

//
// Load your audio data to the "buffer" pointer here
//

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);     // Size of the struct
exinfo.length = length;                             // Length of sound - PCM data in bytes

system->createSound((const char *)buffer, FMOD_OPENMEMORY_POINT, &exinfo, &sound);
// As FMOD is using the data stored at the buffer pointer as is, without copying it into its own buffers, the memory cannot be freed until after Sound::release is called
FMOD_Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;
void *buffer = 0;
int length = 0;

//
// Load your audio data to the "buffer" pointer here
//

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);     // Size of the struct
exinfo.length = length;                             // Length of sound - PCM data in bytes

FMOD_System_CreateSound(system, (const char *)buffer, FMOD_OPENMEMORY_POINT, &exinfo, &sound);
// As FMOD is using the data stored at the buffer pointer as is, without copying it into its own buffers, the memory cannot be freed until after Sound::release is called
FMOD.Sound sound
FMOD.CREATESOUNDEXINFO exinfo;
byte[] buffer;
GCHandle gch;

//
// Load your audio data to the "buffer" array here
//

// Pin data in memory so a pointer to it can be passed to FMOD's unmanaged code
gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);

// Create extended sound info struct
exinfo = new FMOD.CREATESOUNDEXINFO();
exinfo.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO)); // Size of the struct
exinfo.length = (uint)bytes.Length;                             // Length of sound - PCM data in bytes

system.createSound(gch.AddrOfPinnedObject(), FMOD.MODE.OPENMEMORY_POINT, ref exinfo, out sound);
// As FMOD is using the data stored at the buffer pointer as is, without copying it into its own buffers, the memory must stay active and pinned
// Unpin memory with gch.Free() after Sound::release has been called

Not supported for JavaScript.

4.3.2 Creating a Sound from PCM data

FMOD_OPENRAW causes FMOD to ignore the format of the provided audio file, and instead treat it as raw PCM data. Use FMOD_CREATESOUNDEXINFO to specify the frequency, number of channels, and data format of the file. FMOD expects all raw PCM data to be little endian, and integer PCM data to be signed, in order to play correctly.

C
C++
C#
JS

FMOD::Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize           = sizeof(FMOD_CREATESOUNDEXINFO);   // Size of the struct
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Playback rate of sound
exinfo.format           = FMOD_SOUND_FORMAT_PCM16;          // Data format of sound

system->createSound("./Your/File/Path/Here.raw", FMOD_OPENRAW, &exinfo, &sound);
FMOD_Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize           = sizeof(FMOD_CREATESOUNDEXINFO);   // Size of the struct
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.format           = FMOD_SOUND_FORMAT_PCM16;          // Data format of sound

FMOD_System_CreateSound(system, "./Your/File/Path/Here.raw", FMOD_OPENRAW, &exinfo, &sound);
FMOD.Sound sound
FMOD.CREATESOUNDEXINFO exinfo;

// Create extended sound info struct
exinfo = new FMOD.CREATESOUNDEXINFO();
exinfo.cbsize           = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));  // Size of the struct
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.format           = FMOD.SOUND_FORMAT.PCM16;          // Data format of sound

system.createSound("./Your/File/Path/Here.raw", FMOD.MODE.OPENRAW, ref exinfo, out sound);
var sound = {};
var outval = {};
var exinfo = FMOD.CREATESOUNDEXINFO();

// Create extended sound info struct
// No need to define cbsize, the struct already knows its own size in JS
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.format           = FMOD.SOUND_FORMAT.PCM16;          // Data format of sound

system.createSound("./Your/File/Path/Here.raw", FMOD.OPENRAW, exinfo, outval);
sound = outval.val;

4.3.3 Creating a Sound by manually providing sample data

FMOD_OPENUSER causes FMOD to ignore the first argument of System::createSound or System::createStream, and instead create a static sample or stream to which you must manually provide audio data. Use FMOD_CREATESOUNDEXINFO to specify the frequency, number of channels, and data format. You can optionally provide a read callback, which is used to place your own audio data into FMOD's buffers. If no read callback is provided, the sample will be empty, so Sound::lock and Sound::unlock must be used to provide audio data instead.

C
C++
C#
JS

FMOD::Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize           = sizeof(FMOD_CREATESOUNDEXINFO);   // Size of the struct
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.length           = exinfo.defaultfrequency * exinfo.numchannels * sizeof(signed short) * 5;   // Length of sound - PCM data in bytes. 5 = seconds
exinfo.format           = FMOD_SOUND_FORMAT_PCM16;          // Data format of sound
exinfo.pcmreadcallback  = MyReadCallbackFunction;           // To read sound data, you must specify a read callback using the pcmreadcallback field
// Alternatively, use Sound::lock and Sound::unlock to submit sample data to the sound when playing it back

// As sample data is being loaded via callback or Sound::lock and Sound::unlock, pass null or equivalent as first argument
system->createSound(0, FMOD_OPENUSER, &exinfo, &sound);
FMOD_Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;

// Create extended sound info struct
memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize           = sizeof(FMOD_CREATESOUNDEXINFO);   // Size of the struct
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.length           = exinfo.defaultfrequency * exinfo.numchannels * sizeof(signed short) * 5;   // Length of sound - PCM data in bytes. 5 = seconds
exinfo.format           = FMOD_SOUND_FORMAT_PCM16;          // Data format of sound
exinfo.pcmreadcallback  = MyReadCallbackFunction;           // To read sound data, you must specify a read callback using the pcmreadcallback field
// Alternatively, use Sound::lock and Sound::unlock to submit sample data to the sound when playing it back

// As sample data is being loaded via callback or Sound::lock and Sound::unlock, pass null or equivalent as second argument
FMOD_System_CreateSound(system, NULL, FMOD_OPENUSER, &exinfo, &sound);
FMOD.Sound sound
FMOD.CREATESOUNDEXINFO exinfo;

// Create extended sound info struct
exinfo = new FMOD.CREATESOUNDEXINFO();
exinfo.cbsize           = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));  // Size of the struct
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.length           = exinfo.defaultfrequency * exinfo.numchannels * sizeof(short) * 5;   // Length of sound - PCM data in bytes. 5 = seconds
exinfo.format           = FMOD.SOUND_FORMAT.PCM16;          // Data format of sound
exinfo.pcmreadcallback  = MyReadCallbackFunction;           // To read sound data, you must specify a read callback using the pcmreadcallback field
// Alternatively, use Sound::lock and Sound::unlock to submit sample data to the sound when playing it back

// As sample data is being loaded via callback or Sound::lock and Sound::unlock, pass null or equivalent as first argument
system.createSound("", FMOD.MODE.OPENUSER, ref exinfo, out sound);
var sound = {};
var outval = {};
var exinfo = FMOD.CREATESOUNDEXINFO();

// Create extended sound info struct
// No need to define cbsize, the struct already knows its own size in JS
exinfo.numchannels      = 2;                                // Number of channels in the sound
exinfo.defaultfrequency = 44100;                            // Default playback rate of sound
exinfo.length           = exinfo.defaultfrequency * exinfo.numchannels * 2 * 5;      // Length of sound - PCM data in bytes. 2 = sizeof(short) and 5 = seconds
exinfo.format           = FMOD.SOUND_FORMAT.PCM16;          // Data format of sound
exinfo.pcmreadcallback  = MyReadCallbackFunction;           // To read sound data, you must specify a read callback using the pcmreadcallback field
// Alternatively, use Sound::lock and Sound::unlock to submit sample data to the sound when playing it back

// As sample data is being loaded via callback or Sound::lock and Sound::unlock, pass null or equivalent as first argument
system.createSound("", FMOD.OPENUSER, exinfo, outval);
sound = outval.val;

4.3.4 Creating the Sound as a Streamed FSB File

An FSB file contains subsounds, so if you open it as a stream, you may not want FMOD seeking to the first subsound and wasting time. You can use the initialsubsound member of the FMOD_CREATESOUNDEXINFO structure to make the non-blocking open seek to the subsound of your choice.

FMOD_RESULT result;
FMOD::Sound *sound;
FMOD_CREATESOUNDEXINFO exinfo;

memset(&exinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
exinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
exinfo.initialsubsound = 1;

result = system->createStream("../media/sounds.fsb", FMOD_NONBLOCKING, &exinfo, &sound);
ERRCHECK(result);

Then get the subsound you wanted with Sound::getSubSound.

4.4 Avoiding Stalls While Loading or Releasing a Sound

One of the slowest operations is loading a sound. To place a sound load into the background so that it doesn't affect processing in the main application thread, the user can use the FMOD_NONBLOCKING flag in System::createSound or System::createStream.

Immediately a sound handle is returned to the user. The status of the sound being loaded can then be checked with Sound::getOpenState. If a function is called on a sound that is still loading (besides getOpenState), it will typically return FMOD_ERR_NOTREADY. Wait until the sound is ready to play it. The state would be FMOD_OPENSTATE_READY.

To avoid a stall on a streaming sound when trying to free/release it, check that the state is FMOD_OPENSTATE_READY before calling Sound::release.

4.5 Asynchronous I/O and Deferred File Reading

This tutorial describes how to defer file reading in FMOD so that you don't have to immediately satisfy FMOD's requests for data. This sort of behavior is highly desirable in game streaming engines that do not have access to the data yet, or for when accessing data out of order or in a non sequential fashion would greatly degrade performance. FMOD's asynchronous I/O callbacks will allow you to receive an FMOD read request and defer it to a later time when the game is ready. FMOD uses priorities to notify the game engine how urgent each read request is, as sometimes deferring a music stream read (for example) can result in stuttering audio.

4.5.1 Setup : Override FMOD's file system with callbacks

The idea is that you are wanting to override the file I/O that FMOD normally performs internally. You may have done this before with the System::setFileSystem by overriding the following callbacks:

FMOD_FILE_OPENCALLBACK  useropen
FMOD_FILE_CLOSECALLBACK  userclose
FMOD_FILE_READCALLBACK  userread
FMOD_FILE_SEEKCALLBACK  userseek

The normal behavior here is that you would need to satisfy FMOD's read and seek requests immediately in a blocking fashion.
In the open callback, you open your internal file handle and return it to FMOD, along with the file size.
You would have to set all callbacks or file system override would not work. Any callback that is null in the above callback list will cause FMOD to use the default internal system and ignore your callbacks. All callbacks must be set.

With async I/O, there are 2 new callbacks which you can use to replace the 'userread' and 'userseek' callbacks:

FMOD_FILE_ASYNCREADCALLBACK  userasyncread
FMOD_FILE_ASYNCCANCELCALLBACK  userasynccancel

If these callbacks are set, the 'userread' and 'userseek' callbacks are made redundant. You can of course keep 'userread' and 'userseek' defined if you want to switch between the 2 systems for some reason, but when 'userasyncread' is defined, the normal read/seek callbacks will never be called.

4.5.2 Defining the basics - opening and closing the file handle.

Before we start, we'll just define the open and close callback. A very simple implementation using stdio is provided below:

FMOD_RESULT F_CALLBACK myopen(const char *name, unsigned int *filesize, void **handle, void **userdata)
{
    if (name)
    {
        FILE *fp;

        fp = fopen(name, "rb");
        if (!fp)
        {
            return FMOD_ERR_FILE_NOTFOUND;
        }

        fseek(fp, 0, SEEK_END);
        *filesize = ftell(fp);
        fseek(fp, 0, SEEK_SET);

        *userdata = (void *)0x12345678;
        *handle = fp;
    }

    return FMOD_OK;
}

FMOD_RESULT F_CALLBACK myclose(void *handle, void *userdata)
{
    if (!handle)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    fclose((FILE *)handle);

    return FMOD_OK;
}

4.5.3 Defining 'userasyncread'

The idea for asynchronous reading, is that FMOD will request data (note, possibly from any thread - so be wary of thread safety in your code!), but you don't have to give the data to FMOD immediately. You can return from the callback without giving FMOD any data. This is deferred I/O.

For example, here is a definition of an async read callback:

FMOD_RESULT F_CALLBACK myasyncread(FMOD_ASYNCREADINFO *info, void *userdata)
{
    return PutReadRequestOntoQueue(info);
}

Note that we didnt actually do any read here. You can return immediately and FMOD will internally wait until the read request is satisfied. Note that if FMOD decides to wait from the main thread (which it will do often), then you cannot satisfy the queue from the main thread, you will get a deadlock. Just put the request onto a queue. We'll discuss how to let FMOD know that the data is ready in the next section.

There are a few things to consider here:

4.5.4 Defining 'userasynccancel'

If you have queued up a lot of read requests, and have not satisfied them yet, then it is possible that the user may want to release a sound before the request has been fulfilled (ie Sound::release is called).
In that case FMOD will call the async cancel callback to let you cancel any operations you may have pending, that are related to this file.

FMOD_RESULT F_CALLBACK myasynccancel(void *handle, void *userdata)
{
    return SearchQueueForFileHandleAndRemove(info);
}

Note that the above callback implementation will search through our internal linked list (in a thread safe fashion), removing any requests from the queue so that they don't get processed after the Sound is released. If it is in the middle of reading, then the callback will wait until the read is finished and then return.
Do not return while a read is happening, or before a read happens, as the memory for the read destination will be freed and the deferred read will read into an invalid pointer.

4.5.5 Filling out the FMOD_ASYNCREADINFO structure when performing a deferred read

The FMOD_ASYNCREADINFO is the structure you will pass to your deferred I/O system, and will be the structure that you read and fill out when fulfilling the requests.

The structure exposes the features of the async read system. These are:

typedef struct {
  void *  handle;
  unsigned int  offset;
  unsigned int  sizebytes;
  int  priority;
  void *  buffer;
  unsigned int  bytesread;
  FMOD_RESULT  result;
  void *  userdata;
} FMOD_ASYNCREADINFO;

The first 4 members (handle, offset, sizebytes, priority) are read only values, which tell you about the file handle in question, where in the file it wants to read from (so no seek callbacks required!) and how many bytes it wants. The priority value tells you how important the read is as discussed previously.

The next 3 members (buffer, bytesread and result) are values you will fill in, and to let FMOD know that you have read the data.
Read your file data into buffer. sizebytes is how much you should be reading. bytesread is how much you actually read (this could be less than sizebytes).
If you hit the 'end of file' condition and need to return less bytes than were requested - set bytesread to less than sizebytes, and then set the result to FMOD_ERR_FILE_EOF.

Set the result last! Do not set the result before setting the bytesread value and reading the data into buffer. This is because the initial value for result is going to be FMOD_ERR_NOTREADY. When you set the value to FMOD_OK (or an appropriate error code) FMOD immediately sees this as an indication to continue, so if the bytesread or buffer contents are not ready you will get corruption, errors, or unexpected behavior. To prevent this, make setting result the last thing you do before finishing your queue process, after setting bytesread and filling in buffer.

4.5.6 Threading issues & read priorities

As mentioned earlier in this tutorial, FMOD can call the read callback from various different threads, so it is common sense to protect your I/O system from operations happening simultaneously from different threads.

A system that would use FMOD's async I/O feature would most likely be running in its own thread. This is so the blocking wait loops in FMOD's loading calls are not forever waiting for data because the user can't provide it to FMOD.
If the system runs in another thread, it can detect the queue insert, and process the data while FMOD is waiting.

It is actually possible to complete the read as if it wasn't deferred, and do a direct file read into the buffer and set sizebytes/result values from the FMOD async read callback. This is a possible way to reduce delays for extremely urgent FMOD reads.

Currently there are 3 different categories of read priority.

4.6 Supported File Formats

We recommend using the .fsb file format for most purposes.

The Core API also has native/built in code to support many file formats out of the box. WAV, MP3 and Ogg Vorbis are supported by default, but many more obscure formats like AIFF, FLAC and others. Sequenced formats that are played back in realtime with a real time sequencer, are included. MIDI/MOD/S3M/XM/IT are examples of these. A more comprehensive list can be found in the FMOD_SOUND_TYPE list.

In addition, the Core API also has support for user-created file format plug-ins. You can create callbacks for FMOD to call when System::createSound or System::createStream is executed, or when the decoding engine is asking for data. Plug-ins can be created inline with the application, or compiled as a stand-alone dynamic library (ie .dll or .so). See the System::registerCodec documentation for more.