pyquadcortex
Control a Neural DSP Quad Cortex from Python, over USB.
pyquadcortex is a USB client for the Quad Cortex, in the same way Cortex
Control is a USB client for it: it connects over the cable and speaks the
protocol the device already speaks. Nothing on the unit is modified, unlocked,
or jailbroken - no custom firmware, no SD card surgery, no developer mode. Plug
in the USB cable and the device answers.
Recall and edit presets, build scenes, place and route blocks, drive the I/O and the Global EQ, browse the unit's own catalogue of blocks and Neural Captures, and manage setlists - the operations you would do in Cortex Control or on the touchscreen, from a script. What is and is not covered is listed feature by feature in docs/manual-coverage.md.
The library imports as pyquadcortex; a command-line tool named qcctl comes
with it.
Unofficial and not affiliated with, endorsed by, or supported by Neural DSP Technologies. "Quad Cortex" and "Neural DSP" are trademarks of their owner and are used here only to describe what this software talks to.
Status
Working, and verified on real hardware on macOS (Apple Silicon) and Windows,
against CorOS / Cortex Control 4.0.1 (device firmware d14e). Every
operation listed below has been exercised on a physical unit. The test suite runs
fully offline, with no device attached.
The device protocol carries no version of its own, so a future CorOS update could change it. If you are on a newer version and something misbehaves, that is the first thing to suspect - see docs/protocol.md.
Install
pip install pyquadcortex
You also need the hidapi C library, which is what actually opens the USB device:
- macOS:
brew install hidapi - Debian/Ubuntu:
sudo apt install libhidapi-hidraw0 - Windows: included; nothing to do.
Python 3.11 or newer.
macOS note: the
hidpackage looks forlibhidapiby bare name, and Homebrew's library directory is not on the default search path. Prefix commands that talk to the device withDYLD_LIBRARY_PATH=/opt/homebrew/lib.
Before you connect
Quit Cortex Control first. It holds the USB interface exclusively, so while it is running nothing else can talk to the device. (Wi-Fi can stay on, it makes no difference. The Quad Cortex just has to be plugged in over USB.)
Quickstart
Everything here uses the factory library, so it works on any unit.
import pyquadcortex
from pyquadcortex import Input, Instrument, Scene, Setlist
with pyquadcortex.connect() as qc:
# What are we talking to?
print(qc.version().app_fw_version)
# What is in the factory library?
for entry in qc.list_presets(Setlist.FACTORY)[:5]:
print(entry.name)
# Recall a preset by the name shown on the unit, then switch scenes.
amp = qc.find_preset("Brit 2203", Setlist.FACTORY)
qc.recall_preset(Setlist.FACTORY, amp.index)
qc.switch_scene(Scene.B)
# Re-point a preset's input to Return 1 and save it as your own.
# (Pick an empty slot; this overwrites whatever is in it.)
bass = qc.find_preset("Cali Basswalk", Setlist.FACTORY)
preset = qc.read_preset(Setlist.FACTORY, bass.index)
qc.reroute_grid_input(preset, Input.RETURN_1)
qc.save_current_preset(Setlist.USER, "30A", "Cali Basswalk [Ret1]",
instrument=Instrument.BASS)
connect() finds the device, opens it, and completes the handshake the device
requires, so what you get back is ready to use. As a context manager it also
releases the device when the block ends; otherwise call qc.close().
Closing tells the device the client is leaving, which is what Cortex Control does on
quit. If you supplied your own transport and so own teardown yourself, send it with
qc.disconnect() before you tear down.
Presets are addressed by name via find_preset(), or directly by the slot name
shown on the unit ("30A"), or by linear index. Scenes, inputs, outputs, and
instrument tags all have readable names, so nothing here is a bare number.
What it can do
Four things worth knowing about, each with the full detail in docs/api.md.
Edit a preset
Blocks, parameters, routing and bypass, addressed by row and column. Rows are zero-based here and 1 to 4 on screen, which is the single easiest thing to get wrong - an edit to the wrong row still succeeds and still reads back correctly.
row = free_rows(preset)[0] # not just "a row with no blocks"
qc.set_block(row=row, column=0, model=models.BassAmplifier.AMPED_FLIP_TOP_6464)
qc.set_chain_input(row=row, in_portid=Input.INPUT_2)
qc.set_chain_output(row=row, out_portid=Output.MULTIPLE) # required, not optional
qc.set_param(row=row, column=0, param="MASTER", real=5.0, model=amp)
qc.save_current_preset(Setlist.USER, "30A", "Bass on In 2")
An edit persists only through this kind of row/column-keyed write followed by a save. Writing a whole preset back does nothing.
Build scenes
Scenes are the Quad Cortex's signature feature, and most factory presets build them from per-scene parameter VALUES rather than from bypass. Name a scene and the value belongs to that scene alone.
qc.set_param(row=0, column=5, param="MIX", value=0.8, scene=Scene.C)
qc.set_lane_output(row=0, param="VOLUME", value=0.0, scene=Scene.E) # a silent scene
qc.set_bypass(row=0, column=2, bypassed=True, scene=Scene.B)
Drive the whole unit, not just presets
I/O port levels and ground lift, the Global EQ, footswitch modes, the tuner, the Looper's state, master volume, Gig View, and most of the Device Settings menu.
qc.set_input_port(1, level=0.4) # global: no save, nothing to undo it
qc.set_global_eq(band=3, gain=0.75, filter_type=GlobalEQFilter.PEAK)
qc.set_scene_bypass_behavior(SceneBypassBehavior.ALWAYS_OVERWRITE)
Read Reading global settings safely first - a read straight after a write can return the old value, which looks exactly like a refusal.
Find what is on the unit
The device tells you its own contents: every block it has, every preset, every folder, and over two thousand Neural Captures.
qc.catalog[21005].name # '212 Darkglass Neo (M)'
qc.list_presets(Setlist.FACTORY) # or any folder key at all
qc.list_folders() # 399 of them on the observed unit
qc.captures() # the Neural Capture library
Examples
Five runnable examples are in examples/, with
examples/readme.md describing each. Start with
inspect_preset.py - it only reads, and it prints what a
preset actually contains.
The ones that write are dry runs unless you pass --write, and they name the slot they
would overwrite.
Troubleshooting
If the device disappears mid-session with DeviceNotFoundError, see
docs/troubleshooting.md.
If all you need is to change presets or scenes, you may not need this library at all - the unit accepts MIDI over USB and DIN: bank select plus program change for presets, CC#43 for scenes, CC#35-42 for the footswitches. This library exists for everything MIDI cannot reach.
Command line
qcctl covers the common one-off actions (on macOS, with the
DYLD_LIBRARY_PATH prefix from above):
qcctl version
qcctl recall --slot 28C
qcctl scene --index 3
qcctl dump-preset --slot 28C
Documentation
- docs/api.md - everything the library can do, grouped by what it touches. The reference to the introduction above.
- docs/manual-coverage.md - every feature the Quad Cortex manual describes, against what this library covers. Read this to find out whether your case is supported before you start.
- docs/protocol.md - how the device's USB protocol works: framing, the connect handshake, each operation, and what has been verified.
- docs/architecture.md - how this library is put together, and how to add support for something it does not do yet.
- docs/capture.md - how to read the device's own traffic when you need a message shape this library does not implement yet.
- docs/roadmap.md - where this is meant to go, including the object model of the device that should eventually hide the protocol's rough edges entirely.
- changelog.md - what changed between released versions.
- docs/releasing.md - the release checklist, and why each step exists.
- docs/troubleshooting.md - when the device stops answering.
- contributing.md - development setup and how to submit a change.
Acknowledgements
Inspired by OpenCortex.
License
MIT.
Metadata
Release files for pyquadcortex 0.33.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyquadcortex-0.33.0.tar.gz | 198.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyquadcortex-0.33.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 307.7 kB
Release files / pyquadcortex-0.33.0.tar.gz
| Download URL | pyquadcortex-0.33.0.tar.gz |
|---|---|
| Size | 198.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2643dc8c1dacf23c76cc52fa64e830a4d3f2e9764f42659d09a42f47319b5811
|
|
BLAKE2b-256 checksum How to use checksums |
2b479b7441d9a603473ff95f055b5b00ee3f02887a0bcd26e37e3d5eaf96eb95
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Release files / pyquadcortex-0.33.0-py3-none-any.whl
| Download URL | pyquadcortex-0.33.0-py3-none-any.whl |
|---|---|
| Size | 109.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9547b17edd38e130f793604f29d64d2aa466dfcba887ed370f71b0b9446b01b1
|
|
BLAKE2b-256 checksum How to use checksums |
022c6ba0b9808a1df67bd01e8c8eb8042782f16ec75503c3fae92d1a38114ac7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|