Skip to content

Events & Callbacks

Persistent scripts stay alive and react to DAW changes in real time. Start a persistent script with lua:start instead of lua:execute.

pl.on("event_name", function()
-- called when the event fires
end)
EventFires when
transport_changedPlay/stop state changes
tempo_changedTempo changes
track_addedA track is added
track_removedA track is removed
selection_changedSelection changes
region_addedA region is added
region_removedA region is removed
beatEach beat (musical time)
barEach bar (musical time)
midiIncoming MIDI event
pl.on_beat(function() ... end) -- same as pl.on("beat", fn)
pl.on_bar(function() ... end) -- same as pl.on("bar", fn)
pl.on_midi(function() ... end) -- same as pl.on("midi", fn)
pl.off("beat") -- remove all beat callbacks
local beat_count = 0
pl.on_beat(function()
beat_count = beat_count + 1
local transport = pl.get_transport()
pl.log("Beat " .. beat_count .. " at " .. transport.tempo .. " BPM")
end)
pl.on("transport_changed", function()
local t = pl.get_transport()
if not t.isPlaying then
-- Mute all armed tracks when playback stops
local tracks = pl.get_tracks()
for _, track in ipairs(tracks) do
if track.arm and not track.mute then
pl.cmd("track:toggle-mute", { trackId = track.id })
end
end
end
end)

From the command system:

-- Start a persistent script (from another script or the command palette)
pl.cmd("lua:start", { scriptId = "my-script" })
-- Stop it
pl.cmd("lua:stop", { scriptId = "my-script" })
-- Stop all persistent scripts
pl.cmd("lua:stop-all")
  • Events are detected by reading atomics — zero allocations on the audio thread
  • Callbacks run on the main thread during the render frame
  • The audio thread never knows Lua exists
  • Scripts with no callbacks or timers are automatically cleaned up