Summary
LoadMusicStreamFromPhysFS() (raylib-physfs.h, around line 462) loads the whole file into a buffer via LoadFileDataFromPhysFS() and passes it to LoadMusicStreamFromMemory(). raylib's memory-based music decoders (stb_vorbis, dr_wav, dr_mp3, the module players) reference the caller's buffer rather than copying it, so the buffer must stay alive while the music plays. The wrapper correctly does not free it on success:
Music music = LoadMusicStreamFromMemory(extension, fileData, bytesRead);
// Unload the file data if the music failed to load.
if (music.ctxData == NULL) {
UnloadFileData(fileData);
}
return music;
…but the fileData pointer is then lost. UnloadMusicStream() only frees raylib's decoder state, not our buffer — so every successful LoadMusicStreamFromPhysFS() permanently leaks the entire file contents. For a game cycling through music tracks this grows unbounded.
Suggested Implementation
Add a matching unloader that frees the tracked buffer:
RAYLIB_PHYSFS_DEF void UnloadMusicStreamFromPhysFS(Music music); // Unload music loaded from PhysFS, freeing its file buffer
- Keep a small internal registry (static array of
{void* ctxData; unsigned char* fileData;} entries, grown with MemRealloc) mapping each successfully loaded Music.ctxData to its buffer.
UnloadMusicStreamFromPhysFS() calls UnloadMusicStream(music), then looks up music.ctxData in the registry, frees the buffer with UnloadFileData(), and removes the entry.
- Free any leftover registry entries in
ClosePhysFS() as a safety net (and free the registry itself).
- Document in the
LoadMusicStreamFromPhysFS() doc comment that it must be paired with UnloadMusicStreamFromPhysFS() (plain UnloadMusicStream() leaks the buffer), and update the README API block.
Alternative considered: freeing fileData right after LoadMusicStreamFromMemory() is wrong — the decoders stream from that buffer and it would be a use-after-free during playback.
QA
- Test in
test/raylib-physfs-test.c: load a music file from the mounted resources (an .ogg/.xm in test/resources/), assert ctxData != NULL, then UnloadMusicStreamFromPhysFS() — should not crash, and repeated load/unload in a loop should not grow memory (spot-check with valgrind locally / ASAN).
- Missing-file path still returns a zeroed
Music and frees nothing.
examples/audio/audio_music_stream.c updated to use the new unloader.
Context
- The existing
if (music.ctxData == NULL) UnloadFileData(fileData); failure-path handling was deliberate (buffer must outlive playback), so this is a design gap, not a regression from a specific commit.
- From a planning pass on 2026-07-20. No dependencies on other filed issues.
Summary
LoadMusicStreamFromPhysFS()(raylib-physfs.h, around line 462) loads the whole file into a buffer viaLoadFileDataFromPhysFS()and passes it toLoadMusicStreamFromMemory(). raylib's memory-based music decoders (stb_vorbis, dr_wav, dr_mp3, the module players) reference the caller's buffer rather than copying it, so the buffer must stay alive while the music plays. The wrapper correctly does not free it on success:…but the
fileDatapointer is then lost.UnloadMusicStream()only frees raylib's decoder state, not our buffer — so every successfulLoadMusicStreamFromPhysFS()permanently leaks the entire file contents. For a game cycling through music tracks this grows unbounded.Suggested Implementation
Add a matching unloader that frees the tracked buffer:
{void* ctxData; unsigned char* fileData;}entries, grown withMemRealloc) mapping each successfully loadedMusic.ctxDatato its buffer.UnloadMusicStreamFromPhysFS()callsUnloadMusicStream(music), then looks upmusic.ctxDatain the registry, frees the buffer withUnloadFileData(), and removes the entry.ClosePhysFS()as a safety net (and free the registry itself).LoadMusicStreamFromPhysFS()doc comment that it must be paired withUnloadMusicStreamFromPhysFS()(plainUnloadMusicStream()leaks the buffer), and update the README API block.Alternative considered: freeing
fileDataright afterLoadMusicStreamFromMemory()is wrong — the decoders stream from that buffer and it would be a use-after-free during playback.QA
test/raylib-physfs-test.c: load a music file from the mounted resources (an.ogg/.xmintest/resources/), assertctxData != NULL, thenUnloadMusicStreamFromPhysFS()— should not crash, and repeated load/unload in a loop should not grow memory (spot-check with valgrind locally / ASAN).Musicand frees nothing.examples/audio/audio_music_stream.cupdated to use the new unloader.Context
if (music.ctxData == NULL) UnloadFileData(fileData);failure-path handling was deliberate (buffer must outlive playback), so this is a design gap, not a regression from a specific commit.