Macros & Defines¶
Throughout this guide, you have seen lines like define FLOWS.MyFlow.NOTEPASS += Stl.RoundRobins and define STL.UI_CALLBACKS += my.browser.cb.tag without much explanation. These are not KSP built-ins -- they are part of the IVLS preprocessor's macro and define system, a text-substitution layer that runs before your code is compiled. Understanding this layer is essential because IVLS uses it everywhere: to register nodes, compose flows, declare voice fields, configure UI routing, and much more.
Defines: Text Replacement¶
A define creates a named text substitution. Wherever the compiler encounters the name, it replaces it with the value:
define MAX_VOICES := 64
declare voices[MAX_VOICES] { becomes: declare voices[64] }This is pure text replacement, not a variable declaration. The substitution happens at compile time, before any KSP parsing. The value can be anything -- a number, a string, a comma-separated list, even a partial expression.
Defines can also take parameters, turning them into parameterized defines (effectively inline macros):
define scale(#v#) := (#v# * 1000)
declare x := scale(5) { becomes: declare x := (5 * 1000) }The Accumulation Pattern¶
Standard define uses := and sets the value once. Redefining the same name is an error. But IVLS relies heavily on a second form: += (append):
define MY_LIST := Alpha
define MY_LIST += Beta
define MY_LIST += Gamma
{ MY_LIST is now: Alpha, Beta, Gamma }Each += appends to the existing value with a comma separator. This is the accumulation pattern -- the foundation of IVLS's extensibility. Instead of one central file that lists every node, flow, or callback, each file independently appends its contribution to a shared list.
You have already seen this with IVLS_NODES:
{ In round-robins.ksp }
define IVLS_NODES += Stl.RoundRobins
{ In purge.ksp }
define IVLS_NODES += Stl.Purge
{ In your product's browser.ksp }
define IVLS_NODES += MyBrowserThe framework collects all these += statements across every file, builds the complete list, then processes it. No single file needs to know about all the others.
Key IVLS lists built this way include:
IVLS_NODES-- every node in the instrumentFLOWS-- every flow definitionVoice.ADD_MEMBERS-- voice field extensionsSTL.UI_CALLBACKS-- UI callback registrationsSTL.UI_ROUTINES-- UI refresh routinesStl.Legato-- legato node composition chainKEYMAPS-- keymap definitions
There is also a =+ (prepend) form that inserts at the beginning of the list rather than the end.
Macros: Reusable Code Blocks¶
A macro defines a reusable block of code with text-substitution parameters:
macro set_group_volume(#group#, #value#)
set_engine_par(ENGINE_PAR_VOLUME, #value#, #group#, -1, -1)
end macroParameters are delimited with #. When the macro is called, each #param# is replaced with the argument text:
set_group_volume(0, 500000)
{ becomes: set_engine_par(ENGINE_PAR_VOLUME, 500000, 0, -1, -1) }Macros differ from functions in important ways: they run at compile time (not runtime), they perform text substitution (not value passing), and they can generate declarations, defines, or any other code -- things a function cannot do.
Literate Macros¶
The accumulation pattern builds lists. Literate macros iterate over those lists, applying a pattern to each element. This is how IVLS turns a list of names into actual code.
The basic form:
define COLORS := red, green, blue
literate_macro(paint(#l#)) on COLORSThis expands to:
paint(red)
paint(green)
paint(blue)The special tokens are:
#l#-- replaced with the current list element (the literal text)#n#-- replaced with the current element's index (0, 1, 2...)
You can use #l# inline without calling a macro -- useful for generating repetitive declarations or statements:
define ENGINES := volume, filter, reverb
literate_macro(my.#l#.enabled := TRUE) on ENGINESThis expands to:
my.volume.enabled := TRUE
my.filter.enabled := TRUE
my.reverb.enabled := TRUEThis is exactly how IVLS processes its core lists. When you write define IVLS_NODES += MyNode, the framework later runs a literate macro over IVLS_NODES that assembles each node, calls its init callbacks, and wires up its flows.
Iterate Macros¶
Where literate macros iterate over a comma-separated list, iterate macros iterate over a numeric range:
iterate_macro(my.slider[#n#] := get_ui_id(Main.Slider.#n#)) := 0 to 7This expands to eight lines, with #n# replaced by 0 through 7. It is the compile-time equivalent of a for loop, but it generates code rather than executing it -- useful for UI bindings, array initialization, and repetitive declarations that must exist as separate statements.
Iterate macros also support downto and step:
iterate_macro(process(#n#)) := 10 downto 0 step 2How IVLS Uses These Everywhere¶
Nearly every IVLS mechanism you have encountered is powered by the += / literate macro combination:
Flows: Each flow's NoteOn, NoteOff, NotePass lists are += defines. The framework literate-macros over them to build the node chain.
Voice fields: define Voice.ADD_MEMBERS += legato.vo_src, legato.vo_dest registers new fields. A literate macro processes the combined list to allocate storage.
UI callbacks: define STL.UI_CALLBACKS += my.browser.cb.tag registers a callback name. The framework literate-macros the list to generate dispatch code.
UI routines: define STL.UI_ROUTINES += plc.browser registers a refresh routine. A literate macro generates the calls to uir.plc.browser() inside the UI refresh cycle.
This architecture means you can add features to an instrument by adding files -- each file appends to the relevant lists, and the framework picks them up automatically. No central registry to maintain.
Conditional Declarations¶
Defines interact with the preprocessor's conditional system. You can conditionally include code based on whether a define exists or has a particular value:
define USE_LEGATO := 1
{ Later in code }
if USE_LEGATO = 1
define FLOWS.MyFlow.NOTEON += Stl.MonoLegato
end ifThis lets you create configurable builds where features are toggled on or off through defines, without commenting out code.
Defines and macros are not glamorous -- they are a text-processing layer, not a programming paradigm. But they are the connective tissue of IVLS. The += accumulation pattern lets files register their contributions independently. Literate macros turn those accumulated lists into real code. Together, they enable IVLS's modular architecture where adding a feature means adding a file, not editing a central manifest.