Custom Callbacks¶
In earlier chapters, you used built-in callbacks like cb Init, cb NoteOn, and cb Reload to hook into the IVLS lifecycle. But what if your system needs to notify other nodes about something that isn't part of the standard lifecycle -- a cache refresh, a UI intercept, a parameter sync event?
Custom callbacks let one node define a new event, and other nodes respond to it -- without either side knowing the full list of participants.
How Custom Callbacks Work¶
Custom callbacks use the same __RUN_CB__ mechanism that powers the entire IVLS system. When the compiler encounters __RUN_CB__(MyHook), it collects every node that has a cb MyHook: block and inlines their code at that point.
This means creating a custom callback is a two-part process:
- Declare the hook -- one node calls
__RUN_CB__(MyHook)at the point where the event should fire - Respond to the hook -- any other node implements
cb MyHook:to run code when the event fires
There is no registration step. If a node has a cb MyHook: block and is in the assembly, its code will be included when __RUN_CB__(MyHook) executes.
Declaring a Hook¶
A custom callback is declared by placing __RUN_CB__ inside an existing callback. Here is a simplified example of a parameter engine that fires a UI intercept hook whenever a parameter changes:
node MyLayerEngine:
cb Functions:
function my_layers.uicb_intercept(layer, par, ui_id, value)
__RUN_CB__(MyProduct.LayerUIIntercept)
end function
end nodeWhen my_layers.uicb_intercept() is called, every node that implements cb MyProduct.LayerUIIntercept: has its code executed at that point. The local variables layer, par, ui_id, and value are in scope for the responding nodes.
Responding to a Hook¶
Any node in the assembly can respond by declaring a callback with the matching name:
node MyControlLink:
cb MyProduct.LayerUIIntercept:
{ This code runs whenever the layer engine fires the intercept }
if ui_id > -1
declare l
for l := 0 to NUM_LAYERS - 1
if l # layer
my_layers[l].par[par] := value
end if
end for
end if
end nodeMultiple nodes can respond to the same hook. Their code is inlined in assembly order -- the order nodes appear in IVLS_NODES.
Production Examples¶
Custom callbacks appear throughout production instruments. Two examples illustrate common patterns:
Layer UI Intercept¶
In Polycore, the layer engine box fires a Polycore.LayerUIIntercept hook whenever a layer parameter is changed through the UI. The control-link node responds to this hook to synchronize linked layers:
{ In the layer engine data node }
function plc.layers.uicb_intercept(layer, par, ui_id, value)
__RUN_CB__(Polycore.LayerUIIntercept)
end function
{ In the control-link node }
node Polycore.ControlLink:
cb Polycore.LayerUIIntercept:
if _true(plc.globals.par[plc.globals.par.LINK_A + layer])
declare l
for l := 0 to Polycore.LAYERS - 1
if l # layer and _true(plc.globals.par[plc.globals.par.LINK_A + l])
plc.layers[l].par[par] := value
end if
end for
end if
end nodeThe engine doesn't know about control linking. The control-link node doesn't know about the engine's internals. The custom callback connects them cleanly.
Modrix Cache Refresh¶
When the Modrix modulation system recomputes its routing cache, it fires a Modrix.RefreshCache hook. Products respond to update their own routing analysis:
node Polycore.ModrixAPI:
cb Modrix.RefreshCache:
call polycore.analyze_routings()
end nodeThis lets each product maintain its own view of which parameters are modulated, without Modrix needing to know anything about the product's parameter structure.
Naming Conventions¶
Custom callback names typically follow the pattern Product.EventName or System.EventName:
Polycore.LayerUIInterceptPolycore.GlobalUIInterceptModrix.RefreshCacheTokyo.PreValueWriteTokyo.PreParamSync
The prefix scopes the callback to its origin system, preventing name collisions between unrelated subsystems.
When to Use Custom Callbacks¶
Custom callbacks are the right tool when:
- A system needs to notify other systems about an event, but shouldn't depend on them directly
- Multiple unrelated nodes need to react to the same trigger
- You want to keep subsystems decoupled so they can be developed and tested independently
They are not the right tool for communication within a single node or for per-voice processing (use flows and voice fields for that).
Custom callbacks are a simple mechanism -- they are just __RUN_CB__ with a name you choose -- but they are the primary way IVLS subsystems communicate without coupling. Every production instrument uses them to connect its engine boxes, UI logic, and feature systems.