Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions docs/midi-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ The mapping below is transport-independent. What ships today:
| **USB** (a USB-MIDI class device — the MIDI edition) | The DevKit's USB port, the one the panel is flashed over, plugged into the computer. No driver and no session: the panel enumerates as a MIDI device named `Patternflow` (a virtual serial port comes with it — that is the console's log and Improv, leave it be). MIDI edition only: the port has to be the S3's USB-OTG controller for a MIDI device to exist, and that is a build-time choice ([`EDITIONS.md`](EDITIONS.md)). **Data only** — the panel is powered from `J4` as always, never from that cable. Both transports share the map below and can be up at once. |

Channel: **1** by default (`PF_MIDI_CHANNEL`; `0` listens on every channel
and sends on 1). MIDI Thru is off — the panel never echoes a host's messages
back to it.
and sends on 1). MIDI Thru is off: the panel never echoes a host's CC or note
back to it. A Program Change from the host comes back as the pattern change it
made, as every pattern change does.

## Host → device

| Message | Meaning |
|---|---|
| **CC 20 · 21 · 22 · 23** | Knob 1–4, **absolute**. `0..127` maps to the device's `0..1000` bus (rounded; 127 = 1000). The value **holds** until a hand turns that knob, exactly like `POST /api/params` and MQTT `param/N`. |
| **CC 24 · 25 · 26 · 27** | Knob 1–4, **relative**. `64` = no motion, `65` = +1 detent, `63` = −1, and so on (two's-complement offset). Merged with physical motion at 1× per step — the same lane as OSC's `/patternflow/knob/N/delta`. Does not release an absolute hold. |
| **CC 24 · 25 · 26 · 27** | Knob 1–4, **relative**. `64` = no motion, `65` = +1 detent, `63` = −1, and so on (binary offset). Merged with physical motion at 1× per step — the same lane as OSC's `/patternflow/knob/N/delta`. Does not release an absolute hold. |
| **Note 60 · 61 · 62 · 63** (C4–D♯4) | Button 1–4. Note-on = press (edge), the button reads as held for as long as the note is held; note-off (or velocity 0) releases. |
| **Program Change `n`** | Select pattern index `n` (0-based, the order on `/patterns`). Treated as a person's choice: it is remembered across reboots, like a knob pick. Ignored while a show claims the panel. |

Expand All @@ -48,7 +49,7 @@ the channel is `0`.

The device's rules, not MIDI's: **a hand on an encoder beats everything.** An
absolute CC pins a knob until that encoder moves; relative CCs and notes are
just more input, and the audio lanes yield to both.
just more input, and the audio lanes yield to the hand and to an absolute CC.

## Device → host

Expand Down Expand Up @@ -135,6 +136,10 @@ most.
- **1.2** (unreleased) — additive receive diagnostics, transport service
independent of display frames, and suppression of outbound CCs generated
from absolute-bus compatibility clicks. No new transport or CC mapping.
Where this file said two things, the section that defines the message now
stands: CC 24–27 are binary offset, as their examples always were; a host's
Program Change comes back as the pattern change it made; the audio lanes
yield to a hand and an absolute CC.
- **1.1** (2026-09-06) — outbound CC 24–27 paced: one message per knob per
50 ms at most (`PF_MIDI_OUT_MIN_INTERVAL_MS`), the final value always sent;
`rel` carries the steps summed since the previous message. On the Audio
Expand Down
2 changes: 1 addition & 1 deletion docs/rest-api-vectors/absent-caps.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"contract": "docs/rest-api.md#get-apistatus",
"says": "The default build reports `[\"patterns\",\"params\",\"sleep\"]` and nothing else; each [edition](EDITIONS.md) adds its own — Audio adds `osc` and `audio`, Performance adds `weather`, `mqtt` and `shows`.",
"says": "The default build reports `[\"patterns\",\"params\",\"sleep\"]` and nothing else; each [edition](EDITIONS.md) adds its own — Audio adds `osc`, `audio`, `audio-in` and `midi`, Performance adds `clock`, `weather`, `mqtt` and `shows`.",
"features": [],
"steps": [{ "send": { "http": "GET /api/status" }, "expect": { "reply": { "status": 200, "body": { "caps": ["patterns", "params", "sleep"] } } } }]
}
3 changes: 2 additions & 1 deletion docs/rest-api-vectors/state-params-subset.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"contract": "docs/rest-api.md#post-apiparams",
"says": "`p1`..`p4`, each `0..1000`, any subset; at least one required.",
"says": "`p1`..`p4`, each `0..1000`, any subset. A request needs at least one `pN` or `dN`.",
"features": [],
"steps": [
{
Expand All @@ -11,6 +11,7 @@
}
},
{ "send": { "http": "POST /api/params" }, "expect": { "reply": { "body": { "ok": false, "error": "<string>" } } } },
{ "send": { "http": "POST /api/params?d2=1" }, "expect": { "reply": { "status": 200, "body": { "ok": true } } } },
{
"send": { "http": "POST /api/params?p2=1001" },
"expect": { "reply": { "body": { "ok": false, "error": "<string>" } }, "status": { "paramActive": [true, false, true, false] } }
Expand Down
Loading
Loading