Skip to content

️🛠 - Implement synthesizer voice culling #106

Description

@maxwellmattryan

Description

The DSP graph currently processes all blocks unconditionally during the audio callback, regardless of whether a voice is actively producing sound. This leads to unnecessary CPU usage when:

  • No note is currently playing (voice is idle)
  • A voice has completed its release phase and the envelope has reached the floor threshold
  • The signal passing through a chain of blocks has dropped below an audible level

Proposed Solution

Implement a voice culling system that tracks voice activity state and skips processing for voices that aren't producing meaningful output. This optimization should:

  1. Track voice lifecycle state - Extend VoiceState or EnvelopeBlock to expose whether a voice is idle, active, or releasing
  2. Provide culling hooks - Allow the graph or plugin to query whether processing should be skipped
  3. Support threshold-based culling - Optionally detect when output signals fall below a configurable floor (e.g., -120dB)
  4. Maintain real-time safety - No allocations or locks in the culling decision path

Current Implementation Context

  • VoiceState (bbx_dsp/src/voice.rs) tracks monophonic voice state with legato support
  • EnvelopeBlock (bbx_dsp/src/blocks/modulators/envelope.rs) has EnvelopeStage enum (Idle, Attack, Decay, Sustain, Release)
  • Envelope uses ENVELOPE_FLOOR constant (~-120dB) for floor detection
  • Graph processes all blocks in topological order without activity checks

Design Considerations

  • Should work with current monophonic design and future polyphonic voice pool
  • Culling decision should be cheap (branch on stage/flag, not signal analysis)
  • Consider exposing is_idle() or should_cull() API on EnvelopeBlock
  • May need to propagate "active" state through connected blocks in graph

Open questions

  • Should culling be opt-in per block or automatic based on envelope state?
  • How should culling interact with blocks that have internal state (filters with decay, reverb tails)?
  • Should there be a configurable "tail time" after release to handle effects?
  • When polyphony is added, should voice culling feed into voice stealing priority?

Acceptance criteria

  • Voice activity state (idle/active/releasing) is queryable from EnvelopeBlock or VoiceState
  • Processing can be conditionally skipped for idle voices without introducing artifacts
  • No allocations or locks added to the audio thread for culling decisions
  • CPU usage measurably decreases when voices are idle vs. always processing
  • Existing examples and tests continue to work correctly

Tasks

TBD

Metadata

Metadata

Labels

No labels
No labels

Fields

No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions