Choosing Static vs Stream Audio Sources in LÖVE: A Practical Decision Guide
Decide when to decode audio fully into RAM with static sources or stream from disk in LÖVE, including memory trade-offs, latency, and a validation sketch.
18 Oct 2025, 15:44 UTC

Loading audio in LÖVE forces an early engineering decision: decode the entire file into memory or stream it from disk during playback. Choose wrong and you either burn RAM on a short sound effect or freeze the frame while a long track loads. The safe default is static for short, repeated sounds and stream for long background audio, but the exact boundary depends on your constraints.
Constraints That Drive the Choice
Before picking a source type, weigh these project-specific limits:
- RAM budget: Static sources hold decoded PCM (pulse-code modulation, the raw uncompressed audio samples) in memory. A 10-second stereo WAV at 44.1 kHz can consume several megabytes.
- Load-time hitch tolerance: Decoding a large file on the main thread causes a frame hitch—a visible pause that stutters animation.
- Playback start latency: Static sources begin instantly; streamed sources may need one or more frames to buffer before audible output.
- Seeking requirements: Static sources support arbitrary seeking. Streamed seeking depends on the audio format and the backend LÖVE uses on that platform.
- Concurrent voice limits: A voice is one simultaneously playing sound channel. Exceeding the audio driver's active voice limit can silently stop the oldest playing sources.
- Target platform audio backend: Desktop and mobile builds may enforce different limits on simultaneous open streams.
Static vs Stream at a Glance
| Dimension | Static source | Stream source |
|---|---|---|
| Memory footprint | High: full decoded PCM in RAM | Low: small buffer, decoded incrementally |
| Load behavior | Decodes once at load; can stall the frame | Opens file handle; minimal upfront cost |
| Start latency | Immediate | Small delay while initial buffer fills |
| Seeking | Fully supported | Format- and backend-dependent |
| Concurrent instances | Many, bounded by voice limit | Few; platform-specific stream ceiling |
| Typical use | SFX, UI clicks, short loops | Music, ambient beds, long voice lines |
Trade-offs in Practice
Static sources trade memory for responsiveness. Because the compressed file is expanded to PCM at load time, playback is instantaneous and seeking is trivial. The cost is RAM proportional to the decoded size, not the compressed file size. A large static WAV can cause a noticeable frame hitch at load.
Streamed sources trade a small startup delay for a tiny memory footprint. They are appropriate for long tracks, but exceeding the platform's concurrent stream limit can cause playback failures without an explicit error. Prefer Ogg Vorbis for streamed audio; some formats have weak or inconsistent seeking and streaming support across LÖVE audio backends.
Concrete Implementation
Place the following in your LÖVE project's main.lua or a module loaded during love.load(). It runs inside the LÖVE runtime, not a system shell. The process needs read access to the asset files in your project directory or mounted archives.
local function loadAudio()
local beforeSfx = collectgarbage('count')
local sfx = love.audio.newSource('jump.wav', 'static')
local afterSfx = collectgarbage('count')
local beforeMusic = collectgarbage('count')
local music = love.audio.newSource('track.ogg', 'stream')
local afterMusic = collectgarbage('count')
print('SFX type:', sfx:getType(), 'KB delta:', afterSfx - beforeSfx)
print('Music type:', music:getType(), 'KB delta:', afterMusic - beforeMusic)
sfx:play()
music:setLooping(true)
music:play()
print('SFX playing immediately:', sfx:isPlaying())
print('Music playing immediately:', music:isPlaying())
end
Expected checks: getType() should return 'static' and 'stream' respectively. The memory delta for the static source reflects its decoded PCM cost; the stream source delta should be negligible. Calling isPlaying() immediately after play() typically shows the static source active while the stream source may still be buffering.
Validating on Your Target Build
Memory and latency behave differently across platforms. Run this validation on the actual desktop or mobile build you intend to ship.
- Memory cost: Record
collectgarbage('count')before and after each load. A large static WAV will show a significant jump; a streamed Ogg will not. - Start latency: Call
isPlaying()on the same frame asplay(). If a streamed source returns false, it needs additional frames before audio is audible. - Concurrent stream ceiling: Increment the number of simultaneously playing streamed sources until playback stops. Document the platform limit; exceeding it can fail silently.
- Voice saturation: Trigger many short static sounds at once. Depending on the driver, the oldest playing sounds may stop without warning when the active voice limit is reached.
Limitations and Risks
- Static WAV files consume RAM proportional to decoded PCM size, not compressed file size.
- Streamed sources are subject to platform-specific limits on simultaneous open streams; exceeding them can cause playback failures without an explicit error.
- Seeking support for streamed sources varies by format and audio backend.
- Exceeding
love.audio's active voice limit may silently stop the oldest playing sounds depending on the driver.
A practical way to check the result is to keep the validation sketch in a debug build and run it on every target platform before finalizing your audio budget.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.