# Concurrency · zvuk

zvuk v1.14.1 · MIT · https://zvuk.schmooky.dev/concepts/concurrency/

Voice limits with a steal strategy. Keeps polyphony bounded, and decides which voice dies when the limit is hit.

Concept

# Concurrency

Voice limits with a steal strategy. Keeps polyphony bounded, and decides which voice dies when the limit is hit.

Hold a button that fires a 50 ms click every frame. Thirty seconds later you have eighteen hundred live voices, a stalled audio thread, and a fan at full tilt. Nothing in the Web Audio API stops you.

Concurrency is a per-bus cap on how many voices may sound at once, together with a rule for what happens when a new voice arrives at a full bus. That rule is the _steal strategy_. It picks an existing voice to end so the new one can play, and it runs synchronously, before `play()` returns.

## How many voices can play at once?

As many as you allow. The cap is declared per bus, so a UI bus can hold four while an SFX bus holds thirty-two. A bus with no `concurrency` block is unbounded, which is the right default for music and the wrong one for anything a player can spam.

Slots are bounded. When full, the steal strategy picks a victim, synchronously, before play() returns.

## Strategies

-   **oldest** kills the voice that started first. This is the default, and the right call for SFX rain.
-   **lowest-priority** kills the voice with the smallest `priority`. Use it when you have a hierarchy: a hero attack outranks a footstep, which outranks an ambience tick.
-   **quietest** kills the least audible voice. It ranks candidates by an existing meter tap where one exists and by the voice's own gain otherwise, so it costs nothing per candidate.
-   **none** rejects the new voice. The returned [`Voice`](/api/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

Change `max` and `steal`, then mash "Fire voice". Slots fill, and once you pass the cap you can watch which voice the strategy picks.

Use your own sound

sfx busidle

max4steal strategyoldestlowest-priorityquietestnone (reject)

active: 0 / 4spawned: 0

fire voicestop all

Unlock & load

## 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 it ends naturally or another priority-10 voice arrives.

## Pitfalls

Don't use `steal: 'none'` for SFX.

Rejecting voices makes a player notice missing sounds in exactly the dense moments where they are paying attention. Stealing one is far less obvious. Reserve `'none'` for a voice or alarm bus, where you want the announcement that is already playing to finish.

Don't set max too low for sprites.

A cascading match-3 can fire twelve or more one-shots in a single frame. At `max: 4` that sounds chopped. Profile real gameplay before clamping.

## Related

-   [Voice](/concepts/voice/) carries the `priority` you set on play.
-   [Bus](/concepts/bus/) is where the concurrency block lives.
