Overview
Beyond receiving MIDI events through callbacks like onNote and onController, uvi-script can also generate outgoing MIDI events. This is useful for building arpeggiators, chord generators, MIDI processors, and automation scripts.
There are two approaches:
- Helper functions — concise, dedicated functions for each event type
- postEvent() — low-level, allows full control over all event fields
Helper Functions
These convenience functions wrap postEvent with the correct event type. All accept optional channel and input parameters for routing to specific Parts.
Control Change
Send a MIDI CC message:
-- send mod wheel (CC1) value 100 on the same channel
end
-- reset all controllers on note release
for cc = 1, 119 do
end
end
void onNote(table e)
event callback that will receive all incoming note-on events if defined.
void onRelease(table e)
event callback executed whenever a note off message is received.
function postEvent(e, delta)
send a script event back to the script engine event queue.
Definition api.lua:857
function controlChange(cc, val, ch, inp)
sends a ControlChange event.
Definition api.lua:1067
The loop stops at CC 119: controllers 120-127 are channel-mode messages (all sound off, reset all controllers, all notes off, omni/mono/poly mode), not ordinary controllers. The engine acts on them — CC 120 cuts every playing voice immediately and CC 123 releases every held note — so they must not be sent as part of a controller reset.
- See also
- controlChange, onController
Pitch Bend
Send a pitch bend message. The value is normalized to [-1.0; 1.0]:
-- pitch bend ramp on each note
for i = 0, 100 do
end
end)
end
function pitchBend(b, ch, inp)
sends a PitchBend event.
Definition api.lua:1098
function wait(ms)
suspend the current thread callback execution for the given number of milliseconds.
Definition wrapper.lua:39
function spawn(fun,...)
Launch a function in a separate parallel execution thread (deferred execution)
Definition api.lua:1201
- See also
- pitchBend, onPitchBend
Program Change
Send a program change message:
-- switch program based on velocity range
if e.velocity > 100 then
else
end
end
function programChange(val, ch, inp)
sends a ProgramChange event.
Definition api.lua:1083
- See also
- programChange, onProgramChange
Aftertouch
Send channel aftertouch or polyphonic aftertouch:
-- convert velocity to channel aftertouch
end
-- per-note pressure from velocity
end
function afterTouch(v, ch, inp)
sends an AfterTouch event.
Definition api.lua:1113
function polyAfterTouch(v, n, ch, inp)
sends a PolyAfterTouch event.
Definition api.lua:1129
- See also
- afterTouch, polyAfterTouch, onAfterTouch, onPolyAfterTouch
Modifying Incoming Events
Instead of generating new events, you can modify incoming events before forwarding them with postEvent. This is the most efficient way to transform MIDI data:
-- Remap CC1 (mod wheel) to CC11 (expression)
if e.controller == 1 then
e.controller = 11
end
end
void onController(table e)
event callback that will receive all incoming control-change events when defined.
You can modify any field of the event table before posting it:
-- Transpose all notes up one octave
e.note = e.note + 12
end
e.note = e.note + 12
end
- Note
- When modifying
onNote events, remember to apply the same transformation in onRelease so the engine can match the note-off to the correct voice.
RPN, NRPN and Data Entry CCs
A few controller numbers are consumed before they reach the script: CC 6 and CC 38 (data entry MSB/LSB), CC 98 and CC 99 (NRPN LSB/MSB), and CC 100 and CC 101 (RPN LSB/MSB) do not trigger onController (or onEvent) and do not update getCC.
To receive them as plain CC events, opt in at script load time:
enable_rpn_as_cc(true) -- receive CC 100/101
enable_nrpn_as_cc(true) -- receive CC 98/99
enable_data_entry_as_cc(true) -- receive CC 6/38
Once enabled, the MSB/LSB state machine is the script's responsibility: decode the parameter number from CC 99/98 (NRPN) or CC 101/100 (RPN), then the value from CC 6/38, inside onController.
Using postEvent Directly
The helper functions are shortcuts for postEvent. You can achieve the same result (and more) by constructing event tables directly:
-- these two are equivalent:
Event types.
Definition Engine.cpp:723
Using postEvent directly gives access to additional fields like layer, plus an optional second argument delta (timing offset in ms):
-- send CC to a specific layer with a 10ms delay
type =
Event.ControlChange,
controller = 1,
value = 100,
layer = 2
}, 10)
Raw MIDI with postMidiEvent
For playback of MIDI files or raw MIDI data, use postMidiEvent with MidiEvent objects. The class lives in the uvi table, and the constructor takes the channel before the event type: uvi.MidiEvent(timestamp, channel, type, byte1, byte2). The Event type constants are global and need no prefix.
- Note
- MidiEvent objects use the raw 0-based MIDI channel in [0;15], unlike the event tables passed to callbacks and postEvent, which use 1-based channels in [1;16] (with 0 accepted by postEvent as omni). Channel 1 in an event table is channel 0 here.
-- note-on: channel 0, note 60, velocity 100
local
event = uvi.MidiEvent(0, 0,
Event.NoteOn, 60, 100)
void postMidiEvent(MidiEvent e)
send a MidiEvent directly to the script engine event queue.
- Note
- Events posted via postMidiEvent don't have voice IDs. For advanced voice tracking, use playNote and releaseVoice instead.
- See also
- MIDI, Events, Event Callbacks, Asynchronous Operations, Porting from Kontakt (KSP)