All apps · 0 apps
patchferret
Docker app from Stoatworks Labs' Repository
Overview
Readme
View on GitHubPatchFerret
AI-assisted project. This codebase was created with Claude Code (Anthropic), directed and reviewed by a human author. The Behringer X32 adapter is written against a real 2,104-line scene file saved by an actual console and is asserted against it in CI — 90 tests across the workspace, of which 17 cover the X32 adapter: the connector→slot→channel composition, the head-amp index split across the local and both AES50 links, and the output-source enumeration. The generated PDFs are produced and visually inspected. No output has ever been loaded back into a console, no conversion between consoles exists yet, and every format other than the X32
.scnis unimplemented. The CL/QL and SQ adapters were built from controlled diffs in the manufacturers' own offline editors and are checked against those files; the CL/QL patch table is found at an absolute offset established on a single QL5 and is validated before use rather than trusted. The Yamaha MMS adapter is checked against the 41 factory scenes shipped inside DM3 and TF Editor, but its patch-source encoding has never been confirmed against a console's patch screen, and on TF no connector resolves at all — the tool says so rather than guessing. The X32 signal-source enumerations are likewise derived from community documentation and corroborated against real files, not from a running desk — see Provenance.
Turn a mixing console show file into documentation an engineer can actually use: a patch list, a specification sheet and a wiring topology diagram, as PDFs.
Try it: https://patchferret.stoatworks-labs.com — an early preview, and still very much in development.
Runs in the browser or on the command line. The browser build parses and renders entirely in WebAssembly, so a show file never leaves the machine — there is no upload and no server.
What it does today
show file → format adapter → PFX model → ├─ patch list PDF
├─ specification PDF
├─ topology PDF
└─ PFX interchange XML
| Console | File | Status |
|---|---|---|
| Behringer X32 / Midas M32 / X-Air | .scn |
Supported |
| Yamaha DM3 / DM7 / TF | .dm3s .tfs .dm7s (+ presets) |
Supported — names and input patch; head amps and sends not yet |
| Yamaha CL / QL | .CLF |
Supported — input patch and channel names |
| Allen & Heath Avantis / dLive | .tar.gz |
Supported — input patch and strip inventory; names and preamps not yet |
| Allen & Heath SQ | NVDATA.DAT |
Supported — input patch; names and socket class not yet identified |
| DiGiCo, Avid VENUE | — | Planned |
Format research for the unimplemented consoles lives in the private patchferret-research
repository, along with sample show files (which are client data and do not belong in a public
repo).
Use it
Command line
cargo build --release
./target/release/patchferret info myshow.scn
./target/release/patchferret report myshow.scn -o ./docs
./target/release/patchferret xml myshow.scn -o myshow.pfx.xml
info prints a summary and, importantly, the fidelity list — everything the adapter read
but could not carry into the model, and which would therefore be lost in a conversion.
The report header
Reports carry a header with your logo, the event, venue, engineer and so on. None of that is in a show file — a console stores a mixer state, not the job it was built for — so it comes from a job sheet you supply:
patchferret job-template -o job.txt # a starting point
patchferret report myshow.scn -j job.txt -o ./docs
The sheet is key: value lines, and any key it does not recognise becomes an extra header
field, so "Truck call" or "Rider rev" work without the tool knowing about them:
Event: Summer Live 2026
Date: 12-14 September
Artist: The Something Band
Venue: Old Granada Studios
Engineer: A. Sargeant
Contact: 07700 900000
Truck call: 06:00
logo: ./logo.jpg
The browser version has the same fields as a form, and accepts any image for the logo — it converts on a canvas before handing the bytes over. The CLI takes JPEG, or PNG without transparency; embedding a transparent PNG would mean decoding pixels, which the dependency-free PDF writer deliberately does not do. It says so rather than dropping the logo silently.
Browser
rustup target add wasm32-unknown-unknown
./scripts/build-web.sh
python3 -m http.server 8731 --directory web
Then open http://localhost:8731. Module scripts and WASM will not load from file://, so it
has to be served over HTTP.
Why the patch list is not obvious
On an X32, getting from an XLR to a fader takes three hops, and all three have to be composed:
/config/routing/INmaps blocks of eight physical connectors onto the 32 input slots./ch/NN/configselects which input slot feeds channelNN— a free mapping./headamp/NNNholds the gain for a connector, in a flat index across local and both AES50 links.
Assuming channel N is fed by XLR N produces a confident and wrong patch list. In the test fixture, slots 25–32 are AES50-B connectors 1–8 while channels 25–32 carry them, and six channels reach no connector at all. PatchFerret composes the chain and marks the dead rows.
The PFX model
patchferret-model defines a console-neutral show model and its XML serialisation. The design
rule is that the model is lossy in a declared way: anything an adapter understood but the
model cannot express becomes a Diagnostic, which the spec sheet prints. A show that converts
with no diagnostics is one we can claim to have fully understood; anything else says so.
Two choices worth knowing:
- Head amps hang off the socket, not the channel. On a shared stage box the gain belongs to the connector and every console listening to it shares that gain. Modelling it per-channel is the standard way to produce a patch list that lies about gain sharing.
- The input-slot indirection is preserved. Resolving it away at parse time would make the connector column a guess and would make re-patching impossible to express later.
ConsoleProfile describes what a desk has — channel counts, bus counts, EQ bands, physical
I/O. It is the prerequisite for the conform work below, and is populated now so the shape is
fixed before adapters depend on it.
Provenance of the X32 enumerations
The signal-source numbering is not published by the vendor. It is derived from community documentation and corroborated against real scene files. Two internal checks support it, and both are asserted in CI:
- No value in the decoded range resolves to a matrix above 6. The X32 has exactly six, so the competing "1–16 are the mix buses" reading is ruled out — it would require matrix 7 and 8.
/outputs/p16/01 26 <-EQdecodes to "direct out of channel 1, pre-EQ", which is what an Ultranet port 1 conventionally carries.
Every output row in the test fixture decodes cleanly, which is asserted as a test. Values outside the mapped ranges are reported as diagnostics rather than guessed.
Not built yet
The longer-term goals, in dependency order:
- Editing the patch — re-assign connector→channel and write the show file back out. The model already keeps the slot indirection this needs; no adapter can write yet.
- Console-to-console conversion — read one format, write another via PFX.
- Conform — reconcile a show against a target
ConsoleProfileand report what does not fit, with intelligent mapping where a straight copy is impossible.
Each of these needs a writing adapter, which is strictly harder than reading, and needs hardware to verify against. None of it should be trusted until a converted show has been loaded into a real console.
Layout
crates/patchferret-model PFX model, XML, console profiles
crates/patchferret-formats adapter trait, registry, X32 / Yamaha / A&H adapters
crates/patchferret-report dependency-free PDF writer and the three reports
crates/patchferret-cli the local tool
crates/patchferret-wasm C-ABI entry point for the browser
web/ browser front end
See AGENTS.md for the onboarding detail.
Run your own copy
PatchFerret is a static page, so hosting it yourself is one container serving the built files — the same files the hosted copy serves, running somewhere that still works when the venue has no internet.
Docker. The image is built by this repo's docker.yml workflow on every
push and published as ghcr.io/stoatworks-labs/patchferret:
docker run -d --name patchferret --restart unless-stopped -p 8544:80 ghcr.io/stoatworks-labs/patchferret:latest
Or docker compose up -d with the docker-compose.yml
in this repo, which maps the same port. Either way it is then at
http://localhost:8544/.
Unraid. Search Community Applications for PatchFerret — the template is
templates/patchferret.xml
in stoatworks-unraid, which is what the CA feed reads.
Stoatworks Burrow lists it under Self-hosted, with the compose file a click away.
The Dockerfile, docker-compose.yml, docker/ and the workflow are
generated from fleet.json in stoatworks-unraid. Change them there and
regenerate rather than editing them here.
Licence
MIT.
X32, M32, and the console names in this repository are trademarks of their respective owners. PatchFerret contains no vendor code and is not affiliated with or endorsed by any console manufacturer.
Install Patchferret on Unraid in a few clicks.
Find Patchferret in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/stoatworks-labs/patchferret:latestRuntime arguments
- Web UI
http://[IP]:[PORT:80]/- Network
bridge- Shell
sh- Privileged
- false
Template configuration
Host port for the web interface.
- Target
- 80
- Default
- 8544
- Value
- 8544