uvi-script
Musical event scripting with Lua
Loading...
Searching...
No Matches
Asynchronous Operations

Overview

uvi-script provides asynchronous operations to prevent blocking the real-time audio thread when performing file I/O, loading samples, or other time-consuming tasks.

All asynchronous operations run on background threads and communicate their results back to the script thread safely, ensuring real-time audio processing is never interrupted.

Common Patterns

Callback Pattern

All async functions accept a callback that will be called upon completion. This is the recommended pattern for most use cases:

browseForFile("open", "Select file", "", "*.wav", function(task)
if task.success then
print("Selected: " .. task.result)
else
print("Operation cancelled or failed")
end
end)
function browseForFile(mode, title, initialFileOrDirectory, filePatterns, callback)
launch a file chooser to select a file to open or save
Definition api.lua:32

The callback receives an AsyncTask object with the following properties:

  • task.success - Boolean indicating if the operation succeeded
  • task.finished - Boolean indicating completion status

Operation-specific results are exposed through dedicated properties:

loadData and loadTextData are different: their callback receives the loaded data directly (a Lua table or a string), not a task object — see Custom Data I/O below.

Polling Pattern

Alternatively, you can poll the task status in a loop. This is useful when you need to perform other work while waiting:

function onInit()
local task = purge(Program.layers[1])
-- Continue doing other work while the purge happens
while not task.finished do
print("Purging samples...")
wait(100) -- Check every 100ms
end
print("Purge complete!")
end
A Patch that represents a monotimbral instrument.
Definition Engine.cpp:257
function purge(target, callback)
purge an/several element(s)/oscillator(s) from its/their content in order to release memory.
Definition api.lua:361
void onInit()
initial callback that is called just after the script initialisation if the script was successfully a...
function wait(ms)
suspend the current thread callback execution for the given number of milliseconds.
Definition wrapper.lua:39

Polling only works where wait can actually suspend execution: inside an event callback, onInit, or a thread started with spawn / run. At the top level of the script, or in a widget's changed callback, wait() returns immediately and the loop spins without ever letting the task complete — use the callback pattern there instead.

Available Async Operations

File Dialogs

Use browseForFile to let users select files without blocking audio:

-- Open file dialog
browseForFile("open", "Select Audio File", "", "*.wav;*.aif", function(task)
if task.success then
local filepath = task.result
print("User selected: " .. filepath)
end
end)
-- Save file dialog
browseForFile("save", "Save Preset", "MyPreset", "*.preset", function(task)
if task.success then
saveData(presetData, task.result)
end
end)
function saveData(data, path, callback)
save data to file.
Definition api.lua:239

Sample Management

Loading and purging samples can take time. Use async operations to avoid audio glitches:

function onInit()
-- Purge samples from a layer
local task = purge(Program.layers[1])
-- Optionally wait for completion (see the polling pattern above)
while not task.finished do
wait(50)
end
print("Layer purged successfully")
end
A layer of sounds.
Definition Engine.cpp:274
table layers
Layer list for this Program (1-indexed, use #Program.layers to get the count)
Definition Engine.cpp:266

State Management

Save and restore the entire ScriptProcessor state, including widget values and custom data stored via onSave / onLoad :

  • saveState — serialize the processor state to a file
  • loadState — restore processor state from a file
-- Let the user pick a destination, then save state
browseForFile("save", "Save State", "MyState", "*.state", function(task)
if task.success then
saveState(task.result, function(t)
if t.success then print("State saved") end
end)
end
end)
-- Restore state from a user-selected file
browseForFile("open", "Load State", "", "*.state", function(task)
if task.success then
loadState(task.result, function(t)
if t.success then print("State restored") end
end)
end
end)
function saveState(path, callback)
save state to file.
Definition api.lua:121
function loadState(path, callback)
load state from file.
Definition api.lua:106

Custom Data I/O

Save and load arbitrary data to and from files:

JSON encoding does not support cyclic references or userdata values. Only plain Lua types (numbers, strings, booleans, and nested tables) are serialized.

The save callbacks receive a task object (check task.success). The load callbacks receive the loaded data directly: a decoded Lua table for loadData, a plain string for loadTextData. If the file cannot be read, the load callback is simply not invoked.

-- Save a preset table as JSON
local preset = {name = "Warm Pad", cutoff = 800, resonance = 0.6}
saveData(preset, "/path/to/preset.json", function(task)
if task.success then print("Preset saved") end
end)
-- Load it back: the callback receives the decoded table
loadData("/path/to/preset.json", function(data)
print("Loaded preset: " .. data.name)
end)
-- Save raw text
saveTextData("some config line\nanother line", "/path/to/config.txt", function(task)
if task.success then print("Text saved") end
end)
-- Load raw text: the callback receives the file contents as a string
loadTextData("/path/to/config.txt", function(text)
print("Contents: " .. text)
end)
function saveTextData(data, path, callback)
save string to file.
Definition api.lua:173
function loadTextData(path, callback)
load string from file.
Definition api.lua:204
function loadData(path, callback)
load data from file.
Definition api.lua:145

When polling instead of passing a callback, nothing is decoded on your behalf: the returned task (an AsyncDataLoadTask) exposes the raw file contents through its data property — a string, or nil if the file could not be read. For files written by saveData, decode the JSON yourself with the uvi.json module, which provides decode and encode:

local json = require('uvi.json')
function onInit()
local task = loadData("/path/to/preset.json")
while not task.finished do
wait(50)
end
if type(task.data) == "string" then
local preset = json.decode(task.data)
print("Loaded preset: " .. preset.name)
end
end

Sample Loading

Load audio files into oscillators and configure their playback parameters:

  • loadSample — load an audio file into an oscillator
  • setPlaybackOptions — set start, end, loop points, and direction (units are in samples; loopType: 0=None, 1=Forward, 2=Alternate, 3=OneShot)
  • unpurge — reload previously purged samples
Note
None of these target a SampleMappingOscillator, which loads a whole sample set from a mapping file and purges it a layer at a time — see Sample Mapping.
local osc = Program.layers[1].keygroups[1].oscillators[1]
-- Load a sample and configure loop points
loadSample(osc, "/path/to/kick.wav", function(task)
if task.success then
-- Set playback: start=0, end=44100, loop Forward from 1000 to 40000, forward
setPlaybackOptions(osc, 0, 44100, 1, 1000, 40000, true, function(t)
if t.success then print("Playback configured") end
end)
end
end)
-- Reload purged samples
unpurge(osc, function(task)
if task.success then print("Samples reloaded") end
end)
function setPlaybackOptions(oscillator, start, end_, loopType, loopStart, loopEnd, playForward, callback)
set playback options inside the oscillator
Definition api.lua:92
function loadSample(oscillator, path, callback)
load a sample inside the oscillator
Definition api.lua:67
function unpurge(target, callback)
unpurge an/several element(s)/oscillator(s) content in order to load their memory.
Definition api.lua:395

Oscillator Introspection

After loading a sample, you can query the oscillator for metadata:

local osc = Program.layers[1].keygroups[1].oscillators[1]
-- Sample information (available on sample-based oscillators)
local info = osc.sampleInfo
if info then
print("Name:", info.name)
print("Duration:", info.duration, "ms")
print("Sample rate:", info.samplerate, "Hz")
print("Channels:", info.channels)
end
-- Slice information (available on Slice oscillators)
if osc.numSlices > 0 then
for i = 1, osc.numSlices do
local slice = osc:getSliceInfo(i)
print("Slice", i, "start:", slice.start, "ms", "duration:", slice.duration, "ms")
end
end

See Oscillator for the full list of properties including purged, looplabInfo, and numSlices.

MIDI File Operations

Load, create, and save MIDI files:

The task passed to the callback exposes the sequence through its midi property. Events are accessed per track with getNumEventsForTrack and getEvent (both 1-indexed).

-- Load a MIDI file and iterate the events of its first track
loadMidi("/path/to/pattern.mid", function(task)
if task.success then
local seq = task.midi
for i = 1, seq:getNumEventsForTrack(1) do
local event = seq:getEvent(1, i)
print("Event " .. i .. ": type=" .. event.type
.. " byte1=" .. event.byte1)
end
end
end)
-- Create an empty MIDI sequence (2 tracks, up to 256 events per track), then save
createMidiFile(2, 256, function(task)
if task.success then
local seq = task.midi
saveMidi(seq, "/path/to/output.mid", function(t)
if t.success then print("MIDI file saved") end
end)
end
end)
function createMidiFile(maxNumTracks, maxNumEvents, callback)
create a midi file asynchronously
Definition api.lua:275
function loadMidi(path, callback)
load a midi file asynchronously
Definition api.lua:260
function saveMidi(midifile, path, callback)
save a midi file asynchronously
Definition api.lua:296

Impulse Response Loading

Load impulse response files into a IReverb effect:

local reverb = Program.inserts[1] -- a SampledReverb effect
loadImpulse(reverb, "/path/to/hall.wav", function(task)
if task.success then print("IR loaded") end
end)
table inserts
all InsertEffect for this node
Definition Engine.cpp:262
SampledReverb.
Definition Engine.cpp:213
function loadImpulse(reverb, path, callback)
load and impulse response inside the reverb.
Definition api.lua:327

Thread Safety

Background Execution
All async operations execute on background threads, completely separate from the real-time audio thread. This ensures audio processing is never interrupted.
Callback Execution
The completion callback is executed on the script's main thread, making it safe to access script variables and call uvi-script API functions.
Data Sharing
Data passed between threads is safely copied. You don't need to worry about race conditions or locks when using async operations.

Best Practices

Check Success Status
Always verify the operation succeeded before using results:
browseForFile("open", "Select file", "", "*.*", function(task)
if task.success then
-- Safe to use task.result
print("File: " .. task.result)
else
-- User cancelled or error occurred
print("No file selected")
end
end)
See also
Async, AsyncTask, browseForFile, purge
spawn, wait, onSave, onLoad, Porting from Kontakt (KSP)