Skip to content

Support writing side-button actions (blocks configuring bank switching from the CLI) #18

Description

@oveddan

Problem

Side-button actions are exported but cannot be planned or applied. Today the only
way to change what a side button does is the DJ TechTools MIDI Fighter Utility,
which means a device that is otherwise fully configurable from the CLI still needs
a detour through a GUI app for one setting class.

This bit me on a real show rig. The Twister driving a live Chromatik show had no
side button assigned to any bank action
— the six were ccToggle, shiftPage2Toggle,
ccToggle, ccToggle, shiftPage1Toggle, ccToggle. Banks 2–4 were fully
configured (correct CCs, colors, indicators) and completely unreachable from the
hardware. Pressing the side buttons toggled empty shift pages, which reads as
"bank 2 is broken/dark" rather than "no button is bound to bank switching."
Diagnosing that took an export; fixing it can't be done with this tool at all.

Why this looks small

The read path already exists and already knows every action code.

src/model.ts:124 reads all six from global tags 2–7:

const sideButtons = Array.from({ length: 6 }, (_, index) => {
  const code = required(values, index + 2, `Side button ${index + 1}`);
  return { number: index + 1, action: named(code, SIDE_ACTIONS) };
});

SIDE_ACTIONS (src/model.ts:60) already enumerates the bank actions we'd want:

code action code action
0 ccHold 8 bankUp
1 ccToggle 9 bankDown
2 noteHold 10 bankSelect
3 noteToggle 11 bank1
4 shiftPage1Hold 12 bank2
5 shiftPage2Hold 13 bank3
6 shiftPage1Toggle 14 bank4
7 shiftPage2Toggle

Verified against a live export: button 2 reports code: 7 / shiftPage2Toggle,
button 5 reports code: 6 / shiftPage1Toggle. The indexing is confirmed correct.

Meanwhile GLOBAL_FIELDS in src/planner.ts:50 already writes other global tags
(0, 1, 8, 9, 31–38) through the same tag-based mechanism — including
sideButtonsBanked at tag 1, which sits immediately next to the six side-button
tags. Nothing structurally distinguishes tags 2–7 from tags already supported.

Proposed change

Add six planner paths:

globals.sideButtons.1.action.code  →  tag 2
...
globals.sideButtons.6.action.code  →  tag 7

as { kind: "number", min: 0, max: 14 }, matching the SIDE_ACTIONS length.
Accepting the action name as well as the code would be nicer at the call site
(--set globals.sideButtons.3.action.code=bank2), but the numeric form alone
would unblock this.

Then drop the two "read-only" caveats:

  • .claude/skills/mft-configurator/references/settings.md:71
  • the "Side-button actions are read-only" bullet in the consuming skill's rules

The actual unknown

Whether the device accepts writes to global tags 2–7 at all. The read side
proves the tags exist and are reported; it does not prove they are writable. If
the firmware treats them as read-only, this closes as won't-fix — but that should
be established by trying a single-target plan against a real device and reading
the verification outcome, not assumed. Given apply re-reads and verifies after
every changed target, a rejected write should surface cleanly as failed rather
than silently corrupting state.

Worth checking a backup exists before the first attempt, since there is still no
restore command (#14 territory).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions