diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 3a71dc230..2efbc238f 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -261,7 +261,9 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior return -1; } - AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; + DataWin* audioGroup = ma->base.audioGroups[sound->audioGroup]; + DataWin_loadAudoIfNeeded(audioGroup, (uint32_t)sound->audioFile); + AudioEntry* entry = &audioGroup->audo.entries[sound->audioFile]; ma_decoder_config decoderConfig = ma_decoder_config_init_default(); result = ma_decoder_init_memory(entry->data, entry->dataSize, &decoderConfig, &slot->decoder); @@ -684,7 +686,9 @@ static float maGetSoundLength(AudioSystem* audio, int32_t soundOrInstance) { ma_result decResult; if (inAudo) { if (0 > sound->audioFile || (uint32_t) sound->audioFile >= ma->base.audioGroups[sound->audioGroup]->audo.count) return 0.0f; - AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; + DataWin* audioGroup = ma->base.audioGroups[sound->audioGroup]; + DataWin_loadAudoIfNeeded(audioGroup, (uint32_t)sound->audioFile); + AudioEntry* entry = &audioGroup->audo.entries[sound->audioFile]; ma_decoder_config decoderConfig = ma_decoder_config_init_default(); decResult = ma_decoder_init_memory(entry->data, entry->dataSize, &decoderConfig, &decoder); } else { @@ -750,6 +754,7 @@ static void maGroupLoad(AudioSystem* audio, int32_t groupIndex) { DataWinParserOptions options = {0}; options.parseAudo = true; + options.lazyLoadAudio = audio->dw->lazyLoadAudio; if (audio->dw->mappedFile) options.loadType = DATAWINLOADTYPE_MAP_FILE; DataWin *audioGroup = DataWin_parse(((MaAudioSystem*)audio)->fileSystem->vtable->resolvePath(((MaAudioSystem*)audio)->fileSystem, buf), options); diff --git a/src/audio/openal/al_audio_system.c b/src/audio/openal/al_audio_system.c index 228adf4e3..45ed55f0a 100644 --- a/src/audio/openal/al_audio_system.c +++ b/src/audio/openal/al_audio_system.c @@ -443,6 +443,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior return -1; } + DataWin_loadAudoIfNeeded(ma->base.audioGroups[sound->audioGroup], (uint32_t)sound->audioFile); AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; uint32_t channels = 0; @@ -889,6 +890,7 @@ static float maGetSoundLength(AudioSystem* audio, int32_t soundOrInstance) { bool inAudo = !isRegular || isEmbedded || isCompressed; if (inAudo) { if (0 > sound->audioFile || (uint32_t) sound->audioFile >= ma->base.audioGroups[sound->audioGroup]->audo.count) return 0.0f; + DataWin_loadAudoIfNeeded(ma->base.audioGroups[sound->audioGroup], (uint32_t)sound->audioFile); AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; uint32_t channels, sampleRate, bitsPerSample, audioDataLen; @@ -946,6 +948,7 @@ static void maGroupLoad(AudioSystem* audio, int32_t groupIndex) { DataWinParserOptions options = {0}; options.parseAudo = true; + options.lazyLoadAudio = audio->dw->lazyLoadAudio; if (audio->dw->mappedFile) options.loadType = DATAWINLOADTYPE_MAP_FILE; DataWin *audioGroup = DataWin_parse(((AlAudioSystem*)audio)->fileSystem->vtable->resolvePath(((AlAudioSystem*)audio)->fileSystem, buf), options); diff --git a/src/audio/web/web_audio_system.c b/src/audio/web/web_audio_system.c index e9741e8a5..a0e0361ac 100644 --- a/src/audio/web/web_audio_system.c +++ b/src/audio/web/web_audio_system.c @@ -207,7 +207,9 @@ static int32_t webPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prio return -1; } - AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; + DataWin* audioGroup = ma->base.audioGroups[sound->audioGroup]; + DataWin_loadAudoIfNeeded(audioGroup, (uint32_t)sound->audioFile); + AudioEntry* entry = &audioGroup->audo.entries[sound->audioFile]; ma_decoder_config decoderConfig = ma_decoder_config_init_default(); result = ma_decoder_init_memory(entry->data, entry->dataSize, &decoderConfig, &slot->decoder); @@ -547,7 +549,9 @@ static float webGetSoundLength(AudioSystem* audio, int32_t soundOrInstance) { ma_result decResult; if (inAudo) { if (0 > sound->audioFile || (uint32_t) sound->audioFile >= ma->base.audioGroups[sound->audioGroup]->audo.count) return 0.0f; - AudioEntry* entry = &ma->base.audioGroups[sound->audioGroup]->audo.entries[sound->audioFile]; + DataWin* audioGroup = ma->base.audioGroups[sound->audioGroup]; + DataWin_loadAudoIfNeeded(audioGroup, (uint32_t)sound->audioFile); + AudioEntry* entry = &audioGroup->audo.entries[sound->audioFile]; ma_decoder_config decoderConfig = ma_decoder_config_init_default(); decResult = ma_decoder_init_memory(entry->data, entry->dataSize, &decoderConfig, &decoder); } else { @@ -612,6 +616,7 @@ static void webGroupLoad(AudioSystem* audio, int32_t groupIndex) { DataWinParserOptions options = {0}; options.parseAudo = true; + options.lazyLoadAudio = audio->dw->lazyLoadAudio; DataWin *audioGroup = DataWin_parse(((WebAudioSystem*)audio)->fileSystem->vtable->resolvePath(((WebAudioSystem*)audio)->fileSystem, buf), options); arrput(audio->audioGroups, audioGroup); } diff --git a/src/data_win.c b/src/data_win.c index bd98a9b5d..2c590432f 100644 --- a/src/data_win.c +++ b/src/data_win.c @@ -2573,7 +2573,7 @@ void DataWin_loadTxtrIfNeeded(DataWin* dw, uint32_t textureId) { } } -static void parseAUDO(BinaryReader* reader, DataWin* dw) { +static void parseAUDO(BinaryReader* reader, DataWin* dw, bool loadAudioDataLazily) { Audo* a = &dw->audo; uint32_t count; @@ -2590,7 +2590,9 @@ static void parseAUDO(BinaryReader* reader, DataWin* dw) { a->entries[i].dataSize = BinaryReader_readUint32(reader); a->entries[i].dataOffset = (uint32_t)BinaryReader_getPosition(reader); // Load audio data into owned buffer - if (dw->mappedFile) { + if (loadAudioDataLazily) { + a->entries[i].data = nullptr; + } else if (dw->mappedFile) { a->entries[i].data = dw->mappedFile + a->entries[i].dataOffset; } else if (a->entries[i].dataSize > 0) { a->entries[i].data = (uint8_t *)safeMalloc(a->entries[i].dataSize); @@ -2602,6 +2604,31 @@ static void parseAUDO(BinaryReader* reader, DataWin* dw) { free(ptrs); } +void DataWin_loadAudoIfNeeded(DataWin* dw, uint32_t audioEntryId) { + Audo* a = &dw->audo; + AudioEntry* entry = &a->entries[audioEntryId]; + + if (!entry->present || entry->dataSize == 0) return; + if (entry->data != nullptr) return; + + if (!dw->lazyLoadFile) { + fprintf(stderr, "loadAudoIfNeeded: called without a lazy load file.\n"); + return; + } + + entry->data = (uint8_t *)safeMalloc(entry->dataSize); + + memset(entry->data, 0, entry->dataSize); + long old_seek = ftell(dw->lazyLoadFile); + fseek(dw->lazyLoadFile, entry->dataOffset, SEEK_SET); + size_t read = fread(entry->data, 1, entry->dataSize, dw->lazyLoadFile); + fseek(dw->lazyLoadFile, old_seek, SEEK_SET); + + if (read != entry->dataSize) { + fprintf(stderr, "loadAudoIfNeeded: couldn't read %u bytes to load audio entry %u.\n", entry->dataSize, audioEntryId); + } +} + // ===[ MAIN PARSE FUNCTION ]=== DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) { @@ -2842,7 +2869,7 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) { } else if (options.parseTxtr && memcmp(chunkName, "TXTR", 4) == 0) { parseTXTR(&reader, dw, chunkEnd, options.lazyLoadTextures); } else if (options.parseAudo && memcmp(chunkName, "AUDO", 4) == 0) { - parseAUDO(&reader, dw); + parseAUDO(&reader, dw, options.lazyLoadAudio); } else { printf("Unknown chunk: %.4s (length %u at offset 0x%zX)\n", chunkName, chunkLength, chunkDataStart - 8); } @@ -2874,7 +2901,8 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) { // If lazy-loading rooms, keep the file handle open for DataWin_loadRoomPayload, otherwise close it now dw->lazyLoadRooms = options.lazyLoadRooms; dw->lazyLoadTextures = options.lazyLoadTextures; - if (options.lazyLoadRooms || options.lazyLoadTextures) { + dw->lazyLoadAudio = options.lazyLoadAudio; + if (options.lazyLoadRooms || options.lazyLoadTextures || options.lazyLoadAudio) { dw->lazyLoadFile = file; dw->lazyLoadFilePath = safeStrdup(filePath); dw->fileSize = (size_t) fileSize; diff --git a/src/data_win.h b/src/data_win.h index 3e39ad975..3f892940e 100644 --- a/src/data_win.h +++ b/src/data_win.h @@ -50,6 +50,8 @@ typedef struct { bool lazyLoadRooms; // If true, TXTR objects will be loaded on demand via DataWin_loadTxtrIfNeeded, and unloaded if memory is tight. bool lazyLoadTextures; + // If true, AUDO objects will be loaded on demand via DataWin_loadAudoIfNeeded. + bool lazyLoadAudio; // When lazyLoadRooms is true, this list indicates which rooms should be loaded during load time instead of demand. They will also not be freed. StringBooleanEntry* eagerlyLoadedRooms; @@ -936,6 +938,7 @@ struct DataWin { size_t fileSize; // cached size of the DataWin, captured at parse time. Used for platforms where fseek(SEEK_END)+ftell is unreliable due to buffering (like the PlayStation 2). bool lazyLoadRooms; // mirrors the parser option so Runner can branch without re-reading options bool lazyLoadTextures; // ditto, but with TXTR pages + bool lazyLoadAudio; // ditto, but with AUDO entries }; DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options); @@ -956,5 +959,6 @@ void DataWin_bumpVersionTo(DataWin* dw, uint32_t major, uint32_t minor, uint32_t void GamePath_computeInternal(GamePath* path); PathPositionResult GamePath_getPosition(GamePath* path, float t); void DataWin_loadTxtrIfNeeded(DataWin* dw, uint32_t textureId); +void DataWin_loadAudoIfNeeded(DataWin* dw, uint32_t audioEntryId); #endif /* _BS_DATA_WIN_H_ */ diff --git a/src/desktop/main.c b/src/desktop/main.c index d29308795..ccb8bd045 100644 --- a/src/desktop/main.c +++ b/src/desktop/main.c @@ -248,6 +248,7 @@ typedef struct { bool lazyRooms; StringBooleanEntry* eagerRooms; // stb_ds string-keyed set of room names bool lazyTextures; + bool lazyAudio; DataWinLoadType loadType; int profilerFramesBetween; // 0 = disabled #ifdef ENABLE_VM_OPCODE_PROFILER @@ -420,6 +421,7 @@ static void printUsage(const char *argv0) { " --save-folder - Set the directory will save files will be stored\n" " --game-args - Arguments to pass to the game\n" " --lazy-textures - Load textures into VRAM on first use, improving startup times\n" + " --lazy-audio - Load audio into RAM on first use, reducing memory usage\n" " --load-type - Specify how data.win is loaded, per-chunk or all at once\n" #ifdef EABLE_VM_OPCODE_PROFILER " --profile-opcodes - Rank which GML opcodes were executed the most\n" @@ -479,6 +481,7 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) {"save-folder", required_argument, nullptr, 'B'}, {"game-args", required_argument, nullptr, 'N'}, {"lazy-textures", no_argument, nullptr, 'L'}, + {"lazy-audio", no_argument, nullptr, 'K'}, {"load-type", required_argument, nullptr, 999}, #ifdef ENABLE_VM_OPCODE_PROFILER {"profile-opcodes", no_argument, nullptr, 'Q'}, @@ -574,6 +577,9 @@ static void parseCommandLineArgs(CommandLineArgs* args, int argc, char* argv[]) case 'L': args->lazyTextures = true; break; + case 'K': + args->lazyAudio = true; + break; case 'e': shput(args->eventsToBeTraced, optarg, true); break; @@ -1038,6 +1044,7 @@ int main(int argc, char* argv[]) { options.loadType = args.loadType; options.lazyLoadRooms = args.lazyRooms; options.lazyLoadTextures = args.lazyTextures; + options.lazyLoadAudio = args.lazyAudio; options.eagerlyLoadedRooms = args.eagerRooms; DataWin* dataWin = DataWin_parse(currentDataWinPath, options);