Overview
uvi-script provides per-voice control over tuning, volume, panning, and fading. Each voice represents a single playing note and has a unique ID that can be used to manipulate it individually.
This allows for sophisticated voice-level effects like vibrato, tremolo, detuning, crossfading, and dynamic expression that operate independently on each note.
Voice Lifecycle
Understanding the voice lifecycle is essential for effective voice manipulation:
- Voice Creation - A voice is created via playNote or postEvent, which returns a unique voice ID
- Voice Manipulation - The voice can be modified using changeTune, changePan, changeVolume
- Voice Fading - The voice can be faded using fadein, fadeout, or fade
- Voice Release - The voice ends via releaseVoice or automatic release when the note ends
Creating Voices
-- Method 1: Forward incoming event and get voice ID
-- Method 2: Create new voice with explicit parameters
local id2 =
playNote(e.note, e.velocity, -1) -- -1 = sustain until released
-- Method 3: Create voice with specific layer and channel
local id3 =
playNote(60, 100, 1000, 1, 0) -- C4, vel 100, 1s duration, layer 1
end
void onNote(table e)
event callback that will receive all incoming note-on events if defined.
function playNote(note, vel, duration, layer, channel, input, vol, pan, tune, slice, oscIndex)
helper function to generate a note event.
Definition api.lua:931
function postEvent(e, delta)
send a script event back to the script engine event queue.
Definition api.lua:842
Tracking Voice IDs
Always store voice IDs if you plan to manipulate them later:
-- Later, manipulate this specific voice
end
function wait(ms)
suspend the current thread callback execution for the given number of samples.
Definition wrapper.lua:39
function changeTune(voiceId, shift, relative, immediate)
change the tuning of specific voice in (fractionnal) semitones.
Definition api.lua:477
Managing Multiple Active Voices
Most real-world scripts need to track several playing voices simultaneously. The standard pattern is a table keyed by note number:
local voices = {} -- voices[note] = voiceId
voices[e.note] = id
-- now you can manipulate this specific voice later
changePan(
id, (e.note / 127) * 2 - 1) -- spread by pitch
end
voices[e.note] = nil
end
void onRelease(table e)
event callback executed whenever a note off message is received.
function changePan(voiceId, pan, relative, immediate)
changes the pan position of a specific note event.
Definition api.lua:504
This pattern is essential for effects that need to act on all active voices, such as pitch bend applied from a controller:
local voices = {}
end
voices[e.note] = nil
end
for note, id in pairs(voices) do
end
end
void onPitchBend(table e)
event callback that will receive all incoming pitch-bend events if defined.
Voice Parameters
Tuning Control
Use changeTune to modify the pitch of a voice in semitones:
-- Bend pitch up by 2 semitones (whole step)
-- Bend pitch down by 1 semitone
-- Detune by 10 cents
end
Volume Control
Use changeVolume to modify the volume of a voice:
-- Set volume to 50%
-- Set volume to 100%
-- Mute the voice
end
function changeVolume(voiceId, gain, relative, immediate)
changes a voice's volume.
Definition api.lua:519
Volume range: 0.0 (silent) to 1.0 (full volume)
For decibel-based control, use changeVolumedB instead. This is often more intuitive for mixing tasks where you think in dB attenuation:
-- Attenuate by 6 dB (roughly half perceived loudness)
-- Boost by 3 dB
end
function changeVolumedB(voiceId, dBGain, relative, immediate)
change the volume of specific voice in decibels.
Definition api.lua:546
Panning Control
Use changePan to modify the stereo position of a voice:
-- Pan hard left
-- Pan center
-- Pan hard right
-- Pan slightly right
end
Pan range: -1.0 (hard left) to 1.0 (hard right), 0.0 is center
Fading Voices
Control voice envelope with fade functions:
-- Fade in over 500ms
-- Later,
fade out over 1000ms and stop voice
fadeout(
id, 1000, true) -- true = release voice after
fade
end
function fadeout(voiceId, duration, killVoice, reset, layer)
starts a volume fade-out.
Definition api.lua:598
function fade(voiceId, targetValue, duration, layer)
starts a volume fade.
Definition api.lua:651
function fadein(voiceId, duration, reset, layer)
starts a volume fade-in for a specific voice.
Definition api.lua:632
Common Use Cases
Detuning for Richness
Create a richer sound by layering slightly detuned copies:
-- Play root note
local id1 =
playNote(e.note, e.velocity, -1)
-- Add slightly detuned copies for chorus effect
local id2 =
playNote(e.note, e.velocity, -1)
local id3 =
playNote(e.note, e.velocity, -1)
-- Optional: Pan them for width
end
Dynamic Vibrato
Apply vibrato that intensifies over time:
local vibratoRate =
Knob(
"Vibrato Rate", 5, 0, 10)
local vibratoDepth =
Knob("Vibrato Depth", 0.1, 0, 0.5)
local elapsed = 0
-- Vibrato depth increases over time
local depthEnvelope = math.min(elapsed / 1000, 1.0)
local phase = 2 * math.pi * vibratoRate.value * elapsed / 1000
local modulation = vibratoDepth.value * depthEnvelope * math.sin(phase)
elapsed = elapsed + 5
end
end
Knob widget.
Definition ui.cpp:1424
function isNoteHeld()
return true is the note that created this callback is still held.
Definition api.lua:810
Tremolo Effect
Create rhythmic volume modulation:
local tremoloRate =
Knob(
"Tremolo Rate", 5, 0, 20)
local tremoloDepth =
Knob("Tremolo Depth", 0.5, 0, 1)
local phase = 2 * math.pi * tremoloRate.value *
getTime() / 1000
local modulation = 1 - (tremoloDepth.value * (1 - math.cos(phase)) / 2)
end
end
function getTime()
get the number of milliseconds elapsed since the script engine start.
Definition api.lua:740
Auto-Panning
Create movement in the stereo field:
local panRate =
Knob(
"Pan Rate", 0.5, 0, 5)
local phase = 2 * math.pi * panRate.value *
getTime() / 1000
local pan = math.sin(phase)
end
end
Voice Crossfading
Smoothly transition between voices:
-- Play two different layers
-- Start with layer 1 only
-- Crossfade from layer 1 to layer 2 over 2 seconds
for i = 0, 100 do
local mix = i / 100
end
end
A layer of sounds.
Definition Engine.cpp:255
Pitch Bend Effect
Create smooth pitch glides:
-- Bend from -2 semitones to 0 over 500ms
for i = 0, 100 do
local bend = -2.0 + (2.0 * i / 100)
changeTune(
id, bend,
false,
false) -- Smooth interpolation
end
end
Unison/Ensemble Effect
Create a thick unison sound with multiple detuned voices:
local unisonVoices = 5
local unisonDetune =
Knob(
"Detune", 0.1, 0, 0.5)
local unisonSpread =
Knob("Stereo Spread", 0.5, 0, 1)
for i = 1, unisonVoices do
local
id =
playNote(e.note, e.velocity, -1)
-- Detune each voice differently
local detune = (i - (unisonVoices + 1) / 2) * unisonDetune.value / unisonVoices
-- Spread across stereo field
local pan = ((i - 1) / (unisonVoices - 1) - 0.5) * 2 * unisonSpread.value
end
end
Best Practices
- Always Store Voice IDs
- If you plan to manipulate a voice later, store its ID:
-- GOOD: Store ID for later use
-- BAD: Can't manipulate the voice later
-- How do we change
this voice now?
ScriptProcessor & this
Reference to the current ScriptProcessor instance.
Definition Engine.cpp:404
- Clean Up Voice Data
- Remove stored voice data when voices end:
voiceData[id] = {startTime =
getTime()}
-- Clean up
voiceData[id] = nil
end
function waitForRelease()
suspend the current thread callback execution until the note that created this callback is released e...
Definition wrapper.lua:47
- Limit Voice Count
- Be mindful of CPU usage when creating many voices:
-- GOOD: Reasonable voice count
for i = 1, 5 do
end
end
-- POTENTIALLY BAD: Too many voices
for i = 1, 50 do -- May cause CPU issues
end
end
Sample Start Offset
Use setSampleOffset to change where playback begins within a sample, specified in milliseconds. This is applied to an already-playing voice, letting you skip into the middle of a sample on the fly.
-- Random start offset (0–80 ms) for natural variation on repeated notes
local offset = math.random() * 80
end
function setSampleOffset(voiceId, value)
changes a sample starting point in milliseconds.
Definition api.lua:558
Script Modulation
Script modulation sources are modulation slots in the engine that can be driven entirely from script code. You assign a script modulation source to a destination in the engine UI, then send values from your script. This allows continuous, per-voice or broadcast parameter control — useful for expression, morphing, or any automation that goes beyond simple note-on changes.
Use sendScriptModulation to send a value to a modulation source:
- id — modulation source index (configured in the engine UI)
- targetValue — destination value, [0,1] unipolar or [-1,1] bipolar depending on the mapping
- rampTime — interpolation time in ms (default 20 ms, avoids zipper noise)
- voiceId — optional; pass a voice ID for per-voice modulation, or
nil to broadcast to all voices
-- Broadcast example: map MIDI CC1 (mod wheel) to script modulation source 0
if e.controller == 1 then
local value = e.value / 127 -- Normalize to 0..1
end
end
void onController(table e)
event callback that will receive all incoming control-change events when defined.
function sendScriptModulation(id, targetValue, rampTime, voiceId)
send a script modulation event to control a Script Modulation source.
Definition api.lua:417
Use sendScriptModulation2 for piece-wise linear ramps where you need an explicit start value. Parameters are the same, with an additional startValue before targetValue:
-- Per-voice expression: ramp modulation source 1 from 0 to 1 over 500 ms
end
function sendScriptModulation2(id, startValue, targetValue, rampTime, voiceId)
send a script modulation event to control a Script Modulation source.
Definition api.lua:447
See Also
- See also
- Voice Manipulation - Voice Manipulation manipulation functions
-
playNote - Create a new voice
-
postEvent - Forward event and get voice ID
-
changeTune - Modify voice tuning
-
changeVolume - Modify voice volume (linear)
-
changeVolumedB - Modify voice volume (decibels)
-
changePan - Modify voice panning
-
setSampleOffset - Set sample playback start position
-
sendScriptModulation - Drive a script modulation source
-
sendScriptModulation2 - Drive a script modulation source with explicit start value
-
fadein - Fade voice in
-
fadeout - Fade voice out
-
releaseVoice - Release a voice
-
isNoteHeld - Check if note is still held
-
Threading and Timing - Threading and timing guide
-
Examples Gallery - Examples gallery