Concept
Concurrency
Voice limits with a steal strategy. Keeps polyphony bounded; chooses which voice dies when the limit is hit.
TL;DR
A bus can cap how many voices play simultaneously. When the cap is reached
and a new voice arrives, a steal strategy picks an existing voice
to terminate so the new one can play. This is what keeps
spam-button × 50ms × 30s from burning your CPU.
Mental model
Strategies
- oldest — kill the voice that started first. Default. Best for SFX rain.
- lowest-priority — kill the voice with the smallest
priority. Best when you have a hierarchy (hero attack > footstep > ambience tick). - quietest — kill the voice with the lowest perceived loudness. Falls back to
oldestwhen meters aren't available. - none — reject the new voice. The returned
Voiceis alreadyended.
API
createEngine({
buses: {
sfx: { concurrency: { max: 32, steal: 'oldest' } },
voice: { concurrency: { max: 1, steal: 'lowest-priority' } },
},
}); engine.bus('sfx').setConcurrency({ max: 8, steal: 'lowest-priority' }); Live demo
Twiddle max and steal; mash "Fire voice" — watch slots fill,
then the steal logic in action when you exceed the cap.
Recipes
Protect important voices
// Hero hit — protected from stealing.
engine.sound('player-hit').play({ priority: 10 });
// Footstep — cheap, expendable.
engine.sound('footstep').play({ priority: 0 });
With steal: 'lowest-priority', the player-hit voice
survives until either it ends naturally or another priority-10 voice
arrives.
Pitfalls
Don't use
Rejecting voices makes the player notice missing sounds in dense
moments — far worse than stealing one. Reserve steal: 'none' for SFX.'none' for
"voice" or "alarm" buses where you want the existing announcement to
finish uninterrupted.
Don't set max too low for sprites.
A cascading match-3 can fire 12+ SFX in one frame; max: 4
will sound chopped. Profile real gameplay before clamping.