mahmoud@saada: ~/blog
cat tone3000-linux-rig.md

An AI Agent Built My Entire Guitar Rig on Omarchy, So I Just Play

Part two: on AI-first Omarchy, an agent built my whole guitar rig, from neural amp tones to GuitarMood, the app that is my pedalboard. Free, open source, no setup left. Now I just practice.▋

Part two. Part one: How Claude's Fable Model Saved My Gear and Got Me Excited About Playing Guitar Again

GuitarMood on an Omarchy desktop: bank 3, The Number of the Beast, 1982, Hallowed Be Thy Name, RHYTHM lit in red on the bottom row of footswitches, the toggles on the top row and the two expression pedals on the right

GuitarMood, a real screenshot with the rig live: bank 3, RHYTHM lit, the song's key, tempo and solo echo up top.

TL;DR

Last time, my FCB1010 finally drove BIAS FX 2. Now it drives a guitar rig that's AI-native end to end, on Omarchy. The tone is neural: amp models learned from real gear. The rig was built by an agent: Claude Code running Claude Opus 5.5 wrote every piece, down to GuitarMood, the app that is my pedalboard. And there's no setup left to think about: open GuitarMood, stomp, play. Omarchy made that possible, because an agent can drive every part of it. All of it is free and open source.

The story is a 12-minute read. After it come the receipts and the full lab notes: 24 charts, 6 datasets, every A/B test and all 36 presets, all on this page. Code: github.com/saada/fcb1010-biasfx2 (MIT).

Update 3: seven banks, and a rig that knows my guitar

· newest first; yesterday's update is right below

Two new banks, reverbs off the busy core, and every preset voiced for the guitar I actually play.

Sounds. I asked: “Do we have a journey tone like who's crying now outro solo?” Now it opens a whole bank.

BankSW1–5SW7
5 80s LEADS newWho's Crying Now · Sweet Child O' Mine · Crazy Train · Master of Puppets · EruptionChorus
6 LEGENDS newPride and Joy · Voodoo Child · Bohemian Rhapsody · Whole Lotta Love · Back in BlackVibe
0 MAIDEN rebuiltClean = Hallowed Be Thy Name intro · Acoustic = Dance of Death, Dream of MirrorsHarmony

SW6 wah, SW8 boost, SW9 delay and SW10 tuner in both new banks.

Nobody documented Neal Schon's amp on Escape, so Who's Crying Now runs on the one amp anyone named: a cranked '71 Twin, with a TS9 that squeezes the crest from 15.0 to 13.0 dB (D27). The ten new songs, once spread from −4.3 to −20.3 LUFS, all sit at −13.9 to −14.0. Legends' SW7 is a Uni-Vibe built from LSP Phaser, not yet measured (D28). The Maiden clean lost 4.7 dB of boom under 250 Hz. The acoustic measured darker than my dry guitar, and now has 18 dB more at 2–5 kHz (D24).

GuitarMood showing bank 5, 80s Leads: Who's Crying Now lit on SW1, Sweet Child O' Mine, Crazy Train, Master of Puppets and Eruption beside it, Boost (Ibanez TS9 clean push) and Delay (tape delay, 500 ms) lit on the top row next to Wah, Chorus and Tuner

Bank 5 from ch 16 PC 50, rendered offscreen with no rig.

Free TONE3000 captures again: @slamminmofo's amps carry five new songs, plus @dahmanmusic's '71 Twin, @ripper's 1959RR, @dogusaltin's Mark IIC+, Fabio Zani's Dumble Steel and AmalgamAudio's JTM45/100, and for Maiden @danielesporma's JMP 1987, @origrino's JMP 2203, @raymondvisser's piezo and @ygorthyago's Taylor 816. Thank you all.

GuitarMood showing bank 6, Legends: Pride and Joy lit on SW1, Voodoo Child, Bohemian Rhapsody, Whole Lotta Love and Back in Black beside it, Vibe (Uni-Vibe) and Boost (Ibanez TS9) lit on the top row next to Wah, Delay and Tuner; the setlist shows seven banks

Bank 6 (PC 60) with SW7's Vibe, a DAW toggle that survives song changes.

The rig got smarter. I said: “the sound seems wildly louder when i play specific palm muted notes vs the rest of the sound”. My own take, through the real engine, put B1 at −0.3 dB and B2 at +1.0 dB from the median palm note. The rig is flat. The suspect, still unmeasured, is my room: a 2.8 m dimension resonates at 61.7 and 123.4 Hz, exactly B1 and B2 (D23).

Then: “It makes the system feel sluggish despite not really using all the cpu.” One realtime thread did all the DSP while about 11 of 12 cores idled, and long reverbs made it burst every 32nd cycle. They now run on threads of their own. Maiden Acoustic's worst callback went from 4.70 to 1.43 ms, and an interleaved live A/B read 0 and 41 xruns before, then 0, 0, 0 and 2 after, plus one outlier that didn't repeat (D25). TONE3000 had also trimmed the lead-in off 24 delay-only echoes, landing their first repeat on the note. One −79 dBFS sample puts every delay back within 0.5 ms (D26).

Last: “are you aware of what guitar I'm playing on and have you put that into consideration across our presets and rig?” Only partly. My JP70's hot bridge humbucker (about 18 kΩ) resonates near 2.0 kHz in a pickup model, a Strat near 4.1 kHz. Now all 33 presets name their record's pickup, and the builder EQs the amp's input to match, moving the level by 0.5 dB at most. Only Maiden Heavy gets its low B tightened, by 6 dB (D29).

How. Parallel Claude Opus 5.5 subagents, six PRs (#6 to #11) merged in one day, briefs written with Matt Pocock's writing-for-agents skill. AGENTS.md now records my guitar, so the next agent knows it's a seven-string JP70.

I said what I heard, and every answer came back with a number. Back to that Who's Crying Now outro.

Update 2: five banks, tone by words, the last flash

· newest first; yesterday's update is right below

A day later, the board grew again. I kept playing, kept saying what I wanted, and the agents kept shipping. Four things changed.

Two new banks, five total.

BankSW1–5SW7
0 MAIDENRhythm · Lead · Clean · Acoustic · CrunchHarmony
1 80sVan Halen I · VH 1984 · Pyromania · Hysteria · U2 StreetsChorus
2 80s CLEAN newIs This Love · Rule the World · Every Breath You Take · Hysteria Clean · This Charming ManChorus
3 VARIETYComfortably Numb · Radiohead · Nirvana · Djent · Purple RainOctaver
4 MODERN newSatan Full Rig · Satan 50 Modern · Low Tuned · Lead · StormbladeOctaver

SW6 is still the wah and SW10 the tuner in every bank.

80s CLEAN is every chorus-drenched clean I grew up on. TONE3000 can't do chorus, so the DAW does: a real stereo chorus (LSP Chorus Stereo) on SW7 in both 80s banks, with each song's own rate, depth and mix (D20).

GuitarMood showing bank 2, 80s Clean: Is This Love lit on SW1, Rule the World, Every Breath You Take, Hysteria Clean and This Charming Man beside it, and Chorus lit on SW7 with the song's rate, next to Wah, Boost, Delay and Tuner; the setlist shows five banks

Bank 2 with SW7's chorus on, rendered offscreen with no rig attached.

MODERN is Randall Satan territory, built from free community captures on TONE3000: @nillmtd's full rig of Ola Englund's Satan + Fortin 33, Paul Jacovino's Satan 50 and Solar CHUG, and @elihis's Fortin 33. The cabs are free PLAP IRs from Lancaster Audio (Warren Huart's Marshall V25s, Cameron Webb's Uberschall, Ulrich Wild's Albion), and SW5 is Glenn Fricker's free Stormblade A2 from Spectre Media. Thank you all (D18).

Say it and it's done. What I asked for: I want to say "make all cleans sound fatter" and have it take 2s instead of 20 mins. Now every number we tune lives in one sheet, rigs/tone.csv, with @clean-style group rows and a small word vocabulary. One command edits the sheet and renders every clean it touched, before and after, in the real TONE3000 engine, offline:

$ uv run tone3000.py tune @clean fatter
tune @clean fatter x1: 8 presets in reach
  eq_100 +1, eq_250 +1.5, eq_3k5 -0.5
preset         <250 250-800  2-5k
Maiden Clean      =   +0.3  -1.2
U2 Streets     +0.2   +0.1  -1.1
Purple Rain    +0.8   +0.6  -0.5
Is This Love   +0.8   +0.5  -0.5
Rule the World +0.4   +0.2  -0.8
Every Breath   +0.3   +0.2  -0.9
Hysteria Clean +0.7   +0.2  -0.5
Charming Man   +0.4   +0.5  -0.8
8 presets, 10 rendered, 6 from cache

Trimmed output: band energy changes in dB, before → after. The renderer, rigsim.py, hosts the native TONE3000 plugin offline and lands within 0.1 dB of the live bench (D19). Edit plus renders: 3.3 s measured.

Add --apply and it's live on my next stomp, because TONE3000 rereads the preset file on every Program Change. No restart, no clicking.

The last pedal flash. Every layout change meant re-flashing the FCB1010, and I finally asked: "why do you keep making me flash the thing". So I flashed it one last time. Now the pedal only sends fixed addresses on channel 16, and a tiny ALSA router, fcb_router.py, maps them to the layout in software. It adds 0.28 ms median live, hot-reloads when the layout changes, and runs in the helper or inside GuitarMood. Banks are just code now (D21).

GuitarMood showing bank 4, Modern: Satan Full Rig lit on SW1, Satan 50 Modern, Satan 50 Low Tuned, Satan 50 Lead and Stormblade beside it, Wah, Octaver, Lead, Drive and Tuner on the top row

Bank 4, reached from one ch 16 address through the same router code GuitarMood runs.

Volume, fixed. Then I noticed the volume pedal was wrong: toe-down was super loud, and switching sounds reset it, ignoring where my foot was. The pedal was driving TONE3000's own output level, which every preset change re-applies. Now it drives a gain stage in the DAW, right before the limiter, that no preset touches. It works like a real volume pedal: heel is silent, full toe is exactly 0 dB, and rocking it forward resets it. 0 dB is set for YouTube play-along: the simulator levelled every preset to YouTube's −14 LUFS (cleans −15) in a few seconds. Before that, the presets were spread across 11 dB, with Hysteria 8 dB hot and Van Halen I 4 dB quiet. Now a backing track and my rig come out balanced, and the monitor knob sets both (D22).

How. Same as yesterday: Claude Opus 5.5 split the work across parallel subagents, and every one of them followed AGENTS.md: presets are code, and measure, then claim. That's the whole point. I say what I hear or what I want. Agents ship it while I keep playing.

Update: no glitches, open chugs, three banks

· the rest of this post is the original story

I've played this rig every day since, and three things bugged me. I said them out loud. Claude Code fixed all three in one evening while I kept playing.

Acoustic glitches. My acoustic sounds crackled, and turning the delay on made it worse. The agent found why: Qtractor tells the plugin to expect blocks of up to 2048 frames, then runs 256. TONE3000 v0.0.9 sized every IR convolver for the 2048, so every small cycle paid for a big one. v0.0.12 caps it (upstream PR #223). The agent installed it, taught the preset builder its new file format, and measured again:

Acoustic presetv0.0.9v0.0.12
Acoustic360 / 640 / 0
Maiden 84 Acoustic62 / 8755 / 0
Maiden 2000s Acoustic2 / 26110 / 0*

Xruns per minute, delay on / delay off, from pw-top. *400/min in the first seconds after the preset change while its IRs load, 0 once settled. Lab notes D15

Palm mutes. I said they sounded too muffled. The agent cleared the noise gate first: it ported v0.0.12's gate code and fed it plucked bursts, which pass untouched down to −50 dBFS. The suspect left was the bus compressor (−18 dB, 3:1, 10 ms attack), clamping every chug's pick attack. It's bypassed by default now; the limiter stays. Honest bit: that rests on my ears plus the simulation, not yet a measured A/B on the rig (D16).

Banks. I had ten: three song banks and seven Maiden eras. Way too many banks for Iron Maiden lol. Now there are three:

BankSW1–5SW6–9
0 MAIDENRhythm · Lead · Clean · Acoustic · CrunchWah · Harmony · Evil delay · Madness delay
1 80sVan Halen I · VH 1984 · Pyromania · Hysteria · U2 StreetsWah · Octaver · Boost · Delay
2 VARIETYComfortably Numb · Radiohead · Nirvana · Djent · Purple RainWah · Octaver · Lead · Drive

SW10 is the tuner in every bank.

MAIDEN is built on the best tone in the rig, Brave New World: Murray's Marshall JMP-1 on the left, Smith's JCM2000 on the right. One RHYTHM, one LEAD, and two delay buttons: Evil delay (375 ms) and Madness delay (415 ms, tempo still unconfirmed). The 80s bank comes from cited rig research. The seven era banks are archived in rigs/archive, not deleted.

GuitarMood showing bank 0, Maiden, Brave New World tone: RHYTHM lit on SW1, Lead, Clean, Acoustic and Crunch beside it, Wah, Harmony, Evil Delay, Madness Delay and Tuner on the top row, and a setlist of three banks: Maiden, 80s, Variety

The new board, bank 0, rendered offscreen with no rig attached (hence "display only").

How it got built. Claude Opus 5.5 split the work across parallel subagents: the plugin upgrade, the 80s research, the bank build, and an agent guide. That last one is my favourite. The repo now has an AGENTS.md, written with Matt Pocock's writing-for-agents skill, so the next agent arrives knowing the rules: presets are code, check whether I'm playing first, and no sound change counts until a number backs it.

That's the AI-first Omarchy loop in one evening. I said what I heard. Agents measured it and fixed it. I kept playing.

Act one · leaving Wine

10 ms on paper, mud in my hands

Last time, the pedalboard that lived under my desk for years finally talked to BIAS FX 2, and I stood in my living room grinning through The Evil That Men Do. Then I did the thing every developer threatens to do: I left macOS for Omarchy, the opinionated Arch + Hyprland setup. I love it. It's also AI-first: it ships with Claude Code, Codex and OpenCode installed. I didn't expect that to matter for guitar. It turned out to be the whole story. My guitar rig, meanwhile, did not come along quietly.

BIAS FX 2 is Windows/macOS only, so it came along under Wine. The scripts from last time still worked, presets loaded, the pedal switched them, and the app claimed about 10 ms of latency. It felt like playing through a swimming pool. That number only covers the app's own buffer; under Wine the audio also crosses an ASIO shim, a JACK bridge and PipeWire, and none of that shows up in the friendly "10 ms". Skill issue? Maybe. Architecture issue? Definitely.

Why Omarchy made this possible

Here's the part I didn't see coming. Everything in this post was built without me touching a mouse, and that's not me being stubborn. It's what Omarchy is built for. It's AI-first in exactly the places an agent needs:

So the agent built a whole musician's setup the way it writes code: read, write, run, measure. My job was playing, listening, and saying what felt wrong. Here's how that went, one act at a time.

A native amp sim, and presets as code

First job: get off Wine. TONE3000 had just shipped a native Linux plugin. TONE3000 is a huge, free, community catalog of Neural Amp Modeler captures: real amps, pedals and whole rigs, modelled until the model is basically the box, plus impulse responses for cabinets, rooms and plates. A 1972 Hiwatt. The Gallien-Krueger preamp from Adrian Smith's Seventh Son rack. The player is open source under MIT, and I was not ready for how good it sounds. At a 256-sample buffer it felt tighter than BIAS under Wine at a nominal 128.

It has no scripting API, but it has files. A preset is a binary JUCE ValueTree that embeds the tone data itself, and Program Change N loads the Nth entry of a JSON list. Round-trip those and a script can write presets the app can't tell from hand-made ones. Then I pointed Claude Code at the catalog with fifteen research agents, one per band, each finding the best-era rig, citing sources and writing the preset as JSON. My favourite find: Iron Maiden went solid-state Gallien-Krueger for Somewhere in Time and Seventh Son, not Marshall. I had no idea.

Two things I only learned by measuring: 256 samples with multi-core on meant zero xruns, and a Linux volume slider had been quietly stealing 24 dB between the rig and my ears.

Dropouts per minute, worst preset
128, multi-core off352
128 samples42
192 samples26
256 samples0

Xruns from pw-top, researched dual rigs, oversampling off (L2–L3). All 39 runs ↓

Act two · a DAW nobody clicks

Generated, never clicked

Fifteen rigs and zero dropouts later, I hit the ceiling of a standalone amp sim: no real wah, no harmony, no compressor. A DAW can do all three, but DAWs want you to click, and an agent can't click. It can write files and run commands, so the DAW is generated: qtractor_rig.py writes the entire Qtractor session (tracks, plugins, MIDI bindings) and starts it. Nothing in it was ever touched with a mouse. A compressor and limiter on the way out halved the loudness spread across presets and ended a day of hand-levelling. (Picking the DAW ↓)

Loudness of the 15 song presets (LUFS, quietest to loudest)
Dynamics off9.2 dB
Compressor + limiter4.6 dB
−19−8

Dynamics off: −18.0 to −8.8 LUFS, peaks to +3.0 dBFS. On: −15.5 to −10.9, nothing above −1.0 dBFS (D1). The full chart ↓

Act three · up the Irons

Seven eras of Maiden on one floorboard

Then I asked for the fun part: every Iron Maiden era from The Number of the Beast to Brave New World, with rhythm, solo, clean and acoustic sounds I can switch mid-song, as close to the records as possible. Seven more research agents, one per album, wrote 21 presets backed by 143 source citations. Every bank is one era, and every bank has the same board:

The Maiden banks (03–09). Bottom row: scenes. Top row: toggles. All ten banks ↓

I played it live from the real board, every switch's MIDI checked on the way in, and my verdict was "sounds amazing". (The seven eras, the gear, the harmony and every measurement ↓)

Act four · five tuners

SW10: five tries at a tuner

The last switch should have been the easy one. TONE3000's own tuner is a button with no MIDI, so SW10 mutes the rig and opens a tuner on the guitar instead. Picking that tuner went sideways:

  1. Hand-rolled in Python. No: I wanted existing software.
  2. x42 Tuna via jalv. Needed XWayland. Worked. Ugly.
  3. FMIT. Fixed Qt layouts that tiling crushed. My review, verbatim: "insanely ugly with bad aspect ratios".
  4. Chromatic. Beautiful, after a Hyprland window rule. Then practice started cutting up, and I typed "oh i think it's picking up from my mic?" It was: two leftover instances were listening to the mic, not the guitar, and cost the rig about 250 dropouts in 12 seconds. Fixed, but it took a port re-patcher and a kill-before-open rule to show one note name.
  5. Stop building, go shopping. Then it hit me: "all our tuners are dumb solutions... omarchy has existing plugins in the marketplace".

It does. Fretwise (MIT, verified in the Omarchy marketplace) lives in the bar, draws the note, the cents and the string in the theme's colours, and only listens while its panel is open. SW10 points it at the guitar input and opens its panel through the shell's IPC; the next press hides it. No window to float, nothing left running. Lesson learned: before building the thing, check what your desktop already ships. (All five, with screenshots ↓)

Act five · GuitarMood

GuitarMood: the rig as an Omarchy app

Last time I ended with a promise: a TUI that draws the pedalboard and lights up the switch you just stomped. By now the rig sounded right, but using it still meant a terminal to start it and a pedal that tells you nothing. Is the wah on? Did the boost reset? Which bank was Fear of the Dark? So I asked for "a desktop app that shows live status of what i selected in the pedal as a hypland tile and autoscale and plays nice with omarchy... I don't have to guess anything."

Claude's first plan was GTK. I asked: "omarchy seems to love Qt?" It does: the Omarchy shell is Quickshell, which is QML. So the promised TUI grew into a Qt window instead: GuitarMood, a uv project with a QML front end. (Editing the layout from it is still to come; the board itself is still two tables in rig.py.) It reads the active theme's colours and recolours live when I switch themes.

The window is the pedalboard

The layout mirrors the FCB1010 under my feet: toggles on top, scenes below, UP and DOWN naming the bank one tap away, expression pedals on the right, the setlist down the side. Every tile says what that switch does in the bank I'm on. Everything is sized from the window, so it fits whatever tile Hyprland hands it. In a quarter of the screen it still reads from across the room:

GuitarMood in a quarter tile of the screen, next to btop, pw-top and a MIDI monitor

A quarter tile, sharing the screen with btop, pw-top and a MIDI monitor (tap to enlarge).

And Omarchy's pop-out (SUPER+O) pins it to every workspace. Below about 700 px wide it switches to a compact layout: the bank, the scene in huge letters, and the toggles as chips. A status light for the rig that follows me around:

A small pinned GuitarMood panel: bank 3, The Number of the Beast, RHYTHM in large red letters and five toggle chips

Popped out with SUPER+O: bank 3, RHYTHM, five toggles, none on.

Never guess

The pedal only sends fixed messages (a toggle sends CC 127 whether it's switching on or off), so GuitarMood rebuilds the state from the MIDI by the rig's own rules: DAW effects survive a song change, amp-sim blocks reset on every preset load. Anything it can't know shows "?" until you press a switch. The tests replay my first real session and check it ends exactly where I did.

On is open, off is close, and it's two-way

Because every part of Omarchy is a command, the agent wired GuitarMood into the desktop itself: a launcher entry, and a Hyprland rule that parks Qtractor on a hidden workspace. Open it and the rig starts. A second launch just focuses the window. Close it (SUPER+W), log out or Ctrl-C, and it closes the tuner, saves the session and quits Qtractor; tested end to end, nothing left running. And it works both ways: click a tile or press 1–9, 0 and GuitarMood sends exactly what the FCB would, from the same code that wrote the pedal's layout. The pedal itself can't be told anything, so its own display keeps its bank, and presses from the screen are marked "on screen". (How it works, in detail ↓)

Epilogue · free, open, played

Free for anyone

So that's the rig. What does it cost? In software, nothing. No Wine, no paid DAW, no subscription. Everything between the guitar cable and the speakers runs natively on Linux, and every piece is open source. I checked each license:

PartLicense
TONE3000 plugin amps, cabs, IRsMIT
NeuralAmpModelerCore the neural modelsMIT
Qtractor the generated DAWGPL-2.0+
x42 · LSP · Guitarix harmony, dynamics, wahGPL / LGPL
PipeWire · Hyprland · OmarchyMIT · BSD-3 · MIT
Fretwise the tunerMIT
PySide6 · uv GuitarMood's window, packagingLGPL-3.0 · MIT
fcb1010-biasfx2 everything I wrote, GuitarMood includedMIT

Checked on 2026-09-27. Full table ↓

The tones aren't software: they're community captures on TONE3000, free with a free account. The hardware isn't free either: an FCB1010, a Scarlett and a guitar.

What it can't do (yet)

It's not finished, and I'd rather say so up front:

Next: a much lighter Omarchy plugin

The tuner showed the better shape. Fretwise is a bar widget and a QML panel with a tiny standard-library Python process behind it, installed with one command. GuitarMood should be the same: the bank and scene in the bar, the board in a panel drawn by the shell that's already running, and a listing in the Omarchy marketplace. Then turning the rig on is clicking an icon in the bar. Let's make Omarchy the best OS for musicians.

Try it

Everything is on main of github.com/saada/fcb1010-biasfx2 (MIT). On Omarchy:

sudo pacman -S --needed uv qtractor guitarix \
  x42-plugins-lv2 lsp-plugins-lv2
omarchy plugin add \
  https://github.com/WayneKruger/omarchy-fretwise.git
git clone \
  https://github.com/saada/fcb1010-biasfx2
cd fcb1010-biasfx2
./bootstrap.sh ~/Downloads/TONE3000-*.tar.gz
uv run rig.py send   # the FCB layout, once
uv run --project guitarmood \
  guitarmood install

First set your audio interface and MIDI port at the top of tone3000.py, turn your monitor knob down, and grab the TONE3000 download from tone3000.com/plugin. The README has the details.

Then every time you play: SUPER+SPACE → GuitarMood. SW10 to tune. Pick a bank with UP/DOWN and press RHYTHM, CLEAN or ACOUSTIC to load the era. Play. SUPER+W when you're done. The one-time setup (FCB upload, real time) and the Maiden song recipes are in the README's practice guide.

Last time I pair-programmed with Claude Fable. This time, all of it, from the reverse-engineered preset format to GuitarMood and this write-up, I built with Claude Code running Claude Opus 5.5. Claude wrote the code and ran the research agents and every measurement. I played, listened, and said "bro, wtf is this ugly tuner" when it was needed.

The point: practice

Here's what changed. The setup is done, and it stays done. I sit down, open GuitarMood, tune with SW10, pick an era and play along with the record. A Saturday goes to the Rime of the Ancient Mariner solo, not to MIDI menus. The next step is the one the pedalboard was always for: playing this for people, with a screen that tells me exactly where I am.

There's never been a better time to be a musician on Linux: the OS gets out of the way, an agent builds the rig, and I practice. Up the Irons.

The evidence

Receipts

That's the story. If you'd rather check than take my word for it, good: every claim above was measured, not vibed. Here are the strongest ones, each linked to its experiment in the lab notes right below:

Lab notes

This is the deep end. The story above is the short version; what follows is the whole thing, unabridged, the way it happened: every experiment with its question, method and result, all 24 charts with their data, the CSVs, all fifteen song presets with every capture, the seven Maiden eras and the full pedal map. It walks the same five acts again, in full detail. Read it straight through, or jump in:

Act one · leaving Wine

10 ms on paper, mud in my hands

BIAS FX 2 under Wine claimed 10 ms and felt like a swimming pool.

BIAS FX 2 is a Windows/macOS app, so on Linux it runs under Wine. And it did run! Presets loaded, the FCB1010 switched them, the tones sounded like my tones. The app even told me my latency was about 10 ms, which is perfectly playable.

It did not feel like 10 ms. It felt like playing through a swimming pool.

So I did what you do. Dropped PipeWire's quantum from 1024 to 128. Built WineASIO and registered it by hand at a fixed 128-sample buffer (Wine 11 doesn't ship a separate wine64 anymore, so even registering the driver was a side quest). Switched BIAS to its "Low" latency mode. Gave myself realtime privileges. Better. Still not tight enough to play comfortably.

The number in the app only covers the app's own buffer. Under Wine, the audio then goes through a WASAPI/ASIO shim, into a PulseAudio/JACK bridge, into PipeWire. Every layer brings its own buffering, and none of it shows up in that friendly "10 ms". Skill issue? Maybe. Architecture issue? Definitely.

Enter TONE3000

A native, MIT-licensed NAM player, tighter at 256 samples than BIAS at 128.

Then I found out TONE3000 had shipped a native Linux plugin.

If you haven't run into it: TONE3000 is a huge, free, community catalog of Neural Amp Modeler (NAM) captures. Somebody puts a real amp, pedal or full rig through a neural network until the model is basically the box, and uploads it. Next to those sit impulse responses (IRs) for cabinets, rooms, plates and springs. There are captures of a 1972 Hiwatt DR103. Of John Mayer's actual Two-Rock prototype. Of a Gallien-Krueger 2000CPL, the preamp in the rack Adrian Smith used on Seventh Son. All free to load, with a free account. The captures belong to the community that made them, shared under TONE3000's tone-sharing policy.

And now there's a native Linux player for all of it: a standalone app and a plugin, talking straight to JACK/PipeWire, no Wine anywhere. It's version 0.0.9, it's free, and, as I only found out while writing this post, it's open source: the plugin's code is on GitHub under the MIT license. I was not ready for how good it sounds.

The first chords through it at a 256-sample buffer felt better than BIAS FX under Wine at a nominal 128. That settled it: the rig was moving.

MIDIFCB1010SW1–10 · EXP A/BUSB-MIDI cableALSA client 24TONE3000 standalone (native)ALSA seq port 128:1 → MidiMapperPC 0–14, CC 20–24AUDIOGuitar (JP70)humbuckersScarlett 2i2Input 2 · InstPipeWire graph (pipewire-jack)quantum 256 samples = 5.3 msJACK client: guitar in,stereo outSpotify · YouTubeapps at 100%Scarlett outsink 100% · knobNAM input calibration: the Scarlettinstrument input clips at +12.5 dBu(gain knob at minimum)

The whole rig: MIDI from the floor into TONE3000's ALSA sequencer port, audio through the Scarlett into PipeWire, where TONE3000 is just another JACK client.

No API? No problem. Again.

The T3KB preset format, reverse-engineered; toggles flip on any value ≥ 64.

Same problem as last time, though. I did not want to click together fifteen presets and MIDI-learn every switch by hand. I wanted the rig as code. TONE3000 has no scripting API. What it has is files:

  • ~/.config/TONE3000/Presets/<id>.t3kpreset: four magic bytes, T3KB, then a JUCE ValueTree in its binary form. Every block embeds the tone's catalog JSON and the raw .nam or .wav bytes, so a preset is fully self-contained.
  • Presets/order.json: a JSON array of preset ids. Program Change N loads the Nth entry. That's the entire PC mapping.
  • TONE3000.settings: the standalone's XML settings. The interesting bit is filterState, the plugin state in JUCE's own flavour of base64 (a little-endian bit stream whose alphabet starts with a dot, because of course it does). Inside it lives the MidiMappings tree.

JUCE ValueTrees are delightfully simple once you've seen one: a NUL-terminated type name, a count of properties, each one a name plus a typed value, then the children, recursively. The whole reader fits on a screen:

def read_tree(b, i):
    j = b.index(0, i); name = b[i:j].decode(); i = j + 1     # type name, NUL-terminated
    n, i = read_cint(b, i)                                     # property count
    props = {}
    for _ in range(n):
        j = b.index(0, i); k = b[i:j].decode(); i = j + 1
        props[k], i = read_var(b, i)                           # typed var: int/bool/double/str/binary...
    n, i = read_cint(b, i)                                     # child count
    kids = []
    for _ in range(n):
        c, i = read_tree(b, i)                                 # and recurse
        kids.append(c)
    return Node(name, props, kids), i

def load_t3kb(data):
    assert data[:4] == b"T3KB", "not a TONE3000 file"
    node, end = read_tree(data, 4)
    assert end == len(data)
    return node

Round-trip that, and you can build a preset from scratch that the app can't tell from one you clicked together.

Then the MIDI mapper. The stock FCB1010 sends CC value 127 on every press of a toggle switch. Some apps treat that as "on, on, on, on" and you can never turn anything off. Rather than guess, we disassembled MidiMapper::applyEvent in the TONE3000 binary. (Had I known the source was public, I'd have read that instead.) For toggle targets, any value ≥ 64 flips the state and anything ≤ 63 is ignored. So 127, 127, 127 means on, off, on. Verified live: CC 23 flipped the Slipknot preset's TS808 off, and the release message was ignored. The pedal didn't need reprogramming at all. On BIAS FX 2 those same CCs mean wah / octaver / delay / distortion / tuner; on TONE3000 they now mean gate / spread / boost / drive / echo.

One catch: TONE3000 maps CCs to block positions ("Block 2 power"), globally, not to specific pedals. So every preset has to share one slot layout, and the floorboard layout falls out of that:

FCB1010 · same layout in every presetSW6CC 20noise gateSW7CC 21stereo spreadSW8CC 22slot 1 boostSW9CC 23slot 2 driveSW10CC 24echo L5 + R3SW1PC 0/5/10song presetSW2PC 1/6/11song presetSW3PC 2/7/12song presetSW4PC 3/8/13song presetSW5PC 4/9/14song presetEXP ACC 27trebleEXP BCC 7outputBanks 00 / 01 / 02 → SW1–5 send PC 0–4 / 5–9 / 10–14chain slotsslot 1boostslot 2driveslot 3ampslot 4cabslot 5echoslot 6+ambience

TONE3000 maps CCs to block positions, so every preset shares one slot layout. Gold = switchable from the floor. This is the day-one layout; act two reshuffles the top row, see the full pedal map.

Now one script owns the whole thing. tone3000.py build downloads the captures, writes all fifteen presets in PC order and the MIDI map. tone3000.py configure sets up audio, MIDI, NAM calibration and the Linux side. bootstrap.sh does all of it on a fresh machine. Quit TONE3000 first, though: the standalone rewrites its settings on exit, and it will happily undo your work.

Fifteen research agents walk into a studio

One agent per band, and the Maiden one overruled the internet.

This is where it got silly, in the best way.

My BIAS FX 2 presets were approximations built up over years of fiddling. TONE3000's catalog has captures of the actual gear. So why approximate?

With Claude Code I spun up fifteen research agents, one per song or band, all with the same brief: find the band's best-era record or live rig (who played what, through what, on which record), cite sources, search the TONE3000 catalog for captures of that exact gear, and write the preset as a JSON file, with notes on everything that had to be approximated. Presets are data now:

{
  "pc": 10,
  "name": "Iron Maiden",
  "reference": "The Evil That Men Do — Seventh Son of a Seventh Son (1988, Musicland Munich, prod./eng. Martin Birch)",
  "rig": "Dave Murray: Strat -> Gallien-Krueger 250ML-series preamp -> Marshall 4x12 (left). Adrian Smith: ...",
  "sources": ["https://www.guitarworld.com/gw-archive/steve-harris-and-dave-murray-iron-maiden-open-1988-guitar-world-interview", "..."],
  "notes": "1986-88 was Maiden's solid-state GK era for BOTH players ...",
  "left": [
    { "role": "boost", "type": "nam", "tone_id": 87914, "model_id": 742716, "enabled": false, "label": "Boss SD-1 as clean boost" },
    { "role": "drive", "type": "nam", "tone_id": 35412, "model_id": 381302, "enabled": false, "label": "MXR Distortion+" },
    { "role": "amp",   "type": "nam", "tone_id": 27127, "model_id": 379866, "label": "Gallien-Krueger 250ML (Murray)" },
    { "role": "cab",   "type": "ir",  "tone_id": 67526, "model_id": 463991, "label": "1987 Marshall 1960A G12T-75, MD421" },
    { "role": "echo",  "type": "echo", "delay_ms": 373, "feedback": 0.3, "cutoff_hz": 5000, "mix": 0.22, "enabled": false },
    ...
  ],
  "split_after": 2,
  "right": [ ... GK 2000CPL (Smith), same cab with an SM57, same echo, 480L plate ... ],
  "params": { "chainPanLeft": 0.0, "chainPanRight": 1.0, "alignWobbleEnabled": 1.0, "alignWobble": 0.3 }
}

What came back was better than anything I'd have dialed in myself:

  • Comfortably Numb: a real 1972 Hiwatt DR103 with its own Fane 4x12 as a full-rig capture, a Vick '73 Ram's Head Muff at sustain 7 (Gilmour didn't max it, the amp does part of the work), and an EMT 140 plate.
  • Purple Rain: Prince's Mesa Mark IIB run crystal clean. The IIC+ conversion only came for the 1984 tour, and no IIB capture exists, so a real 1984 IIC+ "Silver Zephyr" clean channel stands in. A DS-1 for the solo, just like on the record.
  • Tornado of Souls: Mustaine's JCM800 2203 at "every knob dimed" hard left, Marty Friedman's Bogner Triple Giant hard right.
  • Dream Theater: Awake's quad-tracked wall. A Mark IIC+ with the GEQ in Petrucci's V on one side, a 90s Dual Rectifier on the other.
  • Slipknot: Iowa-era Mick Thomson's VHT Pittbull into an IR billed as Thomson's Iowa Carvin cab, and Jim Root's 5150.
  • Radiohead: split after slot 1, so the ShredMaster hits only Jonny's Fender Eighty-Five on the left while Ed's clean AC30 stays shimmery on the right.
  • Iron Maiden: my favourite, because the agent overruled the internet. The common summary says Dave Murray = Marshall. The 1988 Guitar World interview and the gear histories say both guitarists moved to Gallien-Krueger for Somewhere in Time and Seventh Son. Solid-state GKs! On Seventh Son! So: a GK 250ML for Murray on the left, a GK 2000CPL (the preamp inside Smith's 2100SEL rack) on the right. I had no idea.

Plus Periphery (5150 and a Mark V in Mark IV mode), OK Computer, Morning Glory (Bluesbreaker + AC30, with Rockfield's live room as a Lexicon 480L wood room), Nevermind (DS-1 into a Mesa Studio Preamp, doubled with a Bassman), The Colour and the Shape, Mayer's Two-Rock, Petrucci's cleans, an acoustic simulator and a blackface Twin. Ten of the fifteen came back as dual-rig stereo, because that's how those records were made: two guitarists, or two amps, hard-panned.

Tricks I didn't know a capture player could do

Dual stereo rigs, generated echo IRs, one switch for both echoes.

TONE3000 is, on paper, "load a capture, load an IR". In practice it has just enough routing to get very creative:

GuitarmonoL1 boostSD-1 · SW8L2 driveMXR D+ · SW9shared pedalsfeed both rigssplitLEFT · Dave MurrayGK 250MLL3 amp1960A T75L4 cab IR373 ms echoL5 · off480L plateL6 reverb IRRIGHT · Adrian SmithGK 2000CPLR1 amp1960A T75R2 cab IR373 ms echoR3 · off480L plateR4 reverb IRstereo outpan L 0 · R 1align wobble 0.3SW10 · CC 24two targets

A dual-rig preset (PC 10, Iron Maiden): shared pedals, a split, two amp/cab/echo/reverb chains panned hard left and right, and one footswitch driving both echoes.

  • Dual stereo chains with a shared pedal split. Two full amp chains, and the split point is per preset. Pedals before it feed both rigs; everything after is per side, panned wherever you like.
  • Align and Spread wobble as chorus. TONE3000 can't modulate anything. But Align (time alignment between the two rigs) and Spread (stereo widening) both have a wobble. A slow wobble between two rigs is close enough to the GK's built-in stereo chorus, a TC 2290 or a Small Clone to make me smile.
  • Generated BBD echo IRs. There's no delay block either. But a delay with a fixed time is just an impulse response, so the builder synthesizes one, Boss DM-2 style:
def analog_echo(delay_ms=300.0, feedback=0.6, repeats=8, cutoff_hz=2800.0, rate=48000):
    """Wet-only IR of an analog (BBD) delay like a Boss DM-2: each repeat passes the
    bucket-brigade's low-pass again, so the echoes darken as they decay."""
  • Catalog reverb IRs. An EMT 140 plate emulation of the one at Sunset Sound (where Purple Rain was mixed), a Lexicon 480L plate for Martin Birch's desk plate, a 480L wood room for Rockfield, the Fender Twin's own spring tank, and an MIT-measured hotel ballroom standing in for St Catherine's Court.
  • NAM input calibration. The Scarlett's instrument input clips at +12.5 dBu with the gain knob at minimum. Tell NAM that, and each capture gets hit at the level the original amp saw.
  • Per-block pre-EQ that turns humbuckers into single coils. My JP70 has humbuckers; plenty of these records were made with single coils. Six bands of EQ before the amp model ([-3, -2.5, 0, 1.5, 3, 2] dB at 100 Hz / 250 / 650 / 1.6k / 3.5k / 8k) trim the mud and add the glass.
  • One CC, two targets. On the day-one board, SW10 toggled the echo on both sides of a dual rig at once. We mapped CC 24 to two targets, sent one CC, and read back the saved state: both flipped, and the app kept both mappings. (Act two takes this further: LEAD is one CC on three targets, the boost plus both echoes. And SW10 ends up as the tuner.)

The case of the silent IRs

Odd-length and non-48 kHz WAVs load as silence.

Building all of this surfaced two bugs in v0.0.9, both of the worst kind: nothing crashes, the IR just loads as silence. The log says IR prepared: 1 ch, 0.00 s and your lovely plate reverb does absolutely nothing.

Bug one: IRs that aren't 48 kHz. The Lexicon 480L plate was a 44.1 kHz file and loaded as 0.00 s. Resampled to 48 kHz, it loaded as 3.13 s. Easy: the builder now resamples everything.

Bug two was sneakier. After resampling, 9 of the 53 IR instances across five presets were still silent. Hypothesis: RIFF word alignment. A WAV chunk with an odd byte length is supposed to be followed by a pad byte, and 24-bit mono audio with an odd number of frames produces exactly that. So we tabulated the data-chunk parity of every IR instance in the rig:

Before: 53 IR instances across 15 presets, grouped by data-chunk parityodd length · 9 of 9 silent ✕even length · 44 of 44 loaded ✓PCM90 INSTR Rich Plate (Dream Theater L6) · PCM16 stereo 44.1 kHz · data chunk 272889 bytes (odd) → reported 0.00 sPCM90 INSTR Rich Plate (Dream Theater R4) · PCM16 stereo 44.1 kHz · data chunk 272889 bytes (odd) → reported 0.00 sMesa Oversized V30 SM57 (Djent R2) · PCM24 mono 48 kHz · data chunk 2271 bytes (odd) → reported 0.00 s480L Large Wood Room (Oasis L6) · PCM16 stereo 44.1 kHz · data chunk 272535 bytes (odd) → reported 0.00 s480L Large Wood Room (Oasis R4) · PCM16 stereo 44.1 kHz · data chunk 272535 bytes (odd) → reported 0.00 sNevermind 4x12 DIST 1 (Nirvana L4) · PCM32 mono 44.1 kHz · data chunk 61803 bytes (odd) → reported 0.00 sNevermind 4x12 DIST 4 (Nirvana R2) · PCM32 mono 44.1 kHz · data chunk 61803 bytes (odd) → reported 0.00 sPCM91 Large Hall (Petrucci Clean L6) · PCM24 stereo 44.1 kHz · data chunk 497787 bytes (odd) → reported 0.00 sPCM91 Large Hall (Petrucci Clean R4) · PCM24 stereo 44.1 kHz · data chunk 497787 bytes (odd) → reported 0.00 seven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengtheven-length data chunk → loaded with a real lengthAfter: every IR written with an even frame count · 53 of 53 loaded ✓after the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedafter the fix: loadedloaded as silence (TONE3000 logs “IR prepared: 0.00 s”)loaded (real length reported)

The data

experimentIRsourcedata bytesparityreported sloaded
E1 sample rateLexicon 480L LargePlate (as downloaded)PCM16 stereo 44.1 kHz0.00no
E1 sample rateLexicon 480L LargePlate (resampled)PCM16 stereo 44.1 kHz494604even3.13yes
E1 sample rateSunset Sound EMT 140 plate (trimmed)PCM24 4ch 48 kHz720000even4.17yes
E2 odd data chunkPCM90 INSTR Rich Plate (Dream Theater L6)PCM16 stereo 44.1 kHz272889odd0.00no
E2 odd data chunkPCM90 INSTR Rich Plate (Dream Theater R4)PCM16 stereo 44.1 kHz272889odd0.00no
E2 odd data chunkMesa Oversized V30 SM57 (Djent R2)PCM24 mono 48 kHz2271odd0.00no
E2 odd data chunk480L Large Wood Room (Oasis L6)PCM16 stereo 44.1 kHz272535odd0.00no
E2 odd data chunk480L Large Wood Room (Oasis R4)PCM16 stereo 44.1 kHz272535odd0.00no
E2 odd data chunkNevermind 4x12 DIST 1 (Nirvana L4)PCM32 mono 44.1 kHz61803odd0.00no
E2 odd data chunkNevermind 4x12 DIST 4 (Nirvana R2)PCM32 mono 44.1 kHz61803odd0.00no
E2 odd data chunkPCM91 Large Hall (Petrucci Clean L6)PCM24 stereo 44.1 kHz497787odd0.00no
E2 odd data chunkPCM91 Large Hall (Petrucci Clean R4)PCM24 stereo 44.1 kHz497787odd0.00no
E2 odd data chunkall 44 other IR instances across 15 presetsmixedeven>0yes
E2 fixall 53 IR instances after padding to even frame countmixedeven>0yes

Every IR instance in the 15 presets (n = 53), grouped by the parity of its WAV data chunk. Hover a square for the file. Data: ir-loading.csv.

Nine odd, nine silent. Forty-four even, forty-four loaded. That includes a 6 KB Mesa 4x12 IR straight from the catalog with a 2,271-byte data chunk, so it wasn't my encoder. The fix is one line:

def wav_write(samples, rate=48000):
    """Mono 24-bit PCM WAV. Always an even number of frames: TONE3000 loads a WAV
    whose data chunk has an odd byte length as silence."""
    samples = list(samples) + ([0.0] if len(samples) % 2 else [])
    ...

Result: 15 of 15 presets, 53 of 53 IRs, zero silent.

Latency, measured instead of vibed

39 runs: 256 samples with multi-core on means 0 xruns.

After the BIAS FX experience, I didn't trust any latency number I hadn't measured. So we measured.

Test bench: ThinkPad X1 Yoga Gen 8 (i7-1365U) on AC, PipeWire 1.6 with pipewire-jack, Focusrite Scarlett 2i2 3rd Gen, TONE3000 v0.0.9 standalone at 48 kHz. The metrics come from pw-top, sampled once a second on the TONE3000 node: B/Q (busy time ÷ cycle period, the share of each audio cycle spent processing) and xruns (cycles where the node missed its deadline; every one is an audible click). Program changes and CCs were injected with aseqsend, so every run is repeatable without touching the pedal.

L1. Hypothesis: at ~11% DSP load there's plenty of headroom for a 64-sample buffer and 4× or 8× oversampling. Rejected. 4× oversampling ran at a lower average load than 2× and still dropped out.

L2. The researched presets (dual rigs, 3–5 second echo and reverb IRs) started crackling at 128 samples. One variable at a time: process priority? Not it. Multi-core off? 104 to 352 xruns a minute, so multi-core is essential. Oversampling off helps a little. Removing the long IRs helps but isn't the root cause. Peak B/Q never went above 0.66, so this wasn't a CPU problem, it was scheduling headroom. (My session also didn't have the realtime group yet, and another app was streaming 44.1 kHz audio into the 48 kHz graph, which has to be resampled.)

L3. Sweep the buffer. 192 samples was worse than 256, with 26 xruns a minute, most likely because non-power-of-two quanta don't line up with the USB transfer size. 256 samples, oversampling off, multi-core on: zero xruns across 180 seconds on the heaviest dual rigs.

L1 · v1 single-rig preset (Slipknot), buffer and oversampling sweep128 samples · OS off128 samples · OS off: worst 0 xruns/min · 0 xruns in 5 s over 1 run(s) · Slipknot0 1 run, 5 s128 samples · OS 2×128 samples · OS 2×: worst 0 xruns/min · 0 xruns in 85 s over 3 run(s) · Slipknot0 3 runs, 85 s128 samples · OS 4×128 samples · OS 4×: worst 6 xruns/min · 2 xruns in 20 s over 1 run(s) · Slipknot6 1 run, 20 s128 samples · OS 8×128 samples · OS 8×: worst 12 xruns/min · 3 xruns in 15 s over 1 run(s) · Slipknot12 1 run, 15 s64 samples · OS off64 samples · OS off: worst 4 xruns/min · 4 xruns in 90 s over 2 run(s) · Slipknot4 2 runs, 90 s64 samples · OS 2×64 samples · OS 2×: worst 24 xruns/min · 12 xruns in 30 s over 1 run(s) · Slipknot24 1 run, 30 sL2 · v2 researched rigs at 128 samples, one change at a timev2 rigs · OS 2× · nice 5v2 rigs · OS 2× · nice 5: worst 14 xruns/min · 25 xruns in 180 s over 6 run(s) · Acoustic, Comfortably Numb, Iron Maiden, Petrucci Clean, Radiohead14 6 runs, 180 slaunched at nice 0launched at nice 0: worst 32 xruns/min · 39 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean32 3 runs, 90 smulti-core OFFmulti-core OFF: worst 352 xruns/min · 370 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Cleanmulti-core OFF: worst 352 xruns/min · 370 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean352 3 runs, 90 soversampling offoversampling off: worst 18 xruns/min · 25 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean18 3 runs, 90 slong IRs removedlong IRs removed: worst 8 xruns/min · 10 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean8 3 runs, 90 s0 dB out, −60 dB gate, tails trimmed0 dB out, −60 dB gate, tails trimmed: worst 42 xruns/min · 61 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean42 3 runs, 90 sL3 · buffer sweep, v2 rigs, oversampling off128 samples (2.7 ms)128 samples (2.7 ms): worst 42 xruns/min · 61 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean42 3 runs, 90 s192 samples (4.0 ms)192 samples (4.0 ms): worst 26 xruns/min · 39 xruns in 90 s over 3 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean26 3 runs, 90 s256 samples (5.3 ms)256 samples (5.3 ms): worst 0 xruns/min · 0 xruns in 180 s over 4 run(s) · Comfortably Numb, Iron Maiden, Petrucci Clean, Radiohead0 4 runs, 180 s ← final setting256 samples · OS 2×256 samples · OS 2×: worst 2 xruns/min · 4 xruns in 120 s over 2 run(s) · Iron Maiden, Petrucci Clean2 2 runs, 120 s0102030405060xruns per minute (worst preset in the group; lower is better)

The data

runbufferOSmulti-corepresetsB/Q avgB/Q maxxruns/minnote
1128offonSlipknot50.110.110first launch; ~290us busy per 2667us cycle
21282xonSlipknot50.200.2302x oversampling first look
31284xonSlipknot200.170.206.0
41288xonSlipknot150.240.3012.0
51282xonSlipknot200.110.270
6642xonSlipknot300.120.1924.0
764offonSlipknot300.140.230
864offonSlipknot600.160.304.0longer run
91282xonSlipknot600.180.300chosen config for v1
101282xonPetrucci Clean300.250.636.0dual rig
111282xonIron Maiden300.170.246.0dual rig
121282xonRadiohead300.210.466.0dual rig
131282xonComfortably Numb300.170.268.0single rig
141282xonAcoustic300.180.2510.0single rig
151282xonPetrucci Clean300.240.3814.0repeat
161282xonComfortably Numb300.180.2914.0launched via systemd (nice 0)
171282xonPetrucci Clean300.240.3032.0
181282xonIron Maiden300.150.2032.0
191282xoffComfortably Numb300.360.53104.0everything on the audio thread
201282xoffPetrucci Clean300.440.62284.0
211282xoffIron Maiden300.340.51352.0
22128offonComfortably Numb300.200.2516.0
23128offonPetrucci Clean300.230.5416.0
24128offonIron Maiden300.150.1918.0
25128offonComfortably Numb300.140.286.0echo + reverb IRs removed (diagnostic build)
26128offonPetrucci Clean300.190.266.0
27128offonIron Maiden300.110.138.0
28128offonComfortably Numb300.210.3140.0output 0 dB; gate -60 dB; echo IR tails trimmed
29128offonPetrucci Clean300.210.2540.0
30128offonIron Maiden300.140.1942.0
31256offonComfortably Numb300.170.220
32256offonPetrucci Clean300.230.460
33192offonComfortably Numb300.200.3226.0non power-of-two
34192offonPetrucci Clean300.230.4526.0
35192offonIron Maiden300.160.4526.0
36256offonIron Maiden600.110.220final config
37256offonRadiohead600.170.290final config
382562xonPetrucci Clean600.270.662.0
392562xonIron Maiden600.190.462.0

Xruns per minute from pw-top (1 Hz samples on the TONE3000 node), worst preset per configuration, with the number of runs and total seconds behind each bar. 39 runs, 5–60 s each. Data: latency-xruns.csv.

And here's the chart that changed how I think about this. If xruns tracked CPU, these dots would climb from left to right. They don't:

051025501002003500.00.10.20.30.40.50.60.7average DSP load (B/Q)run 19 · Comfortably Numb · 128 samples · OS 2x · multi-core off · B/Q avg 0.36 / max 0.53 · 104.0 xruns/min (30 s)run 20 · Petrucci Clean · 128 samples · OS 2x · multi-core off · B/Q avg 0.44 / max 0.62 · 284.0 xruns/min (30 s)run 21 · Iron Maiden · 128 samples · OS 2x · multi-core off · B/Q avg 0.34 / max 0.51 · 352.0 xruns/min (30 s)run 1 · Slipknot · 128 samples · OS off · multi-core on · B/Q avg 0.11 / max 0.11 · 0 xruns/min (5 s)run 2 · Slipknot · 128 samples · OS 2x · multi-core on · B/Q avg 0.20 / max 0.23 · 0 xruns/min (5 s)run 3 · Slipknot · 128 samples · OS 4x · multi-core on · B/Q avg 0.17 / max 0.20 · 6.0 xruns/min (20 s)run 4 · Slipknot · 128 samples · OS 8x · multi-core on · B/Q avg 0.24 / max 0.30 · 12.0 xruns/min (15 s)run 5 · Slipknot · 128 samples · OS 2x · multi-core on · B/Q avg 0.11 / max 0.27 · 0 xruns/min (20 s)run 6 · Slipknot · 64 samples · OS 2x · multi-core on · B/Q avg 0.12 / max 0.19 · 24.0 xruns/min (30 s)run 7 · Slipknot · 64 samples · OS off · multi-core on · B/Q avg 0.14 / max 0.23 · 0 xruns/min (30 s)run 8 · Slipknot · 64 samples · OS off · multi-core on · B/Q avg 0.16 / max 0.30 · 4.0 xruns/min (60 s)run 9 · Slipknot · 128 samples · OS 2x · multi-core on · B/Q avg 0.18 / max 0.30 · 0 xruns/min (60 s)run 10 · Petrucci Clean · 128 samples · OS 2x · multi-core on · B/Q avg 0.25 / max 0.63 · 6.0 xruns/min (30 s)run 11 · Iron Maiden · 128 samples · OS 2x · multi-core on · B/Q avg 0.17 / max 0.24 · 6.0 xruns/min (30 s)run 12 · Radiohead · 128 samples · OS 2x · multi-core on · B/Q avg 0.21 / max 0.46 · 6.0 xruns/min (30 s)run 13 · Comfortably Numb · 128 samples · OS 2x · multi-core on · B/Q avg 0.17 / max 0.26 · 8.0 xruns/min (30 s)run 14 · Acoustic · 128 samples · OS 2x · multi-core on · B/Q avg 0.18 / max 0.25 · 10.0 xruns/min (30 s)run 15 · Petrucci Clean · 128 samples · OS 2x · multi-core on · B/Q avg 0.24 / max 0.38 · 14.0 xruns/min (30 s)run 16 · Comfortably Numb · 128 samples · OS 2x · multi-core on · B/Q avg 0.18 / max 0.29 · 14.0 xruns/min (30 s)run 17 · Petrucci Clean · 128 samples · OS 2x · multi-core on · B/Q avg 0.24 / max 0.30 · 32.0 xruns/min (30 s)run 18 · Iron Maiden · 128 samples · OS 2x · multi-core on · B/Q avg 0.15 / max 0.20 · 32.0 xruns/min (30 s)run 22 · Comfortably Numb · 128 samples · OS off · multi-core on · B/Q avg 0.20 / max 0.25 · 16.0 xruns/min (30 s)run 23 · Petrucci Clean · 128 samples · OS off · multi-core on · B/Q avg 0.23 / max 0.54 · 16.0 xruns/min (30 s)run 24 · Iron Maiden · 128 samples · OS off · multi-core on · B/Q avg 0.15 / max 0.19 · 18.0 xruns/min (30 s)run 25 · Comfortably Numb · 128 samples · OS off · multi-core on · B/Q avg 0.14 / max 0.28 · 6.0 xruns/min (30 s)run 26 · Petrucci Clean · 128 samples · OS off · multi-core on · B/Q avg 0.19 / max 0.26 · 6.0 xruns/min (30 s)run 27 · Iron Maiden · 128 samples · OS off · multi-core on · B/Q avg 0.11 / max 0.13 · 8.0 xruns/min (30 s)run 28 · Comfortably Numb · 128 samples · OS off · multi-core on · B/Q avg 0.21 / max 0.31 · 40.0 xruns/min (30 s)run 29 · Petrucci Clean · 128 samples · OS off · multi-core on · B/Q avg 0.21 / max 0.25 · 40.0 xruns/min (30 s)run 30 · Iron Maiden · 128 samples · OS off · multi-core on · B/Q avg 0.14 / max 0.19 · 42.0 xruns/min (30 s)run 33 · Comfortably Numb · 192 samples · OS off · multi-core on · B/Q avg 0.20 / max 0.32 · 26.0 xruns/min (30 s)run 34 · Petrucci Clean · 192 samples · OS off · multi-core on · B/Q avg 0.23 / max 0.45 · 26.0 xruns/min (30 s)run 35 · Iron Maiden · 192 samples · OS off · multi-core on · B/Q avg 0.16 / max 0.45 · 26.0 xruns/min (30 s)run 31 · Comfortably Numb · 256 samples · OS off · multi-core on · B/Q avg 0.17 / max 0.22 · 0 xruns/min (30 s)run 32 · Petrucci Clean · 256 samples · OS off · multi-core on · B/Q avg 0.23 / max 0.46 · 0 xruns/min (30 s)run 36 · Iron Maiden · 256 samples · OS off · multi-core on · B/Q avg 0.11 / max 0.22 · 0 xruns/min (60 s)run 37 · Radiohead · 256 samples · OS off · multi-core on · B/Q avg 0.17 / max 0.29 · 0 xruns/min (60 s)run 38 · Petrucci Clean · 256 samples · OS 2x · multi-core on · B/Q avg 0.27 / max 0.66 · 2.0 xruns/min (60 s)run 39 · Iron Maiden · 256 samples · OS 2x · multi-core on · B/Q avg 0.19 / max 0.46 · 2.0 xruns/min (60 s)0.00.10.20.30.40.50.60.7peak DSP load (B/Q)run 19 · Comfortably Numb · 128 samples · OS 2x · multi-core off · B/Q avg 0.36 / max 0.53 · 104.0 xruns/min (30 s)run 20 · Petrucci Clean · 128 samples · OS 2x · multi-core off · B/Q avg 0.44 / max 0.62 · 284.0 xruns/min (30 s)run 21 · Iron Maiden · 128 samples · OS 2x · multi-core off · B/Q avg 0.34 / max 0.51 · 352.0 xruns/min (30 s)run 1 · Slipknot · 128 samples · OS off · multi-core on · B/Q avg 0.11 / max 0.11 · 0 xruns/min (5 s)run 2 · Slipknot · 128 samples · OS 2x · multi-core on · B/Q avg 0.20 / max 0.23 · 0 xruns/min (5 s)run 3 · Slipknot · 128 samples · OS 4x · multi-core on · B/Q avg 0.17 / max 0.20 · 6.0 xruns/min (20 s)run 4 · Slipknot · 128 samples · OS 8x · multi-core on · B/Q avg 0.24 / max 0.30 · 12.0 xruns/min (15 s)run 5 · Slipknot · 128 samples · OS 2x · multi-core on · B/Q avg 0.11 / max 0.27 · 0 xruns/min (20 s)run 6 · Slipknot · 64 samples · OS 2x · multi-core on · B/Q avg 0.12 / max 0.19 · 24.0 xruns/min (30 s)run 7 · Slipknot · 64 samples · OS off · multi-core on · B/Q avg 0.14 / max 0.23 · 0 xruns/min (30 s)run 8 · Slipknot · 64 samples · OS off · multi-core on · B/Q avg 0.16 / max 0.30 · 4.0 xruns/min (60 s)run 9 · Slipknot · 128 samples · OS 2x · multi-core on · B/Q avg 0.18 / max 0.30 · 0 xruns/min (60 s)run 10 · Petrucci Clean · 128 samples · OS 2x · multi-core on · B/Q avg 0.25 / max 0.63 · 6.0 xruns/min (30 s)run 11 · Iron Maiden · 128 samples · OS 2x · multi-core on · B/Q avg 0.17 / max 0.24 · 6.0 xruns/min (30 s)run 12 · Radiohead · 128 samples · OS 2x · multi-core on · B/Q avg 0.21 / max 0.46 · 6.0 xruns/min (30 s)run 13 · Comfortably Numb · 128 samples · OS 2x · multi-core on · B/Q avg 0.17 / max 0.26 · 8.0 xruns/min (30 s)run 14 · Acoustic · 128 samples · OS 2x · multi-core on · B/Q avg 0.18 / max 0.25 · 10.0 xruns/min (30 s)run 15 · Petrucci Clean · 128 samples · OS 2x · multi-core on · B/Q avg 0.24 / max 0.38 · 14.0 xruns/min (30 s)run 16 · Comfortably Numb · 128 samples · OS 2x · multi-core on · B/Q avg 0.18 / max 0.29 · 14.0 xruns/min (30 s)run 17 · Petrucci Clean · 128 samples · OS 2x · multi-core on · B/Q avg 0.24 / max 0.30 · 32.0 xruns/min (30 s)run 18 · Iron Maiden · 128 samples · OS 2x · multi-core on · B/Q avg 0.15 / max 0.20 · 32.0 xruns/min (30 s)run 22 · Comfortably Numb · 128 samples · OS off · multi-core on · B/Q avg 0.20 / max 0.25 · 16.0 xruns/min (30 s)run 23 · Petrucci Clean · 128 samples · OS off · multi-core on · B/Q avg 0.23 / max 0.54 · 16.0 xruns/min (30 s)run 24 · Iron Maiden · 128 samples · OS off · multi-core on · B/Q avg 0.15 / max 0.19 · 18.0 xruns/min (30 s)run 25 · Comfortably Numb · 128 samples · OS off · multi-core on · B/Q avg 0.14 / max 0.28 · 6.0 xruns/min (30 s)run 26 · Petrucci Clean · 128 samples · OS off · multi-core on · B/Q avg 0.19 / max 0.26 · 6.0 xruns/min (30 s)run 27 · Iron Maiden · 128 samples · OS off · multi-core on · B/Q avg 0.11 / max 0.13 · 8.0 xruns/min (30 s)run 28 · Comfortably Numb · 128 samples · OS off · multi-core on · B/Q avg 0.21 / max 0.31 · 40.0 xruns/min (30 s)run 29 · Petrucci Clean · 128 samples · OS off · multi-core on · B/Q avg 0.21 / max 0.25 · 40.0 xruns/min (30 s)run 30 · Iron Maiden · 128 samples · OS off · multi-core on · B/Q avg 0.14 / max 0.19 · 42.0 xruns/min (30 s)run 33 · Comfortably Numb · 192 samples · OS off · multi-core on · B/Q avg 0.20 / max 0.32 · 26.0 xruns/min (30 s)run 34 · Petrucci Clean · 192 samples · OS off · multi-core on · B/Q avg 0.23 / max 0.45 · 26.0 xruns/min (30 s)run 35 · Iron Maiden · 192 samples · OS off · multi-core on · B/Q avg 0.16 / max 0.45 · 26.0 xruns/min (30 s)run 31 · Comfortably Numb · 256 samples · OS off · multi-core on · B/Q avg 0.17 / max 0.22 · 0 xruns/min (30 s)run 32 · Petrucci Clean · 256 samples · OS off · multi-core on · B/Q avg 0.23 / max 0.46 · 0 xruns/min (30 s)run 36 · Iron Maiden · 256 samples · OS off · multi-core on · B/Q avg 0.11 / max 0.22 · 0 xruns/min (60 s)run 37 · Radiohead · 256 samples · OS off · multi-core on · B/Q avg 0.17 / max 0.29 · 0 xruns/min (60 s)run 38 · Petrucci Clean · 256 samples · OS 2x · multi-core on · B/Q avg 0.27 / max 0.66 · 2.0 xruns/min (60 s)run 39 · Iron Maiden · 256 samples · OS 2x · multi-core on · B/Q avg 0.19 / max 0.46 · 2.0 xruns/min (60 s)xruns / min← same 0.10–0.25 averageload: 0 to 42 xruns/minmulti-core off0.46 peak, 0 xruns(256 samples)0.19 peak, 42 xruns/min(128 samples)64 / 128 samples192 samples256 samples128, multi-core off

All 39 runs: DSP load (B/Q = busy time ÷ cycle period) against xruns per minute, on a square-root scale so 0–40 stays readable next to 352. Hover a dot for the run.

At the same 0.10–0.25 average load you get anything from 0 to 42 xruns a minute. The buffer size decides, not the load. Even peak load doesn't predict it: a 256-sample run peaked at 0.46 with zero xruns, while a 128-sample run peaked at 0.19 and clicked 42 times a minute. Xruns are about bursty per-cycle work and scheduling jitter, not the mean.

SettingValueWhy
AudioJACK (pipewire-jack), 48 kHz / 256 samples (5.3 ms)0 xruns on the stereo rigs; 128 crackled (L2–L3)
Oversamplingoff2× / 4× / 8× added xruns (L1, L3)
Multi-coreonoff = 104–352 xruns/min (L2)
NAM sizeA2-Fullhighest accuracy
InputScarlett Input 2, mono, calibration 12.5 dBucalibration assumes the gain knob at minimum
Output+14 dBsee the Great Reset below
Gateon, −60 dB−35 dB chopped note decays (L4)

The final settings, from TONE3000.md. Every experiment is written up as question → hypothesis → method → result → conclusion in experiments/README.md.

The Great Reset

A Linux volume slider was stealing 24 dB.

With the latency solved, one problem was left: everything was quiet. Not broken. Just small. I had to run my monitors at 50% to get a sound I'd call "playing guitar".

And I'd already been "fixing" that all day. First I cranked TONE3000's output to +24 dB and set the noise gate to −35 dB in every preset. The +24 dB hard-clipped the interface, because TONE3000 normalizes every capture to around −18 dB loudness, and the −35 dB gate chopped the tails off my notes. So: gate back to −60 dB, and a real measurement against Spotify, which said my guitar sat about 15 dB under the music. Split the difference: guitar +14 dB, Spotify −3 dB. The numbers looked great. The room still sounded quiet.

Patches on patches. Time to stop patching and do it properly.

// the principle: gain staging

Every digital stage at unity (0 dB, 100%). Sources matched to a common loudness reference. And exactly one volume control in the whole chain: the hardware knob on the interface.

Safety first. Before touching anything, I turned the Scarlett's Monitor knob all the way down. The 2i2 3rd Gen's knob has no numbers, it just runs from about 7 o'clock to 5 o'clock, so "all the way down" means fully counter-clockwise. You'll see why in a second: +24 dB was about to arrive, and my ears and my monitors would like to keep existing.

Then the audit, stage by stage, from the top:

  • Hardware. Monitor knob at 50%. Suspicious, but it was only compensating for something.
  • Linux. The PipeWire Scarlett sink was at "40%". That looks harmless until you remember PipeWire volumes are cubic: 0.4³ = 0.064 linear, which is −24 dB of software attenuation on everything. TONE3000, Spotify, YouTube, all of it. There's the culprit.
  • Interface (the ALSA scarlett2 controls). Input 2 on Inst ✓. Direct Monitor off ✓. Phantom power on, but that only lives on the XLR pins, harmless. And Air on ✗: an analog presence boost before NAM, coloring the DI every capture expects to hear. Off it goes.
  • Apps. Spotify on this machine plays through a librespot-based backend that hardcodes its player config, so loudness normalization can't be turned on without a rebuild. WirePlumber had also remembered the −3 dB from my earlier "fix", filed under a blank app name. Back to 100%. YouTube in Chromium at 100% makes a great reference, because YouTube normalizes everything to about −14 LUFS.
  • TONE3000. The output fader, still at +14 dB.
STAGEBEFOREAFTER THE RESETScarlett Input 2ALSA scarlett2 controls✓Inst✕Air ON (analog presence boost)✓Direct Monitor off✓Inst✓Air off: flat DI for NAM✓Direct Monitor offTONE3000 inputNAM input calibration✓12.5 dBu, gain knob at minimum✓12.5 dBu, unchangedTONE3000 output faderglobal, all presets✕+24 dB (clipped), then +14 dB✓+14 dB: right all alongSpotify · YouTubejoin the mix at the sink✕Spotify stream −3 dB, keptby WirePlumber (blank app name)✓every app at 100%YouTube ≈ −14 LUFS: referencePipeWire Scarlett sinksoftware volume, cubic scale✕40% = 0.064 linear= −24 dB on EVERYTHING✓100% = unity, 0 dBScarlett Monitor knobhardware, ~7 to 5 o'clock✕at 50% to compensate✓fully down first, thenthe ONLY volume controlMonitors / headphones✕quiet✓guitar ≈2.5 dB over music✕ not at unity, or hiding a patch ✓ unity, or set on purpose

The knob map: every gain stage between my guitar and my ears, before and after the reset.

Then: measurement, not ears. We captured TONE3000's output and the YouTube stream side by side (two pw-record nodes, each linked by port id) while I played along. The guitar came in at −16.3 dBFS RMS with peaks at −5.4, against YouTube at −18.9 RMS (−14.3 at its loudest). That's the guitar about 2.5 dB over the music with 5 dB of headroom. Exactly where you want it for playing along.

-35-30-25-20-15-10-500 dBFS = clippinglevel in dBFS · dot = active RMS (400 ms windows) · tick = peak · 20 s per capture1 · Start: TONE3000 out 0 dB, Spotify 0 dBSpotify, step 1: RMS -16.0 dBFS, peak -7.8 dBFS (20 s)SpotifyGuitar, step 1: RMS -30.6 dBFS, peak -17.9 dBFS (20 s)guitarguitar 14.6 dB under the music2 · The patch (L5): TONE3000 +14 dB, Spotify −3 dBSpotify, step 2, loud passages: ≈-19.0 dBFS RMS (step 1 minus the 3 dB stream cut; the 20 s capture landed on a quiet passage at -31.3 dBFS)SpotifyGuitar, step 2: RMS -18.3 dBFS, peak -7.0 dBFS (20 s)guitar≈1 dB on top, 7 dB headroom (but the room was still quiet)3 · After the Great Reset: TONE3000 +14 dB, every other stage at unityYouTube in Chromium at 100% (loudness-normalized ≈ −14 LUFS), step 3: RMS -18.9 dBFS, sample peak -1.2 dBFS, loudest 400 ms window −14.3 dBFS (20 s)YouTubeGuitar, step 3: RMS -16.3 dBFS, peak -5.4 dBFS (20 s)guitarguitar ≈2.5 dB over the music, 5 dB headroomhollow = derived (step 1 loud passages minus the 3 dB cut)

The data

stepTONE3000 out dBstream gainguitar RMSguitar peakmusic RMSmusic peaknote
100-30.6-17.9-16.0-7.8user playing along; guitar ~15 dB under the music
214-3-18.3-7.0-31.3-18.8spotify in a quieter passage (stream gain verified: 0.705 linear = -3.0 dB)
3140 (YouTube/Chromium at 100%)-16.3-5.4-18.9-1.2after gain staging: PipeWire Scarlett sink 0.40 (-24 dB!) -> 1.00; reference = YouTube (loudness-normalized ~-14 LUFS)

TONE3000's output and the music stream captured side by side with pw-record while I played along, 20 s per step, Iron Maiden preset. Both taps sit before the sink, which is exactly why the −24 dB never showed up in these numbers. Data: levels.csv.

And here's the punchline: TONE3000's +14 dB was right all along. The −24 dB sink was the problem the whole time, and every fix before it was compensating for a slider I never looked at.

Now there's one knob to rule them all, and tone3000.py configure enforces the Linux side, so a fresh install can't drift: sink at unity, Input 2 on Inst, Air off, Direct Monitor off.

What TONE3000 can't do (yet)

No modulation, frozen delays, some stand-in captures.

I'm excited about this plugin, so let me be honest about the gaps too:

  • No time-varying effects. Chorus, flanger, phaser, wah, pitch and rotary can't be captured. So no wah on EXP A anymore (it sweeps treble instead), no Leslie for Gilmour, no Whammy for My Iron Lung, and no harmonizer. The 3rds trick from my last post is BIAS-only; on TONE3000 alone, the Maiden twin leads need two players again. (Acts two and three bring back the wah and the harmony, from a DAW.) Align and Spread wobble get you movement, not modulation.
  • No built-in delay or reverb. Generated echo IRs work, but they're frozen: fixed time, no tap tempo, no feedback knob mid-song.
  • Some captures are stand-ins. A IIC+ plays Prince's IIB, a PCM90/91 plays the PCM70, the Triple Giant is a circuit simulation rather than a real unit, the Sunset Sound plate is a DAW emulation, there's no Colorsound Powerboost, and both Leslie captures in the catalog failed to resolve. Every preset's notes say exactly what's approximated.
  • One amp per side. Oasis's Orange, WEM and Valvestate layers and Jim Root's Laney didn't make the cut.
  • Loose ends. The relative loudness of the two sides of each dual rig is still untested, the tuner isn't MIDI-mappable (act four is five attempts at a tuner on SW10 anyway), and the silent-IR bugs are worked around, not fixed.

And it's version 0.0.9. For a 0.0.9, this is ridiculous.

End of act one

Fifteen rigs, zero xruns in the standalone.

A native Linux amp sim, fifteen researched rigs, one floorboard that didn't need a single SysEx byte changed, zero xruns at 256 samples in the standalone, and exactly one volume knob. On a laptop. (The xruns come back in act three, with three TONE3000s in a DAW. I'm not done with them.)

Same as last time, I did all of this pair-programming, this time with Claude Code running Claude Opus 5.5. It reverse-engineered the preset format and the settings blob, read the disassembly, ran the fifteen research agents, and ran every A/B test above with pw-top and pw-record while I played. The thing I appreciate most: when the experiments rejected our hypotheses, the write-ups say so.

It sounded great. It also meant a terminal to start it and a pedal that tells you nothing about what it just switched. Hold that thought until act five.

Act two · a DAW nobody clicks

Is this all just working around not having a compressor?

Why a DAW at all.

The Saturday wasn't over.

With every knob back at unity, I went after the last annoyance: some presets were way louder than others. Slipknot was way louder than Nirvana. So I played one steady riff while a script stepped through all fifteen presets and measured each one's loudness (BS.1770 integrated LUFS, per preset and per channel). Untrimmed, they spanned 39 dB, from Oasis at +0.9 LUFS down to Acoustic at −38.6, and 10 of the 15 peaked above 0 dBFS. My first shortcut, replaying a recorded DI into TONE3000's JACK input, measured every preset at −12.7 LUFS ±0.1, which is how you learn that input doesn't exercise the chain like the real one. Measured live, per-chain trims pulled the distorted presets to within ±1 dB of −14 LUFS (L7). Then toggling a drive pedal made the volume jump again.

Somewhere in the middle of trimming preset number who-knows-what, I typed the question that should have come first: is this all just working around not having a compressor?

Yes. A studio has a compressor and a limiter after the amp. TONE3000 is a capture player: nothing sits after the amp to catch a peak or even out a preset that came out 3 dB hot. That's a DAW's job. So the rig was moving again.

Picking a DAW a script can drive

Maolan out, Qtractor in, generated end to end.

The requirements were short. Linux-native. Lightweight. Free. And scriptable end to end, so Claude could build the whole rig and I'd never touch a mouse. No Wine: I had just escaped it. And not Reaper: I didn't want to buy it, and I'd already uninstalled it.

How strongly did I feel about the scriptable part? This is the actual message I sent:

don't make me click anything please automate it for me or this DAW isn't it!

That became the acceptance test.

First candidate: Maolan 0.3.0. A young DAW written in Rust, shipped as an AppImage, and on paper perfect: it has an OSC API that covers tracks, plugins and routing, and the whole chain built fine over OSC. Then reality:

  • the GUI panicked twice in f32::clamp (min > max) when the window was small;
  • on JACK, track add always fails with "Engine needs to open audio device", because it only checks the non-JACK driver. That's still the case in engine HEAD;
  • there's no OSC command to save a session, so a script can build a rig it can never keep;
  • MIDI learn can't target plugin parameters, so no footswitches.

Second candidate: Qtractor 1.6.4. Older, less shiny, and its sessions are plain XML. Which means the session doesn't have to be driven at all. It can just be written. qtractor_rig.py generates the entire rig: the buses and their exact JACK connections, a MIDI track holding the plugin chain, per-plugin CC bindings, and even TONE3000's state blob (a qCompress'd T3KB, the same format we reverse-engineered for the presets). Transport and recording run over MMC. Save-and-quit is SIGUSR1, then SIGTERM. Nothing is clicked.

What the script needsMaolan 0.3.0Qtractor 1.6.4
Linux-native, no Wine, free✓✓
Build the chain from code✓ OSC API✓ session XML, generated
Add a track on JACK✗ "Engine needs to open audio device"✓
Save from a script✗ no OSC save✓ SIGUSR1
Footswitch CCs onto plugin parameters✗ MIDI learn can't target them✓ per-plugin CC bindings
Record from a scriptnot reached✓ MMC

From experiment D0 in experiments/README.md.

Two constraints came out of reading Qtractor's source rather than its docs. Plugins only receive MIDI on MIDI tracks and buses, so the guitar comes in through an Audio Insert on a MIDI track. And only the CLAP build of a plugin gets raw Program Change; the VST3 path drops it. TONE3000 ships a CLAP, so that's the one the session loads.

MIDIFCB1010PC + CCQtractor MIDI bus “FCB”PC/CC straight into TONE3000 · Qtractor binds SW6 wah, SW7 octaver, EXP A sweepAUDIOGuitar inScarlett In 2WahGuitarixTONE3000CLAP pluginOctaverGuitarixCompressorLSP, 3:1Limiterx42, −1 dBTPScarlett outbus RigRecording: MMC record/play from the script → “Rig Print” (processed stereo) + “DI” (dry, sample-aligned)

The first DAW rig (experiments D0–D4): TONE3000 as a CLAP plugin between real wah and octaver plugins, with a compressor and limiter on the way out. Gold = switchable from the floor.

A nice side effect: the DAW brings back what the FCB layout was originally for. SW6 is a real wah again, SW7 a real octaver, EXP A sweeps the wah. They're Guitarix plugins, so they do the time-varying things a capture can't. TONE3000 keeps SW8 boost, SW9 drive, SW10 echo and EXP B output. For now: the top row moves around a bit later on, and the full pedal map has where it ended up.

Did the compressor earn its place?

Loudness spread 9.2 → 4.6 dB, no more overs.

Bench: same laptop and interface, Qtractor on pipewire-jack at 256 samples / 48 kHz. The test input is a dry DI loop played into the rig's insert with pw-play, so it goes through the same plugin instance as the live guitar (unlike the JACK-input replay that fooled me above). Program changes and CCs go into Qtractor's FCB port with aseqsend, and every PC was verified by reading activePresetName back from the saved plugin state.

D1. Hypothesis: an LSP compressor (−18 dB threshold, 3:1, 10/120 ms, +3 dB makeup) plus the x42 true-peak limiter at −1 dBTP shrink the loudness spread across presets and remove the overs. Method: the full 15-preset sweep, once with both plugins deactivated and once with both active, everything else identical.

Integrated loudness (LUFS)Sample peak (dBFS)dynamics offcompressor + limiter on-18-16-14-12-10-8-8-6-4-20+2+40 dBFS clips−1 dBTP ceiling0 Comfortably NumbComfortably Numb, dynamics off: -13.4 LUFSComfortably Numb, dynamics on: -11 LUFSComfortably Numb, dynamics off: -1.7 dBFSComfortably Numb, dynamics on: -1 dBFS1 Purple RainPurple Rain, dynamics off: -18 LUFSPurple Rain, dynamics on: -15.5 LUFSPurple Rain, dynamics off: -1.1 dBFSPurple Rain, dynamics on: -1 dBFS2 Tornado of SoulsTornado of Souls, dynamics off: -14.4 LUFSTornado of Souls, dynamics on: -11.6 LUFSTornado of Souls, dynamics off: -6.9 dBFSTornado of Souls, dynamics on: -4.1 dBFS3 Dream TheaterDream Theater, dynamics off: -14.4 LUFSDream Theater, dynamics on: -12.1 LUFSDream Theater, dynamics off: -5.4 dBFSDream Theater, dynamics on: -3 dBFS4 SlipknotSlipknot, dynamics off: -14.7 LUFSSlipknot, dynamics on: -12 LUFSSlipknot, dynamics off: -4.8 dBFSSlipknot, dynamics on: -2.4 dBFS5 DjentDjent, dynamics off: -14.5 LUFSDjent, dynamics on: -11.8 LUFSDjent, dynamics off: -5.3 dBFSDjent, dynamics on: -2.6 dBFS6 RadioheadRadiohead, dynamics off: -11.9 LUFSRadiohead, dynamics on: -11.8 LUFSRadiohead, dynamics off: -0.6 dBFSRadiohead, dynamics on: -1 dBFS7 OasisOasis, dynamics off: -14.2 LUFSOasis, dynamics on: -11.4 LUFSOasis, dynamics off: -6.3 dBFSOasis, dynamics on: -3.3 dBFS8 NirvanaNirvana, dynamics off: -13.2 LUFSNirvana, dynamics on: -11.1 LUFSNirvana, dynamics off: -4.9 dBFSNirvana, dynamics on: -2.7 dBFS9 Foo FightersFoo Fighters, dynamics off: -14.9 LUFSFoo Fighters, dynamics on: -12.5 LUFSFoo Fighters, dynamics off: -4.6 dBFSFoo Fighters, dynamics on: -2.8 dBFS10 Iron MaidenIron Maiden, dynamics off: -13.6 LUFSIron Maiden, dynamics on: -11.4 LUFSIron Maiden, dynamics off: -1.9 dBFSIron Maiden, dynamics on: -1 dBFS11 My CleanMy Clean, dynamics off: -17.1 LUFSMy Clean, dynamics on: -14.8 LUFSMy Clean, dynamics off: -1.3 dBFSMy Clean, dynamics on: -1 dBFS12 Petrucci CleanPetrucci Clean, dynamics off: -14 LUFSPetrucci Clean, dynamics on: -13 LUFSPetrucci Clean, dynamics off: 2.8 dBFSPetrucci Clean, dynamics on: -1 dBFS13 AcousticAcoustic, dynamics off: -8.8 LUFSAcoustic, dynamics on: -10.9 LUFSAcoustic, dynamics off: 3 dBFSAcoustic, dynamics on: -1 dBFS14 Glassy CleanGlassy Clean, dynamics off: -14.9 LUFSGlassy Clean, dynamics on: -13.4 LUFSGlassy Clean, dynamics off: -0.5 dBFSGlassy Clean, dynamics on: -1 dBFSspread, off9.2 dBspread, on4.6 dBworst peak, off: +3.0 dBFS (Acoustic)worst peak, on: -1.0 dBFS

The data

PCpresetdynamicspedalsLUFSpeak dBFS
0Comfortably Numboffas loaded-13.4-1.7
1Purple Rainoffas loaded-18.0-1.1
2Tornado of Soulsoffas loaded-14.4-6.9
3Dream Theateroffas loaded-14.4-5.4
4Slipknotoffas loaded-14.7-4.8
5Djentoffas loaded-14.5-5.3
6Radioheadoffas loaded-11.9-0.6
7Oasisoffas loaded-14.2-6.3
8Nirvanaoffas loaded-13.2-4.9
9Foo Fightersoffas loaded-14.9-4.6
10Iron Maidenoffas loaded-13.6-1.9
11My Cleanoffas loaded-17.1-1.3
12Petrucci Cleanoffas loaded-14.02.8
13Acousticoffas loaded-8.83.0
14Glassy Cleanoffas loaded-14.9-0.5
0Comfortably Numboffdrive toggled-12.63.9
1Purple Rainoffdrive toggled-15.2-4.8
2Tornado of Soulsoffdrive toggled-14.6-6.6
3Dream Theateroffdrive toggled-14.9-5.7
4Slipknotoffdrive toggled-14.8-4.6
5Djentoffdrive toggled-14.2-4.6
6Radioheadoffdrive toggled-14.3-0.9
7Oasisoffdrive toggled-15.5-6.2
8Nirvanaoffdrive toggled-17.5-6.2
9Foo Fightersoffdrive toggled-14.3-4.8
10Iron Maidenoffdrive toggled-11.7-1.0
11My Cleanoffdrive toggled-17.6-5.3
12Petrucci Cleanoffdrive toggled-13.83.4
13Acousticoffdrive toggled-14.7-1.6
14Glassy Cleanoffdrive toggled-15.1-1.0
0Comfortably Numbonas loaded-11.0-1.0
1Purple Rainonas loaded-15.5-1.0
2Tornado of Soulsonas loaded-11.6-4.1
3Dream Theateronas loaded-12.1-3.0
4Slipknotonas loaded-12.0-2.4
5Djentonas loaded-11.8-2.6
6Radioheadonas loaded-11.8-1.0
7Oasisonas loaded-11.4-3.3
8Nirvanaonas loaded-11.1-2.7
9Foo Fightersonas loaded-12.5-2.8
10Iron Maidenonas loaded-11.4-1.0
11My Cleanonas loaded-14.8-1.0
12Petrucci Cleanonas loaded-13.0-1.0
13Acousticonas loaded-10.9-1.0
14Glassy Cleanonas loaded-13.4-1.0
0Comfortably Numbondrive toggled-12.1-1.0
1Purple Rainondrive toggled-12.2-1.9
2Tornado of Soulsondrive toggled-11.7-3.7
3Dream Theaterondrive toggled-12.5-3.3
4Slipknotondrive toggled-12.0-1.7
5Djentondrive toggled-11.5-1.9
6Radioheadondrive toggled-13.0-1.0
7Oasisondrive toggled-12.7-3.4
8Nirvanaondrive toggled-14.8-3.5
9Foo Fightersondrive toggled-11.8-2.8
10Iron Maidenondrive toggled-9.9-1.0
11My Cleanondrive toggled-14.8-2.8
12Petrucci Cleanondrive toggled-12.9-1.0
13Acousticondrive toggled-14.0-1.0
14Glassy Cleanondrive toggled-13.3-1.0

D1: the 15 presets on the DI loop, dynamics bypassed vs compressor + limiter on. Hover a dot for the value. Data: daw-dynamics.csv.

Confirmed. The spread across all fifteen presets halved, from 9.2 dB (−18.0 … −8.8 LUFS) to 4.6 dB (−15.5 … −10.9). The ten distorted presets went from a 3.0 dB window to 1.5 dB. And two presets, Acoustic and Petrucci Clean, had been clipping the output outright at +3.0 and +2.8 dBFS. Now nothing passes −1 dBFS. The cleans stay a bit lower on purpose; Purple Rain should not be as loud as Slipknot.

D2. Same sweep, but with SW9 (the drive slot, CC 23) toggled after each program change:

dynamics offcompressor + limiter on-6-5-4-3-2-10+1+2+3+40 Comfortably NumbComfortably Numb, dynamics off: -13.4 → -12.6 LUFS (+0.8 dB)Comfortably Numb, dynamics on: -11 → -12.1 LUFS (-1.1 dB)1 Purple RainPurple Rain, dynamics off: -18 → -15.2 LUFS (+2.8 dB)Purple Rain, dynamics on: -15.5 → -12.2 LUFS (+3.3 dB)2 Tornado of SoulsTornado of Souls, dynamics off: -14.4 → -14.6 LUFS (-0.2 dB)Tornado of Souls, dynamics on: -11.6 → -11.7 LUFS (-0.1 dB)3 Dream TheaterDream Theater, dynamics off: -14.4 → -14.9 LUFS (-0.5 dB)Dream Theater, dynamics on: -12.1 → -12.5 LUFS (-0.4 dB)4 SlipknotSlipknot, dynamics off: -14.7 → -14.8 LUFS (-0.1 dB)Slipknot, dynamics on: -12 → -12 LUFS (+0.0 dB)5 DjentDjent, dynamics off: -14.5 → -14.2 LUFS (+0.3 dB)Djent, dynamics on: -11.8 → -11.5 LUFS (+0.3 dB)6 RadioheadRadiohead, dynamics off: -11.9 → -14.3 LUFS (-2.4 dB)Radiohead, dynamics on: -11.8 → -13 LUFS (-1.2 dB)7 OasisOasis, dynamics off: -14.2 → -15.5 LUFS (-1.3 dB)Oasis, dynamics on: -11.4 → -12.7 LUFS (-1.3 dB)8 NirvanaNirvana, dynamics off: -13.2 → -17.5 LUFS (-4.3 dB)Nirvana, dynamics on: -11.1 → -14.8 LUFS (-3.7 dB)9 Foo FightersFoo Fighters, dynamics off: -14.9 → -14.3 LUFS (+0.6 dB)Foo Fighters, dynamics on: -12.5 → -11.8 LUFS (+0.7 dB)10 Iron MaidenIron Maiden, dynamics off: -13.6 → -11.7 LUFS (+1.9 dB)Iron Maiden, dynamics on: -11.4 → -9.9 LUFS (+1.5 dB)11 My CleanMy Clean, dynamics off: -17.1 → -17.6 LUFS (-0.5 dB)My Clean, dynamics on: -14.8 → -14.8 LUFS (+0.0 dB)12 Petrucci CleanPetrucci Clean, dynamics off: -14 → -13.8 LUFS (+0.2 dB)Petrucci Clean, dynamics on: -13 → -12.9 LUFS (+0.1 dB)13 AcousticAcoustic, dynamics off: -8.8 → -14.7 LUFS (-5.9 dB)Acoustic, dynamics on: -10.9 → -14 LUFS (-3.1 dB)14 Glassy CleanGlassy Clean, dynamics off: -14.9 → -15.1 LUFS (-0.2 dB)Glassy Clean, dynamics on: -13.4 → -13.3 LUFS (+0.1 dB)the solo driveDS-1 offAcousticloudness change when SW9 (drive, CC 23) is toggled after the PC, dB

D2: loudness change when the drive slot (SW9, CC 23) is toggled right after each PC. Positive = louder with the toggle. Data: daw-dynamics.csv.

The mean jump went from 1.47 to 1.13 dB, the largest from 5.9 dB (Acoustic) to 3.7 dB, and the worst peak with the drive toggled from +3.9 to −1.0 dBFS. What's left is mostly musical: Purple Rain's solo drive adds 3.3 dB and dropping Nirvana's DS-1 takes away 3.7. A solo pedal that doesn't get louder isn't a solo pedal.

D3, every pedal on at once: wah and octaver on top of Nirvana, Qtractor at 0.19–0.30 B/Q, about 1.0–1.6 ms of the 5.3 ms cycle, and no xruns added. The octaver is audibly there, and measurably too: the 40–120 Hz band rose 2.9 dB relative to 120–1000 Hz at the same loudness.

D4, recording by script. qtractor_rig.py record sends MMC record + play, qtractor_rig.py stop ends the take. Six seconds of DI later there were two files: rig-Rig_Print-1.wav (the processed stereo rig, peak −1.0 dBFS) and rig-DI-1.wav (the dry mono DI, peak −1.4 dBFS), both exactly 320,512 frames long and sample-aligned. Every take comes with a DI I can re-amp later. The footswitches were checked the same way: SW6 turned the wah on, SW7 the octaver, and EXP A at 32/127 put the wah at 0.252, all read back from the saved session.

So now I had a real rig: pedals, amps, a mix bus, a recorder. Which meant I could finally ask for what I actually wanted.

Act three · up the Irons

Seven eras of Maiden on one floorboard

21 presets, the pedal map, instant scenes, levels, the harmony.

Here is the brief, verbatim:

hardcore maiden sound range from their top album sounds from each era... be maiden heavy... switching sounds within the same song quickly... make it world class

That's two projects hiding in one sentence. One is research: what did Dave Murray, Adrian Smith and Janick Gers actually play, record by record? The other is engineering: switching sounds within a song, instantly. The second one turned out to be the hard one.

Seven research agents, one per album

Same trick as the first fifteen presets, scaled up. Seven research agents, one per album era: 1982, 1983, 1984, 1986, 1988, 1992 and the 2000s reunion. Each one had to find who played what through what on that record, cite it, find TONE3000 captures of that exact gear, and write three presets: heavy, clean and acoustic. That's 21 presets backed by 143 source citations (70 distinct URLs), and every file lists what had to be approximated.

BankEraLeft: Dave MurrayRight
03The Number of the Beast (1982)Marshall JMP 2204 50 W (1977)Smith: JMP 1987 50 W (1973), inputs jumpered, volumes full
04Piece of Mind (1983)1976 JMP 50 W full rig, 1960A blackbacksSmith: 1980 JMP 2204
05Powerslave (1984)Marshall 1987 50 W, vol 7, mids 10Smith: JMP 2203 100 W (1983)
06Somewhere in Time (1986)Gallien-Krueger 250ML, "Maiden" preamp settingSmith: GK 250ML full rig
07Seventh Son of a Seventh Son (1988)GK 250MLSmith: GK 2000CPL, the preamp in the 2100SEL rack
08Fear of the Dark (1992)Marshall JCM900 4100Gers: JCM800 2203
09Brave New World / Dance of Death (2000, 2003)Marshall JMP-1 preset 04 "British Steel" into a 1960BV V30Smith: JCM2000 DSL Ultra Gain

The heavy preset of each era. Clean and acoustic presets, cabs, mics, pedals and every source: rigs/RIGS.md and rigs/15–35.

Reading the notes these agents wrote was the most fun I've had with a pile of JSON:

  • 1982: Guitar World in 1983 says "four fifty-watt Marshalls". The catalog has one of each 50 W JMP type, so Murray gets a 1977 2204 (master volume) and Smith a 1973 1987 (non-master) with the inputs jumpered and both volumes full: Smith's "natural overdriven Marshall".
  • 1983: Smith says the studio setup was the stage setup, amps in "a big wooden room" at Compass Point. So the always-on ambience is a Lexicon 480L Large Wood Room, and the echo (SW9 in these banks) is 375 ms: a quarter note of The Trooper at ~160 BPM.
  • 1984: Smith's own summary of the Powerslave tone is a 100-watt Marshall and "maybe an Ibanez Tube Screamer", and "that was it as far as the guitar sound of Powerslave goes". So the TS9 sits in the drive slot, off by default, because Murray used none.
  • 1986: both players on the solid-state GK 250ML. The delay is timed to Wasted Years: a quarter note at 154 BPM (390 ms) on Murray's side, a dotted eighth (292 ms) on Smith's. And since it's the most chorused Maiden record, it gets more Align wobble than Seventh Son.
  • 1988: the Seventh Son rig from the first half of this post, re-cut for the scene bank. Its echo (SW9) is 380 ms, deliberately matched to the DAW's solo delay so the two land on the same repeats instead of flamming.
  • 1992: the one period quote is Murray in 1990, showing off "new Marshall amps that go to 20". That's the JCM900 4100 Hi Gain Dual Reverb, whose gain dials run 0–20. Gers' head from that era isn't documented, so the right side gets the JCM800 2203 Gers had used since Gillan, and the notes say it's a guess.
  • 2000s: Murray's 2003 rack is fully documented (JMP-1 into a 9200 power amp into 1960BV Vintage 30s). Kevin Shirley added no reverb on Dance of Death, just the studio's room mics, so the ambience is a wood room at 10%. The acoustic preset is Journeyman, Maiden's first and only fully acoustic song.

And the acoustics are thought through too. Seventh Son's acoustic preset puts a steel-string dreadnought on the left and a nylon classical body on the right, for Murray's classical outro on The Prophecy.

The full pedal map

Before the engineering, here's where the board ended up, after everything else in this act and the next: the harmony, the per-song echo and, last of all, a tuner. Ten banks, two layouts. It's declared once, as data, in rig.py (uv run rig.py show prints it), and both boards below are drawn from that same file. Everything is on MIDI channel 1, except the second and third Program Change of a Maiden switch: channel 2 for the DAW's clean rig, channel 3 for its harmony rig.

Song banks 00–02: one song per switch. The bottom row loads a song, the top row is pedals.

FCB1010 · banks 00–02 · one song per switchWAHSW6 · CC 20GuitarixOCTAVERSW7 · CC 21GuitarixLEADSW8 · CC 26boost + echoDRIVESW9 · CC 23slot 2TUNERSW10 · CC 28mutes the rigEXP ACC 27wah sweepEXP BCC 7volumeSW1PC 0 · 5 · 10song presetSW2PC 1 · 6 · 11song presetSW3PC 2 · 7 · 12song presetSW4PC 3 · 8 · 13song presetSW5PC 4 · 9 · 14song presetevery song switch also sends CC 80 = 63 · CC 81 = 0: heavy rig, Solo block offComfortablyNumbPurple RainTornadoof SoulsDreamTheaterSlipknotBANK 00PC 0–4DjentRadioheadOasisNirvanaFooFightersBANK 01PC 5–9Iron MaidenMy CleanPetrucciCleanAcousticGlassyCleanBANK 02PC 10–14

The data

BankSW1SW2SW3SW4SW5
00Comfortably Numb PC 0Purple Rain PC 1Tornado of Souls PC 2Dream Theater PC 3Slipknot PC 4
01Djent PC 5Radiohead PC 6Oasis PC 7Nirvana PC 8Foo Fighters PC 9
02Iron Maiden PC 10My Clean PC 11Petrucci Clean PC 12Acoustic PC 13Glassy Clean PC 14

Banks 00–02: the fifteen songs, drawn from rig.py. Blue = the same in all ten banks: SW6 WAH, SW10 TUNER and the two expression pedals.

SW8 is LEAD now: the boost and the song's solo echo on one switch. That's one CC, 26, mapped onto three TONE3000 targets: the boost slot and the echo on both sides of a dual rig. The echo used to have SW10 to itself. SW10 is the tuner now, in every bank.

Maiden banks 03–09: one layout for every era. The floorboard had to make "switch sounds within the same song" a single stomp. So every Maiden bank has the same five scenes on the bottom row, and the same five pedals on the top:

FCB1010 · banks 03–09 · same layout for every eraWAHSW6 · CC 20GuitarixHARMONYSW7 · CC 252nd guitarBOOSTSW8 · CC 22slot 1DELAY +REVERBSW9 · CC 24slot 5 + R3TUNERSW10 · CC 28mutes the rigEXP ACC 27wah sweepEXP BCC 7volumeRHYTHMSW1 · drive 63PCs · heavySOLOSW2 · drive 72CCs · Solo onCLEANSW3 · drive 63PCs · cleanACOUSTICSW4 · drive 63PCs · cleanCRUNCHSW5 · drive 48CCs · heavydrive = CC 80 (heavy inputLevel) · every scene switch also sends CC 81 (which rig), always absolute03 · 1982The Numberof the Beast04 · 1983Piece of Mind05 · 1984Powerslave06 · 1986Somewherein Time07 · 1988Seventh Son08 · 1992Fear ofthe Dark09 · 2000sBrave New WorldDance of Death

Banks 03–09: one album era per bank, same switches everywhere. Gold = scene switches (absolute CCs, see the table below) and SW7 HARMONY, which replaced the octaver in these banks. Blue = the same in all ten banks.

RHYTHM, SOLO, CLEAN, ACOUSTIC and CRUNCH are scenes, not songs. SOLO pushes the amps and adds a lead echo. CRUNCH is the guitar-volume-rolled-back sound, like the Fear of the Dark intro. CLEAN and ACOUSTIC are the era's reverby clean and electric-to-acoustic presets. To change song or era, pick the bank and press RHYTHM, CLEAN or ACOUSTIC: those three load the era.

SOLO and CRUNCH take care of the gain here, so these banks have no drive switch: SW9 carries the era's delay + reverb instead. SW7 started out as the octaver here too. It didn't stay that way: it's now HARMONY, the second guitarist, and that story comes after the xruns. Each era also gets a signature song, which sets SOLO's echo time and HARMONY's key (more on that later):

BankEraSignature songScaleSolo echoPCs
03The Number of the Beast (1982)Hallowed Be Thy NameE natural minor571 ms15 · 16 · 17 · 36
04Piece of Mind (1983)The TrooperE natural minor375 ms18 · 19 · 20 · 37
05Powerslave (1984)Rime of the Ancient MarinerE natural minor536 ms21 · 22 · 23 · 38
06Somewhere in Time (1986)Wasted YearsE natural minor390 ms24 · 25 · 26 · 39
07Seventh Son of a Seventh Son (1988)The Evil That Men DoE natural minor375 ms27 · 28 · 29 · 40
08Fear of the Dark (1992)Fear of the DarkD Dorian750 ms30 · 31 · 32 · 41
09Brave New World / Dance of Death (2000, 2003)Blood BrothersE natural minor339 ms33 · 34 · 35 · 42

From the scene-bank table in rigs/RIGS.md. PCs: heavy · clean · acoustic · harmony.

Whose solo? Both guitarists play at once on this board: Murray on the left, Smith or Gers on the right. No footswitch tells you whose solo it is, so the guitar does. Neck pickup for Murray's fluid legato, bridge for Smith's and Gers' bite. SOLO then adds the push and the echo on top.

What every switch sends. Scene switches send absolute values, so they're instant and always land in the same state. Everything on the top row is a toggle: the stock FCB sends 127 on every press, and every press flips it.

SwitchSong banks 00–02Maiden banks 03–09
SW1SONG: PC 0–14 on ch 1, CC 80 = 63, CC 81 = 0. Preset load. Loads the song and resets the scene state: heavy rig, Solo block off.RHYTHM: PC heavy (ch 1) + clean (ch 2) + harmony (ch 3), CC 80 = 63, CC 81 = 0. Instant scene. Twin-guitar rhythm. After a bank change, this is what loads the era.
SW2SOLO: no PC, CC 80 = 72, CC 81 = 0. Instant scene. Amps pushed, +2 dB and the song's lead echo.
SW3CLEAN: PC heavy (ch 1) + clean (ch 2) + harmony (ch 3), CC 80 = 63, CC 81 = 127. Instant scene. The era's reverby clean.
SW4ACOUSTIC: the same PCs with the acoustic preset on ch 2, CC 80 = 63, CC 81 = 127. Instant scene. Electric-to-acoustic.
SW5CRUNCH: no PC, CC 80 = 48, CC 81 = 0. Instant scene. The volume-knob-down sound.
SW6WAH: CC 20. Toggle. The Guitarix wah in the DAW; EXP A sweeps it.
SW7OCTAVER: CC 21. Toggle. Guitarix, an octave down.HARMONY: CC 25. Toggle. The second guitarist, a diatonic third up in the song's key.
SW8LEAD: CC 26. Toggle. The boost (slot 1) and the song's echo (slot 5, and R3 on a dual rig) together.BOOST: CC 22. Toggle. The boost pedal (slot 1).
SW9DRIVE: CC 23. Toggle. The song's drive pedal (slot 2).DELAY + REVERB: CC 24. Toggle. The era's delay + reverb (slot 5 and R3).
SW10TUNER: CC 28. Toggle. GuitarMood mutes the rig and opens the Fretwise tuner in the Omarchy bar, on the clean guitar input. Press again to play.
EXP ACC 27: wah sweep.
EXP BCC 7: volume (TONE3000's output level).

From the pedal map in README.md, with the numbers from rig.py.

The tuner. SW10 mutes the rig and opens a tuner on the guitar input; press it again to play. It's the same in all ten banks. It also took five tries and one very confusing practice session, so it gets its own act.

Why a scene can't just be a preset

The obvious design is a preset per scene and a Program Change per stomp. Before building that, we measured what a PC costs.

D5. Question: can scenes within a song be separate presets? Method: the DI loop plays continuously while aseqsend fires a PC every 2 seconds; the output is recorded and the 10 ms RMS envelope around each switch is inspected, four switches per pair.

02550751001251501752001/16 @ 160 BPM1/8 @ 160 BPMIron Maiden ↔ My CleanIron Maiden ↔ My Clean: 130–160 ms more than 20 dB below the running level; floor digital silence (−140 dB)130–160 msfloor: digital silence (−140 dB)Iron Maiden ↔ SlipknotIron Maiden ↔ Slipknot: 60–170 ms more than 20 dB below the running level; floor digital silence60–170 msfloor: digital silencere-send the PC already loadedre-send the PC already loaded: 0–10 ms more than 20 dB below the running level; floor −37 … −48 dB: the DI's own dynamics≤ 10 msfloor: −37 … −48 dB: the DI's own dynamicstime more than 20 dB below the running level after a PC, ms (range over 4 switches)

D5: how long the output sits more than 20 dB below its running level after a Program Change, as ranges over 4 switches per pair. The dashed tempo lines are arithmetic from The Trooper's ~160 BPM, not measurements.

Rejected. A Program Change mutes the plugin while it rebuilds the chain: 60–170 ms of digital silence, longer on the heavier rigs. That's fine between songs. Mid-song it's most of an eighth note at The Trooper's ~160 BPM, and it lands exactly where you don't want it: on the first chord of the solo. But look at the last row. Re-sending the PC that's already loaded doesn't rebuild the chain. That one detail is what makes the whole scene engine possible.

The scene engine: two TONE3000s and a Selector

If loading a preset leaves a hole, don't load one. Load both sounds up front and switch between them.

MIDIFCB1010banks 03–09MIDI bus “FCB”ch 1 → heavy · ch 2 → cleanHEAVY TRACKGuitarScarlett In 2Selectorinsert · CC 81WahSW6/EXP ATONE3000 heavyPC ch 1CC 80 = inputLevelOctaverCC 21SoloCC 80 > 63+2 dB · song echoCLEAN TRACKClean Inhears only the sendTONE3000 cleanPC ch 2clean / acousticsend when active(heavy path muted)BUS “Rig”(also feeds Rig Print)bus RigstereoCompressorLSP · 3:1 @ −18Limiterx42 · −1 dBTPScarlett outone knobCC 81PC ch 1 · CC 80CC 80PC ch 2

The scene engine as qtractor_rig.py generates it. Dashed gold = MIDI; gold boxes are switched by the scene CCs.

  • Two instances. Qtractor runs a heavy TONE3000 on MIDI channel 1 and a clean one on channel 2. Changing song loads the era into both.
  • The Selector. An Audio Insert at the top of the heavy chain, switched by CC 81. Bypassed, it passes the guitar on to the heavy rig. Active, it sends the guitar to the clean rig's input and mutes the heavy path. It's one parameter, so the choice is exclusive by construction: there is no state where both rigs hear the guitar, or neither.
  • CC 80 does two jobs. The heavy TONE3000 reads it through its own MIDI map as inputLevel = value/127, like rolling the guitar volume. And Qtractor switches the Solo block on above 63.
  • Absolute values, always. Every stomp sends both CCs with fixed values, so a scene lands in the same state no matter what came before. No toggles to get out of sync.
SwitchSceneHeavy PC (ch 1)Clean PC (ch 2)Harmony PC (ch 3)CC 80 driveCC 81 rig
SW1RHYTHMsongcleanharmony63heavy
SW2SOLO———72heavy
SW3CLEANsongcleanharmony63clean
SW4ACOUSTICsongacousticharmony63clean
SW5CRUNCH———48heavy

What each scene switch sends, from rigs/RIGS.md. RHYTHM, CLEAN and ACOUSTIC re-send the song's PCs, which only reload anything when you've changed bank. The channel 3 column arrived with the harmony.

Why a Selector instead of just binding CC 81 to "heavy off, clean on"? Because Qtractor allows one observer per (type, channel, CC). One CC can't drive two parameters. And a PC reaches Qtractor's controllers as a trigger with value 127, so a PC can only ever switch a parameter one way. One parameter that routes gets around both.

D6, the checks, with Iron Maiden as the heavy rig and My Clean as the clean one. With CC 81 = 0 the Selector's send measured −180 dBFS, so the clean rig hears nothing; with 127 it carried the DI at −21.6 dBFS. CC 80 = 72 set the heavy inputLevel to 0.567 and switched the lead delay on, while the clean instance didn't budge. Then a scene walk, rhythm → solo → clean → rhythm → crunch → clean: sound throughout. The quietest 10 ms within 300 ms of each switch was −17 … −29 dB, against −16 … −19 dB for the same DI with no switch at all. No PC-style hole anywhere.

D7, the price: with both instances loaded, Qtractor ran at 0.43–0.58 B/Q against 0.19–0.30 with one, still with zero xruns at 256 samples. The clean rig hears the guitar through a JACK self-connection, which should add one period (5.3 ms) on the clean and acoustic scenes only. That's what JACK does with feedback connections; I haven't measured it.

The race: a Program Change wipes CC 80

The first version of the board had a bug you could only find by reading state back.

D8. Method: send the loaded preset's PC and CC 80 = 72 in one burst, then read the heavy inputLevel back from the saved session. Expected 0.567 (72/127). Got 0.496, the preset's own value. CC 80 on its own gave 0.567, and so did sending it 100 ms after the PC. Cause: TONE3000 re-applies a preset's parameters asynchronously after a PC, even for the preset already loaded, so it quietly undid the CC that arrived in the same burst.

The fix is in the table above. SOLO and CRUNCH send only CCs. RHYTHM, CLEAN and ACOUSTIC do send PCs, but at CC 80 = 63, which is exactly the drive every heavy preset loads with, so the race has nothing left to change. rig.py's verify step runs before every upload and refuses a board that breaks either rule.

The second trap was subtler. With dynamics bypassed, SOLO's input push (0.496 → 0.567, roughly +3.4 dB if the parameter spans ±24 dB like the block gains) made the solo only +0.4 … +1.1 LUFS louder. Of course: a cranked amp compresses. Pushing it gives you more sustain and more hair, not more level. Great for feel, useless for being heard over the band. Turning up the Guitarix delay didn't help either (≤ 0.2 dB), because its gain only scales the echoes.

So SOLO got a Solo block: an LSP slap-back delay with the dry signal at +2 dB and a single 380 ms tap (feedback 0.3, filtered to 250 Hz – 4.5 kHz, 10 dB down). It switches on above CC 80 = 63 and is exactly unity when bypassed.

0+1 dB+2 dB+3 dB+4 dBSOLO pushes the amps onlyMaiden 82: SOLO pushes the amps only +0.5 dBMaiden 83: SOLO pushes the amps only +0.4 dBMaiden 84: SOLO pushes the amps only +0.5 dBMaiden 86: SOLO pushes the amps only +0.4 dBMaiden 88: SOLO pushes the amps only +1.1 dBMaiden 92: SOLO pushes the amps only +0.8 dBMaiden 2000s: SOLO pushes the amps only +0.4 dB+0.4 … +1.1+ Solo block, no dynamics+ Solo block, no dynamics: +2.9 … +3.1 dB (range from the experiment log)+2.9 … +3.1+ Solo block, dynamics onMaiden 82: + Solo block, dynamics on +1.7 dBMaiden 83: + Solo block, dynamics on +1.6 dBMaiden 84: + Solo block, dynamics on +1.8 dBMaiden 86: + Solo block, dynamics on +1.9 dBMaiden 88: + Solo block, dynamics on +1.9 dBMaiden 92: + Solo block, dynamics on +2.1 dBMaiden 2000s: + Solo block, dynamics on +1.4 dB+1.4 … +2.1CRUNCH under RHYTHM, dynamics onMaiden 82: CRUNCH under RHYTHM, dynamics on +0.5 dBMaiden 83: CRUNCH under RHYTHM, dynamics on +0.3 dBMaiden 84: CRUNCH under RHYTHM, dynamics on +0.6 dBMaiden 86: CRUNCH under RHYTHM, dynamics on +0.4 dBMaiden 88: CRUNCH under RHYTHM, dynamics on +1.1 dBMaiden 92: CRUNCH under RHYTHM, dynamics on +0.9 dBMaiden 2000s: CRUNCH under RHYTHM, dynamics on +0.1 dB+0.1 … +1.1loudness difference between scenes, dB · one dot per era · outline = range only

D8/D9: SOLO over RHYTHM (and CRUNCH under it), one dot per era. The outlined bar is the range the experiment log reports for the Solo block without dynamics; that pass has no per-era rows in the CSV. Data: maiden-levels.csv.

Now the solo sits 2.9–3.1 dB over the rhythm before the compressor, and 1.4–2.1 dB after it. CRUNCH lands 0.1–1.1 dB under RHYTHM: quieter, but mostly just cleaner, like rolling back the volume knob.

13 dB between Murray and Smith

Then the leveling, and the best bug of the day.

D9. The first sweep of all 21 presets, as the agents delivered them, measured with the compressor and limiter bypassed so every trim is linear. Heavy rhythm ranged from −9.1 to −13.7 LUFS. Acoustic ranged from −15.5 up to +10.9 LUFS, with peaks at +25.9 dBFS. That's 1992's acoustic preset, running roughly 26 dB hotter than the target.

And from the second pass on we measured each side separately. On Piece of Mind, Murray's side came in at −9.3 LUFS and Smith's at −22.4: 13.1 dB apart. Somewhere in Time was 11.3 dB apart the other way round. In a hard-panned dual rig, that's not a stereo mix. That's one guitarist.

Here's the part that stung: with the dynamics on, none of this was audible in the numbers. The limiter had been flattening all of it. A limiter is a safety net, and a safety net is a terrible place to hide your gain staging.

The fixes, in order:

  • Gain-staging audit. Two acoustic presets drove the Boss AC-3 capture far hotter than the validated Acoustic preset (PC 13) does: 1992's by +20 dB from a compressor in front of it, 1986's by +9 dB on the input. Both went back to unity. The trim script also learned to skip partial-mix blocks, where a gain change only scales the wet part.
  • Trims. Each chain gets its trim on the last level-setting block before the echo and ambience, so the reverbs follow. Each chain is shifted to the average of its two sides, which balances them. Then two linear passes toward −14 LUFS for heavy and −15.5 for clean and acoustic.
Heavy rhythm (target −14 LUFS)1 · deliveredMaiden 82 rhythm, pass “1 · delivered”: -13.7 LUFSMaiden 83 rhythm, pass “1 · delivered”: -9.1 LUFSMaiden 84 rhythm, pass “1 · delivered”: -13.7 LUFSMaiden 86 rhythm, pass “1 · delivered”: -10 LUFSMaiden 88 rhythm, pass “1 · delivered”: -13.4 LUFSMaiden 92 rhythm, pass “1 · delivered”: -13.4 LUFSMaiden 2000s rhythm, pass “1 · delivered”: -12.6 LUFS4.6 dB2 · gain fixMaiden 82 rhythm, pass “2 · gain fix”: -13.7 LUFSMaiden 83 rhythm, pass “2 · gain fix”: -9.1 LUFSMaiden 84 rhythm, pass “2 · gain fix”: -13.7 LUFSMaiden 86 rhythm, pass “2 · gain fix”: -10 LUFSMaiden 88 rhythm, pass “2 · gain fix”: -13.4 LUFSMaiden 92 rhythm, pass “2 · gain fix”: -13.4 LUFSMaiden 2000s rhythm, pass “2 · gain fix”: -12.6 LUFS4.6 dB3 · trim + balanceMaiden 82 rhythm, pass “3 · trim + balance”: -14.2 LUFSMaiden 83 rhythm, pass “3 · trim + balance”: -17.7 LUFSMaiden 84 rhythm, pass “3 · trim + balance”: -14.3 LUFSMaiden 86 rhythm, pass “3 · trim + balance”: -17 LUFSMaiden 88 rhythm, pass “3 · trim + balance”: -14.2 LUFSMaiden 92 rhythm, pass “3 · trim + balance”: -14.1 LUFSMaiden 2000s rhythm, pass “3 · trim + balance”: -14.2 LUFS3.6 dB4 · second trimMaiden 82 rhythm, pass “4 · second trim”: -14 LUFSMaiden 83 rhythm, pass “4 · second trim”: -14.1 LUFSMaiden 84 rhythm, pass “4 · second trim”: -13.9 LUFSMaiden 86 rhythm, pass “4 · second trim”: -14 LUFSMaiden 88 rhythm, pass “4 · second trim”: -14 LUFSMaiden 92 rhythm, pass “4 · second trim”: -14 LUFSMaiden 2000s rhythm, pass “4 · second trim”: -14 LUFS0.2 dBfinal · dyn. onMaiden 82 rhythm, pass “final · dyn. on”: -11.7 LUFSMaiden 83 rhythm, pass “final · dyn. on”: -11.9 LUFSMaiden 84 rhythm, pass “final · dyn. on”: -11.5 LUFSMaiden 86 rhythm, pass “final · dyn. on”: -11.5 LUFSMaiden 88 rhythm, pass “final · dyn. on”: -11.7 LUFSMaiden 92 rhythm, pass “final · dyn. on”: -11.6 LUFSMaiden 2000s rhythm, pass “final · dyn. on”: -12.5 LUFS1.0 dBClean (target −15.5 LUFS)1 · deliveredMaiden 82 clean, pass “1 · delivered”: -14.4 LUFSMaiden 83 clean, pass “1 · delivered”: -13.6 LUFSMaiden 84 clean, pass “1 · delivered”: -15.9 LUFSMaiden 86 clean, pass “1 · delivered”: -13.5 LUFSMaiden 88 clean, pass “1 · delivered”: -12.9 LUFSMaiden 92 clean, pass “1 · delivered”: -14.9 LUFSMaiden 2000s clean, pass “1 · delivered”: -12.6 LUFS3.3 dB2 · gain fixMaiden 82 clean, pass “2 · gain fix”: -14.5 LUFSMaiden 83 clean, pass “2 · gain fix”: -13.6 LUFSMaiden 84 clean, pass “2 · gain fix”: -15.9 LUFSMaiden 86 clean, pass “2 · gain fix”: -13.5 LUFSMaiden 88 clean, pass “2 · gain fix”: -12.9 LUFSMaiden 92 clean, pass “2 · gain fix”: -15 LUFSMaiden 2000s clean, pass “2 · gain fix”: -12.6 LUFS3.3 dB3 · trim + balanceMaiden 82 clean, pass “3 · trim + balance”: -18.1 LUFSMaiden 83 clean, pass “3 · trim + balance”: -16.7 LUFSMaiden 84 clean, pass “3 · trim + balance”: -16.1 LUFSMaiden 86 clean, pass “3 · trim + balance”: -15.6 LUFSMaiden 88 clean, pass “3 · trim + balance”: -15.6 LUFSMaiden 92 clean, pass “3 · trim + balance”: -15.4 LUFSMaiden 2000s clean, pass “3 · trim + balance”: -16.8 LUFS2.7 dB4 · second trimMaiden 82 clean, pass “4 · second trim”: -15.6 LUFSMaiden 83 clean, pass “4 · second trim”: -15.5 LUFSMaiden 84 clean, pass “4 · second trim”: -15.5 LUFSMaiden 86 clean, pass “4 · second trim”: -15.5 LUFSMaiden 88 clean, pass “4 · second trim”: -15.3 LUFSMaiden 92 clean, pass “4 · second trim”: -15.5 LUFSMaiden 2000s clean, pass “4 · second trim”: -15.5 LUFS0.3 dBfinal · dyn. onMaiden 82 clean, pass “final · dyn. on”: -13.3 LUFSMaiden 83 clean, pass “final · dyn. on”: -13.7 LUFSMaiden 84 clean, pass “final · dyn. on”: -12.9 LUFSMaiden 86 clean, pass “final · dyn. on”: -13.3 LUFSMaiden 88 clean, pass “final · dyn. on”: -13.3 LUFSMaiden 92 clean, pass “final · dyn. on”: -13.5 LUFSMaiden 2000s clean, pass “final · dyn. on”: -12.8 LUFS0.9 dBAcoustic (target −15.5 LUFS)1 · deliveredMaiden 82 acoustic, pass “1 · delivered”: -13.7 LUFSMaiden 83 acoustic, pass “1 · delivered”: -15.5 LUFSMaiden 84 acoustic, pass “1 · delivered”: -13.6 LUFSMaiden 86 acoustic, pass “1 · delivered”: -13.6 LUFSMaiden 88 acoustic, pass “1 · delivered”: -13.7 LUFSMaiden 92 acoustic, pass “1 · delivered”: 10.9 LUFSMaiden 2000s acoustic, pass “1 · delivered”: -3.9 LUFS26.4 dB2 · gain fixMaiden 82 acoustic, pass “2 · gain fix”: -13.7 LUFSMaiden 83 acoustic, pass “2 · gain fix”: -15.4 LUFSMaiden 84 acoustic, pass “2 · gain fix”: -13.6 LUFSMaiden 86 acoustic, pass “2 · gain fix”: -22.4 LUFSMaiden 88 acoustic, pass “2 · gain fix”: -13.7 LUFSMaiden 92 acoustic, pass “2 · gain fix”: -9.8 LUFSMaiden 2000s acoustic, pass “2 · gain fix”: -3.9 LUFS18.5 dB3 · trim + balanceMaiden 82 acoustic, pass “3 · trim + balance”: -16.8 LUFSMaiden 83 acoustic, pass “3 · trim + balance”: -16.5 LUFSMaiden 84 acoustic, pass “3 · trim + balance”: -15.6 LUFSMaiden 86 acoustic, pass “3 · trim + balance”: -16.6 LUFSMaiden 88 acoustic, pass “3 · trim + balance”: -15.8 LUFSMaiden 92 acoustic, pass “3 · trim + balance”: -15.5 LUFSMaiden 2000s acoustic, pass “3 · trim + balance”: -15.6 LUFS1.3 dB4 · second trimMaiden 82 acoustic, pass “4 · second trim”: -15.4 LUFSMaiden 83 acoustic, pass “4 · second trim”: -15.5 LUFSMaiden 84 acoustic, pass “4 · second trim”: -15.4 LUFSMaiden 86 acoustic, pass “4 · second trim”: -15.5 LUFSMaiden 88 acoustic, pass “4 · second trim”: -15.4 LUFSMaiden 92 acoustic, pass “4 · second trim”: -15.5 LUFSMaiden 2000s acoustic, pass “4 · second trim”: -15.5 LUFS0.1 dBfinal · dyn. onMaiden 82 acoustic, pass “final · dyn. on”: -13.3 LUFSMaiden 83 acoustic, pass “final · dyn. on”: -14 LUFSMaiden 84 acoustic, pass “final · dyn. on”: -13.7 LUFSMaiden 86 acoustic, pass “final · dyn. on”: -13.6 LUFSMaiden 88 acoustic, pass “final · dyn. on”: -14 LUFSMaiden 92 acoustic, pass “final · dyn. on”: -13.2 LUFSMaiden 2000s acoustic, pass “final · dyn. on”: -13.3 LUFS0.8 dB-24-20-16-12-8-40+4+8+12integrated loudness, LUFS · one dot per album era · label = spread across the 7 erashollow = final pass with the compressor and limiter on (not trimmed further)

The data

passdynamicserasceneLUFSpeak dBFSleft LUFSright LUFS
nodyn-1 as deliveredoffMaiden 82rhythm-13.7-3.2
nodyn-1 as deliveredoffMaiden 82solo-13.3-2.5
nodyn-1 as deliveredoffMaiden 82crunch-14.2-3.3
nodyn-1 as deliveredoffMaiden 82clean-14.42.6
nodyn-1 as deliveredoffMaiden 82acoustic-13.71.2
nodyn-1 as deliveredoffMaiden 83rhythm-9.11.9
nodyn-1 as deliveredoffMaiden 83solo-8.72.2
nodyn-1 as deliveredoffMaiden 83crunch-9.31.3
nodyn-1 as deliveredoffMaiden 83clean-13.62.0
nodyn-1 as deliveredoffMaiden 83acoustic-15.52.1
nodyn-1 as deliveredoffMaiden 84rhythm-13.7-3.1
nodyn-1 as deliveredoffMaiden 84solo-13.2-3.0
nodyn-1 as deliveredoffMaiden 84crunch-14.3-3.2
nodyn-1 as deliveredoffMaiden 84clean-15.9-4.7
nodyn-1 as deliveredoffMaiden 84acoustic-13.6-1.5
nodyn-1 as deliveredoffMaiden 86rhythm-10.00.6
nodyn-1 as deliveredoffMaiden 86solo-9.51.1
nodyn-1 as deliveredoffMaiden 86crunch-10.80.4
nodyn-1 as deliveredoffMaiden 86clean-13.5-0.6
nodyn-1 as deliveredoffMaiden 86acoustic-13.60.4
nodyn-1 as deliveredoffMaiden 88rhythm-13.4-1.0
nodyn-1 as deliveredoffMaiden 88solo-12.20.3
nodyn-1 as deliveredoffMaiden 88crunch-15.1-2.8
nodyn-1 as deliveredoffMaiden 88clean-12.90.9
nodyn-1 as deliveredoffMaiden 88acoustic-13.7-0.1
nodyn-1 as deliveredoffMaiden 92rhythm-13.4-4.1
nodyn-1 as deliveredoffMaiden 92solo-12.6-3.2
nodyn-1 as deliveredoffMaiden 92crunch-14.5-4.4
nodyn-1 as deliveredoffMaiden 92clean-14.9-2.1
nodyn-1 as deliveredoffMaiden 92acoustic10.925.9
nodyn-1 as deliveredoffMaiden 2000srhythm-12.6-1.7
nodyn-1 as deliveredoffMaiden 2000ssolo-12.3-1.1
nodyn-1 as deliveredoffMaiden 2000scrunch-12.8-2.3
nodyn-1 as deliveredoffMaiden 2000sclean-12.61.3
nodyn-1 as deliveredoffMaiden 2000sacoustic-3.98.7
nodyn-2 after gain-staging fixoffMaiden 82rhythm-13.7-3.2-18.5-15.4
nodyn-2 after gain-staging fixoffMaiden 82solo-13.3-2.5-17.9-15.1
nodyn-2 after gain-staging fixoffMaiden 82crunch-14.2-3.3-19.4-15.8
nodyn-2 after gain-staging fixoffMaiden 82clean-14.52.5-25.5-14.9
nodyn-2 after gain-staging fixoffMaiden 82acoustic-13.71.2-14.6-20.9
nodyn-2 after gain-staging fixoffMaiden 83rhythm-9.11.9-9.3-22.4
nodyn-2 after gain-staging fixoffMaiden 83solo-8.72.2-9.0-22.0
nodyn-2 after gain-staging fixoffMaiden 83crunch-9.31.3-9.5-22.8
nodyn-2 after gain-staging fixoffMaiden 83clean-13.62.0-20.8-14.5
nodyn-2 after gain-staging fixoffMaiden 83acoustic-15.42.1-16.6-21.8
nodyn-2 after gain-staging fixoffMaiden 84rhythm-13.7-3.1-18.5-15.5
nodyn-2 after gain-staging fixoffMaiden 84solo-13.2-2.9-17.8-15.1
nodyn-2 after gain-staging fixoffMaiden 84crunch-14.4-3.2-19.4-16.0
nodyn-2 after gain-staging fixoffMaiden 84clean-15.9-4.7-21.6-17.3
nodyn-2 after gain-staging fixoffMaiden 84acoustic-13.6-1.3-18.4-15.3
nodyn-2 after gain-staging fixoffMaiden 86rhythm-10.00.7-21.6-10.3
nodyn-2 after gain-staging fixoffMaiden 86solo-9.50.8-21.4-9.7
nodyn-2 after gain-staging fixoffMaiden 86crunch-10.90.5-21.7-11.3
nodyn-2 after gain-staging fixoffMaiden 86clean-13.5-0.7-16.1-17.0
nodyn-2 after gain-staging fixoffMaiden 86acoustic-22.4-7.6-23.5-28.9
nodyn-2 after gain-staging fixoffMaiden 88rhythm-13.4-1.0-17.5-15.6
nodyn-2 after gain-staging fixoffMaiden 88solo-12.20.4-17.2-13.9
nodyn-2 after gain-staging fixoffMaiden 88crunch-15.1-2.7-17.7-18.6
nodyn-2 after gain-staging fixoffMaiden 88clean-12.90.9-15.4-16.5
nodyn-2 after gain-staging fixoffMaiden 88acoustic-13.7-0.1-15.6-18.1
nodyn-2 after gain-staging fixoffMaiden 92rhythm-13.4-4.1-15.8-17.1
nodyn-2 after gain-staging fixoffMaiden 92solo-12.6-3.2-15.3-16.0
nodyn-2 after gain-staging fixoffMaiden 92crunch-14.5-4.4-16.5-18.8
nodyn-2 after gain-staging fixoffMaiden 92clean-15.0-2.1-19.1-17.2
nodyn-2 after gain-staging fixoffMaiden 92acoustic-9.82.5-15.2-11.3
nodyn-2 after gain-staging fixoffMaiden 2000srhythm-12.6-1.7-16.9-14.7
nodyn-2 after gain-staging fixoffMaiden 2000ssolo-12.3-1.0-16.5-14.4
nodyn-2 after gain-staging fixoffMaiden 2000scrunch-12.8-2.3-16.9-15.0
nodyn-2 after gain-staging fixoffMaiden 2000sclean-12.61.3-13.4-20.3
nodyn-2 after gain-staging fixoffMaiden 2000sacoustic-3.98.7-5.9-8.2
nodyn-3 after trim + side balanceoffMaiden 82rhythm-14.2-5.0-17.2-17.2
nodyn-3 after trim + side balanceoffMaiden 82solo-13.7-4.2-16.6-16.9
nodyn-3 after trim + side balanceoffMaiden 82crunch-14.8-5.1-18.1-17.5
nodyn-3 after trim + side balanceoffMaiden 82clean-18.1-3.7-21.1-21.1
nodyn-3 after trim + side balanceoffMaiden 82acoustic-16.8-2.7-19.8-19.7
nodyn-3 after trim + side balanceoffMaiden 83rhythm-17.7-9.5-20.7-20.8
nodyn-3 after trim + side balanceoffMaiden 83solo-17.4-8.9-20.4-20.4
nodyn-3 after trim + side balanceoffMaiden 83crunch-18.0-10.1-20.9-21.2
nodyn-3 after trim + side balanceoffMaiden 83clean-16.7-2.7-20.0-19.5
nodyn-3 after trim + side balanceoffMaiden 83acoustic-16.5-0.8-19.4-19.7
nodyn-3 after trim + side balanceoffMaiden 84rhythm-14.3-4.9-17.2-17.3
nodyn-3 after trim + side balanceoffMaiden 84solo-13.8-4.4-16.6-16.9
nodyn-3 after trim + side balanceoffMaiden 84crunch-15.0-5.0-18.2-17.8
nodyn-3 after trim + side balanceoffMaiden 84clean-16.1-4.6-19.1-19.1
nodyn-3 after trim + side balanceoffMaiden 84acoustic-15.6-3.2-20.4-17.3
nodyn-3 after trim + side balanceoffMaiden 86rhythm-17.0-8.5-20.0-20.0
nodyn-3 after trim + side balanceoffMaiden 86solo-16.6-7.7-19.8-19.4
nodyn-3 after trim + side balanceoffMaiden 86crunch-17.5-9.1-20.1-21.0
nodyn-3 after trim + side balanceoffMaiden 86clean-15.6-2.4-18.6-18.7
nodyn-3 after trim + side balanceoffMaiden 86acoustic-16.6-3.2-19.3-20.0
nodyn-3 after trim + side balanceoffMaiden 88rhythm-14.2-2.5-17.2-17.2
nodyn-3 after trim + side balanceoffMaiden 88solo-13.1-1.1-16.9-15.5
nodyn-3 after trim + side balanceoffMaiden 88crunch-15.6-4.3-17.4-20.2
nodyn-3 after trim + side balanceoffMaiden 88clean-15.6-1.8-18.6-18.7
nodyn-3 after trim + side balanceoffMaiden 88acoustic-15.8-2.5-18.6-19.0
nodyn-3 after trim + side balanceoffMaiden 92rhythm-14.1-5.3-17.1-17.0
nodyn-3 after trim + side balanceoffMaiden 92solo-13.3-4.3-16.6-16.0
nodyn-3 after trim + side balanceoffMaiden 92crunch-15.2-5.6-17.8-18.8
nodyn-3 after trim + side balanceoffMaiden 92clean-15.4-1.8-18.4-18.4
nodyn-3 after trim + side balanceoffMaiden 92acoustic-15.5-3.2-20.9-17.0
nodyn-3 after trim + side balanceoffMaiden 2000srhythm-14.2-3.2-17.2-17.2
nodyn-3 after trim + side balanceoffMaiden 2000ssolo-13.8-2.8-16.8-16.8
nodyn-3 after trim + side balanceoffMaiden 2000scrunch-14.3-2.8-17.2-17.5
nodyn-3 after trim + side balanceoffMaiden 2000sclean-16.8-3.0-19.8-19.8
nodyn-3 after trim + side balanceoffMaiden 2000sacoustic-15.6-4.1-18.7-18.6
nodyn-4 after second trimoffMaiden 82rhythm-14.0-4.8-17.0-17.0
nodyn-4 after second trimoffMaiden 82solo-13.5-4.0-16.4-16.7
nodyn-4 after second trimoffMaiden 82crunch-14.6-4.9-17.9-17.4
nodyn-4 after second trimoffMaiden 82clean-15.6-1.1-18.6-18.6
nodyn-4 after second trimoffMaiden 82acoustic-15.4-1.4-18.4-18.4
nodyn-4 after second trimoffMaiden 83rhythm-14.1-5.9-17.1-17.1
nodyn-4 after second trimoffMaiden 83solo-13.7-5.2-16.8-16.7
nodyn-4 after second trimoffMaiden 83crunch-14.4-6.4-17.3-17.5
nodyn-4 after second trimoffMaiden 83clean-15.5-1.5-18.5-18.6
nodyn-4 after second trimoffMaiden 83acoustic-15.50.3-18.5-18.5
nodyn-4 after second trimoffMaiden 84rhythm-13.9-4.5-17.0-16.9
nodyn-4 after second trimoffMaiden 84solo-13.4-4.2-16.3-16.5
nodyn-4 after second trimoffMaiden 84crunch-14.6-4.6-17.9-17.4
nodyn-4 after second trimoffMaiden 84clean-15.5-4.0-18.5-18.5
nodyn-4 after second trimoffMaiden 84acoustic-15.4-3.1-20.2-17.1
nodyn-4 after second trimoffMaiden 86rhythm-14.0-5.5-17.0-17.0
nodyn-4 after second trimoffMaiden 86solo-13.6-4.7-16.8-16.5
nodyn-4 after second trimoffMaiden 86crunch-14.5-6.1-17.1-18.0
nodyn-4 after second trimoffMaiden 86clean-15.5-2.3-18.5-18.5
nodyn-4 after second trimoffMaiden 86acoustic-15.5-2.1-18.5-18.5
nodyn-4 after second trimoffMaiden 88rhythm-14.0-2.3-17.0-17.0
nodyn-4 after second trimoffMaiden 88solo-12.9-1.0-16.7-15.3
nodyn-4 after second trimoffMaiden 88crunch-15.4-4.1-17.2-20.0
nodyn-4 after second trimoffMaiden 88clean-15.3-1.3-18.3-18.3
nodyn-4 after second trimoffMaiden 88acoustic-15.4-2.1-18.4-18.4
nodyn-4 after second trimoffMaiden 92rhythm-14.0-5.2-16.9-17.0
nodyn-4 after second trimoffMaiden 92solo-13.2-4.3-16.4-16.0
nodyn-4 after second trimoffMaiden 92crunch-15.2-5.5-17.6-18.8
nodyn-4 after second trimoffMaiden 92clean-15.5-1.9-18.6-18.5
nodyn-4 after second trimoffMaiden 92acoustic-15.5-3.2-20.9-17.0
nodyn-4 after second trimoffMaiden 2000srhythm-14.0-3.0-17.0-17.0
nodyn-4 after second trimoffMaiden 2000ssolo-13.6-2.6-16.6-16.6
nodyn-4 after second trimoffMaiden 2000scrunch-14.1-2.6-17.0-17.3
nodyn-4 after second trimoffMaiden 2000sclean-15.5-1.7-18.5-18.5
nodyn-4 after second trimoffMaiden 2000sacoustic-15.5-3.9-18.5-18.5
final - Solo block - dynamics ononMaiden 82rhythm-11.7-2.0-14.8-14.7
final - Solo block - dynamics ononMaiden 82solo-10.0-1.0-12.9-13.2
final - Solo block - dynamics ononMaiden 82crunch-12.2-2.6-15.5-14.9
final - Solo block - dynamics ononMaiden 82clean-13.3-1.0-16.3-16.3
final - Solo block - dynamics ononMaiden 82acoustic-13.3-1.0-16.3-16.4
final - Solo block - dynamics ononMaiden 83rhythm-11.9-3.4-14.9-14.9
final - Solo block - dynamics ononMaiden 83solo-10.3-1.8-13.4-13.3
final - Solo block - dynamics ononMaiden 83crunch-12.2-3.8-15.1-15.2
final - Solo block - dynamics ononMaiden 83clean-13.7-1.0-16.7-16.7
final - Solo block - dynamics ononMaiden 83acoustic-14.0-1.0-16.9-17.0
final - Solo block - dynamics ononMaiden 84rhythm-11.5-2.7-14.6-14.5
final - Solo block - dynamics ononMaiden 84solo-9.7-1.0-12.7-12.8
final - Solo block - dynamics ononMaiden 84crunch-12.1-2.6-15.3-14.8
final - Solo block - dynamics ononMaiden 84clean-12.9-2.4-15.9-15.9
final - Solo block - dynamics ononMaiden 84acoustic-13.7-2.4-18.5-15.5
final - Solo block - dynamics ononMaiden 86rhythm-11.5-2.9-14.5-14.5
final - Solo block - dynamics ononMaiden 86solo-9.6-1.0-12.8-12.5
final - Solo block - dynamics ononMaiden 86crunch-11.9-3.4-14.5-15.4
final - Solo block - dynamics ononMaiden 86clean-13.3-1.0-16.3-16.3
final - Solo block - dynamics ononMaiden 86acoustic-13.6-1.2-16.6-16.6
final - Solo block - dynamics ononMaiden 88rhythm-11.7-1.0-14.7-14.8
final - Solo block - dynamics ononMaiden 88solo-9.8-1.0-13.3-12.3
final - Solo block - dynamics ononMaiden 88crunch-12.8-1.7-14.5-17.5
final - Solo block - dynamics ononMaiden 88clean-13.3-1.0-16.3-16.3
final - Solo block - dynamics ononMaiden 88acoustic-14.0-1.1-16.9-17.1
final - Solo block - dynamics ononMaiden 92rhythm-11.6-2.6-14.5-14.6
final - Solo block - dynamics ononMaiden 92solo-9.5-1.0-12.7-12.4
final - Solo block - dynamics ononMaiden 92crunch-12.5-2.8-15.0-16.2
final - Solo block - dynamics ononMaiden 92clean-13.5-1.0-16.5-16.5
final - Solo block - dynamics ononMaiden 92acoustic-13.2-1.2-18.6-14.7
final - Solo block - dynamics ononMaiden 2000srhythm-12.5-3.4-15.5-15.4
final - Solo block - dynamics ononMaiden 2000ssolo-11.1-2.2-14.1-14.1
final - Solo block - dynamics ononMaiden 2000scrunch-12.6-3.1-15.6-15.7
final - Solo block - dynamics ononMaiden 2000sclean-12.8-1.0-15.8-15.8
final - Solo block - dynamics ononMaiden 2000sacoustic-13.3-2.1-16.4-16.3

D9: every era's loudness in each measurement pass, dynamics bypassed until the last row. Dashed line = target. Hover a dot for the era. Data: maiden-levels.csv.

Dave Murray (left)Smith / Gers (right)First per-side sweep (pass 2)-24-20-16-12-8'82 · SmithMaiden 82 heavy rhythm, First per-side sweep (pass 2): Murray (left) -18.5 LUFSMaiden 82 heavy rhythm, First per-side sweep (pass 2): Smith (right) -15.4 LUFS3.1 dB'83 · SmithMaiden 83 heavy rhythm, First per-side sweep (pass 2): Murray (left) -9.3 LUFSMaiden 83 heavy rhythm, First per-side sweep (pass 2): Smith (right) -22.4 LUFS13.1 dB'84 · SmithMaiden 84 heavy rhythm, First per-side sweep (pass 2): Murray (left) -18.5 LUFSMaiden 84 heavy rhythm, First per-side sweep (pass 2): Smith (right) -15.5 LUFS3.0 dB'86 · SmithMaiden 86 heavy rhythm, First per-side sweep (pass 2): Murray (left) -21.6 LUFSMaiden 86 heavy rhythm, First per-side sweep (pass 2): Smith (right) -10.3 LUFS11.3 dB'88 · SmithMaiden 88 heavy rhythm, First per-side sweep (pass 2): Murray (left) -17.5 LUFSMaiden 88 heavy rhythm, First per-side sweep (pass 2): Smith (right) -15.6 LUFS1.9 dB'92 · GersMaiden 92 heavy rhythm, First per-side sweep (pass 2): Murray (left) -15.8 LUFSMaiden 92 heavy rhythm, First per-side sweep (pass 2): Gers (right) -17.1 LUFS1.3 dB2000s · SmithMaiden 2000s heavy rhythm, First per-side sweep (pass 2): Murray (left) -16.9 LUFSMaiden 2000s heavy rhythm, First per-side sweep (pass 2): Smith (right) -14.7 LUFS2.2 dBAfter trim + balance (pass 4)-24-20-16-12-8Maiden 82 heavy rhythm, After trim + balance (pass 4): Murray (left) -17 LUFSMaiden 82 heavy rhythm, After trim + balance (pass 4): Smith (right) -17 LUFS0.0 dBMaiden 83 heavy rhythm, After trim + balance (pass 4): Murray (left) -17.1 LUFSMaiden 83 heavy rhythm, After trim + balance (pass 4): Smith (right) -17.1 LUFS0.0 dBMaiden 84 heavy rhythm, After trim + balance (pass 4): Murray (left) -17 LUFSMaiden 84 heavy rhythm, After trim + balance (pass 4): Smith (right) -16.9 LUFS0.1 dBMaiden 86 heavy rhythm, After trim + balance (pass 4): Murray (left) -17 LUFSMaiden 86 heavy rhythm, After trim + balance (pass 4): Smith (right) -17 LUFS0.0 dBMaiden 88 heavy rhythm, After trim + balance (pass 4): Murray (left) -17 LUFSMaiden 88 heavy rhythm, After trim + balance (pass 4): Smith (right) -17 LUFS0.0 dBMaiden 92 heavy rhythm, After trim + balance (pass 4): Murray (left) -16.9 LUFSMaiden 92 heavy rhythm, After trim + balance (pass 4): Gers (right) -17 LUFS0.1 dBMaiden 2000s heavy rhythm, After trim + balance (pass 4): Murray (left) -17 LUFSMaiden 2000s heavy rhythm, After trim + balance (pass 4): Smith (right) -17 LUFS0.0 dBper-channel integrated loudness of the heavy RHYTHM scene, LUFS (dynamics bypassed)

D9: the two guitarists in each era's heavy rhythm, measured per channel. Pass 1 has no per-side numbers, so the first per-side sweep (pass 2) is the before. Data: maiden-levels.csv.

BeforeAfter (no dynamics)Final (with dynamics)
Heavy rhythm−9.1 … −13.7−13.9 … −14.1−11.5 … −12.5
Clean−12.6 … −15.9−15.3 … −15.6−12.8 … −13.7
Acoustic−15.5 … +10.9−15.4 … −15.5−13.2 … −14.0
L/R side mismatch, heavyup to 13.1 dB≤ 0.1 dB≤ 0.1 dB
Solo over rhythm+0.4 … +1.1 dB+2.9 … +3.1 dB+1.4 … +2.1 dB

Loudness in LUFS unless noted. From experiment D9 in experiments/README.md.

All 21 presets now sit within 0.2 dB of their targets before the dynamics, with both guitarists within 0.1 dB of each other. Murray on the left, Smith (or Gers) on the right, at the same volume, the way the records pan them.

Xruns: scenes vs. song changes

Last question: does all this switching click?

D10. Method: one continuous 3-minute DI stream, so no audio nodes join or leave the graph during the run, and Qtractor's error count from pw-top. Phase A: 30 scene switches in 60 seconds, with PCs exactly as the FCB sends them. Phase B: 10 bank changes in 60 seconds, each loading two presets.

A · 30 scene switches in 60 s, PCs exactly as the FCB sends them: 0 xrunsscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunscene switch: no xrunB · 10 bank changes in 60 s = 20 preset loads: 1 xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunone xrun during phase B (which load is not recorded; position is arbitrary)preset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunpreset load: no xrunno xrunone xrun somewhere in phase B (position arbitrary)one continuous 3-min DI stream · Qtractor ERR from pw-top · DSP median 38 %, peak 87 % (preset loads)

D10: one square per switch. The run logs only the total per phase, so the red square's position is arbitrary. The loop turned out to be silent: see the correction below.

Zero xruns across 30 scene switches. One xrun across 20 preset loads, so about 1 in 20 preset loads glitches, and only at a song change, where a click between songs is the least of anyone's worries. The DSP median was 38% of the cycle, with the 87% peak during preset loads.

One more lesson for the measurement notebook: an earlier sweep counted 9 xruns, because its recorder and player joined the audio graph for every single reading. Graph churn causes xruns too. Measure with persistent nodes.

Correction. That lesson didn't go far enough. While measuring the harmony below, we found that D10's DI loop was a headerless .raw file, and pw-play can't read those. The rig had been processing silence. Re-measured with real audio, the two-instance rig at steady state runs at 0.32 B/Q median with 0 xruns in 30 seconds. The scene and bank numbers for the rig as it stands now are in the next section, and they're less rosy. Measure your measurement.

Twin guitars: the harmony footswitch

Here's the thing about "world class" Maiden: it's two guitars. Murray and Smith playing the same line in harmony is the sound, from Hallowed Be Thy Name to The Evil That Men Do. A floorboard of perfectly levelled amps with one guitar on it is a very good cover band.

So I asked for it, with the same condition as every other feature on this board: go ahead, "as long as it's toggleable". SW7 in the Maiden banks became HARMONY. Stomp it and the second guitarist joins, a third above whatever you play, through the partner's own amp. Stomp it again and the second guitar drops out.

A third above what, though? In E minor, a third above E is G, three semitones up. A third above G is B, four semitones up. A fixed pitch shift gets half the notes wrong. The harmony has to know the key, and pick the minor or major third note by note, the way a second guitarist reading the same chart would.

The test. No listening by ear, not for this. A synthetic plucked melody runs up the E minor scale for an octave, E4 to E5, eight notes. The harmony voice is recorded on its own and pitch-tracked with YIN every 10 ms. Score: the share of frames that sit on the right third.

v1: Rubber Band +3.5 semitones, then x42's autotune, both after the amp. The idea: shift everything halfway between a minor and a major third, then let the autotune snap to the nearest note in the key. Exactly one of the two thirds is in the key, so the snap picks it. Neat on paper. On the rig: right on 63.4% of frames.

So we took it apart, on a clean tone. The autotune alone pulled every out-of-key note down onto the scale. Rubber Band alone shifted by exactly +3.50 semitones. Together, they put 8 of 8 thirds in place, each within 0.02 semitones. Both parts worked. The chain didn't. The only difference was the input: the failing run was pitch-tracking a distorted signal, after the amp.

v2: harmonise before the amp. If distortion confuses the tracker, don't show it distortion. A Harmony Tap on the heavy track sends the clean DI to a new Harmony track, which shifts and snaps the dry guitar and then runs it through a third TONE3000 loaded with the era's rig. Through the distorted Maiden 83 rig: 8 of 8 notes, 100% of frames. And 80–85 xruns in 30 seconds, with B/Q at 0.53 median against 0.32 for the two instances. Perfect pitch, unplayable.

Frames on the right third (%)Xruns in 30 ssteady state, real audio02550751000306090v1 · Rubber Band +3.5, then autotune,both after the ampv1 · Rubber Band +3.5, then autotune, both after the amp: 63.4 % of frames on the right third63.4 %not measuredv2 · Rubber Band + autotune on the DI,then a third TONE3000v2 · Rubber Band + autotune on the DI, then a third TONE3000: 100 % of frames on the right third, 8/8 notes100 %8/8 notesv2: 80–85 xruns in 30 s (B/Q 0.53 median)80–85v3 · three autotune stages on the DI,then a light partner presetv3 · three autotune stages on the DI, then a light partner preset: 100 % of frames on the right third, 8/8 notes100 %8/8 notesv3: 0 xruns in the first 30 s run0test melody E4 … E5, E minor · YIN pitch track, 10 ms frames

D11: the three versions of the harmony. v1's xruns weren't measured; v3's 0 is the first 30 s run. Source: experiments/README.md D11.

Which part was it? Same method as always: one variable at a time. Each piece of v2 on its own, on top of the two-instance rig, at steady state with real audio this time.

DSP load, B/Q (bar = median, tick = max)Xruns / 30 s00.20.40.60.80306090heavy + clean (two instances)heavy + clean (two instances): median 0.32 B/Q, max 0.44heavy + clean (two instances): max 0.44 B/Q0.32 / 0.44heavy + clean (two instances): 0 xruns in 30 s0+ the input loop only+ the input loop only: median 0.33 B/Q, max 0.50+ the input loop only: max 0.50 B/Q0.33 / 0.50+ the input loop only: 0 xruns in 30 s0+ Rubber Band and autotune only+ Rubber Band and autotune only: median 0.38 B/Q, max 0.71+ Rubber Band and autotune only: max 0.71 B/Q0.38 / 0.71+ Rubber Band and autotune only: 1 xruns in 30 s1+ the third TONE3000 only+ the third TONE3000 only: median 0.45 B/Q, max 0.68+ the third TONE3000 only: max 0.68 B/Q0.45 / 0.68+ the third TONE3000 only: 0 xruns in 30 s0all of itall of it: median 0.52 B/Q, max 0.70all of it: max 0.70 B/Q0.52 / 0.70all of it: 85 xruns in 30 s85v3: autotune stages + light presetv3: autotune stages + light preset: median 0.48 B/Q, max 0.60v3: autotune stages + light preset: max 0.60 B/Q0.48 / 0.60v3: autotune stages + light preset: 0 xruns in 30 s0steady state, real audio · middle rows: one part added at a time

D11: v2's parts one at a time on top of the two-instance rig, then all of them, then the v3 rebuild. Hover a bar for the value. Source: the table in experiments/README.md D11.

No single culprit. Every part alone was fine: one xrun, at most. All of it together: 85. The code comment that came out of this says it plainly: Rubber Band's FFT spikes, on top of a third TONE3000, were too much for the cycle.

v3: drop the pitch shifter. The fix came from a detail of x42's autotune. Besides snapping, it can shift by an offset. And when a note lands exactly halfway between two notes of the scale, it resolves the tie downward. So "shift up 2 semitones, then snap" is always exactly one step up the scale. If the whole step stays in the key, it stays. If it leaves the key, it lands precisely between two scale notes (a diatonic scale never has two out-of-key semitones in a row), and the tie pulls it back to the half step. Two of those stages make a third. A last stage only snaps. Here's what that does to every note of the test melody:

E natural minor (E F# G A B C D) · x42 set to G major, the same notesyou playE4F#4G4A4B4C5D5E5step 1+2, snapF#GG# → tie ↓ABCC# → tie ↓DEF#step 2+2, snapGG# → tie ↓ABCC# → tie ↓DEF#GG# → tie ↓last stagesnap onlyGABCDEF#Gintervalminor 3rdminor 3rdmajor 3rdminor 3rdminor 3rdmajor 3rdmajor 3rdminor 3rd“G# → tie ↓”: the whole step left the key, sat exactly between two scale notes, and snapped down

What the three autotune stages do to the E minor test melody, following the tie rule in qtractor_rig.py. The pitch test found all eight thirds (D11).

A minor third where the key has one, a major third where it doesn't, and no FFT anywhere. It's exactly the bridge of The Evil That Men Do, per the research notes in rigs/27: E–F#–G over G–A–B.

The third TONE3000 went on a diet too. Instead of the era's full dual rig, the builder generates a one-chain harmony preset per bank from the heavy preset's right-hand side: the partner guitarist's amp and cab, Smith's (or Gers', for 1992), with no reverb IRs (harmony_rig(), PCs 36–42 in RIGS.md). It loads on PC channel 3, so RHYTHM, CLEAN and ACOUSTIC now load three presets per bank.

HEAVY TRACKGuitarScarlett In 2SelectorCC 81Harmony Tappasses on + sendsWah → TONE3000 heavy → Octaver → Solothe scene engine aboveHarmony InSW7 · CC 25toggleStep 1x42 autotune+2, snapStep 2x42 autotune+2, snapSnapx42 autotuneoffset 0TONE3000PC ch 3partner amp + cabbus Rigcomp + limiterHARMONY TRACK · clean DI in, the partner guitarist outthe clean DI, as a send: the heavy chain is unchangedPER SONGhelperuser service, started by upheavy PC of a scene bank → CC 85: the Solo echo time→ CC 86, 87, 88: the song's scale, one CC per autotune stage

The harmony track as qtractor_rig.py generates it. Gold = switched from the floor; dashed = MIDI.

v3, measured: 8 of 8 notes, 100% of frames, B/Q 0.48 median and 0.60 max, and 0 xruns in the first 30-second run. Last, the level. The second guitarist should sound like one guitarist, and the main pair is two. Untrimmed, the harmony came in 1.3–2.7 dB under the pair; each era's harmony preset is now trimmed to 3 dB under.

Then the reverb tails. A third instance also changed what a bank change costs: 7 bank changes, 71 xruns. With the long echo and reverb IRs taken out: 0, at 0.40 B/Q. Long convolutions stall preset loads. So every reverb tail is now cut at 2.0 seconds (IR_TAIL_MAX_S in tone3000.py; they had been 3.5 s). The reverbs stay, and bank changes went to 0 xruns over 7. Scene switches: 3 over 20.

And then the honest part. Repeat steady runs of the same rig, nothing touched: 31, 4 and 12 xruns, at a median load of only 0.43–0.55 of the cycle. A load that low doesn't explain xruns that random. Scheduling does. No audio thread on this machine was running real-time: Qtractor's and PipeWire's data loops were SCHED_OTHER, ulimit -r was 0, and PipeWire's log said RTKit error: ServiceUnknown. Remember the realtime group from act one, the one my session didn't have yet? I'm in it, realtime-privileges is installed, but the group was added after I logged in, so no running process has it.

020406080Three TONE3000s: bank and scene changes7 bank changes, 3.5 s reverb tails7 bank changes, 3.5 s reverb tails: 71 xruns717 bank changes, long IRs removed7 bank changes, long IRs removed: 0 xruns07 bank changes, tails cut at 2.0 s7 bank changes, tails cut at 2.0 s: 0 xruns020 scene switches, tails cut at 2.0 s20 scene switches, tails cut at 2.0 s: 3 xruns3Same rig, nothing touched: three repeat steady runsrun 1run 1: 31 xruns31run 2run 2: 4 xruns4run 3run 3: 12 xruns12xruns · the steady runs: median only 0.43–0.55 B/Q

D11: xruns with three TONE3000s. The 3.5 s tail length is from the IR_TAIL_MAX_S comment; the run lengths of the repeat steady runs aren't logged.

The fix should be boring: log out and back in, or install rtkit. Then measure everything again.

D13, from the round that added the tuner, says it wasn't that boring. rtkit gave PipeWire real-time priority, but PipeWire's rtkit path also caps a real-time process at 200 ms of CPU, and Qtractor got killed at 0.47 s every time, even with an empty session. So JACK clients now skip rtkit (~/.config/pipewire/jack.conf) and take real time from the realtime group's limits instead, which have no cap, once that group is active in my login session. That still needs the re-login, so every xrun number in this act comes from a session with no real-time audio thread.

Every song gets its own echo and key

HARMONY needs to know the key, and SOLO's echo should land on the song's tempo, not a generic 380 ms. Both are per song, and the FCB has no room left: it sends two CCs per switch, and CC 80 and 81 took both.

So a small helper does it. qtractor_rig.py helper is a user service that up starts, and it watches the FCB. (Today GuitarMood does the same job in-process.) When a scene bank's heavy Program Change goes by, it sends Qtractor two values: the song's solo echo time on CC 85, to the Solo block's Delay 1 time, and its harmony scale to the three autotune stages. That's CC 86, 87 and 88 for one value, because Qtractor still allows one observer per CC.

Which song? One more round of research: for each era, the signature harmony song, with its key and tempo from Musicnotes' official transcriptions and from note data extracted from Songsterr. The echo is a quarter note at the song's tempo.

BankPresetSongKeyx42 scaleSolo echoBPM
03Maiden 82Hallowed Be Thy NameE natural minorG Major571 ms105
04Maiden 83The TrooperE natural minorG Major375 ms160
05Maiden 84Rime of the Ancient MarinerE natural minorG Major536 ms112
06Maiden 86Wasted YearsE natural minorG Major390 ms154
07Maiden 88The Evil That Men DoE natural minorG Major375 ms160
08Maiden 92Fear of the DarkD DorianC Major750 ms80
09Maiden 2000sBlood BrothersE natural minorG Major339 ms177

Generated from the song block in each heavy preset (rigs 15, 18, …, 33), sources included. The autotune gets the scale with the same notes: G major for E natural minor, C major for D Dorian.

The research also turned up two places where a third is the wrong answer, and the notes say so: The Trooper's main riff is harmonised in sixths, and the record plays Fear of the Dark's melody in unison. On Fear of the Dark, leave SW7 off. On The Trooper, a third is a stand-in: closer to the record than one guitar, but not the record.

D12, the test: replay an FCB Program Change 30 (bank 08, Fear of the Dark) into the helper, then read the saved session back. The Solo delay said 748 ms. The target is 750, and one CC step is 7.9 ms, so that's as close as a CC gets. All three autotune stages said scale 1: C major, the notes of D Dorian.

Act four · five tuners

SW10: five tries at a tuner

Five tuners with screenshots, and 250 xruns from the wrong input.

The last switch should have been the easy one. Every pedalboard has a tuner, and TONE3000 even has one built in. But it's a button you click in its UI (setTunerEnabled, from its web UI) with no MIDI target, so no footswitch can reach it. So SW10 goes through the helper: CC 28 mutes the rig and opens a tuner on the guitar, and the next press closes it and brings the rig back.

That was Sunday. Picking the tuner is where it went sideways.

Try 1: hand-rolled, in Python. Claude's first attempt. I didn't want to maintain a custom tuner. I wanted existing software.

Try 2: x42 Tuna, in its own window via jalv. It comes with the plugin pack the rig already installs. But x42's UIs embed as X11 and jalv's GtkPlug crashes under Wayland, so it needed XWayland. It worked. It was ugly.

Try 3: FMIT, a native Qt tuner. Its layouts are fixed, and Hyprland's tiling crushed them:

FMIT tiled by Hyprland: a large empty Error graph fills most of the window, and the tuner's numeric readout is squeezed into the bottom-right corner

FMIT in a Hyprland tile. An empty "Error" graph takes most of the window, and the readout I actually need is squeezed into the bottom-right corner, cut off at the edge.

My review, verbatim: "insanely ugly with bad aspect ratios". And not playing nice with Hyprland.

Try 4: Chromatic, a GTK4 tuner from Flathub. One big note name, one big gauge, nothing else, which is exactly what a tuner should be. In the default tile, though, Hyprland gave it 466×508, which clipped the gauge, and Omarchy's default opacity let the desktop show through. One window rule fixed both: float it, centre it, 1100×760, fully opaque. In ~/.config/hypr/hyprland.lua:

o.window("^io\\.github\\.nate_xyz\\.Chromatic$", {
  float = true, center = true, size = { 1100, 760 },
  tag = "-default-opacity", opacity = "1 1" })
Chromatic in the default Hyprland tile, reading B2 at +44 cents, with the right end of its gauge and the needle's pivot cut off
Chromatic floating at 1100 by 760 after the window rule, the whole gauge visible, idle with no note

Chromatic before and after the rule. Before, in the default tile: it reads B2 at +44 cents, but the +50 end of the gauge and the needle's pivot are cut off. After: floating, centred, 1100×760 and opaque, idle ("--") and waiting for a note. That was the tuner on SW10, until try 5.

Muting took four tries too (D13). While I tune, the tuner should hear the guitar and nothing else should. A toggled Insert muted fine, then stayed silent after the second press; at the head of the heavy track it also lost its input links. wpctl set-mute can't mute a JACK node ("does not support mute"). Unpatching the guitar doesn't stick either: Qtractor re-patches its saved insert connections within seconds. What works is the Selector trick again: a latch-mode Insert at the head of the heavy track, driven by absolute CC 29 values from the helper. It sits before the Selector and the harmony tap, so all three rigs go quiet and their reverb tails ring out. Replay test: after one press, the mute is on and one tuner window is open; after the second, the mute is off and no windows are left.

Then I practised with it. Playing along, the sound kept cutting up. And the tuner seemed to be following my voice. So I typed:

oh i think it's picking up from my mic?

It was. D14: Qtractor's error count went up by about 250 in 12 seconds (6620 → 6870). Two Chromatic instances were running, one left open by an earlier toggle, using about 96% CPU between them: nearly a whole core. And both were recording Scarlett Input 1, the mic, not Input 2, the guitar. Chromatic records through ALSA-on-PipeWire and takes the default source, and the PIPEWIRE_NODE the helper passed through flatpak run didn't stick.

The fix is two rules in the helper. Kill any running instance before opening one, so there are never two. And once Chromatic's input port (ALSA plug-in [chromatic]:input_MONO) appears, replace whatever is connected to it with the guitar. Now it's one window, fed only from Input 2, closed cleanly by the second press. With the tuner closed: 1 xrun in about 15 seconds, at 0.43–0.58 B/Q. Not zero, but a different planet from 250 in 12.

My ears caught this one before any measurement did; the measurements explained it. "Close the tuner" is still the first fix for crackles in the practice guide. The rest waits on real time (D13): until I log out and back in, Qtractor's audio thread runs SCHED_OTHER.

Try 5: stop building, go shopping. Chromatic worked. It also took a Flatpak, a window rule, a port re-patcher and a kill-before-open rule to show one note name. Then, a day later:

i just realize all our tuners are dumb solutions... omarchy has existing plugins in the marketplace that are tuners like Chromatic Tuner or Fretwise or Pitchfork. Fretwise looks amazing

It does. The Omarchy shell has a plugin marketplace, and the marketplace has tuners. Fretwise (MIT, verified in the marketplace) lives in the bar. It's a dot-matrix readout of the note, the cents and the string, drawn in the active theme's colours. Its pitch detection is standard-library Python, so installing it is one omarchy plugin add. And it only listens while its panel is open, which is exactly the contract SW10 wants. A press sets Fretwise's captureSource to the guitar input (omarchy bar set) and opens its panel through the shell's IPC (omarchy-shell shell summon). The next press hides it. No window to float, no port to re-patch, nothing left running.

Chromatic stays in the code as the fallback for a desktop without Omarchy. Five tuners in, the lesson generalises: before building the thing, check what your desktop already ships. That's also where act five starts.

Act five · GuitarMood

GuitarMood: the rig as an Omarchy app

The full write-up, every screenshot.

By now the rig sounded the way I wanted. Using it still meant a terminal: qtractor_rig.py up to start it, down to stop it, and in between a pedal that tells you nothing. The FCB1010 shows a bank number and one lit LED. Is the wah on? Did the boost reset when I changed song? Which bank was Fear of the Dark again? So I asked for this:

I want this whole thing to be a desktop app that shows live status of what i selected in the pedal as a hypland tile and autoscale and plays nice with omarchy. Use uv for everything. Instead of having to start and stop just add an omarchy app that i can turn on and off and it takes care of clean up on shutdown. I don't have to guess anything and show me live what each pedal does in real time, what bank I'm on, etc...

Omarchy speaks Qt

Claude's first plan was GTK4, because it was already installed. Halfway into it I asked: "omarchy seems to love Qt?" It does. Omarchy's shell is Quickshell, which is QML, and every plugin in my bar is a QML file. So GuitarMood is Qt too: PySide6 with a QML front end, packaged as a uv project, so uv run brings everything it needs and nothing touches the system Python. It reads the active theme's colors.toml every couple of seconds, so it recolours when I switch themes. And it uses the system monospace font, like everything else on the desktop.

The window is the pedalboard

The layout mirrors the FCB1010 as it sits under my feet: SW6–SW10 on the top row, SW1–SW5 on the bottom, UP and DOWN at the end of each row, EXP A and EXP B on the right. Every tile says what that switch does in the bank I'm on, down to which pedal model sits in the boost slot. Up top: the bank, the album, the era's signature song with its key, tempo and solo echo, and the scene in big letters in its own colour. The setlist of all ten banks runs down the side. UP and DOWN name the bank one tap away, because the FCB's own display only shows a number.

Any tile Hyprland hands it

Everything in the window is sized from the window, so it works at whatever size a tiling window manager comes up with. Next to btop, it keeps the whole board:

GuitarMood in the left half of the screen with the full board and setlist, btop in the right half

Half the screen, next to btop. In btop's process list, guitarmood sits at 0.0% CPU and 207 MB; qtractor, running all three TONE3000s, at 7.8% and 425 MB.

In a quarter of the screen it still reads from across the room, and the setlist stays:

Four tiles: GuitarMood top left, btop top right, an aseqdump MIDI monitor bottom left, pw-top bottom right

A quarter tile, sharing the screen with btop, pw-top and a raw MIDI monitor waiting for the FCB (aseqdump). pw-top shows the rig at a quantum of 256 at 48 kHz. Its ERR column is honest too: 51 on Qtractor's node. More on that under what it can't do.

And Omarchy's pop-out (SUPER+O) floats a window, pins it to every workspace and keeps it on top. Below about 700 pixels wide, GuitarMood switches to a compact layout: the bank, the scene in huge letters and the toggles as chips. It's a status light for the rig that follows me across workspaces:

A small pinned GuitarMood panel over btop, showing bank 3, The Number of the Beast, RHYTHM in large red letters and five toggle chips, none lit

Popped out with SUPER+O at 560×360 over btop: bank 3, RHYTHM, and the five toggles, none of them on.

Never guess

Drawing tiles is the easy part. The hard part is knowing what's actually on, from a pedal that only sends fixed messages: a toggle switch sends CC 127 on every press, whether it's switching something on or off. So GuitarMood rebuilds the rig's state from the MIDI, following the rig's own rules:

  • DAW toggles (wah, octaver, harmony) are Qtractor plugins. Every press flips them, and they survive a song change.
  • TONE3000 toggles (boost, drive, delay, lead) flip too, but every Program Change on channel 1 reloads the preset and puts each block back to the preset's own state. GuitarMood knows each preset's defaults because it reads the same rigs/*.json the presets were built from.
  • The scene is decided by the CC 81 that ends every scene switch's burst (PC ch 1, PC ch 2, PC ch 3, CC 80, CC 81). Above 63 means the clean instance, playing whatever channel 2 last loaded. Otherwise CC 80 picks rhythm, solo or crunch.
  • The bank is the one the last press came from. UP and DOWN send nothing, so the screen can't see you browsing banks, only choosing one.

It also took over the helper's job: the per-song echo and key, and SW10. One process reads the FCB, so the tuner light on screen is the tuner's real state. If the rig is already running when it opens, GuitarMood joins it instead of starting a second one. It stops the old helper, asks Qtractor to save (SIGUSR1), and reads the wah, harmony and tuner states back from the session. Everything only a press can tell it (the bank, the scene, the TONE3000 blocks) shows a "?" until you press something. No guessing, as ordered.

The tests replay the raw MIDI of the first real session I played on this board, 538 lines of it, and check that it ends where I ended: Seventh Son, RHYTHM, key E minor, UP pointing at Fear of the Dark. Another walks every switch in every bank and checks that the switch's own messages light it up. All ten pass.

On is open, off is close

guitarmood install puts it in the Omarchy launcher and adds two Hyprland rules. Launching goes through omarchy-launch-or-focus, so a second launch focuses the window instead of starting a second rig. Qtractor opens on a hidden special workspace, because nobody needs to look at it; SUPER+CTRL+G peeks at it. Closing the window (SUPER+W), logging out or a Ctrl-C all run the same shutdown: close the tuner, save the session, quit Qtractor. Tested end to end on the real rig. Launch: Qtractor up on special:rig, GuitarMood tiled. Close: Qtractor saved and gone, nothing left running.

One bug worth passing on. Qt 6's quit() politely asks every window to close first. GuitarMood's window refuses to close until the rig is down, so quit() quietly cancelled itself and the app hung. exit(0) doesn't ask.

Two-way, mostly

Once the screen knew everything, the obvious question: can I change things from it? Half of it, yes. Click a tile, or press 1–9 and 0 for SW1–SW10, and GuitarMood sends exactly what the FCB would send for that switch. The messages come from the same rig.build() that wrote the pedal's sysex, into the same Qtractor port. Clicking a bank in the setlist or an UP/DOWN tile, or pressing the arrow keys, loads that bank's SW1. On the real rig, Powerslave's RHYTHM, SOLO, CLEAN, WAH and HARMONY sent from the app switched Qtractor's Selector, Solo, Wah and Harmony In exactly as expected, read back from a saved session.

The other half is hardware. A stock FCB1010 can't be told anything: its display and LEDs only follow your feet. So presses from the screen are marked "on screen", and the pedal keeps showing its own bank. That's mostly harmless: toggles, SOLO and CRUNCH send the same messages in every bank. The one thing to remember is that RHYTHM, CLEAN or ACOUSTIC on the pedal load the bank the pedal shows, and the screen follows.

What it can't do (yet)

Every limit, in detail.

  • The harmony is a diatonic third, one key per song. Sixths (The Trooper's riff), a line that changes key mid-song, or the two guitars playing different lines still need a second player.
  • No real chorus, no guitar synth. TONE3000 has no modulation, so the GK's built-in chorus and Smith's CE-3 are Align wobble stand-ins, and the Roland guitar-synth layers all over Somewhere in Time are out of reach.
  • Some reverbs are stand-ins. No capture exists of Battery's EMT plates (a DAW EMT 140 emulation plays them), Compass Point's Lexicon 224 or Smith's PCM 60 (a 480L and a PCM90 "pcm60 room" preset stand in), and the JCM900's own spring tank is a Valvestate's.
  • Some gear is a best guess. Gers' 1990–92 head and Smith's Brave New World studio amps aren't documented, and sources disagree on Smith's Piece of Mind head. The notes in each preset say so.
  • Played, but not A/B'd. Every number above comes from a DI loop or a synthetic melody through the rig. Since then I've played the Maiden scenes live from the real FCB, with every switch's MIDI checked on the way in, and my verdict was "sounds amazing". What hasn't happened is an A/B against the actual records, era by era, harmony included. That's the next Saturday.
  • Xruns: diagnosed, not fixed. rtkit is out (D13), so the JACK clients only get real time once the realtime group is active in my login session. That needs a re-login I haven't done yet, and every xrun count in the Maiden chapter needs re-measuring after it. Meanwhile: close the tuner before you play (D14). The quarter-tile screenshot above shows where that stands: Qtractor's node had logged 51 errors by then, with the tuner closed.
  • The pedal can't follow the screen. A stock FCB1010 has no way to be told what bank or scene the screen picked, and UP/DOWN send no MIDI, so the screen only learns a bank when you press a switch in it.
  • GuitarMood is heavier than it needs to be. A 76 MB PySide6 download to draw one window, next to a shell that already runs QML all day. That's next.
  • Loose ends. The clean path's extra 5.3 ms is expected, not measured. Run the standalone or the DAW, not both.

Free for anyone

Every license, checked.

This was the point from the start, and it held all the way: no Wine, no paid DAW, no subscription. Every piece of software in the rig runs natively on Linux, and every piece is open source. I checked each license rather than assume:

PartIts job in the rigLicense
TONE3000 plugin 0.0.9amps, cabs, pedals and IRs; three instances in the DAWMIT
NeuralAmpModelerCorethe neural amp models, inside the pluginMIT
Qtractor 1.6.4the DAW, generated by a scriptGPL-2.0-or-later
x42-pluginsthe harmony's autotune stages, the true-peak limiterGPL-2.0-or-later, GPL-3.0-or-later
LSP pluginsthe compressor, the Solo slap delayLGPL-3.0-or-later
Guitarixwah and octaverGPL-2.0-or-later (some parts GPL-3.0 / LGPL-3.0)
PipeWireaudio and JACKMIT, LGPL-2.1-or-later
Hyprland · Omarchythe desktopBSD-3-Clause · MIT
Fretwisethe SW10 tunerMIT
PySide6 (Qt for Python)GuitarMood's windowLGPL-3.0 (or GPL)
uvPython packagingMIT or Apache-2.0
fcb1010-biasfx2the preset builder, the DAW generator, the FCB layout and GuitarMoodMIT

From the Arch packages (pacman -Qi), the projects' GitHub repositories and PyPI metadata, checked on 2026-09-27.

Two honest footnotes. The tones aren't software: they're community captures on TONE3000, free to load with a free account and shared under TONE3000's tone-sharing policy. And the hardware isn't free: a Behringer FCB1010, a Focusrite Scarlett and a guitar. Everything between the guitar cable and the speakers is.

Practice guide

Once per machine, every session, Maiden recipes, troubleshooting.

This is the part I actually use. It's the README's practice guide, adapted: the things you do once per machine, then the things you do every time you pick up the guitar.

Once

  1. Install and build. The packages, the TONE3000 download and Fretwise are in Try it above. ./bootstrap.sh builds every preset and the DAW session, and uv run --project guitarmood guitarmood install puts GuitarMood in the launcher.
  2. Program the FCB1010. Write the layout: uv run rig.py syx ~/Music/fcb-rig/fcb1010-maiden.syx. Put the pedal in receive mode: hold DOWN at power-on, tap UP to CONFIGURATION, then tap SW7 (SYSEX RCV). Send it: amidi -p hw:2,0 -s ~/Music/fcb-rig/fcb1010-maiden.syx (hw:2,0 is my MIDI interface; amidi -l lists yours). Hold DOWN to save. Then recalibrate the expression pedals: hold SW1+SW5 at power-on, then heel and toe each pedal.
  3. Real-time audio. Add your user to the realtime group (realtime-privileges) and log out and back in. Then turn rtkit off for JACK apps, because its 200 ms cap kills Qtractor (D13): copy /usr/share/pipewire/jack.conf to ~/.config/pipewire/ and set rtportal.enabled = false and rtkit.enabled = false in the libpipewire-module-rt args.
  4. Gain staging. Set the Scarlett's monitor knob to your loudest comfortable level. The Linux output (the sink) stays at 100%. Play Spotify or YouTube at 100%: the guitar is levelled to sit just above it. One knob, like act one said.

Every session

  1. Plug in the Scarlett (guitar in Input 2) and the FCB, then open GuitarMood (SUPER+SPACE). It takes about 10 seconds and starts on the Maiden 82 bank.
  2. Tune: press SW10. The rig mutes and Fretwise opens in the bar, listening to your guitar. Tune, then press SW10 again.
  3. Pick a bank with UP/DOWN (or click it in GuitarMood's setlist), and press RHYTHM, CLEAN or ACOUSTIC first: those load the era. SOLO and CRUNCH only change the drive, so they assume the era is already loaded.
  4. Play along with the track in Spotify or YouTube.
  5. Record a take: uv run qtractor_rig.py record, then uv run qtractor_rig.py stop. It saves the full rig and the dry DI in ~/Music/fcb-rig/, so you can re-amp later.
  6. When you're done, close GuitarMood (SUPER+W). It saves the session and stops the rig.

Maiden song recipes

Song (bank)How to play it on the board
Hallowed Be Thy Name (03)CLEAN for the arpeggio intro → RHYTHM when the band comes in → SOLO for the leads. HARMONY on the twin lines (E minor).
The Trooper (04)RHYTHM gallop → SOLO for the solo. HARMONY for the main riff's twin line.
Rime of the Ancient Mariner (05)RHYTHM → CLEAN for the quiet middle section → back to RHYTHM → SOLO.
Wasted Years (06)SOLO + SW9 (delay + reverb) for the intro lead → RHYTHM for the verses.
The Evil That Men Do (07)RHYTHM, then HARMONY for the harmonised bridge.
Fear of the Dark (08)CRUNCH + SW9 for the quiet intro melody (750 ms echo) → RHYTHM for the gallop → SOLO. HARMONY is set to D Dorian, but the record plays that melody in unison.
Journeyman (09)ACOUSTIC for the whole song.

From the practice guide in README.md. The record harmonises The Trooper's riff in sixths (see above), so HARMONY's third is an approximation there.

Two soloists: neck pickup for Murray's fluid legato, bridge for Smith's and Gers' bite. Wah is SW6 plus EXP A; volume swells are EXP B.

When something's off

SymptomFix
No soundThe tuner mute may be on: press SW10. Otherwise check Input 2 and the Scarlett's monitor knob.
Wrong era or tone after changing bankPress RHYTHM, CLEAN or ACOUSTIC to load the era.
Crackles or dropoutsClose the tuner (SW10). Log out and in so Qtractor gets real-time priority. Avoid heavy apps while playing.
Guitar too loud or quiet next to the musicUse the Scarlett monitor knob for overall level and EXP B for the guitar; leave the Linux volume at 100%.
The rig won't startGuitarMood shows the error. Close it and open it again. From a terminal: uv run qtractor_rig.py down, then open GuitarMood. For details: journalctl --user -u qtractor-rig.
GuitarMood shows "?"It joined a rig that was already running. Press a scene switch and it syncs.
Screen and pedal show different banksYou picked the bank on screen, and the FCB can't follow. Tap UP/DOWN on the pedal to the same bank.

That's every number I have. If you want the rig for yourself, the commands are in Try it. Then stop reading and go play.