Skip to content

Objects ​

When the script asks controls.get(3) for the third control, what comes back is not a number and not a plain table. It is an object: the control itself, as far as Lua is concerned, with its own name, colour, position and value, and with functions that act on exactly that control. You have been using objects since the chapter on conditions. This chapter explains what you were doing.

The objects the controller gives you ​

Put a fader on the page, so that the preset has a control with reference #1, and send this script:

lua
local control = controls.get(1)

print(control:getName())

control:setName("Cutoff")
control:setColor(ORANGE)

local value = control:getValue()
print(value:getValue())

The log shows the control's old name and its current value, and on the controller the fader is now called Cutoff and drawn in orange. Three objects were involved: the control, which came from controls.get; its value, which came from control:getValue(); and the module controls, which is not an object at all but a table of functions, as the previous chapter explained.

The colon is the thing to notice. control:setName("Cutoff") means call setName on this control. Lua passes the control itself along, silently, as a first argument that the function sees under the name self. With a dot you would have to write control.setName(control, "Cutoff"), which is the same call with the control named twice. The colon is the short form, and it is the form to use whenever you call a function that belongs to a particular thing.

If you mix them up, the message in the log is usually about self being nil, or about a number being handed where a control was expected. That is the sign to check your dots and colons.

Devices, values, pages, groups and the sysexBlock of a patch response all work this way. device:getChannel(), value:setValue(64), sysexBlock:peek(152): an object, a colon, and a function that knows which object it was called on.

Making an object of your own ​

Lua has no classes, and it does not need them. An object is a table that holds data together with the functions that work on it, and the colon does the rest. Let's make a small one that stands for a MIDI note:

lua
local NOTE_NAMES = { "C", "C#", "D", "D#", "E", "F",
                     "F#", "G", "G#", "A", "A#", "B" }

Note = {}
Note.__index = Note

function Note.new(number, velocity)
    local note = { number = number, velocity = velocity }
    setmetatable(note, Note)
    return note
end

function Note:name()
    return NOTE_NAMES[(self.number % 12) + 1] .. (self.number // 12 - 2)
end

function Note:play(port, channel)
    midi.sendNoteOn(port, channel, self.number, self.velocity)
end

local middleC = Note.new(60, 100)
local fifth = Note.new(67, 80)

print(middleC:name())
print(fifth:name())

middleC:play(PORT_1, 1)

The log reads C3 and G3, and a synthesizer on Port 1 plays a middle C that nobody turns off, which you can fix with what you learned about schedule.after.

Read the pieces one at a time. Note is a table that will hold the functions every note shares. Note.new builds a fresh table with the data of one note, and setmetatable tells Lua that when something is missing from this table it should look in Note, which is what the __index line arranged. So middleC:name() finds no name in middleC itself, looks in Note, finds it there, and calls it with self set to middleC.

That is the whole mechanism. Two notes, each with its own number and velocity, sharing one set of functions. The Note:name() definition with a colon is the mirror image of the call: it declares that the function expects self, without you having to write the parameter.

When to bother ​

For a formatter or a short callback, an object is more than you need; a function and a table do fine. Objects pay off when a preset keeps track of several things of the same kind that each carry their own state: the steps of a sequencer, the voices of a layer, the LFOs of a modulation page. The Building an Arpeggiator in One Prompt tutorial shows a preset of that size, and when you read its script you will now recognise what you see.

What comes next ​

You have the whole language now: values, decisions, loops, tables, functions, modules and objects. The last chapter in this part is not about new features but about habits, the handful of small patterns that experienced Lua programmers reach for without thinking, and that make a script easy to read a year later.

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