Voice Fields and vo_field¶
So far, we've accessed built-in voice fields like Voice[self].note and Voice[self].vel to read which MIDI note triggered a voice and how hard it was pressed. These fields are part of every voice automatically -- the framework populates them when a key is pressed.
But real instruments need more than note and velocity. A phrase-based instrument needs to know which phrase this voice is playing. A multi-layer instrument needs to know which layer a voice belongs to. A legato instrument needs to track the source note for portamento. None of these are built-in -- they're specific to your instrument's design.
Voice fields let you attach custom data to every voice. You declare a field once, and it becomes available on every voice in the system, just like note and vel.
Declaring a Voice Field¶
You declare a voice field using the vo_field statement:
vo_field my_layer = 0 if (in_range(my_layer, 0, 3))This creates a new field called my_layer on every voice, with a default value of 0. The if clause is a validation predicate -- a condition that the field's value must satisfy. If a voice ends up with an invalid value, the framework will catch it during development.
Let's break down the syntax:
vo_field declares the field.
my_layer is the field name. After declaration, you access it as Voice[self].my_layer.
= 0 sets the default value. Every new voice starts with this value.
if (in_range(my_layer, 0, 3)) is the validation predicate. It's checked in debug builds to catch logic errors -- if a voice's my_layer ends up being 5, something went wrong, and the validator will flag it.
Where to Declare Voice Fields¶
Voice fields are declared inside cb Init (or cb ICB), typically in the node that owns the concept the field represents:
node MyLayerLogic:
cb Init:
vo_field my_layer = 0 if (in_range(my_layer, 0, 3))
vo_field my_sound_id = -1 if (my_sound_id >= -1)Because voice fields are part of the Voice cluster, they need to be declared early -- during initialization, before any voices are created.
Reading and Writing Voice Fields¶
Once declared, voice fields work exactly like built-in fields:
{ Read the current voice's layer }
declare layer := Voice[self].my_layer
{ Write to the current voice }
Voice[self].my_layer := 2
{ Read from another voice }
declare other_layer := Voice[some_other_vo].my_layerYou can read and write voice fields from any node in any flow, as long as the voice reference is valid.
Production Examples¶
In production instruments, voice fields carry domain-specific data that flows through the voice pipeline:
In Sylvan Phrases, each voice carries which phrase sound it should play:
vo_field phr.sound_id = 0 if in_range(phr.sound_id, 0, phr.get_num_sounds.const() - 1)In Polycore, voices track which sound from the layer engine they're assigned to:
vo_field plc.sound_id = -1 if ((search(plc.sound_ids, plc.sound_id) # -1) or (plc.sound_id = -1))Notice how the validation predicates match the domain. Sylvan's phr.sound_id must be a valid sound index. Polycore's plc.sound_id must either be -1 (unassigned) or exist in the sound ID lookup table.
The Validation Predicate¶
The if clause in a vo_field declaration is not a guard or a filter -- it's a debugging tool. During development, if a voice's field value fails the predicate, the framework reports the error. In production builds, the predicate is compiled out.
Good predicates catch logic errors early:
{ Must be a valid MIDI note }
vo_field my_note = 0 if (in_range(my_note, 0, 127))
{ Must be non-negative or the sentinel -1 }
vo_field my_ref = -1 if (my_ref >= -1)
{ Must exist in a lookup table }
vo_field my_type = 0 if (search(valid_types, my_type) # -1)Think of them as assertions: "if this is ever false, I have a bug."
Voice Fields vs. Engine Box Parameters¶
Voice fields and engine box parameters serve different purposes:
-
Engine box parameters are global instrument state -- one value shared across all voices. Volume, attack, mode selectors. They persist across preset loads and sync with the UI.
-
Voice fields are per-voice data -- each voice carries its own copy. Which layer this voice belongs to, which phrase it's playing, what its portamento source note is. They exist only for the lifetime of the voice.
If you need a value that's the same for all voices and survives a preset change, it's a parameter. If you need a value that differs per voice and travels with it through the flow, it's a voice field.
Voice fields are simple to declare and use, but they're one of the most important tools in instrument design. They let you encode your instrument's logic directly into the voice system, carrying domain-specific information through every stage of playback.