VMC (Voice Modulation Container)¶
A VMC is a reference-counted modulation data store paired with a voice. Every voice has a vmc field pointing to its VMC. The VMC tree mirrors the voice tree structurally, but is managed by reference counting — a VMC is cleaned up when its reference count reaches zero and it has no child VMCs.
Why VMCs Exist¶
VMCs provide the mechanism for modulation to persist beyond a note's natural release point. The VMC itself does not magically outlive its voice — it stays alive as long as its reference count is above zero. The developer is responsible for managing voice lifetimes to keep VMCs alive when needed.
For example, a legato crossfade needs a volume modbit to keep ramping after the source voice's key is released. The developer accomplishes this by keeping a voice reference alive (via Voice.copy() pinning the ref count, or by running modulation code in a NoteOff callback on the releasing voice). The VMC provides the container; the developer provides the lifecycle management.
Key Concepts¶
The VMC Tree¶
VMCs shadow the voice tree. When a child voice is created from a parent, its VMC becomes a child of the parent's VMC:
Voice tree: VMC tree:
parent_vo parent_vmc
↓ ↓
child_vo child_vmc
↓ ↓
sound_vo sound_vmcA VMC is cleaned up when its ref_count reaches zero and it has no child VMCs. If the developer keeps a voice reference alive (via copy, runtime, or NoteOff callback logic), the associated VMC stays alive too.
Key invariant: If a child VMC exists, its entire ancestor chain must continue to exist. Ancestors can never be pruned while a descendant is alive.
Modbits¶
A Modbit is the atomic unit of modulation. It represents a single continuous value applied to a single parameter — additively or multiplicatively — and is attached to a VMC. When a Modbit's value changes, the change propagates to all sound voices that sit below it in the VMC tree.
const ivls.mod_par
VOLUME // additive millibels or multiplicative (10000 = 100%)
PAN // additive pan units
TUNE // additive cents × 1000
// + script mods declared via Modbit.SCRIPT_MODS
end const
const ivls.mod_type
ADD
MULT
end constCreating and using a modbit:
// Local modbit: deleted when deregistered from all VMCs
declare mb := Modbit.local(ivls.mod_par.VOLUME, ivls.mod_type.MULT)
// Attach to a voice's VMC
Modbit.add_to_voice(mb, target_vo)
// Set value — propagates immediately to all registered sound voices
Modbit.set(mb, 8000) // 80% volume for MULTFor MULT parameters: 0–10000 represents 0%–100%. For ADD parameters: VOLUME uses millibels, TUNE uses cents × 1000.
EMCache¶
Each sound voice gets an EMCache — a per-voice cache of the summed ADD and MULT values for every mod parameter. When any Modbit above a sound voice in the VMC tree changes, IVLS walks down to all registered sound voices and updates their EMCaches.
VMC.register_sound_voice(vo) is called automatically when a sound event is triggered. It allocates the EMCache and walks the entire ancestor tree to apply all existing modbits immediately. The EMCache means a sound voice always has its current total modulation available without traversing the VMC tree on each access.
Script Mods¶
Script mods extend the parameter list with custom parameters, such as per-band EQ gain:
define Modbit.SCRIPT_MODS += EQ_GAIN1
declare const EQ_GAIN1.IDX := 43 // Kontakt script parameter index
declare const EQ_GAIN1.BASE := 500000 // base valueWhen Modbit.set is called on a script mod parameter, IVLS calls set_event_par_arr on all registered sound voices.
VMC Manipulation Functions¶
Four functions create or restructure VMCs:
| Function | Use case |
|---|---|
ivls.make_modulable_voice(tpl) |
Spawn a child that inherits parent modulation but has its own scope |
ivls.localize_vmc(vo) |
Narrow scope for a branch without cutting parent inheritance |
ivls.isolate_vmc(vo) |
Cut inheritance entirely — release noise, anything that should not receive further updates |
ivls.replace_vmc(vo) |
Swap out modulation context during legato or transition |
Fades¶
Fades are built on VMC modbits. The fade system creates a modulation voice, attaches a MULT volume modbit, and drives it over time with ivls.wait_ms(5) ticks.
declare mod_vo := fades.out(vo, time_ms, kill) // fade out
declare mod_vo := fades.in(vo, time_ms) // fade in
declare mod_vo := fades.curve_out(vo, time_ms, curve, kill)
declare mod_vo := fades.curve_in(vo, time_ms, curve)The returned mod_vo is a child voice. Call ivls.play(mod_vo) to launch the ramp. See stl-modulation-fades for the full API.
Legato Crossfade Pattern (from Tokyo)¶
// Fade out the source
declare src_fade_vo := fades.curve_out(src_vo, out_ms, out_curve, TRUE)
ivls.play(src_fade_vo)
// Fade in the transition interval
declare trans_vo := tky.play_artic_voice(self, artic, voice_type, tky.flows.play_interval)
declare trans_fade_in_vo := fades.curve_in(trans_vo, in_ms, in_curve)
ivls.play(trans_fade_in_vo)
ivls.play(trans_vo)VMC Fade vs. Native fade_out()¶
Native fade_out() |
VMC fade | |
|---|---|---|
| Cost | Very cheap | More expensive (taskfunc + loop) |
| VMC visibility | None | Full — modbit is in the tree |
| Works for step-seq sounds | No — event IDs may be recycled | Yes |
| Composes with other modbits | No | Yes |
Use native fade_out() for simple cheap attenuation on a specific event. Use VMC fades when modulation must compose, when working with step-sequenced sounds, or when downstream systems need to observe the fade.
Release Noise Isolation¶
When spawning a release noise voice, use ivls.isolate_vmc to cut inheritance so it does not receive further real-time modulation updates:
cb NoteOff:
declare rel_vo := ivls.new_formal_voice(self)
ivls.isolate_vmc(rel_vo) // new root VMC — inherits nothing going forward
ivls.reflow_voice(rel_vo, mf.release_flow)
ivls.play(rel_vo)The release voice fires with the summed modulation state in place at the moment isolate_vmc was called, then receives no further updates. Do not manually replicate upstream modbits onto the release voice — just isolate it.
Connections to Other Parts of IVLS¶
The VMC tree mirrors the voices voice tree. nodes that trigger sounds register them with VMC.register_sound_voice (done automatically inside stl-play-event). The stl-modulation-fades module builds on VMC modbits for all timed volume modulation. modrix routes modulation through the VMC system to reach Kontakt parameters.
Patterns and Caveats¶
- VMC lifetime is managed by
ref_count. A voice holding a VMC reference increments the count; deleting the voice decrements it. When a VMC's count reaches zero and it has no children, it becomes deletable. - Multiple modbits on the same voice combine: MULT modbits multiply together, ADD modbits sum. A fade-out modbit and a sustain-pedal modbit on the same voice both apply.
ivls.isolate_vmcis a cut — the isolated voice receives the current accumulated state at the moment of isolation and nothing afterward. Use it for sounds that should be unresponsive to ongoing CC movement (release samples, one-shots).ivls.localize_vmcnarrows scope without cutting — the voice still receives modulation from ancestors above the localization point.
Related¶
- voices — Voice struct, lifecycle, and the relationship between voices and VMCs
- stl-modulation-fades — full fade API reference and curve types