Building a Preset with an AI Agent
What you will learn
- How to set up a folder on your computer that is a preset slot on the controller, and how the Electra One app keeps the two together.
- How to work in VS Code with Claude Code, so you describe a preset in plain language and the agent writes it.
- How to let the agent check its own work - the controller's screen, its log, and the MIDI it sends - instead of you reporting back every time.
- How to keep what works, with tests the controller runs and with git.
Introduction
Building a preset by hand means knowing two things: what your instrument expects on the wire, and how the Electra One says it. Plenty of people enjoy that. If you are not one of them, there is another way: describe what you want in ordinary words and let an AI coding agent write the files for you.
This works better here than it does with most hardware, for a simple reason. A preset is not a binary - it is a few text files and a Lua script, sitting in a folder. An agent can write them, put them on the controller, take a picture of the screen, read the controller's log, and fix what it got wrong, all without asking you to click anything. You are there to say what you want and to judge the result.
You need the desktop app
Everything in this tutorial happens in the Electra One app
- the offline desktop version of the editor. The browser editor cannot see folders on your computer, which is what this whole setup is built on.
Nothing here requires you to write code. You will read some, because seeing what the agent wrote is the only way to know whether you agree with it, and the tutorial explains what you are looking at as it goes.
What you need
- An Electra One controller, connected by USB.
- The Electra One app, installed and started.
- VS Code, a free text editor.
- Claude Code, the coding agent, with an Anthropic account. Codex and other agents work the same way; the folder does not care which one edits it.
- Twenty minutes, and an idea of a preset you want.
Step 1: Give the preset a folder
Make an empty folder somewhere you will find it again - Documents/presets/my-first-preset is fine. This folder is about to become one slot of your controller: every file in it is a file of that slot.
In the Electra One app, open the preset you want to work on, then go to CONTROLLER and open the Current slot page. Leave that page open for the rest of this tutorial. It is the page that does the work; with it closed, the app will not hear the agent asking for anything.
Now open Settings and find Watched folder:
- Click Choose folder and pick the folder you just made.
- Tick Watch a folder for this slot.
- Tick Write CLAUDE.md into the folder.
That third one writes a short file into the folder explaining the folder's own rules to whatever agent you open in it - which files mean something, how to ask for a change to be sent to the controller, and where to read what happened. You do not have to explain any of that yourself, and you never have to read it either.
Which files matter
preset.json is the preset, main.lua is its script, router.lua is the router's, and there are a few more JSON files for particular jobs. Anything else in the folder - notes, a README, a subfolder - is simply left alone. It is never sent to the controller.
Step 2: Open the folder in VS Code
Start VS Code and choose File → Open Folder, then pick the same folder. (Dragging the folder onto the VS Code icon does the same thing.)
The Explorer on the left now shows what is in it: CLAUDE.md, and a .electra folder that the app uses to talk back. Both appear the moment the watch is on. If the slot already had files, they arrive here too.
Two things in VS Code are worth knowing before you start:
- The terminal. View → Terminal, or
Ctrl+`(Cmd+`on a Mac), opens a command line inside the folder. That is where the agent runs. - Source Control, the branch icon in the left bar. Click Initialize Repository and VS Code starts tracking every change to the folder. You do not need to understand git to benefit from this: it means you can always see exactly what the agent changed, and undo it with one click.
Do initialise the repository. It costs one click and it is the difference between "something broke" and "this line broke it".
Step 3: Start the agent
In the VS Code terminal, type:
claudeClaude Code starts in the folder and reads CLAUDE.md on its own. From now on you talk to it in that panel, in plain sentences.
Two extensions worth having
Claude Code for VS Code shows every edit as a side-by-side diff - the old file on the left, the proposed one on the right - which is a friendlier way to follow what is happening than watching text scroll past. Search for Claude Code in the Extensions view.
Electra One for VS Code makes sending to the controller VS Code's own build task: Cmd+Shift+B (Ctrl+Shift+B on Windows) writes every changed file to the slot and reloads the preset, runs the tests, prints the controller's log in the terminal panel, and puts a file the app refused - or JSON that does not parse, or a test that failed - in the Problems panel on the line that broke it. It also adds Electra: Run Tests and Electra: Photograph Screen to the command palette. It is not on the marketplace yet; it ships with the app's source as tools/vscode-electra, and its README says how to run it.
One thing to know before asking for anything. Models know a lot about MIDI in general and very little about this controller in particular, and the difference shows up as a script that looks right and does nothing. The app has the answer built in: a set of reference files, written for agents and generated from the firmware itself, that it hands over when asked. The CLAUDE.md in the folder tells the agent to ask for them before writing anything, so usually you need do nothing. If you want to be sure, say it yourself once, at the start:
Ask the app for its reference files first, and treat them as the authority on how this controller works.
The files land in .electra/reference/ - out of the way, and never committed. You will not need to read them; the agent will.
Step 4: Ask for the preset
Now describe what you want. The more concrete you are, the less there is to guess:
Build me a preset for the Electra One Mini. Page 1: eight dials sending CC 20 to 27 on MIDI channel 1, named after the eight parameters of my drum machine - Tune, Decay, Snap, Level, Tone, Pitch, Noise, Drive. Then push it to the controller and take a screenshot so I can see it.
The agent writes preset.json and any Lua the preset needs. In VS Code you can watch the files appear in the Explorer, and Source Control shows each change.
Nothing has reached the controller yet, and that is deliberate: editing the folder is safe, sending is a separate act. There are two ways to send:
- The agent asks. It writes a small file,
.electra/request.json, saying push. The app sees it, writes every changed file to the slot, and reloads the preset. This is what the last sentence of the prompt above asked for. - You press the button. On the Current slot page, the app shows what has changed in the folder and waits for Apply changes.
Use whichever suits your mood. Early on, pressing the button yourself is a good way to see what is going on; later, letting the agent do it is the whole point.
If nothing happens
A request is answered by the Current slot page. If that page is not open, or the controller is not connected, the agent waits and then reports that nothing answered. Open the page and ask again.
Step 5: Look at what happened
The agent can now see the result on its own. Two things come back into the .electra folder:
screen.png- a photograph of the controller's display, taken on request.log.txt- what the controller printed while it loaded and ran the preset. Lua errors appear here, and nowhere else.
So instead of you describing the problem, you can ask:
The third dial's name is cut off. Take a screenshot and tell me what you see.
and the agent looks. This is the part that feels different from other AI coding: it is not writing into the dark and hoping.
A quiet log is not a clean log
The controller prints something every time it loads a preset, so a completely empty log.txt means the log did not arrive - not that everything is fine. If something looks wrong on the screen but the log is silent, trust the screen.
Look at the controller too. That is the one judgement the agent cannot make for you: whether this is the preset you wanted.
Step 6: Change things
From here it is a conversation. Ask for one change at a time and look after each:
Make the four bottom dials a different colour, and give the page the name "Drums".
The Decay knob should show milliseconds rather than a number from 0 to 127.
That second one produces Lua - a short function that turns the value into text before it is drawn. You do not need to write it, but do open the file and read it. It is usually four lines, and it is your preset now.
When something is wrong, say what you saw rather than what you think the cause is:
Turning the second dial does nothing on the synth.
The agent can check what went out on the wire, what the controller thinks the control is, and what the script did, and it will get to the cause faster than a guess would.
Step 7: Lock in what works, with tests
Once a preset does something you care about, there is a way to keep it doing it. A preset can carry a test file, main-tests.lua, beside its script. Each test is a few lines saying when this knob is turned, this message should go out, and the controller itself runs them and reports which passed.
Ask for them:
Write tests covering the eight dials - each one should send its own CC on channel 1 - and run them.
The agent writes main-tests.lua into the folder and asks the app to run it; from then on every push runs the tests on its own, and the result comes back to the agent with the rest of the answer. In the app you can run them yourself too: in the Lua pane, the Preset tests tab has a Run button, and the results appear case by case.
Then comes the part only you can do. Try the preset on the instrument - turn the knobs, listen - and when a behaviour is right, say so:
Decay really is right now. Mark that case approved.
An approved case carries the date you confirmed it, and it means something stronger than "it passes": it means a human heard it and accepted it. From then on, if a later change turns that case red, the app reports the push as an error - the files were sent, but the change is wrong until you say otherwise - and the agent is told to stop and ask rather than quietly fix the test. The agent never approves a case by itself - passing its own test proves only that the script does what the test says, not that the test says the right thing.
This is what makes a long session safe. Three prompts later, when you ask for something new, the agent runs the tests and finds out immediately if it broke something you had already settled.
Step 8: Commit what you have
In VS Code's Source Control panel, type a short line saying what changed - eight drum dials with a decay formatter - and click the tick to commit.
Do it whenever something works. A commit is a point you can return to, and "undo everything since the version that worked" becomes a click instead of an afternoon. It also gives the agent something to read: what did we change since the last commit? is a question it can answer exactly.
How to talk to the agent
A few habits that make the difference between a good session and a frustrating one.
Say where. Which page, which knob, which MIDI channel, which instrument. Add some controls invites a guess; add four dials on page 2, CC 30 to 33, channel 3 does not.
One change at a time. A request that contains five changes produces one result you cannot take apart. Five small requests produce five you can.
Ask it to verify. Ending a request with push it and take a screenshot, or push it and read the device log, turns a hopeful change into a checked one. It costs one sentence.
Describe symptoms, not diagnoses. The display flickers when I turn knob 3 is more useful than I think the repaint is wrong.
Give it the instrument's manual. If you are building a preset for a synthesizer that speaks SysEx, the MIDI implementation chart from its manual is the single most valuable thing you can hand over. Drop the PDF in the folder and say it is there.
Say when to stop. If you are not sure which page I mean, ask me is worth saying once at the start.
Without a terminal: the AI panel in the app
If the terminal is not for you, the desktop app has an assistant of its own. In the Tools pane, the AI tab talks to the project you have open: it can read it, add and change controls, write Lua, upload to the controller and read its log. It runs on your own Anthropic API key, which you save once on the Settings page.
It is the same conversation in a smaller room, and it reads the same reference files - a section at a time, as it needs them. It writes and runs the tests too, and it asks you before it marks one approved. What it cannot do is take a picture of the controller's screen; that is the terminal's, and it is one reason the folder-and-VS-Code setup is worth the extra ten minutes.
When something goes wrong
| What you see | What it usually is |
|---|---|
| The agent says nothing answered its request | The Current slot page is not open, or the controller is not connected |
| The app refuses to apply anything | preset.json does not parse - ask the agent to fix it, nothing was sent |
| The preset loads but a control is blank | A Lua error. Ask the agent to read .electra/log.txt |
| Everything looks right and the synth does not respond | Wrong channel or wrong port. Ask the agent to check the SysEx log |
| A file you removed from the folder is still on the controller | Sending is additive - the folder never deletes files from the device |
Where to go next
You now have a working loop: describe, let it build, look, correct, keep. The same loop builds far more than a page of dials - a full instrument editor that asks the synthesizer what it is playing, a sequencer, a custom control that draws itself.
- Vibe coding presets - the same ground in more technical detail: the request protocol, the reference corpus, what the agent can see
- Lua tests - the test files in full
- Debugging a preset's Lua - for when the log is not enough and you want to stop the script on a line