Releases: monzta1/ToneCommand
Release list
ToneCommand 1.2.2
The one-click updater is now fail-safe.
A bad release can no longer break the app. Before ToneCommand restarts into an update, it proves the new code actually loads (imports and builds the app) in a throwaway check. If the pull, the reinstall, or that check fails, the checkout is reverted to the version that was already running and the failure is reported plainly. The working server is never stopped for code that has not been shown to start, and if a restart somehow does not come back, the page gives a clear recovery step instead of a blank error.
Everything from 1.2.1 (one-click hands-off updates) is included. Update from the banner or Settings with one click.
ToneCommand 1.2.1
One-click, fully hands-off updates.
Click UPDATE in the banner or in Settings and ToneCommand pulls, reinstalls, and restarts itself, then the page reloads onto the new version. No terminal, no manual restart. The restart is graceful and releases the FM9's MIDI port first, so it never leaves a poisoned port, and the update button always shows once a newer release exists (disabled with a reason when it cannot auto-update, so it never silently vanishes).
Everything from 1.2.0 (the updater, the three-OS install docs, the delay-dry-note rulebook fix) is included.
Upgrading from 1.2.0: just click UPDATE. From anything older, git pull then .venv/bin/pip install -e . then restart, once.
ToneCommand 1.2.0
The app now knows its own version, tells you when a new one ships, and can update itself in one click.
What's new in 1.2.0
- Version awareness + one-click updates. Settings shows the running version and a CHECK FOR UPDATES button. On launch ToneCommand asks GitHub for the latest release (cached, silent when offline) and, when you're behind, shows a banner with the new version, a release-notes link, and the exact upgrade commands for your OS. On a clean checkout on the main branch it also offers a one-click UPDATE button that runs
git pulland the reinstall for you.- It never pulls or restarts on its own, is blocked during gig lock, and refuses a checkout with local changes or on another branch, so a live FM9 session is never yanked out from under you.
- Install docs make macOS, Windows and Linux unmissable. The README Install section now shows all three OSes with visible commands instead of hiding Windows behind a link, and SETUP.md gains a proper Linux section (ALSA/rtmidi build deps, USB-MIDI note, ffmpeg).
- A rulebook guardrail so a delay never hides the note again. From a live SLO-100 build where the delay ran so wet the dry note was inaudible: the planner now knows the dry must always be heard, a pedal on a wet mix must never reach a dry-killing value, effects bypass Thru not Mute, and it must never claim a structure it does not actually build.
Upgrading from 1.1.0: git pull, then .venv/bin/pip install -e . (Windows: .venv\Scripts\pip install -e .), then restart. After this one manual update, future updates are one click from inside the app.
Full notes in CHANGELOG.md.
ToneCommand 1.1.0
A full visual redesign, and the Review stage is now editable.
🎸 Watch it build: TONECOMMAND builds a Soldano SLO-100 inspired preset on a real FM9 — seven scenes from crunch and rhythm through high-gain to a singing SLO-style lead, planned and voiced by ToneCommand, confirmed by the player, then played.
What's new in 1.1.0
- Inline value editing in Review. Every numeric parameter with a published range is now an editable field in the AFTER column. Type a new value, it lights amber the moment it differs from the plan, and on commit it clamps to the block's real min and max. The number you set is exactly what the confirmed send transmits, through the same read-back-verified path. The plan is a starting point you dial in, not take-it-or-leave-it.
- A rack/HUD redesign of the whole interface. The five-stage flow (Request, Plan, Review, Confirm, Send) is now a row of angled hexagon step-tabs, each with an icon, lit on the active stage and flipping to a green check once done. Machined panels, a brushed faceplate and footer, a labelled command shelf, and the signal chain shown by default, centred and enlarged to use the full width.
- Prev/next preset arrows and a wider preset field, so a full name reads and you can step presets without opening the list.
- Holds up on a short desktop: the signal chain yields its space so a populated Review list is never squeezed to nothing.
Same five-stage flow, scene rack, review table and confirm gate as 1.0.0, reskinned. No functionality was removed, and the full test suite (1159 tests) stayed green. Old soul. New machine. Humans in command.
Full notes in CHANGELOG.md.
ToneCommand 1.0.0
OLD SOUL. NEW MACHINE. HUMANS IN COMMAND.
Talk to your FM9. It builds the tone. Type a sentence, get a gig-ready,
multi-scene preset on your Fractal FM9 in minutes: the signal chain wired, amps
voiced per scene, cabs chosen, scenes named on the footswitches. You read every
parameter change it proposes, you confirm, and only then does it land on the
hardware over USB MIDI, verified by reading the unit back. A language model
proposes; it never sends. Nothing reaches your amp until you say go.
Proof, not screenshots: all eight scenes of a drop C metalcore preset, each built by typing a sentence, played through on a real FM9 → https://youtu.be/7f840w5mQis
What's in 1.0.0
- Build a tone from a video you don't have time to watch. Paste a YouTube
rig rundown, a forum thread, or a chat. On a captioned video it reads the whole
transcript and keeps only the gear and settings in about twenty seconds; the
intro, the sponsor read and every tangent are thrown out, so a two-hour
walkthrough becomes a compact tone spec without watching it. No captions? It
transcribes locally on your machine. - One-tap +Create lays a fresh rig on a free slot, or clears the loaded
buffer on a full unit, so there is always a blank canvas to build on (#44). - Both expression pedals, bound by name. "Put global volume on Pedal 1 and
wah on Pedal 2" (#11). - Pick a cabinet by name in a build: "put a 4x12 V30 on the rhythm" (#45).
- Compose a preset from parts of the tones you already own: "clone BT Marco
Sfogli, take the gain stage from it and the delay from the Andy Timmons tone." - A prebuilt starter template laid in one pass, and batched verification,
so the slow, fragile part of a build is gone (#47). - A bounded repair pass (#39) and a health scan after every build (#51)
that catches a dead or duplicated scene the moment it lands. - A tone rulebook, dialed in by ear on real hardware: cleans ship loud,
leads out-saturate the rhythm, no scene ships silent.
Read-back verification on every write, a blast-radius preview before anything is
sent, gig lock, and bring-your-own-AI (ChatGPT, Gemini, Grok, DeepSeek, Kimi, a
local model, or the Claude CLI).
Full notes in CHANGELOG.md.
0.9.0: install tones from Gift of Tone, one request box, builds that finish
The release that turns ToneCommand into something a player can just talk to.
Install tones by asking (#42)
- Say "get me the Periphery tones from Gift of Tone" and it downloads the pack, pulls out the FM9 presets, skips the Axe-Fx/FM3 versions, lets you pick slots, and writes them to the unit. First one loads automatically.
- Works offline against a local tone folder too: "find the luke tones and load them" combs the folder you set, matches nicknames, and installs, no unzipping. Filler words, plurals and typos no longer break the search.
- Purchased .fasBundle packs install with their cabs, filed to the slots the artist intended. Loose .syx files install through the same review-and-send flow, whitelisted and verified by read-back.
- A pick-what-to-install plan when a pack has more than one preset: everything checked by default, editable slots, a live summary of exactly what WRITE will do.
One place to ask, and it just builds
- The desktop control surface: a single request box for every kind of request. Describe a sound, ask for a whole rig, or paste a link, and it routes internally. No modes to choose.
- Empty slots build their own starting chain when a tone needs one, then voice it, inside the reviewed send.
- One planner window for every request, so a big multi-scene build never times out for being large.
- Plain language throughout: waiting, plans, and failures speak like a player, with the protocol and model details kept in diagnostics.
Multiple AI planners, set up in a click
- ChatGPT, Gemini, Grok, DeepSeek and Kimi are one-click services; each keeps its own key and model. The signed-in Claude CLI is found on its own. The picker asks who plans rather than handing you a form.
Fixes worth calling out
- From-empty builds no longer abort halfway: a block that had landed but read back with a transient id (a gate reads 18 for a beat before 146) no longer false-fails the whole build. Verified on hardware.
- Long preset names at the FM9's length limit no longer fail a build.
- Changing presets on the front panel no longer makes the rig cycle through every scene.
Nothing reaches the FM9 until you send, every flash write is whitelisted and read back, and nothing writes blind.
Nightly 2026-09-03: the desktop control surface
Nightly build of the redesigned desktop control surface. Not a stable
release: this is the new five-stage console fresh off a full day of live
hardening, published for early eyes and brave rigs. v0.8.0 remains the
last stable release.
What is in it
- The five-stage command console. REQUEST, PLAN, REVIEW, CONFIRM,
SEND replace the tabbed page. Scenes and the live signal path stay on
screen through every stage; the final SEND TO FM9 exists only inside an
armed, eight-second confirm gate that Esc or any target change disarms. - Review as a change table with filters and blast radius, Confirm as
a facts matrix (target, scope, destination, recovery), Send as an
observable transmission ending at EARS: PENDING. - Manual control as an Inspector opened from the signal chain itself.
- WHO PLANS YOUR TONES. AI setup is named cards with plain costs and
states (READY, NEEDS A KEY with a link, NEEDS SETUP); keys and models
are stored per service; ChatGPT, Gemini, Grok, DeepSeek, Kimi, local
models and OpenRouter are one click each; the Local model card finds
LM Studio or Ollama on its own. - Verified end to end on real FM9 hardware: writes sent through the armed
gate, read back verified, and undone from the shelf.
Honest state
1033 tests pass and the workflow has survived one long evening of real
use, which is exactly one evening. Edit-buffer safety, store whitelists
and read-back verification are unchanged underneath. Expect rough edges
in layout and copy; file what you find.
0.8.0: one wait experience everywhere, and STOP means stop
Watch 0.8.0 do its thing: one sentence, an empty FM9 slot, six minutes, a complete 8-scene Judas Priest preset. Unedited: https://www.youtube.com/watch?v=jCNAZyyt5o8
One wait experience, everywhere. The COMMAND box learned to count seconds,
listen for a heartbeat and offer a way out months before the rest of the app
did; every other path that could run for more than a few seconds still hid
behind a frozen line of text. This release moves all of them onto the same
machinery, and makes STOP mean stop.
Added
- The claude CLI backend streams. Plans stream their action count and
chat streams its words through--output-format stream-json, so the
"31 changes written" counter finally fires on the zero-configuration
default install instead of only behind an OpenAI-compatible router. The
wire format was verified against the real CLI, not assumed. - STOP stops the backend. Abandoning a plan, chat, build or source read
kills the planner subprocess on the server instead of leaving it burning
for minutes while holding the settings lock. A request that does have to
wait behind an earlier one now says QUEUED instead of pretending to work. - BUILD FROM A SOURCE streams. Reading narrates its stages off the wire
(fetching, downloading audio, the first-run whisper model, transcribing,
extracting) with an elapsed count and STOP; building shows the same
changes-written counter as the COMMAND box. Both used to be a static line
over minutes of work, the exact failure the plan stream was built to end. - FIX IT goes through the streaming planner, with the banner, the count
and STOP, instead of a bare ASKING... over a multi-minute call. - The preset scan counts. Reading all 512 slot names is about fifteen
seconds of MIDI; the popover, RESCAN and NAMES now count n/512 through it
instead of showing "nothing matches" over an empty list, which read as
"your unit is empty". - The health scan names the scene it is standing on, so the noises the
rig makes during a scan are narrated rather than left unexplained, and the
button counts SCENE n/8. - GIG MODE is on the page. A header pill shows the performance lockout
and toggles it; it used to exist only as an environment variable and an
API, so the page looked normal while every control failed one refusal at a
time, and the refusal then told a guitarist to POST JSON. - The blast-radius sweep says so: mapping which scenes share channels steps
the rig through all eight scenes audibly, and the log now names that read
when it happens. - The LINK pill tracks the cable, both ways, in about a second. The
server keeps one CoreMIDI client with a real notify callback on a runloop
thread, which is the thing that actually keeps a macOS process's view of
the MIDI bus alive (two earlier fixes for this are recorded, disproven,
in KNOWN_QUIRKS), andGET /api/link/streampushes presence changes to
the page the moment they happen. Verified across six live plug/unplug
cycles on real hardware.
Fixed
- A build aimed at an empty slot lays its own foundations. A 135-action
plan used to halt at "ADD amp" on a freshly erased slot and tell the
player to go and press BUILD A STARTING CHAIN themselves: the tool knew
the problem, knew the remedy, owned the code for it, and handed the work
back. A transmit containing add_block now builds the starting chain
itself when the loaded slot is empty (announced on the plan card before
confirmation, reported as its own row in the results), and an add for a
block already present counts as satisfied instead of halting everything
after it. - Erasing is one confirmation now; the typing test is retired. The
typed-name echo refused legitimate attempts over invisible double
spaces, then over a machine-built title, then got fed the slot number,
all in one evening. What survives is the part that protects: ERASE only
arms once the slot's name has been read and shown, the single dialog
names the slot and exactly what it holds, and the page sends the name it
displayed for the server to match against flash, so a slot that changed
since it was shown still refuses. API callers keep the name contract,
spacing and case forgiven, and a typed slot number gets a reply naming
the actual ask instead of a bare refusal. - Every slot label now leads with the number on your unit. The header
pill said 159 while every label below said "158 (FM9-Edit 159)", so the
app looked like it disagreed with itself about which preset was loaded.
Labels are "159 (wire 158)" everywhere now: the front-panel number
first, the MIDI wire number named for what it is in the bracket. One
function owns the format, so nothing can drift back. The SAVE dropdown
also fills its names right after startup instead of saying "name not
read" until NAMES was pressed. - The SAVE panel contradicted its own dropdown after an unsaved build.
The "Loaded:" line printed the edit buffer's name as though it were the
slot's, directly above a dropdown showing what flash actually holds, and
the two read as mismatching data. They are two true facts about two
different things; when they differ the panel now names both and says
which is which: what the slot holds, what your unsaved edits are called,
and that SAVE writes the second over the first. - A refused action was missing from the live transmit count. The
validation branch skipped the progress callback, so the SENDING counter
stuck at n-1 of N while the final banner said otherwise. Found by running
the stream against the real server, not by reading the code, which had
looked fine. Refusals now count as failed steps. - ERASE refused legitimate attempts and looked broken. The typed-name
confirmation demanded an exact match against names that carry internal
double spaces the eye cannot see and machine-built titles nobody retypes
from memory; two real attempts in a row were refused, and the refusal
landed only in the LOG panel, far from the button. Spacing runs and case
are forgiven now (the name itself is not), refusals are logged
server-side, and both the refusal and the success are announced in the
strip. (The typing itself was retired later the same evening; see the
one-confirmation entry above.) - A failed action was pointed at, not named. "1 did not apply, marked
above" left the player hunting through a hundred folded cards, twice in
one evening. The outcome banner now names each failure with its reason,
the card list opens itself and scrolls to the first failed card, and the
server logs every refused action so a failure can be diagnosed without
asking the player to read their browser back. - SHOW LOG sometimes needed several presses. The working strip rebuilt
its entire HTML every second, so the button being pressed was destroyed
between mousedown and mouseup and the click fell into the gap. The strip
is built once now and only its text updates. - The strip vanished at the finish, so completion looked like nothing.
It now holds the verdict for a few seconds, in the one place that cannot
be scrolled away from: DONE in green with the count (and the stored slot
when one was written), partial sends in amber, failures in red, and
BUILD COMPLETE when a plan lands. A click dismisses it early. - A finished build looked like nothing happening. The completion note
landed in the chat transcript while the page scrolled you to the plan
panel, so the one line saying "the build worked, TRANSMIT is next" sat
exactly where you no longer were. The plan panel now opens with its own
verdict strip: how many changes, that nothing has been sent, that
TRANSMIT is the next step, and the truth about UNDO for store plans. - The preset dropdown kept the overwritten preset's name after a stored
plan. The slot-name cache was only ever invalidated by RESCAN, NAMES,
rename and erase; a store inside a transmitted plan never told it. The
store result already carries the slot's new name, so the cache entry is
now corrected in place (no rescan) and the page re-pulls its lists after
any transmit that stored. The SAVE button's full rescan-after-save is
gone for the same reason: it spent seconds of MIDI to learn a name the
store result had already delivered. - The transmit banner lied after a stored plan. "Your presets are
untouched; UNDO covers what landed" was shown after a plan whose store had
just overwritten a flash slot, false on both counts and caught live on the
first real build through the new pipeline. The outcome copy now checks
whether a store landed and names the overwritten slot when one did. - Gig mode could audibly walk all eight scenes mid-song. The shared-
channel sweep ran automatically when the preset changed and was not gig
gated, so a front-panel preset change during a set had the tool stepping
scenes while someone played through it. It now refuses during gig mode and
answers from cache only. - HOLD A and HOLD B stayed dead for the rest of the session after any undo
or recall: the recall disabled all five buttons and the refresh only ever
re-enabled three. - UNDO and RECALL say UNDOING.../RECALLING... while a forty-value restore
runs; the preset pill shows a busy state while the unit switches.
Recipe sharing stops being code and starts being a thing that exists. The
worker and schema had been written and tested since 0.4.x and never deployed,
so the tone database existed as source and as nothing else: every recipe
anybody wrote went into the local outbox and stayed there.
Added
- The sharing service is live, on Cloudflare Workers and D1. It holds an
inbox and a counter and never content: recipes live in this repository's
recipes/folder. That split decides the failure mode. If the worker is
down, browsing and using recipes still work, only submission and ranking
pause, and the client hol...
0.7.0
Recipe sharing stops being code and starts being a thing that exists. The
worker and schema had been written and tested since 0.4.x and never deployed,
so the tone database existed as source and as nothing else: every recipe
anybody wrote went into the local outbox and stayed there.
Added
- The sharing service is live, on Cloudflare Workers and D1. It holds an
inbox and a counter and never content: recipes live in this repository's
recipes/folder. That split decides the failure mode. If the worker is
down, browsing and using recipes still work, only submission and ranking
pause, and the client holds both until it returns. Nothing anybody writes
depends on it being up. AUTO_PUBLISH, off by default. On, a submission is committed straight
intorecipes/and is live immediately, with no human step. Every
submission is recorded in D1 either way, so it is one flag to undo with
nothing lost. Be clear-eyed about what it means:/submitis
unauthenticated, so with it on, anyone who can reach the worker can write a
file into a public repository. That is a deliberate choice while there are
no users rather than an oversight.- A note when a recipe was made on different firmware. Recipes name models
rather than numbering them, so a step resolves through the loading rig's own
roster and a model that does not exist there is refused rather than becoming
its neighbour. That covers the structural risk and not the audible one:
Fractal revises voicings between releases, andtested_firmwarewas being
recorded and shown to nobody. The plan now says so before you transmit. service/wrangler.toml, which did not exist, so there was nothing to deploy
with.
Fixed
- The app could not find the service the docs told you to configure.
share.endpoint()read onlyos.environ, while the planner and the store
whitelist both fall back to.env. So the documented setup left sharing
silently dark: no endpoint, recipes queueing forever, nothing anywhere
saying why. A configuration that fails closed AND says nothing is the worst
of both. - A submitted recipe could carry anything. The validator checked the
envelope and never looked inside a step, sosteps: [1,2,3]passed, and
publishing writes the whole body, so any invented top-level key was
preserved verbatim into the repository. On an unauthenticated endpoint with
auto-publish on, that is "anyone may write arbitrary JSON into a public
repo". Now: known keys only, every step an object whose kind is a real
action, no invented keys inside steps, text bounded.storeis refused
outright, being the one action that writes to flash. - Publishing refuses to overwrite. Not moderation, integrity: without it
anyone could post a recipe named after a curated tone and silently replace
it. - The suite no longer reads the developer's real
.envwhen testing sharing.
The.envfallback leaked within a minute of being added, into a test
asserting sharing was local only.
0.6.1
Fixed
- The simulator let a discrete write of zero land on an enum. Hardware
never does: sub 09 carrying a zero value IS the zeroed GET, and it reads
rather than writes for EVERY parameter, not only the continuous ones. The
proof was already indevice.set_param_ordinal, which sends ordinal 0 as a
CONTINUOUS 0.0 precisely because the discrete path cannot carry it. - 0.6.0 shipped the narrower guard, taken from #37 while merging #35 on the
reasoning that it protected a legitimate discrete write of ordinal 0. There
is no such write. Narrowing by parameter kind invented a distinction the
device does not make, and left the simulator MORE permissive than the
hardware, which is the wrong direction for a test double: code doing a
discrete zero would have passed here and silently done nothing on the rig.
Reported by @bschmalz81401 on #37. test_zeroed_get_is_noopnow stands the settle window down. It had been
passing for a reason unrelated to what it claimed: the window served the
read from the pre-write snapshot while the live buffer had already been
zeroed, so the damage was hidden on any machine fast enough. It failed only
on CI. With no window there is nowhere for a bad write to hide, and a
companion test now covers the enum case the old guard got wrong.