/*============================================================================== 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 // // 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(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; iunload()); 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; iisValid()) { 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; iunloadAll() ); ERRCHECK( system->flushCommands() ); ERRCHECK( system->release() ); Common_Close(); return 0; }