Z zvuk
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

SLOTS — max: 3, steal: oldest slot 1 voice A slot 2 voice B slot 3 voice C new! voice D A is the oldest → stolen, slot freed AFTER: [ B C D ] — A.ended fires synchronously OTHER STRATEGIES lowest-priority quietest none — reject new
Slots are bounded. When full, the steal strategy picks a victim — synchronously — before play() returns.

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 oldest when meters aren't available.
  • none — reject the new voice. The returned Voice is already ended.

API

At construction ts
createEngine({
  buses: {
    sfx: { concurrency: { max: 32, steal: 'oldest' } },
    voice: { concurrency: { max: 1, steal: 'lowest-priority' } },
  },
});
Live tuning ts
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

ts ts
// 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 steal: 'none' for SFX.
Rejecting voices makes the player notice missing sounds in dense moments — far worse than stealing one. Reserve '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.

Related

  • Voicepriority on play.
  • Bus — where concurrency lives.