427 lines
13 KiB
C++
427 lines
13 KiB
C++
/*==============================================================================
|
|
Load Banks Example
|
|
Copyright (c), Firelight Technologies Pty, Ltd 2012-2025.
|
|
|
|
This example demonstrates loading banks via file, memory, and user callbacks.
|
|
|
|
The banks that are loaded are:
|
|
|
|
* SFX.bank (file)
|
|
* Music.bank (memory)
|
|
* Vehicles.bank (memory-point)
|
|
* VO.bank (custom)
|
|
|
|
The loading and unloading is asynchronous, and we displays the current
|
|
state of each bank as loading is occuring.
|
|
|
|
### See Also ###
|
|
* Studio::System::loadBankFile
|
|
* Studio::System::loadBankMemory
|
|
* Studio::System::loadBankCustom
|
|
* Studio::Bank::loadSampleData
|
|
* Studio::Bank::getLoadingState
|
|
* Studio::Bank::getSampleLoadingState
|
|
* Studio::Bank::getUserData
|
|
* Studio::Bank::setUserData
|
|
|
|
For information on using FMOD example code in your own programs, visit
|
|
https://www.fmod.com/legal
|
|
==============================================================================*/
|
|
#include "fmod_studio.hpp"
|
|
#include "fmod.hpp"
|
|
#include "common.h"
|
|
#include <stdio.h>
|
|
|
|
//
|
|
// Some platforms don't support cross platform fopen. Or you can disable this
|
|
// to see how the sample deals with bank load failures.
|
|
//
|
|
#ifdef COMMON_PLATFORM_SUPPORTS_FOPEN
|
|
#define ENABLE_FILE_OPEN
|
|
#endif
|
|
|
|
//
|
|
// Load method as enum for our sample code
|
|
//
|
|
enum LoadBankMethod
|
|
{
|
|
LoadBank_File,
|
|
LoadBank_Memory,
|
|
LoadBank_MemoryPoint,
|
|
LoadBank_Custom
|
|
};
|
|
static const char* BANK_LOAD_METHOD_NAMES[] =
|
|
{
|
|
"File",
|
|
"Memory",
|
|
"Memory-Point",
|
|
"Custom"
|
|
};
|
|
|
|
//
|
|
// Sanity check for loading files
|
|
//
|
|
#ifdef ENABLE_FILE_OPEN
|
|
static const size_t MAX_FILE_LENGTH = 2*1024*1024*1024ULL;
|
|
#endif
|
|
|
|
//
|
|
// Custom callbacks that just wrap fopen
|
|
//
|
|
FMOD_RESULT F_CALL customFileOpen(const char *name, unsigned int *filesize, void **handle, void *userdata)
|
|
{
|
|
#ifdef ENABLE_FILE_OPEN
|
|
// We pass the filename into our callbacks via userdata in the custom info struct
|
|
const char* filename = (const char*)userdata;
|
|
FILE* file = fopen(filename, "rb");
|
|
if (!file)
|
|
{
|
|
return FMOD_ERR_FILE_NOTFOUND;
|
|
}
|
|
fseek(file, 0, SEEK_END);
|
|
size_t length = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
if (length >= MAX_FILE_LENGTH)
|
|
{
|
|
fclose(file);
|
|
return FMOD_ERR_FILE_BAD;
|
|
}
|
|
*filesize = (unsigned int)length;
|
|
*handle = file;
|
|
return FMOD_OK;
|
|
#else
|
|
return FMOD_ERR_FILE_NOTFOUND;
|
|
#endif
|
|
}
|
|
|
|
FMOD_RESULT F_CALL customFileClose(void *handle, void *userdata)
|
|
{
|
|
#ifdef ENABLE_FILE_OPEN
|
|
FILE* file = (FILE*)handle;
|
|
fclose(file);
|
|
#endif
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL customFileRead(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void *userdata)
|
|
{
|
|
*bytesread = 0;
|
|
#ifdef ENABLE_FILE_OPEN
|
|
FILE* file = (FILE*)handle;
|
|
size_t read = fread(buffer, 1, sizebytes, file);
|
|
*bytesread = (unsigned int)read;
|
|
// If the request is larger than the bytes left in the file, then we must return EOF
|
|
if (read < sizebytes)
|
|
{
|
|
return FMOD_ERR_FILE_EOF;
|
|
}
|
|
#endif
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL customFileSeek(void *handle, unsigned int pos, void *userdata)
|
|
{
|
|
#ifdef ENABLE_FILE_OPEN
|
|
FILE* file = (FILE*)handle;
|
|
fseek(file, pos, SEEK_SET);
|
|
#endif
|
|
return FMOD_OK;
|
|
}
|
|
|
|
//
|
|
// Helper function that loads a file into aligned memory buffer
|
|
//
|
|
FMOD_RESULT loadFile(const char* filename, char** memoryBase, char** memoryPtr, int* memoryLength)
|
|
{
|
|
// If we don't support fopen then just return a single invalid byte for our file
|
|
size_t length = 1;
|
|
|
|
#ifdef ENABLE_FILE_OPEN
|
|
FILE* file = fopen(filename, "rb");
|
|
if (!file)
|
|
{
|
|
return FMOD_ERR_FILE_NOTFOUND;
|
|
}
|
|
fseek(file, 0, SEEK_END);
|
|
length = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
if (length >= MAX_FILE_LENGTH)
|
|
{
|
|
fclose(file);
|
|
return FMOD_ERR_FILE_BAD;
|
|
}
|
|
#endif
|
|
|
|
// Load into a pointer aligned to FMOD_STUDIO_LOAD_MEMORY_ALIGNMENT
|
|
char* membase = reinterpret_cast<char*>(malloc(length + FMOD_STUDIO_LOAD_MEMORY_ALIGNMENT));
|
|
char* memptr = (char*)(((size_t)membase + (FMOD_STUDIO_LOAD_MEMORY_ALIGNMENT-1)) & ~(FMOD_STUDIO_LOAD_MEMORY_ALIGNMENT-1));
|
|
|
|
#ifdef ENABLE_FILE_OPEN
|
|
size_t bytesRead = fread(memptr, 1, length, file);
|
|
fclose(file);
|
|
if (bytesRead != length)
|
|
{
|
|
free(membase);
|
|
return FMOD_ERR_FILE_BAD;
|
|
}
|
|
#endif
|
|
|
|
*memoryBase = membase;
|
|
*memoryPtr = memptr;
|
|
*memoryLength = (int)length;
|
|
return FMOD_OK;
|
|
}
|
|
|
|
//
|
|
// Helper function that loads a bank in using the given method
|
|
//
|
|
FMOD_RESULT loadBank(FMOD::Studio::System* system, LoadBankMethod method, const char* filename, FMOD::Studio::Bank** bank)
|
|
{
|
|
if (method == LoadBank_File)
|
|
{
|
|
return system->loadBankFile(filename, FMOD_STUDIO_LOAD_BANK_NONBLOCKING, bank);
|
|
}
|
|
else if (method == LoadBank_Memory || method == LoadBank_MemoryPoint)
|
|
{
|
|
char* memoryBase;
|
|
char* memoryPtr;
|
|
int memoryLength;
|
|
FMOD_RESULT result = loadFile(filename, &memoryBase, &memoryPtr, &memoryLength);
|
|
if (result != FMOD_OK)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
FMOD_STUDIO_LOAD_MEMORY_MODE memoryMode = (method == LoadBank_MemoryPoint ? FMOD_STUDIO_LOAD_MEMORY_POINT : FMOD_STUDIO_LOAD_MEMORY);
|
|
result = system->loadBankMemory(memoryPtr, memoryLength, memoryMode, FMOD_STUDIO_LOAD_BANK_NONBLOCKING, bank);
|
|
if (result != FMOD_OK)
|
|
{
|
|
free(memoryBase);
|
|
return result;
|
|
}
|
|
|
|
if (method == LoadBank_MemoryPoint)
|
|
{
|
|
// Keep memory around until bank unload completes
|
|
result = (*bank)->setUserData(memoryBase);
|
|
}
|
|
else
|
|
{
|
|
// Don't need memory any more
|
|
free(memoryBase);
|
|
}
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// Set up custom callback
|
|
FMOD_STUDIO_BANK_INFO info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.size = sizeof(info);
|
|
info.opencallback = customFileOpen;
|
|
info.closecallback = customFileClose;
|
|
info.readcallback = customFileRead;
|
|
info.seekcallback = customFileSeek;
|
|
info.userdata = (void*)filename;
|
|
|
|
return system->loadBankCustom(&info, FMOD_STUDIO_LOAD_BANK_NONBLOCKING, bank);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Helper function to return state as a string
|
|
//
|
|
const char* getLoadingStateString(FMOD_STUDIO_LOADING_STATE state, FMOD_RESULT loadResult)
|
|
{
|
|
switch (state)
|
|
{
|
|
case FMOD_STUDIO_LOADING_STATE_UNLOADING:
|
|
return "unloading ";
|
|
case FMOD_STUDIO_LOADING_STATE_UNLOADED:
|
|
return "unloaded ";
|
|
case FMOD_STUDIO_LOADING_STATE_LOADING:
|
|
return "loading ";
|
|
case FMOD_STUDIO_LOADING_STATE_LOADED:
|
|
return "loaded ";
|
|
case FMOD_STUDIO_LOADING_STATE_ERROR:
|
|
// Show some common errors
|
|
if (loadResult == FMOD_ERR_NOTREADY)
|
|
{
|
|
return "error (rdy)";
|
|
}
|
|
else if (loadResult == FMOD_ERR_FILE_BAD)
|
|
{
|
|
return "error (bad)";
|
|
}
|
|
else if (loadResult == FMOD_ERR_FILE_NOTFOUND)
|
|
{
|
|
return "error (mis)";
|
|
}
|
|
else
|
|
{
|
|
return "error ";
|
|
}
|
|
default:
|
|
return "???";
|
|
};
|
|
}
|
|
|
|
//
|
|
// Helper function to return handle validity as a string.
|
|
// Just because the bank handle is valid doesn't mean the bank load
|
|
// has completed successfully!
|
|
//
|
|
const char* getHandleStateString(FMOD::Studio::Bank* bank)
|
|
{
|
|
if (bank == NULL)
|
|
{
|
|
return "null ";
|
|
}
|
|
else if (!bank->isValid())
|
|
{
|
|
return "invalid";
|
|
}
|
|
else
|
|
{
|
|
return "valid ";
|
|
}
|
|
}
|
|
|
|
//
|
|
// Callback to free memory-point allocation when it is safe to do so
|
|
//
|
|
FMOD_RESULT F_CALL studioCallback(FMOD_STUDIO_SYSTEM *system, FMOD_STUDIO_SYSTEM_CALLBACK_TYPE type, void *commanddata, void *userdata)
|
|
{
|
|
if (type == FMOD_STUDIO_SYSTEM_CALLBACK_BANK_UNLOAD)
|
|
{
|
|
// For memory-point, it is now safe to free our memory
|
|
FMOD::Studio::Bank* bank = (FMOD::Studio::Bank*)commanddata;
|
|
void* memory;
|
|
ERRCHECK(bank->getUserData(&memory));
|
|
if (memory)
|
|
{
|
|
free(memory);
|
|
}
|
|
}
|
|
return FMOD_OK;
|
|
}
|
|
|
|
//
|
|
// Main example code
|
|
//
|
|
int FMOD_Main()
|
|
{
|
|
void *extraDriverData = 0;
|
|
Common_Init(&extraDriverData);
|
|
|
|
FMOD::Studio::System* system;
|
|
ERRCHECK( FMOD::Studio::System::create(&system) );
|
|
ERRCHECK( system->initialize(1024, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, extraDriverData) );
|
|
ERRCHECK( system->setCallback(studioCallback, FMOD_STUDIO_SYSTEM_CALLBACK_BANK_UNLOAD) );
|
|
|
|
static const int BANK_COUNT = 4;
|
|
static const char* BANK_NAMES[] =
|
|
{
|
|
"SFX.bank",
|
|
"Music.bank",
|
|
"Vehicles.bank",
|
|
"VO.bank",
|
|
};
|
|
|
|
FMOD::Studio::Bank* banks[BANK_COUNT] = {0};
|
|
bool wantBankLoaded[BANK_COUNT] = {0};
|
|
bool wantSampleLoad = true;
|
|
|
|
do
|
|
{
|
|
Common_Update();
|
|
|
|
for (int i=0; i<BANK_COUNT; ++i)
|
|
{
|
|
if (Common_BtnPress((Common_Button)(BTN_ACTION1 + i)))
|
|
{
|
|
// Toggle bank load, or bank unload
|
|
if (!wantBankLoaded[i])
|
|
{
|
|
ERRCHECK(loadBank(system, (LoadBankMethod)i, Common_MediaPath(BANK_NAMES[i]), &banks[i]));
|
|
wantBankLoaded[i] = true;
|
|
}
|
|
else
|
|
{
|
|
ERRCHECK(banks[i]->unload());
|
|
wantBankLoaded[i] = false;
|
|
}
|
|
}
|
|
}
|
|
if (Common_BtnPress(BTN_MORE))
|
|
{
|
|
wantSampleLoad = !wantSampleLoad;
|
|
}
|
|
|
|
// Load bank sample data automatically if that mode is enabled
|
|
// Also query current status for text display
|
|
FMOD_RESULT loadStateResult[BANK_COUNT] = { FMOD_OK, FMOD_OK, FMOD_OK, FMOD_OK, };
|
|
FMOD_RESULT sampleStateResult[BANK_COUNT] = { FMOD_OK, FMOD_OK, FMOD_OK, FMOD_OK, };
|
|
FMOD_STUDIO_LOADING_STATE bankLoadState[BANK_COUNT] = { FMOD_STUDIO_LOADING_STATE_UNLOADED, FMOD_STUDIO_LOADING_STATE_UNLOADED, FMOD_STUDIO_LOADING_STATE_UNLOADED, FMOD_STUDIO_LOADING_STATE_UNLOADED };
|
|
FMOD_STUDIO_LOADING_STATE sampleLoadState[BANK_COUNT] = { FMOD_STUDIO_LOADING_STATE_UNLOADED, FMOD_STUDIO_LOADING_STATE_UNLOADED, FMOD_STUDIO_LOADING_STATE_UNLOADED, FMOD_STUDIO_LOADING_STATE_UNLOADED };
|
|
for (int i=0; i<BANK_COUNT; ++i)
|
|
{
|
|
if (banks[i] && banks[i]->isValid())
|
|
{
|
|
loadStateResult[i] = banks[i]->getLoadingState(&bankLoadState[i]);
|
|
}
|
|
if (bankLoadState[i] == FMOD_STUDIO_LOADING_STATE_LOADED)
|
|
{
|
|
sampleStateResult[i] = banks[i]->getSampleLoadingState(&sampleLoadState[i]);
|
|
if (wantSampleLoad && sampleLoadState[i] == FMOD_STUDIO_LOADING_STATE_UNLOADED)
|
|
{
|
|
ERRCHECK(banks[i]->loadSampleData());
|
|
}
|
|
else if (!wantSampleLoad && (sampleLoadState[i] == FMOD_STUDIO_LOADING_STATE_LOADING || sampleLoadState[i] == FMOD_STUDIO_LOADING_STATE_LOADED))
|
|
{
|
|
ERRCHECK(banks[i]->unloadSampleData());
|
|
}
|
|
}
|
|
}
|
|
|
|
ERRCHECK( system->update() );
|
|
|
|
Common_Draw("==================================================");
|
|
Common_Draw("Bank Load Example.");
|
|
Common_Draw("Copyright (c) Firelight Technologies 2012-2025.");
|
|
Common_Draw("==================================================");
|
|
Common_Draw("Name Handle Bank-State Sample-State");
|
|
|
|
for (int i=0; i<BANK_COUNT; ++i)
|
|
{
|
|
char namePad[64] = {0};
|
|
int bankNameLen = strlen(BANK_NAMES[i]);
|
|
memset(namePad, ' ', 15);
|
|
strncpy(namePad, BANK_NAMES[i], bankNameLen);
|
|
|
|
Common_Draw("%s %s %s %s",
|
|
namePad,
|
|
getHandleStateString(banks[i]),
|
|
getLoadingStateString(bankLoadState[i], loadStateResult[i]),
|
|
getLoadingStateString(sampleLoadState[i], sampleStateResult[i]));
|
|
}
|
|
|
|
Common_Draw("");
|
|
Common_Draw("Press %s to load bank 1 via %s",Common_BtnStr(BTN_ACTION1), BANK_LOAD_METHOD_NAMES[0]);
|
|
Common_Draw("Press %s to load bank 2 via %s",Common_BtnStr(BTN_ACTION2), BANK_LOAD_METHOD_NAMES[1]);
|
|
Common_Draw("Press %s to load bank 3 via %s",Common_BtnStr(BTN_ACTION3), BANK_LOAD_METHOD_NAMES[2]);
|
|
Common_Draw("Press %s to load bank 4 via %s",Common_BtnStr(BTN_ACTION4), BANK_LOAD_METHOD_NAMES[3]);
|
|
Common_Draw("Press %s to toggle sample data", Common_BtnStr(BTN_MORE));
|
|
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
|
|
|
|
Common_Sleep(50);
|
|
} while (!Common_BtnPress(BTN_QUIT));
|
|
|
|
ERRCHECK( system->unloadAll() );
|
|
ERRCHECK( system->flushCommands() );
|
|
ERRCHECK( system->release() );
|
|
|
|
Common_Close();
|
|
|
|
return 0;
|
|
}
|