Routing with Flows¶
So far, our flows have been straightforward: a voice enters, visits each node in sequence, and ends at PlayEvent. But what if you want different behavior depending on the state of the instrument? Maybe sustain articulations should go through one set of processing nodes, while release noises should go through another. Maybe the engine mode changes the entire playback pipeline.
This is where flow routing comes in. Rather than cramming conditional logic into every node, IVLS lets you redirect a voice into a different flow mid-pipeline.
The Divert Pattern¶
The most common routing pattern in IVLS is the Divert node -- a node that inspects the voice and sends it to one of several flows based on some condition.
Here's a simple example: an instrument that routes sustain notes through one flow and release noises through another.
node MyDivert:
cb NotePass:
if Voice[self].is_release = TRUE
ivls.reflow_voice(self, my_release_flow)
else
ivls.reflow_voice(self, my_sustain_flow)
end if
end nodeivls.reflow_voice() takes a voice and sends it into a new flow. The voice's stage is reset, and it begins visiting the nodes in the new flow from the start. The original flow is abandoned.
NotePass vs NoteOn¶
You'll notice the Divert node above uses cb NotePass rather than cb NoteOn. These are both playback callbacks, but they behave differently:
cb NotePass is synchronous. The voice passes through instantly, and execution continues in the same thread. There is no NoteOff counterpart. This is ideal for routing decisions, quick modifications, and any node that doesn't need to wait or track the voice's lifetime.
cb NoteOn is asynchronous. It creates a new execution context for the voice, and it has a corresponding cb NoteOff that fires when the key is released. This is necessary for nodes that need to wait (like timed playback) or that need to do cleanup on release.
The rule of thumb: default to NotePass unless you specifically need async behavior or NoteOff handling. Divert nodes almost always use NotePass.
Setting Up the Flows¶
A Divert node typically declares its target flows in cb Flows:
node MyDivert:
cb Flows:
define FLOWS += my_sustain_flow, ...
my_release_flow
ivls.register_node(my_sustain_flow, Stl.Pedals)
ivls.register_node(my_sustain_flow, MyPlayEvent)
ivls.register_node(my_release_flow, MyReleasePlayEvent)
cb NotePass:
if Voice[self].is_release = TRUE
ivls.reflow_voice(self, my_release_flow)
else
ivls.reflow_voice(self, my_sustain_flow)
end if
end nodeThe main flow (the one the keymap points to) registers MyDivert as one of its nodes. When the voice reaches MyDivert, the NotePass callback inspects the voice and redirects it into either my_sustain_flow or my_release_flow. Each of those flows has its own sequence of nodes, ending in its own PlayEvent.
Chaining Divert Nodes¶
Divert nodes can be chained. The first Divert might choose between engine modes, the second might choose between articulation types, the third might choose between legato and non-legato playback. Each one narrows the path further:
{ Main flow }
ivls.register_node(main_flow, Divert.EngineStyle)
{ Engine style divert leads to... }
ivls.register_node(standard_flow, Stl.Pedals)
ivls.register_node(standard_flow, Divert.PlaybackType)
{ Playback type divert leads to... }
ivls.register_node(non_legato_flow, PrepareSound)
ivls.register_node(non_legato_flow, MyPlayEvent)Each level of diversion adds a branching point. The voice always follows exactly one path through the tree of flows.
Production Scale¶
Production instruments like Tokyo Scoring Strings have 10 or more flows, handling sustains, legato transitions, release noises, portamento, and various engine modes. But they're all built from this same pattern: a Divert node with a NotePass callback that calls ivls.reflow_voice().
The power of this approach is that each flow remains simple and linear. The complexity lives in the routing between flows, not inside the flows themselves.
Now that you understand how to route voices between flows, there's one more common playback feature to cover before we move on: sustain pedal support.