Skip to content

Modrix


A finished instrument doesn't just play samples -- it lets the performer shape the sound in real time. LFOs modulate filter cutoff. Envelopes control volume swell. MIDI CCs drive vibrato depth. A modulation matrix connects any modulation source to any destination parameter, with configurable depth and routing.

Modrix is a standalone modulation matrix system for IVLS instruments. It provides LFO, AHDSR envelope, CC, velocity, key range, random, stepper, and other modulator types, routable to any engine box parameter through a visual routing interface.

Modrix is a product subsystem, not part of core IVLS. It is integrated into products like Polycore that need deep modulation capabilities.

Python Package Integration


Modrix is configured through a Python package that generates both KSP code and NKA metadata. The typical integration has two steps:

1. Create the Modrix Instance

from modrix import create, emit

modrix = create(extra_mods=[], nka_data_dir="./Resources/data")

create() builds the modulation matrix configuration:

  • Combines the built-in modulators (LFO, AHDSR, Stepper, Velocity, Key Range, CC, Pitch Bend, Aftertouch, Random, and others) with any product-specific extra modulators
  • Generates engine box metadata (ranges, defaults, moddability flags) for every parameter of every modulator
  • Saves the metadata as an NKA file for runtime loading

2. Emit the KSP Code

ksp_code = emit(modrix)

emit() renders the KSP source code from Jinja2 templates, producing all the node definitions, parameter engines, and routing infrastructure that the runtime system needs.

The output is imported into the product's build as generated code.

Modulator Types


Modrix ships with a comprehensive set of modulators:

Type Description Key Parameters
LFO Low-frequency oscillator Waveform, Rate, Sync, Phase, Rectify, Width, Quantize
AHDSR Envelope generator Attack, Hold, Decay, Sustain, Release, Curves
Stepper Step sequencer Steps, Values, Key Map
Velocity Note velocity mapping Curve
Vel Range Velocity range gate Low, High, Continuous, Curve
Key Range Key range mapping Low, High, Continuous, Curve
CC MIDI controller CC Number, Curve
CC Range CC range gate CC Number, Low, High, Continuous, Curve
Pitch Bend Pitch bend wheel Curve, Quantize
Aftertouch Channel aftertouch Curve
Random Random value generator Unipolar, Noise, Curve
Keyswitch Note-triggered switch Note, Latch, Use Velocity
Macro NKS macro knob Index, Unipolar, Curve
XY Pad Two-axis pad Index, Axis, Unipolar, Curve

Each modulator type is defined as a Modulator dataclass with a list of Parameter dataclasses. Parameters have names, ranges, defaults, and flags for moddability, table display, and labeling.

The System API


A product integrates with Modrix by implementing a set of system API functions. These functions tell Modrix how to read from and write to the product's parameter system.

There are 9 required functions that every participating system must implement:

  • get_control_min(routing) -- return the minimum value for the routed parameter
  • get_control_max(routing) -- return the maximum value for the routed parameter
  • get_control_base(routing) -- return the current unmodulated value
  • modulate(routing, mod_offset) -- apply a modulation offset to the parameter
  • base_from_proxy(routing, ui_id) -- read a parameter value from a proxy UI control
  • base_to_proxy(routing, ui_id) -- write a parameter value to a proxy UI control
  • set_base_default(routing, ui_id) -- reset a parameter to its default via a proxy
  • get_routing_name(routing) -- return the display name for a routing destination
  • get_routing_category(routing) -- return the category name for a routing destination

And 7 optional functions for extended behavior:

  • fill_modrix_browser_destinations() -- populate the routing browser UI
  • get_routing_compat(route, a, b) -- check if two routing slots are compatible
  • dismiss_destination(routing) -- handle cleanup when a routing is removed
  • xy_to_system(ui_id, pad_idx) -- sync XY pad values to the system
  • analyze_routings() -- called on cache refresh to rebuild routing analysis

Each routing is encoded as a compact integer with fields for the target system, layer, and parameter. The MODRIX.ROUTE(routing, field) macro extracts individual fields.

The Processing Loop


At runtime, Modrix processes modulation in a continuous loop:

  1. Modulator tick -- each active modulator computes its current output value (LFO phase, envelope level, CC value, etc.)
  2. Routing map -- the system iterates through all active routings, computing the modulation offset for each destination by multiplying the modulator output by the routing depth
  3. Parameter bind -- for each routing, the modulate() function is called on the target system, which applies the offset to the parameter's live value

The routing cache is rebuilt whenever routings change. The Modrix.RefreshCache custom callback notifies participating systems to update their internal routing analysis.

Integration Example


Here is a condensed view of how Polycore integrates with Modrix:

node Polycore.ModrixAPI:
    cb Init:
        { Link NKS macro knobs to Modrix controllers }
        modrix.controllers.ctrl_link_add(modrix.controllers.par.macro_1, ...)
        modrix.controllers.push_data_id_to_ctrls()

    cb Modrix.RefreshCache:
        { Rebuild internal routing analysis }
        call polycore.analyze_routings()

    cb Functions:
        function polycore.get_control_min(routing) -> return
            return := plc.layers[0].par[par_route].engine_min
        end function

        function polycore.modulate(routing, mod_offset)
            plc.layers[layer].par[par].mod_offset := mod_offset
        end function

        { ... remaining API functions ... }
end node

The product defines one node that implements the full system API. Modrix handles the modulator processing, routing management, and UI -- the product only needs to describe how its parameters are accessed.


Modrix is a significant subsystem -- its Python package, KSP templates, and runtime nodes form a self-contained modulation engine. Products that need modulation integrate with it through the system API; products that don't need modulation simply don't include it.