Skip to content

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:

lua
for i = 1, 10 do
    print("message #" .. i)
end

Ten 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:

lua
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))
end

string.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:

lua
for note = 36, 96, 12 do
    print("C in octave " .. (note // 12 - 2))
end

for countdown = 3, 1, -1 do
    print(countdown)
end

Walking 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:

lua
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)
end

pairs walks a table whose entries have names rather than positions. The order it chooses is its own, so do not rely on it:

lua
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)
end

The 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:

lua
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:

lua
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
end

Lua 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:

lua
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
end

The _ 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:

lua
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.

Electra One proudly uses Lua and ArduinoJson.
For support contact info@electra.one · © 2019-2026 Electra One