Lua Editor and Debugger
A preset can carry a Lua script, a small program that runs on the controller together with the preset. The script can format values, react to MIDI messages, read patch dumps, draw its own controls, sequence notes and much more. This page describes the tools the editor gives you for it: the Lua tab to write the script and watch its output, and the Debugger to stop the script on the controller and look inside while it runs.
What the script can do is described in the Preset Lua Extension reference. If you are new to Lua, the MIDI and Lua course starts from the beginning.
The Lua tab
The Lua tab is available in Basic and Expert mode.

At the top, two tabs choose the script:
- Main is the preset's script,
main.lua. It is loaded with the preset. - Router is the preset's router script,
router.lua, which works on the MIDI messages passing through the router.
The editor is the same one used by Visual Studio Code:
- syntax colouring, and a minimap of the whole script on the right,
- F1, or right-click and Command Palette, lists every command with its shortcut: find and replace, go to line, comment lines, fold blocks, add cursors and so on,
- completions for the Electra One Lua API, see below.
The script is part of the project. It is saved with the project, Undo reverts changes to it, and Send to Electra sends it to the controller together with the preset. There is no separate upload: edit, then send.
Drag the bar between the script and the log to share the height.
Completions
While you type, the editor offers the functions of the Electra One Lua API, with their parameters and a short description:

- After a library name and a dot, such as
controller.ormidi., it offers the functions of that library. - After an object and a colon, such as
control:, it offers the object's methods. - Choosing a function inserts it with its parameters, ready to fill in; Tab moves to the next parameter.
- After
function patch.orfunction midi., it offers the callbacks the controller calls, and inserts the whole function definition. - Press Ctrl Space to show or hide the description of the selected function. Pointing at a function name in the script shows its description too.
The completions are generated from the firmware's function tables and the developer documentation when the editor is built. The Router script gets the completions of the router API.
⌘ S (save) and ⌘ ⇧ S (Send to Electra) work while you type in the script. ⌘ Z there undoes your typing.
The + buttons
In the settings of a control, the Formatter and Function fields have a + next to them (Expert). Type a function name and click +: an empty function of that name, with the right parameters, is added to the end of the Main script. Event actions of the type Lua function have the same button.
The log
Under the script, the log shows the messages the controller reports: what it loads, errors in the script, and everything the script prints with print().
- Log folds the log away to a single line with the latest message, and opens it again.
- Filter messages... shows only the lines that contain the text.
- The level selector sets how much the controller reports: Disabled, Critical, Warning, Info or Trace.
print()output is shown at every level except Disabled. - Auto-scroll keeps the newest line in view.
- CLEAR empties the log.
The browser remembers the level for the next time.
The log keeps the last 500 lines, each with the time it arrived. Errors in the script are shown here, with the line they happened on: when a function doesn't do what you expect, look at the log first.
The debugger
Expert. The debugger stops the preset's script on the controller, at a line you choose, while the preset is running and the instrument is playing. At that moment you can see which functions were called to get there, the value of every variable, and the state of the controller's threads, and you can evaluate Lua expressions in the stopped function. Then you step through the script line by line, or let it run on.
This is what developers expect from their tools on a computer. On a MIDI controller, it is unique: the script is not simulated in the browser, it is the real script, on the real hardware, with the real MIDI data.

A debugging session
This walks through the picture above. The DX7 Voice Editor shows the keyboard scaling break point as a note name, with a formatter:
function noteName(midiNote)
local octave = math.floor(midiNote / 12) - 2
local name = NOTE_NAMES[midiNote % 12 + 1]
return name .. octave
end
function formatBreakPoint(valueObject, value)
local note = value + 21
local text = noteName(note)
return text
endOpen split view with the Lua tab on one side and the Debugger on the other. Send the preset to the controller, so it runs the same script you see.
Set a breakpoint. In the Lua tab, click the line number of line 24,
return name .. octave. A red dot marks the breakpoint.Attach. Click Attach, the first icon of the debugger's toolbar. The status says "attached to DX7 Voice Editor, running". On the controller, a thin amber line under the status bar and a bug icon in the bottom bar show that the debugger is attached.

Electra One Mk2 with the debugger attached: the amber line under the status bar and the bug icon in the bottom left corner Make the line run. Turn the Break Point knob. The controller calls the formatter to show the new value, and the script stops.
Look. The Lua tab highlights line 24. The status says "stopped at 24 on Application Thread", and the amber line on the controller turns red.
- CALL STACK shows
noteNameat main.lua:24, called by the function at line 28,formatBreakPoint, at main.lua:30. - LOCALS shows
midiNote= 64,octave= 3 andname= "E". - Global lists the script's own globals; click
NOTE_NAMESto see the twelve names. - EVALUATE: type
NOTE_NAMES[midiNote % 12 + 1] .. octaveand press Enter. The answer is "E3", what the controller will show.
- CALL STACK shows
Step. Step out runs to the end of
noteNameand stops informatBreakPointat line 31, wheretextnow holds "E3".Continue, and Detach when you're done.

Breakpoints
- Click a line number in the Main script to set or remove a breakpoint. The Router script cannot be debugged.
- A breakpoint also stops at the same line of any module the script loads with
require. - A preset can have up to 32 breakpoints. They are sent to the controller as you set them, and again when you attach or send the preset. Opening another preset in the editor removes them.
- A breakpoint is a line number. If you add or remove lines above it, move the breakpoint too, and send the preset so the controller runs the same lines.
- Clear N breakpoints in the debugger removes them all, in the editor and on the controller.
- A breakpoint on a line that never runs, such as the
endof a function that returns earlier, never stops.
The toolbar
| Button | What it does |
|---|---|
| Attach / Detach | Starts and ends the debugging session. While attached, breakpoints stop the script. |
| Pause at the next line | Stops the script at the next line it runs, wherever that is. A script that is only waiting for something to happen stops when it next runs. |
| Step over | Runs the current line, including any functions it calls, and stops at the next line. |
| Step in | Runs to the next line, stepping into a function the current line calls. |
| Step out | Runs to the end of the current function and stops in the function that called it. In a function the controller called directly, such as a formatter, it continues. |
| Continue | Lets the script run on until the next breakpoint. |
| Reload preset | Reloads the preset on the controller. The debugger stays attached to the new script. |
| Local, Upvalue, Global | Show the local variables or the upvalues of the selected frame, or the script's own globals. Available while stopped. |
| Stack | Reads the call stack again. |
| Threads | Shows the controller's threads, while attached. |
| Map | Shows the parameter map: every value the preset's controls hold, by device. While the script runs, it also writes the map into the log. |
| Clear N breakpoints | Removes all breakpoints. |
| Reboot | Restarts the controller, after asking. |
The status next to the buttons says what the debugger is doing: detached, attached and running, stopped at a line on a thread, or waiting for a preset with a script.
The inspector
While the debugger is attached, the inspector next to the log shows the following; the frames, variables and evaluation need a stopped script:

- CALL STACK: the functions that led to the stopped line, the innermost first. Functions the controller calls directly, such as formatters and callbacks, are shown as "function at line N". Click a frame to see its variables; the evaluation then runs in that frame too.
- LOCALS: the local variables of the selected frame, with value and type.
- UPVALUES: variables of enclosing functions the frame uses.
- GLOBALS: the script's own global variables and functions. The Lua and Electra One built-ins are left out; the heading says how many.
- A variable that holds a table has a ›. Click it to show the table's fields; click a field that is a table to go deeper.
- THREADS: every thread of the firmware with its state, priority and how much of its stack it has used. STOPPED marks the thread the script is stopped on, CMDS the thread that carries the debugger's commands.
- EVALUATE: type a Lua expression and press Enter. It runs in the selected frame, with its local variables, and shows the result. It can also run a statement. Assigning to a local variable here does not change the script's variable; assigning to any other name creates a global.
What stops, and what keeps running
The controller runs the script from several threads, and a breakpoint stops only the thread that reached it:
| Thread | Runs | While it is stopped |
|---|---|---|
| Application Thread | timers, formatters, value functions, knob and touch handlers, and all midi.on… callbacks, including midi.onMessage and midi.onSysex | the screen and the knobs don't respond; MIDI keeps flowing through the router |
| Lcd Thread | functions that draw custom controls | the screen freezes; the rest keeps working |
| MIDI Thread | patch.onResponse | cannot be stopped |
The MIDI thread also carries the debugger's commands, so stopping it would stop the debugger itself. A breakpoint there is reported in a red box, "cannot stop the MIDI Thread", and the script runs on. To debug patch.onResponse, move the work you want to inspect into a function that runs on the application thread, or use print().
While a script is stopped, the controller skips the work that would need it, such as timer ticks and redraws, instead of queueing it. When the stop was on another thread than the application thread, such as the Lcd Thread, the debugger says after continuing how many calls were skipped. Other presets' scripts are not stopped, but their timers and callbacks wait while the application thread is stopped.
A step that runs past the end of a callback stays armed, and stops at the next line the script runs, for example at the next timer tick.
When the preset is reloaded, sent again, or another preset is opened on the controller, the debugger follows the preset on screen and sends the breakpoints again; they then stop at those line numbers of that preset's script. The script's first lines and its onLoad, onReady and onEnter functions have already run by then and don't stop.
The Lua command line
Under the debugger, the command line runs Lua in the preset's script while it is running: type a command and press Enter; Shift Enter adds a line. Its output appears in the log when the command prints something:
print(controls.get(19):getValue("value"):getMessage():getValue())prints the MIDI value of the Break Point, 39.
clear empties the log. While the script is stopped, use EVALUATE instead.