Common Idioms
Every language has a few turns of phrase that its speakers use without thinking. They are not grammar; a beginner can write perfectly correct Lua without any of them. But they are what makes a script read as if it was written by someone at ease, and once you know them you will see them in every tutorial and every preset in the library. Here are the ones worth adopting first.
Local by default
Write local in front of every variable and every helper function unless you have a reason not to. The reasons not to are the ones the chapter on functions listed: a formatter, a value function, a callback such as patch.onResponse, anything the controller looks up by name. Everything else is yours, and local keeps it that way.
local CELL_W = 27 -- a setting of this script
local function boxLeft(column)
return 10 + column * CELL_W
end
function formatPercent(valueObject, value) -- the controller looks this up
return string.format("%d %%", value)
endA fallback with or
Because nil and false are the only false values, a or b gives you a when it exists and b when it does not. It is Lua's way of saying this, or else that, and it saves an if every time a value might be missing:
local name = "TUTORIAL"
for i = 1, 10 do
local character = string.byte(name, i) or 32
print(character)
endstring.byte gives nil past the end of an eight character name, and or 32 fills the remaining two places with a space, because a DX7 name is always ten characters long. The Editing a Patch Name tutorial sends exactly these bytes. The same idiom gives a setting a default: local tempo = tempo or 120.
Guard clauses
Deal with the case you are not interested in first, leave, and let the rest of the function assume the normal case. The function stays flat and reads from top to bottom:
function midi.onControlChange(midiInput, channel, controllerNumber, value)
if midiInput.playback then
return
end
midi.sendControlChange(PORT_2, channel, controllerNumber, value)
endThis forwards control changes from one port to another, but not the ones a capture is playing back, which have already gone out once. Without the guard, the forwarding line would sit inside an if not midiInput.playback then block, and a function with three such conditions would be nested three deep. The pot callback of a custom control opens the same way: if event.delta == 0 then return end, because a finger landing on the knob is not a turn.
Choosing between two values
Lua has no one-line if for picking one of two values, but and and or together do the job, and you will meet the shape everywhere:
local enabled = timer.isEnabled()
local label = enabled and "ON" or "OFF"
print(label)Read it as if enabled then "ON" else "OFF". It works because enabled and "ON" is "ON" when enabled is true and false otherwise, and or then picks the second option. There is one trap: if the first choice is itself false or nil, the or falls through to the second one regardless. Use the idiom when the first value is a string or a number, and a plain if when it is not.
Build it once, outside the function
A table that never changes belongs at the top of the script, not inside the function that uses it. A formatter runs on every step of a knob sweep, and there is no reason to rebuild the twelve note names each time:
local NOTE_NAMES = { "C", "C#", "D", "D#", "E", "F",
"F#", "G", "G#", "A", "A#", "B" }
function formatNote(valueObject, value)
return NOTE_NAMES[(value % 12) + 1] .. (value // 12 - 2)
endThe capitals say this does not change, and the position says this is built once. The same goes for the DX7 algorithm table, the list of CC names, and any other data your script carries.
Keep callbacks short
The controller calls your functions in between painting the screen and handling MIDI, and while your function runs, nothing else does. A formatter should format, a MIDI callback should note what arrived and return, and a timer tick should do a little work rather than a lot. When something has to wait, hand it to schedule.after instead of waiting in place:
function preset.onReady()
schedule.after(200, patch.requestAll)
endThat line asks the instrument for its patches a fifth of a second after the preset is ready, without holding anything up in the meantime. It is the pattern from the Lua reference, and a good one to copy.
Print to find out
The last idiom is not Lua at all but a working habit. When a function does not do what you expect, do not reason about it; put a print in it, send the preset, and read the log. When several values belong on one line, use logger.write, which works like string.format:
logger.write("note %d velocity %d on port %d", 60, 100, 0)When a print is not enough, the editor's debugger stops the script on the controller at a line of your choosing and shows every variable, which the Debugging a Preset's Lua tutorial walks through. The log, though, is where to look first, and it is where you will find the answer nine times out of ten.
What comes next
This is the end of the language part of the book. You know how Lua is written and how the controller calls into it. The third part turns to the tutorials, where everything from both halves of the book is put to work on real presets, and explains what each of them will teach you.