Skip to content

The Cluster System


Everything in IVLS runs inside KSP taskfuncs -- asynchronous functions that can wait without blocking other voices. But KSP has a fundamental limitation: you cannot call a taskfunc from inside on release. The release callback runs in a restricted context that doesn't support async execution.

This is a serious problem. When the player lifts a key, IVLS needs to process the VOICE_OFF input through the same async node pipeline that handled the VOICE_ON. Nodes may need to wait for timing alignment, spawn release voices, or run cleanup logic that takes time. None of that is possible from on release directly.

The cluster system solves this by bridging the synchronous on release context into an async taskfunc.

The Three-Step Cycle


The cluster system works through a cycle of three steps:

Step 1: Tick

On the on release callback, the cluster marks that a voice needs async processing. It records which voice needs to be released and sets up the handoff.

Step 2: Synthetic Play

The cluster then fires a synthetic play_note -- a silent, internal note event that creates a new KSP thread. This new thread is in a context that supports taskfuncs. The synthetic note uses a password mechanism: it sends a special ID value that the on note callback checks.

Step 3: Password-Checked Dispatch

When the synthetic note arrives in on note, the system checks the password. If it matches, the system knows this is a cluster dispatch -- not a real MIDI note -- and routes it to Voice._process(ref), which runs the voice through its flow in a proper async taskfunc context.

The voice's input is set to ivls.input_type.VOICE_OFF before dispatch, so when nodes check the input type, they see it as a release and run their NoteOff callbacks.

What This Means in Practice


You never interact with the cluster system directly. When you write a node with cb NoteOff:, your code runs inside an async taskfunc -- you can call ivls.wait_ms(), spawn new voices, and do anything you'd normally do. The cluster system made that possible behind the scenes.

Here is what actually happens when a key is released:

  1. KSP fires on release
  2. The release handler sets Voice[play_vo].input := ivls.input_type.VOICE_OFF
  3. It calls Voice._process(play_vo), which starts the voice processing taskfunc
  4. The voice travels through its flow, hitting each node's NoteOff callback
  5. Nodes can wait, spawn releases, clean up resources -- all in async context

The extension ticks and waits in the release handler (wait_ticks, wait(DURATION_SIXTEENTH / 8)) ensure the voice's Kontakt sound event is properly extended past the MIDI note-off boundary, giving the async processing time to complete.

The TCM Stack


The cluster system relies on the TCM (Task Control Manager), which manages the pool of concurrent async tasks. Each voice processing pass consumes one slot on the TCM stack.

The TCM is initialized with a fixed capacity:

tcm.init(128)

This means up to 128 concurrent waiting tasks. Since node processing is brief (most nodes complete in microseconds), the practical limit is far higher than 128 simultaneous notes. The theoretical maximum is approximately 10,000 concurrent waiting nodes, because tasks that have completed their waits free their slots immediately.

The one-node-deep constraint means each cluster dispatch occupies a single TCM slot for the duration of its processing. Deep chains of ivls.wait() calls within a single voice path share that slot.

Why This Matters


Understanding the cluster system explains several things about how IVLS works:

  • Why nodes can use ivls.wait_ms() in NoteOff -- because NoteOff doesn't run in on release directly; it runs in a taskfunc that the cluster system created
  • Why there's a latency extension on release -- the synthetic note needs time to fire and dispatch before the original note's thread exits
  • Why Voice has release_fired as a field -- to prevent double-processing if the cluster fires multiple times
  • Why cluster.legal exists -- it's a gate that prevents dispatch during initialization or reload, when the system isn't ready for async processing

The cluster system is the engine that makes IVLS async voice processing possible. You'll never need to modify or configure it, but knowing it's there explains why the node model works the way it does.