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:
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:
-- 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
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:
-- Purge samples from a layer
-- Optionally
wait for completion (see the polling pattern above)
while not task.finished do
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
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
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
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')
local task =
loadData("/path/to/preset.json")
while not task.finished do
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.
-- 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
if t.success then print("Playback configured") end
end)
end
end)
-- Reload purged samples
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:
-- 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
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:
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:
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)