Playing Sounds¶
We now know that voices travel through flows, visiting nodes in sequence. But so far, nothing has actually made any sound. The node responsible for triggering audio in Kontakt is called the PlayEvent, and it's the final destination of most flows.
Rather than writing the entire sound-triggering mechanism from scratch, IVLS provides a template called Stl.PlayEvent.Template that handles the heavy lifting. Your job is to inherit from it and fill in two small pieces: what note to play and which sample groups to allow.
The PlayEvent Template¶
Here's the basic pattern for creating a PlayEvent node:
node MyPlayEvent from Stl.PlayEvent.Template:
cb EventArgs:
{ Set up note, velocity, and offset }
cb EventGroups:
{ Choose which sample groups to allow }
end nodeThe from Stl.PlayEvent.Template syntax means your node inherits from the template. The template handles calling play_note(), applying volume/pan/tune from the voice, and managing the event's lifecycle. You only need to override two callbacks.
cb EventArgs¶
cb EventArgs is where you configure the properties of the note event before it fires. The template gives you access to these variables:
event.note-- the MIDI note number to play (defaults to the voice's note)event.vel-- the velocity (defaults to the voice's velocity)event.offset-- the sample start offset in microseconds (defaults to 0)
If you don't need to modify any of these, you can leave cb EventArgs empty and the defaults will be used. But if you need to transpose the note or adjust the velocity, this is where you do it:
cb EventArgs:
event.note := event.note + 12 { transpose up one octave }cb EventGroups¶
cb EventGroups is where you tell Kontakt which sample groups are allowed to play. By default, no groups are allowed. You must explicitly enable the groups you want:
cb EventGroups:
event.groups[0] := TRUE { allow the first group }The event.groups array has one slot per group in your Kontakt instrument. Set a slot to TRUE to allow that group to play for this event.
In practice, you'll usually be selecting groups based on some voice data -- an articulation index, a sound ID, or a round-robin counter. Here's a more realistic example from a production instrument:
cb EventGroups:
if Voice[self].my_sound_id > -1
event.groups[my_get_group_index(Voice[self].my_sound_id)] := TRUE
end ifWhat Happens Internally¶
When a voice reaches your PlayEvent node, the template does the following in order:
- Reads
event.note,event.vel, andevent.offset(after yourEventArgscallback has run) - Calls
play_note()to trigger the Kontakt event - Applies volume, pan, and tune from the voice object
- Reads
event.groups(after yourEventGroupscallback has run) and sets the group allow list for the event - Continues to the next node in the flow, if there is one
On NoteOff, the template calls note_off() to stop the event. All of this lifecycle management is handled for you.
A Complete Minimal Example¶
Let's put together a PlayEvent that plays a single group on every key press. Combined with a simple flow, this is enough to hear sound from your instrument:
node MyPlayEvent from Stl.PlayEvent.Template:
cb EventArgs:
{ Use defaults -- play the note and velocity from the voice }
cb EventGroups:
event.groups[0] := TRUE { allow group 1 }
end node
node MyPlaybackLogic:
cb Flows:
define FLOWS += my_flow
ivls.register_node(my_flow, MyPlayEvent)
end nodeWith a keymap pointing keys to my_flow (covered on the next page), pressing a key would create a voice, send it through my_flow, and MyPlayEvent would call play_note() with the pressed note's pitch and velocity, allowing only group 1 to sound.
This is the simplest possible instrument in IVLS. Every production instrument builds on this exact pattern -- the only difference is the complexity of what happens in EventArgs, EventGroups, and the nodes that come before PlayEvent in the flow.