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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/guide_stuck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ body:
id: where
attributes:
label: Where in the guide
description: The guide (Build, Play or Make), the chapter and the step. Filled in for you if you came from the guide.
description: The guide (Build, Play, Make or Audio), the chapter and the step. Filled in for you if you came from the guide.
placeholder: "Play · 01 Flash, step 5 — Hold BOOT, tap RST, let go."
validations:
required: true
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/console-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ jobs:
python-version: "3.x"
- name: Headers match console/*.html
run: python firmware/toolchain/console_pages.py check
# audio-in.html is itself generated, from the browser extension's mapping
# editor. The step above proves the header matches the page; this proves
# the page matches the editor it was assembled from.
- name: audio-in.html matches the extension's editor
# audio-in.html is itself generated: from the browser extension's mapping
# editor and the console/_audio_in* files. The step above proves the
# header matches the page; this proves the page is what those sources
# assemble to, and that it is inside its size budget (BUDGET in the
# script: the stamped page, gzip -9, as the panel sends it). The step
# above reports both too; this one keeps them a line of their own.
- name: audio-in.html matches its sources and is inside its size budget
run: python firmware/toolchain/build_audio_in_page.py --check
12 changes: 12 additions & 0 deletions .github/workflows/firmware-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,15 @@ jobs:
# of the split is that the blast radius differs.
- name: Internal RAM this composition has already spent
run: python firmware/toolchain/check_footprint.py --only ${{ matrix.composition }}

# The one check of the pattern SDK that needs the Xtensa compiler, so it
# runs here, where the build above has just installed one, and once
# rather than five times. abi/pf_libm.h puts inline code behind floorf,
# fmodf and five more in every .pfm; firmware-checks.yml proves the
# arithmetic on the runner's own CPU, and cannot compile the half of the
# header that exists only in a module build. This builds a small module
# with build_module.py and reads the object: each spelling inline, the
# library call left only as its fallback, no fmaf import. A few seconds.
- name: Module SDK's libm is inline in a module the real compiler built
if: matrix.composition == 'default'
run: python firmware/toolchain/check_module_libm.py
15 changes: 14 additions & 1 deletion .github/workflows/firmware-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ name: Core knows no features
# what every pattern built afterwards renders — and reaches only newly built
# modules, since an installed .pfm carries its own compiled copy.
#
# check_libm.py — abi/pf_libm.h, which puts inline FPU code behind floorf,
# ceilf, truncf, roundf, fminf, fmaxf and fmodf in every .pfm so that a
# pattern nobody edits stops calling into the firmware's libm per pixel. The
# claim is that each of the seven returns exactly what libm returns, so this
# is equality of bits, not a band: all 2^32 floats through the one-argument
# functions and about three billion comparisons through the rest, against
# this runner's libm. One mismatch fails. It is the slow step here - a
# minute or two. (The half of that header only a module build compiles is
# read out of a built module by check_module_libm.py, in firmware-build.yml,
# where there is an Xtensa compiler.)
#
# check_parser.py — the vendored HTTP request parser, the real Parsing.cpp
# against a scripted socket and a fake clock. It runs on pf-net, where a loop
# that waits for the peer without sleeping starves IDLE0 and the Core-0
Expand Down Expand Up @@ -93,11 +104,13 @@ jobs:
run: python firmware/toolchain/check_oe.py --sanitize
- name: Pattern SDK maths, colour and noise still measure the same
run: python firmware/toolchain/check_math.py --sanitize
- name: Module SDK's inline floorf/fmodf return what libm returns, bit for bit
run: python firmware/toolchain/check_libm.py --sanitize
- name: Thumbnail mailbox ownership and invalidation
run: python firmware/toolchain/check_thumbs.py --sanitize
- name: Wi-Fi and name-service recovery
run: python firmware/toolchain/check_network.py --sanitize
- name: Runtime admission and frame-boundary lifecycle
- name: Runtime admission, resident modules and frame-boundary lifecycle
run: python firmware/toolchain/check_runtime.py --sanitize
- name: Crash record tells this reset's dump from an older one
run: python firmware/toolchain/check_crash.py --sanitize
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ out/
# Local staging for a release upload (bins duplicated from the flasher folder).
firmware/release/
firmware/patternflow/patternflow_secrets.h
firmware/patternflow/patternflow_secrets.h.shelf-aside

# KiCad 자동 생성/백업
*.kicad_prl
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ It is standalone (not Eurorack), powered from a 5 V power bank through a screw t
- `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.
- `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/<name>/<name>_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` and `/guide/make`, 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.
- `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.
- `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`.
- `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.

Expand All @@ -33,8 +33,8 @@ It is standalone (not Eurorack), powered from a 5 V power bank through a screw t
- KiCad exports: Export Gerbers from `hardware/pcb/kicad/patternflow.kicad_pcb`. Export STLs from `hardware/case/source/patternflow_case.blend`.

## Versioning
- Project: v3.10.5 (current, released 2026-10-02), using unified semantic versioning across firmware, hardware, web, and docs — the firmware reports it as `PF_IMPROV_FW_VERSION` in `net_config.h`, and `CHANGELOG.md` gets a section per release. Current hardware is the v3.9 board — the v3.0 board with the USB-C footprint removed, same enclosure, pin map and guide; every v3.x release has been firmware/web on it.
- Editions carry their own version line, independent of the project version (`PF_VARIANT_VERSION` in `firmware/bundles/<name>/overrides.h`): Audio v0.6.5, Performance v0.4.0 at the time of writing. The shelf (`web/src/app/editions/editions-data.ts`) and the flasher manifest (`web/public/flash/manifest.json`) name the images that are live; only those images are kept in `web/public/flash/bin/` — older ones live on their release tags. The one exception is a try-out image: a frozen copy of a composition that is not on the shelf, named by the features page (`tryOut` in `web/src/app/features/features-data.ts`) so people can install it to try, never bumped with the core, and retired only by a newer try-out of the same name. `clock` and `midi` are bundles the tree keeps building (CI compiles every composition) but does not put on the shelf: the clock is a feature, and the MIDI bundle is the composition that proves the USB-OTG build; each has a try-out image (`clock-v0.1.5`, `midi-v0.1.0`). Neither is in the `EDITIONS` tuples of `release.py`/`check_versions.py`; an edition joins those, the cards and the line above only when it goes on the shelf, and its maintainer cuts it with `release.py edition <name> vA.B.C` (docs/RELEASING.md).
- Project: v3.11.0 (current, released 2026-10-04), using unified semantic versioning across firmware, hardware, web, and docs — the firmware reports it as `PF_IMPROV_FW_VERSION` in `net_config.h`, and `CHANGELOG.md` gets a section per release. Current hardware is the v3.9 board — the v3.0 board with the USB-C footprint removed, same enclosure, pin map and guide; every v3.x release has been firmware/web on it.
- Editions carry their own version line, independent of the project version (`PF_VARIANT_VERSION` in `firmware/bundles/<name>/overrides.h`): Audio v0.7.0, Performance v0.4.0 at the time of writing. The shelf (`web/src/app/editions/editions-data.ts`) and the flasher manifest (`web/public/flash/manifest.json`) name the images that are live; only those images are kept in `web/public/flash/bin/` — older ones live on their release tags. The one exception is a try-out image: a frozen copy of a composition that is not on the shelf, named by the features page (`tryOut` in `web/src/app/features/features-data.ts`) so people can install it to try, never bumped with the core, and retired only by a newer try-out of the same name. `clock` and `midi` are bundles the tree keeps building (CI compiles every composition) but does not put on the shelf: the clock is a feature, and the MIDI bundle is the composition that proves the USB-OTG build; each has a try-out image (`clock-v0.1.5`, `midi-v0.1.0`). Neither is in the `EDITIONS` tuples of `release.py`/`check_versions.py`; an edition joins those, the cards and the line above only when it goes on the shelf, and its maintainer cuts it with `release.py edition <name> vA.B.C` (docs/RELEASING.md).
- Firmware source lives in `firmware/patternflow/`; use release tags for versioning instead of encoding the release in the folder name. Tags are `vX.Y.Z`.
- Conventions: filenames lowercase with underscores; commit messages start with the area (`firmware:`, `web:`, `docs:`, `hardware:`) then a short present-tense summary.

Expand Down
Loading
Loading