Loops
A DX7 voice name is ten characters, and each one is sent to the synthesizer as its own parameter change. Written out by hand, that is ten nearly identical lines, and the eleventh time you need a similar thing you will wish you had not. A loop writes the line once and runs it as many times as needed.
Counting
The most useful loop in Lua is the numeric for. It names a variable, gives it a starting value and an ending value, and runs the lines between do and end once for each step:
for i = 1, 10 do
print("message #" .. i)
endTen lines appear in the log, message #1 to message #10. The variable i exists only inside the loop, and it takes the next value each time around.
Let's make it do real work. A DX7 writes its voice name as parameters 145 to 154, one character each. A loop can build the ten messages from the name, and for now we print them instead of sending them:
local name = "Synthbrass"
for i = 1, 10 do
local character = string.byte(name, i)
print(string.format("F0 43 10 01 %02X %02X F7", 0x10 + i, character))
endstring.byte gives the code of the character at position i, and the parameter number is split across two bytes exactly as the Editing a Patch Name tutorial explains. Ten well formed SysEx messages, from four lines.
The step does not have to be one. A third number after the range sets it, and a negative step counts down:
for note = 36, 96, 12 do
print("C in octave " .. (note // 12 - 2))
end
for countdown = 3, 1, -1 do
print(countdown)
endWalking through a table
Very often the thing you want to repeat over is a table, Lua's collection type, which has a chapter of its own. Two loops are built for exactly that. ipairs walks a list in order, handing you the position and the value:
local NOTE_NAMES = { "C", "C#", "D", "D#", "E", "F",
"F#", "G", "G#", "A", "A#", "B" }
for position, name in ipairs(NOTE_NAMES) do
print(position .. " = " .. name)
endpairs walks a table whose entries have names rather than positions. The order it chooses is its own, so do not rely on it:
local CC_NAMES = { [1] = "Mod Wheel", [7] = "Volume",
[10] = "Pan", [74] = "Cutoff" }
for number, name in pairs(CC_NAMES) do
print("CC " .. number .. " is " .. name)
endThe difference matters. ipairs stops at the first gap, so for the table above, whose keys jump from 1 to 7, it would print one line and stop. When the table is a list, use ipairs; when it is a lookup, use pairs.
Repeating while something holds
The while loop does not count. It checks a condition, runs its lines if the condition is true, and checks again, until the condition turns false:
local value = 1000
local bytes = 0
while value > 0 do
value = value // 128
bytes = bytes + 1
end
print("1000 needs " .. bytes .. " MIDI data bytes")The answer is two, which the More Bits & Bytes chapter told us already; here the script worked it out. Something inside the loop must move the condition towards false, otherwise the loop never ends. Lua also has repeat ... until, which tests at the end and therefore runs its body at least once. I use it rarely and you can safely postpone it.
A loop that does not end
On a computer, an endless loop is an annoyance. On the controller it is the screen no longer following the knobs, because the script holds the preset while it runs. A timer.onTick or scheduled function that runs for ten seconds is stopped by the firmware; code at the top of the script is not. Holding the six main buttons for two seconds stops every timer and schedule. Better still, make sure every while has a way out.
Leaving early
break ends a loop on the spot. The usual reason is that you found what you were looking for:
local NOTE_NAMES = { "C", "C#", "D", "D#", "E", "F",
"F#", "G", "G#", "A", "A#", "B" }
for position, name in ipairs(NOTE_NAMES) do
if name == "F#" then
print("F# is number " .. position)
break
end
endLua has no continue to skip one round. Wrap the body in an if instead; it reads just as well.
Loops inside loops
A loop can contain another. The script in the Lua reference that shows one group of controls at a time uses exactly this: an outer loop over the groups, an inner loop over the controls in each group. Printed rather than hiding anything, it looks like this:
local controlGroups = {
{ 20, 21, 22 },
{ 26, 27, 28 },
{ 32, 33 }
}
for groupNumber, group in ipairs(controlGroups) do
for _, controlId in ipairs(group) do
print("group " .. groupNumber .. " has control " .. controlId)
end
endThe _ is a name by convention: it tells the reader that the position is not used, only the value.
A loop that plays
Finally, a loop that sends MIDI. This plays a C major chord on Port 1, channel 1, and lets it go after half a second. If a synthesizer is listening there, you will hear it:
local CHORD = { 60, 64, 67 }
for _, note in ipairs(CHORD) do
midi.sendNoteOn(PORT_1, 1, note, 100)
end
schedule.after(500, function()
for _, note in ipairs(CHORD) do
midi.sendNoteOff(PORT_1, 1, note, 0)
end
end)The second half asks the controller to run a function later rather than waiting in place. Waiting inside a script would hold the whole preset, as the warning above explains; schedule.after hands the work back and the script returns at once. It is the one habit of a well behaved Electra One script that you should pick up early.
What comes next
Every loop above walked through a table, and we have been using tables without properly introducing them. The next chapter puts that right.