diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 77679420..24ab53dc 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -9,5 +9,10 @@
## Tested
+## Which row
+
+
---
- [ ] Touches `web/` → it builds (`npm run build`)
+- [ ] Touches `firmware/` → every composition builds (`./firmware/bundles/build.sh all`), and the core still names no feature (`python firmware/toolchain/check_boundaries.py`)
+- [ ] Touches a markdown file → its relative links resolve (`python3 .github/scripts/check_links.py`)
diff --git a/AGENTS.md b/AGENTS.md
index a6646372..9a3f68dd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -8,10 +8,10 @@ Patternflow is an open-source hardware instrument: four rotary encoders controll
It is standalone (not Eurorack), powered from a 5 V power bank through a screw terminal, and it is not an analog video synthesizer. Sound arrives through the Audio edition (on-board microphone, OSC, MIDI), not the default firmware.
## Repository map
-- `docs/` — contracts and long-form docs: `EDITIONS.md` (the firmware seam — read it before touching firmware), `rest-api.md` / `osc-spec.md` / `midi-spec.md` / `audio-ws-spec.md` (wire contracts integrations are built against), `pfst-v2-spec.md` (show files), `panel-compatibility.md`, `RELEASING.md`, `SERVICES.md` (production server ops), the assembly map (`docs/assembly/`), the manifesto, investigations, and media (build-guide images in `docs/build-guide/`). Root-level guides: `BUILD_GUIDE.md` (the **v3.0 board**), `BUILD_GUIDE_v2.md` (legacy v2.x board), `PATTERN_GUIDE.md` (community → deck → Pattern Lab → device), `FEATURE_GUIDE.md` (writing a firmware feature or edition), `AUDIO_GUIDE.md` (sound into the panel). The live roadmap is patternflow.work/journal and the `/roadmap` page.
+- `docs/` — contracts and long-form docs: `EDITIONS.md` (the firmware seam — read it before touching firmware), `rest-api.md` / `osc-spec.md` / `midi-spec.md` / `audio-ws-spec.md` (wire contracts integrations are built against), `pfst-v2-spec.md` (show files), `panel-compatibility.md`, `RELEASING.md`, `SERVICES.md` (production server ops), the assembly map (`docs/assembly/`), the manifesto, investigations, and media (build-guide images in `docs/build-guide/`). Root-level guides: `BUILD_GUIDE.md` (the **v3 board**: v3.9, and v3.0 the same way), `BUILD_GUIDE_v2.md` (legacy v2.x board), `PATTERN_GUIDE.md` (community → deck → Pattern Lab → device), `FEATURE_GUIDE.md` (writing a firmware feature or edition), `AUDIO_GUIDE.md` (sound into the panel). The live roadmap is patternflow.work/journal and the `/roadmap` page.
- `firmware/` — PlatformIO/Arduino code for the ESP32-S3. Main sketch folder: `firmware/patternflow/` with `patternflow.ino`, `config.h` (pin mappings, brightness, limits, LED calibration), `net_config.h` (Wi-Fi / Improv / OTA / self-update defaults and the lane scale — core only; a feature's own tunables live in `features//_config.h`, and settings TUNE a feature, they never add one; per-device secrets in gitignored `patternflow_secrets.h`), and `pattern_registry.h` (function-pointer pattern table; compiles in Origin alone — the rest of the curated set ships as the Basics pack). Shared engine code lives in `firmware/patternflow/src/` (`core_display.h` HUB75 driver, `core_encoders.h`, canvas/color/math/noise helpers, Wi-Fi/OTA/web-console modules, the hotspot in `core_hotspot.h`) — core only: OSC, audio, MQTT, weather, shows, MIDI and the clock all live in `features/`. `firmware/patternflow/abi/` is the frozen host ⇄ module contract. Curated patterns live in `firmware/patternflow/presets/` (`preset_origin.h`, `preset_wave_saw.h`, dated presets), each using its own pattern namespace; user patterns are `.pfm` modules uploaded to the device over Wi-Fi, built from `firmware/modules/` via `firmware/toolchain/build_module.py` (the old `custom1.h`–`custom3.h` root slots are gone). Features attach through `firmware/patternflow/features/` (named `addons/` until 2026-08-30; legacy spellings are shimmed for out-of-tree bundles — see docs/EDITIONS.md) — a directory and a descriptor of function pointers, with the core naming none of them (see its README). `firmware/bundles/` names firmwares built from the same tree (the shelf editions `audio` and `performance`, plus `clock` and `midi`, compositions the tree keeps buildable but does not publish; the default build has no bundle): two files each, saying which features compile in and what the build calls itself; `firmware/bundles/build.sh` builds the default or a named one, `shelf.sh` stages a publishable image. Device console pages are plain HTML in `firmware/patternflow/console/`, spliced into `*_index.h` by `firmware/toolchain/console_pages.py`. Tooling lives in `firmware/toolchain/` (repo-level scripts) and `firmware/patternflow/toolchain/` (PlatformIO `extra_scripts`, which must stay sketch-relative). Also: `firmware/encoder_test/` (standalone encoder diagnostic) and `firmware/CUSTOM_PATTERNS.md` (pattern authoring guide).
-- `hardware/` — Hardware designs. `case/` (Blender source in `source/`, STLs by printer bed size, `legacy_v2/` for the v2.x board), `pcb/` (KiCad 10.0 source, Gerbers per revision, schematic PDF), `bom/` (`bom_v3.9.csv` — **the BOM source of truth**; the guide's table is derived from it. `bom_v3.0.csv` is kept for the previous board).
-- `web/` — Next.js site at patternflow.work: landing page (`/`, `/pattern`, `/build`, `/inside` are tabs of one view), the interactive guide (`/guide` is a hub over `/guide/build`, `/guide/play`, `/guide/make` and `/guide/audio`, each with a `/ko` twin; one 3D stage in `components/guide/`, the firmware simulator in `lib/guide/`), Pattern Lab (`/pattern-lab`), community (`/community/**`, SQLite + Drizzle + Better Auth, only on the Pi deployment), browser flasher (`/flash`), the edition shelf (`/editions`; `/variants` redirects there), the feature catalogue (`/features`, reels per feature), device update handoff (`/update`), journal, roadmap. `lib/pattern/` is the pattern runtime and annotations, `lib/lab/` the Pattern Lab, `lib/community/` the community, `lib/ai/` the Gemini client. Architecture doc: `web/ARCHITECTURE.md`. The JS presets in `web/src/lib/presets/` are the source of truth for firmware preset headers.
+- `hardware/` — Hardware designs. `case/` (Blender source in `source/`, STLs by printer bed size, `knobs/`, `legacy_v2/` for the v2.x board, `legacy_lasercut/` for the v1/v2 acrylic drawings, and `remixes/` — community enclosures, one folder each, whose README is its license header), `pcb/` (KiCad 10.0 source, Gerbers per revision, schematic PDF), `bom/` (`bom_v3.9.csv` — **the BOM source of truth**; the guide's table is derived from it. `bom_v3.0.csv` is kept for the previous board).
+- `web/` — Next.js site at patternflow.work: landing page (`/`, `/pattern`, `/build`, `/inside` are tabs of one view), the interactive guide (`/guide` is a hub over `/guide/build`, `/guide/play`, `/guide/make` and `/guide/audio`, each with a `/ko` twin; one 3D stage in `components/guide/`, the firmware simulator in `lib/guide/`), Pattern Lab (`/pattern-lab`), community (`/community/**`, SQLite + Drizzle + Better Auth, only on the Pi deployment), browser flasher (no page of its own: the **Flash Patternflow** button on `/pattern` and in the Play guide, installing `public/flash/manifest.json` and the images in `public/flash/bin/`; the bare `/flash` redirects to `/guide/play#flash`), the edition shelf (`/editions`; `/variants` redirects there), the feature catalogue (`/features`, reels per feature), device update handoff (`/update`), journal, roadmap. `lib/pattern/` is the pattern runtime and annotations, `lib/lab/` the Pattern Lab, `lib/community/` the community, `lib/ai/` the Gemini client. Architecture doc: `web/ARCHITECTURE.md`. The JS presets in `web/src/lib/presets/` are the source of truth for firmware preset headers.
- `tools/` — desktop-side helpers: `patternflow-audio-extension` (the browser audio-react extension — also the authoring source the device's `/audio-in` console page is assembled from), `patternflow-audio-android` (phone capture app), `rtpmidi-probe`, and `case-models` (builds the /build preview's per-case 3D models, `web/public/cases//model.glb`, from the case files in the repo; run by hand, not by the site build).
- `integrations/` — host-software integrations, each with its own README. `integrations/ableton/` is the Max for Live bridge (knobs → Live parameters over OSC). The Home Assistant integration left this repository on 2026-09-03; its author maintains it separately. Integrations are built against the contracts in `docs/` (`rest-api.md`, `osc-spec.md`, `midi-spec.md`), not against the firmware source. `docs/rest-api.md` also has the table for choosing between HTTP, OSC and MQTT, and the rules that make the device's single-connection web server easy to knock over.
diff --git a/AUDIO_GUIDE.md b/AUDIO_GUIDE.md
index 6460a30e..a6e8536e 100644
--- a/AUDIO_GUIDE.md
+++ b/AUDIO_GUIDE.md
@@ -25,7 +25,11 @@ written for AI coding agents; everything above it is for people.
Any of these puts the same firmware on your panel:
- **One click:** [patternflow.work/editions](https://patternflow.work/editions)
- → Audio → flash from the browser.
+ → Audio → **Install to my panel**. Your browser hands the image to a panel
+ that already runs Patternflow, over Wi-Fi; patterns, networks and settings
+ stay. A DevKit that has never run Patternflow takes the core first, from
+ **Flash Patternflow** ([BUILD_GUIDE §8](BUILD_GUIDE.md#8-firmware)), and
+ then this.
- A release's `patternflow-audio.ino.bin` via the console's `/update` page.
- Build it yourself: `./firmware/bundles/build.sh audio` (add
`flash ` to send it over Wi-Fi).
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24acf1b3..9e903d47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,11 +13,16 @@ All notable changes to Patternflow will be documented in this file, newest first
- **/build lets you pick the case, and the 3D view shows the one you pick.** The Build panel's switch (the official v3.9 case, Besoiobiy's printed case, SimonePDA's laser-cut one; `?case=` links to one) brings up that case's card, a remix's opening with its README on GitHub, and swaps the device in the preview for that case, with the same v3.9 board, DevKit and LED panel inside (and, on Besoiobiy's case, its own knob caps). SimonePDA's case comes in its two finishes, acrylic and MDF. Each model is made by `tools/case-models` from the case's own files in this repository (the guide's v3.9 case, Besoiobiy's STLs, SimonePDA's cutting layout) and sits beside the case's photos, `web/public/cases//model.glb`. The model the page loads first is 0.32 MB, where the old v3.0 one (`3dforweb.glb`, now removed) was 1.15 MB; a remix's model loads when it is picked, and the guide takes its LED panel from the official one.
- **/build has a fourth case, mbchars' horizontal desktop one, in the switch and in the 3D view.** Its card is its README's facts and the author's photos, and the switch is four tabs, two by two on a phone. `tools/case-models/mbchars.py` puts its model together from the remix's STLs, since no assembled file exists, and checks it against the README; in it the panel lies the long way (`ledTurn` in `placement.json`), the official knob is printed white (`knobLook`), and the orange is the one in the author's 3MF, `#F46B16` (`pla_orange`). The model is 0.38 MB and loads when the case is picked; the other three rebuild byte for byte. The case is 41 cm across, so on a desktop's tall view it is drawn smaller, by as little as keeps it clear of the arrows; on a phone all four cases, and on a desktop the three upright ones, are still drawn at the one scale. The build map's pin for the same build goes by the maker's name now, mbchars (it was extraplojo).
+- **The 3D view on /build has arrows and a name.** On the Build tab, arrows either side of the device step through the cases, round and back to the first, as the build map's arrows step through builds; the switch, the card and `?case=` follow. Over the device is whose case it is, the name on its tab (Official, Besoiobiy, SimonePDA, mbchars), in the build map's label.
- **The preview's case is white, not beige, and the rest of it looks more like the photos.** The printed parts are a neutral white PLA (`#f4f4f2`, matte), where the guide's warm white had come along; the case front measures b\* +0.4 and L\* 89.8 where it was +1.7 and 82.5. The frame goes through the shoulder of the Neutral tone curve, which leaves what is under its knee as lit and rolls off what is brighter with its channels together, so a bright light no longer clips red and green first and leaves the white yellow. The light is made on the page from light panels, as on the guide's stage, instead of an environment map fetched from a CDN at run time. The LED panel is a grid of round LEDs on a black mask where it was solid blocks on grey (each LED becomes its whole cell once it is under about three pixels across, as on a phone), its sides and back are the panel's black frame, and what glowed before (the whites, and the bright yellows and cyans of a pattern from the editor) glows as before. The official knob is the printed knob's own profile drawn round, where it was a 32-sided prism. SimonePDA's clear acrylic is clear: the canvas is see-through, and every partly covered pixel used to reach the page far too bright, which drew the sheet frosted white; its three feet, which no photo shows on it, now come out only with the parts and on Assemble. Every case is drawn at one scale, so the LED panel is the same size in all three (the remixes drew it 3.5 to 4% smaller). The three models were rebuilt with `tools/case-models/build.sh`; the official one grows by 0.8 KB.
+- **The guide's first flash starts in download mode, and its Wi-Fi assumes the step won't come.** The Play guide's 01 Flash (`/guide/play`, and `/ko`), where the Build guide sends a new DevKit, now has hold BOOT, tap RST, let go as its fourth card, before Flash Patternflow, every first flash, instead of a fallback for a missing port. The Wi-Fi card says plainly that after an install from download mode the dialog usually ends without asking, and gives the moves in order: close it, press RST once, Flash Patternflow again, the port again, Connect to Wi-Fi; its flasher screens lead with that path ("Usually: after RST"), the install-right-away case second, and it carries its own Flash Patternflow button. A new last card, after Power, is the panel's own network for when that didn't take: hold K2 for HOTSPOT and `patternflow-xxxx`, join it with the password `patternflow`, open `http://192.168.4.1/` and pick your network on the Wi-Fi page, with what to do when it isn't in the phone's list yet (it comes up about 15 seconds after power-on). The stage follows the cards, and the device simulator draws the NETWORK screen's hotspot (`setHotspot`). The Build guide's flash card, the Pattern tab's notes under Flash Patternflow and the breadboard page say the same order. Anchors `#flash-1` to `#flash-3` are where they were; `#flash-4` is now download mode.
+- **`/flash` goes to the guide's Flash chapter.** It had no page, though the device's Update page ("flash it over USB at patternflow.work/flash") and every release's FLASHING.md send people there; it now redirects to `/guide/play#flash`. `/flash/manifest.json` and `/flash/bin/*` are untouched.
### Docs
- **The build pages say what is true now.** Laser cut is no longer "being prepared" on /build, in the README, the v2 guide or `llms.txt`: SimonePDA's case is it. The printing time is about 10 hours for the official case on a 256 mm bed (/build said 10–12, the breadboard guide 11), the v2 guide and the assembly map name the v3.9 board, and the breadboard guide flashes through the DevKit's native USB port from the Pattern tab and links the sections that exist.
+- **The README is new from its first screen.** A one-line definition, the hero and the photosensitivity warning, then four doors (Build, Play, Make patterns, Add sound), each to its guide page with the written guide beside it, and **First light**: J4-only power, download mode first, the Wi-Fi step that usually doesn't come and what to do, the panel's own network, the Basics pack. The story, the community, the facts and the editions follow, shorter, with the long reference material folded instead of cut.
+- **The READMEs and docs were tidied.** Where a doc gives the first flash (`firmware/README.md`, `PATTERN_GUIDE.md`, `SUPPORT.md`, `docs/assembly/`) it now says download mode first, that the Wi-Fi step usually doesn't appear the first time and what to do, and the hotspot. The indexes (`docs/README.md`, `docs/REPOSITORY.md`, the hardware and remix READMEs, `web/README.md`, `web/ARCHITECTURE.md`) list the guide pages, the three case remixes, `legacy_lasercut/` and the bundles that exist, and stale wording went: the NETWORK screen's old rows, the custom slots, "variant", the dead `/flash` page. `BUILD_GUIDE.md` and `BUILD_GUIDE_v2.md` are as they were.
## [3.11.0] - 2026-10-04
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c7d695cd..3cf1200a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,6 +7,7 @@ Patternflow is a device, a firmware, a website and a pile of documentation, and
| You have… | It goes in | A change touches | What CI runs on it |
| :--- | :--- | :--- | :--- |
| A fix to a guide, a README or a spec | the markdown file itself: the root guides, `docs/`, any folder's README | one file | every relative link must resolve (`docs-links`) |
+| A fix to the **interactive guide** at [patternflow.work/guide](https://patternflow.work/guide) (Build, Play, Make, Audio) | its words are TypeScript, English and Korean side by side: `web/src/components/guide/copy.ts` for Play, `web/src/components/guide/copy/` for Build, Make, Audio and the hub | the copy, both languages | `web-ci` |
| A fix to the **breadboard** build guide | `web/src/app/build/breadboard/page.tsx` — that guide is React, not markdown | JSX text | `web-ci` (`next build`) |
| Your finished build, for the [build map](https://patternflow.work/inside) | `web/src/components/sections/InsideGlobe/builds.ts` plus a photo folder `web/public/builds//` — see [Your build](#your-build). Not a git person? The [Share your build](../../issues/new?template=share_build.yml) form does the same | one `Build` entry and its photos | `web-ci` (the typecheck rejects a malformed entry) |
| A firmware **feature** | `firmware/patternflow/features//`, plus one line in the bundle it joins (`firmware/bundles//features_local.h`) | the folder, the bundle line, an inventory row in `features/README.md`; a console page also adds a row to `firmware/toolchain/console_pages.py`; a library also goes in `platformio.ini` | `firmware-checks` (boundary, ABI freeze, presets, console pages…) and `firmware-build` (every edition, from scratch) |
diff --git a/FEATURE_GUIDE.md b/FEATURE_GUIDE.md
index 96db5e76..8b16150c 100644
--- a/FEATURE_GUIDE.md
+++ b/FEATURE_GUIDE.md
@@ -160,7 +160,9 @@ python firmware/toolchain/console_pages.py check # if you added a console page
Compiling is not testing — most historical regressions were invisible on the
build in front of whoever caused them, which is why `build.sh all` exists.
CI runs the same set ([`firmware-checks.yml`](.github/workflows/firmware-checks.yml),
-[`console-sync.yml`](.github/workflows/console-sync.yml)).
+[`console-sync.yml`](.github/workflows/console-sync.yml), and
+[`firmware-build.yml`](.github/workflows/firmware-build.yml) for every
+composition, one job each).
If the user asks for a PR against this repository: it should touch only
`features//` plus a bundle, pass everything above, and follow
diff --git a/PATTERN_GUIDE.md b/PATTERN_GUIDE.md
index ca4cf717..a32a39d0 100644
--- a/PATTERN_GUIDE.md
+++ b/PATTERN_GUIDE.md
@@ -24,22 +24,30 @@ in a window, and a practice AI to take the two prompts.
## 0. Before you start
-**A USB cable is for exactly one moment.** The very first browser flash at
-[patternflow.work/pattern](https://patternflow.work/pattern) — you set up
-Wi-Fi as part of it, and from then on **everything is wireless.** Whether
-you're in the community or in Pattern Lab, every path a pattern takes to the
-device goes over Wi-Fi.
+**A USB cable is for exactly one moment: the very first flash**, with the
+ESP32-S3 out of the board, from **Flash Patternflow** at
+[patternflow.work/pattern](https://patternflow.work/pattern). Put the module
+in download mode first — hold **BOOT**, tap **RST**, let go of BOOT — then
+press the button. The Wi-Fi step usually does not appear after that first
+install, and that is expected: press **RST**, run **Flash Patternflow** again
+and choose **Connect to Wi-Fi**, or seat the module, power the panel and join
+the network it raises for itself, `patternflow-xxxx` (password
+`patternflow`), to set Wi-Fi at `http://192.168.4.1/`. From then on
+**everything is wireless.** Whether you're in the community or in Pattern
+Lab, every path a pattern takes to the device goes over Wi-Fi.
So this whole guide rests on one assumption: **the device is powered on and
on the same Wi-Fi network as your computer.** If you haven't done the first
flash and Wi-Fi setup yet, finish the
-[firmware section of BUILD_GUIDE](BUILD_GUIDE.md#8-firmware) first.
+[firmware section of BUILD_GUIDE](BUILD_GUIDE.md#8-firmware) first, or the
+Play guide's [Flash chapter](https://patternflow.work/guide/play#flash), which
+walks it on a board you can turn.
| You need | Why |
| --- | --- |
| An assembled, flashed Patternflow | It's what this guide is about |
| The device connected to Wi-Fi | The premise of every transfer here |
-| Firmware **v3.2.0 or newer** | Pattern modules (wireless install) need it |
+| Firmware **v3.10.5 or newer** (the flasher installs the current release) | Pattern modules (wireless install) need v3.2.0, the Basics pack v3.5.1, and the larger patterns, as they are built now, v3.10.5 |
| A community account (optional) | Browsing, knobs and deck-building work without one. Sending to your device (building) and sharing / liking / forking need a sign-in |
The device's page lives at **`patternflow.local`**. Android can't resolve
@@ -55,7 +63,7 @@ travels to the device as a module — a `.pfm` file of a few KB — and arrives
over Wi-Fi in seconds.** The firmware is not rebuilt and nothing is reflashed.
The buttons wear slightly different names on different screens —
-`Send to my Patternflow`, `SEND TO MY BOARD`, `APPLY TO MY PATTERNFLOW` — but
+`Send to my Patternflow`, `SEND TO MY BOARD`, `Apply to my Patternflow` — but
they are all the same thing: compile this pattern into a module, hand it to
the board.
@@ -313,7 +321,7 @@ to **try it on the board first.**

-1. Press **`APPLY TO MY PATTERNFLOW`** — it builds the module and takes
+1. Press **`Apply to my Patternflow`** — it builds the module and takes
seconds.
2. In the dialog, press **`Send over Wi-Fi`** — with the device on and
connected, it uploads immediately.
@@ -363,11 +371,12 @@ onto their device. The loop has come all the way around.
| Symptom | Check |
| --- | --- |
+| The panel isn't on Wi-Fi, or the flasher never asked for a network | Expected after a first flash. Press **RST**, run **Flash Patternflow** again and choose **Connect to Wi-Fi** (2.4 GHz only; type the name exactly). Or power the panel, wait about 15 s, join its own network `patternflow-xxxx` (password `patternflow`; hold **K2** for the exact name; not in the phone's list yet? refresh it) and pick your network at `http://192.168.4.1/` — [BUILD_GUIDE §8](BUILD_GUIDE.md#8-firmware) |
| `Send over Wi-Fi` does nothing, or fails | Device power · **same Wi-Fi network** as the computer · on Android, use the IP from the NETWORK screen (hold K2) instead of `patternflow.local` |
| The device's Patterns page won't open | Same as above — try typing `patternflow.local` into the address bar directly |
| A card has no `+` (add to deck) | Not a bug — the author shared it without an `.h` header (not hardware-verified) |
| The `.h` conversion looks wrong on the device | Ask the AI to convert again (a plain retry often fixes it) · simplify the pattern code · try a different model |
-| Modules uploaded but don't appear in the list | Firmware older than v3.2.0 — update it at [patternflow.work/update](https://patternflow.work/update) |
+| Modules uploaded but don't appear in the list | Firmware older than v3.2.0 — update it at [patternflow.work/update](https://patternflow.work/update). A larger pattern that shows PATTERN FAILED on firmware older than v3.10.5 needs the same update |
| A `.zip` pack was dropped but nothing installed | The pack has to contain `.pfm` modules. A zip of `.js` patterns is not a pack — build them first |
| The deck's order came out wrong on the device | Re-download the pack: the running order ships inside it, and a pack built before the deck was rearranged carries the old one |
| The device is out of storage | The Patterns page shows free space (KB free) at the top right — delete modules you don't use |
diff --git a/README.md b/README.md
index 2208549a..e1d9fbe0 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@
+
+
# Patternflow
+**An open-source LED synthesizer you play with four knobs.**
+8,192 pixels on a 128 × 64 LED matrix, one ESP32-S3, and every frame computed live. Nothing is pre-rendered.
+
[](https://github.com/engmung/Patternflow)
[](./LICENSE-MIT)
[](./LICENSE-CC-BY-SA)
@@ -7,6 +12,8 @@
[](https://www.crowdsupply.com/engmung/patternflow)
[](https://discord.gg/Vr9QtsxeTk)
+
+
@@ -17,19 +24,65 @@
> **Photosensitivity warning.** Patternflow displays rapidly changing light patterns that may trigger seizures in people with photosensitive epilepsy. If you experience any discomfort, stop use immediately.
-**Patternflow is an open-source LED synthesizer.** You play it with four knobs. 8,192 pixels across a 128 × 64 matrix respond the instant you turn one; nothing is pre-rendered, every frame is computed live on the device. A pattern is just a small file, so every Patternflow plays every pattern anyone makes.
+**Patternflow is an open-source reinterpretation of Nam June Paik's *Participation TV* (1963):** a screen you play instead of watch. A pattern is just a small file, so every Patternflow plays every pattern anyone makes, and everything the instrument is made of is open: the PCB, the case, the firmware, the website and the whole workflow for making patterns.
+
+
+
+
+
+### [Build one →](https://patternflow.work/guide/build)
+Solder the board, print the case, put it together: every step on a 3D Patternflow you can turn in the browser.
+Written: [BUILD_GUIDE.md](BUILD_GUIDE.md) · [every other route](docs/assembly/README.md) · [breadboard build](https://patternflow.work/build/breadboard)
+
+ |
+
+
+### [Play it →](https://patternflow.work/guide/play)
+Flash it, get it on your Wi-Fi, learn the four knobs, install patterns, open the console.
+Written: [First light](#first-light), just below · [BUILD_GUIDE.md §8 Firmware](BUILD_GUIDE.md#8-firmware)
+
+ |
+
+
+
+
+### [Make patterns →](https://patternflow.work/guide/make)
+Describe a look to an AI, play it in the browser, shape it in the Pattern Lab, send it to your panel over Wi-Fi.
+Written: [PATTERN_GUIDE.md](PATTERN_GUIDE.md) · [Custom Patterns](firmware/CUSTOM_PATTERNS.md)
+
+ |
+
+
+### [Add sound →](https://patternflow.work/guide/audio)
+A browser tab's audio, a microphone on the board, MIDI from Ableton, Logic or Bitwig.
+Written: [AUDIO_GUIDE.md](AUDIO_GUIDE.md) · [MIDI in Ableton](docs/midi-ableton.md)
-**Start with [the guide](https://patternflow.work/guide).** It builds one with you, shows you how to play it, walks you through making patterns, and connects sound and MIDI, each step on a 3D Patternflow you can turn in the browser: **[Build](https://patternflow.work/guide/build)** · **[Play](https://patternflow.work/guide/play)** · **[Make](https://patternflow.work/guide/make)** · **[Audio](https://patternflow.work/guide/audio)**.
+ |
+
+
+
+**[The guide](https://patternflow.work/guide) is the place to start**, in English and Korean; a step that didn't get you through has a **Stuck here?** link, which opens an issue with that step filled in. Writing firmware instead? [FEATURE_GUIDE.md](FEATURE_GUIDE.md) → [docs/EDITIONS.md](docs/EDITIONS.md).
+
+## First light
+
+For a board that's built. The same steps, with the flasher's own screens, are the Play guide's **[Flash chapter](https://patternflow.work/guide/play#flash)**.
+
+1. **Power only through `J4`, the screw terminal:** a stripped USB cable from a 5 V power bank. Never USB-C. The DevKit's USB-C ports are for flashing, with the DevKit out of the board.
+2. **Take the DevKit out and plug it into a computer with desktop Chrome or Edge,** through its **left** port, labelled `USB` (antenna up, ports toward you; the only port the Wi-Fi step works on), with a USB-C cable that carries data. A charge-only cable is the usual reason nothing shows up.
+3. **Download mode first, every first flash:** hold **BOOT**, tap **RST**, release BOOT. Then press **[Flash Patternflow](https://patternflow.work/guide/play#flash)**, pick `USB JTAG/serial debug unit` and install (about two minutes). It erases the flash; a new build has nothing to lose.
+4. **The Wi-Fi step usually won't appear, and that's normal:** after download mode the board usually doesn't restart into Patternflow by itself. Press **RST**, press **Flash Patternflow** again and pick the port: the dialog now opens on Patternflow's own menu. Choose **Connect to Wi-Fi**, wait out the short scan (it lists nothing), and type your network's exact name (capitals count, 2.4 GHz only) and its password.
+5. **Seat the DevKit (orientation per the silkscreen), power up, hold K2.** `CONNECTED` and an address: you're on Wi-Fi. `HOTSPOT` and a name like `patternflow-a1b2`: it found no network it knows, so it became one. Join that from a phone or laptop (password `patternflow`), open `http://192.168.4.1/`, pick your network on the Wi-Fi page, add its password and save. Not in the phone's list yet? It only comes up about 15 s after power-on: wait, refresh the list, and match the name K2 shows. The phone says there's no internet: true, and it stays connected.
+6. **On your Wi-Fi, open the console** at `patternflow.local` (on Android, type the IP K2 shows) and install the **[Basics pack](https://community.patternflow.work/community/decks)**: 33 patterns, one click. A new board runs Origin alone.
+
+> **Already running Patternflow?** Skip download mode, which erases installed patterns and saved Wi-Fi: update from the console's **Update** page over Wi-Fi and everything stays. BOOT + RST is for the first flash, or a rescue.
## Where it began
Almost every screen around us is a playback device. It takes in content and shows it to us, and we sit in front of it and watch. Patternflow is different: touch it and it answers in that instant, and nobody plays the same thing twice.
-In January 2026 I stopped by the Nam June Paik Art Center, which happened to be running an event marking twenty years since Paik's death. That is how I ended up standing in front of *Participation TV* (1963): you speak into a microphone and the picture on the screen moves with your voice. It turned the viewer from someone watching into someone taking part. I spoke into it, and my voice became the picture.
-
-Two months later I connected an LED matrix and a potentiometer for the first time.
+In January 2026 I stopped by the Nam June Paik Art Center, which happened to be running an event marking twenty years since Paik's death. That is how I ended up standing in front of *Participation TV* (1963): you speak into a microphone and the picture on the screen moves with your voice. It turned the viewer from someone watching into someone taking part. I spoke into it, and my voice became the picture. Two months later I connected an LED matrix and a potentiometer for the first time.
-Where Paik brought the audience into the work, Patternflow hands over the making of it.
+**Where Paik brought the audience into the work, Patternflow hands over the making of it.**
I put that first pattern, *Patternflow: Origin*, on an LED matrix with four knobs and posted it online. Some people asked how to buy it; far more asked how to build it. So everything went public: schematics, firmware, 3D models, the build guide, and the whole workflow for making new patterns.
@@ -42,57 +95,48 @@ I put that first pattern, *Patternflow: Origin*, on an LED matrix with four knob
Builds by Nath (UK), shooter (Poland), day (France), Enerjoy (Norway), SimonePDA (UAE), Nick (USA), and Jon (USA).
-Builders from around the world keep adding pins to the **[build map](https://patternflow.work/inside)**. Someone built theirs as a Raspberry Pi port with a 64 × 32 matrix, another layered paper and acrylic into a diffuser, others cut their own enclosures. Two of them live in DJ booths.
+Builders keep adding pins to the **[build map](https://patternflow.work/inside)**: a Raspberry Pi port on a 64 × 32 matrix, a diffuser layered from paper and acrylic, enclosures cut from scratch. Two of them live in DJ booths.
-I started this alone. It hasn't worked that way for a while now. The web-upload feature wasn't mine: someone suggested it, built a proof of concept, and shared the code, and two days later it shipped as a main feature. The `.pfm` pattern modules came the same way. A contributor designed the loader, built it, and proved it on hardware before proposing it. They're core to the instrument now.
-
-The clearest case started on the other side of the world. A media art collective took Patternflow's pattern modules, added MIDI control and a microphone, and built a browser-based instrument of their own: **[MOTIFLOW](https://azmano.art/portfolio-item/motiflow/)**, recently shown in a gallery. Patternflow had become something people build their own instruments out of.
+I started this alone. It hasn't worked that way for a while now. Web upload began as someone else's suggestion and proof of concept, and shipped as a main feature two days later. The `.pfm` pattern modules came the same way: a contributor designed the loader, built it and proved it on hardware before proposing it, and they're core to the instrument now. On the other side of the world, a media art collective took the pattern modules, added MIDI control and a microphone, and built a browser-based instrument of their own, **[MOTIFLOW](https://azmano.art/portfolio-item/motiflow/)**, recently shown in a gallery.
**Patternflow is a thing we're all making now.**
## Make it yours
-### 1. The device
+### The device
-The **[Full Build Guide](BUILD_GUIDE.md)** covers the official route: the custom PCB and a 3D-printed enclosure. The **[build guide in 3D](https://patternflow.work/guide/build)** is the same route, every step shown on a board you can turn. Don't want to order a board? The **[Breadboard Build Guide](https://patternflow.work/build/breadboard)** wires the same electronics with jumper wires instead, and that's a real Patternflow, not a temporary prototype. Every other combination is on the **[Assembly Map](docs/assembly/README.md)**. Parts run about US$100–200 ([BOM](BUILD_GUIDE.md#1-bill-of-materials-bom)), it's all big through-hole joints, and every first-timer who has built one finished it. Most came back saying the soldering was the fun part.
+The **[Build Guide](BUILD_GUIDE.md)** is the official route, a custom PCB in a 3D-printed case, and the **[build guide in 3D](https://patternflow.work/guide/build)** is the same route on a board you can turn. The **[breadboard build](https://patternflow.work/build/breadboard)** wires the same electronics with jumper wires, and that's a real Patternflow, not a temporary prototype; every other combination is on the **[Assembly Map](docs/assembly/README.md)**. Buying the LED panel? Read **[Panel Compatibility](docs/panel-compatibility.md)** first. Parts run about US$100–200 ([BOM](BUILD_GUIDE.md#1-bill-of-materials-bom)), the joints are all big through-hole ones, and every first-timer who has built one finished it. Most came back saying the soldering was the fun part.
-**When yours lights up, tell us.** Post it in [Discord](https://discord.gg/Vr9QtsxeTk) or fill in the [Share your build](../../issues/new?template=share_build.yml) form and it goes on the build map, a globe of Patternflows where each pin carries its build's story. The map is for the ones people made themselves: every pin is someone who built one from these files, in their own material, wherever they are. The goal is simple: cover it with pins.
+**When yours lights up, tell us** in [Discord](https://discord.gg/Vr9QtsxeTk) or through the [Share your build](../../issues/new?template=share_build.yml) form, and it goes on the build map: a globe of Patternflows people made themselves, from these files, in their own material, each pin carrying its build's story. The goal is simple: cover it with pins.
-If you'd rather have one arrive assembled, tested, and flashed, the **[Crowd Supply campaign](https://www.crowdsupply.com/engmung/patternflow)** is currently live. Back the campaign to have an assembled unit, DIY kit, or custom enclosure shipped worldwide.
+Rather have one arrive assembled, tested and flashed? The **[Crowd Supply campaign](https://www.crowdsupply.com/engmung/patternflow)** is for an assembled unit, a DIY kit or a custom enclosure, shipped worldwide.
-### 2. Patterns
+### Patterns
-You don't need hardware to start. The **[Live Editor](https://patternflow.work/pattern)** is a full Patternflow simulator: turn the virtual knobs and it behaves like the real thing. Patterns start as JavaScript in the browser, and you don't have to write it yourself. Click **Copy creation prompt**, paste it into your AI assistant with a description of the look you want, paste back the code it returns, and play. You don't write GLSL or touch a rendering pipeline: the template handles the encoder mapping, brightness curve and pixel buffer, and you describe the visuals.
+You don't need hardware to start. The **[Live Editor](https://patternflow.work/pattern)** is a full Patternflow simulator, virtual knobs and all. Patterns are JavaScript, and you don't have to write it yourself: click **Copy creation prompt**, paste it into your AI assistant with the look you want, paste back the code it returns, and play. The template handles the encoder mapping, brightness curve and pixel buffer; you describe the visuals.
-
-
-
+**[Pattern Lab](https://patternflow.work/pattern-lab)** is the full studio, and where a pattern reaches the hardware. Generate variations in batches (with your own Gemini key, or through any AI chat), shape color ramps, retune knob ranges, then send it to your device: it builds into a small `.pfm` module and installs over Wi-Fi in about ten seconds, with no cable, no reflash and no IDE. Graphic Export renders the same pattern as a print-size PNG or an MP4 loop, in your browser, with nothing uploaded.
-**[Pattern Lab](https://patternflow.work/pattern-lab)** is the full studio, and where a pattern reaches the hardware. Generate variations in batches (in the Lab with your own Gemini key, or through any AI chat), shape color ramps, retune knob ranges, then send it to your device: it builds into a small `.pfm` module and installs over Wi-Fi, about ten seconds start to finish. You never plug in a cable, reflash the board, or open an IDE. The Graphic Export panel takes the same pattern off the panel: a PNG at print size for a business card, or an MP4 loop for a post, rendered in your browser with nothing uploaded.
-
-When it looks right, publish it to the **[Community](https://community.patternflow.work/community)**. More than a hundred patterns are up already and the range keeps widening, from quiet waves to chaos-theory studies, every one written as code and every one playable under the same four knobs. Collect patterns into a deck and send it to your board in one click. Browsing needs no account, and publishing asks a username and password, no email. The **[Pattern Guide](PATTERN_GUIDE.md)** walks the whole loop, and the **[Make guide](https://patternflow.work/guide/make)** is that loop as a tutorial you do as you read.
+Then publish it to the **[Community](https://community.patternflow.work/community)**: more than a hundred patterns, from quiet waves to chaos-theory studies, all playable under the same four knobs. Collect them into a deck and send it to your board in one click. Browsing needs no account; publishing asks a username and password, no email. New studies go up on **[Instagram](https://www.instagram.com/patternflow.work)** almost daily; publish something that stands out and it can get featured, or ask for a collab and we'll post yours together.
-
+
+
-Patterns don't stay on the wall, either. New pattern studies go up on **[Instagram](https://www.instagram.com/patternflow.work)** almost daily. Publish something that stands out and it can get featured there, or ask for a collab on Instagram and we'll post yours together.
-
-### 3. The ecosystem
+### The ecosystem
-Patterns are the surface. Underneath is an instrument still being designed, in the open, by whoever shows up. This is the whole system on one napkin:
+Patterns are the surface. Underneath is an instrument still being designed, in the open, by whoever shows up. The whole system on one napkin:
-It's already moving in directions I didn't choose. One contributor is building out MQTT further than I've had time to follow: units reaching each other across a network, one person's playing coming out of somebody else's device. Another is working on sound on the board itself. Another is taking the TouchDesigner link further than I did. None of it was assigned.
-
-**[CONTRIBUTING.md](CONTRIBUTING.md)** covers how contributions flow. Questions and ideas go to the **[Discord](https://discord.gg/Vr9QtsxeTk)**; where to ask what is **[SUPPORT.md](SUPPORT.md)**.
+It's already moving in directions I didn't choose. One contributor is taking MQTT further than I've had time to follow: units reaching each other across a network, one person's playing coming out of somebody else's device. Another is working on sound on the board itself, another on the TouchDesigner link. None of it was assigned. Join in: [Contributing](#contributing).
## Quick facts
@@ -100,22 +144,21 @@ It's already moving in directions I didn't choose. One contributor is building o
| :--- | :--- |
| **Display** | 128 × 64 HUB75 RGB LED matrix, P2.5 (320 × 160 mm) |
| **Brain** | ESP32-S3-WROOM-1 **N16R8** (16 MB flash, 8 MB PSRAM) · standalone, no sending card |
-| **Input** | 4× EC11 rotary encoders with push-switch; long-press encoder 4 to switch patterns ([controls](#playing-it)) |
-| **Power** | 5 V over USB from any power bank; about **4 h per 10,000 mAh** at max brightness with a typical pattern (see [runtime](#power--runtime)) |
+| **Input** | 4× EC11 rotary encoders with push-switch; long-press K4 to switch patterns ([controls](#playing-it)) |
+| **Power** | 5 V from any USB power bank into **`J4`, the screw terminal**, the only power input (never USB-C) · about **4 h per 10,000 mAh** at max brightness ([runtime](#power--runtime)) |
| **Size / weight** | 245 × 325 × 36 mm (9.6 × 12.8 × 1.4 in) · 933 g (2.06 lb) |
| **Firmware** | Arduino-compatible C++, modular pattern architecture, runtime switching (no reflash) |
-| **Flashing** | Everything from the browser: one USB flash the first time, then it's all Wi-Fi. Patterns install as modules in seconds, full firmware builds land wirelessly too. A local build (PlatformIO) only for firmware development or other matrix resolutions |
-| **Connectivity** | Wi-Fi and USB. Network MIDI (a MIDI port in any DAW — Ableton, Logic, Bitwig), bidirectional OSC (Max/TouchDesigner/Resolume), MQTT and audio-react each ship in an [edition](docs/EDITIONS.md) you install in one click — the way patterns do — and switching keeps your patterns, networks and settings |
-| **Editions** | One image ships on the board; others are a click away on [the shelf](https://patternflow.work/editions); what each feature does, with a reel of it, is on [the features page](https://patternflow.work/features). Writing a feature or cutting your own firmware starts at **[docs/EDITIONS.md](docs/EDITIONS.md)** |
+| **Flashing** | From the browser: one USB flash of the DevKit, out of the board, the first time; after that patterns and firmware updates all arrive over Wi-Fi. A local build (PlatformIO) only for firmware development or other matrix resolutions |
+| **Connectivity** | Wi-Fi, an [HTTP API](docs/rest-api.md), and a network of its own when no known one is in reach · network MIDI, OSC, MQTT and audio-react in one-click [editions](#editions-midi-osc-mqtt--audio-react) |
| **Build** | ~1 h hands-on (≈30 min soldering + ≈30 min assembly, first-build friendly) + ~10 h 3D printing · US$100–200 in parts ([BOM](BUILD_GUIDE.md#1-bill-of-materials-bom)) |
| **License** | MIT (firmware & web code) · CC-BY-SA 4.0 (hardware, docs, bundled patterns) · community patterns are licensed by their authors ([summary](docs/LICENSE-SUMMARY.md)) |
### Power & runtime
-Patternflow runs off any standard USB power bank that can supply a couple of amps at 5 V. You don't need a wall adapter, so it stays portable. Rule of thumb: **about 4 hours per 10,000 mAh** at maximum brightness with a typical pattern.
+Patternflow runs off any standard USB power bank that can supply a couple of amps at 5 V, wired into `J4`. You don't need a wall adapter, so it stays portable. Rule of thumb: **about 4 hours per 10,000 mAh** at maximum brightness with a typical pattern.
-Measured runtimes
+Measured runtimes, sleep mode, and full-white patterns
In testing, a 40,000 mAh bank dropped about 13% over 2 hours at **full brightness**, which is roughly **15 hours on a full charge**:
@@ -125,84 +168,126 @@ In testing, a 40,000 mAh bank dropped about 13% over 2 hours at **full brightnes
| 20,000 mAh | ~8 hours |
| 40,000 mAh | ~15 hours |
-These were measured with a bright generative pattern at maximum brightness, which is how the device is normally used. Lowering the brightness (long-press encoder 1) extends runtime well beyond these figures.
+These were measured with a bright generative pattern at maximum brightness, which is how the device is normally used. Lowering the brightness (long-press K1) extends runtime well beyond these figures.
-**Sleep mode** takes it further: the panel goes dark, the HUB75 transfer stops and the board idles, while staying on Wi-Fi so it can be woken again from MQTT or a browser. Hold encoder 2 for the NETWORK screen, then turn encoder 1; any knob or button wakes it. The pattern that was running comes back, as it does after a full power cut.
+**Sleep mode** takes it further: the panel goes dark, the HUB75 transfer stops and the board idles, while staying on Wi-Fi so it can be woken again from MQTT or a browser. Hold K2 for the NETWORK screen, then turn K1; any knob or button wakes it. The pattern that was running comes back, as it does after a full power cut.
-**A pattern that fills the screen with white is a different story.** The same 40,000 mAh bank dropped 9% in 30 minutes: about 26.6 W, or roughly 4.8 A at 5 V, which is above what a typical USB-A port is rated for and enough to make the connector warm. That works out to ~5.5 hours on a 40,000 mAh bank. A total-pixel-power clamp is going into the firmware so this can't happen; until then, avoid near-full-white patterns on a power bank, or use a supply and cable rated for the current.
+**A pattern that fills the screen with white is a different story.** The same 40,000 mAh bank dropped 9% in 30 minutes: about 26.6 W, or roughly 4.8 A at 5 V, which is above what a typical USB-A port is rated for and enough to make the connector warm (~5.5 hours on that bank). The firmware's total power clamp, on by default, keeps that from happening: it holds the panel to a 2.4 A budget, the high-power USB-A rating, so a white-out frame lands at roughly half brightness instead, while an ordinary bright pattern (~1.9 A) never notices. The budget is `power_budget` on the [`/api/display`](docs/rest-api.md#get-apidisplay) endpoint.
## Playing it
-Every encoder does two jobs. Turn it, or give it a short press, and you are moving the pattern that's running. Hold it down and a device function opens instead, and a second long-press closes it again.
+Every knob does two jobs. Turn it, or give it a short press, and you move the pattern that's running; what each knob does there is the pattern's own decision, so the same knob feels different on every one. Hold it down and a device function opens instead, and a second long-press closes it.
-
+
-What a knob does to a running pattern is the pattern's own decision, so the same knob feels different on every one of them. That is what encoder 3 is for: hold it and each knob puts its own number on the panel, which saves counting around the front.
-
-The device functions themselves are fixed. Encoder 1 is brightness. Encoder 4 opens the pattern list, where you turn to browse the names and long-press again to load one, choosing from whatever is installed at the time. Encoder 2 puts the board's IP address on the panel, and typing that into a browser opens the web console, which carries every feature the device has and works from a phone.
-
-[patternflow.work/guide/play](https://patternflow.work/guide/play) has all of this on a Patternflow you can turn, press and hold, from flashing the ESP32 to the console. The same guide builds one with you ([Build](https://patternflow.work/guide/build)) and takes you through the community and Pattern Lab ([Make](https://patternflow.work/guide/make)); [patternflow.work/guide](https://patternflow.work/guide) is where to start.
+| Hold | Where | Opens |
+| :--- | :--- | :--- |
+| **K1** | top right | **Brightness** |
+| **K2** | top left | **Network**: the board's IP address (with no Wi-Fi, the name of its own network). Type the IP into a browser for the web console: every feature the device has, from a phone too |
+| **K3** | bottom right | **Knob numbers**, each knob's own number on the panel |
+| **K4** | bottom left | **Patterns**: turn to browse what's installed, long-press again to load one |
-## On the device
+[patternflow.work/guide/play](https://patternflow.work/guide/play) has all of this on a Patternflow you can turn, press and hold, from flashing the ESP32 to the console.
-A new board boots into **Origin**, concentric sine waves sampled by an emergent grid, and long-pressing encoder 4 opens the list of whatever else is installed. Patterns live on the filesystem rather than inside the program. The firmware compiles in Origin alone, as the failsafe a board can always boot into, and everything else installs as **`.pfm` modules over Wi-Fi**, up to 128 of them, no reflash. Adding a pattern never costs a firmware update, and a firmware update never touches your patterns, your Wi-Fi, or your storage.
+### What's on the device
-A new board is therefore nearly empty, so a set ships with it: the **Basics pack**, 33 patterns, at the top of the [decks shelf](https://community.patternflow.work/community/decks). One click installs the lot, no account and no build queue, or drop the `.zip` on your board's Patterns page yourself. The [Live Editor](https://patternflow.work/pattern) opens with its own preset library of 42 patterns, each loadable and remixable in the browser.
+A new board boots into **Origin**, concentric sine waves sampled by an emergent grid, the one pattern compiled into the firmware as the failsafe a board can always boot into. Everything else lives on the filesystem and installs as **`.pfm` modules over Wi-Fi**, up to 128 of them, no reflash: adding a pattern never costs a firmware update, and a firmware update never touches your patterns, your Wi-Fi, or your storage. The **Basics pack**, 33 patterns at the top of the [decks shelf](https://community.patternflow.work/community/decks), installs in one click with no account, or drop its `.zip` on your board's Patterns page yourself. The [Live Editor](https://patternflow.work/pattern) opens with its own library of over forty, each loadable and remixable in the browser.
-A local firmware build (PlatformIO, driven by `firmware/bundles/build.sh`) is only needed for feature development or targeting an LED matrix with a different resolution; the setup is [`firmware/README.md`](firmware/README.md) and a new feature starts at [FEATURE_GUIDE.md](FEATURE_GUIDE.md). Patterns never need it: [Custom Patterns](firmware/CUSTOM_PATTERNS.md). To rebuild the shipped pack from the repo's own preset sources, see [`firmware/toolchain/make_pack.py`](firmware/toolchain/make_pack.py).
+A local firmware build (PlatformIO, driven by `firmware/bundles/build.sh`) is only for feature development or an LED matrix with a different resolution: the setup is [`firmware/README.md`](firmware/README.md), a new feature starts at [FEATURE_GUIDE.md](FEATURE_GUIDE.md), and patterns never need it ([Custom Patterns](firmware/CUSTOM_PATTERNS.md)). The shipped pack rebuilds from the repo's own preset sources with [`firmware/toolchain/make_pack.py`](firmware/toolchain/make_pack.py).
-## MIDI, OSC, MQTT & audio-react
+## Editions: MIDI, OSC, MQTT & audio-react
-Each of these lives in an **edition** — a firmware you install from [the shelf](https://patternflow.work/editions) in one click, the same way patterns arrive, and switching keeps your patterns, Wi-Fi networks and settings. MIDI, OSC and audio-react are in **Audio**; MQTT is in **Performance**, alongside sequences and the Director. Why it is arranged that way, and how to build your own: [`docs/EDITIONS.md`](docs/EDITIONS.md).
+The default firmware is the instrument and nothing else, which leaves the most memory for your patterns. MIDI, OSC, MQTT and audio-react ship in **editions** on **[the shelf](https://patternflow.work/editions)**, installed in one click the way patterns arrive; switching, and switching back, keeps your patterns, Wi-Fi networks and settings. Each feature with a reel of it: [the features page](https://patternflow.work/features). Why it's arranged this way, and how to build your own: [`docs/EDITIONS.md`](docs/EDITIONS.md).
-**MIDI, over the network.** *New.* The panel is a MIDI port: RTP-MIDI over Wi-Fi, native on macOS and iOS, one free driver on Windows. Its four knobs and buttons map to anything in Ableton with Ctrl-M, and a DAW drives the panel back with plain CC and Program Change — the Pattern Lab's Director exports a show as exactly those four CC lanes, so a show can play on the panel from Live's transport. Sensitivity per knob, an absolute or relative mode, and the session that reconnects itself all live on the panel's console. Step by step with screenshots: [`docs/midi-ableton.md`](docs/midi-ableton.md); the contract: [`docs/midi-spec.md`](docs/midi-spec.md).
+| | What it does | Edition | Read |
+| :--- | :--- | :--- | :--- |
+| **MIDI over Wi-Fi** | The panel is a MIDI port in any DAW; knobs and buttons map with Ctrl-M, and CC and Program Change drive it back | Audio | [MIDI in Ableton](docs/midi-ableton.md) · [spec](docs/midi-spec.md) |
+| **OSC, both ways** | Knobs, presses and pattern switches stream out; incoming OSC moves the panel exactly like a hand on the encoder | Audio | [spec](docs/osc-spec.md) · [Max for Live bridge](integrations/ableton) |
+| **Audio-react** | A browser tab's audio in four bands you shape, or a microphone on the board (five leads to the DevKit); every pattern reacts with no audio code of its own | Audio | [AUDIO_GUIDE.md](AUDIO_GUIDE.md) · [extension](tools/patternflow-audio-extension) |
+| **MQTT** | Both ways, on the broker you already run; two boards on one broker follow each other | Performance | [spec](docs/mqtt-spec.md) |
+| **HTTP** | Knob state, pattern selection, brightness, status | every image | [API](docs/rest-api.md) |
+
+Performance, maintained by **[Simone Majocchi](https://github.com/SimonePDA)**, also carries sequences (cue lists, timelines, the night/wake scheduler), FlowLocal and the Director, the weather mapped onto the knobs, and the clock.
+
+
+More on MIDI, OSC, MQTT and audio-react
-**Bidirectional OSC.** Over Wi-Fi, Patternflow speaks OSC in both directions: knob turns, button presses, and pattern switches stream out to a remote host (Ableton Live, Max/MSP, TouchDesigner, anything that speaks OSC), and incoming OSC messages drive the device exactly like physical encoder motion. Play Patternflow as a controller for your set, let your set drive the light, or both at once. OSC is the language of Max, TouchDesigner, Resolume and Processing, and it carries more than MIDI can — pattern names, floats, a heartbeat. For Ableton Live Suite there's a ready-made Max for Live bridge in [`integrations/ableton`](integrations/ableton). Click Connect, map the four knobs to any Live parameters, done. The wire protocol is documented in [`docs/osc-spec.md`](docs/osc-spec.md).
+**MIDI, over the network.** The panel is a MIDI port: RTP-MIDI over Wi-Fi, native on macOS and iOS, one free driver on Windows. Its four knobs and buttons map to anything in Ableton with Ctrl-M, and a DAW drives the panel back with plain CC and Program Change. The Pattern Lab's Director exports a show as exactly those four CC lanes ([Director → MIDI](docs/director-midi.md)), so a show can play on the panel from Live's transport. Sensitivity per knob, an absolute or relative mode, and the session that reconnects itself all live on the panel's console.
-**MQTT.** Patternflow also speaks MQTT, both ways, on the broker you already run ([the contract](docs/mqtt-spec.md)). Knob turns and pattern changes publish as they happen; messages coming the other way move the knobs and switch patterns exactly as a hand on the encoder would. Two boards pointed at the same broker follow each other, which is the short version of why it exists. It also puts the device on the same bus as the rest of a home or venue setup, so Home Assistant, Node-RED or a lighting desk can drive it without anything Patternflow-specific in the middle. Point it at a broker on the device's own **MQTT** page; a pattern can be addressed by name or by slug. Publishing `1` to `/sleep` puts the panel to sleep and `0` wakes it, with the current state mirrored on `/sleep/state`. That is enough for a Home Assistant switch, and it is the one command a panel obeys whether it is set to Publisher or Subscriber. Contributed by **[@SimonePDA](https://github.com/SimonePDA)** (Simone Majocchi), along with the browser-side zip unpacking that makes pattern packs install in one click.
+**Bidirectional OSC.** Knob turns, button presses and pattern switches stream out to a remote host (Ableton Live, Max/MSP, TouchDesigner, anything that speaks OSC), and incoming OSC messages drive the device exactly like physical encoder motion. Play Patternflow as a controller for your set, let your set drive the light, or both at once. OSC is the language of Max, TouchDesigner, Resolume and Processing, and it carries more than MIDI can: pattern names, floats, a heartbeat. For Ableton Live Suite there's a ready-made Max for Live bridge in [`integrations/ableton`](integrations/ableton): click Connect, map the four knobs to any Live parameters, done.
+
+**MQTT.** Patternflow speaks MQTT both ways, on the broker you already run. Knob turns and pattern changes publish as they happen; messages coming the other way move the knobs and switch patterns exactly as a hand on the encoder would. Two boards pointed at the same broker follow each other, which is the short version of why it exists. It also puts the device on the same bus as the rest of a home or venue setup, so Home Assistant, Node-RED or a lighting desk can drive it without anything Patternflow-specific in the middle. Point it at a broker on the device's own **MQTT** page; a pattern can be addressed by name or by slug. Publishing `1` to `/sleep` puts the panel to sleep and `0` wakes it, with the current state mirrored on `/sleep/state`: enough for a Home Assistant switch, and the one command a panel obeys whether it is set to Publisher or Subscriber. Contributed by **[@SimonePDA](https://github.com/SimonePDA)** (Simone Majocchi), along with the browser-side zip unpacking that makes pattern packs install in one click.
**Audio-react.** The Chrome/Edge extension in [`tools/patternflow-audio-extension`](tools/patternflow-audio-extension) captures the current tab's audio, splits it into four bands you shape on a response graph, and sends the levels to the panel over a WebSocket. A pattern reads them as ordinary parameters, so every encoder-driven pattern reacts with no audio code of its own. The panel can also listen for itself: an on-board microphone runs the same analysis on the device, with no browser involved.
+
+
## How it's built
-Patternflow is built around a standalone ESP32-S3 driving a HUB75 RGB LED matrix at low resolution, where each pixel reads as a discrete point of light with its own brightness and color. Four rotary encoders feed firmware written in Arduino-compatible C++ around a modular pattern architecture: each pattern is a self-contained module with its own setup, update, and draw routines, while the shared framework handles input, LED rendering, mode transitions, and color calibration. I designed the PCB myself; the enclosure is 3D-printed by default. Two community remixes sit beside it in [`hardware/case/remixes/`](hardware/case/remixes/README.md), another printed case and a laser-cut one in clear acrylic or MDF, and a stainless steel version is still in progress.
+Patternflow is built around a standalone ESP32-S3 driving a HUB75 RGB LED matrix at low resolution, where each pixel reads as a discrete point of light with its own brightness and color. Four rotary encoders feed firmware written in Arduino-compatible C++ around a modular pattern architecture: each pattern is a self-contained module with its own setup, update, and draw routines, while the shared framework handles input, LED rendering, mode transitions, and color calibration. I designed the PCB myself; the enclosure is 3D-printed by default. Community remixes sit beside it in [`hardware/case/remixes/`](hardware/case/remixes/README.md): two more printed cases, one of them holding the panel landscape on two desk stands, and a laser-cut one in clear acrylic or MDF. A stainless steel version is still in progress.
## Repository & documentation
-> **Moving fast.** [v3.11.0 is out](https://github.com/engmung/Patternflow/releases/tag/v3.11.0): installed patterns run faster - the Basics pack is rebuilt and every one of its 33 patterns gains, 1.5 times the frames a second at the median, once the firmware is updated and the pack installed again - and a pattern you go back to resumes at once instead of loading again. The console is light and reads as one thing, and the Audio edition's page and browser extension are rebuilt: the page says who has the knobs and saves only what you changed, and the extension holds the knobs through silence. There is an [Audio guide](https://patternflow.work/guide/audio) beside Build, Play and Make. Audio is re-cut on this core as v0.7.0; Performance stays at v0.4.0, on core 3.10.4, until its maintainer re-cuts it. [v3.10.5](https://github.com/engmung/Patternflow/releases/tag/v3.10.5) made every pattern 2.45 ms a frame faster and moved a pattern's code to PSRAM, so patterns that were refused for size load and the console has more room while one is running; the panel raises its own Wi-Fi hotspot when it knows no network in reach, a panel that crashed says which pattern it was in and where, a pattern that hangs no longer takes the console down with it, and five requests that could reboot the board cannot any more. [v3.10.4](https://github.com/engmung/Patternflow/releases/tag/v3.10.4) made the panel film properly on a phone. It refreshes at exactly 300 Hz — the one rate the shutter speeds a phone actually uses (1/50, 1/60, 1/100, 1/30) all divide — so the rolling bands are gone at those; 1/120 and slow motion still band, and [the firmware README](firmware/README.md#refresh-rate-anti-flicker-for-video) says why and what to set. [v3.10.3](https://github.com/engmung/Patternflow/releases/tag/v3.10.3) made the panel's colours come out right: bright edges had been growing purple and sky-blue fringes and greys read pink; the cause was in the display driver — which bit plane each brightness window actually lights — and part of it was 3.10.1's own dark-end fix. The planes are exactly binary now, and because the fix is in the driver every installed pattern gets it without a rebuild. It also brings a [Knobs page](docs/rest-api.md#get-apiknobs-and-post-apiknobs) on the device (which way each encoder counts, set from the browser) and the [features catalogue](https://patternflow.work/features). Both editions were re-cut on that core. [v3.10.2](https://github.com/engmung/Patternflow/releases/tag/v3.10.2) made a Format that did not take stop reporting success, and a storage that will not mount say why. [v3.10.1](https://github.com/engmung/Patternflow/releases/tag/v3.10.1) fixed an upload that could reboot the board, a failed settings write that could make the next boot forget a good pattern, a mic-less Audio panel holding the knobs, and MIDI notes that would not let go. [v3.10.0](https://github.com/engmung/Patternflow/releases/tag/v3.10.0) made a heavy pattern come on every time. [v3.9.5](https://github.com/engmung/Patternflow/releases/tag/v3.9.5) taught the web console to survive a slow Wi-Fi link and a heavy pattern, and put thumbnails under the knob. [v3.8.0](https://github.com/engmung/Patternflow/releases/tag/v3.8.0) split the firmware into [editions](docs/EDITIONS.md) — core, Audio (on-board microphone, OSC, network MIDI) and Performance (sequences, MQTT, weather) — installable in one click and switchable without losing your patterns. [v3.2.0](https://github.com/engmung/Patternflow/releases/tag/v3.2.0) made patterns install over Wi-Fi as `.pfm` modules, no reflash. On v2.x hardware? Everything you need stays bundled at [v2.1.0](https://github.com/engmung/Patternflow/releases/tag/v2.1.0). Follow the [changelog](CHANGELOG.md) and the [journal](https://patternflow.work/journal) for what's current.
+Folder by folder, and what you want to do → where to start: **[docs/REPOSITORY.md](docs/REPOSITORY.md)**. The docs are indexed in [`docs/README.md`](docs/README.md).
+
+
+The top-level folders
| Folder | Contents |
| :--- | :--- |
| `firmware/` | Arduino code for ESP32-S3, the custom pattern template, and the toolchain that builds patterns into `.pfm` modules and packs |
| `hardware/` | Enclosure files and electronics source files (case, PCB, Gerbers, schematic PDF) |
| `web/` | Next.js site (landing, the interactive guide, Live Editor, Pattern Lab, community, browser flasher & build server, journal); the breadboard build guide is a React page here |
-| `docs/` | The contracts (HTTP, OSC, MIDI, MQTT, audio WebSocket, show files), how the firmware is put together (`EDITIONS.md`), the assembly map, walk-throughs, records and media — indexed in [`docs/README.md`](docs/README.md), with the folder-by-folder map in [`docs/REPOSITORY.md`](docs/REPOSITORY.md) |
+| `docs/` | The contracts (HTTP, OSC, MIDI, MQTT, audio WebSocket, show files), how the firmware is put together (`EDITIONS.md`), the assembly map, walk-throughs, records and media |
| `tools/` | Desktop-side helpers, including the audio-react browser extension |
| `integrations/` | Host-software bridges: Ableton Live / Max for Live (OSC knob mapping) |
| `.github/` | Issue and PR templates, and the CI that runs on every pull request (web, every firmware edition, doc links, console pages) |
-**Guide (interactive):** [Start here](https://patternflow.work/guide) · [Build it](https://patternflow.work/guide/build) · [Play it](https://patternflow.work/guide/play) · [Make patterns](https://patternflow.work/guide/make) · [Sound and MIDI](https://patternflow.work/guide/audio): each step shown on a 3D Patternflow you can turn, in English and Korean
-**Build:** [Full Build Guide](BUILD_GUIDE.md) · [Assembly Map](docs/assembly/README.md) · [Panel Compatibility](docs/panel-compatibility.md) · [Hardware files](hardware/README.md)
-**Play:** [Pattern Guide](PATTERN_GUIDE.md) · [Custom Patterns](firmware/CUSTOM_PATTERNS.md) · [Audio Guide](AUDIO_GUIDE.md) · [MIDI in Ableton](docs/midi-ableton.md)
-**Extend:** [Feature Guide](FEATURE_GUIDE.md) · [Editions](docs/EDITIONS.md) · [Firmware build](firmware/README.md) · [Web architecture](web/ARCHITECTURE.md) · [HTTP API](docs/rest-api.md) · [OSC Spec](docs/osc-spec.md) · [MIDI Spec](docs/midi-spec.md) · [Director → MIDI](docs/director-midi.md)
-**Project:** [Contributing](CONTRIBUTING.md) · [Support](SUPPORT.md) · [Repository map](docs/REPOSITORY.md) · [Changelog](CHANGELOG.md) · [License Summary](docs/LICENSE-SUMMARY.md) · [Manifesto](docs/manifesto.md)
+
+**Guide (interactive):** [Start here](https://patternflow.work/guide) · [Build it](https://patternflow.work/guide/build) · [Play it](https://patternflow.work/guide/play) · [Make patterns](https://patternflow.work/guide/make) · [Sound and MIDI](https://patternflow.work/guide/audio), in English and Korean
+**Build:** [Build Guide](BUILD_GUIDE.md) · [Assembly Map](docs/assembly/README.md) · [Panel Compatibility](docs/panel-compatibility.md) · [Hardware files](hardware/README.md) · [v2.x board](BUILD_GUIDE_v2.md)
+**Play:** [Pattern Guide](PATTERN_GUIDE.md) · [Custom Patterns](firmware/CUSTOM_PATTERNS.md) · [Audio Guide](AUDIO_GUIDE.md) · [MIDI in Ableton](docs/midi-ableton.md)
+**Extend:** [Feature Guide](FEATURE_GUIDE.md) · [Editions](docs/EDITIONS.md) · [Firmware build](firmware/README.md) · [Web architecture](web/ARCHITECTURE.md) · [HTTP API](docs/rest-api.md) · [OSC Spec](docs/osc-spec.md) · [MIDI Spec](docs/midi-spec.md) · [MQTT Spec](docs/mqtt-spec.md) · [Director → MIDI](docs/director-midi.md)
+**Project:** [Contributing](CONTRIBUTING.md) · [Support](SUPPORT.md) · [Repository map](docs/REPOSITORY.md) · [Changelog](CHANGELOG.md) · [License Summary](docs/LICENSE-SUMMARY.md) · [Manifesto](docs/manifesto.md)
**Links:** [patternflow.work](https://patternflow.work) · [Community](https://community.patternflow.work/community) · [Crowd Supply](https://www.crowdsupply.com/engmung/patternflow) · [Releases](../../releases) · [Discord](https://discord.gg/Vr9QtsxeTk) · [Instagram](https://www.instagram.com/patternflow.work)
+**Moving fast.** [v3.11.0 is out](https://github.com/engmung/Patternflow/releases/tag/v3.11.0): installed patterns run faster, a pattern you go back to resumes at once, and there is an [Audio guide](https://patternflow.work/guide/audio) beside Build, Play and Make. Follow the [changelog](CHANGELOG.md) and the [journal](https://patternflow.work/journal) for what's current.
+
+
+Recent releases in brief
+
+- **[v3.11.0](https://github.com/engmung/Patternflow/releases/tag/v3.11.0)**: installed patterns run faster. The Basics pack is rebuilt and every one of its 33 patterns gains, 1.5 times the frames a second at the median, once the firmware is updated and the pack installed again; a pattern you go back to resumes at once instead of loading again. The console is light and reads as one thing, and the Audio edition's page and browser extension are rebuilt: the page says who has the knobs and saves only what you changed, and the extension holds the knobs through silence. Audio is re-cut on this core as v0.7.0; Performance stays at v0.4.0, on core 3.10.4, until its maintainer re-cuts it.
+- **[v3.10.5](https://github.com/engmung/Patternflow/releases/tag/v3.10.5)**: every pattern 2.45 ms a frame faster, and a pattern's code moved to PSRAM, so patterns that were refused for size load and the console has more room while one is running. The panel raises its own Wi-Fi hotspot when it knows no network in reach, a panel that crashed says which pattern it was in and where, a pattern that hangs no longer takes the console down with it, and five requests that could reboot the board cannot any more.
+- **[v3.10.4](https://github.com/engmung/Patternflow/releases/tag/v3.10.4)**: the panel films properly on a phone. It refreshes at exactly 300 Hz, the one rate the shutter speeds a phone actually uses (1/50, 1/60, 1/100, 1/30) all divide, so the rolling bands are gone at those; 1/120 and slow motion still band, and [the firmware README](firmware/README.md#refresh-rate-anti-flicker-for-video) says why and what to set.
+- **[v3.10.3](https://github.com/engmung/Patternflow/releases/tag/v3.10.3)**: the panel's colours come out right. Bright edges had been growing purple and sky-blue fringes and greys read pink; the cause was in the display driver (which bit plane each brightness window actually lights), and part of it was 3.10.1's own dark-end fix. The planes are exactly binary now, and because the fix is in the driver every installed pattern gets it without a rebuild. Also a [Knobs page](docs/rest-api.md#get-apiknobs-and-post-apiknobs) on the device (which way each encoder counts, set from the browser) and the [features catalogue](https://patternflow.work/features). Both editions were re-cut on that core.
+- **[v3.10.2](https://github.com/engmung/Patternflow/releases/tag/v3.10.2)**: a Format that did not take stops reporting success, and a storage that will not mount says why.
+- **[v3.10.1](https://github.com/engmung/Patternflow/releases/tag/v3.10.1)**: fixed an upload that could reboot the board, a failed settings write that could make the next boot forget a good pattern, a mic-less Audio panel holding the knobs, and MIDI notes that would not let go.
+- **[v3.10.0](https://github.com/engmung/Patternflow/releases/tag/v3.10.0)**: a heavy pattern comes on every time.
+- **[v3.9.5](https://github.com/engmung/Patternflow/releases/tag/v3.9.5)**: the web console survives a slow Wi-Fi link and a heavy pattern, and thumbnails sit under the knob.
+- **[v3.8.0](https://github.com/engmung/Patternflow/releases/tag/v3.8.0)**: the firmware split into [editions](docs/EDITIONS.md), core, Audio (on-board microphone, OSC, network MIDI) and Performance (sequences, MQTT, weather), installable in one click and switchable without losing your patterns.
+- **[v3.2.0](https://github.com/engmung/Patternflow/releases/tag/v3.2.0)**: patterns install over Wi-Fi as `.pfm` modules, no reflash.
+- **On v2.x hardware?** Everything you need stays bundled at [v2.1.0](https://github.com/engmung/Patternflow/releases/tag/v2.1.0), with [BUILD_GUIDE_v2.md](BUILD_GUIDE_v2.md).
+
+
+
## Contributing
-Builds, documentation fixes, part sourcing tips, enclosure remixes, firmware features and host-software bridges are all welcome. **[CONTRIBUTING.md](CONTRIBUTING.md)** has one table of where each kind goes and what CI runs on it; **[SUPPORT.md](SUPPORT.md)** says where to ask. Patterns don't come here: publish them from the Pattern Lab to the Community.
+Builds, documentation fixes, part sourcing tips, enclosure remixes, firmware features and host-software bridges are all welcome. **[CONTRIBUTING.md](CONTRIBUTING.md)** has one table of where each kind goes and what CI runs on it. Questions and ideas go to the **[Discord](https://discord.gg/Vr9QtsxeTk)**, and **[SUPPORT.md](SUPPORT.md)** says where to ask what. Patterns don't come here: publish them from the Pattern Lab to the Community.
## Story so far
+
+From a visit to the Nam June Paik Art Center in January 2026 to a worldwide Crowd Supply campaign, month by month
+
| When | Milestone |
| :--- | :--- |
| **Jan 2026** | *Patternflow: Origin*, the first work as a new media artist, built around 3D-printed forms and the seed of what became Patternflow · visited the Nam June Paik Art Center |
@@ -216,6 +301,8 @@ Builds, documentation fixes, part sourcing tips, enclosure remixes, firmware fea
| **Next** | Deliver rewards to backers at the highest quality · send Patternflow further out into the world · collaborate with more artists · earn academic recognition |
| **2028** | Grow Patternflow into a self-sustaining community and ecosystem, then move on to the next project |
+
+
Longer write-ups and the full story behind each step live on the **[journal](https://patternflow.work/journal)**: the whole process, written up at least weekly since the beginning, the thinking and the emotions included, and the parts that went badly too. If you want to know why this project exists and what it costs to keep alive, start there.
### Sponsor
@@ -230,7 +317,7 @@ Patternflow's PCB fabrication and 3D-printed enclosure are sponsored by **[PCBWa
## License
-A file's SPDX header is the authority where one exists; folders are not license boundaries, and files that cannot carry a header (STL, Gerber, images) take the license the nearest README states. Full breakdown in the **[License Summary](docs/LICENSE-SUMMARY.md)**.
+Two licenses, in two separate files. A file's SPDX header is the authority where one exists; folders are not license boundaries, and files that cannot carry a header (STL, Gerber, images) take the license the nearest README states. Full breakdown in the **[License Summary](docs/LICENSE-SUMMARY.md)**.
- Firmware & web code: **MIT** ([LICENSE-MIT](./LICENSE-MIT))
- Hardware, designs & docs: **CC-BY-SA 4.0** ([LICENSE-CC-BY-SA](./LICENSE-CC-BY-SA))
diff --git a/SECURITY.md b/SECURITY.md
index 98915fe8..1193d2bb 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -7,13 +7,13 @@ Please report privately through [GitHub's report form](https://github.com/engmun
## What is in scope
- **The community site** ([community.patternflow.work](https://community.patternflow.work)): accounts and sessions, uploads, the sandboxed iframe that runs published patterns, moderation.
-- **The browser flasher and update handoff** ([patternflow.work/flash](https://patternflow.work/flash), `/update`): the firmware images it serves and where they come from.
+- **The browser flasher and update handoff** (the **Flash Patternflow** button on [patternflow.work/pattern](https://patternflow.work/pattern) and in the Play guide, which installs the images under `/flash/`; and [`/update`](https://patternflow.work/update)): the firmware images they serve and where they come from.
- **The build worker** that turns a pattern into a `.pfm` module on the community host.
- **Published firmware images.** They must not carry credentials. Three early releases did carry the maintainer's Wi-Fi credentials, which is why the release workflow now refuses an image built with `patternflow_secrets.h`. If you find one that slipped through, that is a report.
## What is a documented trust model, not a vulnerability
-The device's HTTP server on the LAN has **no authentication**: anyone on the same network can call any `/api/*` route, including `POST /update`. This is deliberate and documented in [docs/rest-api.md](docs/rest-api.md#transport) — the panel is an instrument on a home or venue network, with the same posture as ArduinoOTA's default. `PF_WEBUPDATE_ALWAYS_ARMED 0` is the one lever that narrows it. Reports that amount to "the LAN API is open" will be closed with a pointer here; reports of a way to reach that API from *outside* the LAN, or from a web page on another origin, are in scope.
+The device's HTTP server on the LAN has **no authentication**: anyone on the same network can call any `/api/*` route, including `POST /update`. The same holds on the panel's own hotspot (`patternflow-xxxx`, raised when it finds no network it knows), whose password is the documented default `patternflow` until it is changed on the Wi-Fi page. This is deliberate and documented in [docs/rest-api.md](docs/rest-api.md#transport) — the panel is an instrument on a home or venue network, with the same posture as ArduinoOTA's default. `PF_WEBUPDATE_ALWAYS_ARMED 0` is the one lever that narrows `/update`; the hotspot takes its own password, or is switched off, on the Wi-Fi page. Reports that amount to "the LAN API is open" will be closed with a pointer here; reports of a way to reach that API from *outside* the LAN, or from a web page on another origin, are in scope.
## Supported versions
diff --git a/SUPPORT.md b/SUPPORT.md
index b5f6e112..a06b26f7 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -6,6 +6,7 @@ One table for "where do I ask". Issues are for things that change this repositor
| :--- | :--- |
| Learn to build, play, make patterns for one or give it sound and MIDI | [The guide](https://patternflow.work/guide) — each step on a 3D Patternflow in the browser. A step that didn't get you through has a **Stuck here?** link, which opens an issue with that step filled in |
| Get unstuck building, soldering or flashing | [Discord → hardware-help](https://discord.com/channels/1497757947827327067/1499907910707187833) — photos and quick back-and-forth |
+| Flash the DevKit for the first time, or get the panel onto Wi-Fi | [The Play guide's Flash chapter](https://patternflow.work/guide/play#flash), or [`BUILD_GUIDE.md` §8](BUILD_GUIDE.md#8-firmware) in writing. The short version is below the table |
| Show a pattern I made, or get feedback on one | Publish it from the [Pattern Lab](https://patternflow.work/pattern-lab) to the [Community](https://community.patternflow.work/community), then [Discord → patterns](https://discord.com/channels/1497757947827327067/1499908302962819236) |
| Put my finished build on the [build map](https://patternflow.work/inside) | [Share your build](https://github.com/engmung/Patternflow/issues/new?template=share_build.yml) — or post it in Discord |
| Talk about where the project should go, or find people working on the same direction | [Discord](https://discord.gg/Vr9QtsxeTk) — the dev-log and the project channels |
@@ -13,6 +14,15 @@ One table for "where do I ask". Issues are for things that change this repositor
| Report a security problem | [SECURITY.md](SECURITY.md) — privately, not as an issue |
| Contribute code, hardware files or docs | [CONTRIBUTING.md](CONTRIBUTING.md) |
+**The first flash, in short.**
+
+1. The DevKit out of the board, on its **left** USB-C port (labelled `USB`), with a cable that carries data — a charge-only cable is the most common reason nothing shows up — in desktop Chrome or Edge. That port is for flashing and data only; the panel is always powered through `J4`, the screw terminal.
+2. **Download mode, every first flash:** hold **BOOT**, tap **RST**, let go of BOOT. Then press **Flash Patternflow** (on the [Pattern tab](https://patternflow.work/pattern) under *Got the hardware?*, or in the Play guide) and pick the port (`USB JTAG/serial debug unit`).
+3. **The Wi-Fi step usually does not appear** after *Installation complete!* That is expected: press **RST** once, press **Flash Patternflow** again, pick the port, choose **Connect to Wi-Fi**, and after about ten seconds of scanning type your network's name exactly (capitals count; 2.4 GHz only) and its password.
+4. **If that will not take,** put the DevKit back in the board and power it: about 15 seconds after it finds no network it knows, the panel raises its own, `patternflow-xxxx` (password `patternflow`; hold **K2** to see the exact name). Not in your phone's list yet? Wait, and refresh the list. Join it (the phone will say the network has no internet, which is true; stay on it), open `http://192.168.4.1/`, and pick your network on the Wi-Fi page.
+
+A board that already runs Patternflow is updated over Wi-Fi from its console's Update page instead — download mode erases the patterns and saved networks.
+
**If Discord is blocked where you live**, the community site still works for patterns and decks without it, and a bug or a documentation issue here reaches the maintainer the same day. A discussion venue that works everywhere is planned; until it exists, an issue is the fallback for a question too.
There is no support email. Replies come from the maintainer and from other builders, usually within a day or two.
diff --git a/docs/EDITIONS.md b/docs/EDITIONS.md
index fab3a183..78774c23 100644
--- a/docs/EDITIONS.md
+++ b/docs/EDITIONS.md
@@ -14,9 +14,10 @@ four knobs.** That sentence decides everything below it, and it is the only
test worth applying.
What that sentence names is not a feature and never composes out: the pattern
-loader, the four encoders, the panel driver, Wi-Fi, `/update`, sleep, the
-console. Those are the device. **The default build carries no features at
-all**, and a panel running it does the whole of what Patternflow is for.
+loader, the four encoders, the panel driver, Wi-Fi (its own hotspot
+included), `/update`, sleep, the console. Those are the device. **The default
+build carries no features at all**, and a panel running it does the whole of
+what Patternflow is for.
Everything else — sequences, MQTT, OSC, weather, audio — is a way of driving
that device from somewhere else. Real, wanted, first-class, and not the thing
@@ -290,22 +291,30 @@ Two files under `firmware/bundles//`:
#include "osc/feature_osc.h"
#include "audio/feature_audio.h"
#include "audio_in/feature_audio_in.h"
+#include "midi/feature_midi.h"
#define PF_FEATURE_LIST \
&PFFeatureOsc::descriptor, \
&PFFeatureAudio::descriptor, \
- &PFFeatureAudioIn::descriptor
+ &PFFeatureAudioIn::descriptor, \
+ &PFFeatureMidi::descriptor
```
```c
// overrides.h — what it calls itself, and anything it needs set differently
#define PF_VARIANT "audio"
-#define PF_VARIANT_VERSION "v0.2.0" // yours, moves at your pace
-#define PF_WIFI_TX_POWER WIFI_POWER_17dBm
+#define PF_VARIANT_VERSION "v0.7.0" // yours, moves at your pace
+#define PF_AUDIO_IN_DRIVES_KNOBS 1 // the microphone drives the knobs here
```
+That is the Audio edition's pair with the comments taken out (v0.7.0 when
+this was written). It pinned `PF_WIFI_TX_POWER` too until the core went to
+full power, and dropped it in v0.7.0 — which is why every setting in an
+`overrides.h` carries its reason: one copied without it stays after the reason
+has gone.
+
A third file, `env`, is optional: one line naming the PlatformIO env to build
in, for the rare edition whose difference is a build flag rather than a
-setting. The MIDI edition is the one so far — its USB port has to be the OTG
+setting. The MIDI bundle is the one so far — its USB port has to be the OTG
controller for a MIDI device to exist, and the Arduino core decides that at
compile time (`ARDUINO_USB_MODE`). Everything else about it is the two files
above; `build.sh` and `shelf.sh` read the env name and look for the outputs
@@ -463,6 +472,9 @@ because no phone showed it in its pick list; and the Wi-Fi setup portal is a
parked branch, `feat/wifi-setup-portal`, in no edition and not to be merged as
it stands — it predates the seam and edits the core. What that attempt learned
is in [`investigations/2026-08-the-wifi-portal-a-samsung-never-drew.md`](investigations/2026-08-the-wifi-portal-a-samsung-never-drew.md).
+The need it was for — setting Wi-Fi up from a phone — is met in the core
+since 3.10.5 by the panel's own hotspot (`src/core_hotspot.h`): the
+device's Wi-Fi, so core rather than a feature.
---
diff --git a/docs/README.md b/docs/README.md
index 096215e3..ca4b90a9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -21,16 +21,20 @@ What is here, and what is not. The rule for the split: **a guide somebody follow
- [`midi-ableton.md`](midi-ableton.md) — the panel as a MIDI port in Ableton Live, with screenshots
- [`director-midi.md`](director-midi.md) — how a Director show becomes a MIDI clip
-- [`assembly/`](assembly/README.md) — the assembly map: which enclosure with which electronics, and where each route is documented
- [`../firmware/CUSTOM_PATTERNS.md`](../firmware/CUSTOM_PATTERNS.md) — the hands-on pattern route (the everyday one is [`PATTERN_GUIDE.md`](../PATTERN_GUIDE.md))
- [`../firmware/patternflow/console/README.md`](../firmware/patternflow/console/README.md) — editing or adding a device console page
## Building it
-- [`assembly/`](assembly/README.md) — the map. The full guide is [`../BUILD_GUIDE.md`](../BUILD_GUIDE.md); the files to order are described in [`../hardware/README.md`](../hardware/README.md)
+- [`assembly/`](assembly/README.md) — the assembly map: which enclosure with which electronics, and where each route is documented. The full guide is [`../BUILD_GUIDE.md`](../BUILD_GUIDE.md); the files to order are described in [`../hardware/README.md`](../hardware/README.md); enclosures other people built are [`../hardware/case/remixes/`](../hardware/case/remixes/README.md)
+- The same build in 3D, step by step, is [patternflow.work/guide/build](https://patternflow.work/guide/build). Its firmware chapter hands over to the Play guide's [Flash chapter](https://patternflow.work/guide/play#flash), which is also where to look when the first flash or the Wi-Fi step goes wrong (the written version is [`BUILD_GUIDE.md` §8](../BUILD_GUIDE.md#8-firmware))
- [`build-guide/images/`](build-guide/images/) — photos for the build guides (`images/` is the v2 set, `images/v3/` the current board)
- [`LICENSE-SUMMARY.md`](LICENSE-SUMMARY.md) — MIT for code, CC BY-SA 4.0 for hardware, docs and bundled patterns, and what that means for a pattern you publish or a remix you share
+## On the site, not in this folder
+
+The interactive guide at [patternflow.work/guide](https://patternflow.work/guide) has four parts, each in English and Korean: [Build](https://patternflow.work/guide/build) (soldering one from bare parts), [Play](https://patternflow.work/guide/play) (the first hour with a built one: flash, knobs, patterns, console), [Make](https://patternflow.work/guide/make) (community and Pattern Lab) and [Audio](https://patternflow.work/guide/audio) (the Audio edition: a browser tab, the microphone, MIDI with a DAW). They cover the same ground as the guides in the root, and every step has a **Stuck here?** link that opens an issue with that step filled in.
+
## Contributing
- [`../CONTRIBUTING.md`](../CONTRIBUTING.md) — one table: what you have → where it goes → what CI runs on it
diff --git a/docs/RELEASING.md b/docs/RELEASING.md
index fb438b7e..6aa9548e 100644
--- a/docs/RELEASING.md
+++ b/docs/RELEASING.md
@@ -106,5 +106,5 @@ Work happens on `dev`, then lands on `main` through a pull request. `main` is pr
- `v1.x` -- first public buildable release, then the multi-pattern firmware and browser flasher.
- `v2.x` -- the v2.0 board (GPIO0 cold-boot fix, cleaned silkscreen), custom pattern workflow, the web platform. `v2.1.0` is the last release for v2.x hardware.
-- `v3.0.0` -- the v3.0 board; `v3.9` (2026-09, unreleased as a tag) removed its USB-C footprint and changed nothing else. Every later `v3.x` is firmware/web on that hardware: `.pfm` modules over Wi-Fi (3.2), shows and the Director (3.6), the feature seam (3.7), editions (3.8).
+- `v3.0.0` -- the v3.0 board; `v3.9` (2026-09, unreleased as a tag) removed its USB-C footprint and changed nothing else. Every later `v3.x` is firmware/web on that hardware: `.pfm` modules over Wi-Fi (3.2), shows and the Director (3.6), the feature seam (3.7), editions (3.8), the panel's own hotspot and pattern code in PSRAM (3.10.5), patterns that stay loaded when you leave them (3.11).
- Editions on the shelf (`audio`, `performance`) carry their own version lines, independent of the project version — see `docs/EDITIONS.md`. `clock` and `midi` are bundles CI builds but the shelf does not carry.
diff --git a/docs/REPOSITORY.md b/docs/REPOSITORY.md
index e22c6b03..828b4743 100644
--- a/docs/REPOSITORY.md
+++ b/docs/REPOSITORY.md
@@ -6,13 +6,13 @@ A map for people. (AI coding agents get the same map with more rules in [`../AGE
| Folder | What it is | Start at |
| :--- | :--- | :--- |
-| `firmware/` | The ESP32-S3 firmware. `patternflow/` is the sketch: `patternflow.ino`, `config.h` (pins, brightness, limits, calibration), `net_config.h` (Wi-Fi, OTA, update defaults), `src/` (the engine: HUB75 driver, encoders, canvas, colour, noise, Wi-Fi, web console — the **core**, compiled unchanged into every published firmware), `features/` (everything optional: OSC, audio, MQTT, MIDI, shows, weather, clock — attached through hooks the core exposes), `presets/` (the curated patterns), `abi/` (the frozen contract a `.pfm` module is compiled against), `console/` (the device's web pages as plain HTML). `bundles/` names the editions (which features compile in) and holds `build.sh`. `toolchain/` is the repo-level scripts: build a module, make the Basics pack, and the `check_*.py` that CI runs. `modules/` is the module toolchain's working folder, not a place patterns are contributed to | [`firmware/README.md`](../firmware/README.md) for building; [`EDITIONS.md`](EDITIONS.md) before touching anything |
-| `hardware/` | `pcb/` (KiCad source, Gerbers per revision, schematic PDF), `case/` (STLs by printer bed size, knob plates, the Blender source, legacy v2 cases, and `remixes/` for community variants), `bom/` (the CSV that is the BOM source of truth) | [`hardware/README.md`](../hardware/README.md) |
-| `web/` | The Next.js site at patternflow.work: landing page, the interactive guide (`/guide`, with `/guide/build`, `/guide/play`, `/guide/make` and `/guide/audio`), Live Editor, Pattern Lab, the community (only on the community host), browser flasher, edition shelf, device update handoff, journal, roadmap. The JS presets in `src/lib/presets/` are the source of truth for the firmware's preset headers. The breadboard build guide is a React page here, `src/app/build/breadboard/` | [`web/README.md`](../web/README.md), then [`web/ARCHITECTURE.md`](../web/ARCHITECTURE.md) |
+| `firmware/` | The ESP32-S3 firmware. `patternflow/` is the sketch: `patternflow.ino`, `config.h` (pins, brightness, limits, calibration), `net_config.h` (Wi-Fi, OTA, update defaults), `src/` (the engine: HUB75 driver, encoders, canvas, colour, noise, Wi-Fi, web console — the **core**, compiled unchanged into every published firmware), `features/` (everything optional: OSC, audio, the microphone, MQTT, MIDI, shows, weather, clock, BLE — attached through hooks the core exposes), `presets/` (the curated patterns), `abi/` (the frozen contract a `.pfm` module is compiled against), `console/` (the device's web pages as plain HTML). `bundles/` names the compositions — the shelf editions `audio` and `performance`, plus `clock` and `midi`, which CI keeps compiling but the shelf does not carry — and holds `build.sh` and `shelf.sh`. `toolchain/` is the repo-level scripts: build a module, make the Basics pack, and the `check_*.py` that CI runs. `modules/` is the module toolchain's working folder, not a place patterns are contributed to. `encoder_test/` is a standalone sketch for checking the four encoders, and `CUSTOM_PATTERNS.md` the hands-on pattern route | [`firmware/README.md`](../firmware/README.md) for building; [`EDITIONS.md`](EDITIONS.md) before touching anything |
+| `hardware/` | `pcb/` (KiCad source, Gerbers per revision, schematic PDF), `case/` (STLs by printer bed size, knob plates, the Blender source, legacy v2 cases, the old v1/v2 laser-cut drawings in `legacy_lasercut/`, and `remixes/` — enclosures other people designed and built, one folder each), `bom/` (the CSV that is the BOM source of truth) | [`hardware/README.md`](../hardware/README.md) |
+| `web/` | The Next.js site at patternflow.work: landing page, the interactive guide (`/guide`, with `/guide/build`, `/guide/play`, `/guide/make` and `/guide/audio`), Live Editor, Pattern Lab, the community (only on the community host), the browser flasher (the **Flash Patternflow** button on the Pattern tab and in the Play guide; the images it installs are `public/flash/`), edition shelf, feature catalogue, device update handoff, journal, roadmap. The JS presets in `src/lib/presets/` are the source of truth for the firmware's preset headers. The breadboard build guide is a React page here, `src/app/build/breadboard/` | [`web/README.md`](../web/README.md), then [`web/ARCHITECTURE.md`](../web/ARCHITECTURE.md) |
| `docs/` | Contracts other software is built against, how the firmware is put together, walk-throughs, the assembly map, project records and media | [`README.md`](README.md) |
-| `tools/` | Clients Patternflow ships that run on their own: the audio-react browser extension (also the authoring source of the device's `/audio-in` page), the Android capture app, an RTP-MIDI probe. Also `case-models/`, the scripts that make the /build page's 3D model of each case | [`tools/README.md`](../tools/README.md) |
+| `tools/` | Clients Patternflow ships that run on their own: the audio-react browser extension (also the authoring source of the device's `/audio-in` page), the Android capture app, an RTP-MIDI probe. Also `case-models/`, the scripts that make the /build page's 3D model of each case, the official one and every remix on its case switch | [`tools/README.md`](../tools/README.md) |
| `integrations/` | Bridges that run inside or beside someone else's software (Ableton / Max for Live today) and talk to the panel only through the contracts in `docs/` | [`integrations/README.md`](../integrations/README.md) |
-| `.github/` | Issue and pull-request templates, the CI workflows (web, firmware editions, doc links, console-page sync, firmware release assets, Discord dev-log) and their helper scripts | the header comment of each workflow |
+| `.github/` | Issue and pull-request templates (including the guide's **Stuck here?** form and **Share your build**), the CI workflows (web, every firmware composition, the core-names-no-feature checks, doc links, community folders carrying their README, console-page sync, firmware release assets, Discord dev-log) and their helper scripts | the header comment of each workflow |
| root | `README.md`, the five guides (`BUILD_GUIDE.md`, `BUILD_GUIDE_v2.md`, `PATTERN_GUIDE.md`, `FEATURE_GUIDE.md`, `AUDIO_GUIDE.md`), `CHANGELOG.md`, `CONTRIBUTING.md`, `SUPPORT.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `CITATION.cff`, the two license texts and the `LICENSE` pointer, `AGENTS.md` (+ `CLAUDE.md`, a one-line shim) | |
The rule for where a document lives: **a guide somebody follows start to finish is at the root; a contract, a reference or a record is in `docs/`; a folder explains itself in its own README.**
@@ -22,7 +22,7 @@ The rule for where a document lives: **a guide somebody follows start to finish
| Goal | Path |
| :--- | :--- |
| Build one | [`README.md`](../README.md) → [`BUILD_GUIDE.md`](../BUILD_GUIDE.md) (or the [assembly map](assembly/README.md) for the other routes) → [`hardware/README.md`](../hardware/README.md) for which files to order. The same build in 3D: [patternflow.work/guide/build](https://patternflow.work/guide/build) |
-| Use the one you built | [patternflow.work/guide/play](https://patternflow.work/guide/play) — flashing, the knobs, installing patterns and the console, on a Patternflow you can turn |
+| Use the one you built | [patternflow.work/guide/play](https://patternflow.work/guide/play) — flashing, the knobs, installing patterns and the console, on a Patternflow you can turn. The first flash goes through download mode (hold BOOT, tap RST, let go of BOOT), and the Wi-Fi step usually does not appear the first time: the [Flash chapter](https://patternflow.work/guide/play#flash) and [`BUILD_GUIDE.md` §8](../BUILD_GUIDE.md#8-firmware) say what to do, in order |
| Make a pattern | [`PATTERN_GUIDE.md`](../PATTERN_GUIDE.md) — the Pattern Lab, the community, and the device. No local toolchain involved. The same loop as a tutorial: [patternflow.work/guide/make](https://patternflow.work/guide/make). [`firmware/CUSTOM_PATTERNS.md`](../firmware/CUSTOM_PATTERNS.md) is the older, more hands-on route |
| Write a firmware feature or cut an edition | [`FEATURE_GUIDE.md`](../FEATURE_GUIDE.md) → [`EDITIONS.md`](EDITIONS.md) → [`features/README.md`](../firmware/patternflow/features/README.md) → [`bundles/README.md`](../firmware/bundles/README.md) |
| Control the panel from other software | [`rest-api.md`](rest-api.md) has the table for choosing between HTTP, OSC, MIDI and MQTT; then the spec for the one you chose |
@@ -43,7 +43,7 @@ Filenames are lowercase with underscores. Tags are `vX.Y.Z`; the firmware source
The site, the firmware and the release notes hold absolute `blob/main/…` links into this tree, and none of them is checked by CI. Before renaming or moving any of these, grep `web/src`, `web/content`, `firmware/patternflow/console` and `firmware/patternflow/features/*/*_index.h`:
-- `README.md`, `BUILD_GUIDE.md` (three section anchors are linked from the site), `AUDIO_GUIDE.md`, `FEATURE_GUIDE.md`, `PATTERN_GUIDE.md`
+- `README.md`, `BUILD_GUIDE.md` (the site links its section anchors — §1, §2, §4, §8, §8.2 and §10 when this was written — so a renamed heading breaks them), `AUDIO_GUIDE.md`, `FEATURE_GUIDE.md`, `PATTERN_GUIDE.md`
- `docs/assembly/README.md`, `docs/midi-ableton.md`, `docs/midi-spec.md` (the last two are baked into the Audio edition's console pages)
- `docs/EDITIONS.md`, `docs/rest-api.md`, `firmware/README.md`, `hardware/bom/bom_v3.9.csv`
- `web/src/app/compliance/` — the `/compliance` URL is printed on the box. Never move, redirect or noindex it.
diff --git a/docs/assembly/README.md b/docs/assembly/README.md
index 07010a0d..da89ad3d 100644
--- a/docs/assembly/README.md
+++ b/docs/assembly/README.md
@@ -7,7 +7,7 @@
Patternflow is not a single, rigid kit. It is a modular system divided into two core parts:
-1. **Enclosure** — how you house the device (3D printed, or a community remix such as the laser-cut one below).
+1. **Enclosure** — how you house the device (3D printed, or one of the community remixes below).
2. **Electronics** — how you wire the hardware (custom PCB, or breadboard).
Build those two, flash the firmware, and your Patternflow is alive.
@@ -31,6 +31,7 @@ This is the route [BUILD_GUIDE.md](../../BUILD_GUIDE.md) walks start to finish,
| 3D printed enclosure | Breadboard / jumper-wire electronics | Available — [Breadboard Build Guide](https://patternflow.work/build/breadboard) |
| Laser-cut acrylic or MDF sheet | Custom PCB | Community — [`remixes/simonepda-lasercut/`](../../hardware/case/remixes/simonepda-lasercut/README.md), by Simone Majocchi. Built by its author in both materials; its README stands in for the printing and case-assembly sections of the build guide |
| 3D printed, glued halves with screwed-on covers | Custom PCB | Community — [`remixes/besoiobiy-printed/`](../../hardware/case/remixes/besoiobiy-printed/README.md), by Besoiobiy. Built by its author; the panel is bolted through the case into its own M3 sockets, so check your panel's thread size and socket depth first. Its README stands in for the printing and case-assembly sections of the build guide and sets the wiring order |
+| 3D printed, horizontal on a desk, with stands | Custom PCB | Community — [`remixes/mbchars-horizontal-desktop-printed/`](../../hardware/case/remixes/mbchars-horizontal-desktop-printed/README.md), by Mykyta Bilous (mbchars). Built by its author; the panel lies the long way with the controls on the right, and there is no power-bank bay — a separate USB-PD trigger module feeds `J4`, the screw terminal, at 5 V. Its README and assembly notes stand in for the printing and case-assembly sections of the build guide |
| Any other enclosure | either | Not an official path. Community variants live in [`hardware/case/remixes/`](../../hardware/case/remixes/README.md); the old acrylic drawings, for the v1 and v2 boards, are in [`legacy_lasercut/`](../../hardware/case/legacy_lasercut/README.md) |
The breadboard path exists to make Patternflow easier and cheaper to start. A breadboard build is not just a temporary prototype — if that form is enough for you, it is a valid Patternflow build. Want a more finished object later? Move to the PCB and printed-enclosure path whenever you like.
@@ -41,7 +42,7 @@ The custom PCB path is stable. PCBA may become a later electronics path for peop
To bring the hardware to life you flash it with firmware, and you can **create and run your own generative patterns with AI coding assistants**.
-Custom patterns no longer need a local toolchain or a reflash: a pattern built in the browser installs on the panel over Wi-Fi as a `.pfm` module in seconds (the browser flasher writes the firmware itself over USB once, and sets up Wi-Fi while it is at it). The PlatformIO route remains only for firmware feature development or targeting a different LED matrix resolution.
+Custom patterns no longer need a local toolchain or a reflash: a pattern built in the browser installs on the panel over Wi-Fi as a `.pfm` module in seconds (the browser flasher writes the firmware over USB once, with the DevKit out of the board and in download mode — hold BOOT, tap RST — and Wi-Fi is set after it, usually on a second go with the flasher or from the panel's own network; the Play guide's [Flash chapter](https://patternflow.work/guide/play#flash) has the order, and [`BUILD_GUIDE.md` §8](../../BUILD_GUIDE.md#8-firmware) the details). The PlatformIO route remains only for firmware feature development or targeting a different LED matrix resolution.
- **[Make your own patterns](../../PATTERN_GUIDE.md)** — the Pattern Lab, the community, and sending a pattern to your device over Wi-Fi. [`firmware/CUSTOM_PATTERNS.md`](../../firmware/CUSTOM_PATTERNS.md) is the hands-on route.
diff --git a/docs/director-midi.md b/docs/director-midi.md
index 22d39c63..7b5a0620 100644
--- a/docs/director-midi.md
+++ b/docs/director-midi.md
@@ -106,7 +106,7 @@ where the `midi` feature lives.
**Leave Remote on the Output row off.** With it on, Live sends every mapped
parameter's value back to the panel on the same CC, and the panel reads that
position as a relative turn: the knob pins itself to one value and ticks
- whenever you move it. The same applies to the USB port on the MIDI edition.
+ whenever you move it. The same applies to the USB port on the MIDI bundle.
**Then, to send a lane:**
diff --git a/firmware/CUSTOM_PATTERNS.md b/firmware/CUSTOM_PATTERNS.md
index 02c0ae85..b4af51ff 100644
--- a/firmware/CUSTOM_PATTERNS.md
+++ b/firmware/CUSTOM_PATTERNS.md
@@ -2,16 +2,19 @@
Patternflow includes a workflow for creating patterns without writing low-level rendering code. Two paths converge in the same place — the Live Editor at [patternflow.work](https://patternflow.work) → **Pattern** section → **Live Editor**.
-- **AI-assisted** — describe the pattern in plain language, paste the AI's output into the editor, tune knobs, copy to C++, flash. No shader knowledge required.
+- **AI-assisted** — describe the pattern in plain language, paste the AI's output into the editor, tune knobs, copy to C++, send it to the board. No shader knowledge required.
- **Direct** — write the JavaScript pattern by hand in the editor, then convert to C++. For anyone comfortable with fragment-shader-style code.
-> **New: you no longer need the Arduino IDE.** The last step — turning a pattern into firmware and getting it onto the board — now runs in the browser: the server compiles, your browser flashes over USB. See [step 5](#5-convert-to-c-and-flash). A local build is still the route for firmware development itself; that is PlatformIO through `firmware/bundles/build.sh`, not the Arduino IDE (the IDE cannot build the editions' library set).
-
-> **Newer still: patterns can install without any flashing at all.** On firmware
-> with loadable-module support, the same `.h` builds into a tiny `.pfm` module
-> that installs over Wi-Fi from the device's `/patterns` page — a few KB, no
-> reflash, no reboot, unlimited slots. The community cart does this end to end.
-> Details: [firmware/README.md → Loadable pattern modules](README.md#loadable-pattern-modules-pfm).
+> **No Arduino IDE, no flashing, no cable.** The last step — getting a pattern
+> onto the board — runs in the browser: a server compiles the `.h` into a
+> small `.pfm` module (a few KB) and your browser hands it to the board over
+> Wi-Fi, where it appears in the list with no reflash and no reboot (up to 128
+> of them). See [step 5](#5-convert-to-c-and-flash); a community pattern's
+> **Send to my Patternflow** does the same end to end. Details:
+> [firmware/README.md → Loadable pattern modules](README.md#loadable-pattern-modules-pfm).
+> A local build is still the route for firmware development itself; that is
+> PlatformIO through `firmware/bundles/build.sh`, not the Arduino IDE (the IDE
+> cannot build the editions' library set).
The 5-step path below is the AI-assisted route. The direct route reuses the same editor; skip to [Direct coding](#direct-coding-no-ai) if you don't need AI help.
@@ -119,7 +122,7 @@ Drop the generated header file into the firmware folder:
firmware/patternflow/
├── patternflow.ino
├── config.h ← hardware pins / display / limits / LED calibration
-├── net_config.h ← Wi-Fi / OTA defaults (+ feature tuning defaults)
+├── net_config.h ← Wi-Fi / Improv / OTA / hotspot defaults (core only)
├── patternflow_secrets.example.h ← copy to patternflow_secrets.h for credentials
├── pattern_registry.h ← edit this
├── _TEMPLATE.h ← starting point for a new preset
diff --git a/firmware/README.md b/firmware/README.md
index 3b89cced..c7c29014 100644
--- a/firmware/README.md
+++ b/firmware/README.md
@@ -12,6 +12,7 @@ This file is long because it is the firmware's reference. Most people need one p
| you want to | read |
| --- | --- |
+| put Patternflow on a new board and get it on Wi-Fi | not this file: [`BUILD_GUIDE.md` §8](../BUILD_GUIDE.md#8-firmware), or the Play guide's [Flash chapter](https://patternflow.work/guide/play#flash). The short version is [First flash and Wi-Fi](#first-flash-and-wi-fi) |
| build and flash the firmware locally | [Setup](#setup) — the board package, PlatformIO, the libraries |
| write a feature or cut an edition | [`docs/EDITIONS.md`](../docs/EDITIONS.md) first, then [Adding features — the constraints that matter](#adding-features--the-constraints-that-matter) here |
| write a pattern | [`PATTERN_GUIDE.md`](../PATTERN_GUIDE.md) (nothing here is needed); the hands-on route is [`CUSTOM_PATTERNS.md`](CUSTOM_PATTERNS.md) |
@@ -34,6 +35,48 @@ patterns and settings intact.
feature is, how to write one, how to cut an edition, and the promises every
build keeps so switching stays safe. Read it before writing a feature.
+## First flash and Wi-Fi
+
+Nothing on this page is needed to put the released firmware on a board: the
+browser does it. The full walk-through is
+[`BUILD_GUIDE.md` §8](../BUILD_GUIDE.md#8-firmware) and, on a board you can
+turn, [patternflow.work/guide/play#flash](https://patternflow.work/guide/play#flash).
+In order:
+
+1. The DevKit **out of the board**, on its **left** USB-C port (`USB`, the
+ native one), with a cable that carries data. Desktop Chrome or Edge. The
+ port is for flashing and data only — the panel is powered through `J4`,
+ the screw terminal, and nothing else.
+2. **Download mode, every first flash:** hold **BOOT**, tap **RST**, let go
+ of BOOT. The port then shows as `USB JTAG/serial debug unit`.
+3. **Flash Patternflow** (the Pattern tab on patternflow.work, or the guide),
+ pick that port, *Install Patternflow*, confirm. About two minutes. From
+ download mode the flasher cannot tell a board already runs Patternflow,
+ so it erases the whole flash, patterns and saved Wi-Fi included — nothing
+ to lose on a new build; a running board updates over Wi-Fi instead (see
+ [Wireless update from the browser](#wireless-update-from-the-browser)).
+4. **The Wi-Fi step usually does not appear** after an install from download
+ mode, because the board does not restart into Patternflow by itself
+ ([`core_improv.h`](#core_improvh--patternflowimprov) can only answer once it
+ runs). Expected, not a failure: press **RST** once, press **Flash
+ Patternflow** again, pick the port, and the dialog opens on Patternflow's
+ own menu. *Connect to Wi-Fi*; after about 10 s of "Scanning" (the firmware
+ sends no list) type the network's name exactly and its password. 2.4 GHz
+ only.
+5. **Or skip the flasher for Wi-Fi.** Seat the DevKit and power the panel:
+ about 15 s after it finds no network it knows (`PF_HOTSPOT_AUTO_AFTER_MS`),
+ it raises its own, `patternflow-xxxx` (the last two MAC bytes), password
+ `patternflow` ([`core_hotspot.h`](#core_hotspoth--patternflowhotspot)).
+ Hold **K2** and the NETWORK screen shows `HOTSPOT` and the name. Not in
+ the phone's list yet? It only exists from about 15 s after power-on:
+ wait, refresh the list, and match the exact name on K2. Join it (the phone
+ says "no internet", which is true; stay on it), open
+ `http://192.168.4.1/`, and pick your network on the Wi-Fi page.
+
+Once it is on your network, hold **K2** for its address; the console is at
+`patternflow.local` (on Android, type the IP). A new board runs Origin only
+— the Basics pack installs from the decks page over Wi-Fi.
+
## Setup
### Required board package
@@ -152,6 +195,7 @@ firmware/
│ │ ├── core_sleep.h # Sleep mode: panel off, board idle, still on the network
│ │ ├── core_wifi.h # Multi-network Wi-Fi (up to 5 saved, tried in order)
│ │ ├── core_improv.h # Improv-Serial provisioning from the browser flasher
+│ │ ├── core_hotspot.h # The panel's own Wi-Fi network when no saved one answers
│ │ ├── core_http.h # The one port-80 WebServer every page registers on
│ │ ├── core_net_task.h # The network task on Core 0: Wi-Fi, HTTP, Improv, OTA
│ │ ├── core_loop_sync.h # Hands a handler's frame-touching work to the loop task
@@ -167,7 +211,7 @@ firmware/
│ │ ├── pf_feature.h # The hook interface a feature implements
│ │ ├── pf_features.h # The dispatcher
│ │ ├── features.h # The default list (empty) and the escape hatch
-│ │ ├── osc/ audio/ audio_in/ midi/ ble/ mqtt/ show/ weather/
+│ │ ├── osc/ audio/ audio_in/ midi/ ble/ mqtt/ show/ weather/ clock/
│ │ └── README.md # What a feature is and the hooks it can answer
│ ├── console/ # The device's web pages as plain HTML — edit these
│ └── toolchain/ # PlatformIO extra_scripts (must stay sketch-relative)
@@ -339,7 +383,7 @@ PatternflowOta::handle(); // first line of loop() — UDP poll, ~free when idle
Shares Wi-Fi credentials and connection with OSC (if both are enabled, the connection is reused). When `PF_OTA_ENABLED` is 0 everything compiles to a no-op. See the [OTA Updates](#ota-updates-for-developers) section below for the user-facing workflow.
### `core_improv.h` — PatternflowImprov
-Self-contained [Improv-Serial](https://www.improv-wifi.com/serial/) implementation so the **browser flasher** (ESP Web Tools, behind the website's "Flash" button) can set Wi-Fi over USB right after flashing — no rebuild, no `patternflow_secrets.h`. The main sketch only needs:
+Self-contained [Improv-Serial](https://www.improv-wifi.com/serial/) implementation so the **browser flasher** (ESP Web Tools, behind the website's **Flash Patternflow** button) can set Wi-Fi over USB once the firmware is running — no rebuild, no `patternflow_secrets.h`. The main sketch only needs:
```cpp
PatternflowImprov::begin(); // in setup() — announces it speaks Improv
@@ -348,13 +392,19 @@ PatternflowImprov::handle(); // in loop() — drains Serial, drives provisioni
The flasher sends the SSID/password over serial; `core_wifi.h` stores them in NVS (separate `pf_wifi` namespace) and uses them in preference to the built-in `PF_WIFI_SSID/PASS` placeholders on every boot. Shares the debug USB Serial — the host parser scans for the `IMPROV` header and ignores `println()` noise. Compiled in only when Wi-Fi is actually used (one of OTA/OSC/audio enabled) and `PF_IMPROV_ENABLED` is 1; otherwise a no-op. Set `#define PF_IMPROV_ENABLED 0` in `patternflow_secrets.h` to drop it.
+The flasher can only ask for Wi-Fi once Patternflow is running and answering on that port. After an install from download mode (BOOT + RST) the board usually does not restart into Patternflow by itself, so the flasher's dialog ends at *Installation complete!* with no Wi-Fi step; pressing RST and opening the flasher again brings up Patternflow's own menu with *Connect to Wi-Fi*. The flasher waits up to 20 s for that answer after an install (`new_install_improv_wait_time` in `web/public/flash/manifest.json`), and a join is given up on after 30 s.
+
+### `core_hotspot.h` — PatternflowHotspot
+The panel as a network of its own, so Wi-Fi can be set up without the flasher, and a panel can be used where there is no network at all. Core, not a feature: every image since 3.10.5 has it. In the default `auto` mode, once the station has had no link for `PF_HOTSPOT_AUTO_AFTER_MS` (15 s) the panel raises a WPA2 access point named `patternflow-xxxx` (the hostname plus the last two bytes of its MAC, `patternflow-a1b2`), password `PF_HOTSPOT_PASS` (`patternflow`) until it is changed on `/wifi`, and the whole console answers at `http://192.168.4.1/`. The NETWORK screen (hold K2) shows `HOTSPOT` and the name while it is what there is. On the Wi-Fi page the network-name field offers the networks the hotspot's channel scan saw, so a phone user picks one instead of typing a case-sensitive name; the panel then tries it at once and reports on the page and on its LEDs whether it joined. The hotspot answers every DNS name with the panel and fails the phone's internet probe at once, so the phone says "no internet" — true — and stays connected. Modes `off` / `auto` / `always` are kept in NVS and set on `/wifi` or `POST /api/hotspot` ([`docs/rest-api.md`](../docs/rest-api.md)). The header comment records what the bench decided and why, line by line.
+
## Patterns
The pattern list has two tiers:
-- **Presets** — the curated showcase in `presets/`, compiled into `firmware.bin`.
- Pattern 1 is always `Origin`. They work whether or not the filesystem mounts,
- and switching between them is instant.
+- **Presets** — compiled into `firmware.bin`. Today that is `Origin` alone,
+ pattern 1, the failsafe; the rest of the curated set in `presets/` ships as
+ the Basics pack. A preset works whether or not the filesystem mounts, and
+ switching to one is instant.
- **Modules** — `.pfm` files on the FATFS partition, discovered at boot and
appended after the presets. This is how new patterns normally arrive now: a
~3–22 KB file installed over Wi-Fi at `http://patternflow.local/patterns`,
@@ -438,7 +488,7 @@ surface — the math headers are literally the same files, included with
`-DPF_MODULE_BUILD`.
**Install paths**, easiest first:
-- Community cart → "Send over Wi-Fi": opens `/patterns?src=`, and
+- A community deck or pattern page → "Send over Wi-Fi" (or a deck's "Install to my board"): opens `/patterns?src=`, and
the page fetches and installs every file itself. Nothing downloaded.
- Drag any number of `.pfm`/`.json` files onto `/patterns` — a visible queue
uploads them one by one with progress, retries, and per-file results.
@@ -785,9 +835,9 @@ For the original defaults:
### Longpress actions
- **Encoder 1 longpress (≥1s)** — enter/exit global brightness mode. While active, K1 rotation adjusts panel brightness (5–255, ~5 per detent), the active pattern does not see K1 input, and a "BRIGHTNESS XX%" overlay shows the current level. Exits on a second longpress or after 5 seconds of idle. Value persists across reboots via NVS.
-- **Encoder 2 longpress (≥1s)** — enter/exit the NETWORK screen (portrait status view: Wi-Fi state, local IP, OSC on/off and its remote host/port, audio-react on/off). Inside the screen, settings are toggled by **knob rotation, not clicks** — right = ON, left = OFF — so that holding K2 to leave can't flip anything on the way out:
- - **turn K2** → OSC send/receive on/off (persists in NVS, so the device reboots into the same state). Wi-Fi stays connected either way; the toggle only enables/disables traffic. If the firmware was built with `PF_OSC_ENABLED 0` the row still shows but the toggle is inert ("REBUILD WITH PF_OSC_ENABLED=1").
- - **turn K3** → audio-react on/off (also persisted).
+- **Encoder 2 longpress (≥1s)** — enter/exit the NETWORK screen (portrait status view: the Wi-Fi state and the board's IP address — or, while the panel has no Wi-Fi and its own hotspot is up, `HOTSPOT` and the hotspot's name — plus a row for each of the first two features in the build that can be switched at runtime). The core does not know what those rows are: a feature appears by declaring a short name and a switch (`features/README.md`). On the default build there are none; on the Audio edition they are `OSC` and `AUD` (audio-react). Inside the screen, rows are toggled by **knob rotation, not clicks** — right = ON, left = OFF — so that holding K2 to leave can't flip anything on the way out:
+ - **turn K2** → the first row on/off (persists in NVS, so the device reboots into the same state). Wi-Fi stays connected either way; the switch only enables/disables that feature's traffic.
+ - **turn K3** → the second row on/off (also persisted).
- **turn K1** → sleep (see [Sleep mode](#sleep-mode) below). Either direction, unlike the toggles above: the screen is about to go dark, so "right = on" has nothing to mean.
- **turn K4** → hand off to the UPDATE screen, which arms the `/update` endpoint (the arming *is* the security model — see `core_web_update.h`).
@@ -901,15 +951,15 @@ From a pattern's point of view every one of them is the user turning the four en
Two switches, for a local build:
-- **Compile-time** — whether a feature is linked in at all is the bundle: `firmware/bundles//features_local.h` lists the features an edition carries ([`bundles/README.md`](bundles/README.md)). A feature's own tunables live in `features//_config.h` (the audio WebSocket port and knob scale in `features/audio/audio_config.h`, the OSC ports in `features/osc/osc_config.h`, the broker defaults in `features/mqtt/mqtt_config.h`), every one `#ifndef`-guarded so a line in `patternflow_secrets.h` wins. `PF_OSC_ENABLED 0` in the secrets file compiles OSC out of an edition that would otherwise carry it; the K2 info screen then shows `OFF (compile-time)`.
-- **Runtime** — once compiled in, OSC and MIDI are switched on and off from the panel's NETWORK screen (hold K2), the state saved in NVS; the K2 screen shows `WAIT HOST` until the first OSC packet teaches the panel your computer's address.
+- **Compile-time** — whether a feature is linked in at all is the bundle: `firmware/bundles//features_local.h` lists the features an edition carries ([`bundles/README.md`](bundles/README.md)). A feature's own tunables live in `features//_config.h` (the audio WebSocket port and knob scale in `features/audio/audio_config.h`, the OSC ports in `features/osc/osc_config.h`, the broker defaults in `features/mqtt/mqtt_config.h`), every one `#ifndef`-guarded so a line in `patternflow_secrets.h` wins. `PF_OSC_ENABLED 0` in the secrets file compiles OSC's traffic out of an edition that would otherwise carry it (its NETWORK row stays, and switches nothing).
+- **Runtime** — once compiled in, a feature with a runtime switch gets a row on the panel's NETWORK screen (hold K2; turn K2 for the first row, K3 for the second), the state saved in NVS. On the Audio edition those rows are OSC and AUD; MIDI is switched on its console page (`/midi`). OSC learns your computer's address from the first packet it receives — send `/patternflow/ping` (the Max for Live bridge's Connect button does) — see [`docs/osc-spec.md`](../docs/osc-spec.md).
## Wireless update from the browser
Once the device is on Wi-Fi, it can flash itself from any browser on the same network — no Arduino IDE, no USB cable, no TLS setup ([#232](https://github.com/engmung/Patternflow/issues/232)).
-1. Get a firmware `.bin`: build one on [patternflow.work](https://patternflow.work) and hit *Download .bin*, or export one locally (`arduino-cli compile --output-dir …` — use the app image, not a merged full-flash image).
-2. Open `http://patternflow.local` — the device serves a small console (audio sync / firmware update) — and pick **Firmware Update**, or go straight to `/update`. If `.local` doesn't resolve (common on Android), use `http:///update`; the device shows its IP on the NETWORK screen (hold K2) and on the UPDATE screen.
+1. Get a firmware `.bin`, or let the site fetch it for you: when a newer release exists the console says so and links to [patternflow.work/update](https://patternflow.work/update), which hands the image to the panel; an edition installs the same way from [the shelf](https://patternflow.work/editions). A file of your own is a release's `patternflow.ino.bin` (or `patternflow-.ino.bin`), or a local build (`./firmware/bundles/build.sh`, or `build.sh flash `, which also sends it) — the app image, not a merged full-flash image.
+2. Open `http://patternflow.local` — the device's console — and pick **Update** in its header, or go straight to `/update`. If `.local` doesn't resolve (common on Android), use `http:///update`; the device shows its IP on the NETWORK screen (hold K2) and on the UPDATE screen.
3. Drop the `.bin` on the page. The panel shows flash progress; the device verifies, reboots, and comes back on the new firmware in about ten seconds.
Notes:
diff --git a/firmware/bundles/README.md b/firmware/bundles/README.md
index 73546715..4741b04a 100644
--- a/firmware/bundles/README.md
+++ b/firmware/bundles/README.md
@@ -7,7 +7,8 @@
Named firmwares, built from this repository.
Everything Patternflow does lives in `patternflow/` — the panel, the pattern
-loader, Wi-Fi, sleep, OSC, MQTT, the show player, weather, audio. **A bundle
+loader, Wi-Fi and the hotspot, sleep, and in `patternflow/features/` OSC,
+MQTT, the show player, weather, audio, MIDI, the clock. **A bundle
does not add code.** It is two files saying which of that gets compiled in,
and which settings differ.
@@ -26,12 +27,12 @@ lives in an edition built from these two files.
```bash
./firmware/bundles/build.sh # the default: no features
./firmware/bundles/build.sh audio # the audio bundle
-./firmware/bundles/build.sh midi # the MIDI edition, in its own env (bundles/midi/env)
+./firmware/bundles/build.sh midi # the MIDI bundle, in its own env (bundles/midi/env)
./firmware/bundles/build.sh audio flash patternflow.local
./firmware/bundles/build.sh all # every composition + the marker scan
# proving each binary carries exactly
# its features
-./firmware/bundles/shelf.sh audio v0.5.0 # a publishable image: secrets
+./firmware/bundles/shelf.sh audio vA.B.C # a publishable image: secrets
# moved aside, scanner proven against a
# control build, four files staged for
# the shelf
diff --git a/firmware/patternflow/README.md b/firmware/patternflow/README.md
index a9dd99d9..4f3507dd 100644
--- a/firmware/patternflow/README.md
+++ b/firmware/patternflow/README.md
@@ -28,11 +28,10 @@ FATFS partition (on the device, not in this repo)
and appended after the presets. No reflash, no reboot, up to 128 of them.
Build one with `firmware/toolchain/build_module.py`.
-> The old `custom1.h`–`custom3.h` slots in the sketch root are **gone** —
-> modules are what they were for, and no longer cost a rebuild. Only `Origin`
-> is compiled in today; the rest of the curated set ships as the Basics pack
-> (`web/public/packs/basics.zip`, built from `presets/` by
-> `firmware/toolchain/make_pack.py`) and installs from the Patterns page.
+> Only `Origin` is compiled in today; the rest of the curated set ships as the
+> Basics pack (`web/public/packs/basics.zip`, built from `presets/` by
+> `firmware/toolchain/make_pack.py`) and installs from the Patterns page. The
+> old `custom1.h`–`custom3.h` slots are gone — see [Registry](#registry).
## Source of truth
@@ -169,8 +168,9 @@ soon as anything else wanted RAM. One preset plus a pack you choose leaves
The preset sources are still in `presets/` as the editable originals —
`toolchain/port_preset.py` is what turns one back into a module.
-> **The `custom1..3` slots are gone.** They were the hand-edited way to get a
-> pattern on a device, and uploading a `.pfm` replaced them. `PF_CUSTOM_SLOT_COUNT`
+> **The `custom1.h`–`custom3.h` slots in the sketch root are gone.** They were
+> the hand-edited way to get a pattern on a device, and uploading a `.pfm`
+> replaced them — modules are what they were for, and no longer cost a rebuild. `PF_CUSTOM_SLOT_COUNT`
> is `0`, and the region between the `PF_CUSTOM_SLOTS_BEGIN` / `PF_CUSTOM_SLOTS_END`
> markers exists only so the web build service can compile a submitted pattern
> into a whole image for devices whose firmware predates the module loader.
@@ -184,19 +184,22 @@ The preset sources are still in `presets/` as the editable originals —
Two things about that set which the file itself won't tell you:
- **Not every pattern made the cut.** 0516, 0517, 0519-2, 0524, 0524-2, 0526, 0529 and 0530 were left out for performance lag or rendering problems on real ESP32 hardware. A pattern that looks fine in the browser can still miss frame budget on the device. 0609, 0614, 0614-2, 0619, 0622 and 0624 were posted after the module loader made the compiled-in showcase unnecessary and were never ported. `firmware/toolchain/check_presets.py` (run in CI) keeps this list honest: a new web preset has to be ported or added there with a reason.
-- **Patterns posted to Instagram after 0602 aren't bundled here.** They're on Discord; convert them yourself, or install them as `.pfm` modules.
+- **New patterns are not added here.** The set runs from 0510 to 0719 (with the gaps above); patterns since are published from the Pattern Lab to the Community, and install on a board from there as `.pfm` modules.
Anything not compiled in is one upload away — build a module and send it to the device over Wi-Fi, no reflash.
## Licensing (read before submitting)
-Patterns are licensed **CC-BY-SA-4.0** (same commons as the hardware; see
+The patterns in `presets/` — the curated set, and so the Basics pack — are
+licensed **CC-BY-SA-4.0** (same commons as the hardware; see
`/LICENSE-CC-BY-SA`). Two simple, community-enforceable rules: **give credit,
-and share alike.**
+and share alike.** A pattern you publish to the Community instead carries the
+license you pick there ([`docs/LICENSE-SUMMARY.md`](../../docs/LICENSE-SUMMARY.md));
+publishing is how patterns are shared now, not a pull request
+([`CONTRIBUTING.md`](../../CONTRIBUTING.md)).
-**Inbound = outbound:** by submitting a pattern (PR, issue, or Discord) you
-agree to license it under **CC-BY-SA-4.0**, with attribution kept in the
-metadata header. No copyright assignment (no CLA) — you keep authorship; the
+**Inbound = outbound:** a pattern that does come into this tree is licensed
+under **CC-BY-SA-4.0**, with attribution kept in the metadata header. No copyright assignment (no CLA) — you keep authorship; the
project just gets the right to bundle and redistribute it.
Authors may set a different license in the metadata header, as long as it
diff --git a/firmware/patternflow/console/README.md b/firmware/patternflow/console/README.md
index 2b60139d..728003a3 100644
--- a/firmware/patternflow/console/README.md
+++ b/firmware/patternflow/console/README.md
@@ -1,7 +1,8 @@
# The device console
-These are the pages the panel serves over your LAN. They are ordinary HTML
-files — open one in a browser, edit it, refresh.
+These are the pages the panel serves over your LAN, and on its own hotspot
+at `http://192.168.4.1/` when it has no network (`src/core_hotspot.h`). They
+are ordinary HTML files — open one in a browser, edit it, refresh.
You do not need the firmware, a device, or a toolchain to work on them.
diff --git a/firmware/patternflow/features/README.md b/firmware/patternflow/features/README.md
index 36126b31..85657fcd 100644
--- a/firmware/patternflow/features/README.md
+++ b/firmware/patternflow/features/README.md
@@ -10,13 +10,13 @@ Features that attach to the core without the core knowing they exist.
This directory is the answer to one question: *when the core updates, how
does somebody else's firmware take the update without pain?* Git merges are
only painful when both sides edit the same files — so the rule is that a
-variant edits none.
+firmware built on the core edits none.
-> **A variant adds directories here and two files of its own. It edits
+> **A firmware adds directories here and two files of its own. It edits
> nothing.** `git merge upstream` has nothing to conflict over, ever.
An earlier version of this said "and one line to `features.h`" — which was
-one line in a *core* file, so every variant conflicted on it at every core
+one line in a *core* file, so every firmware conflicted on it at every core
update, forever, on the same line. One line is enough to make taking
updates a chore that eventually stops happening, and a firmware that stops
taking updates is the fork this directory exists to prevent.
@@ -49,14 +49,15 @@ for why any of this exists.
| `features.h` | The default list, and the escape hatch. **The core owns it; no firmware edits it.** |
| `feature_presets.h` | Patterns contributed by features. Same arrangement: `#ifndef PF_FEATURE_PRESETS`, so a firmware can decline them. |
-A variant's `features_local.h` defines `PF_FEATURE_LIST` and `features.h` steps
+A firmware's `features_local.h` defines `PF_FEATURE_LIST` and `features.h` steps
aside. It may add, drop and reorder; `PF_FEATURES_NONE` builds with none at
all:
```c
+#include "osc/feature_osc.h"
#include "audio_in/feature_audio_in.h"
-#define PF_FEATURE_LIST \\
- &PFFeatureOsc::descriptor, \\
+#define PF_FEATURE_LIST \
+ &PFFeatureOsc::descriptor, \
&PFFeatureAudioIn::descriptor
```
@@ -67,7 +68,7 @@ it reaches settings the feature list cannot:
#define PF_VARIANT "audio"
#define PF_VARIANT_VERSION "v0.1.0" // reported in /api/status, worn as
// a badge in the console header
-#define PF_WIFI_TX_POWER WIFI_POWER_17dBm
+#define PF_AUDIO_IN_DRIVES_KNOBS 1 // a feature's own default, changed
#define PF_FEATURE_PRESETS // no presets from features you lack
```
@@ -180,7 +181,7 @@ Then one line in the bundle that carries it — `firmware/bundles//feat
| `audio/` | the WebSocket on port 81 that a client capturing sound elsewhere (the browser extension, the phone app) writes the four knob lanes through, and the switch that lets it (`/api/audio`) | The one with a server of its own, and a row in the device's own menu. The analysis and the mapping happen in the client; no page is served from here. `docs/audio-ws-spec.md`. |
| `audio_in/` | the on-board PDM microphone: FFT on a Core-0 task, four bands onto the knob lanes, `/audio-in` and `/api/audio-in` | The first feature that did not already exist in the core; four hooks, no core edits. Its console page is assembled from the audio extension's mapping editor and the panel's own half in `console/_audio_in_*` (`toolchain/build_audio_in_page.py`, run by `console_pages.py build`). |
| `osc/` | Max / TouchDesigner / Ableton, both directions | The fifth, and the first that did not fit — see below. |
-| `midi/` | MIDI in and out over RTP-MIDI (Wi-Fi) and, in the MIDI edition, USB: CC 20–23 absolute, 24–27 relative, notes, Program Change | Transport-agnostic mapping in `core_midi.h`; the network transport beside it (`core_midi_rtp.h`), and the USB one (`core_midi_usb.h`) that compiles in only when the build's USB port is the OTG stack — a build flag, which is why the MIDI edition has its own env (`bundles/midi/env`). `docs/midi-spec.md`. |
+| `midi/` | MIDI in and out over RTP-MIDI (Wi-Fi) and, in the MIDI bundle, USB: CC 20–23 absolute, 24–27 relative, notes, Program Change | Transport-agnostic mapping in `core_midi.h`; the network transport beside it (`core_midi_rtp.h`), and the USB one (`core_midi_usb.h`) that compiles in only when the build's USB port is the OTG stack — a build flag, which is why the MIDI bundle has its own env (`bundles/midi/env`). `docs/midi-spec.md`. |
| `clock/` | the time cut out of the running pattern, `/clock`, `/api/clock` | Settings in its own NVS namespace, a page with a live preview, and the first user of `composeFrame`: huge anti-aliased digits the pattern shows through (typefaces rasterised offline by `toolchain/build_clock_glyphs.py`, several faces), with what fills the digits and what surrounds them chosen separately. Owns the time zone as a POSIX string with its DST rule; the core keeps the time (`src/core_clock.h`), this shows it. |
| `ble/` | Wi-Fi provisioning from a phone over Improv-BLE | **In no edition.** The lifecycle works on hardware (advertise only while the panel cannot join, release the controller's memory after) but linking it costs ~12 KB of internal RAM and the one phone it was tried with never listed the panel. Opt-in for whoever finishes it; the header records what was measured. |
diff --git a/firmware/patternflow/presets/README.md b/firmware/patternflow/presets/README.md
index eb555371..649962bb 100644
--- a/firmware/patternflow/presets/README.md
+++ b/firmware/patternflow/presets/README.md
@@ -1,12 +1,12 @@
# presets/
-The curated patterns, as C++ headers. `preset_origin.h` is the one compiled into every firmware image — the failsafe a board can always boot into. The rest are built into `.pfm` modules by `firmware/toolchain/make_pack.py` and ship as the **Basics pack** (`web/public/packs/`), which a new board installs in one click. `presetPatterns[]` in [`../pattern_registry.h`](../pattern_registry.h) decides what is compiled in; [`../README.md`](../README.md) explains the registry.
+The curated patterns, as C++ headers. `preset_origin.h` is the one compiled into every firmware image — the failsafe a board can always boot into. The rest are ported to module sources by `firmware/toolchain/port_preset.py`, built into `.pfm` modules by `build_module.py`, bundled by `make_pack.py`, and ship as the **Basics pack** (`web/public/packs/`), which a new board installs in one click. `presetPatterns[]` in [`../pattern_registry.h`](../pattern_registry.h) decides what is compiled in; [`../README.md`](../README.md) explains the registry.
**This set is curated by the maintainer.** Patterns are not contributed here: they are made in the [Pattern Lab](https://patternflow.work/pattern-lab) and published to the Community, where every Patternflow can play them. [`../../CUSTOM_PATTERNS.md`](../../CUSTOM_PATTERNS.md) is the hands-on route for your own board.
## Twins
-Every header here has a JavaScript twin in `web/src/lib/presets/`, and **the JavaScript is the source of truth**: the header is generated from it (the Pattern Lab's *Copy C++ prompt* flow, or `firmware/toolchain/port_preset.py` in the other direction). `firmware/toolchain/check_presets.py` runs in CI and fails when the two sets drift. Its `WEB_ONLY` list names the web presets that deliberately have no header — the ones that missed frame budget on the real ESP32, and the ones posted after the module loader made a compiled-in showcase unnecessary; delete an entry there the day its header lands. `preset_calib.h` is the display-calibration pattern and has no web twin on purpose.
+Every header here has a JavaScript twin in `web/src/lib/presets/`, and **the JavaScript is the source of truth**: the header is generated from it (the Pattern Lab's *Copy C++ prompt* flow), and `firmware/toolchain/port_preset.py` takes the header on from there, into a module. `firmware/toolchain/check_presets.py` runs in CI and fails when the two sets drift. Its `WEB_ONLY` list names the web presets that deliberately have no header — the ones that missed frame budget on the real ESP32, and the ones posted after the module loader made a compiled-in showcase unnecessary; delete an entry there the day its header lands. `preset_calib.h` is the display-calibration pattern and has no web twin on purpose.
## Names
diff --git a/firmware/patternflow/src/fonts/README.md b/firmware/patternflow/src/fonts/README.md
index 77324eba..1689e375 100644
--- a/firmware/patternflow/src/fonts/README.md
+++ b/firmware/patternflow/src/fonts/README.md
@@ -2,18 +2,13 @@
From [trip5/Matrix-Fonts](https://github.com/trip5/Matrix-Fonts) (CC-BY).
-| File | Use |
-|------|-----|
-| `MatrixLight8X.h` | SELECT titles, MQTT message banners |
-| `MatrixLight6.h` | NETWORK / OSC / AUD / KNOB MAP / chrome hints |
+**Optional — not the fonts the panel uses by default.** The panel's own screens draw with the Adafruit GFX built-in 5×7 (pattern names on SELECT), TomThumb (NETWORK rows, hints, status lines) and Org_01 (banners and titles); `../core_ui_fonts.h` binds those three roles. This pair is kept here for anyone who prefers it — the fonts Simone Majocchi's tree uses — and is switched in from `patternflow_secrets.h` or an edition's `overrides.h`, with the block at the bottom of `../core_ui_fonts.h`.
-Regenerate after updating a `.bdf`:
+| File | Role it can take |
+|------|-----|
+| `MatrixLight8X.h` | SELECT titles, banners (`PF_UI_FONT_SELECT`, `PF_UI_FONT_TITLE`) |
+| `MatrixLight6.h` | NETWORK / KNOB MAP / chrome hints (`PF_UI_FONT_CHROME`) |
-```bash
-python toolchain/bdf_to_gfxfont.py src/fonts/MatrixLight8X.bdf \
- -o src/fonts/MatrixLight8X.h --name MatrixLight8X
-python toolchain/bdf_to_gfxfont.py src/fonts/MatrixLight6.bdf \
- -o src/fonts/MatrixLight6.h --name MatrixLight6
-```
+The `.bdf` sources sit beside the headers. The `toolchain/bdf_to_gfxfont.py` this page used to name for regenerating them is not in this repository, so regenerating a header after editing a `.bdf` needs a BDF → Adafruit GFXfont converter of your own.
ASCII `0x20`–`0x7E` only (overlays run text through `asciiFold`).
diff --git a/firmware/toolchain/README.md b/firmware/toolchain/README.md
index 8ca9933a..729aeced 100644
--- a/firmware/toolchain/README.md
+++ b/firmware/toolchain/README.md
@@ -2,10 +2,12 @@
The repository-level scripts around the firmware: what builds a module or a pack, what assembles a console page, and the `check_*.py` that CI runs. Each script's docstring is its manual (`python firmware/toolchain/