How to Add Portamento¶
Add pitch glide between notes so that when a legato transition is detected, the sounding voice smoothly slides from the source pitch to the destination pitch.
Prerequisites¶
- A working IVLS product with at least one flow and
Stl.PlayEvent - Legato detection already set up (
Stl.MonoLegatoregistered in a flow) -- see How to Add Legato Stl.Legatoincluded viaIvls.STL(part of the standard library bundle)- Familiarity with portamento concepts and Modbits
Steps¶
1. Add the portamento core node to IVLS_NODES¶
Stl.Portamento.Core declares the per-voice portamento fields (porta.init_note, porta.init_vo, porta.modbit, porta.run_owner) and the helper functions. Include it in your product's node list:
2. Create a portamento playback node from the template¶
Inherit from Stl.Portamento.Template and implement two virtual callbacks: AllowInitPorta (controls whether a non-legato note starts a portamento glide) and PortaFound (runs the actual pitch glide when a legato transition is detected).
node MyProduct.Play.Portamento from Stl.Portamento.Template:
cb AllowInitPorta:
{ Set porta_on_init := TRUE to glide even on the first note (auto mode) }
{ Set porta_on_init := FALSE for fingered mode (glide only on overlapping notes) }
porta_on_init := FALSE
cb PortaFound:
{ Seize tuning control for this voice }
portamento.seize_tuning_for(init_vo, self)
{ Calculate the target tune offset in millicents }
declare target_tune := (new_note - init_note) * 100000
{ Glide over time (in ms) }
declare glide_time := my.porta_time
if glide_time < 2
{ Instant snap }
Modbit.set(mb, target_tune)
else
{ Timed glide: ramp the modbit from current to target }
declare current_tune := Modbit[mb].value
declare stride := (target_tune - current_tune) / glide_time
declare t
for t := 0 to glide_time
if path_on and portamento.still_owned_by(init_vo, self) = TRUE
Modbit.set(mb, current_tune + stride * t)
ivls.wait_ms(1)
else
t := glide_time
end if
end for
{ Snap to exact target }
if path_on and portamento.still_owned_by(init_vo, self) = TRUE
Modbit.set(mb, target_tune)
end if
end if
end node3. Register the portamento node in a flow¶
Create a flow that chains Stl.MonoLegato (for legato detection) followed by your portamento node, then moves to your sound output flow:
node MyProduct.Flows:
cb Flows:
define FLOWS += my.porta_flow, my.sound_flow
{ Portamento flow: detect legato, then apply pitch glide }
ivls.register_node(my.porta_flow, Stl.MonoLegato)
ivls.register_node(my.porta_flow, MyProduct.Play.Portamento)
ivls.register_move(my.porta_flow, my.sound_flow)
{ Sound output flow }
ivls.register_node(my.sound_flow, Stl.PlayEvent)
end node4. Route voices with a Divert node¶
Use a Divert node to send voices to either the portamento flow or the normal sound flow based on a portamento on/off parameter:
node MyProduct.Divert.PlayMode:
cb NotePass:
if my.portamento_enabled = TRUE
ivls.reflow_voice(self, my.porta_flow)
else
ivls.reflow_voice(self, my.sound_flow)
end if
end nodeRegister the divert node in your base flow so it runs before the sound path:
Key variables available in PortaFound¶
The template makes these variables available inside your cb PortaFound:
| Variable | Type | Purpose |
|---|---|---|
init_vo |
integer | The voice that holds the initial pitch modbit |
init_note |
integer | The MIDI note the glide starts from |
new_note |
integer | The MIDI note the glide targets |
mb |
integer | The Modbit handle controlling the pitch offset |
self |
integer | The current voice triggering the legato |
src |
integer | The source (previous) voice in the legato chain |
dest |
integer | The destination voice in the legato chain |
Key portamento functions¶
| Function | Purpose |
|---|---|
portamento.set_init_voice(vo) |
Creates the initial pitch modbit and assigns it to the voice |
portamento.connect_data(src, dest) |
Copies portamento fields from source to destination voice |
portamento.seize_tuning_for(init_vo, vo) |
Takes ownership of the pitch modbit so this voice controls the glide |
portamento.still_owned_by(init_vo, vo) |
Returns TRUE if this voice still owns the pitch modbit |
Verify¶
- Enable portamento and play two overlapping notes -- the second note should glide from the first note's pitch
- Play the same two notes without overlapping -- no glide should occur (in fingered mode)
- If using auto mode (
porta_on_init := TRUE), the first note of a new phrase should also glide from the last played pitch - Adjust glide time -- shorter values should produce snappier transitions, longer values a slower slide
Further reading¶
- Guide: Portamento for conceptual details on fingered vs. auto portamento
- How to: Add Legato for setting up the legato detection that portamento depends on
- Guide: Modbits for understanding how
Modbit.set()drives real-time pitch changes