Skip to content

Runtimes


The voice tree's auto-release behavior is exactly what you want most of the time. Release the parent, release the children, done. But there's one common scenario where it breaks down: the sustain pedal.

When you press a key and then release it while holding the sustain pedal, the sound should keep playing. But the key-up event naturally wants to release the voice and all its children. The voice tree doesn't know about the pedal -- it just knows the parent was released.

Runtimes solve this. A runtime is a container that "adopts" voices, preventing them from being released when their parent is released. The voices stay alive as long as the runtime exists, regardless of what happens to their parent in the voice tree.

Creating a Runtime


ivls.create_runtime() allocates a new runtime container:

declare my_runtime := ivls.create_runtime()

The runtime is now an empty container, ready to accept voices.

Adding Voices to a Runtime


ivls.add_voice_to_runtime() places a voice under the runtime's control:

ivls.add_voice_to_runtime(my_runtime, some_voice)

When a voice is added to a runtime, its auto_release is set to FALSE. This means it will no longer be released when its parent is released. The runtime now owns it.

You can add multiple voices to the same runtime:

ivls.add_voice_to_runtime(my_runtime, voice_a)
ivls.add_voice_to_runtime(my_runtime, voice_b)
ivls.add_voice_to_runtime(my_runtime, voice_c)

Releasing a Runtime


ivls.release_runtime() releases the runtime and all voices it contains:

ivls.release_runtime(my_runtime)

Every voice in the runtime is released, and the runtime container is freed. This is the single cleanup point -- you don't need to track individual voices.

How Stl.Pedals Uses Runtimes


This is exactly the pattern the built-in Stl.Pedals node uses for sustain pedal behavior:

  1. When the sustain pedal goes down, the node creates a new runtime
  2. When a note is released while the pedal is held, the voice is reparented into the runtime instead of being released
  3. When the sustain pedal comes up, the node releases the runtime, which releases all the sustained voices at once

Here's the conceptual pattern:

{ Pedal goes down }
pedal.runtime := ivls.create_runtime()

{ Note released while pedal is held -- reparent instead of releasing }
ivls.reparent_voice(voice, pedal.runtime)

{ Pedal comes up -- release everything }
ivls.release_runtime(pedal.runtime)

The ivls.reparent_voice() function is a convenience wrapper around ivls.add_voice_to_runtime(). It adds the voice to the runtime, which disables auto_release, so the voice survives the key-up event.

When the Pedal Lifts


The beauty of this pattern is the cleanup. When ivls.release_runtime() is called, every voice that was sustained through the pedal is released in one operation. You don't need to iterate over notes, check which ones were held, or manage a list of sustained voices. The runtime is the list.

Runtime Limits


The system supports up to 1024 concurrent runtimes. In practice, you'll rarely need more than a handful -- typically one per active pedal per MIDI channel.

Runtimes vs. the Voice Tree


Runtimes and the voice tree serve complementary purposes:

  • The voice tree handles the normal case: parent released, children released. This is the default for most voices.

  • Runtimes handle the exception: voices that need to outlive their parent. Sustain pedals, sostenuto, and any scenario where a voice's lifetime is determined by something other than its parent.

A voice can be part of the voice tree and belong to a runtime simultaneously. The tree structure (parent, child, siblings) remains intact -- the runtime just overrides the auto-release behavior.


Runtimes are a simple but powerful tool. By grouping voices into runtime containers, you can control their lifetime independently of the voice tree. The sustain pedal is the most common use case, but the same pattern works for any scenario where voices need to be held and released as a group.