This page collects all bundled Lua examples, organized by category. Each script is a self-contained starting point that you can load directly into MachFive and tweak to fit your own patches.
| Note Processing | Voice Effects | Performance & Articulation | Sequencing | Utilities
|
|
Chorder
chord harmonizer with presets
|
Ensemble
ensemble with pan and time spread
|
legato
legato with crossfade and retrigger
|
StepSequencer
beat-synced step sequencer with position display
|
CCFilter
block a specific midi cc
|
|
InvertPitch
mirror notes around a center pitch
|
tremolo
amplitude and pan lfo
|
monoBassLine
monophonic bass synth with sequencer
|
|
CCRedirect
remap a midi cc number to another
|
|
Keyswitch
automatic layer switching via keyswitches
|
Unison
detuned unison voices
|
portamento
portamento with pitch glide
|
|
CCSmooth
smooth incoming cc messages over time
|
|
quarterTone
quarter-tone keyboard mapping
|
vibrato
per-voice pitch vibrato
|
|
|
IRLoader
hierarchical menu for impulse response loading
|
|
TimbreShifting
borrow neighbouring keygroup timbres
|
VoiceTracker
track and manipulate active voices
|
|
|
MidiLearn
midi learn for note assignment
|
|
|
|
|
|
PanelSwitcher
tab-based panel switching with main/fx/seq views
|
|
|
|
|
|
SampleDropper
load samples via drag and drop
|
Note Processing
Chorder
Chord Harmonizer with Presets
Generates chords by playing up to 6 simultaneous notes with configurable pitch shifts and velocity scaling. Ships with several named presets (Major, Fifth, Jazz, Debusian, etc.) selectable from a Menu widget.
Demonstrates: playNote, Knob, Menu, preset system, velocity scaling
local shift = {}
local velocity = {}
local presets = {
{"--", {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}},
{"Default", {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}},
{"Debusian", {-16, 1.02}, {3, 0.54}, {18, 0.83}, {-6, 0.74}, {-9, 1.16}, {0, 1}},
{"Film Noir", {-2, 0.83}, {5, 1.25}, {-6, 0.74}, {12, 0.65}, {0, 1}, {0, 1}},
{"Jazz for dummies", {3, 0.54}, {5, 0.88}, {-16, 1}, {-10, 1}, {0, 1}, {0, 1}},
{"Major", {4, 1}, {7, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}},
{"House for to go", {3, 1}, {7, 1}, {-12, 1}, {0, 1}, {0, 1}, {0, 1}},
{"Fifth", {0, 1}, {7, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}},
{"Fourth", {0, 1}, {5, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}},
{"Grandiosa", {-12, 1}, {-24, 1.33}, {12, 1.41}, {7, 1}, {0, 1}, {0, 1}}
}
for i=1,6 do
shift[i] =
Knob(
"Shift"..tostring(i), 0, -36, 36,
true)
end
for i=1,6 do
velocity[i] =
Knob("Velocity_"..tostring(i), 1, 0.01, 2)
end
local presetNames = {}
for i, preset in ipairs(presets) do
presetNames[i] = preset[1]
end
presetMenu =
Menu(
"Presets", presetNames)
presetMenu.
persistent =
false -- avoid overwrite of current state at reload
presetMenu.changed = function(self)
for i=1,6 do
shift[i].value = presets[self.value][i+1][1]
velocity[i].value = presets[self.value][i+1][2]
end
end
presetMenu:changed()
local done = {} -- store already played notes in order to avoid redundancy
for i=1,6 do
if not done[shift[i].value] then
playNote(e.note + shift[i].value, math.min(127, e.velocity*velocity[i].value))
done[shift[i].value] = true;
end
end
end
-- eat event, release is automatic with
playNote
end
Knob widget.
Definition ui.cpp:1424
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 playNote(note, vel, duration, layer, channel, input, vol, pan, tune, slice, oscIndex)
helper function to generate a note event.
Definition api.lua:931
⬇ Download Chorder.lua
InvertPitch
Mirror Notes Around a Center Pitch
Mirrors incoming MIDI notes around a user-defined center pitch using a Knob widget. Notes equidistant above the center are mapped below it and vice-versa, creating an intervallic inversion effect.
Demonstrates: onNote callback, Knob widget, pitch arithmetic, playNote
CenterPitch =
Knob(
"Center_Pitch", 60, 0, 127,
true)
local center = CenterPitch.value
local delta = e.note-center
local note = center - delta
if note>=0 and note<=127 then
end
end
function onRelease()
-- eat event
end
⬇ Download InvertPitch.lua
Keyswitch
Automatic Layer Switching via Keyswitches
Assigns a range of low MIDI keys as keyswitches that select which layer receives subsequent notes. The keyswitch range is coloured red on the keyboard for visual feedback.
Demonstrates: Program.layers, playNote with layer parameter, setKeyColour, onNote / onRelease
local numLayers = #
Program.
layers -- number of layer in the program
local lastLayerActivated = 1 -- id of the last activated layer
local KSbaseNote = 36 -- MIDI note where keyswitch are located
if e.note >= KSbaseNote and e.note < KSbaseNote + numLayers then -- if the note is one of the keyswitch keys
lastLayerActivated = e.note - KSbaseNote + 1 -- update the activated layer id
else
playNote(e.note, e.velocity, -1, lastLayerActivated) -- not a keyswitch key so we play the note on the activated layer
end
end
-- eat
event as release is done automatically by
playNote
end
for i=KSbaseNote, KSbaseNote+numLayers-1 do
end
A Patch that represents a monotimbral instrument.
Definition Engine.cpp:238
table layers
Layer list for this Program (1-indexed, use Program.layers to get the count)
Definition Engine.cpp:247
function setKeyColour(note, colour)
customize the keyboard colours.
Definition ui.lua:50
⬇ Download Keyswitch.lua
quarterTone
Quarter-Tone Keyboard Mapping
Remaps the standard 12-tone keyboard to a 24-tone quarter-tone scale relative to a selectable root note. Even intervals map directly; odd intervals are detuned by 50 cents using changeTune.
Demonstrates: Menu widget, changeTune, microtonal pitch mapping, modular arithmetic
notes = {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"}
notenames={}
for i=1,128 do
notenames[i] = notes[(1+(i-1)%12)] .. (math.floor(i/12) - 2)
end
Root =
Menu("root", notenames)
Root.value = 60+1
local root = Root.value-1
local note = e.note
local velocity = e.velocity
local detune = 0
if (e.note-root)%2 == 0 then -- no detune
note = root + math.floor((e.note-root)/2)
else
if e.note > root then
note = math.floor((e.note-root)/2) + root + 1
detune = -0.5 -- minus 50 cents
elseif e.note < root then
note = math.floor((e.note-root)/2) + root - 1
detune = 0.50 -- plus 50 cents
end
end
if detune ~= 0 then
end
end
function onRelease(e)
-- release is automatic
end
void setSize(number w, number h)
set the script UI dimensions explicitly.
function changeTune(voiceId, shift, relative, immediate)
change the tuning of specific voice in (fractionnal) semitones.
Definition api.lua:477
⬇ Download quarterTone.lua
TimbreShifting
Borrow Neighbouring Keygroup Timbres
Shifts the played note into a neighbouring keygroup and retunes it back to the original pitch, effectively borrowing that keygroup's timbre. The shift amount is controlled by a bipolar Knob.
Demonstrates: playNote, changeTune with absolute flag, timbral manipulation
shiftKnob =
Knob(
"shift", 0, -5, 5,
true)
local shift = shiftKnob.value
local note = e.note + shift
local
id =
playNote(note, e.velocity, -1)
end
function onRelease()
-- eat event release is automatic in
playNote
end
⬇ Download TimbreShifting.lua
Voice Effects
Ensemble
Ensemble with Pan and Time Spread
Similar to Unison but adds per-voice pan spread and staggered onset timing to simulate an ensemble. Each voice is shifted, retuned, panned, and delayed by a small jitter amount.
Demonstrates: playNote, changeTune, changePan, changeVolume, wait
PanSpread =
Knob(
"PanSpread", 1.0, 0, 1)
TimeSpread =
Knob("TimeSpread", 0.5, 0, 1)
shifts = {0, 1, -1, 2, -2}
--shifts = {0, 1, -1, 2, -2, 3, -3}
--shifts = {0, 1, -1, 2, -2, 3, -3, 4, -4}
local panSpread = PanSpread.value
local timeSpread = TimeSpread.value
local numShifts = #shifts
for i=1,numShifts do
local shift = shifts[i]
local note = e.note + shift
local tune = -shift
wait(timeSpread*10) -- 10 ms jitter
end
end
function onRelease()
-- eat event release is automatic in
playNote
end
function wait(ms)
suspend the current thread callback execution for the given number of samples.
Definition wrapper.lua:39
function changeVolume(voiceId, gain, relative, immediate)
changes a voice's volume.
Definition api.lua:519
function changePan(voiceId, pan, relative, immediate)
changes the pan position of a specific note event.
Definition api.lua:504
⬇ Download Ensemble.lua
tremolo
Amplitude and Pan LFO
Modulates volume and pan of each voice with a sine-wave LFO whose frequency scales with MIDI note number. The volume oscillates between 0 and 1 while pan follows a cosine (90-degree phase offset).
Demonstrates: changeVolume, changePan, isNoteHeld, wait, postEvent
lfoFreq =
Knob(
"freq", 4.0, 0, 10) -- 4 Hz
local duration = 0 -- in seconds
local step = 5 -- ms
local freq = lfoFreq.value * e.note/128.0
local volume = 0.5 * ( 1 + math.sin(2 * math.pi * freq * duration))
local pan = math.sin(2 * math.pi * freq * duration + math.pi/2)
duration = duration + step/1000.0
end
end
function isNoteHeld()
return true is the note that created this callback is still held.
Definition api.lua:810
function postEvent(e, delta)
send a script event back to the script engine event queue.
Definition api.lua:842
⬇ Download tremolo.lua
Unison
Detuned Unison Voices
Stacks multiple detuned copies of the incoming note. The number of voices and the maximum detune spread are adjustable. Alternating voices are tuned sharp and flat in increasing amounts, and volume is auto-scaled by the square root of the voice count.
Demonstrates: playNote, Knob, changeTune, changeVolume
Voices =
Knob(
"numVoices", 5, 2, 10,
true)
Detune =
Knob("Detune", 10.0, 0, 40) -- cents
local nVoices = Voices.value
local detune = Detune.value
for i=1,nVoices do
local note = e.note
local i2 = math.floor(i/2)
local rest = i%2
local tune = detune * i2 / 100.0
if rest == 1 then
tune = tune * -1.0
end
local
id =
playNote{e.note, e.velocity, vol=1/math.sqrt(nVoices), tune=tune}
end
end
-- eat
event release is automatic in
playNote
end
⬇ Download Unison.lua
vibrato
Per-Voice Pitch Vibrato
Applies a sine-wave pitch vibrato to each voice independently. Frequency and depth are adjustable in real time via Knob widgets. The LFO runs inside the onNote callback using a wait loop gated by isNoteHeld.
Demonstrates: changeTune, isNoteHeld, wait, per-voice LFO, Knob
Freq =
Knob(
"Freq", 4.0, 0, 10) -- 4 Hz
Depth =
Knob(
"Depth", 0.5, 0, 1)
local step = 5 -- ms
local
id =
postEvent(e) -- duration is omitted
local phase = 0
local depth = Depth.value
local freq = Freq.value
local modulation = depth * math.sin(2 * math.pi * phase)
phase = phase + (step/1000.0) * freq
end
end
⬇ Download vibrato.lua
VoiceTracker
Track and Manipulate Active Voices
Maintains a table of all active voice IDs keyed by note number. Applies a velocity-dependent pan spread: low notes left, high notes right. Demonstrates the voice tracking pattern used in many real-world scripts.
Demonstrates: postEvent, changePan, voice ID tracking, onNote / onRelease
local voices = {} -- track active voices: voices[note] = voiceId
voices[e.note] = id
-- pan spread: note 0 = hard left, note 127 = hard right
local pan = (e.note / 127) * 2 - 1
end
voices[e.note] = nil
end
⬇ Download VoiceTracker.lua
Performance & Articulation
legato
Legato with Crossfade and Retrigger
Implements monophonic legato by crossfading between overlapping notes. New notes fade in from a sample offset while the previous note fades out. An optional retrigger mode re-voices the last held note on release.
Demonstrates: postEvent, fadein, fadeout, setSampleOffset, releaseVoice, note stack
local notes = {}
Fade =
Knob(
"fade", 40, 10, 100)
local sampleOffset = 50 -- ms
if #notes > 0 then
local fadetime = Fade.
value
fadeout(notes[#notes].
id, fadetime,
true)
table.insert(notes, e)
else
table.insert(notes, e)
end
end
function onRelease(e)
for i,noteon in ipairs(notes) do
if noteon.note == e.note then
table.remove(notes, i)
local shouldRetrigger = Retrigger.value and #notes > 0 and i > #notes
if shouldRetrigger then
local noteon = notes[#notes]
local
id =
playNote(noteon.note, noteon.velocity)
noteon.id = id
local fadetime = Fade.value
end
break
end
end
end
function releaseVoice(voiceId)
release a specific voice by sending it a note off message
Definition api.lua:1008
function fadeout(voiceId, duration, killVoice, reset, layer)
starts a volume fade-out.
Definition api.lua:598
function setSampleOffset(voiceId, value)
changes a sample starting point in milliseconds.
Definition api.lua:558
function fadein(voiceId, duration, reset, layer)
starts a volume fade-in for a specific voice.
Definition api.lua:632
⬇ Download legato.lua
monoBassLine
Monophonic Bass Synth with Sequencer
A complete instrument script featuring oscillator mixing, amplitude ADSR, resonant filter with LFO modulation, and an 8-step pitch sequencer. Demonstrates full UI layout with multiple Panels, Tables, Menus, and Knobs, as well as Mapper and Unit types for automatic value scaling and display.
Demonstrates: Panel, Table, Menu, Knob, Mapper, Unit, waitBeat, setSize, makePerformanceView
--------------------------------------------------------------------------------
-- init direct access to most used engine nodes
--------------------------------------------------------------------------------
local ampEnv =
Program.
layers[1].keygroups[1].modulations[
"Amp. Env"]
local oscillators = keygroup.oscillators
local filter = keygroup.inserts[1]
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local oscPanel =
Panel(
"Oscs")
local oscVolume = oscPanel:
Knob("Osc", 1, 0, 1)
oscVolume.fillColour = "lightgrey"
oscVolume.outlineColour = "orange"
oscVolume.mapper =
Mapper.Cubic
oscVolume.unit =
Unit.LinearGain
oscVolume.changed = function(self)
oscillators[1]:setParameter("Gain", self.value)
end
oscVolume:changed()
local subVolume = oscPanel:
Knob("Sub", 0, 0, 1)
subVolume.fillColour = "lightgrey"
subVolume.outlineColour = "orange"
subVolume.mapper =
Mapper.Cubic
subVolume.unit =
Unit.LinearGain
subVolume.changed = function(self)
oscillators[2]:setParameter("Gain", self.value)
end
subVolume:changed()
local noiseVolume = oscPanel:
Knob("Noise", 0, 0, 1)
noiseVolume.fillColour = "lightgrey"
noiseVolume.outlineColour = "orange"
noiseVolume.mapper =
Mapper.Cubic
noiseVolume.unit =
Unit.LinearGain
noiseVolume.changed = function(self)
oscillators[3]:setParameter("Gain", self.value)
end
noiseVolume:changed()
--------------------------------------------------------------------------------
-- Amplitude ADSR
--------------------------------------------------------------------------------
local adsrPanel =
Panel("AmpEnv")
local attack = adsrPanel:
Knob("Attack", 0.009, 0.009, 1.06)
attack.outlineColour = "magenta"
attack.mapper =
Mapper.Exponential
attack.unit =
Unit.Seconds
attack.changed = function(self)
ampEnv:setParameter("AttackTime", self.value)
end
attack:changed()
local decay = adsrPanel:
Knob("Decay", 0.174, 0.174, 2.477)
decay.outlineColour = "magenta"
decay.mapper =
Mapper.Exponential
decay.unit =
Unit.Seconds
decay.changed = function(self)
ampEnv:setParameter("DecayTime", self.value)
end
decay:changed()
local sustain = adsrPanel:
Knob("Sustain", 1, 0, 1)
sustain.outlineColour = "magenta"
sustain.unit =
Unit.PercentNormalized
sustain.changed = function(self)
ampEnv:setParameter("SustainLevel", self.value)
end
sustain:changed()
local release = adsrPanel:
Knob("Release", 0.05, 0.05, 5.028)
release.outlineColour = "magenta"
release.mapper =
Mapper.Exponential
release.unit =
Unit.Seconds
release.changed = function(self)
ampEnv:setParameter("ReleaseTime", self.value)
end
release:changed()
--------------------------------------------------------------------------------
-- Filter
--------------------------------------------------------------------------------
local filterPanel =
Panel("Filter")
-- widget constructors also accept a single table, mixing positional and named
-- arguments (similar to Python keyword arguments)
local cutoff = filterPanel:
Knob{
"Cutoff", 20000, 20, 20000,
fillColour = "lightgrey",
outlineColour = "yellow",
changed = function(self)
filter:setParameter("Freq", self.value)
end
}
cutoff:changed()
local reso = filterPanel:
Knob("Reso", 0, 0, 1)
reso.fillColour = "lightgrey"
reso.outlineColour = "yellow"
reso.unit =
Unit.PercentNormalized
reso.changed = function(self)
filter:setParameter("Q", self.value)
end
reso:changed()
local lfoToCutoff = filterPanel:
Knob("lfoToCutoff", 0, -1, 1)
lfoToCutoff.fillColour = "lightgrey"
lfoToCutoff.outlineColour = "yellow"
lfoToCutoff.unit =
Unit.PercentNormalized
lfoToCutoff.changed = function(self)
filter:getParameterConnections("Freq")[1]:setParameter("Ratio", self.value)
end
lfoToCutoff:changed()
--------------------------------------------------------------------------------
-- Mini bass line Sequencer
--------------------------------------------------------------------------------
local seqPanel =
Panel("Sequencer")
local resolutions = {0.5, 0.25, 0.125}
local resolutionNames = {"1/8", "1/16", "1/32"}
local numSteps = 8
local steps = seqPanel:
Table(
"pitch", numSteps, 0, -12, 12,
true)
local res = seqPanel:
Menu{
"Resolution", resolutionNames, selected=2}
-- displayText overrides the automatic unit display with a custom string,
-- useful when the value needs domain-specific formatting
local gate = seqPanel:
Knob(
"Gate", 1, 0, 1)
gate.changed = function(self)
self.displayText = string.format("%d%%", self.value * 100)
end
gate:changed()
res.backgroundColour = "black"
res.textColour = "cyan"
res.arrowColour = "grey"
res.outlineColour = "#1fFFFFFF" -- transparent white
local positionTable = seqPanel:Table("steps"..tostring(i), numSteps, 0, 0, 1, true)
positionTable.enabled = false
positionTable.persistent = false
function clearPosition()
for i = 1, numSteps do
positionTable:setValue(i, 0)
end
end
local arpId = 0
local heldNotes = {}
function arpeg(arpId_)
local index = 0
while arpId_ == arpId do
local e = heldNotes[#heldNotes]
local p = resolutions[res.value]
local note = e.note + steps:getValue(index+1)
playNote(note, e.velocity, beat2ms(gate.value*p))
positionTable:setValue((index - 1 + numSteps) % numSteps + 1, 0)
positionTable:setValue((index % numSteps)+1, 1)
index = (index+1) % numSteps
waitBeat(p)
end
end
--------------------------------------------------------------------------------
-- callbacks
--------------------------------------------------------------------------------
function onNote(e)
table.insert(heldNotes, e)
if #heldNotes == 1 then
arpeg(arpId)
end
end
function onRelease(e)
for i,v in ipairs(heldNotes) do
if v.note == e.note then
table.remove(heldNotes, i)
if #heldNotes == 0 then
clearPosition()
arpId = arpId + 1
end
break
end
end
end
--------------------------------------------------------------------------------
-- UI positioning
--------------------------------------------------------------------------------
local margin = 10
oscPanel.x = margin
oscPanel.y = margin
oscPanel.width = 400
oscPanel.height = 60
filterPanel.x = oscPanel.x
filterPanel.y = oscPanel.y + oscPanel.height + margin
filterPanel.width = 400
filterPanel.height = 60
adsrPanel.x = filterPanel.x
adsrPanel.y = filterPanel.y + filterPanel.height + margin
adsrPanel.width = 500
adsrPanel.height = 60
seqPanel.x = adsrPanel.x
seqPanel.y = adsrPanel.y + adsrPanel.height + margin
seqPanel.width = 630
seqPanel.height = 150
steps.y = steps.y + 10
steps.width = 500
steps.height = 130
positionTable.x = steps.x
positionTable.y = steps.y - 10
positionTable.width = steps.width
positionTable.height = 10
res.x = steps.x + steps.width + margin
res.y = steps.y
gate.x = steps.x + steps.width + margin
gate.y = steps.y + 70
setSize(650, 380)
makePerformanceView()
Predefined mapper types.
Definition ui.cpp:534
@ Exponential
Exponential mapper, the parameter's range should be strictly positive.
Definition ui.cpp:546
The Synthesis primitive.
Definition Engine.cpp:301
Panel widget.
Definition ui.cpp:1502
Table widget.
Definition ui.cpp:1231
Predefined unit types.
Definition ui.cpp:592
@ Hertz
display Hz symbol.
Definition ui.cpp:619
⬇ Download monoBassLine.lua
portamento
Portamento with Pitch Glide
Creates a smooth pitch glide between consecutive notes using a custom coroutine-based glide function. The outgoing note glides up while the incoming note glides down, producing a continuous portamento effect.
Demonstrates: changeTune, spawn, fadein, fadeout, playNote
local numNotes = 0
local lastid = -1
local lastnote = 0
Fade =
Knob(
"fade", 100, 1, 500)
function glide(id, from, to, duration, period)
duration = duration or 100 -- 100 ms
period = period or 10 -- 5 ms
local doglide = function()
local value = from
local increment = (to - from) * period / duration
local t = 0
local immediate = true
while t < duration do
immediate = false
value = value + increment
t = t + period
end
end
_spawn(doglide)
end
if numNotes > 0 then
local fadetime = Fade.value
glide(lastid, 0, (e.note-lastnote), fadetime)
fadein(lastid, fadetime, true)
glide(lastid, (lastnote-e.note), 0, fadetime)
lastnote = e.note
else
lastnote = e.note
end
numNotes = numNotes + 1
end
function onRelease(e)
numNotes = math.max(0, numNotes - 1)
end
⬇ Download portamento.lua
Sequencing
StepSequencer
Beat-Synced Step Sequencer with Position Display
A simple 8-step pitch sequencer that plays in sync with the host tempo. Uses a Table widget for step editing and a second Table as a visual position indicator. Demonstrates waitBeat, Table widget, and the sequencer pattern found in many factory presets.
Demonstrates: Table, waitBeat, playNote, beat2ms, spawn, Panel
local numSteps = 8
local panel =
Panel(
"Sequencer")
panel.backgroundColour = "3f000000"
local steps = panel:
Table("pitch", numSteps, 0, -12, 12, true)
steps.width = 500
steps.height = 120
-- position indicator (read-only)
local position = panel:
Table("pos", numSteps, 0, 0, 1, true)
position.enabled = false
position.persistent = false
position.width = steps.width
position.height = 10
local resolution = panel:
Menu{
"Resolution", {
"1/8",
"1/16",
"1/32"}, selected = 2}
local resValues = {0.5, 0.25, 0.125} -- in beats
local seqId = 0
seqId = seqId + 1
local myId = seqId
local step = 0
while myId == seqId do
local res = resValues[resolution.value]
local note = e.note + steps:getValue(step + 1)
-- update position display
position:setValue((step - 1 + numSteps) % numSteps + 1, 0)
position:setValue(step + 1, 1)
step = (step + 1) % numSteps
end
end
function onRelease(e)
seqId = seqId + 1 -- stop the running loop
for i = 1, numSteps do position:setValue(i, 0) end
end
@ PercentNormalized
display % symbol but actual value is in the 0..1 range
Definition ui.cpp:607
function beat2ms(beat)
Convert beat duration to milliseconds based on the current tempo.
Definition conversions.lua:38
function waitBeat(beat)
Suspend execution for a tempo-synchronized duration in beats.
Definition conversions.lua:142
void makePerformanceView()
make this script User Interface visible in performance view.
Definition ui.lua:107
⬇ Download StepSequencer.lua
Utilities
CCFilter
Block a Specific MIDI CC
Blocks a user-selected CC from passing through. All other events are forwarded unchanged. Demonstrates selective event filtering with onController.
Demonstrates: onController, postEvent, event filtering, Knob
local blocked =
Knob{
"Blocked CC", 64, 0, 127,
true} -- integer knob
if e.controller ~= blocked.value then
end
end
void onController(table e)
event callback that will receive all incoming control-change events when defined.
⬇ Download CCFilter.lua
CCRedirect
Remap a MIDI CC Number to Another
Remaps incoming CC messages from one controller number to another. Other events pass through unchanged. Demonstrates event property mutation before forwarding.
Demonstrates: onController, postEvent, event mutation, Menu
local source =
Menu{
"Source CC", {
"1 (Mod Wheel)",
"2 (Breath)",
"7 (Volume)",
"11 (Expression)"}}
local target =
Menu{
"Target CC", {
"1 (Mod Wheel)",
"2 (Breath)",
"7 (Volume)",
"11 (Expression)"}, selected = 4}
local ccMap = {1, 2, 7, 11}
if e.controller == ccMap[source.value] then
e.controller = ccMap[target.value] -- mutate the event
end
end
⬇ Download CCRedirect.lua
CCSmooth
Smooth Incoming CC Messages Over Time
Applies exponential smoothing to a MIDI CC, producing gradual transitions instead of abrupt jumps. Demonstrates spawning a background task from onController and generating CC output.
Demonstrates: onController, controlChange, spawn, wait, Knob, Mapper
local targetCC =
Knob{
"CC", 1, 0, 127,
true} -- which CC to smooth
local currentValue = 0
local targetValue = 0
local running = false
function smoothCC()
running = true
while math.abs(currentValue - targetValue) > 0.5 do
currentValue = currentValue + (targetValue - currentValue) * 0.15
end
currentValue = targetValue
running = false
end
if e.controller == targetCC.value then
targetValue = e.value
if not running then
end
else
end
end
@ Cubic
Cubic mapper:
Definition ui.cpp:570
@ Seconds
display s symbol.
Definition ui.cpp:611
function controlChange(cc, val, ch, inp)
sends a ControlChange event.
Definition api.lua:1033
function spawn(fun,...)
Launch a function in a separate parallel execution thread (deferred execution)
Definition api.lua:1165
⬇ Download CCSmooth.lua
IRLoader
Hierarchical Menu for Impulse Response Loading
Demonstrates a hierarchical Menu (with path-based entries) to browse and load impulse responses into a SampledReverb insert. The menu creates nested sub-menus automatically from the "/" separators.
Demonstrates: Menu, Label, loadImpulse
-- Hierarchical menu: "/" creates sub-menu levels
local irList = {
"Halls/Large Hall",
"Halls/Medium Hall",
"Halls/Small Hall",
"Plates/Bright Plate",
"Plates/Dark Plate",
"Rooms/Studio A",
"Rooms/Studio B",
"Rooms/Living Room",
"Springs/Short Spring",
"Springs/Long Spring"
}
local irMenu =
Menu{
"IR", irList,
hierarchical = true, -- enable nested sub-menus
backgroundColour = "333333",
textColour = "white"
}
local status =
Label{
"status"}
status.
text =
"No IR loaded"
status.textColour = "aaaaaa"
irMenu.changed = function(self)
local irName = self.selectedText
local irPath = "impulses/" .. irName .. ".wav"
status.text = "Loading..."
status.textColour = "aaaaaa"
if task.success then
status.text = irName
status.textColour = "00FF88"
else
status.text = "Failed: " .. irName
status.textColour = "FF4444"
end
end)
end
text label widget.
Definition ui.cpp:984
string text
text to display on screen
Definition ui.cpp:992
table inserts
all InsertEffect for this node
Definition Engine.cpp:243
function loadImpulse(reverb, path, callback)
load and impulse response inside the reverb.
Definition api.lua:316
⬇ Download IRLoader.lua
MidiLearn
MIDI Learn for Note Assignment
Demonstrates the MIDI learn pattern: press a button to enter learn mode, then play a note to assign it. The learned note is displayed and used to transpose incoming notes.
Demonstrates: Button, Label, onNote, setKeyColour, resetKeyColour, MIDI learn pattern
local learnedNote = 60
local learning = false
local learnBtn =
Button{
"Learn"}
learning = true
status.text = "Play a note..."
end
local status =
Label{
"status"}
if learning then
-- assign the played note
learnedNote = e.note
status.text = string.format("Note: %d", learnedNote)
learning = false
else
-- transpose relative to learned note
local offset = e.note - 60
playNote(learnedNote + offset, e.velocity, -1)
end
end
function onRelease(e)
-- eat releases:
playNote handles them via duration -1
end
function resetKeyColour(note)
customize the keyboard colours.
Definition ui.lua:61
⬇ Download MidiLearn.lua
PanelSwitcher
Tab-Based Panel Switching with Main/FX/Seq Views
Demonstrates the standard pattern for building a tabbed interface. Three OnOffButtons act as tab selectors, toggling visibility of three Panel containers. Each panel holds its own set of widgets. This pattern is used extensively in Falcon factory presets.
Demonstrates: OnOffButton, Panel, Knob, Slider, Table, Mapper, Unit
-- Tab buttons
local tabNames = {"Main", "FX", "Seq"}
local tabButtons = {}
local tabPanels = {}
local contentY = 30
local contentH = 100
for i = 1, #tabNames do
bounds = {(i - 1) * 80, 0, 78, 25},
backgroundColourOff = "333333",
backgroundColourOn = "FF8800",
textColourOff = "aaaaaa",
textColourOn = "ffffff",
persistent = false
}
tabPanels[i] =
Panel{bounds = {0, contentY, 500, contentH},
backgroundColour = "2f000000"
}
end
-- Main panel: basic voice controls
-- FX panel: filter controls
-- Seq panel: step sequencer
tabPanels[3]:
Table(
"Steps", 8, 0, -12, 12,
true)
tabPanels[3]:
Knob{
"Rate", 0.25, 0.0625, 1}
-- Tab switching: deselect all others, show only the active panel
for i = 1, #tabButtons do
for j = 1, #tabButtons do
tabButtons[j]:setValue(j == i, false) -- deselect others without triggering callback
tabPanels[j].visible = (j == i) -- show only matching panel
end
end
end
tabButtons[1]:changed() -- show Main tab by default
setSize(500, contentY + contentH + 5)
Horizontal or vertical slider widget.
Definition ui.cpp:1356
@ Pan
display -1;1 pan value type
Definition ui.cpp:635
@ SemiTones
display semitones symbol
Definition ui.cpp:643
@ LinearGain
display dB symbol but with normalized gain
Definition ui.cpp:627
⬇ Download PanelSwitcher.lua
SampleDropper
Load Samples via Drag and Drop
Creates a drag-and-drop zone that loads dropped audio files into the first oscillator of the current layer. Demonstrates DnDArea and async loadSample with visual feedback.
Demonstrates: DnDArea, loadSample, Program.layers, Label, FileFormat
local status =
Label{
"status"}
status.
text =
"Drop a sample here"
status.align = "centred"
status.textColour = "white"
dnd.bounds = {5, 30, 350, 60}
dnd.backgroundColour = "3fFFFFFF"
dnd.fileDropped = function(self)
status.text = "Loading..."
if task.success then
status.text = osc.sampleInfo.name
else
status.text = "Load failed"
end
end)
end
DnDArea widget.
Definition ui.cpp:946
function loadSample(oscillator, path, callback)
load a sample inside the oscillator
Definition api.lua:56
⬇ Download SampleDropper.lua
See Also