diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1fba492..9560839 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,8 +12,6 @@ jobs:
- uses: actions/checkout@v4
- name: Build the container, the firmware and the installer packages
run: node tools/op.mjs package
- - name: Host tests
- run: node tools/op.mjs test
- uses: actions/upload-artifact@v4
with:
name: openpocket-build
@@ -22,3 +20,14 @@ jobs:
docs/firmware/*.fwsc
docs/firmware/manifest.json
build/SHA256SUMS
+ - name: Build the MIDI test channel package (never the installer default)
+ run: node tools/op.mjs package-test
+ - uses: actions/upload-artifact@v4
+ with:
+ name: openpocket-midi-test
+ path: |
+ firmware/openpocket-midi-test.bin
+ docs/firmware/midi-test/
+ build/SHA256SUMS-midi-test
+ - name: Host tests
+ run: node tools/op.mjs test
diff --git a/.gitignore b/.gitignore
index cd0c4ea..df9ac70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
build/
firmware/build/
firmware/openpocket.bin
+firmware/openpocket-*.bin
+docs/firmware/midi-test/
docs/firmware/*.fwsc
docs/firmware/manifest.json
*.o
diff --git a/Makefile b/Makefile
index 591ee81..8fcdcbf 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,7 @@
# make build firmware/openpocket.bin, the update loader and docs/firmware/*.fwsc + manifest.json
# make test the host tests (tests/run_tests.sh)
# make package build + build/SHA256SUMS of the outputs
+# make package-test the MIDI test channel package (docs/firmware/midi-test/, never the installer default)
# make clean remove every build output (needs no Docker)
# make shell a shell in the build container
# make image (re)build the container image
@@ -11,8 +12,8 @@
# make render IN=guitar.wav [OUT=out.wav] [PRESET=1] a WAV through the engine
# make appimage [DUMP=fm1-1.bin] fm1-300.bin for the SD-card recovery
# `make NATIVE=1 build` skips Docker (toolchain installed with firmware/setup.sh).
-.PHONY: doctor build test package clean shell image serve render appimage
-doctor build test package clean shell image serve:
+.PHONY: doctor build test package package-test clean shell image serve render appimage
+doctor build test package package-test clean shell image serve:
@node tools/op.mjs $@
render:
@node tools/op.mjs render "$(IN)" "$(OUT)" "$(PRESET)"
diff --git a/README.md b/README.md
index 5e58bde..5a264cf 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,9 @@ Written from scratch for the pedal's own chip (JieLi AC791N, two cores), install
Greenback, T75, Alnico Blue, Jensen, Cream, DI) with a MIC position
- **Pro levels (v36)**: every amp, block and preset at the same loudness (-23 LUFS, measured on real guitar);
24 factory presets round the fitted amps, 28 USER slots; a PRESET LVL per preset; soft output limiter
+- **MIDI over USB-C (test build v37)**: Program Change selects a preset, Control Change switches blocks and sets their models and
+ settings, the pedal tells the computer when you change the sound, MIDI clock sets the delay time. Class-compliant, no driver;
+ Bluetooth MIDI is not in yet. Reference: [docs/MIDI.md](docs/MIDI.md), hardware test: [docs/MIDI-TEST.md](docs/MIDI-TEST.md)
- What changed since v33: [docs/WHATS-NEW-v34-v36.md](docs/WHATS-NEW-v34-v36.md)
- **New screen (v36)**: a PLAY page readable from a distance (preset number and name, amp, chain in colour) and an EDIT
page with big values and bars
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index de42e5f..13eada5 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -29,6 +29,7 @@ Apple silicon and other ARM machines Docker emulates it (Rosetta or QEMU); you c
| `make doctor` | check Node, Docker and the image, one line each (exit 1 when the build cannot work) |
| `make build` | `firmware/build.sh` in the container: `firmware/openpocket.bin`, the update loader, `docs/firmware/PocketX_9NN.fwsc`, `BlackBox_9NN.fwsc`, `manifest.json` |
| `make package` | `build`, then `build/SHA256SUMS` |
+| `make package-test` | the MIDI test channel (`OP_CHANNEL=midi-test`): `firmware/openpocket-midi-test.bin` and `docs/firmware/midi-test/` (v37 MIDI TEST, its own `manifest.json`), with `build/SHA256SUMS-midi-test`. The release files are not touched and the installer offers it only with `?channel=midi-test` |
| `make test` | `tests/run_tests.sh` (engine, preset store, screen, simulated pedal, installer, editor; the browser page tests also run when Playwright is installed) |
| `make clean` | remove every build output (on the host; needs neither Docker nor the image) |
| `make shell` | a shell in the container (as your user) |
diff --git a/docs/MIDI-TEST.md b/docs/MIDI-TEST.md
new file mode 100644
index 0000000..28d5809
--- /dev/null
+++ b/docs/MIDI-TEST.md
@@ -0,0 +1,112 @@
+# MIDI over USB-C: hardware test plan
+
+For the first run of **OpenPocket v37 MIDI TEST** on a real pedal. Nothing here was tried on hardware yet: the pedal is the only
+way to check the USB behaviour and the sound. The design is in [MIDI.md](MIDI.md). Takes about 45 minutes. Tick as you go and
+send back the "report" block at the end.
+
+**Do this on one pedal first.** The Donner Pocket X is the better first choice: the M-VAVE BlackBox install is still being
+tested (issue #1: a restart loop after power cycles in some versions, rescue mode on the installer page is the cure).
+
+## 0. Before you start
+
+- [ ] Know your way back (section 7). Have your official firmware file for the pedal at hand, and the SD-card recovery
+ (`flash/`, `fm1-1.bin`) if you made one.
+- [ ] Chrome or Edge on a computer, a USB **data** cable, the pedal on and charged.
+- [ ] A MIDI monitor: macOS: Audio MIDI Setup (Window > Show MIDI Studio) and the free MIDI Monitor; Windows: MIDI-OX or
+ Bome MIDI Translator; Linux: `aseqdump -l` then `aseqdump -p "OpenPocket"`.
+- [ ] A DAW or a MIDI controller that can send Program Change and Control Change (Ableton, Logic, Reaper, GarageBand with a
+ MIDI app, or a free web tool such as the Web MIDI Monitor and sender pages).
+- [ ] The package: a build of the pull request gives `docs/firmware/midi-test/` (`node tools/op.mjs package-test`), or the CI
+ artifact. Serve it with `node tools/op.mjs serve` (offline) and open `http://localhost:8765/index.html?channel=midi-test`,
+ or use the page you were given.
+
+## 1. Install the test build
+
+- [ ] Open the installer with `?channel=midi-test`. A warning banner says "TEST CHANNEL" and the package line says `v37 (midi-test)`.
+- [ ] Connect the pedal, check that it is found, press Install, wait for "Done". Do not unplug while it works.
+- [ ] The pedal restarts. The boot screen shows **v37 MIDI TEST**.
+- [ ] The sound is normal: play a few presets, each block of one preset, the tuner (hold HOME). Note the number at the bottom
+ right ("C1 42%"): baseline DSP load with the same preset: ______ %.
+- [ ] Open the web editor (`editor.html`): it connects, shows the presets, and a knob turned on the pedal moves on the page.
+
+## 2. The MIDI page
+
+- [ ] Hold FX for about a second: the **MIDI** page opens. Rows: CHANNEL OMNI, SEND ON, CLOCK > DELAY OFF. "USB: NO HOST"
+ or "HOST CONNECTED" is right.
+- [ ] A short press of FX (not held) still cycles the effects (DRV, SFX, SYN...). It acts when you let go.
+- [ ] Knob changes the row, PUSH goes to the next row, after the last row it leaves. HOME leaves too.
+- [ ] Change CHANNEL to 3, leave. The screen shows "SAVING..." briefly, the sound mutes for a fraction of a second. Power
+ the pedal off and on: the page still says 3.
+
+Set it back to OMNI for the next steps.
+
+## 3. Over USB-C: the pedal receives
+
+Plug the pedal into the computer (the pedal should appear as a MIDI port named **OpenPocket**) and send:
+
+- [ ] **Program Change 0, 1, 2, 11**: the pedal shows preset 01, 02, 03, 12 each time, the sound follows. Program Change 60 does nothing.
+- [ ] **CC 25 = 0 / 127** (AMP off / on): the AMP tile on the screen goes dark and back, the sound loses and gets its amp.
+- [ ] **CC 77 sweep 0 to 127** (AMP setting 1, GAIN): the gain bar moves and the sound changes smoothly. No crackle, no
+ glitch, no mute. Also try CC 102 to 105 (DELAY settings), CC 106 (REVERB).
+- [ ] **CC 57 = 0, 64, 127** (AMP model): the model changes (first, middle, last).
+- [ ] **CC 7 sweep** (volume): the VOL number at the top follows.
+- [ ] The MIDI page's "LAST:" line shows what you just sent (open it with FX held; it is read-only for the host while open).
+- [ ] CHANNEL 3: a message on channel 1 does nothing, on channel 3 works. Back to OMNI.
+- [ ] While the **SAVE page** (hold PUSH) is open, a Program Change from the computer is ignored. HOME to leave.
+- [ ] **Clock:** CLOCK > DELAY ON, select a preset with the delay on (press DLY), start the DAW playing at 120 BPM: the delay
+ TIME shows about 500 ms (the nearest step is 498 ms) and follows a change of tempo to 90 BPM (about 670 ms). With
+ CLOCK > DELAY OFF nothing changes.
+
+## 4. Over USB-C: the pedal sends
+
+With the MIDI monitor listening to the pedal's port and SEND ON:
+
+- [ ] Footswitch up / down: one **Program Change** per press, with the slot number minus 1.
+- [ ] Turn the knob on a block's page: **Control Change** messages with the numbers of [MIDI.md](MIDI.md). Switch a block: 127 / 0.
+- [ ] Nothing is sent at power-on, and nothing when the computer was unplugged and is plugged back in.
+- [ ] SEND OFF: the pedal sends nothing; it still receives.
+- [ ] A change sent **to** the pedal is not sent back (watch the monitor while moving a DAW fader: no echo).
+- [ ] Loop test: a DAW track that echoes everything it receives (MIDI through) back to the pedal does not make it chatter.
+
+## 5. Audio with MIDI flowing
+
+- [ ] Play a high-gain preset continuously for 5 minutes while the DAW sends a CC sweep and clock: no crackle, no dropout, no
+ mute, no "BYP" at the bottom right. Note the load: ______ % (baseline above). A rise of more than a few percent is worth reporting.
+- [ ] Same with a long note flood (a keyboard or a DAW sending 100 notes a second): the pedal ignores the notes and the sound
+ stays clean.
+- [ ] While the flood runs, use the **web editor** (change a preset, rename, save): it keeps working. (Before this release a
+ busy MIDI port could stop the editor.)
+
+## 6. The update path still works
+
+- [ ] On the normal installer page (without `?channel=`), Connect: the pedal is found and shows `OpenPocket v37`. Press Install
+ (this puts **v36** back) and wait for "Done": the boot screen shows v36, the sound is normal.
+- [ ] Open the test page again and install v37 MIDI TEST once more: it also works in this direction.
+- [ ] The editor still connects on both versions.
+
+## 7. Way back and recovery
+
+1. **To the previous OpenPocket:** the normal installer page (no `?channel=`), Install (v36).
+2. **To the official firmware:** the installer page, step 3 "Back to the official firmware", with your official `.fwsc`.
+3. **A pedal that restarts in a loop:** the installer's rescue mode (step 2), plug the pedal in; it installs by itself.
+4. **A pedal that does not start at all:** the SD-card recovery (`flash/`, `makeimage.bat` / `node tools/op.mjs appimage`).
+
+This build does not change the boot, update-entry or recovery code, so all four work as for v36.
+
+## 8. Report back
+
+Please send, even if everything works:
+
+```
+Pedal: Pocket X / BlackBox Computer + OS:
+Installed v37 MIDI TEST: yes/no Boot screen showed v37 MIDI TEST: yes/no
+USB port name seen: Apps tried (DAW, monitor):
+Section 2 (page): ok / what failed
+Section 3 (receive): ok / what failed (which CC or PC numbers)
+Section 4 (send): ok / what failed
+Section 5: DSP load baseline ___ % with MIDI ___ %, any crackle / dropout / BYP:
+Section 6 (update, both ways): ok / what failed
+Anything strange on screen or in sound:
+```
+
+For a failure, add what the MIDI page's "LAST:" line showed and the exact bytes your monitor showed. A photo of the screen helps.
diff --git a/docs/MIDI.md b/docs/MIDI.md
new file mode 100644
index 0000000..3f733b6
--- /dev/null
+++ b/docs/MIDI.md
@@ -0,0 +1,200 @@
+# OpenPocket MIDI over USB-C
+
+OpenPocket answers MIDI Program Change and Control Change from a computer, a DAW or a controller over the USB-C cable,
+and tells the computer when you change the sound on the pedal. This page is the design and the reference. The hardware
+checklist for the first test is [MIDI-TEST.md](MIDI-TEST.md).
+
+**Status.** Written and tested on a PC (host simulators, see "How it was checked"). Not yet tried on a real pedal. It ships
+as a separate test package (OpenPocket v37 MIDI TEST), not in the normal installer. **Bluetooth MIDI is not included**: see
+"Bluetooth MIDI: why not yet".
+
+## What the pedal does
+
+**Receives** (USB, from the host):
+
+| Message | What happens |
+| --- | --- |
+| Program Change 0 to 51 | Loads preset slot 1 to 52, as the footswitches do (unsaved changes are dropped). 52 and above are ignored |
+| Control Change, the map below | Switches a block on or off, picks a block's model, sets one of its 5 settings, or sets the MASTER volume |
+| MIDI clock (24 per quarter note), when CLOCK > DELAY is on | Sets the delay TIME to one quarter note |
+| Start, Continue, Stop | Restart the tempo measurement |
+| Everything else | Ignored: notes, pitch bend, bank select (0 and 32), modulation, sustain, "all notes off", system messages |
+
+**Sends** (when SEND is on and a host is connected):
+
+| What you do on the pedal | What the computer receives |
+| --- | --- |
+| Change the preset (footswitch, knob, SAVE to another slot) | One Program Change with the slot number (0 to 51) |
+| Turn a setting, switch a block, pick a model | One Control Change per change, with the value from the map |
+
+A change made by the host is not sent back. Changes made from the web editor are sent like changes made on the pedal. The
+sound the pedal starts on is not sent at power-on. Nothing is sent while no host is connected, and nothing old is sent
+when one connects.
+
+**Channel.** CHANNEL is OMNI (receive on any channel; send on channel 1) or 1 to 16 (receive and send on that channel only).
+The clock and the transport bytes have no channel and are always taken.
+
+## The Control Change map
+
+Value scaling: a setting is 0 to 100 on the pedal and 0 to 127 in MIDI (`v * 127 / 100`, rounded; the way back is exact).
+On or off: 64 and above is on, below 64 is off; the pedal sends 127 or 0. A model number n of N models is the slice
+`n * 128 / N` to `(n + 1) * 128 / N - 1` of 0 to 127; the pedal sends the middle of the slice. Send the highest value to get the
+last model.
+
+| Block | On / off | Model | Setting 1 | 2 | 3 | 4 | 5 |
+| --- | --- | --- | --- | --- | --- | --- | --- |
+| GATE | 20 | 52 | 33 | 34 | 35 | 36 | 37 |
+| COMP | 21 | 53 | 39 | 40 | 41 | 42 | 43 |
+| PITCH | 22 | 54 | 44 | 45 | 46 | 47 | 48 |
+| SFX | 23 | 55 | 49 | 50 | 51 | 70 | 71 |
+| DRIVE | 24 | 56 | 72 | 73 | 74 | 75 | 76 |
+| AMP | 25 | 57 | 77 | 78 | 79 | 80 | 81 |
+| CAB | 26 | 58 | 82 | 83 | 84 | 85 | 86 |
+| EQ | 27 | 59 | 87 | 88 | 89 | 90 | 91 |
+| MOD | 28 | 60 | 92 | 93 | 94 | 95 | 96 |
+| DELAY | 29 | 61 | 97 | 102 | 103 | 104 | 105 |
+| REVERB | 30 | 62 | 106 | 107 | 108 | 109 | 110 |
+| SYN | 31 | 63 | 111 | 112 | 113 | 114 | 115 |
+
+CC **7** (volume) sets the MASTER volume, 0 to 127 to 0 to 100, like the MASTER knob (turning the knob takes over again).
+
+What the "setting" numbers mean depends on the model (the screen shows the names): for the AMP they are GAIN, BASS, MID,
+TREBLE, PRESENCE; setting 3 of GATE is the preset LEVEL (50 = 0 dB); setting 1 of DELAY is TIME. Only the settings the model
+has do anything; the others are stored and ignored. See [SOUNDS-AND-CONTROLS.md](SOUNDS-AND-CONTROLS.md) and
+[EDITOR-PROTOCOL.md](EDITOR-PROTOCOL.md).
+
+The numbers were chosen so that nothing special is touched: 0 and 32 (bank), 6 and 38 (data entry), 64 to 69 (pedals), 98 to 101
+(NRPN and RPN) and 120 to 127 (channel mode, "all notes off") are never used, and 7 is the only standard one. A few settings
+use 33 to 51, which are the low halves of controllers 1 to 19; a controller that sends 14-bit pairs on those would move them
+by accident. Map them to one of the free numbers in your DAW instead.
+
+The map is in one place in the source, `px_cc_param()` and its siblings in `firmware/px_midi.h`; a test checks that every
+number is unique, reserved ones are free, and a value sent by one pedal is applied by another to the same sound.
+
+## Tempo for the delay
+
+Turn **CLOCK > DELAY** on (default off). When the host sends MIDI clock, the pedal times 24 clocks, and sets the delay TIME to
+the quarter note once two measurements in a row agree within 3 ms (so a drifting host does not make the delay jump). Quarter
+notes shorter than 200 ms or longer than 2 s (above 300 or below 30 BPM) are ignored, and the TIME knob only reaches 2 s. The
+nearest TIME step is chosen: 12.25 ms steps up to 1 s, 50 ms steps above it. REVERSE has its own range (250 ms to 1 s). To
+follow a tempo with another note value, set the delay's own TIME; this is not a division setting.
+
+## Settings and the screen
+
+**Hold the FX button for 0.8 s** (a short press still cycles the effects, now when you release it) to open the **MIDI page**:
+
+| Row | Values | Default |
+| --- | --- | --- |
+| CHANNEL | OMNI, 1 to 16 | OMNI |
+| SEND | ON, OFF | ON |
+| CLOCK > DELAY | ON, OFF | OFF |
+
+The knob changes the row, PUSH goes to the next one (after the last it leaves), HOME leaves. The page also shows whether a
+host is connected and the last Program or Control Change received ("LAST: CC 25 = 127"), which is what to look at first when
+nothing happens. Leaving saves the settings only if you changed something (one flash write, about 0.3 s of silence, like SAVE).
+
+The settings are one byte in the header of the preset store (`firmware/px_store.h`). Older copies have 0 there, which means the
+defaults. A power cut while saving leaves the old or the new settings, never a broken store (tested).
+
+While the SAVE page, the MIDI page, the tuner or the block mover is open, the host's Program Change and Control Change are read
+and ignored, so a DAW cannot change the preset under a save in progress.
+
+## How it is wired, and what stays separate
+
+The USB device is felucca-bsp's `chip/usb.c` as before, **unchanged**: a class-compliant USB-MIDI interface (no driver on macOS,
+Windows 10 and later, Linux, iOS or Android; the port is named "OpenPocket"). It is polled at 2 kHz from a timer interrupt. This
+work does not touch the descriptors, `ota.c`, the loader or the boot, update-entry and recovery paths.
+
+There are two kinds of traffic on the one cable, and they never meet:
+
+1. **SysEx** (`F0 ... F7`) belongs to the update and the editor only: the installer's update protocol (`ota.c`), the editor's
+ frames `F0 7D 4F 50 ...` ([EDITOR-PROTOCOL.md](EDITOR-PROTOCOL.md)) and the rescue key. `usb.c` collects SysEx into one frame
+ buffer for them. A SysEx that is not theirs (a General MIDI reset from a DAW, a patch dump) is taken out of that buffer and
+ thrown away by `ota_take`; a SysEx longer than the buffer is dropped. A test sends a foreign SysEx to the simulated pedal
+ before an update, and the update still works.
+2. **Musical messages** (Program Change, Control Change, clock and transport) go into a 64-entry ring that `usb.c` fills and
+ `firmware/px_midi_io.h` empties every pass of the main loop. Nothing sends SysEx into that ring and nothing in it is SysEx.
+
+One fix came with this: until now nothing emptied that ring. `usb.c` leaves the host's USB packet unread while the ring is nearly
+full, so a DAW sending clock or notes would, after a few dozen messages, also have stopped the editor's and the installer's SysEx,
+which arrive in the same packets. The ring is now always drained (messages the pedal does not use are dropped), and the
+overflow flag `usb.c` sets is cleared once the ring is empty. A test floods the ring and checks it recovers.
+
+Code map: `engine/px_dsp.c` has `px_dly_value_for_ms` (the TIME value nearest a delay time); `firmware/px_midi.h` is the pure
+logic (decoder, CC map, what to send, settings byte, clock), with no hardware; `firmware/px_midi_io.h` is the glue to the rings
+and the pedal's state; `firmware/ui.h` has the MIDI page; `firmware/px_store.h` keeps the settings byte.
+
+## Cost and safety
+
+- **CPU and audio.** The MIDI code runs on the main loop (CPU0), not in the audio interrupt and not on the DSP core. Per pass it
+ reads the ring (usually empty) and compares the 84 on/model/setting bytes of the preset with the last values sent. The binary
+ grows by about 3.3 KB. The DSP load was **not measured on a pedal** (no hardware here): the hardware checklist asks for the
+ "C1 xx%" reading with and without MIDI traffic. Nothing was added to the audio path.
+- **Flash.** The only new write is the settings save, in the preset store's own area, through the existing commit.
+- **Update and recovery paths.** Unchanged. The tests of the installer, the update loader and the editor run as before.
+- **Never flash from here.** The test package is built and checked on a PC; the captain installs it.
+
+## Bluetooth MIDI: why not yet
+
+Bluetooth MIDI (the standard BLE MIDI service) is **not** in this release. What the repository and the official firmware show:
+
+- The repository has only register definitions for the chip's Bluetooth baseband (`firmware/bsp/chip/fm1_ble*.h`). They were
+ written from a clean-room fact sheet, and the files state that nothing in them has run on a real chip. The radio's analog
+ start-up has open items, and there is **no link layer, no L2CAP, no ATT or GATT server, no pairing code**.
+- The official BlackBox firmware does contain a complete Bluetooth stack and a BLE-MIDI service (a scout report of
+ 2026-11-05, kept privately and not in this repository): a standard GATT table with the BLE MIDI service, a parser for the
+ timestamped packets with running status and multi-packet SysEx, all as ordinary tasks of its own operating system. There is no
+ hardware or ROM shortcut: the stack is code in that image, bound to the vendor's operating system, and cannot be copied (it
+ is vendor code). The same report found how the official firmware describes itself on USB (a MIDI interface with one input and
+ one output cable plus USB audio), and found **no published Program Change or Control Change map** in it.
+- JieLi's public Apache-2.0 SDK ships the controller and host stacks only as precompiled libraries that expect the vendor
+ operating system; whether those may be linked here needs the captain's decision on their licence, and each one needs OpenPocket to
+ supply the operating-system services it calls.
+
+**What a BLE stack would need** (future work, in the order it would be done):
+
+1. The captain scans a stock pedal with a phone (advertised name, data, interval) and connects it to a Mac as a Bluetooth MIDI
+ device, to fix the target behaviour. No code.
+2. An advertise-only build on a test channel, to prove the radio comes up from the stored calibration without disturbing the audio.
+3. Either write a minimal stack from the Bluetooth Core specification (link layer slave, L2CAP, ATT, a GATT server with the
+ BLE MIDI service `03B80E5A-EDE8-4B33-A751-6CE34EC4C700` and characteristic `7772E5DB-3868-4112-A1A9-F2669D106BF3`,
+ LE security for pairing), or build against the SDK's libraries once their licence is settled.
+4. A BLE-MIDI framing layer: a header byte and per-message timestamps, running status, SysEx across packets, and a decoder that
+ ends in the same `px_midi_decode` used here (the musical code is already independent of the transport).
+5. A setting to switch the radio off (saved in the store; default off, so a pedal that never uses it pays nothing), and a
+ measurement of the DSP load and glitches with a link up.
+6. Regulatory care: the radio must run with the chip's stored calibration, and the transmit power must not exceed what the
+ stock firmware uses.
+
+## How it was checked (and what was not)
+
+Run `node tools/op.mjs test` (see [BUILDING.md](BUILDING.md)):
+
+- `tests/midisim.c`: the packet decoder (channel filter, bad status, bad data bytes, SysEx never decoded, cable number), the CC
+ map (unique, reserved numbers free, exact inverse, value scaling round trips), what the pedal sends (Program Change, one
+ Control Change per change, no echo, SEND off), the settings byte, MIDI clock (120 BPM, jitter, gaps, a changing tempo), and
+ that a sound sent by one pedal is applied unchanged by another.
+- `tests/midiio.c`: the real glue code against the ring: Program Change and Control Change change the sound, are ignored while
+ SAVE, tuner or the MIDI page is open, channel filtering, a flood is drained and recovers, clock sync on and off.
+- `tests/test_store.c`: the settings survive a power cycle, a preset save and a power cut at every step.
+- `tests/uisim.c`: the MIDI page is drawn completely, the channel wraps, leaving saves only if changed.
+- `tests/test_installer.mjs`: an update still succeeds after a foreign SysEx.
+- `tests/test_op.mjs`, `tests/test_page.mjs`: the test package command and the installer's test channel.
+
+**Not checked, because it needs the pedal:** the USB enumeration and the port name on each OS, a real DAW or app sending, the
+timing of replies, the DSP load and any dropout with MIDI flowing, the real screen, the update path on the new build, power
+cycles. That is what [MIDI-TEST.md](MIDI-TEST.md) is for.
+
+## The test package
+
+`node tools/op.mjs package-test` (or `make package-test`) builds **OpenPocket v37 MIDI TEST** into `docs/firmware/midi-test/`
+(`PocketX_937.fwsc`, `BlackBox_937.fwsc`, its own `manifest.json`) and `firmware/openpocket-midi-test.bin`. The boot screen and
+the editor say `v37 MIDI TEST`. The normal files (`docs/firmware/manifest.json`, v36) are not touched, and the normal installer
+page never offers it. To install it, open the installer with `?channel=midi-test` (a banner says it is a test build). To go back,
+open the normal page and press Install (v36), or use the official-firmware way back on the same page.
+
+## Licences and references
+
+The USB code is felucca-bsp's (MPL-2.0, see [NOTICE.md](../NOTICE.md)); the MIDI code here is GPL-3.0-only like the rest of
+OpenPocket. The CC map is OpenPocket's own. Facts about the official firmware come from a private analysis of the captain's
+own device, used only to learn how it behaves, and no vendor code, bytes or tables are reproduced.
diff --git a/docs/SOUNDS-AND-CONTROLS.md b/docs/SOUNDS-AND-CONTROLS.md
index f1bc9db..2184d91 100644
--- a/docs/SOUNDS-AND-CONTROLS.md
+++ b/docs/SOUNDS-AND-CONTROLS.md
@@ -147,6 +147,28 @@ core, `BYP` = overload guard: turn a block off). `EDITED`: the sound differs fro
Top centre of PLAY: the **battery** (v36), from the chip's own VBAT/4 channel (green, amber below 30 %, red below 15 %;
`USB` when the 5 V supply is there); the tuner page shows the same reading in volts.
+## MIDI over USB-C (test build v37; [MIDI.md](MIDI.md))
+**Hold FX** for 0.8 s: the MIDI page (CHANNEL OMNI or 1 to 16, SEND, CLOCK > DELAY). Program Change 0 to 51 loads slot 1 to 52;
+Control Change sets the sound; the pedal sends Program Change and Control Change for what you do on it.
+
+| Block | On / off | Model | Setting 1 | 2 | 3 | 4 | 5 |
+| --- | --- | --- | --- | --- | --- | --- | --- |
+| GATE | 20 | 52 | 33 | 34 | 35 | 36 | 37 |
+| COMP | 21 | 53 | 39 | 40 | 41 | 42 | 43 |
+| PITCH | 22 | 54 | 44 | 45 | 46 | 47 | 48 |
+| SFX | 23 | 55 | 49 | 50 | 51 | 70 | 71 |
+| DRIVE | 24 | 56 | 72 | 73 | 74 | 75 | 76 |
+| AMP | 25 | 57 | 77 | 78 | 79 | 80 | 81 |
+| CAB | 26 | 58 | 82 | 83 | 84 | 85 | 86 |
+| EQ | 27 | 59 | 87 | 88 | 89 | 90 | 91 |
+| MOD | 28 | 60 | 92 | 93 | 94 | 95 | 96 |
+| DELAY | 29 | 61 | 97 | 102 | 103 | 104 | 105 |
+| REVERB | 30 | 62 | 106 | 107 | 108 | 109 | 110 |
+| SYN | 31 | 63 | 111 | 112 | 113 | 114 | 115 |
+
+On / off: 64 and up is on. Settings 0 to 127 are 0 to 100. Model: the whole 0 to 127 range is divided between the block's models.
+CC 7 is the MASTER volume. Details, the clock sync and the limits: [MIDI.md](MIDI.md).
+
## Saving your sounds (v25)
- `EDITED` at the top of the PLAY screen means the sound has been edited since the preset was loaded.
- **Long press on PUSH**: SAVE page. The knob picks the slot (1 to 52; the current preset is offered first; the
diff --git a/docs/firmware/README.md b/docs/firmware/README.md
index 889122d..d2fc368 100644
--- a/docs/firmware/README.md
+++ b/docs/firmware/README.md
@@ -1,3 +1,7 @@
The installer's packages land here when you run `build.bat` (or `firmware/build.sh`):
`PocketX_9NN.fwsc`, `BlackBox_9NN.fwsc` and `manifest.json`. They are published with the release and on GitHub Pages
by `PUBLISH-GITHUB.bat`, not kept in the repository.
+
+A test build (for example `node tools/op.mjs package-test`) goes to a subfolder, `midi-test/`, with its own `manifest.json`. The
+installer page offers it only through `index.html?channel=midi-test`, with a banner; the normal page and the files above are never
+changed by it.
diff --git a/docs/index.html b/docs/index.html
index fdc946d..9395bfe 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -85,6 +85,7 @@
2
…
+
@@ -148,6 +149,7 @@
3
foot1: "OpenPocket is an independent project, not affiliated with Donner or M-VAVE. Install at your own risk. Source code: GPL-3.0.",
foot2: "Update protocol and loader from Felucca and felucca-bsp (Hügelton Instruments, GPL-3.0 / MPL-2.0). JieLi AC79 SDK files: Apache-2.0. Inter typeface: SIL OFL 1.1.",
e_notfound: "Pedal not found. Check the cable (data), close other MIDI apps, then try again.",
+ testchan: "TEST CHANNEL: this installs a test build, not the release. It is for people who agreed to try it, and it is not the version the normal installer page offers. To go back, open this page without ?channel=… and press Install.",
bbwarn: "M-VAVE BlackBox: v31 is the fix for issue #1 (OpenPocket restarted every 4 s after the pedal was switched off and on), still being tested on the BlackBox. Please report how it goes on GitHub. If your BlackBox restarts in a loop, use the rescue mode below.",
toeditor: "Preset editor →",
rescueon: "Rescue mode, for a pedal that restarts in a loop: tick this, press Connect, then plug the pedal in (or let it restart). The install starts by itself as soon as the pedal answers; it stops the pedal's sound processing at once, so the restarts stop. It installs the official file chosen in step 3 if there is one for this pedal, otherwise OpenPocket.",
@@ -178,15 +180,19 @@