- Rust 67.9%
- TypeScript 15%
- Svelte 13.8%
- Java 1.6%
- Python 1.1%
- Other 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .claude | ||
| captures | ||
| crates | ||
| docs | ||
| openspec | ||
| packaging | ||
| proto | ||
| tools | ||
| .gitignore | ||
| .woodpecker.yaml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| cliff.toml | ||
| LICENSE | ||
| README.md | ||
cortexcontrol
Warning
Use at your own risk. cortexcontrol talks to your Quad Cortex with a reverse-engineered protocol. It can change or lose presets, captures and settings, or leave the device in a bad state. You alone are responsible for your device and your data. The authors take no responsibility for any damage. This project is not affiliated with or endorsed by Neural DSP.
Note
Work in progress. Things are missing, change often and may break.
A Linux port of Neural DSP's Cortex Control, the desktop app for the Quad Cortex. There are two main apps, both built on the same device code:
cortexcontrol, the desktop app (GUI)cortextui, the terminal app (TUI)
Next to them, cortexctl is a command-line tool for scripts and debugging.
Download
Releases are on the Releases page.
Each has one archive for x86_64 Linux,
cortexcontrol-vX.Y.Z-x86_64-linux.tar.gz, with the three apps, the udev
rule, the license and this README. Download it and sha256sum.txt into
one folder, then check and unpack it:
sha256sum -c sha256sum.txt
tar xzf cortexcontrol-vX.Y.Z-x86_64-linux.tar.gz
All apps need the udev rule for device access. Install it from the unpacked directory, then replug the Quad Cortex (docs/linux-setup.md has the details):
sudo install -m 0644 70-quad-cortex.rules /etc/udev/rules.d/
sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=hidraw
The desktop app also needs WebKitGTK 4.1 from your distribution:
sudo pacman -S webkit2gtk-4.1 # Arch
sudo apt install libwebkit2gtk-4.1-0 # Debian/Ubuntu
sudo dnf install webkit2gtk4.1 # Fedora
The binaries are built on Debian 12, so they need glibc 2.36 or newer.
Desktop app
cortexcontrol shows the connected device's preset grid, parameters,
scenes, mode and tempo, and follows changes made on the device.
- Edit the loaded preset. Knobs, switches and lists change their param, and the editor's switch bypasses the block. A param's context menu (right click, the context-menu key or Shift+F10) sets whether it follows scenes.
- Add, change and remove blocks. The "+" of an empty slot, or Enter on it, opens the model browser: models by category, favorites first, with a search field and Guitar/Bass/All and Mono/Stereo/All filters. "Change model" replaces a block. Delete removes the selected block without asking, since Undo brings it back.
- IRs and captures. IR loaders and Neural Captures get pickers that list the device's IR and capture libraries.
- Scenes. The scene panel previews any scene. The device stays on its own scene until you switch it from the preview hint.
- Presets. Undo and Redo (Ctrl+Z, Ctrl+Shift+Z), Save (Ctrl+S) and Save As. Save works in place for a preset in "My Presets"; for any other it opens Save As. Clicking the preset name lists "My Presets"; picking one loads it at once. Like the official app, loading does not ask about unsaved edits; Undo brings them back.
- Traffic panel, like
cortexctl monitor.
Not yet possible: moving blocks, and editing inputs, outputs, splitters, mixers and EQ bands. Nothing can be edited while a scene preview is shown.
Build and run
System packages (WebKitGTK 4.1, GTK 3, libsoup 3, librsvg):
sudo pacman -S webkit2gtk-4.1 gtk3 libsoup3 librsvg # Arch
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libsoup-3.0-dev librsvg2-dev # Debian/Ubuntu
sudo dnf install webkit2gtk4.1-devel gtk3-devel libsoup3-devel librsvg2-devel # Fedora
Plus Rust (see Building) and Node.js 26 or newer with npm.
The app is a Tauri 2 app with a Svelte frontend in
crates/cortex-app/ui/:
cd crates/cortex-app/ui
npm ci
npm run tauri dev # development: Vite dev server + debug build, reloads on changes
npm run tauri build # release binary at target/release/cortexcontrol
Without a device, the app can play a recorded capture (mock mode). Edits
then change only what the app shows; undo, saving, loading and changing
blocks, IRs and captures are disabled. Arguments for the app go after
three -- (one for npm, two for Tauri):
npm run tauri dev -- -- -- --mock "$PWD/../../../captures/quad-cortex-dump-edit-capture-edit.pcapng"
../../../target/release/cortexcontrol --mock ../../../captures/quad-cortex-dump-edit-capture-edit.pcapng
If the window stays blank or flickers, WebKitGTK's GPU path may not work
with your driver; start the app with WEBKIT_DISABLE_DMABUF_RENDERER=1.
Frontend checks, run in crates/cortex-app/ui: npm run check
(svelte-check), npm run lint (ESLint) and npm test (Vitest).
Terminal app
cortextui edits the loaded preset in a terminal, with the keyboard only.
It uses the same device code as the desktop app, but needs no WebKit or
Node.js. It can also run the device's Neural Capture wizard.
cargo build --release -p cortex-tui
./target/release/cortextui
./target/release/cortextui --mock captures/quad-cortex-dump-edit-capture-edit.pcapng
? lists every key, and the bottom line shows the main keys of the part
that has focus. Ctrl-C quits and closes the session, so cortexctl or the
official app can connect straight away.
The screen has the Grid at the top, and below it the editor and the
scenes. It needs at least 101 by 28; block names get longer on wider
terminals. Each block shows a power icon: ● on, ○ bypassed in the
shown scene. Under it are its category and markers: s bypass follows
scenes, ~ expression pedal, #N footswitch, sc side-chain. A splitter
shows as ─S─ and a mixer as ─M─, with lines to the paired row as on
the device.
| Keys | Does |
|---|---|
F1 to F4, g1 to g4 |
Grid row 1 to 4 |
F5, ge |
editor |
F6, gs |
scenes |
F7, gp |
load a preset from "My Presets" |
F8, gc |
Neural Capture |
F9, gb |
model browser for the slot |
F12, gd |
pick a device |
u, Ctrl-R |
undo, redo |
:w |
save in place in "My Presets", or save as for any other preset |
:sav |
save as: name, instrument (h/l) and slot; Tab moves between them |
:q, Ctrl-C |
quit |
h j k l, arrows |
move in the Grid; h and l also stop on S and M, whose params the editor shows read-only |
0, $ |
the row's input, the row's output |
| Space (Grid) | bypass the block or not |
| Enter (Grid) | edit the block; on an empty slot, add one |
a, c (Grid) |
add a block to the empty slot, change the block's model |
x, Delete (Grid) |
remove the block; undo brings it back |
j k (editor) |
next, previous row; the first row is the block's power |
l h, L H (editor) |
raise, lower by a step or by 10; a count first repeats it: 5l |
| Enter (editor) | type a value (500ms, 2k, -3.5, off), or pick from a list, an IR or a capture |
s (editor) |
the param or the bypass follows scenes or not |
j k (scenes) |
preview the next, previous scene; the device stays on its own |
| Enter (scenes) | switch the device to the scene |
| Esc (scenes) | back to live |
| Esc | back one level |
Some terminals keep F1, F10 or F11 for themselves; the g keys work
everywhere.
The model browser, the preset list, the IR and capture lists and long option lists are fuzzy pickers: type to narrow the list, Ctrl-J and Ctrl-K (or Ctrl-N and Ctrl-P, or the arrows) move, Enter picks and Esc closes. In the model browser, Tab cycles Guitar, Bass and All, and Shift-Tab Mono, Stereo and All; favorites come first.
Save as asks before it replaces another preset. With --mock, edits
change only what the app shows; undo, saving, loading, changing blocks,
IRs and captures and every Neural Capture action are not available.
Neural Capture
F8 shows Neural Capture in place of the Grid, the editor and the scenes. It follows the device's capture wizard and opens by itself when the wizard opens on the device; when the wizard closes, the app goes back to the Grid. Esc leaves the screen and the wizard stays open on the device. The device must show its Grid view to open the wizard. The device trains the capture itself; no cloud login is needed.
| Keys | Does |
|---|---|
| Enter (closed) | open the wizard on the device |
l, h (connection pages) |
next, previous page; l on page 5 goes to the IO page |
s (connection pages) |
skip to the IO page |
| Tab (IO page) | show the IO rows or the cab for monitoring |
j k (IO page) |
next, previous row |
l h, L H (IO page) |
raise, lower by a step or a level by 3 dB; h on the first row goes back to page 5 |
| Space (IO page) | switch an on/off row or the input type |
| Enter (IO page) | type a level in dB, pick an impedance or a type, or start the capture on the last row; on the cab, type a value or pick an option, the model or an IR |
c (IO page, ready) |
turn the cab for monitoring on or off |
a (ready) |
A/B: play the capture or the reference |
A (ready) |
auto A/B: switch at once and then every 10 s; A or a stops it |
w (ready) |
go into the save form: name, capture type and instrument (h/l); Tab moves, Enter saves, Esc goes back to A/B and keeps the name |
| Enter (error) | restart the wizard on page 1 |
x |
close the wizard; from the start of a capture on it asks first |
| Esc | leave the screen |
The IO page shows levels in dB, impedances, input types and switches as the device does, and the two input meters as bars. Phantom power is read-only while its input's type is instrument. Tab shows the cab the device plays for monitoring instead of the IO rows: on or off, the model (any cab or IR loader), its IRs and its params, edited as in the editor.
While a capture runs, the screen shows the phase, the progress, the time since the start and the meters. When it is ready, the capture and the reference show side by side, with the one that plays marked, and the save form below. So you can listen and name the capture on one page; auto A/B keeps switching while you type. The save form says why a name can't be used as you type it.
Command-line tool
cortexctl reads the device and makes simple edits. Every command takes
--json, and --device <serial|path> selects a device when several are
connected.
cortexctl list # connected devices
cortexctl info # device type and firmware versions
cortexctl preset # the currently loaded preset: models, params, values in dB, ms, Hz, ...
cortexctl presets # the slots of "My Presets"; * marks the loaded one
cortexctl models # the models a block can be, by category; * marks favorites
cortexctl irs # the IR library: key and name
cortexctl captures # the Neural Capture library: key and name; * marks favorites
cortexctl monitor # live device traffic until Ctrl-C
cortexctl decode FILE # decode a USB capture offline
These are read-only: apart from the connection handshake and keepalives
they only send READ requests. models takes --search TEXT,
--instrument guitar|bass and --channel mono|stereo, which filter like
the app's model browser.
These write commands change the device. Rows, columns, params, scenes and
positions count from 0, as in cortexctl preset and cortexctl presets:
cortexctl set-param ROW COLUMN INDEX VALUE # VALUE normalized, 0 to 1
cortexctl bypass ROW COLUMN on|off
cortexctl scene-mode ROW COLUMN INDEX|bypass on|off # follow scenes or not
cortexctl scene N
cortexctl load [--factory] FOLDER POSITION # unsaved changes are lost
cortexctl undo
cortexctl redo
cortexctl save # in place, in "My Presets"
cortexctl save-as NAME [--position N] [--instrument X] [--overwrite]
cortexctl add-block ROW COLUMN MODEL [--capture KEY] # into a full slot it replaces the block
cortexctl remove-block ROW COLUMN
cortexctl set-ir [--second] ROW COLUMN KEY|none
cortexctl set-capture ROW COLUMN KEY
MODEL is an id from models, KEY a key from irs or captures.
--second sets the second IR of a dual loader. Each command checks first
what it needs, refuses what the device can't do, then prints the device's
answers.
Edits stay unsaved until save or save-as; loading another preset drops
them. save works only for a preset in "My Presets"; for any other, use
save-as. A save-as name has at most 20 characters: letters, digits,
space and # % ( ) ~ ' - ^ _ $ ! { } @ &. Without --position it saves
into the loaded preset's slot, or the first empty one, and it replaces
another preset only with --overwrite. --instrument is one of none,
guitar, bass, synth, vocal or other.
Capture mode drives the device's Neural Capture wizard. The device's screen and these commands both move the wizard:
cortexctl capture open # open the capture dialog
cortexctl capture watch # state, progress and meters until the dialog closes
cortexctl capture page N|NAME # diagram-1 .. diagram-5, io-setup
cortexctl capture io PARAM VALUE # e.g. in1-level 0.1667 (0 dB); VALUE normalized, 0 to 1
cortexctl capture start # record and train, about 5 minutes
cortexctl capture ab capture|reference # listen to the capture or the real amp
cortexctl capture save NAME --type X [--instrument X]
cortexctl capture close
capture watch sends nothing. It prints a line per change, the meters at
most once a second, and stops when the dialog closes. It can run next to
the other commands: the device goes quiet when another command ends, and
watch reconnects after 2 s. save works once the capture is ready
(state 13). Its name has at most 32 characters, from the same set as
save-as, and must not be in the capture library yet. --type is one of
other, amp, combo, amp-cab, cab or pedal; --instrument one of guitar
(the default), bass, synth, vocal or other. The states and IO params are
listed in docs/protocol.md §10.17.
Building
Requires Rust 1.99+ (edition 2024) and libudev headers (libudev-dev,
systemd-devel or systemd-libs, depending on the distro). No protoc
is needed.
cargo build --release -p cortexctl -p cortex-tui # CLI and TUI, no WebKit needed
cargo build --release # everything, needs the desktop app's packages
cargo test --workspace
Then set up device access with the udev rule: docs/linux-setup.md. All three apps use it.
Non-goals
- Cloud features. No Cortex Cloud login, downloads, uploads or sync.
- Changing the device's firmware. No firmware updates, flashing or patching.
- Unlocking paid content. No enabling of plugins or other content the device has no license for. cortexcontrol only uses what the device already offers.
Repository layout
| Path | What |
|---|---|
crates/cortex-app |
The desktop app (Tauri); its frontend is in ui/ |
crates/cortex-tui |
The terminal app (cortextui) |
crates/cortexctl |
The CLI |
crates/cortex-manager |
Finds devices, keeps one session open, sends edits and publishes the state, for both apps |
crates/cortex-state |
The device state and preset view model shared by the CLI and the apps |
crates/cortex-catalog |
The model catalog the device sends during the handshake |
crates/cortex-session |
Message codec (trailer, compression), session threads, handshake, keepalive |
crates/cortex-transport |
HID discovery/open (hidraw) and the 129-byte report framing |
crates/cortex-capture |
Readers for USBPcap/usbmon pcap(ng) and hex captures |
crates/cortex-proto |
Protobuf types generated from proto/, plus the message-type registry |
proto/ |
Cleaned .proto files used by the build |
proto/carved/ |
Raw descriptors carved from Cortex Control.exe by tools/carve.py |
tools/clean_proto.py |
Turns the carved protos into proto/ |
tools/ghidra/ |
Headless Ghidra scripts used for the analysis in docs/protocol.md; each lists the env vars it needs at the top (decompiled output is not committed) |
docs/protocol.md |
The protocol as far as it is understood, with evidence |
captures/ |
USB captures of the official app, used as test fixtures |
packaging/ |
udev rule |
openspec/ |
Specs and change proposals |
re/ |
Local only, git-ignored: Cortex Control.exe, the Ghidra project (re/ghidra_proj), decompiled output (re/out) and logs |
Capturing traffic for bug reports
If something does not work with your device or firmware, a capture of the official app helps most.
-
On Windows, install Wireshark with USBPcap.
-
Start a capture on the USBPcap interface that the Quad Cortex is on.
-
Start Cortex Control, do the thing that fails in cortexcontrol, then quit Cortex Control.
-
Save as
.pcapngand check it decodes:cortexctl decode capture.pcapng | less cortexctl decode --raw capture.pcapng # if typed decoding fails
On Linux you can capture cortexcontrol itself with usbmon:
sudo modprobe usbmon
sudo tshark -i usbmon<bus> -w - > cortexctl.pcapng # bus number from `lsusb`
cortexctl decode cortexctl.pcapng
-w - lets your own shell write the file, so it belongs to you, and it
also works where root may not write to your home directory.
Captures contain your device's serial number and MAC address, plus your presets and Neural DSP account name. Review them before sharing.

