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: 2 additions & 0 deletions BUILD_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

This guide walks you through building a Patternflow on the v3 board from scratch. v3.9 is the current revision; a v3.0 board follows the same steps (its USB-C footprint stays unpopulated, see §2). No prior soldering experience needed — every joint is big, forgiving through-hole, and the board was deliberately kept that simple so a first-time solderer can finish it.

> 🧭 **The same build, in 3D.** [patternflow.work/guide/build](https://patternflow.work/guide/build) walks through this guide step by step on a 3D Patternflow: the parts list, the soldering order with each part going into its real holes, the case, the wiring, first light. This document stays the written reference; follow whichever suits you, or both.

**Estimated build time:** about 1 hour of hands-on work (~30 min soldering + ~30 min assembly), plus ~10 hours of 3D printing. Parts shipping typically takes ~2 weeks — order first, build later.

**What's new in v3** (vs. v2.x):
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ All notable changes to Patternflow will be documented in this file, newest first

### Web

- **A guide you can play along with, in three parts, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). `/guide` is where you pick one by where your Patternflow is, each with its chapters, in order. **Build**, at [/guide/build](https://patternflow.work/guide/build), is for soldering one from bare parts: 01 Gather (the parts list is `bom_v3.9.csv` itself), 02 Order & print, 03 Solder, 04 Into the case, 05 Wire & power, 06 Firmware (which hands over to Play's 01 Flash) and 07 Check & close, each step played on the real v3.9 board and case in 3D, in the order the build happens; the board's known issues stay in `BUILD_GUIDE.md` §10, linked. **Play**, at [/guide/play](https://patternflow.work/guide/play), is the first hour with a built one, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. **Make**, at [/guide/make](https://patternflow.work/guide/make), is 01 Community and 02 Pattern Lab, done by hand: on a big enough screen your own Pattern Lab, a practice community (placeholder patterns, connected to nothing) and a practice AI with set answers sit beside the steps, and a pointer shows each move and waits for yours; phones get the real screens. Sound, MIDI & OSC, MQTT, Clock, Performance and Editions join Make as they are written. Each guide numbers its own chapters from 01. Links into the first version, when Play was `/guide` itself (`/guide#flash`, `/guide#knobs-3`), land on the same step under `/guide/play`. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says which guide, chapter and step (`guide_stuck.yml`).
- **A guide you can play along with, in three parts, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). `/guide` is where you pick one by where your Patternflow is, each with its chapters, in order. **Build**, at [/guide/build](https://patternflow.work/guide/build), is for soldering one from bare parts: 01 Gather (the parts list is `bom_v3.9.csv` itself), 02 Order & print, 03 Solder, 04 Into the case, 05 Wire & power, 06 Firmware (which hands over to Play's 01 Flash) and 07 Check & close, each step played on the real v3.9 board and case in 3D, in the order the build happens; the board's known issues stay in `BUILD_GUIDE.md` §10, linked. **Play**, at [/guide/play](https://patternflow.work/guide/play), is the first hour with a built one, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. **Make**, at [/guide/make](https://patternflow.work/guide/make), is 01 Community and 02 Pattern Lab, done by hand: on a big enough screen your own Pattern Lab, a practice community (placeholder patterns, connected to nothing) and a practice AI with set answers sit beside the steps, and a pointer shows each move and waits for yours; phones get the real screens. Sound, MIDI & OSC, MQTT, Clock, Performance and Editions join Make as they are written. Each guide numbers its own chapters from 01. Links into the first version, when Play was `/guide` itself (`/guide#flash`, `/guide#knobs-3`), land on the same step under `/guide/play`. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says which guide, chapter and step (`guide_stuck.yml`). The 3D stage is one continuous world: moving between the hub, Build and Play is a scene rather than a page load, and pointing at a guide on the hub previews it on the device (Build comes apart, Play turns a knob, Make redraws the panel). The LED panel lights the room with the pattern it is playing, and the page's accent follows that colour. Pretendard is now served in four script-range files, so an English page no longer downloads the Hangul glyphs (a Korean page renders identically). README, BUILD_GUIDE, PATTERN_GUIDE and the assembly map link to the guide.
- **The community's Performances note says .pfs**, which is what the Director saves, instead of "Save-JSON".
- **A moderator can take a pattern or deck off the wall without deleting it.** Somebody else's public pattern or deck now offers moderators *Make private*, with an optional reason: the softer removal, for things like the same pattern posted a fifth time. Nothing about the work changes and its author keeps it — they can still open it, edit it, take it into the lab — it just stops being shown to anyone else. On a pattern the reason is posted underneath as the moderator's comment, where the author can answer it; either way the author is told in their alerts. While the take-down stands the author cannot make it public again (a take-down its subject can undo in one click is a request); *Restore to public* puts it back exactly as it was. A moderator still cannot publish what an author made private themselves. A deck slot left empty by a take-down reads "made private by a moderator", not "by its author". Moderators can now comment on private patterns and keep their alerts about them, so the thread under a take-down works both ways. Migration `0025` adds `hidden_at` to patterns and decks; `check:hidemod` drives it all through the real routes.
- **One press of Publish is one post.** A double-click on *Publish to the wall*, or a mouse switch that bounces, could put the same pattern on the wall twice in the same second ([#455](https://github.com/engmung/Patternflow/issues/455)). The button's disabled state was React state, a render too late for the second click, and after a successful publish it came back enabled while the page changed. The publish, thread, reply, deck, header, performance and report forms now refuse a second press in the same tick (`useSubmitLatch`), and a published pattern keeps its button down until the page changes; a thread whose files failed to attach offers *Open the thread* instead of posting it again. Behind them the server answers an identical submission from the same account within a minute with the post it already made, looked up and inserted in one synchronous transaction so two requests arriving together cannot both get through. `check:publish` fires two identical publishes into the route at the same instant; a version that awaits between the lookup and the insert fails it.
Expand Down
4 changes: 4 additions & 0 deletions PATTERN_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Lab, verify them on real hardware, and share them back.**
Assembly and the first flash are [BUILD_GUIDE.md](BUILD_GUIDE.md). This guide
is what comes after.

Rather do it than read it? [patternflow.work/guide/make](https://patternflow.work/guide/make) is the
same loop as a hands-on tutorial: a practice community, your real Pattern Lab
in a window, and a practice AI to take the two prompts.

- [0. Before you start](#0-before-you-start)
- [1. One concept: a pattern is a small file](#1-one-concept-a-pattern-is-a-small-file)
- [2. A look around the community](#2-a-look-around-the-community)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ What a knob does to a running pattern is the pattern's own decision, so the same

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.
[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.

## On the device

Expand Down Expand Up @@ -187,6 +187,7 @@ Patternflow is built around a standalone ESP32-S3 driving a HUB75 RGB LED matrix
| `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): 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)
Expand Down
2 changes: 1 addition & 1 deletion docs/assembly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Build those two, flash the firmware, and your Patternflow is alive.
| --- | --- | --- | --- |
| [3D printed enclosure](../../hardware/case/README.md) | [Custom PCB, hand-soldered](../../hardware/pcb/README.md) | [Browser flash](../../BUILD_GUIDE.md#8-firmware) · [your own patterns](../../PATTERN_GUIDE.md) | **Current — fully documented** |

This is the route [BUILD_GUIDE.md](../../BUILD_GUIDE.md) walks start to finish: PLA parts on any 256 mm-bed FDM printer, the hand-soldered v3.9 board (all through-hole — deliberately kept first-timer easy), and firmware flashed from the browser. Two ordering shortcuts are wired straight to it:
This is the route [BUILD_GUIDE.md](../../BUILD_GUIDE.md) walks start to finish, and the one the [interactive build guide](https://patternflow.work/guide/build) shows in 3D: PLA parts on any 256 mm-bed FDM printer, the hand-soldered v3.9 board (all through-hole — deliberately kept first-timer easy), and firmware flashed from the browser. Two ordering shortcuts are wired straight to it:

- **PCB** — the [PCBWay shared project](https://www.pcbway.com/project/shareproject/Patternflow_An_LED_synthesizer_776d796c.html): no Gerber upload, and ordering there supports Patternflow development.
- **Case** — the [MakerWorld listing](https://makerworld.com/en/models/3072492-patternflow-open-source-led-synthesizer-case#profileId-3459015): tuned one-click print profiles for Bambu printers (STLs in `hardware/case/` for everyone else).
Expand Down
Binary file added web/public/fonts/Pretendard-Bold.hangul.woff2
Binary file not shown.
Binary file added web/public/fonts/Pretendard-Bold.latin.woff2
Binary file not shown.
Binary file added web/public/fonts/Pretendard-Bold.other.woff2
Binary file not shown.
Binary file added web/public/fonts/Pretendard-Bold.symbols.woff2
Binary file not shown.
Binary file added web/public/fonts/Pretendard-Regular.hangul.woff2
Binary file not shown.
Binary file not shown.
Binary file added web/public/fonts/Pretendard-Regular.other.woff2
Binary file not shown.
Binary file added web/public/fonts/Pretendard-Regular.symbols.woff2
Binary file not shown.
Binary file added web/public/guide/look/pcb-v39-copper.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 60 additions & 2 deletions web/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,18 +1,76 @@
@import "tailwindcss";

/* Pretendard, each weight in four parts. The whole file is 770–790 KB, most
of it Hangul, and a page fetched all of it for one glyph Inter lacks —
an English page for the arrow in a link. The parts are the same glyphs,
outlines and metrics, cut by script (pyftsubset on the files beside
them); their ranges cover all of Unicode between them, so whatever the
whole file drew, one part still draws. A page now fetches only the parts
it has characters for: an arrow costs 38 KB, and the Hangul comes with
the first Korean word. */
@font-face {
font-family: 'Pretendard';
font-weight: 400;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Regular.woff2') format('woff2');
src: url('/fonts/Pretendard-Regular.latin.woff2') format('woff2');
unicode-range: U+0000-052F, U+1E00-1EFF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 400;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Regular.hangul.woff2') format('woff2');
unicode-range: U+1100-11FF, U+3130-318F, U+A960-A97F, U+AC00-D7FF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 400;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Regular.symbols.woff2') format('woff2');
unicode-range: U+2000-2BFF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 400;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Regular.other.woff2') format('woff2');
unicode-range: U+0530-10FF, U+1200-1DFF, U+1F00-1FFF, U+2C00-312F, U+3190-A95F, U+A980-ABFF, U+D800-10FFFF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 700;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Bold.latin.woff2') format('woff2');
unicode-range: U+0000-052F, U+1E00-1EFF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 700;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Bold.hangul.woff2') format('woff2');
unicode-range: U+1100-11FF, U+3130-318F, U+A960-A97F, U+AC00-D7FF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 700;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Bold.symbols.woff2') format('woff2');
unicode-range: U+2000-2BFF;
}
@font-face {
font-family: 'Pretendard';
font-weight: 700;
font-style: normal;
font-display: swap;
src: url('/fonts/Pretendard-Bold.woff2') format('woff2');
src: url('/fonts/Pretendard-Bold.other.woff2') format('woff2');
unicode-range: U+0530-10FF, U+1200-1DFF, U+1F00-1FFF, U+2C00-312F, U+3190-A95F, U+A980-ABFF, U+D800-10FFFF;
}

:root {
Expand Down
13 changes: 13 additions & 0 deletions web/src/app/guide/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ReactNode } from "react";
import GuideWorld from "@/components/guide/world/GuideWorld";

// Everything under /guide — the hub, Build, Play and Make, in both languages
// — is one world: this layout mounts the guide's stage once (GuideWorld), and
// it stays up while the reader moves between the pages. A layout is not
// rendered again on a navigation inside it, so the 3D canvas, the camera, the
// simulated board and the loaded models carry over; each page only says
// which one it is (components/guide/world/usePage.ts).

export default function GuideLayout({ children }: { children: ReactNode }) {
return <GuideWorld>{children}</GuideWorld>;
}
9 changes: 5 additions & 4 deletions web/src/components/guide/CommunityShots.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
box-shadow:
inset 0 0 0 1px rgba(0, 0, 0, 0.55),
0 0 0 1px rgba(0, 0, 0, 0.55),
0 0 18px rgba(255, 106, 61, 0.45);
0 0 18px rgb(var(--g-led-rgb, 255 106 61) / 0.45);
pointer-events: none;
transition:
left 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
Expand Down Expand Up @@ -93,13 +93,13 @@
box-shadow:
inset 0 0 0 1px rgba(0, 0, 0, 0.55),
0 0 0 1px rgba(0, 0, 0, 0.55),
0 0 18px rgba(255, 106, 61, 0.45);
0 0 18px rgb(var(--g-led-rgb, 255 106 61) / 0.45);
}
50% {
box-shadow:
inset 0 0 0 1px rgba(0, 0, 0, 0.55),
0 0 0 1px rgba(0, 0, 0, 0.55),
0 0 26px 3px rgba(255, 106, 61, 0.6);
0 0 26px 3px rgb(var(--g-led-rgb, 255 106 61) / 0.6);
}
}

Expand Down Expand Up @@ -137,7 +137,7 @@
inset: -6px -3px;
}
.dot[data-done="1"] {
border-color: rgba(255, 106, 61, 0.35);
border-color: rgb(var(--g-led-rgb, 255 106 61) / 0.35);
color: var(--g-ink-2, rgba(239, 233, 221, 0.78));
}
.dot[data-on="1"] {
Expand Down Expand Up @@ -168,6 +168,7 @@
margin-top: 10px;
}
.text {
text-wrap: pretty;
grid-area: 1 / 1;
margin: 0;
font-size: 13px;
Expand Down
17 changes: 14 additions & 3 deletions web/src/components/guide/ConsoleWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -634,10 +634,21 @@ export default function ConsoleWindow({ variant, page = "home", lang }: { varian
return () => window.removeEventListener("keydown", key);
}, [lift, close]);

const closed = useCallback(() => {
setLift(null);
const closed = useCallback(() => setLift(null), []);
// Focus goes back to the button that opened the window — once the window is
// gone. Asked for while it was still mounted, its focus trap (Lifted's
// `keep`) took the focus straight back to its own Close button, the window
// then went, and the keyboard was left on <body>, at the top of the page.
const wasLifted = useRef(false);
useEffect(() => {
if (lift) {
wasLifted.current = true;
return;
}
if (!wasLifted.current) return;
wasLifted.current = false;
opener.current?.focus({ preventScroll: true });
}, []);
}, [lift]);

const host = HOST[variant];
const view = VIEW[variant];
Expand Down
Loading
Loading