Skip to content

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.

The layout and the Lua tab in split view
Split view: the Transpose control on the left uses the formatter formatSemitones, written in the Lua tab on the right. The log under the script shows what the controller reports

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:

Completions
Typing controller. offers the functions of the controller library, with the documentation of getSerial
  • After a library name and a dot, such as controller. or midi., 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. or function 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 stopped script
The script stopped at line 24 in noteName, called by the Break Point's formatter. The Lua tab highlights the line; the debugger shows the call stack, the local variables, the preset's globals, the NOTE_NAMES table, the threads and an evaluated expression

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:

lua
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
end
  1. Open 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.

  2. Set a breakpoint. In the Lua tab, click the line number of line 24, return name .. octave. A red dot marks the breakpoint.

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

    The controller while 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
  4. Make the line run. Turn the Break Point knob. The controller calls the formatter to show the new value, and the script stops.

  5. 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 noteName at main.lua:24, called by the function at line 28, formatBreakPoint, at main.lua:30.
    • LOCALS shows midiNote = 64, octave = 3 and name = "E".
    • Global lists the script's own globals; click NOTE_NAMES to see the twelve names.
    • EVALUATE: type NOTE_NAMES[midiNote % 12 + 1] .. octave and press Enter. The answer is "E3", what the controller will show.
  6. Step. Step out runs to the end of noteName and stops in formatBreakPoint at line 31, where text now holds "E3".

  7. Continue, and Detach when you're done.

After stepping out
After Step out, the script is stopped at line 31 of formatBreakPoint, with value, note and text

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 end of a function that returns earlier, never stops.

The toolbar

ButtonWhat it does
Attach / DetachStarts and ends the debugging session. While attached, breakpoints stop the script.
Pause at the next lineStops 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 overRuns the current line, including any functions it calls, and stops at the next line.
Step inRuns to the next line, stepping into a function the current line calls.
Step outRuns 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.
ContinueLets the script run on until the next breakpoint.
Reload presetReloads the preset on the controller. The debugger stays attached to the new script.
Local, Upvalue, GlobalShow the local variables or the upvalues of the selected frame, or the script's own globals. Available while stopped.
StackReads the call stack again.
ThreadsShows the controller's threads, while attached.
MapShows 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 breakpointsRemoves all breakpoints.
RebootRestarts 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:

The inspector
The debugger with the log, the inspector and the toolbar
  • 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:

ThreadRunsWhile it is stopped
Application Threadtimers, formatters, value functions, knob and touch handlers, and all midi.on… callbacks, including midi.onMessage and midi.onSysexthe screen and the knobs don't respond; MIDI keeps flowing through the router
Lcd Threadfunctions that draw custom controlsthe screen freezes; the rest keeps working
MIDI Threadpatch.onResponsecannot 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:

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

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