uvi-script
Musical event scripting with Lua
Loading...
Searching...
No Matches
Lua Language Reference

Overview

uvi-script is built on the Lua programming language, a lightweight, fast, and embeddable scripting language. Understanding Lua is essential for writing effective uvi-scripts.

This page covers the Lua-specific aspects of uvi-script: which version is used, what libraries are available, and the real-time constraints that shape how you write scripts.


Learning Lua

If you're new to Lua or need a reference, we recommend these excellent resources:

These resources are well-written and provide a solid foundation for scripting. Using a simple, widespread, yet powerful scripting language that can be learned online was one of the main motivations for choosing Lua.

What you need to know:

  • Basic syntax (variables, functions, tables, control structures)
  • Tables (Lua's primary data structure)
  • Functions and closures
  • Local vs global variables (critical for uvi-script!)
  • Coroutines (used internally for threading)

The Lua Virtual Machine

uvi-script uses a sandboxed Lua 5.1 virtual machine with custom extensions designed for real-time audio processing.

Real-Time Execution Constraints

Most script code executes with real-time priority to ensure sample-accurate timing and low-latency performance. This places specific constraints on what you can do:

Real-Time Requirements:

  • Predictable execution time - Code must complete within a bounded time
  • No memory allocation - All memory is pre-allocated from a custom pool
  • No blocking operations - Cannot wait on external resources (files, network, etc.)
  • Bounded loops - Avoid lengthy operations like iterating over large tables (10000+ entries)

Exceptions (Non-Real-Time): These callbacks run with relaxed constraints and can allocate memory:

  • Main script chunk (executed once at load time)
  • onLoad - Called when restoring saved state
  • onSave - Called when saving state

Memory Management

To maintain real-time safety, uvi-script uses a pre-allocated memory pool for all Lua operations during real-time execution.

Best Practices:

  • Pre-allocate tables in the main chunk rather than in callbacks
  • Reuse tables instead of creating new ones in loops
  • Use local variables for temporary data (faster and doesn't allocate)
  • Avoid string concatenation in tight loops (creates new strings)

Available Standard Libraries

uvi-script provides access to a subset of Lua's standard libraries. Libraries that require operating system access or blocking I/O are not available to maintain real-time safety.

Available Libraries

base - Basic Functions

  • Core functions: assert, error, pcall, type, tonumber, tostring
  • Table/iteration: pairs, ipairs, next
  • Utility: print, select, unpack

package - Module System

  • require - Load Lua modules
  • Note: Only Lua modules can be loaded, not C libraries

table - Table Manipulation

  • table.insert, table.remove
  • table.concat
  • table.sort
  • table.copy(t) - uvi-script extension: creates a shallow copy of a table
  • Note: Be mindful of performance on large tables in real-time code

string - String Operations

  • string.sub, string.find, string.match
  • string.format, string.gsub
  • Pattern matching (Lua patterns, not full regex)

math - Mathematical Functions

  • Trigonometry: math.sin, math.cos, math.tan, math.asin, etc.
  • Utilities: math.abs, math.floor, math.ceil, math.min, math.max
  • Random: math.random, math.randomseed
  • Constants: math.pi, math.huge

bit - Bitwise Operations

  • Lua 5.1 has no native bitwise operators; this library provides them
  • bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift

class - Object-Oriented Programming

Not Available (Real-Time Safety)

os - Operating System Facilities

  • Not available: Would break real-time guarantees
  • Reasons: Blocking system calls, unpredictable timing

io - File I/O

  • Not available: Would block real-time thread
  • Alternative: Use asynchronous API for file operations

Third-Party Lua Libraries

  • Not allowed: Cannot load external C libraries or most Lua libraries
  • Reasons: Real-time safety, memory allocation, security
Note
For file I/O and other blocking operations, use the asynchronous API which runs on background threads and safely communicates results back to your script.

Built-in Overrides and Utility Globals

The runtime replaces a few base library functions with script-friendly versions and adds several global helpers.

Printing

print(...) writes to the script console, not to a standard output stream. Arguments are converted to text and separated by single spaces (the standard print separates them with tabs). Event tables — tables carrying a numeric type field, such as the argument of onNote — are expanded to their key=value contents instead of being shown as a table address:

print("velocity:", 100) -- velocity: 100
print({type=Event.NoteOn, note=60, velocity=100}) -- note=60 velocity=100 type=144 (field order may vary)
Event types.
Definition Engine.cpp:723

Other tables print as an address, like with the standard print; use the helpers below to display their contents.

print_table(t, printer, indent) prints one key:value line per entry and recurses into nested tables, indenting each nesting level by two extra spaces. printer (optional) is the output function, print by default; indent (optional) is the initial indentation string. Values that are not booleans, numbers, or strings are shown by their type name.

table.tostring(t) returns the top-level key=value pairs of a table as a single space-separated string. Pairs appear in unspecified order and nested tables appear as addresses.

table.print(t) prints table.tostring(t) wrapped in braces. Does nothing if t is not a table.

Table copying

table.copy(t) returns a new table with the same keys and values. The copy is shallow: nested tables are shared with the original, not duplicated.

Assertions and warnings

assert(cond, msg, level) raises an error with message msg (default "error") when cond is false or nil. Unlike the standard assert, it takes a third level argument selecting the stack level at which the error is reported: 1 (the default) blames the caller of assert, 2 blames the caller's caller, and so on — useful for reporting argument errors at the call site of your own functions. Also unlike the standard version, it does not return its arguments on success.

warning(condition, msg, level) has the same signature but never raises an error: when condition is false it prints msg prefixed with warning: [file]:line, where the location is taken from the stack level selected by level (default 1, the caller of warning).

function setVolume(v)
assert(type(v) == "number", "v should be a number", 2) -- reported at the caller of setVolume
warning(v <= 1, "volume above 1 will clip")
end

Object-Oriented Programming

uvi-script includes a built-in class system. Use class to define classes with constructors, methods, and instance state. This is useful for encapsulating complex logic like sequencers, state machines, or multi-voice managers.

Defining a Class

class 'NoteTracker'
function NoteTracker:__init()
self.voices = {}
end
function NoteTracker:add(note, id)
self.voices[note] = id
end
function NoteTracker:remove(note)
self.voices[note] = nil
end
function NoteTracker:forEach(fn)
for note, id in pairs(self.voices) do
fn(note, id)
end
end

Create instances with the class name as a function call:

local tracker = NoteTracker()
function onNote(e)
local id = postEvent(e)
tracker:add(e.note, id)
end
function onRelease(e)
tracker:remove(e.note)
end
function onPitchBend(e)
tracker:forEach(function(note, id)
changeTune(id, e.bend * 2)
end)
end
void onNote(table e)
event callback that will receive all incoming note-on events if defined.
void onPitchBend(table e)
event callback that will receive all incoming pitch-bend 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 changeTune(voiceId, shift, relative, immediate)
change the tuning of specific voice in (fractionnal) semitones.
Definition api.lua:488

Engine Objects

The synthesis engine objects (Program, Layer, Keygroup, Oscillator, etc.) are C++ objects exposed to Lua. They support the same colon method syntax:

local osc = Program.layers[1].keygroups[1].oscillators[1]
osc:setParameter("Gain", 0.5) -- method call
local info = osc.sampleInfo -- property access
print(info.name, info.duration)
A Patch that represents a monotimbral instrument.
Definition Engine.cpp:257
table layers
Layer list for this Program (1-indexed, use #Program.layers to get the count)
Definition Engine.cpp:266

Threading and Coroutines

uvi-script uses Lua coroutines internally to implement its cooperative threading model. Each event callback (onNote, onRelease, etc.) runs as a separate coroutine that can be suspended using wait, waitBeat, or waitForRelease.

What you need to know:

  • Each callback is an independent thread (coroutine)
  • Threads are cooperative - they must explicitly yield using wait() functions
  • Local variables in callbacks are thread-local
  • Global variables are shared across all threads

For detailed information about threading and timing, see Threading and Timing.


See Also