Skip to content

Stl.Modulation and Fades

The fades system provides timed volume modulation for IVLS voices. It is built from Stl.Modulation (base data and the fire function), Fades (the convenience API and flow registration), and the internal playback nodes FadeOut and FadeIn.

Why This Exists

Fading sounds in and out in Kontakt is not trivial when you need the fade to: - Interact with other modulation (CC, LFO, VMC modbits) - Work on step-sequenced sounds whose event IDs may be recycled - Compose with other simultaneous fades on the same voice - Remain cancellable if the source voice dies early

Building this on top of VMC modbits and IVLS voices makes all of those things automatic.

Key Concepts

The Modulation Voice Model

ivls.fire_modulation creates a new voice that travels its own flow (FadeOut or FadeIn), asynchronously from the voice being faded:

function ivls.fire_modulation(vo, time_ms, kill, curve, mod_flow) -> mod_vo
    mod_vo := Voice.copy(vo)
    Voice[mod_vo].mod_time        := time_ms
    Voice[mod_vo].mod_target      := vo
    Voice[mod_vo].mod_kill_on_end := kill
    Voice[mod_vo].mod_curve       := curve
    ivls.reflow_voice(mod_vo, mod_flow)
end function

The modulation voice is an independent IVLS voice. Calling ivls.play(mod_vo) launches the ramp asynchronously. Releasing mod_vo externally cancels the ramp early.

Fade Functions

declare mod_vo := fades.out(vo, time_ms, kill)           // linear fade out
declare mod_vo := fades.in(vo, time_ms)                  // linear fade in
declare mod_vo := fades.curve_out(vo, time_ms, curve, kill)  // shaped fade out
declare mod_vo := fades.curve_in(vo, time_ms, curve)         // shaped fade in

Call ivls.play(mod_vo) to start the ramp. The kill parameter controls whether the target voice is released when the fade completes.

The Curve Parameter

The curve parameter shapes the exponential ramp. 0 is linear. Positive values produce a convex curve (faster at start, slower at end). Negative values produce a concave curve. Range is -100 to 100.

FadeOut Internals

FadeOut allocates a MULT volume modbit, attaches it to the target voice, and ramps from 10000 (100%) to 0 over mod_time milliseconds in 5 ms steps:

declare mb := Modbit.local_modbit(ivls.mod_par.VOLUME, ivls.mod_type.MULT)
Modbit.add_to_voice(mb, target)

while path_on and (t < mod_time) and check_ref(Voice, target)
    voice_mod.float_calc := math.curve_exp(float(curve), 1.0 - (float(t)/float(mod_time)), 0, 10000)
    Modbit.set(mb, int(voice_mod.float_calc))
    t := t + 5
    ivls.wait_ms(5)
end while

Modbit.set(mb, 0)
if kill_on_end = YES
    ivls.release_voice(target)
end if

The loop exits early if the modulation voice is released (path_on becomes false) or if the target voice no longer exists (check_ref fails).

Connection to VMC Modbits

Modbit.add_to_voice(mb, target) registers the modbit against the target voice. Multiple modbits combine: MULT modbits multiply together. A fade-out modbit and a sustain-pedal modbit on the same voice both apply and combine multiplicatively.

This is what makes VMC fades compose with other modulation — they are just modbits in the tree, subject to the same combination rules as everything else.

Equal-Power Crossfade

For legato crossfades, simultaneous fade-out and fade-in produce constant perceived loudness when the curves are complementary:

fades.curve_out(src_vo, crossfade_ms, out_curve, YES)    // fade out + kill source
fades.curve_in(dest_vo, crossfade_ms, in_curve)          // fade in destination

Tokyo Scoring computes the companion curve via tky.legato.calculate_equal_power_companion_curve(), which produces a sine-based complement ensuring out² + in² = 1 at every point. The out-curve varies by dynamic layer and vibrato state.

VMC Fade vs. Native fade_out()

Native fade_out() VMC fade
Cost Very cheap More expensive
VMC visibility None Full
Works for step-seq sounds No Yes
Composes with other modbits No Yes

Use native fade_out() for simple cheap attenuation when you do not need composability. Use VMC fades for crossfades, for step-sequenced sounds, and whenever the modulation must interact with other systems.

Including the Fade System

define Stl.Fades := Fades, FadeOut, FadeIn

Add this to the product's node composition list. Fades declares the two flows and registers FadeOut and FadeIn into them during cb Flows.

Connections to Other Parts of IVLS

Fades are built entirely on vmc modbits. stl-play-event calls VMC.register_sound_voice which makes the target voice responsive to modbit updates from fades. stl-legato uses fades.curve_out and fades.curve_in for equal-power crossfades. stl-portamento uses the same modbit infrastructure for the TUNE ADD modbit but does not use the fade API.

Patterns and Caveats

  • After calling fades.out(vo, ...), always call ivls.play(mod_vo) on the returned voice. The ramp does not start until the modulation voice is played.
  • The 5 ms step granularity means very short fades (< 10 ms) may not complete smoothly. For sub-10 ms transitions, consider a single Modbit.set call instead.
  • Multiple simultaneous fades on the same voice combine multiplicatively. If a voice has both a fade-out modbit (dropping to 0%) and a CC modbit (at 80%), the combined result is 0%. Plan modbit interactions carefully.
  • kill = YES releases the target voice after the fade completes. If the target voice was already released externally before the fade finishes, the check_ref(Voice, target) guard in the loop will exit cleanly.
  • vmc — the modbit system that fades are built on
  • stl-legato — uses fades.curve_out / fades.curve_in for crossfades
  • stl-play-event — VMC.register_sound_voice enables modbit updates on playing sounds