From 6aba306408af90dea268114a11c87769be05a380 Mon Sep 17 00:00:00 2001 From: engmung <122682380+engmung@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:15:19 +0900 Subject: [PATCH 1/3] web: the guide has a fourth part, Audio - the edition, a browser tab, the microphone, MIDI and your DAW /guide/audio (and /ko): getting the Audio edition onto the board first, then three sections with names and no order between them. - A browser tab: the extension, its popup's states, and the extension's real editor running in the page in its demo mode, driving the 3D board's knobs; a hand on a knob wins for five seconds, as on the board. - The microphone: what to buy, five leads on four places (GND takes two), the switch, and the /audio-in page. AUDIO_GUIDE.md and the editions card said four leads and now agree. - MIDI and your DAW: connecting with rtpMIDI, Ableton's Remote and mapping, channel 1 and the numbers each way, the console's MIDI page. - OSC, the phone app and the Director's .mid export are links at the end. The hub has four cards. The simulated board learns the Audio edition (OSC and AUD rows on NETWORK, toggled by K2/K3) and lanes as an overlay that never writes into the stored knob values. editor_demo.py generates web/public/guide/audio-editor from the extension's editor. The console's MIDI page no longer says MIDI has a row on the NETWORK screen. Checked: typecheck, lint, 390 tests, next build, build.sh all and the footprint pins; walked in headless Chrome at 1440 and 390 wide, English and Korean. Not checked on a real phone. Co-Authored-By: Claude Opus 5.5 --- .github/ISSUE_TEMPLATE/guide_stuck.yml | 2 +- AGENTS.md | 2 +- AUDIO_GUIDE.md | 21 +- CHANGELOG.md | 9 + README.md | 4 +- SUPPORT.md | 2 +- docs/REPOSITORY.md | 4 +- docs/midi-spec.md | 4 +- firmware/bundles/README.md | 2 +- firmware/patternflow/console/midi.html | 8 +- .../features/audio_in/core_audio_in_map.h | 2 +- .../features/midi/core_midi_http.h | 3 +- .../patternflow/features/midi/midi_index.h | 383 +++--- firmware/toolchain/editor_demo.py | 334 +++++ web/eslint.config.mjs | 4 + web/public/guide/audio-editor/bridge.js | 187 +++ .../guide/audio-editor/editor-adapter.js | 203 +++ web/public/guide/audio-editor/editor.css | 333 +++++ web/public/guide/audio-editor/editor.js | 1177 +++++++++++++++++ web/public/guide/audio-editor/index.html | 185 +++ web/public/guide/audio-editor/mapping.js | 236 ++++ .../guide/audio/audio-in-listening.webp | Bin 0 -> 14526 bytes web/public/guide/audio/audio-in-mapping.webp | Bin 0 -> 7608 bytes web/public/guide/audio/audio-in-nomic.webp | Bin 0 -> 13916 bytes web/public/guide/audio/audio-in-off.webp | Bin 0 -> 15810 bytes web/public/guide/audio/live-remote.webp | Bin 0 -> 7968 bytes web/public/guide/audio/mic-part.webp | Bin 0 -> 22818 bytes web/public/guide/audio/mic-seated.webp | Bin 0 -> 67640 bytes web/public/guide/audio/mic-wiring.webp | Bin 0 -> 39598 bytes web/public/guide/audio/midi-knobs.webp | Bin 0 -> 23314 bytes web/public/guide/audio/midi-session.webp | Bin 0 -> 18742 bytes web/public/guide/audio/popup-audoff.webp | Bin 0 -> 3290 bytes web/public/guide/audio/popup-idle.webp | Bin 0 -> 2856 bytes web/public/guide/audio/popup-live.webp | Bin 0 -> 2544 bytes web/public/guide/audio/popup-nosound.webp | Bin 0 -> 2576 bytes web/public/guide/audio/rtpmidi-1.webp | Bin 0 -> 9558 bytes web/public/guide/audio/rtpmidi-2.webp | Bin 0 -> 10000 bytes web/public/guide/audio/rtpmidi-3.webp | Bin 0 -> 13540 bytes web/public/guide/audio/rtpmidi-4.webp | Bin 0 -> 11594 bytes web/public/llms.txt | 1 + web/src/app/editions/editions-data.ts | 6 +- web/src/app/guide/audio/ko/page.tsx | 13 + web/src/app/guide/audio/page.tsx | 13 + web/src/app/guide/ko/page.tsx | 2 +- web/src/app/guide/layout.tsx | 2 +- web/src/app/guide/page.tsx | 2 +- web/src/app/sitemap.ts | 7 +- .../components/guide/AudioShots.module.css | 472 +++++++ web/src/components/guide/AudioShots.tsx | 715 ++++++++++ .../components/guide/EditorWindow.module.css | 389 ++++++ web/src/components/guide/EditorWindow.tsx | 448 +++++++ web/src/components/guide/Extras.tsx | 31 +- web/src/components/guide/Guide.module.css | 293 +++- web/src/components/guide/GuideExperience.tsx | 85 +- web/src/components/guide/GuideHub.tsx | 12 +- web/src/components/guide/Hub.module.css | 48 +- web/src/components/guide/checks.ts | 15 +- web/src/components/guide/copy.ts | 54 +- web/src/components/guide/copy/audio.ts | 581 ++++++++ web/src/components/guide/copy/hub.ts | 8 +- web/src/components/guide/copy/make.ts | 15 +- web/src/components/guide/pages.test.ts | 70 +- web/src/components/guide/pages.ts | 34 +- web/src/components/guide/scenes.ts | 33 +- web/src/components/guide/scenes/audio.ts | 138 ++ web/src/components/guide/stage/Device.tsx | 22 +- web/src/components/guide/stage/Fx.tsx | 53 +- .../components/guide/stage/GuideCanvas.tsx | 30 +- web/src/components/guide/stage/KitFx.tsx | 11 +- web/src/components/guide/store.ts | 6 +- web/src/components/guide/ui/panelTint.ts | 10 +- web/src/lib/guide/deviceSim.test.ts | 339 +++++ web/src/lib/guide/deviceSim.ts | 130 +- 73 files changed, 6850 insertions(+), 343 deletions(-) create mode 100644 firmware/toolchain/editor_demo.py create mode 100644 web/public/guide/audio-editor/bridge.js create mode 100644 web/public/guide/audio-editor/editor-adapter.js create mode 100644 web/public/guide/audio-editor/editor.css create mode 100644 web/public/guide/audio-editor/editor.js create mode 100644 web/public/guide/audio-editor/index.html create mode 100644 web/public/guide/audio-editor/mapping.js create mode 100644 web/public/guide/audio/audio-in-listening.webp create mode 100644 web/public/guide/audio/audio-in-mapping.webp create mode 100644 web/public/guide/audio/audio-in-nomic.webp create mode 100644 web/public/guide/audio/audio-in-off.webp create mode 100644 web/public/guide/audio/live-remote.webp create mode 100644 web/public/guide/audio/mic-part.webp create mode 100644 web/public/guide/audio/mic-seated.webp create mode 100644 web/public/guide/audio/mic-wiring.webp create mode 100644 web/public/guide/audio/midi-knobs.webp create mode 100644 web/public/guide/audio/midi-session.webp create mode 100644 web/public/guide/audio/popup-audoff.webp create mode 100644 web/public/guide/audio/popup-idle.webp create mode 100644 web/public/guide/audio/popup-live.webp create mode 100644 web/public/guide/audio/popup-nosound.webp create mode 100644 web/public/guide/audio/rtpmidi-1.webp create mode 100644 web/public/guide/audio/rtpmidi-2.webp create mode 100644 web/public/guide/audio/rtpmidi-3.webp create mode 100644 web/public/guide/audio/rtpmidi-4.webp create mode 100644 web/src/app/guide/audio/ko/page.tsx create mode 100644 web/src/app/guide/audio/page.tsx create mode 100644 web/src/components/guide/AudioShots.module.css create mode 100644 web/src/components/guide/AudioShots.tsx create mode 100644 web/src/components/guide/EditorWindow.module.css create mode 100644 web/src/components/guide/EditorWindow.tsx create mode 100644 web/src/components/guide/copy/audio.ts create mode 100644 web/src/components/guide/scenes/audio.ts create mode 100644 web/src/lib/guide/deviceSim.test.ts diff --git a/.github/ISSUE_TEMPLATE/guide_stuck.yml b/.github/ISSUE_TEMPLATE/guide_stuck.yml index 9249797d9..ff4ae7ccb 100644 --- a/.github/ISSUE_TEMPLATE/guide_stuck.yml +++ b/.github/ISSUE_TEMPLATE/guide_stuck.yml @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 4181b5dfd..0f2472b8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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//_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. diff --git a/AUDIO_GUIDE.md b/AUDIO_GUIDE.md index 7893e9270..a223be388 100644 --- a/AUDIO_GUIDE.md +++ b/AUDIO_GUIDE.md @@ -6,12 +6,15 @@ know where knob values come from. There are two main ways in, and this guide covers both: the **Chrome extension** (any browser tab becomes the source) and the **on-board -microphone** (a $5 part and four solder joints, and the panel hears the +microphone** (a $5 part and five thin leads, and the panel hears the room with no computer involved). A phone app exists too — it's covered briefly at the end — and if you work in a DAW, the panel is also a **MIDI device** ([its own section](#midi--the-panel-as-a-midi-port)) and speaks OSC ([here](#osc--ableton-today-anything-tomorrow)). +The same ground step by step, on a 3D Patternflow in the browser: +[patternflow.work/guide/audio](https://patternflow.work/guide/audio). + Like the [Feature Guide](FEATURE_GUIDE.md), this page ends with a section written for AI coding agents; everything above it is for people. @@ -92,7 +95,7 @@ until you solder one on and switch it on. | Part | What to look for | Notes | | --- | --- | --- | | PDM MEMS microphone breakout | **Adafruit PDM MEMS Microphone Breakout #3492** (MP34DT01-M), or any clone whose pin row reads **3V · GND · SEL · CLK · DAT** | ~$5. This is the one in the photo. | -| Hookup wire | 4 thin leads, ~10 cm | Shorter is better — see routing note below. | +| Hookup wire | 5 thin leads, ~10 cm | Shorter is better — see routing note below. | **Don't buy these instead:** an **INMP441** or other standard I²S mic (it needs three signal pins — this board has exactly two free), or an analog @@ -100,7 +103,7 @@ electret module (no ADC pin is free at all). It must be **PDM**. ### Wiring -Four leads, five pads: +Five leads, four places on the DevKit (GND takes two): ``` mic breakout ESP32-S3 DevKit @@ -111,11 +114,11 @@ Four leads, five pads: │ CLK ───────┼──┼───────────► TX (GPIO 43) │ DAT ───────┼──┼───────────► RX (GPIO 44) └──────────────┘ │ - └──────────► GND ← SEL goes to ground + └──────────► G (GND) ← SEL goes to ground: the same GND pin ``` -- **Don't forget SEL → GND** — jumper it to the breakout's own GND pad so it - shares the same lead. It selects the LEFT channel, the slot a mono read +- **Don't forget SEL → GND** — it is a lead of its own, soldered to the same + GND pin as the GND lead. It selects the LEFT channel, the slot a mono read uses; tied high, the mic reads silence while looking perfectly healthy. - The DevKit silkscreen says **TX / RX**, not 43/44 — those are the ones. They're free because the console talks over native USB, and they are the @@ -127,8 +130,8 @@ the main board: 1. **Pull the DevKit out** of its sockets (straight up). 2. Solder the thin wires on the **top side**, where the header pins poke - through the module — four spots: TX, RX, 3V3, GND (that's what the photo - shows). + through the module — five wires on four spots: TX, RX, 3V3, and GND + twice (that's what the photo shows). 3. Route the wires out the top and plug the DevKit back in. Fully reversible — to undo it, just remove the wires. Two cautions: while @@ -154,7 +157,7 @@ phone app*, is the other input: the same switch as `AUD` on the panel's NETWORK screen.) The same box editor from the extension lives on this page, under -**Microphone mapping**, in the console's own look (light, or dark from the +**Mapping**, in the console's own look (light, or dark from the toggle in the header) — same boxes K1 to K4, same curves, same Preview. Its level axis is the microphone's, about −45 to +2 dB, and its frequencies stop at 8 kHz. Changes save to the panel as you make them; mic settings are stored diff --git a/CHANGELOG.md b/CHANGELOG.md index f76f81c2e..ccb17f2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ All notable changes to Patternflow will be documented in this file, newest first - **The Audio page is a console page now: it says who has the knobs, and it saves only what you changed** (Audio edition). `/audio-in` was the one page the light console left behind: its own dark palette, 12 px type, square corners, a second brand line. It opens light like the rest now, with the console's buttons, chips and list rows, and it does not scroll sideways on a phone (checked at 390 and 320 px). Above the editor is a **Sources** list. *Microphone* has a chip that reads `off`, `starting`, `listening`, `not detected` (nothing on the data pin; the page says to check the DAT lead) or `not answering`, with the input gain beside it; a board with no microphone fitted used to look the same as a healthy one in a silent room. *Browser extension and phone app* is the Audio-React switch, the same one as `AUD` on the NETWORK screen, with `off`, `waiting`, `connected` or `phone app`, shown only on a build that has that path. The line at the top of the page says which of them has the knobs. Under it the mapping editor is the extension's, with what that gained in this release (see Tools): the knobs are K1 to K4 with their tabs under the plot, the level axis is in dB, and Preview takes the plot's place. On this page the line in each box, the cursor on the curve and the readout now show where the firmware has the level (`outputs` and `levelsN` from the poll); the page used to work that out a second time on its own dB scale, which is not the scale the firmware maps a hand-set window on. A panel that has heard nothing draws its stored windows dashed, where it drew four boxes flat on the axis. *Reset K1* asks the firmware for that knob's default: the page's own table of defaults was the extension's, and put a 5-16 kHz band on an axis that ends at 8. *Reset mapping* is pressed twice and says what it resets, in place of a `confirm()` dialog, which the phone app's WebView is not set up to show. Saving: nothing is written until the panel's mapping has been read. A read that failed used to leave the editor on the extension's defaults with nothing on screen to say so, and the next touch saved four default bands over the panel's own. An edit now sends the fields that changed, one request at a time - an Attack nudge is `attack=0.611`, a box's top edge is that band's `inMin` and `inMax` - where every edit posted the whole mapping, 41 arguments, and two open tabs wrote their stale copies over each other's edits. A save the panel did not take is said so, tried once more, and kept for the next change; an edit made just before leaving the page is still sent. Polling: the levels poll rides the console's shared lane (`PF.poll`) as every other page's requests do, so it waits behind an upload in another tab and slows itself on a slow link. Measured on the mock, about 9 polls a second with the microphone on, one every 1.5 s with it off (it had been 9.2 a second there too, for numbers that cannot change), one a second while the panel is not answering, and 0.6 a second on a link as slow as the hotspot. Weight: 17.4 KB gzipped as the panel sends it, where it was 19.9, because comments and indentation are dropped as the page is assembled; CI fails the page above 17.5 KB. `console_pages.py build` assembles the page as its first step, so there is one build command where there were two, and `console_serve.py` serves it assembled from its sources on every request against a port of the firmware's audio handlers, with `/mock?audio=` states for music, a quiet room, no microphone, a stalled one, the phone app and a read that never answers. Checked on that mock in Chromium, light and dark, with no console errors in any state, and on a panel with the microphone: the mapping stored on it survived the update, the page read before it wrote, one slider step posted one field (`attack=0.645`) and held across a reload, and frame time and microphone drops were unchanged with the page open. Not yet on a phone or in the phone app's WebView. - **A microphone that is not delivering no longer moves the knobs, and the microphone API takes one field at a time** (Audio edition). When the PDM driver did not start, or the microphone stopped returning samples, the analysis carried on over its built-in three-tone test signal and those tones reached the knobs, while `source` said `synth (mic stalled)`. The microphone now drives the knobs only while it is returning samples. `POST /api/audio-in` applies a band when any one of its suffixed fields is present; before, a request carrying only `inMax2` was answered `{"ok":true}` and changed nothing, because only `hzMin`, `lut`, `knob`, `muted` or `outMin` counted as naming a band. A `band` outside 0..3 is refused with `400` before anything else in the request is applied. `POST /api/audio-in/reset` takes `band=N` and resets that band and its curve alone, naming it in the answer; a full reset now includes `attack`, and still leaves the microphone switch and auto range alone. `hzRange` is printed to two decimals (`31.25`, the bound a band is really held to). The poll reply is built in one allocation where it grew through about forty, ten times a second. Nothing is removed or renamed: the phone app reads the same configuration it did, and `d=` stays in the WebSocket protocol for earlier extensions. Static RAM is what it was (161,216 bytes of DRAM, the pinned figure). `docs/rest-api.md` describes the microphone endpoints field by field, including what had been there unwritten (both spellings of a band field, `smoothing`, `attack`, `meta`, `lut`, `env`, the phone's `frame` post, every `source` string, and that a body sent without the form `Content-Type` is not read, which turns a `band=2` reset into a reset of everything). `docs/audio-ws-spec.md` 1.1 states the 500 ms lane release that version 1 said did not exist, and which senders hold a value through it: the extension does, the Android capture app does not yet. Checked on a host by running the old and the new handlers on the same requests (78 assertions), by building every composition, and by the one-field post above on a panel. +### Web + +- **The guide has a fourth part: Audio** (`/guide/audio`, Korean at `/guide/audio/ko`). Getting the Audio edition onto the board first, then three sections in no order: sound from a browser tab, the on-board microphone, and MIDI with a DAW (network MIDI, Ableton Live as the worked example). The browser section runs the extension's own mapping editor on its demo source, and the boxes drawn there turn the knobs of the 3D board; a hand on a knob takes it for five seconds, as on the real one. The rest is captures of the real pages and the two photos of the microphone. OSC, the phone app and the Director's `.mid` export are links at the end. The hub (`/guide`) has four cards. + +### Docs + +- **The microphone is five leads on four places, and the docs say so.** `AUDIO_GUIDE.md`, the Audio card on `/editions` and `firmware/bundles/README.md` said four wires; GND and SEL both go to GND, which makes five. `AUDIO_GUIDE.md` also names the Audio page's editor by its heading, **Mapping**. +- **The MIDI console page points at Live's current settings page** (Settings → Link, Tempo & MIDI, not Preferences → MIDI) and no longer mentions a MIDI row on the board's NETWORK screen: that screen shows the first two switchable features, and on the Audio edition those are OSC and AUD. `docs/midi-spec.md` says the same. + ### Tools - **The audio extension holds the knobs through silence, and Live means the panel is being driven** (`tools/patternflow-audio-extension`, 0.2.0). The panel hands a knob back to its encoder 500 ms after the last message that set it, and the extension did not send a frame equal to the last one. So half a second into a silent tab, a paused track or a Gate curve resting on one step, the knobs were let go while the popup said Live: measured, not one message in ten seconds of silence. It now sends an unchanged frame again about every 210 ms (five a second, no gap longer than 223 ms), and the knobs rest where each band rests until Stop. A knob turned by hand during a pause goes back there when its five-second hold ends, and a microphone that is also switched on does not get the knobs during a pause. Owner's decision. Muting a knob hands it back at once (`off=N`), where it waited for the panel's timeout. *Live* used to mean the socket was open, which it also is on a panel whose Audio-React switch is off and which ignores every frame; the extension now asks the panel once when the socket opens (`GET /api/audio`), and off reads *Aud off* with a **Turn on** button. A socket that will not open is explained, where every failure read "WebSocket error": no panel at that address, a panel that does not run the Audio edition, or something that answers and refuses the socket. A silent tab reads "No sound from this tab yet." The popup has one button, Start until a tab is captured and Stop while one is; *Release knobs* is gone (Stop does that), and *Test without audio* holds each knob where its slider is put. The address is taken on Enter, on leaving the field and on Start, where it reconnected on every keystroke (typing `192.168.0.42` tried `192.168.0.4` on the way), and it has a storage key of its own, so an editor tab left open no longer writes an old address back. Closing the captured tab ends the capture and says so, and a restarted service worker no longer shows Idle over a running capture. The frequency labels under the popup's spectrum sit where their frequencies are ("1k" had been over 317 Hz). In the mapping editor the bands are K1 to K4 everywhere, the knob tabs sit directly under the plot, the level axis and the curve's readout are in dB, and the hints are plain sentences ("This band still runs its old boost curve", on a band nobody had touched, is gone). A new install starts with Auto range on and the Smooth curve, as the panel does and as `AUDIO_GUIDE.md` already said; a mapping that is already stored keeps what it has. Switching Auto range off goes back to the windows set by hand, where it replaced all four with wherever the boxes were; dragging a top or bottom edge is what takes them over. Preview puts its scope where the plot is, so the sliders, the curve and the output range it demonstrates stay in reach, where it was a dialog over them. The editor holds together at half a screen's width, and on a touch screen a swipe that starts on an empty part of the plot scrolls the page while one on a box drags it. One model (`mapping.js`) holds the defaults and the level-to-knob arithmetic for the capture, the popup, the editor and the panel's page, where the first three each had a copy. The extension keeps its cream skin. After pulling, press Reload on `chrome://extensions`; the card should read 0.2.0. Reload keeps the stored mapping, so a copy you already had opens as it was, which unless you had changed them is Auto range off and no curve: for the new defaults, press Reset on each of the four knobs in the editor and switch Auto range on, or remove the extension and load it again (which also forgets the panel's address). Checked with the unpacked extension in headless Chromium against a loopback socket and a stand-in for the panel's HTTP side (44 popup checks, and the wire in each state), and against a panel: a tone in the captured tab swung K1 between 0.30 and 0.85, four seconds of silence left the lanes held at their rest value where they used to be released after half a second, switching Audio-React off on the panel turned the popup to *Aud off*, and Stop handed the knobs back to the microphone. Touch was tried only in emulation, and the panel requests only in the Chromium Playwright ships, not in a current Chrome with its local-network permission prompt. diff --git a/README.md b/README.md index 15f71806b..330a44536 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ **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. -**Start with [the guide](https://patternflow.work/guide).** It builds one with you, shows you how to play it, and walks you through making patterns, 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)**. +**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)**. ## Where it began @@ -189,7 +189,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 +**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) diff --git a/SUPPORT.md b/SUPPORT.md index 889478be9..b5f6e1121 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -4,7 +4,7 @@ One table for "where do I ask". Issues are for things that change this repositor | I want to… | Go to | | :--- | :--- | -| Learn to build, play or make patterns for one | [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 | +| 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 | | 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 | diff --git a/docs/REPOSITORY.md b/docs/REPOSITORY.md index 39449c36e..a889d1229 100644 --- a/docs/REPOSITORY.md +++ b/docs/REPOSITORY.md @@ -8,7 +8,7 @@ A map for people. (AI coding agents get the same map with more rules in [`../AGE | :--- | :--- | :--- | | `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` and `/guide/make`), 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) | +| `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) | | `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 | [`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) | @@ -26,7 +26,7 @@ The rule for where a document lives: **a guide somebody follows start to finish | 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 | -| Put sound into it | [`AUDIO_GUIDE.md`](../AUDIO_GUIDE.md) | +| Put sound into it | [`AUDIO_GUIDE.md`](../AUDIO_GUIDE.md). The browser tab, the microphone and MIDI with a DAW, step by step: [patternflow.work/guide/audio](https://patternflow.work/guide/audio) | | Work on the site | [`web/README.md`](../web/README.md) → [`web/ARCHITECTURE.md`](../web/ARCHITECTURE.md) | | Contribute anything | [`CONTRIBUTING.md`](../CONTRIBUTING.md) — one table of where each kind of contribution goes | | Cut a release, run the servers | [`RELEASING.md`](RELEASING.md), [`SERVICES.md`](SERVICES.md) | diff --git a/docs/midi-spec.md b/docs/midi-spec.md index cc2ee64fe..68e0124d7 100644 --- a/docs/midi-spec.md +++ b/docs/midi-spec.md @@ -72,7 +72,9 @@ carries "midi": {"runtime": true, "channel": 1, "outDiv": [1,1,4,1], "outMul": [1,2,1,1], "outMode": "abs", "host": "192.168.0.23", "rtpPeers": 1, "rtpPeer": "MacBook", "rx": 812, "tx": 40} ``` -`runtime` is the device's own switch (the `MIDI` row on the NETWORK screen); +`runtime` is the device's own switch (**MIDI on this panel** on the console's +MIDI page; the NETWORK screen shows the first two switchable features, so +on the Audio edition, where `OSC` and `AUD` come first, MIDI has no row there); off means the port stays open and everything is dropped, the same convention as `OSC` and `AUD`. diff --git a/firmware/bundles/README.md b/firmware/bundles/README.md index 36493f08a..8091d6b5d 100644 --- a/firmware/bundles/README.md +++ b/firmware/bundles/README.md @@ -48,7 +48,7 @@ A bundle earns its place when it carries something the default **cannot**: | | why not in the default | |---|---| -| **audio** | an on-board microphone that needs four wires soldered to the DevKit, a Wi-Fi transmit power that is not the conformance-tested one, and MIDI over the network | +| **audio** | an on-board microphone that needs five leads soldered to the DevKit, a Wi-Fi transmit power that is not the conformance-tested one, and MIDI over the network | | **performance** | the show player, MQTT in every role, weather — Simone Majocchi's performance-director firmware, pinned so a show behaves the same at the next gig | | **clock** | the time cut out of the running pattern, in a choice of faces — a panel on a shelf that tells the time, with nothing else attached. **Not on the shelf since 2026-09-15:** the clock is a feature; this bundle is the composition CI keeps compiling | | **midi** | the DevKit's USB port as a MIDI device, beside RTP-MIDI — a wire into a DAW with no driver and no session. It needs the port on the S3's USB-OTG stack, a build flag the other images cannot carry (that flag is what makes the port their serial console), so this one builds in its own PlatformIO env. Data only; the panel is still powered from `J4`. **Not on the shelf:** the composition that proves the OTG build | diff --git a/firmware/patternflow/console/midi.html b/firmware/patternflow/console/midi.html index 9a0cb18d0..08f51d1b3 100644 --- a/firmware/patternflow/console/midi.html +++ b/firmware/patternflow/console/midi.html @@ -149,14 +149,14 @@

Knobs → MIDI

K3CC 26
-
K4CC 27
-
-

Live positions as the DAW sees them. They start at 64 on boot and stop at the ends; the DAW's takeover mode (Live: Preferences → MIDI → Takeover Mode) smooths the pickup.

+

Live positions as the DAW sees them. They start at 64 on boot and stop at the ends; the DAW's takeover mode (Live: Settings → Link, Tempo & MIDI → Takeover Mode) smooths the pickup.

Switch

-

Same switch as the MIDI row on the panel's NETWORK screen. Off keeps the port open and drops everything.

+

Off keeps the port open and drops everything.

@@ -168,7 +168,7 @@

The map

Note 60–63Button 1–4 (on = press, held while held)Button 1–4 pressed / released Prog ChangeSelect pattern by indexPattern changed -

Ableton: in Preferences → MIDI, switch Track and Remote on for the port's Input and Track on for its Output. Then Ctrl/Cmd-M, click a parameter, turn a knob.

+

Ableton: in Settings → Link, Tempo & MIDI, switch Track and Remote on for the port's Input and Track on for its Output. Then Ctrl/Cmd-M, click a parameter, turn a knob.

@@ -216,7 +216,7 @@

The map

$('st').textContent=!d.runtime?'off':(con?('connected · '+(d.rtpPeer||'')):'listening'); $('st').className='sub '+(con&&d.runtime?'ok':''); var lead=$('lead'); - if(!d.runtime){lead.className='lead off';$('lead-t').textContent='MIDI is switched off';$('lead-p').textContent='Turn it on below, or the MIDI row on the panel.';} + if(!d.runtime){lead.className='lead off';$('lead-t').textContent='MIDI is switched off';$('lead-p').textContent='Turn it on below.';} else if(con){lead.className='lead on';$('lead-t').textContent='Connected to '+(d.rtpPeer||'a computer'); $('lead-p').textContent='The panel is a MIDI port there now.'+(d.host?' It will reconnect on its own.':'');} else{lead.className='lead';$('lead-t').textContent=d.host?('Calling '+d.host+'…'):'Listening'; diff --git a/firmware/patternflow/features/audio_in/core_audio_in_map.h b/firmware/patternflow/features/audio_in/core_audio_in_map.h index 2cb26cd3f..c2529b1e3 100644 --- a/firmware/patternflow/features/audio_in/core_audio_in_map.h +++ b/firmware/patternflow/features/audio_in/core_audio_in_map.h @@ -263,7 +263,7 @@ inline void clampRange(Band& b) { // analysis. Off means the feature costs a panel nothing but the code size. // // Default off, and this is the switch people will actually use. The mic is -// four wires to a breakout rather than a part on the board, so a panel that +// five leads to a breakout rather than a part on the board, so a panel that // installs the Audio edition for OSC and the Chrome extension should not be // running an analysis over a floating pin. Turning it on is one tick on // /audio-in and it persists. diff --git a/firmware/patternflow/features/midi/core_midi_http.h b/firmware/patternflow/features/midi/core_midi_http.h index 67a4d0539..611264b4c 100644 --- a/firmware/patternflow/features/midi/core_midi_http.h +++ b/firmware/patternflow/features/midi/core_midi_http.h @@ -3,7 +3,8 @@ // // GET /midi the page // GET /api/midi channel, outbound sensitivity, host, session -// POST /api/midi?on=0|1 the MIDI row on the NETWORK screen, from here +// POST /api/midi?on=0|1 MIDI on or off (the NETWORK screen's MIDI row, +// on a build where that screen shows one) // POST /api/midi?outDiv=N detents per outbound step, 1..16, persisted // POST /api/midi?outMul=N steps per detent, 1..8 (the other direction) // ...&knob=1..4 apply to one knob; absent = all four diff --git a/firmware/patternflow/features/midi/midi_index.h b/firmware/patternflow/features/midi/midi_index.h index 1e1dda7d9..2e3961dcf 100644 --- a/firmware/patternflow/features/midi/midi_index.h +++ b/firmware/patternflow/features/midi/midi_index.h @@ -159,14 +159,14 @@ html[data-theme=light]{--cream:#F4EFE6;--cream2:#FFFCFA;--bg:#F4EFE6;--panel:#FF
K3CC 26
-
K4CC 27
-
-

Live positions as the DAW sees them. They start at 64 on boot and stop at the ends; the DAW's takeover mode (Live: Preferences → MIDI → Takeover Mode) smooths the pickup.

+

Live positions as the DAW sees them. They start at 64 on boot and stop at the ends; the DAW's takeover mode (Live: Settings → Link, Tempo & MIDI → Takeover Mode) smooths the pickup.

Switch

-

Same switch as the MIDI row on the panel's NETWORK screen. Off keeps the port open and drops everything.

+

Off keeps the port open and drops everything.

@@ -178,7 +178,7 @@ html[data-theme=light]{--cream:#F4EFE6;--cream2:#FFFCFA;--bg:#F4EFE6;--panel:#FF Note 60–63Button 1–4 (on = press, held while held)Button 1–4 pressed / released Prog ChangeSelect pattern by indexPattern changed -

Ableton: in Preferences → MIDI, switch Track and Remote on for the port's Input and Track on for its Output. Then Ctrl/Cmd-M, click a parameter, turn a knob.

+

Ableton: in Settings → Link, Tempo & MIDI, switch Track and Remote on for the port's Input and Track on for its Output. Then Ctrl/Cmd-M, click a parameter, turn a knob.

@@ -226,7 +226,7 @@ function paint(d){ $('st').textContent=!d.runtime?'off':(con?('connected · '+(d.rtpPeer||'')):'listening'); $('st').className='sub '+(con&&d.runtime?'ok':''); var lead=$('lead'); - if(!d.runtime){lead.className='lead off';$('lead-t').textContent='MIDI is switched off';$('lead-p').textContent='Turn it on below, or the MIDI row on the panel.';} + if(!d.runtime){lead.className='lead off';$('lead-t').textContent='MIDI is switched off';$('lead-p').textContent='Turn it on below.';} else if(con){lead.className='lead on';$('lead-t').textContent='Connected to '+(d.rtpPeer||'a computer'); $('lead-p').textContent='The panel is a MIDI port there now.'+(d.host?' It will reconnect on its own.':'');} else{lead.className='lead';$('lead-t').textContent=d.host?('Calling '+d.host+'…'):'Listening'; @@ -297,7 +297,7 @@ PF.status(function(s){ )HTML"; // GENERATED by firmware/toolchain/console_pages.py build — do not edit. -// gzip -9 of MIDI_INDEX_HTML (19039 bytes → 7056 bytes), served with Content-Encoding: gzip. +// gzip -9 of MIDI_INDEX_HTML (18973 bytes → 7026 bytes), served with Content-Encoding: gzip. static const uint8_t MIDI_INDEX_HTML_GZ[] PROGMEM = { 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xc5,0x5c,0xcd,0x72,0xdb,0xc8, 0x76,0xde,0xeb,0x29,0xda,0xf4,0x1d,0x81,0x18,0x83,0x20,0x41,0x4b,0xb2,0x0c,0x0a, @@ -553,193 +553,192 @@ static const uint8_t MIDI_INDEX_HTML_GZ[] PROGMEM = { 0x29,0x1b,0x55,0x89,0xb4,0xa4,0x0c,0x49,0x68,0x89,0xb2,0x66,0x29,0x25,0x5f,0xcc, 0x18,0xc6,0xdd,0xc2,0xc2,0xc3,0x42,0xd1,0xb9,0x30,0xd2,0x11,0x32,0x5e,0x8d,0x16, 0xa9,0x8e,0x47,0xcd,0x34,0x8e,0x52,0xd8,0xa0,0xa2,0x60,0x61,0x78,0x78,0x25,0xa9, - 0x2e,0x2c,0x68,0x33,0x20,0xda,0x34,0x99,0x2f,0xde,0x16,0x72,0x2c,0x0b,0xe4,0x2b, - 0xb9,0x02,0x13,0xfc,0xe5,0x7d,0xd5,0xfb,0x0d,0x7a,0xdb,0x42,0x3f,0xad,0xa5,0x19, - 0xca,0xe3,0xd1,0x15,0x00,0x25,0x23,0xaf,0x2f,0x41,0x94,0xcb,0x9b,0x58,0x8d,0xa6, - 0x35,0x34,0xd1,0xfb,0xa1,0x3a,0x39,0x23,0xe3,0xb4,0xbe,0x94,0x99,0x28,0x4b,0x6b, - 0x76,0x08,0xe9,0x53,0xf6,0x67,0xe0,0xd1,0xd8,0x30,0xed,0xf4,0xe4,0xec,0x37,0xa3, - 0xf5,0xcb,0x70,0x06,0x0c,0xc0,0xbc,0x56,0x22,0x47,0x26,0xd5,0xa8,0x6a,0x78,0x2a, - 0xe8,0xf9,0xc9,0xac,0x51,0x2b,0x85,0x24,0x7f,0x78,0xf1,0xfe,0xe7,0x1f,0xdf,0xbd, - 0x16,0xe5,0xa8,0x90,0x32,0x75,0xc5,0x8f,0xe3,0xb1,0xc1,0x43,0xdc,0x2b,0x83,0x6a, - 0x08,0xe1,0xb3,0x46,0x34,0x5e,0x67,0x50,0xcf,0xb8,0xe6,0xeb,0x62,0x23,0x8c,0x05, - 0x30,0x51,0xcb,0x8d,0x6b,0x7c,0xc6,0x98,0x15,0xb6,0xe9,0x6a,0x7a,0xfa,0x06,0x20, - 0x23,0x9c,0x00,0x9b,0xe1,0x3b,0x5d,0xbf,0x4a,0x45,0x9b,0xa0,0x08,0x6b,0x4e,0x1f, - 0x5c,0xd4,0xf7,0x7e,0x84,0x7c,0xdb,0x1a,0xb3,0xd1,0x5d,0xea,0x66,0x6e,0x76,0x41, - 0xad,0x41,0x36,0x62,0xab,0x27,0x3c,0xd7,0x87,0x4f,0xe2,0x92,0x9a,0x18,0x08,0x7a, - 0x68,0x3b,0x70,0x44,0x75,0x5c,0x58,0x63,0x3e,0x9e,0x2d,0x06,0x32,0x8e,0xc4,0x3c, - 0x55,0x71,0x02,0x38,0x38,0xa5,0x35,0xf3,0x63,0x0a,0x1a,0x15,0x91,0x71,0xeb,0xad, - 0xc7,0x45,0x8c,0xdd,0x8a,0xca,0x0a,0xab,0x02,0xfa,0xee,0x2c,0x8e,0x0c,0xc2,0x17, - 0x37,0x05,0x74,0xc4,0xe2,0x2b,0x09,0x08,0x99,0xc9,0x01,0x07,0xf5,0xf7,0x9d,0x9c, - 0x1e,0x10,0xa7,0x4f,0x76,0x72,0x5a,0x1d,0x7c,0xae,0xe0,0xda,0x8e,0x6e,0xcc,0x26, - 0xed,0xcb,0x62,0xad,0x5d,0x76,0x0b,0x80,0xe6,0x92,0x34,0x37,0xcc,0x68,0x23,0xbb, - 0x73,0xee,0x1f,0xe8,0x64,0xe1,0x88,0x64,0x70,0xb4,0x92,0xd3,0x73,0x0d,0x3d,0x35, - 0xe1,0x36,0xbe,0x05,0x22,0xa7,0x2d,0x87,0x23,0xa6,0x54,0x9e,0xb9,0x99,0xc6,0x89, - 0xe4,0xaf,0xf6,0xee,0x21,0xdc,0x19,0x72,0xec,0x12,0xef,0x92,0x1e,0x85,0xbc,0x63, - 0xf6,0xb7,0x45,0x36,0x11,0xb4,0x1f,0x99,0xc8,0x9a,0xd2,0x25,0x86,0x60,0xb7,0x95, - 0xeb,0x9d,0x13,0xed,0x91,0xe2,0x34,0x92,0x9f,0xea,0xfb,0x66,0x4b,0x45,0x6f,0x0d, - 0x60,0xd8,0x3a,0x65,0xfc,0xad,0xec,0x6b,0xd3,0x31,0x0c,0xd2,0xf6,0x49,0x40,0xbb, - 0xe2,0x84,0x53,0x79,0x0d,0xdc,0xe5,0x7d,0x11,0x8e,0xae,0xd8,0x5f,0xc8,0x00,0x18, - 0x5d,0xcf,0x32,0xc5,0xbb,0x07,0xf2,0x1e,0x02,0xee,0x95,0x6f,0xc0,0x00,0x5e,0xb1, - 0xd7,0x9b,0x9e,0xab,0xa1,0xa6,0x23,0x3d,0x24,0x03,0xab,0x45,0x17,0xb6,0x9c,0x54, - 0x9c,0xab,0x22,0xe9,0x9e,0xcf,0xa2,0xce,0x1b,0x20,0x66,0xe0,0xe9,0x2b,0x7a,0x40, - 0x25,0x2c,0xe0,0xb7,0x8a,0x40,0x34,0x3f,0xcb,0x1c,0x6a,0x53,0xdb,0xf6,0x2e,0x5d, - 0x60,0x3d,0x7d,0xf7,0xfe,0x6d,0x87,0xa3,0x49,0xfb,0x2c,0xcf,0x13,0x49,0x5f,0x6d, - 0x02,0x0e,0x27,0xb0,0x59,0xac,0x2d,0x68,0x4d,0x95,0xca,0x4b,0xbf,0xdb,0x9d,0x60, - 0x23,0x3b,0x1f,0xba,0xd8,0xb1,0x76,0x65,0x3a,0x99,0xcd,0xd3,0x49,0xb7,0xb1,0x25, - 0xed,0x0e,0x93,0x6c,0xd8,0x9d,0x85,0x71,0xda,0x8d,0xb2,0x51,0xd9,0x85,0x0d,0xc7, - 0x9d,0xd0,0x6c,0x49,0x66,0x51,0xeb,0xb4,0xa4,0xcd,0xb6,0xa3,0x37,0x0b,0xc3,0x5b, - 0x03,0x45,0xc2,0xd3,0x3f,0x6a,0xaa,0x32,0x97,0x23,0x9e,0x47,0xef,0xaa,0x53,0x05, - 0xe1,0x29,0x9a,0x80,0x6a,0x18,0xbc,0xd0,0x3d,0x13,0xe8,0xaa,0x17,0x3c,0xf6,0xea, - 0xe7,0xb8,0xff,0xd4,0x8e,0xeb,0x87,0xc7,0x41,0x71,0x3e,0x03,0x5c,0x72,0x81,0xf0, - 0x5f,0x24,0x92,0xbe,0x3e,0xbf,0x7d,0x15,0xa1,0xc7,0x72,0x8f,0x9e,0x84,0xa7,0xb3, - 0x9f,0x80,0x5f,0x45,0xa0,0x80,0x71,0x41,0x6f,0x72,0x04,0xe3,0x30,0x29,0xa5,0x13, - 0x15,0xe1,0x64,0x82,0x40,0x66,0x2e,0x69,0x03,0xc1,0x9b,0x1a,0x19,0x99,0x96,0x79, - 0x39,0xfc,0x99,0x9e,0xe4,0xaf,0x1a,0x06,0x7b,0xdd,0x2e,0x47,0x00,0x8e,0x50,0xa6, - 0x5c,0x59,0xd6,0x4f,0x1d,0xc1,0x1c,0xe8,0x51,0xa3,0x79,0xca,0x55,0x12,0x21,0xa3, - 0x58,0xe9,0xac,0xa5,0x4f,0xad,0xc4,0x4d,0x8c,0xed,0x08,0x0c,0x92,0x1c,0x23,0x0b, - 0x23,0x22,0x56,0x3f,0xb0,0x84,0xf5,0x67,0xfc,0x48,0xff,0x38,0x2e,0x66,0x37,0x61, - 0x21,0x8d,0xa3,0x61,0x7b,0x49,0x15,0x33,0xa6,0xec,0xae,0x04,0x00,0xdd,0xf0,0x4a, - 0xda,0xd7,0xf6,0x62,0xb5,0xac,0xb7,0x2f,0x5d,0x7e,0x53,0x25,0xb8,0x5e,0x12,0xf1, - 0x33,0x4c,0x54,0xce,0x13,0x45,0x3c,0x96,0x61,0x1c,0x89,0x21,0x76,0x6b,0x91,0x14, - 0xb5,0xc0,0xb3,0x44,0x87,0xb6,0x51,0x38,0x27,0x8f,0x8d,0x95,0x2f,0xca,0x04,0xfc, - 0x21,0xf1,0x88,0xb6,0xa9,0xe7,0xd8,0x0e,0x91,0xba,0xe2,0x26,0x86,0x88,0x36,0xd5, - 0x30,0x32,0xdd,0x85,0x9d,0x06,0x1b,0xc8,0x77,0x12,0xf1,0xf4,0x5a,0xd2,0xe3,0x5a, - 0x6a,0xca,0xf4,0xf5,0x01,0x1c,0xd7,0x3b,0xe8,0x91,0x2b,0x02,0xb7,0x51,0x83,0x7f, - 0x40,0x03,0xd5,0x66,0xc3,0x2f,0x9d,0xec,0xea,0xbd,0xfc,0xa4,0x1c,0x9a,0xd9,0x5e, - 0xc0,0x79,0x49,0x69,0x54,0xe1,0x0d,0x48,0x1c,0x3f,0xbd,0xfb,0xfe,0x52,0x86,0xc5, - 0x68,0xfa,0x96,0x3b,0xb7,0x6d,0x47,0x26,0xc1,0x9f,0xda,0xd4,0xf9,0xf3,0x67,0x0b, - 0x5c,0x58,0xf6,0x00,0x63,0xe0,0x6c,0x6d,0x1a,0x47,0x27,0xb3,0xda,0xa1,0x4a,0x9b, - 0x68,0xf0,0x4b,0x16,0x57,0x8e,0x6e,0xf9,0x70,0xf5,0x91,0x3b,0xaf,0xbd,0x75,0xa0, - 0x5f,0x06,0x22,0x8b,0xb4,0x9c,0x05,0xdc,0x70,0x9a,0x45,0xbe,0xf5,0xf6,0xc7,0xcb, - 0xf7,0x96,0x43,0x04,0x7c,0xfa,0x58,0xee,0x78,0x2f,0xc1,0x10,0x59,0xbd,0x65,0xb0, - 0xfd,0x62,0x8c,0xae,0x0b,0x8d,0xdb,0x91,0x9b,0x5d,0x05,0x81,0x36,0x21,0x7b,0x01, - 0x25,0x95,0xe1,0x2d,0x1a,0x25,0xbd,0xca,0x80,0x45,0x8c,0x43,0x28,0x3a,0xb2,0x1c, - 0x0b,0x0d,0x96,0x43,0x27,0xf5,0x95,0x65,0x2f,0x99,0x42,0x4e,0xe7,0x08,0xa0,0x37, - 0x00,0x29,0x2d,0x2b,0xdb,0xd0,0x30,0x92,0xb3,0xb2,0xab,0xf5,0x71,0xb4,0x48,0x30, - 0x34,0x22,0xdb,0x6d,0xbe,0xb0,0x61,0x86,0x59,0x69,0x66,0xf4,0xb3,0x2a,0x6b,0x69, - 0x40,0xb0,0xe2,0x61,0x09,0x49,0x2d,0x1b,0xf6,0x06,0x8b,0xfe,0x7b,0xd2,0xd9,0x50, - 0xa5,0xec,0x47,0x46,0x6b,0xbc,0xc8,0x3f,0xb5,0x2d,0x5d,0xe2,0xb5,0x6c,0x57,0xef, - 0xa6,0xe8,0x82,0x98,0xc0,0x84,0xf4,0x7a,0x13,0x8f,0x62,0x95,0x2f,0xcc,0xa1,0x7e, - 0xa9,0x96,0x15,0x85,0x1d,0x82,0x23,0x99,0xed,0xef,0x93,0xd8,0x1e,0x54,0x62,0xab, - 0x0d,0x5e,0x5f,0x6a,0xf6,0x60,0x98,0x3f,0xc2,0xe1,0x78,0xf7,0xe5,0x9b,0xdd,0x16, - 0xd5,0x37,0xf4,0xfe,0xa9,0xc2,0x3a,0x74,0x22,0xe8,0xd4,0x7b,0xaa,0xba,0xfe,0x61, - 0xee,0xf2,0x09,0x16,0xdb,0xb8,0xe7,0x7b,0x75,0x02,0x8d,0xa3,0x28,0x91,0xae,0xa0, - 0x17,0x42,0x80,0x3e,0x55,0x71,0x4b,0x3e,0xf4,0x61,0x06,0x67,0x8a,0xf3,0x24,0x26, - 0xc3,0x46,0x64,0x8a,0xcb,0xac,0xf8,0x48,0x4f,0x19,0x26,0xb7,0xec,0xf8,0xb1,0x46, - 0x47,0xe2,0x54,0x78,0x2e,0x87,0x9e,0xef,0xce,0x5e,0xfd,0x70,0x19,0x7c,0xf8,0x70, - 0xec,0x78,0x1f,0x9d,0x0f,0x07,0xfc,0xf9,0x98,0x3f,0xfb,0xfc,0xe9,0x99,0xcf,0x3e, - 0x7f,0x3e,0xe6,0xcf,0x03,0xfe,0x3c,0xe2,0xcf,0x63,0xdd,0x47,0xdf,0xf6,0x8e,0x3e, - 0x7e,0x1c,0xac,0x34,0x32,0x81,0x55,0xbc,0xa2,0xdc,0xd8,0x06,0x57,0x0e,0xb8,0x61, - 0x55,0x54,0x7e,0x10,0x07,0xbd,0x41,0x7c,0xc2,0xf3,0xbb,0xba,0x3e,0x3e,0x88,0x1f, - 0x3d,0xb2,0x21,0x58,0x6e,0xfb,0x10,0x7f,0xfc,0xd0,0xfb,0x08,0x9b,0xc4,0xd8,0xfd, - 0xfd,0xba,0xc9,0xa3,0x26,0x22,0x65,0x0c,0x29,0x6e,0x78,0x0b,0x7a,0x9e,0x7a,0xcf, - 0x7a,0x7e,0x93,0x66,0xc7,0x5b,0x33,0x12,0x62,0x89,0x54,0xda,0x9e,0xd4,0xee,0x31, - 0xc1,0x34,0x18,0xd6,0xb6,0xfe,0x71,0xde,0xeb,0x45,0x4f,0xac,0x47,0xd4,0x60,0xfb, - 0xed,0x09,0xe6,0xe2,0x76,0xaf,0x4b,0x6d,0x1e,0xda,0x2c,0xde,0x12,0x5b,0x36,0xab, - 0xb5,0xdf,0xab,0x15,0x16,0x72,0x56,0x1c,0x08,0x82,0x69,0x5a,0xc5,0x92,0x80,0x59, - 0x46,0x7f,0x1a,0x11,0x05,0xe0,0x56,0xcf,0xed,0x00,0xdc,0xd5,0x71,0x04,0xda,0x7e, - 0x8f,0xd1,0x41,0xbf,0xf7,0x2d,0xcd,0xdc,0xa5,0xa9,0x06,0xe6,0x9e,0x0a,0x1a,0x0c, - 0x3f,0xb2,0x04,0xb1,0x38,0x7c,0x22,0xac,0x47,0x6d,0x7e,0x27,0x8b,0x9f,0x56,0x69, - 0x1b,0x02,0xdf,0x7a,0x3d,0xbb,0x8b,0x7f,0x8f,0xac,0x2e,0x71,0x63,0x11,0x0d,0x08, - 0x93,0xa6,0x52,0x8f,0x82,0x3b,0xc6,0x82,0xe3,0xee,0xd6,0x78,0x0d,0x29,0xad,0x86, - 0x64,0xd5,0xba,0xa7,0xf1,0x33,0xa9,0x08,0x74,0x5b,0xf9,0xed,0x9f,0xe6,0x40,0xde, - 0x1a,0x20,0x65,0xc5,0x59,0x92,0xb4,0xad,0x5d,0x4f,0x80,0x92,0x00,0x6b,0x62,0x61, - 0x51,0x50,0x86,0x30,0x94,0xce,0x8a,0x22,0xbc,0x75,0xe3,0x92,0xff,0xa2,0xfd,0xd9, - 0xb5,0xff,0xe1,0xda,0xe1,0xff,0x3e,0x36,0x06,0x55,0xf1,0x86,0x64,0xc8,0xc9,0x33, - 0xaa,0x24,0x86,0xa4,0x11,0x44,0x6e,0xa1,0xf2,0xb7,0x12,0x2c,0x9e,0xf6,0x06,0xda, - 0xf9,0xd9,0xf1,0x9b,0xaf,0x35,0x3e,0x40,0x27,0x82,0xd1,0x33,0xf9,0xcc,0xca,0xc6, - 0x63,0xcb,0x6f,0x63,0x24,0x94,0x5d,0x9f,0xe5,0x10,0x86,0x80,0xac,0x6a,0x5a,0x88, - 0x82,0x96,0x6d,0xd3,0xdb,0x96,0xd0,0x70,0x8a,0x94,0xac,0xa3,0x7a,0x45,0x9b,0xb1, - 0xdc,0x0f,0xf4,0xbe,0x95,0x55,0xce,0x87,0x34,0x12,0x94,0x28,0x40,0xac,0x66,0xb9, - 0xb2,0x7c,0x4b,0x0f,0xe2,0x9c,0x2f,0xc3,0x08,0x59,0xc2,0xa2,0xbf,0xba,0x95,0x5e, - 0x23,0xab,0xfb,0xdb,0x0b,0x7e,0x83,0xa1,0x41,0x96,0xdf,0x9e,0x20,0x56,0x07,0x66, - 0x54,0x67,0x73,0x4d,0x16,0x43,0x2e,0xca,0xa4,0x9c,0xf4,0xe4,0x46,0xf7,0x7c,0xb3, - 0xfb,0x7b,0x76,0x20,0x0e,0x42,0x5c,0xde,0xe7,0x54,0x48,0xb1,0x85,0xe9,0x6c,0x6e, - 0xc5,0x5c,0x6b,0x40,0xc1,0x5e,0x22,0xb6,0x11,0xab,0x58,0xde,0x5d,0x3c,0xa6,0x5f, - 0x60,0xf1,0xbc,0x96,0x2f,0xdc,0x63,0x43,0xbe,0xab,0x43,0x1c,0x2d,0x10,0x96,0xee, - 0x1d,0xac,0xd7,0xa5,0xf1,0x98,0xdc,0x8f,0x19,0xe6,0xad,0x20,0x55,0x50,0x25,0x10, - 0xcc,0x8d,0xcb,0xc4,0x29,0x8c,0x3f,0xb3,0xc4,0x2b,0xa5,0x91,0x4d,0x7d,0x84,0x23, - 0xb2,0xfa,0xf1,0x70,0x57,0xab,0xa5,0x5a,0xdb,0xce,0x35,0xdd,0xbd,0x20,0x33,0x45, - 0xdb,0x3a,0x0f,0x93,0x84,0x2a,0xe9,0xd6,0x23,0xdd,0xf4,0xc8,0xfa,0xf5,0xaf,0xff, - 0x69,0xc1,0x60,0xbe,0xaf,0x0d,0xe6,0xcb,0x6b,0xaa,0x98,0x7d,0x45,0xc7,0x43,0x44, - 0x49,0x9f,0x4d,0x21,0xcc,0x94,0x2e,0xf6,0x20,0xf4,0x48,0x17,0x23,0xa1,0xea,0xd8, - 0xa9,0x2b,0x7e,0x90,0xea,0x26,0x2b,0xae,0x44,0xa5,0xf6,0x7a,0x1b,0xac,0x0f,0x9a, - 0xca,0xe6,0x49,0x13,0xad,0xf2,0xbc,0x79,0x50,0x74,0x4b,0x35,0xe7,0x4a,0xde,0xac, - 0x79,0x4a,0xa0,0x2c,0x94,0xb5,0x33,0x1f,0x26,0xb8,0x3a,0x4c,0x63,0x31,0xd2,0xeb, - 0xb6,0x74,0xce,0xa0,0x2d,0x82,0xec,0xb6,0x06,0x78,0xf6,0x76,0xa6,0xd5,0x0b,0x23, - 0xff,0x31,0x0e,0x63,0xce,0x1e,0x37,0x75,0xba,0x75,0x12,0xc9,0x1a,0x04,0x9f,0x50, - 0x60,0x9b,0xc4,0x8a,0xaf,0x8f,0x2c,0xbb,0xf6,0xa2,0x35,0x4a,0x7c,0xfa,0xee,0x9a, - 0xf3,0xfb,0xc0,0x0c,0x43,0x57,0x7a,0x48,0xab,0x9a,0x97,0xd6,0xb5,0x31,0x29,0x26, - 0x88,0x73,0xb0,0xd6,0xb1,0x10,0xf7,0x7c,0x9e,0x90,0xcc,0xe8,0xf3,0xe7,0xc3,0x5e, - 0xef,0x80,0x42,0x21,0x85,0x01,0xd1,0xd8,0x27,0x54,0xb4,0x68,0x77,0xb7,0xa5,0xc0, - 0x91,0x3e,0xb9,0x42,0x86,0xea,0x3d,0xb3,0x20,0x23,0x44,0x15,0x74,0x64,0x93,0x30, - 0xb7,0x2a,0xc6,0x47,0xa9,0xda,0xe6,0xa4,0xf8,0xf4,0xf9,0x33,0xc7,0xdf,0x2a,0xf4, - 0x28,0x6e,0xa8,0x22,0x06,0x22,0x39,0xe6,0xc8,0xe6,0x8a,0x8a,0x44,0x40,0x1d,0x16, - 0x30,0x7a,0xc5,0x0e,0x1f,0x2d,0xad,0x87,0xa1,0xd1,0x14,0x34,0xd0,0x0a,0xe9,0xc1, - 0x23,0x1b,0x42,0xe3,0x23,0x9a,0xbb,0xfa,0xa2,0x23,0x75,0xaf,0x27,0x45,0x4e,0x2d, - 0x03,0x8a,0xd1,0x7a,0xe6,0x79,0xf2,0xf9,0xb3,0x67,0x53,0x2e,0x6f,0xb4,0x5e,0xc4, - 0xd7,0xd4,0x5a,0x0d,0x29,0x93,0xa0,0xce,0x10,0x4d,0xe0,0x5b,0x22,0xe1,0x97,0x27, - 0x07,0x83,0x12,0x59,0xbe,0x06,0x9e,0x0f,0xaa,0x1d,0x8e,0x5d,0x26,0x1f,0xca,0x8f, - 0xc6,0x64,0xd6,0x90,0x43,0x89,0x76,0x9e,0x11,0x7f,0x4d,0x5c,0x20,0x72,0x93,0x40, - 0x83,0x02,0xfd,0x66,0x7b,0xbb,0x31,0xdc,0xfe,0xa8,0x7b,0xe9,0x26,0x00,0x6b,0xc8, - 0xf7,0x07,0x3a,0xf4,0x59,0x4b,0x4f,0xc8,0x4d,0x9b,0xd6,0xb0,0x9e,0xa0,0x19,0xa2, - 0xee,0xad,0x73,0xb9,0xbf,0xff,0xa0,0xb1,0x07,0x33,0xcb,0xe0,0xc5,0x91,0x1c,0x89, - 0x59,0x97,0xdd,0x76,0x85,0x15,0x67,0xce,0x6a,0x1f,0x38,0xd3,0x70,0xa6,0x44,0x9a, - 0x47,0x6a,0xa0,0x15,0xc5,0x06,0xce,0x50,0xd3,0xb2,0x11,0xf4,0x30,0x07,0x69,0x48, - 0x9f,0x14,0x05,0x44,0xbc,0xe2,0x86,0xb7,0x10,0x30,0x84,0xbb,0x53,0xee,0x43,0xde, - 0x03,0xe9,0xe7,0xfd,0xd6,0xb7,0x1e,0x1a,0x72,0x1d,0x30,0xce,0x5a,0xb1,0x7e,0x1d, - 0x68,0x35,0xbe,0xcd,0xca,0xcf,0x9f,0x3f,0x1c,0x1d,0x38,0xd5,0xff,0x1f,0x6d,0xf0, - 0xa7,0x79,0xba,0x22,0x56,0x37,0xe5,0x17,0xd7,0x7e,0xa7,0xdf,0x27,0x26,0x03,0x6a, - 0xb7,0xaf,0xbb,0x00,0x15,0x00,0x13,0x64,0xc9,0xdf,0x50,0x00,0xec,0x7d,0x63,0x7d, - 0x81,0x88,0x7b,0xbd,0xa1,0x04,0xa2,0x72,0xed,0x5b,0x7f,0xfb,0x2f,0x0d,0x61,0x96, - 0xda,0x68,0xab,0xba,0xed,0x96,0xab,0x57,0x56,0xdb,0x74,0x74,0x32,0xe0,0x5a,0x78, - 0x0f,0x56,0x49,0x95,0x50,0x4c,0xc3,0x59,0xb2,0x94,0xeb,0x1b,0xc1,0xfa,0xf6,0x83, - 0x77,0x03,0x14,0x55,0xcc,0x76,0xc0,0x38,0x9c,0x6f,0xd1,0xa0,0xa5,0x63,0x69,0xe9, - 0x96,0x52,0x87,0xc5,0xb8,0xd0,0xa7,0x33,0xd8,0x97,0x5c,0xf1,0x56,0xcf,0x5e,0x0e, - 0xf6,0x1a,0x4e,0xf6,0x1b,0xe7,0xa0,0x41,0xeb,0x73,0x14,0x6b,0x07,0xa7,0x9b,0xd3, - 0x18,0x43,0xc1,0x2c,0x5c,0x6d,0x6a,0x4e,0xd3,0xac,0x15,0xa8,0x62,0x2e,0x69,0x6f, - 0x46,0x53,0x56,0x82,0xd1,0x2f,0xe1,0x5f,0xaf,0x7c,0x15,0x26,0xa8,0xfd,0x67,0xb0, - 0xc3,0xbd,0xae,0xe1,0x54,0xcc,0xab,0x81,0xcb,0x0b,0x1d,0x0d,0x7c,0xba,0x5c,0xfa, - 0x0b,0x1d,0x05,0x7c,0xc2,0xae,0x60,0xbf,0x3e,0xd3,0xa4,0xd7,0xec,0x38,0xcf,0x37, - 0x60,0x6c,0xb5,0x82,0x25,0x56,0xa0,0xe1,0x5e,0x5e,0x64,0x2a,0x23,0x7c,0x58,0xbd, - 0xe0,0xce,0xbf,0xe7,0xd0,0xae,0x19,0x6b,0xfc,0x66,0x43,0xc2,0x76,0x0b,0x30,0x92, - 0xa5,0x0c,0x2b,0x9b,0x0b,0xae,0x6b,0x25,0xbc,0xda,0x2a,0xc0,0x6c,0x79,0x53,0xb5, - 0x6e,0x96,0x85,0x5e,0xef,0x6f,0x65,0x83,0x7e,0xe9,0xc0,0x44,0xaa,0xeb,0xa5,0xbd, - 0xac,0xa6,0x22,0x24,0x6a,0x6b,0x68,0xca,0x5f,0x97,0x83,0x8a,0xd7,0x6d,0xed,0xac, - 0x17,0x76,0x76,0x49,0x7c,0xc5,0x5f,0x15,0xcf,0x78,0xb7,0x10,0x7c,0x5d,0x01,0xf5, - 0xda,0x1f,0x6c,0x2f,0x3e,0xe7,0xb0,0x10,0x34,0xe7,0x98,0x48,0x75,0xa6,0x54,0x11, - 0x0f,0x91,0x73,0xdb,0x96,0x3e,0x61,0x84,0x81,0x3d,0xf2,0x34,0x1d,0x5d,0x07,0x71, - 0xb6,0x05,0xf9,0xac,0xd6,0x33,0xdc,0xef,0xb5,0xf5,0x48,0x93,0xd6,0xdb,0x94,0xbe, - 0xf7,0xb4,0x7f,0x87,0xda,0xe1,0x84,0x36,0xdb,0x2e,0x7b,0xe9,0x2e,0xd9,0x18,0x9f, - 0x48,0xfd,0xa6,0xb9,0x3e,0xb3,0x3c,0x4c,0xd3,0x83,0x77,0xac,0xb7,0x9a,0x43,0x0b, - 0xdc,0xd3,0xdf,0x80,0x78,0x1d,0x2b,0xab,0xa6,0x6a,0xa2,0x91,0x1d,0x26,0x53,0x6f, - 0xd9,0xc9,0x64,0x4c,0x7f,0x7e,0xf8,0x8c,0xbb,0xeb,0x87,0xae,0x82,0xe6,0x2f,0x86, - 0x48,0x58,0x09,0x02,0x7c,0xaa,0x2e,0xe4,0x38,0xc4,0x56,0x1b,0x19,0x4e,0xff,0x94, - 0xc5,0x16,0xec,0x71,0x21,0xd1,0x99,0x4e,0x80,0x75,0x65,0x82,0x39,0xdf,0x8c,0x7f, - 0x74,0xd7,0xb2,0x9d,0xa9,0x33,0x7d,0x66,0xe9,0xaa,0x1c,0x3d,0x98,0x11,0x1b,0x14, - 0x88,0x75,0xf1,0x73,0x55,0x32,0xaa,0xd6,0xb3,0xc2,0x3c,0x3b,0x62,0x8b,0xb1,0xc2, - 0xfd,0x7d,0xfa,0x24,0x10,0x64,0xaf,0x4d,0xee,0x54,0xcd,0xce,0xae,0xa9,0xcc,0x04, - 0xf5,0x73,0x5c,0xbb,0xa7,0x58,0x27,0x68,0x41,0xda,0x86,0x41,0x26,0x46,0x8f,0xc5, - 0x99,0x07,0x4a,0x9a,0x4f,0xc6,0x31,0x6d,0xae,0x85,0x34,0xaa,0x8d,0x56,0x49,0xbf, - 0xb8,0x23,0x7d,0x2a,0x95,0xc7,0x7c,0x38,0x90,0xeb,0x2a,0x9f,0xae,0x2b,0x52,0x47, - 0x15,0x0e,0x09,0xd9,0xea,0x27,0x0b,0x9d,0x0a,0x8b,0x12,0xa5,0xea,0x59,0x2c,0x7a, - 0x04,0x65,0x1a,0xe7,0x0e,0x57,0x2d,0xcd,0xc9,0xd5,0x44,0x3a,0xf7,0x79,0xf0,0xde, - 0xdd,0x43,0x28,0xe6,0x5f,0x20,0x69,0x56,0xd8,0x3c,0x40,0x3f,0x67,0xb3,0xd4,0x63, - 0x57,0x3b,0x4e,0xb2,0x5d,0xcc,0xfe,0xd3,0xe5,0x73,0x5d,0xe0,0x96,0x9f,0x62,0x7a, - 0x28,0x85,0x0b,0x2c,0x31,0x15,0xc6,0xf9,0x17,0x4a,0x30,0x71,0x06,0x34,0x8b,0x5e, - 0x7a,0x57,0x62,0x2a,0xb0,0x3f,0xbe,0xff,0x8e,0x0e,0x13,0x47,0x57,0x8e,0x39,0x3f, - 0xa4,0x5f,0xf6,0x20,0x72,0xcc,0x6c,0x99,0xe9,0xda,0x64,0x48,0xb5,0x9d,0xf8,0xa7, - 0x72,0x28,0xf8,0x31,0x54,0x57,0xbc,0x96,0x32,0x37,0x9d,0x81,0xdd,0x65,0x39,0xd5, - 0xd3,0x51,0x4d,0x0a,0xb2,0xa3,0x7a,0x38,0x63,0x66,0xc9,0x44,0x27,0x19,0x2f,0xcb, - 0xfc,0x88,0x4b,0xf3,0x47,0x55,0x0c,0x78,0x98,0x07,0xa5,0x6b,0x26,0xa8,0x51,0x34, - 0x70,0x82,0xab,0x65,0x1c,0x3c,0x98,0x57,0x7b,0xd1,0xb9,0xa9,0xb1,0xd4,0xd7,0x75, - 0xa9,0xd9,0x5e,0x34,0xca,0xce,0x1c,0x6d,0x31,0x63,0xe3,0x07,0x65,0xda,0x10,0x61, - 0xcf,0x5e,0xd6,0x45,0x8c,0xb9,0x3b,0xcb,0x90,0x81,0xc9,0x5d,0x37,0xf7,0xd7,0xf3, - 0x15,0xea,0x85,0xf8,0xaa,0x36,0x55,0xb5,0xd1,0x2f,0xb5,0x50,0x6e,0x5f,0x3d,0x4b, - 0x67,0xb6,0xa4,0xbc,0xec,0xaa,0xc6,0x41,0x03,0x2e,0x8a,0x2c,0xcf,0xc1,0x1a,0xd7, - 0x3a,0x98,0x4e,0xa3,0x15,0x94,0x22,0xfd,0x6d,0xb5,0x07,0x19,0x6e,0xed,0xe5,0xd4, - 0xc6,0xbd,0x15,0x40,0x6e,0x2c,0x40,0x6f,0xe3,0x75,0x10,0x6b,0xfc,0xde,0x4a,0x57, - 0x3f,0xf0,0xdb,0xd5,0x3f,0x27,0xf5,0xbf,0xe2,0xf5,0x68,0xdf,0x5f,0x4a,0x00,0x00, + 0x2e,0x2c,0x68,0x33,0x20,0xda,0x34,0x99,0x4f,0xd8,0x90,0xc0,0xa3,0x46,0x12,0xdf, + 0x23,0x19,0x20,0x45,0x61,0x3b,0x91,0x89,0xfd,0x70,0x96,0x0f,0x34,0x7e,0xa4,0x5b, + 0xef,0xab,0xc1,0x6f,0x30,0xd8,0x16,0xfa,0xe1,0x2d,0xcd,0x5f,0x1e,0x8f,0xae,0x80, + 0x2f,0x19,0x88,0x7d,0x09,0xb1,0x5c,0xde,0xc4,0x6a,0x34,0xad,0x91,0x8a,0xde,0x1e, + 0xd5,0xb9,0x1a,0x09,0xa8,0xf5,0xa5,0x44,0x45,0x49,0x5b,0xb3,0x43,0xc0,0x9f,0xc0, + 0x00,0xe3,0x90,0xc6,0xfe,0x69,0xa7,0x63,0x67,0xbf,0x19,0xbc,0xff,0x38,0x1e,0x1b, + 0x70,0xc3,0x8b,0xcb,0x20,0x67,0x82,0xeb,0x2c,0x5e,0x0d,0xbe,0x19,0xa1,0x33,0x48, + 0xf9,0xfa,0xa2,0x09,0x30,0x01,0x19,0xd4,0xab,0xe6,0x82,0x9d,0xb1,0x4c,0x85,0x3d, + 0xb7,0x9a,0x9e,0xbe,0x01,0x62,0x08,0x27,0x00,0x5a,0xf8,0x4e,0xd7,0xaf,0x52,0xd1, + 0x26,0x5c,0xc1,0x72,0xd7,0xa7,0x10,0xf5,0xbd,0x1f,0x21,0x9d,0xb6,0x06,0x60,0x74, + 0x97,0xba,0x99,0x9b,0x5d,0x50,0x6b,0x90,0x8d,0xd8,0x84,0x09,0x9c,0xf5,0xe1,0x60, + 0xb8,0xa4,0x26,0x46,0x75,0x1e,0xda,0x0e,0x1c,0x51,0x9d,0xfd,0xd5,0x00,0x8e,0x67, + 0x8b,0x01,0x73,0x23,0x31,0x4f,0x55,0x9c,0x00,0xdb,0x4d,0x69,0xcd,0xfc,0xcc,0x81, + 0x86,0x38,0x64,0xa9,0x7a,0x1f,0x71,0x11,0x63,0xeb,0xa1,0xb2,0xc2,0xaa,0x50,0xbb, + 0x3b,0x8b,0x23,0x03,0xd7,0xc5,0x4d,0x01,0x09,0xb3,0xf8,0x4a,0x42,0x35,0x66,0x72, + 0x60,0x3b,0xfd,0x7d,0x27,0xa7,0x07,0xc4,0xe9,0x93,0x9d,0x9c,0x56,0xa7,0x98,0x2b, + 0xec,0xb5,0xa3,0x1b,0xb3,0x49,0x9b,0xac,0x58,0x97,0xb5,0xd9,0xc6,0x81,0x80,0x4b, + 0xd2,0xdc,0x30,0xa3,0x5d,0xe9,0xce,0xb9,0x7f,0xa0,0x63,0x82,0x23,0x92,0xc1,0xd1, + 0x4a,0x4e,0xcf,0x35,0x8e,0xd4,0x84,0xdb,0xf8,0x16,0x88,0x9c,0xf6,0x0f,0x8e,0x98, + 0x52,0xad,0xe5,0x66,0x1a,0x27,0x92,0xbf,0xda,0xbb,0x87,0x70,0x67,0xc8,0xb1,0x4b, + 0xbc,0x4b,0x7a,0xae,0xf1,0x8e,0xd9,0xdf,0x16,0xd9,0x44,0xd0,0xe6,0x62,0x22,0x6b, + 0x4a,0x97,0x18,0x82,0xad,0x53,0xae,0xb7,0x41,0xb4,0xe1,0x89,0xd3,0x48,0x7e,0xaa, + 0xef,0x9b,0xfd,0x11,0xbd,0x02,0x80,0x61,0xeb,0x94,0xf1,0xb7,0xb2,0xaf,0x4d,0xb3, + 0x36,0xb0,0xd9,0x27,0x01,0xdd,0xc3,0xe9,0x1d,0x51,0xb2,0xbb,0x02,0x54,0x9f,0xbe, + 0x2f,0xc2,0xd1,0x15,0xc1,0x49,0xf6,0x01,0x46,0xce,0xb3,0x4c,0xf1,0xce,0x80,0x9c, + 0x91,0x40,0x79,0xe5,0x2a,0xb0,0x87,0x57,0xec,0xc2,0xa6,0xe7,0x6a,0xa8,0xe9,0x48, + 0x0f,0xc0,0xc0,0x88,0xd1,0x85,0x0d,0x29,0x15,0xe7,0xaa,0x48,0xba,0xe7,0xb3,0xa8, + 0xf3,0x06,0x68,0x18,0x58,0xf9,0x8a,0x1e,0x3e,0x09,0x8b,0x70,0x86,0x6c,0x0e,0x80, + 0xcc,0xcf,0x29,0x87,0xda,0xf2,0xb6,0x9d,0x4d,0x17,0x4f,0x4f,0xdf,0xbd,0x7f,0xdb, + 0xe1,0xd0,0xd0,0x3e,0xcb,0xf3,0x44,0xd2,0x57,0x9b,0x40,0xc1,0x09,0x4c,0xb8,0x90, + 0xe3,0xa0,0x35,0x55,0x2a,0x2f,0xfd,0x6e,0x77,0x82,0x4d,0xea,0x7c,0xe8,0x62,0x37, + 0xda,0x95,0xe9,0x64,0x36,0x4f,0x27,0xdd,0xc6,0x76,0xb3,0x3b,0x4c,0xb2,0x61,0x77, + 0x16,0xc6,0x69,0x37,0xca,0x46,0x65,0x17,0x26,0x1d,0x77,0x42,0xb3,0xdd,0x98,0x45, + 0xad,0xd3,0x92,0x36,0xd2,0x8e,0xde,0x08,0x0c,0x6f,0x0d,0xcc,0x08,0x4f,0xff,0xa8, + 0xa9,0xca,0x5c,0x8e,0x78,0x1e,0xbd,0x63,0x4e,0x15,0x84,0xa7,0x68,0x02,0xaa,0x4f, + 0xf0,0x42,0xf7,0x4c,0xd4,0xaa,0x5e,0xde,0xd8,0xab,0x9f,0xd1,0xfe,0x53,0x3b,0xae, + 0x1f,0x0c,0x07,0xc5,0xf9,0x0c,0x50,0xc8,0x05,0x7a,0x7f,0x91,0x48,0xfa,0xfa,0xfc, + 0xf6,0x55,0x84,0x1e,0xcb,0x3d,0x7a,0xca,0x9d,0xce,0x75,0x02,0x7e,0xcd,0x80,0xe2, + 0xc7,0x05,0xbd,0xa5,0x11,0x8c,0xc3,0xa4,0x94,0x4e,0x54,0x84,0x93,0x09,0x2c,0xc3, + 0x5c,0xd2,0xe6,0x80,0x37,0x2c,0x32,0x32,0x2d,0xf3,0x72,0xf8,0x33,0x3d,0xa5,0x5f, + 0x35,0x0c,0xf6,0xba,0x5d,0x0e,0x08,0x1c,0xb0,0x4c,0x29,0xb2,0xac,0x9f,0x28,0x82, + 0x39,0xd0,0x63,0x44,0xf3,0x94,0x2b,0x20,0x42,0x46,0xb1,0xd2,0x19,0x49,0x9f,0x48, + 0x89,0x9b,0x18,0x5b,0x0d,0xd8,0x27,0xf9,0x49,0x16,0x46,0x44,0xac,0x7e,0x18,0x09, + 0xeb,0xcf,0xf8,0x71,0xfd,0x71,0x5c,0xcc,0x6e,0xc2,0x42,0x1a,0xbf,0xc3,0xd6,0x91, + 0xaa,0x61,0x4c,0xd9,0x5d,0x09,0x00,0xba,0xe1,0x95,0xb4,0xaf,0xed,0xc5,0x6a,0x59, + 0x6f,0x5f,0xba,0xfc,0x16,0x4a,0x70,0xbd,0x24,0xe2,0x67,0x98,0xa8,0x9c,0x27,0x8a, + 0x78,0x2c,0xc3,0x38,0x12,0x43,0xec,0xc4,0x22,0x29,0x6a,0x81,0x67,0x89,0x8e,0x74, + 0xa3,0x70,0x4e,0x0e,0x1c,0x2b,0x5f,0x94,0x09,0xf8,0x43,0x16,0x11,0x6d,0x53,0xab, + 0xb1,0x1d,0x22,0x75,0xc5,0x4d,0x0c,0xff,0x6c,0xaa,0x4f,0x64,0xba,0x0b,0x3b,0x0d, + 0x36,0x87,0xef,0x24,0xc2,0xeb,0xb5,0xa4,0x47,0xb1,0xd4,0x94,0xe9,0xeb,0xc3,0x35, + 0xae,0x65,0xd0,0xe3,0x54,0x04,0x5c,0xa3,0x06,0xff,0x48,0xfb,0xaa,0xcd,0x86,0x5f, + 0x3a,0xd9,0xd5,0x7b,0xf9,0x49,0x39,0x34,0xb3,0xbd,0x80,0x2f,0x93,0xd2,0xa8,0x7a, + 0x1b,0x90,0x38,0x7e,0x7a,0xf7,0xfd,0xa5,0x0c,0x8b,0xd1,0xf4,0x2d,0x77,0x6e,0xdb, + 0x8e,0x4c,0x82,0x3f,0xb5,0xa9,0xf3,0xe7,0xcf,0x16,0xb8,0xb0,0xec,0x01,0xc6,0xc0, + 0xd9,0xda,0x34,0x8e,0x4e,0x5d,0xb5,0x43,0x95,0x36,0xd1,0xe0,0x17,0x28,0xae,0x1c, + 0xdd,0xf2,0xe1,0xea,0x23,0x77,0x5e,0x7b,0xa3,0x40,0xbf,0xe8,0x43,0x16,0x69,0x39, + 0x0b,0xb8,0xe1,0x34,0x8b,0x7c,0xeb,0xed,0x8f,0x97,0xef,0x2d,0x87,0x08,0xf8,0xf4, + 0xb1,0xdc,0xf1,0xce,0x81,0x21,0xb2,0x7a,0x83,0x60,0xfb,0xa5,0x17,0x5d,0xf3,0x19, + 0xb7,0x23,0x37,0xbb,0x0a,0x02,0x6d,0x42,0xf6,0x02,0x4a,0x2a,0xc3,0x5b,0x34,0x4a, + 0x7a,0x4d,0x01,0x8b,0x18,0x87,0x50,0x74,0x64,0x39,0x16,0x1a,0x2c,0x87,0x4e,0xe1, + 0x2b,0xcb,0x5e,0x32,0x85,0x9c,0xce,0x08,0x40,0x6f,0x00,0x52,0x5a,0x56,0xb6,0xa1, + 0x61,0x24,0x67,0x65,0x57,0xeb,0xe3,0x68,0x91,0x60,0x68,0x44,0xb6,0xdb,0x7c,0x19, + 0xc3,0x0c,0xb3,0xd2,0xcc,0xe8,0x67,0x55,0xb2,0xe2,0x2c,0xdb,0xe0,0x61,0x09,0x49, + 0x2d,0x1b,0xf6,0x06,0x8b,0xfe,0x7b,0xd2,0xd9,0x50,0xa5,0xec,0x47,0x46,0x6b,0xbc, + 0xc8,0x3f,0xb5,0x2d,0x5d,0xbe,0xb5,0x6c,0x57,0xef,0x94,0xe8,0x82,0x98,0xc0,0x84, + 0xf4,0xea,0x12,0x8f,0x62,0x95,0x2f,0xcc,0x81,0x7d,0xa9,0x96,0x15,0x85,0x1d,0x82, + 0x23,0x99,0xed,0xef,0x93,0xd8,0x1e,0x54,0x62,0xab,0x0d,0x5e,0x5f,0x6a,0xf6,0x60, + 0x98,0x3f,0xc2,0xe1,0x78,0x67,0xe5,0x9b,0x9d,0x14,0xd5,0x2e,0xf4,0xde,0x48,0x43, + 0x27,0x29,0xe8,0xb4,0xcf,0xa9,0xf7,0x4b,0x75,0x6d,0xc3,0xdc,0xe5,0xd3,0x29,0xb6, + 0x71,0xcf,0xf7,0xea,0x7c,0x1a,0x47,0x51,0x22,0x5d,0x41,0x2f,0x7b,0x00,0x59,0xaa, + 0xe2,0x96,0x7c,0xe8,0xc3,0x0c,0xce,0x14,0xe7,0x49,0x4c,0x86,0x8d,0xc8,0x14,0x97, + 0x59,0xf1,0x91,0x9e,0x20,0x4c,0x6e,0xd9,0xf1,0x63,0x0d,0x96,0xc4,0xa9,0xf0,0x5c, + 0x0e,0x3d,0xdf,0x9d,0xbd,0xfa,0xe1,0x32,0xf8,0xf0,0xe1,0xd8,0xf1,0x3e,0x3a,0x1f, + 0x0e,0xf8,0xf3,0x31,0x7f,0xf6,0xf9,0xd3,0x33,0x9f,0x7d,0xfe,0x7c,0xcc,0x9f,0x07, + 0xfc,0x79,0xc4,0x9f,0xc7,0xba,0x8f,0xbe,0xed,0x1d,0x7d,0xfc,0x38,0x58,0x69,0x64, + 0x02,0xab,0x78,0x45,0xa9,0xb2,0x0d,0xae,0x1c,0x70,0xc3,0xaa,0xa8,0xfc,0x20,0x0e, + 0x7a,0x83,0xf8,0x84,0xe7,0x77,0x75,0xed,0x7b,0x10,0x3f,0x7a,0x64,0x43,0xb0,0xdc, + 0xf6,0x21,0xfe,0xf8,0xa1,0xf7,0x11,0x36,0x89,0xb1,0xfb,0xfb,0x75,0x93,0x47,0x4d, + 0x44,0xca,0x18,0x52,0xdc,0xf0,0x16,0xf4,0x3c,0xf5,0x9e,0xf5,0xfc,0x26,0xcd,0x8e, + 0xb7,0x66,0x24,0xc4,0x12,0xa9,0xb4,0x3d,0xa9,0xdd,0x63,0x82,0x69,0x30,0xac,0x6d, + 0xfd,0xe3,0xbc,0xd7,0x8b,0x9e,0x58,0x8f,0xa8,0xc1,0xf6,0xdb,0x13,0xcc,0xc5,0xed, + 0x5e,0x97,0xda,0x3c,0xb4,0x59,0xbc,0xdd,0xb5,0x6c,0x56,0x6b,0xbf,0x57,0x2b,0x2c, + 0xe4,0xac,0x38,0x10,0x84,0xda,0xb4,0x8a,0x25,0xe1,0xb4,0x8c,0xfe,0x34,0x22,0x4a, + 0x91,0xdd,0xe8,0xb9,0x1d,0x60,0xbd,0x3a,0x8e,0x40,0xdb,0xef,0x31,0x3a,0xe8,0xf7, + 0xbe,0xa5,0x99,0xbb,0x34,0xd5,0xc0,0xdc,0x53,0x41,0x83,0xe1,0x47,0x96,0x20,0x16, + 0x87,0x4f,0x84,0xf5,0xa8,0xcd,0xef,0x5b,0xf1,0x93,0x28,0x6d,0x43,0xe0,0x5b,0xaf, + 0x67,0x77,0xf1,0xef,0x91,0xd5,0x25,0x6e,0x2c,0xa2,0x01,0x61,0xd2,0x54,0xea,0x51, + 0x70,0xc7,0x58,0x70,0xdc,0xdd,0x1a,0xaf,0x11,0xa6,0xd5,0x90,0xac,0x5a,0xf7,0x34, + 0x7e,0xde,0x14,0x81,0x6e,0x2b,0xbf,0xfd,0xd3,0x1c,0x40,0x5c,0xe3,0xa5,0xac,0x38, + 0x4b,0x92,0xb6,0xb5,0xeb,0xe9,0x4e,0x12,0x60,0x4d,0x2c,0x2c,0x0a,0xca,0x10,0x86, + 0xd2,0x59,0x51,0x84,0xb7,0x6e,0x5c,0xf2,0x5f,0xb4,0x3f,0xbb,0xf6,0x3f,0x5c,0x3b, + 0xfc,0xdf,0xc7,0xc6,0xa0,0x2a,0xde,0x90,0x0c,0x39,0x79,0x46,0x95,0xc4,0x90,0x34, + 0x82,0xc8,0x2d,0x54,0xfe,0x56,0x82,0xc5,0xd3,0xde,0x40,0x3b,0x3f,0x3b,0x7e,0xf3, + 0x95,0xc5,0x07,0xe8,0x44,0xa8,0x7a,0x26,0x9f,0x59,0xd9,0x78,0x6c,0xf9,0x6d,0x8c, + 0x84,0xb2,0xeb,0x73,0x1a,0xc2,0x10,0x90,0x55,0x4d,0x0b,0x51,0xd0,0xb2,0x6d,0x7a, + 0x93,0x12,0x1a,0x4e,0x91,0x92,0x75,0x54,0xaf,0x68,0x33,0xb4,0xfb,0x81,0xde,0xa5, + 0xb2,0xca,0xf9,0x90,0x46,0x82,0x12,0x05,0x88,0xd5,0x2c,0x57,0x96,0x6f,0xe9,0x41, + 0x9c,0xf3,0x65,0x18,0x21,0x4b,0x58,0xf4,0x57,0xb7,0xd2,0x2b,0x62,0x75,0x7f,0x7b, + 0xc1,0x6f,0x27,0x34,0xc8,0xf2,0x9b,0x11,0xc4,0xea,0xc0,0x8c,0xea,0x6c,0xae,0xc9, + 0x62,0xc8,0x45,0x99,0x94,0x93,0x9e,0xdc,0xe8,0x9e,0x6f,0x76,0x7f,0xcf,0x0e,0xc4, + 0x41,0x48,0x97,0xee,0xad,0x01,0x85,0x73,0x89,0xe8,0x45,0xcc,0x60,0x01,0x77,0x71, + 0x91,0x7e,0x81,0x89,0xf3,0x5a,0x82,0x70,0x80,0x0d,0x09,0xae,0x8e,0x60,0xf4,0x92, + 0x59,0x7e,0x77,0x30,0x57,0x17,0xb6,0x63,0x72,0x30,0x5e,0x1a,0xef,0xfd,0xa8,0xfe, + 0x29,0x81,0x51,0xc0,0x2e,0x11,0xa7,0x40,0xfd,0xcc,0x12,0xaf,0x94,0xc6,0x2e,0xf5, + 0x01,0x8c,0xc8,0xea,0x87,0xbb,0x5d,0x2d,0xf8,0x6a,0x6d,0x3b,0xd7,0x74,0xf7,0x82, + 0xcc,0x14,0x6d,0xeb,0x3c,0x4c,0x12,0xaa,0x83,0x5b,0x8f,0x74,0xd3,0x23,0xeb,0xd7, + 0xbf,0xfe,0xa7,0x05,0x93,0xf8,0xbe,0x36,0x89,0x2f,0xaf,0xa9,0x62,0xf6,0x15,0x1d, + 0xee,0x10,0x25,0x7d,0xb2,0x84,0x40,0x52,0xba,0xd8,0x74,0xd0,0x03,0x59,0x8c,0x75, + 0xaa,0x43,0xa3,0xae,0xf8,0x41,0xaa,0x9b,0xac,0xb8,0x12,0x95,0x62,0xeb,0x7d,0xaf, + 0x3e,0x26,0x2a,0x9b,0xe7,0x44,0xb4,0xca,0xf3,0xe6,0x31,0xcf,0x2d,0x55,0x8c,0x2b, + 0x79,0x33,0xcc,0xa1,0x14,0xc9,0x42,0x59,0x3b,0xb1,0x61,0x82,0xab,0xa3,0x30,0x16, + 0x23,0xbd,0x2c,0x4b,0xa7,0x04,0xda,0x22,0xc8,0x32,0x6b,0x08,0x67,0x6f,0xe7,0x52, + 0xbd,0x30,0xf2,0x10,0xe3,0x12,0xe6,0xe4,0x70,0x53,0xa7,0x5b,0xe7,0x88,0xac,0x41, + 0xf0,0x09,0x05,0xb6,0x49,0xac,0xf8,0xfa,0xc8,0xb2,0x6b,0x3f,0x59,0xa3,0xc4,0x67, + 0xe7,0xae,0x39,0x7d,0x0f,0xcc,0x30,0x74,0xa5,0x47,0xac,0xaa,0x79,0x69,0x5d,0x1b, + 0x93,0x62,0x82,0x38,0x07,0x6b,0x1d,0x0b,0x91,0xcd,0xe7,0x09,0xc9,0x8c,0x3e,0x7f, + 0x3e,0xec,0xf5,0x0e,0x28,0xd8,0x91,0xa3,0x8b,0xc6,0x4e,0xa0,0xa2,0x45,0xdb,0xb9, + 0x2d,0x05,0x8e,0xf4,0xb9,0x13,0x72,0x50,0xef,0x99,0x05,0x19,0x21,0x6e,0xa0,0x23, + 0x9b,0x84,0xb9,0x55,0x31,0x3e,0x4a,0xd5,0x36,0x27,0xc5,0xa7,0xcf,0x9f,0x39,0xc2, + 0x56,0xc1,0x45,0x71,0x43,0x15,0x13,0x10,0xab,0x31,0x47,0x36,0x57,0x54,0xd3,0x01, + 0xae,0xb0,0x80,0xc2,0x2b,0x76,0xf8,0x60,0x68,0x3d,0xd0,0x8c,0xa6,0xa0,0x81,0x56, + 0x48,0x0f,0x1e,0xd9,0x10,0x1a,0x1f,0xb0,0xdc,0xd5,0x17,0x1d,0xa9,0x7b,0x3d,0x29, + 0xb2,0x66,0x19,0x50,0x14,0xd6,0x33,0xcf,0x93,0xcf,0x9f,0x3d,0x9b,0xb2,0x75,0xa3, + 0xf5,0x22,0xbe,0xa6,0xd6,0x6a,0x48,0x99,0x04,0x75,0x0e,0x68,0x42,0xdb,0x12,0x29, + 0xbd,0x3c,0x39,0x18,0x94,0xc8,0xe3,0x35,0xb4,0x7c,0x50,0xed,0x61,0xec,0x32,0xf9, + 0x50,0x7e,0x34,0x26,0xb3,0x86,0x0d,0x4a,0xb4,0xf3,0x8c,0xf8,0x6b,0xe2,0x02,0x91, + 0x9b,0x04,0x3a,0xed,0xeb,0xf7,0xd2,0xdb,0x8d,0xe1,0xf6,0x47,0xdd,0x4b,0x37,0x01, + 0x3a,0x43,0xbe,0x3f,0xd0,0x91,0xcd,0x5a,0x02,0x42,0xf6,0xd9,0xb4,0x86,0xf5,0x14, + 0xcc,0x20,0x74,0x6f,0x9d,0xcb,0xfd,0xfd,0x07,0x8d,0x5d,0x96,0x59,0x06,0x2f,0x8e, + 0xe4,0x48,0xcc,0xba,0xec,0xb6,0x2b,0x34,0x38,0x73,0x56,0x3b,0xbd,0x99,0x06,0x2c, + 0x25,0x12,0x39,0x82,0x3f,0xad,0x28,0x36,0x80,0x85,0x9a,0x96,0x8d,0xa0,0x87,0x39, + 0x48,0x43,0xfa,0x9c,0x27,0x20,0xe2,0x15,0x37,0xbc,0x49,0x80,0x21,0xdc,0x9d,0x54, + 0x1f,0xf2,0x2e,0x47,0x3f,0xad,0xb7,0xbe,0xb9,0xd0,0xa0,0xea,0x80,0x91,0xd4,0x8a, + 0xf5,0xeb,0x40,0xab,0xf1,0x6d,0x56,0x7e,0xfe,0xfc,0xe1,0xe8,0xc0,0xa9,0xfe,0xff, + 0x68,0x83,0x3f,0xcd,0xd3,0x15,0xb1,0xba,0x29,0xbf,0xb8,0xf6,0x3b,0xfd,0x36,0x30, + 0x19,0x50,0xbb,0x7d,0xdd,0x05,0x6c,0x00,0x5c,0x20,0x4b,0xfe,0x86,0x02,0x60,0xef, + 0x1b,0xeb,0x0b,0x44,0xdc,0xeb,0x0d,0x25,0x10,0x95,0x6b,0xdf,0xfa,0xdb,0x7f,0x69, + 0x90,0xb2,0xd4,0x46,0x5b,0x55,0x5d,0xb7,0x5c,0xbd,0xb2,0xda,0xa6,0xa3,0x93,0x01, + 0xd7,0xc2,0x7b,0xb0,0x4a,0x9b,0x84,0x53,0x1a,0xce,0x92,0xa5,0x5c,0xc1,0x08,0xd6, + 0x37,0x18,0x8c,0xf7,0x29,0xaa,0x18,0xc0,0x6f,0x1c,0xce,0xb7,0x68,0xd0,0xd2,0xb1, + 0xb4,0x74,0x4b,0xa9,0xc3,0x62,0x5c,0xe8,0xb3,0x15,0xec,0x3c,0xae,0x78,0x33,0x67, + 0x2f,0x07,0x7b,0x0d,0x27,0xfb,0x8d,0x73,0xd0,0xa0,0xf5,0x39,0x8a,0xb5,0x63,0xcf, + 0xcd,0x69,0x8c,0xa1,0x60,0x16,0x2e,0x2f,0x35,0xa7,0x69,0x56,0x03,0x54,0x31,0x97, + 0xb4,0xfb,0xa2,0x29,0x2b,0xc1,0xe8,0x57,0xe8,0xaf,0x57,0xbe,0x0a,0x13,0xd4,0xfe, + 0x33,0xd8,0xe1,0x5e,0xd7,0x70,0x2a,0xe6,0xd5,0x00,0xe2,0x85,0x8e,0x06,0x3e,0x5d, + 0x2e,0xfd,0x85,0x8e,0x02,0x3e,0xa1,0x53,0xb0,0x5f,0x9f,0x48,0xd2,0x4b,0x72,0x9c, + 0xe7,0x1b,0x40,0xb5,0x5a,0xc1,0x12,0x2b,0xd0,0x80,0x2e,0x2f,0x32,0x95,0x11,0x02, + 0xac,0x5e,0x4f,0xe7,0x5f,0x63,0x68,0xd7,0x8c,0x35,0x7e,0x71,0x21,0x61,0xbb,0x95, + 0x09,0x56,0xcc,0xc0,0xb1,0xb9,0xe0,0xba,0x1a,0xc2,0xab,0xad,0x02,0xcc,0x96,0x37, + 0x55,0xeb,0x66,0x59,0xe8,0xf5,0xfe,0x56,0x36,0xe8,0x77,0x0a,0x4c,0xa4,0xba,0x5e, + 0xda,0xcb,0x6a,0x2a,0xc2,0x9a,0xb6,0x06,0x9f,0xfc,0x75,0x39,0xa8,0x78,0xdd,0xd6, + 0xce,0x7a,0xe9,0x66,0x97,0xc4,0x57,0xfc,0x55,0xf1,0x8c,0xf7,0x03,0xc1,0xd7,0x15, + 0x50,0xaf,0xfd,0xc1,0xf6,0xe2,0x73,0x0e,0x0b,0x41,0x73,0x8e,0x89,0x54,0x67,0x4a, + 0x15,0xf1,0x10,0x39,0xb7,0x6d,0xe9,0xf3,0x41,0x18,0xd8,0x23,0x4f,0xd3,0xd1,0x95, + 0x0e,0x67,0x5b,0x90,0xcf,0x6a,0x3d,0xc3,0xfd,0x5e,0x5b,0x8f,0x34,0x69,0xbd,0x11, + 0xe9,0x7b,0x4f,0xfb,0x77,0xa8,0x1d,0x4e,0x68,0xb3,0xed,0xb2,0x97,0xee,0x92,0x8d, + 0xf1,0x89,0xd4,0x6f,0x9a,0xeb,0x33,0xcb,0xc3,0x34,0x3d,0x78,0xc7,0x7a,0xab,0x39, + 0x63,0xc0,0x3d,0xfd,0x0d,0x98,0xd6,0xb1,0xb2,0x6a,0xaa,0x26,0x1a,0xd9,0x61,0x32, + 0xf5,0xa6,0x9c,0x4c,0xc6,0xf4,0xe7,0x47,0xc7,0xb8,0xbb,0x7e,0x64,0x2a,0x68,0xfe, + 0xde,0x87,0x84,0x95,0x20,0xc0,0xa7,0xea,0x42,0x8e,0x43,0x6c,0xa6,0x91,0xe1,0xf4, + 0x0f,0x51,0x6c,0xc1,0x1e,0x17,0x12,0x9d,0xe9,0x04,0x58,0xd7,0x1e,0x98,0xf3,0xcd, + 0xf8,0x47,0x77,0x2d,0xdb,0x99,0x3a,0xd3,0x67,0x96,0xae,0xbb,0xd1,0x63,0x15,0xb1, + 0x41,0x81,0x58,0x17,0x3f,0x15,0x25,0xa3,0x6a,0x3d,0x2b,0xcc,0xb3,0x23,0xb6,0x18, + 0x2b,0xdc,0xdf,0xa7,0x4f,0x02,0x41,0xf6,0xda,0xe4,0x4e,0xd5,0xec,0xec,0x9a,0xca, + 0x4c,0x50,0x3f,0x85,0xb5,0x7b,0x8a,0x75,0x82,0x16,0xa4,0x6d,0x18,0x64,0x62,0xf4, + 0x50,0x9b,0x79,0x1c,0xa4,0xf9,0x5c,0x1b,0xd3,0xe6,0x6a,0x47,0xa3,0x9e,0x68,0x95, + 0xf4,0x7b,0x39,0xd2,0xa7,0xda,0x78,0xcc,0xa7,0x01,0xb9,0xae,0xe3,0xe9,0xca,0x21, + 0x75,0x54,0xe1,0x90,0x90,0xad,0x7e,0x2e,0xd0,0xa9,0xb0,0x28,0x51,0xaa,0x9e,0xa4, + 0xa2,0x07,0x48,0xa6,0x71,0xee,0x70,0x5d,0xd2,0x1c,0x34,0x4d,0xa4,0x73,0x9f,0xc7, + 0xe6,0xdd,0x3d,0x84,0x62,0xfe,0xfd,0x90,0x66,0x0d,0xcd,0x03,0xf4,0x73,0x36,0x8b, + 0x39,0x76,0xb5,0xa7,0x24,0xdb,0xc5,0xec,0x3f,0x5d,0x3e,0xd7,0x25,0x6c,0xf9,0x29, + 0xa6,0x47,0x4a,0xb8,0x84,0x12,0x53,0xe9,0x9b,0x7f,0x5f,0x04,0x13,0x67,0x40,0xb3, + 0xe8,0xa5,0x77,0x25,0xa6,0xc6,0xfa,0xe3,0xfb,0xef,0xe8,0x28,0x70,0x74,0xe5,0x98, + 0xd3,0x3f,0xfa,0x5d,0x0e,0x22,0xc7,0xcc,0x96,0x99,0xae,0x3e,0x86,0x54,0xbd,0x89, + 0x7f,0x2a,0x87,0x82,0x1f,0x22,0x75,0xc5,0x6b,0x29,0x73,0xd3,0x19,0xd8,0x5d,0x96, + 0x53,0x3d,0x1d,0x55,0x9d,0x20,0x3b,0xaa,0x78,0x33,0x66,0x96,0x4c,0x74,0x92,0xf1, + 0xb2,0xcc,0x4f,0xb0,0x34,0x7f,0x12,0xc5,0x80,0x87,0x79,0x50,0xba,0x66,0x82,0x1a, + 0x45,0x03,0x27,0xb8,0x5a,0xc6,0xc1,0x83,0x79,0xb5,0xdb,0x9c,0x9b,0x2a,0x4a,0x7d, + 0x5d,0x17,0x93,0xed,0x45,0xa3,0xb0,0xcc,0xd1,0x16,0x33,0x36,0x7e,0x0e,0xa6,0x0d, + 0x11,0xf6,0xec,0x65,0x5d,0xa6,0x98,0xbb,0xb3,0x0c,0x19,0x98,0xdc,0x75,0x73,0x07, + 0x3d,0x5f,0xa1,0x5e,0x88,0xaf,0x6a,0x53,0x55,0x1b,0xfd,0xce,0x0a,0xe5,0xf6,0xd5, + 0x93,0x70,0xa6,0xea,0xc5,0xcb,0xae,0xaa,0x18,0x34,0xe0,0xa2,0xc8,0xf2,0x1c,0xac, + 0x71,0x35,0x83,0xe9,0x34,0x5a,0x41,0x29,0xd2,0xdf,0x56,0x7b,0x90,0xe1,0xd6,0x5e, + 0x4e,0x6d,0xdc,0x5b,0x01,0xe4,0xc6,0x02,0xf4,0x46,0x5d,0x07,0xb1,0xc6,0xaf,0xa5, + 0x74,0xf5,0xe3,0xba,0x5d,0xfd,0x63,0x50,0xff,0x0b,0x75,0x03,0x36,0xe6,0x1d,0x4a, + 0x00,0x00, }; constexpr size_t MIDI_INDEX_HTML_GZ_LEN = sizeof(MIDI_INDEX_HTML_GZ); // diff --git a/firmware/toolchain/editor_demo.py b/firmware/toolchain/editor_demo.py new file mode 100644 index 000000000..852d57aed --- /dev/null +++ b/firmware/toolchain/editor_demo.py @@ -0,0 +1,334 @@ +"""The audio extension's mapping editor as static files, for the Audio guide. + + python firmware/toolchain/editor_demo.py # write web/public/guide/audio-editor/ + python firmware/toolchain/editor_demo.py --check # is what is there current? + +The guide at /guide/audio shows the extension's real editor - the page the +popup's "Open editor" opens, not a screenshot - in a window on the page, and +what it maps drives the 3D board beside it (web/src/components/guide/ +EditorWindow.tsx is the other end). This writes what that window loads: + + index.html tools/patternflow-audio-extension/editor.html, with one + tag added: bridge.js, between the adapter and the editor + editor.css ) + mapping.js ) the extension's files, byte for byte (line ends + editor-adapter.js ) apart) + editor.js ) + bridge.js BRIDGE_JS below: the guide's end of the editor + +Nothing of the extension is rewritten. A page that is not an extension has no +chrome.storage, so editor-adapter.js runs its own demo source (a synthesized +spectrum; the bar says "demo source"), and bridge.js stands between that +adapter and the editor - its header comment says what it does and why. The +extension's folder is not touched: this only reads it. + +Rerun it after any change to those four files, to editor.html or to BRIDGE_JS; +`--check` exits 1 when the folder is stale. The folder is this script's alone: +it is emptied and rewritten each run. + +bridge.js maps levels to knobs the way the capture does (offscreen.js tick()), +with PFMap's own functions, but the few lines that join them are written +again there. CHAIN below is those lines as offscreen.js has them: if one is +gone the capture's chain has changed, this script stops, and BRIDGE_JS is to +be brought back in step before the list is. +""" + +import argparse +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +REPO = os.path.normpath(os.path.join(HERE, "..", "..")) +EXT = os.path.join(REPO, "tools", "patternflow-audio-extension") +OUT = os.path.join(REPO, "web", "public", "guide", "audio-editor") + +# Copied as they are. +VERBATIM = ["editor.css", "mapping.js", "editor-adapter.js", "editor.js"] + +# The one edit to editor.html. +ADAPTER_TAG = '' +BRIDGE_TAG = '' + +# What bridge.js follows, as offscreen.js writes it (see the docstring). +CHAIN = [ + "const TICK_MS = 33;", + "const REPORT_MS = 120;", + "const a = raw > levels[i] ? config.attack : config.smoothing;", + "levels[i] = a * raw + (1 - a) * levels[i];", + "if (auto) PFMap.trackEnvelope(envelopes[i], levels[i]);", + "const pos = PFMap.position(levels[i], bands[i], auto ? envelopes[i] : null);", + "const output = PFMap.output(bands[i], pos);", + "if (!bands[i].muted) lanes[bands[i].knob] = output;", + "if (running) config = PFMap.normalizeConfig(message.config, PFMap.SOURCE);", +] +# And the two things it leans on in the demo adapter. +ADAPTER = [ + "onFrame(fn) { frameFn = fn; demoFrame(); setInterval(demoFrame, 120); }", + "e && e.hi - e.lo > PFMap.ENV_MIN_SPAN + 1e-6 ? e : null);", +] + + +def read(name): + with open(os.path.join(EXT, name), encoding="utf-8", newline="") as f: + return f.read().replace("\r\n", "\n") + + +def render(): + """{file name: bytes} - everything the folder should hold.""" + for name, lines in (("offscreen.js", CHAIN), ("editor-adapter.js", ADAPTER)): + text = read(name) + for line in lines: + if line not in text: + raise SystemExit( + "%s no longer has the line\n %s\nbridge.js (BRIDGE_JS in " + "editor_demo.py) follows it: bring the bridge in step, then " + "this list." % (name, line)) + html = read("editor.html") + if html.count(ADAPTER_TAG) != 1: + raise SystemExit("editor.html: expected one %s" % ADAPTER_TAG) + html = html.replace(ADAPTER_TAG, ADAPTER_TAG + "\n" + BRIDGE_TAG) + head = "\n" + if not html.startswith(head): + raise SystemExit("editor.html: does not start with its doctype") + html = (head + "\n" + + html[len(head):]) + files = {"index.html": html, "bridge.js": BRIDGE_JS} + for name in VERBATIM: + files[name] = read(name) + return {n: t.encode("utf-8") for n, t in files.items()} + + +def main(): + for stream in (sys.stdout, sys.stderr): + try: + stream.reconfigure(encoding="utf-8", errors="replace") + except (AttributeError, ValueError): + pass + ap = argparse.ArgumentParser(description="Write the Audio guide's editor demo.") + ap.add_argument("--check", action="store_true", help="exit 1 if the folder is stale") + args = ap.parse_args() + files = render() + total = sum(len(b) for b in files.values()) + if args.check: + have = set(os.listdir(OUT)) if os.path.isdir(OUT) else set() + # A Windows checkout has the folder's files with CRLF line ends; what + # is written here, and what git stores, has LF. + def on_disk(n): + with open(os.path.join(OUT, n), "rb") as f: + return f.read().replace(b"\r\n", b"\n") + stale = sorted(n for n, b in files.items() if n not in have or on_disk(n) != b) + stale += sorted(have - set(files)) + if stale: + print("editor demo is stale: %s\nrun: python firmware/toolchain/editor_demo.py" + % ", ".join(stale)) + sys.exit(1) + print("editor demo current (%d files, %d bytes)" % (len(files), total)) + return + os.makedirs(OUT, exist_ok=True) + for n in os.listdir(OUT): + p = os.path.join(OUT, n) + if os.path.isfile(p): + os.remove(p) + for n, b in sorted(files.items()): + with open(os.path.join(OUT, n), "wb") as f: + f.write(b) + print("wrote %d files, %d bytes -> %s" % (len(files), total, os.path.relpath(OUT, REPO))) + for n, b in sorted(files.items()): + print(" %-18s %7d" % (n, len(b))) + + +# ── the guide's end of the editor ────────────────────────────────────────── + +BRIDGE_JS = r'''/* Patternflow Audio guide: between the editor and its demo source. + * GENERATED by firmware/toolchain/editor_demo.py -- edit BRIDGE_JS there, rerun. + * + * Loaded after editor-adapter.js and before editor.js. Outside the extension + * the adapter is its demo: a synthesized spectrum, the mapping in memory, and + * each band's output straight off its level. That is a preview of the editor. + * The guide shows the editor driving a board, so five things are added here, + * around window.PFAdapter and without a change to any of the extension's + * files: + * + * the capture's chain The knob values are worked out as the capture works + * them out (offscreen.js tick()): every 33 ms, the + * level glided by Attack and Damping, auto range's + * envelope at its real rate, PFMap.position and + * PFMap.output, and a muted band sends nothing. The + * editor is handed these values too, so its cursor + * and readout are what the board was told. + * to the board {type:'pf-audio', outputs:[v|null x4]} to the page + * that holds this frame, each tick: v is 0..1 as the + * extension's a= message carries it, null is a lane + * left alone (its '-'). + * one mapping per tab The mapping is kept in sessionStorage, so the + * miniature in the card and the window lifted from + * it are one editor: an edit in either is the other's + * at once (the storage event), and survives a reload. + * Esc {type:'pf-audio', action:'escape'} when Esc is + * pressed and the editor's Preview is not what it + * closes. + * its scroll its own A wheel or a finger that runs past the editor's end + * stops there. Without this the page that holds the + * frame scrolls on, and the guide's step changes + * under the window. + * + * The source stays the adapter's: its spectrum, its levels per band, its + * "demo source" chip. Nothing leaves this folder. + */ +(function () { + 'use strict'; + + var source = window.PFAdapter; + if (!source || typeof PFMap === 'undefined') return; + + var KEY = 'pf-audio-editor:mapping'; + var TICK_MS = 33; // offscreen.js TICK_MS + var REPORT_MS = 120; // offscreen.js REPORT_MS + var CAPS = source.caps(); + + var host = window.parent !== window ? window.parent : null; + function tell(message) { + message.type = 'pf-audio'; + if (host) host.postMessage(message, window.location.origin); + } + + // ── the mapping ───────────────────────────────────────────────────────── + + function stored() { + try { + var text = window.sessionStorage.getItem(KEY); + var raw = text ? JSON.parse(text) : null; + return raw && Array.isArray(raw.bands) ? raw : null; + } catch (e) { + return null; + } + } + function keep(config) { + try { window.sessionStorage.setItem(KEY, JSON.stringify(config)); } catch (e) { /* private mode */ } + } + // A copy made whole, as the capture takes one out of a 'config' message. + function whole(raw) { + return PFMap.normalizeConfig(raw ? JSON.parse(JSON.stringify(raw)) : null, CAPS); + } + + var config = whole(stored()); // what is being mapped with + var configFn = null; + + // The mapping changed somewhere else (the other frame of this tab). + function adopt(raw) { + if (!raw) return; + config = whole(raw); + source.saveConfig(whole(raw)); // the demo source listens where the boxes are + if (configFn) configFn(raw); + } + window.addEventListener('storage', function (e) { + if (e.key === KEY && e.storageArea === window.sessionStorage) adopt(stored()); + }); + + // ── the chain (offscreen.js tick()) ───────────────────────────────────── + + var levels = [0, 0, 0, 0]; + var envelopes = [0, 1, 2, 3].map(PFMap.newEnvelope); + var peakLevels = [-1, -1, -1, -1]; + var peakPos = [0, 0, 0, 0]; + var peakOutputs = [0, 0, 0, 0]; + var heard = null; // the demo source's last frame + var frameFn = null; + var lastReport = 0; + + // editor-adapter.js learned(): an envelope at its minimum span is not one. + function learned(e) { + return e.hi - e.lo > PFMap.ENV_MIN_SPAN + 1e-6 ? { lo: e.lo, hi: e.hi } : null; + } + + function tick() { + if (!heard || !heard.levels || heard.levels.length < 4) return; + var bands = config.bands; + var auto = config.autoRange; + var i; + + for (i = 0; i < 4; i++) { + var raw = PFMap.clamp01(heard.levels[i]); + var a = raw > levels[i] ? config.attack : config.smoothing; + levels[i] = a * raw + (1 - a) * levels[i]; + if (auto) PFMap.trackEnvelope(envelopes[i], levels[i]); + } + + var lanes = [null, null, null, null]; + for (i = 0; i < 4; i++) { + var pos = PFMap.position(levels[i], bands[i], auto ? envelopes[i] : null); + var output = PFMap.output(bands[i], pos); + if (!bands[i].muted) lanes[bands[i].knob] = output; + if (levels[i] > peakLevels[i]) { + peakLevels[i] = levels[i]; + peakPos[i] = pos; + peakOutputs[i] = output; + } + } + tell({ outputs: lanes }); + + var now = performance.now(); + if (frameFn && now - lastReport > REPORT_MS) { + lastReport = now; + frameFn({ + running: true, connected: false, demo: true, + levels: peakLevels.slice(), + pos: peakPos.slice(), + outputs: peakOutputs.slice(), + spectrum: heard.spectrum, + env: auto ? envelopes.map(learned) : [], + autoRange: auto + }); + peakLevels = [-1, -1, -1, -1]; + } + } + + // ── the adapter the editor talks to ───────────────────────────────────── + + window.PFAdapter = { + caps: function () { return CAPS; }, + // What this tab has stored, or nothing (a first run), as the extension's + // own adapter answers from chrome.storage. + loadConfig: function () { + var raw = stored(); + if (raw) source.saveConfig(whole(raw)); + return Promise.resolve(raw); + }, + saveConfig: function (next) { + keep(next); + config = whole(next); + return source.saveConfig(next); + }, + resetBand: function (index, current) { + return source.resetBand(index, current).then(function (next) { + keep(next); + config = whole(next); + return next; + }); + }, + onConfig: function (fn) { configFn = fn; }, + onFrame: function (fn) { + frameFn = fn; + source.onFrame(function (state) { heard = state; }); + tick(); + setInterval(tick, TICK_MS); + } + }; + + document.addEventListener('keydown', function (e) { + if (e.key !== 'Escape') return; + var preview = document.getElementById('pv'); + if (preview && !preview.hidden) return; // the editor closes its Preview + tell({ action: 'escape' }); + }); + + // This page scrolls at its root; what is left of a scroll at its end is + // not handed on to the page that holds the frame. + document.documentElement.style.overscrollBehavior = 'contain'; +})(); +''' + + +if __name__ == "__main__": + main() diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 9a5dd851a..05e000012 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -17,6 +17,10 @@ const eslintConfig = defineConfig([ // fflate vendored beside them. `console_demo.py --check` keeps it in step // with the firmware; lint rules for hand-written code don't apply. "public/guide/console-demo/**", + // The Audio guide's editor is generated the same way: the audio + // extension's own editor files plus a bridge to the page, by + // firmware/toolchain/editor_demo.py. + "public/guide/audio-editor/**", ]), // Layering: lib/ is the bottom. It is imported by app/ and components/ and // must not import from them — a type it needs from a component belongs in diff --git a/web/public/guide/audio-editor/bridge.js b/web/public/guide/audio-editor/bridge.js new file mode 100644 index 000000000..974a6750b --- /dev/null +++ b/web/public/guide/audio-editor/bridge.js @@ -0,0 +1,187 @@ +/* Patternflow Audio guide: between the editor and its demo source. + * GENERATED by firmware/toolchain/editor_demo.py -- edit BRIDGE_JS there, rerun. + * + * Loaded after editor-adapter.js and before editor.js. Outside the extension + * the adapter is its demo: a synthesized spectrum, the mapping in memory, and + * each band's output straight off its level. That is a preview of the editor. + * The guide shows the editor driving a board, so five things are added here, + * around window.PFAdapter and without a change to any of the extension's + * files: + * + * the capture's chain The knob values are worked out as the capture works + * them out (offscreen.js tick()): every 33 ms, the + * level glided by Attack and Damping, auto range's + * envelope at its real rate, PFMap.position and + * PFMap.output, and a muted band sends nothing. The + * editor is handed these values too, so its cursor + * and readout are what the board was told. + * to the board {type:'pf-audio', outputs:[v|null x4]} to the page + * that holds this frame, each tick: v is 0..1 as the + * extension's a= message carries it, null is a lane + * left alone (its '-'). + * one mapping per tab The mapping is kept in sessionStorage, so the + * miniature in the card and the window lifted from + * it are one editor: an edit in either is the other's + * at once (the storage event), and survives a reload. + * Esc {type:'pf-audio', action:'escape'} when Esc is + * pressed and the editor's Preview is not what it + * closes. + * its scroll its own A wheel or a finger that runs past the editor's end + * stops there. Without this the page that holds the + * frame scrolls on, and the guide's step changes + * under the window. + * + * The source stays the adapter's: its spectrum, its levels per band, its + * "demo source" chip. Nothing leaves this folder. + */ +(function () { + 'use strict'; + + var source = window.PFAdapter; + if (!source || typeof PFMap === 'undefined') return; + + var KEY = 'pf-audio-editor:mapping'; + var TICK_MS = 33; // offscreen.js TICK_MS + var REPORT_MS = 120; // offscreen.js REPORT_MS + var CAPS = source.caps(); + + var host = window.parent !== window ? window.parent : null; + function tell(message) { + message.type = 'pf-audio'; + if (host) host.postMessage(message, window.location.origin); + } + + // ── the mapping ───────────────────────────────────────────────────────── + + function stored() { + try { + var text = window.sessionStorage.getItem(KEY); + var raw = text ? JSON.parse(text) : null; + return raw && Array.isArray(raw.bands) ? raw : null; + } catch (e) { + return null; + } + } + function keep(config) { + try { window.sessionStorage.setItem(KEY, JSON.stringify(config)); } catch (e) { /* private mode */ } + } + // A copy made whole, as the capture takes one out of a 'config' message. + function whole(raw) { + return PFMap.normalizeConfig(raw ? JSON.parse(JSON.stringify(raw)) : null, CAPS); + } + + var config = whole(stored()); // what is being mapped with + var configFn = null; + + // The mapping changed somewhere else (the other frame of this tab). + function adopt(raw) { + if (!raw) return; + config = whole(raw); + source.saveConfig(whole(raw)); // the demo source listens where the boxes are + if (configFn) configFn(raw); + } + window.addEventListener('storage', function (e) { + if (e.key === KEY && e.storageArea === window.sessionStorage) adopt(stored()); + }); + + // ── the chain (offscreen.js tick()) ───────────────────────────────────── + + var levels = [0, 0, 0, 0]; + var envelopes = [0, 1, 2, 3].map(PFMap.newEnvelope); + var peakLevels = [-1, -1, -1, -1]; + var peakPos = [0, 0, 0, 0]; + var peakOutputs = [0, 0, 0, 0]; + var heard = null; // the demo source's last frame + var frameFn = null; + var lastReport = 0; + + // editor-adapter.js learned(): an envelope at its minimum span is not one. + function learned(e) { + return e.hi - e.lo > PFMap.ENV_MIN_SPAN + 1e-6 ? { lo: e.lo, hi: e.hi } : null; + } + + function tick() { + if (!heard || !heard.levels || heard.levels.length < 4) return; + var bands = config.bands; + var auto = config.autoRange; + var i; + + for (i = 0; i < 4; i++) { + var raw = PFMap.clamp01(heard.levels[i]); + var a = raw > levels[i] ? config.attack : config.smoothing; + levels[i] = a * raw + (1 - a) * levels[i]; + if (auto) PFMap.trackEnvelope(envelopes[i], levels[i]); + } + + var lanes = [null, null, null, null]; + for (i = 0; i < 4; i++) { + var pos = PFMap.position(levels[i], bands[i], auto ? envelopes[i] : null); + var output = PFMap.output(bands[i], pos); + if (!bands[i].muted) lanes[bands[i].knob] = output; + if (levels[i] > peakLevels[i]) { + peakLevels[i] = levels[i]; + peakPos[i] = pos; + peakOutputs[i] = output; + } + } + tell({ outputs: lanes }); + + var now = performance.now(); + if (frameFn && now - lastReport > REPORT_MS) { + lastReport = now; + frameFn({ + running: true, connected: false, demo: true, + levels: peakLevels.slice(), + pos: peakPos.slice(), + outputs: peakOutputs.slice(), + spectrum: heard.spectrum, + env: auto ? envelopes.map(learned) : [], + autoRange: auto + }); + peakLevels = [-1, -1, -1, -1]; + } + } + + // ── the adapter the editor talks to ───────────────────────────────────── + + window.PFAdapter = { + caps: function () { return CAPS; }, + // What this tab has stored, or nothing (a first run), as the extension's + // own adapter answers from chrome.storage. + loadConfig: function () { + var raw = stored(); + if (raw) source.saveConfig(whole(raw)); + return Promise.resolve(raw); + }, + saveConfig: function (next) { + keep(next); + config = whole(next); + return source.saveConfig(next); + }, + resetBand: function (index, current) { + return source.resetBand(index, current).then(function (next) { + keep(next); + config = whole(next); + return next; + }); + }, + onConfig: function (fn) { configFn = fn; }, + onFrame: function (fn) { + frameFn = fn; + source.onFrame(function (state) { heard = state; }); + tick(); + setInterval(tick, TICK_MS); + } + }; + + document.addEventListener('keydown', function (e) { + if (e.key !== 'Escape') return; + var preview = document.getElementById('pv'); + if (preview && !preview.hidden) return; // the editor closes its Preview + tell({ action: 'escape' }); + }); + + // This page scrolls at its root; what is left of a scroll at its end is + // not handed on to the page that holds the frame. + document.documentElement.style.overscrollBehavior = 'contain'; +})(); diff --git a/web/public/guide/audio-editor/editor-adapter.js b/web/public/guide/audio-editor/editor-adapter.js new file mode 100644 index 000000000..32cce65f2 --- /dev/null +++ b/web/public/guide/audio-editor/editor-adapter.js @@ -0,0 +1,203 @@ +// Patternflow Audio — editor adapter, extension flavor. +// +// The editor module (editor.js) knows nothing about Chrome, the panel, or +// where audio comes from: it talks to exactly window.PFAdapter. The panel's +// /audio-in page is the same editor over another adapter +// (firmware/patternflow/console/_audio_in_adapter.js, which speaks +// fetch('/api/audio-in')). That boundary is the whole architecture; do not +// let editor.js grow a chrome.* call. The contract both adapters keep is +// written at the top of editor.js. +// +// This file is also the extension's SOURCE BAR: the
in editor.html +// (what is feeding the editor, where it is going, Stop). The editor never +// looks in there; every state that arrives is drawn here first and then +// handed on. +// +// Without chrome.* (opened as a plain file, or from a static server) it runs +// a demo: a synthesized music-ish spectrum mapped by the same model, the +// mapping kept in memory. That is a preview of the editor, not of the +// product: nothing persists and nothing is captured. + +(function () { + const hasChrome = typeof chrome !== 'undefined' && chrome.storage && chrome.runtime; + const $ = (id) => document.getElementById(id); + + // What this source is: tab audio through an AnalyserNode. The numbers are + // the model's (PFMap.SOURCE), the two edge rules are its minimum window, and + // a band is never narrower than the 10 Hz the model keeps on load. + const CAPS = { + hzMin: PFMap.SOURCE.hzMin, + hzMax: PFMap.SOURCE.hzMax, + db: PFMap.SOURCE.db, + levelMax: 1, + hzGap: 10, + top: (lo) => lo + PFMap.MIN_WINDOW, + bottom: (hi) => hi - PFMap.MIN_WINDOW + }; + + // ── the source bar ──────────────────────────────────────────────────── + // + // The chip is the capture's own phase (offscreen.js phase()), in the same + // word the popup prints for it, so the two cannot disagree about whether + // the panel is being driven: a socket that is open to a panel whose + // Audio-React switch is off is not "live". Beside it, one sentence when + // there is something to do or something wrong: + // error the capture's own sentence, and both it and the chip in red + // aud off what is off and where its switch is (Turn on is the popup's) + // idle why the last capture ended, if it ended by itself (the tab + // was closed), then how to start one + // otherwise nothing: a capture or the popup's test connection is up, + // and "start a capture" is not what to say beside it + const WORDS = { connecting: 'connecting', off: 'aud off', live: 'live', test: 'test', error: 'error' }; + + function drawBar(state, idleHint) { + const chip = $('sourceChip'); + const hint = $('captureHint'); + const bad = state.phase === 'error'; + chip.textContent = state.demo ? 'demo source' : WORDS[state.phase] || 'idle'; + chip.className = state.phase === 'live' ? 'chip okChip' : bad ? 'chip badChip' : 'chip'; + // Where the capture is being sent, as the socket has it. Nothing is + // connected: no chip, rather than an address nothing is using. + $('hostChip').textContent = state.host || ''; + $('hostChip').hidden = !state.host; + hint.textContent = state.demo ? '' + : state.phase === 'off' ? 'Connected, but Audio-React is off on the panel. Turn it on in the extension popup.' + : state.error || (state.running || state.manual ? '' : (state.note ? state.note + ' ' : '') + idleHint); + hint.classList.toggle('badText', bad); + $('stopBtn').disabled = !state.running || !!state.demo; + } + + // An envelope that is at its minimum span has learned nothing: the band has + // been silent, or has sat at one level, for as long as an envelope + // remembers. The editor is told there is none (see `env` in its contract), + // so it draws the band's own window, and a drag that takes the four windows + // over by hand leaves that one as it was. Seeded from such an envelope it + // became a 4 dB sliver at the floor, and the first sound pegged that knob at + // its top. (A band that has heard a single click does have an envelope, and + // is seeded from it: this guards silence, not thinness.) + const learned = (env) => (env || []).map((e) => + e && e.hi - e.lo > PFMap.ENV_MIN_SPAN + 1e-6 ? e : null); + + if (hasChrome) { + const HINT = 'Start capture from the extension popup, on the tab that is playing.'; + let frameFn = null; + + const take = (state) => { + state = state || {}; + drawBar(state, HINT); + if (frameFn) frameFn({ ...state, env: learned(state.env) }); + }; + const ask = () => chrome.runtime.sendMessage({ type: 'status' }, (response) => { + void chrome.runtime.lastError; + if (response && response.state) take(response.state); + }); + + chrome.runtime.onMessage.addListener((message) => { + if (message.type === 'state') take(message.state); + }); + $('stopBtn').addEventListener('click', () => { + chrome.runtime.sendMessage({ type: 'stop' }, () => void chrome.runtime.lastError); + }); + + window.PFAdapter = { + caps() { + return CAPS; + }, + loadConfig() { + return new Promise((resolve) => + chrome.storage.local.get(PFMap.CONFIG_KEY, (stored) => + resolve(stored[PFMap.CONFIG_KEY] || null))); + }, + // Stored, then told to the capture: that message is how a running + // capture gets the edit (nothing listens to storage). + saveConfig(config) { + return new Promise((resolve) => + chrome.storage.local.set({ [PFMap.CONFIG_KEY]: config }, () => { + chrome.runtime.sendMessage({ type: 'config', config }, () => { + void chrome.runtime.lastError; // background may be asleep; storage holds + resolve(); + }); + })); + }, + resetBand(index, config) { + config.bands[index] = PFMap.defaultBand(index, CAPS); + return this.saveConfig(config).then(() => config); + }, + // The editor is up: one state now, without waiting for the next push. + onFrame(fn) { + frameFn = fn; + ask(); + } + }; + ask(); + return; + } + + // ── demo adapter ────────────────────────────────────────────────────── + const HINT = 'Demo mode — open this page from the extension for live audio.'; + let demoConfig = PFMap.normalizeConfig(null, CAPS); + let frameFn = null; + let t = 0; + + function demoSpectrum() { + t += 0.08; + const values = []; + for (let i = 0; i < 64; i++) { + const frac = i / 63; + const hz = 20 * Math.pow(1000, frac); + const lg = Math.log10(hz); + let v = 0.05; + const beat = 0.5 + 0.5 * Math.max(0, Math.sin(t * 2.2)); + v += (0.45 + 0.35 * beat) * Math.exp(-Math.pow(lg - Math.log10(70), 2) / 0.05); + v += 0.32 * (0.7 + 0.3 * Math.sin(t * 1.1 + 1)) * Math.exp(-Math.pow(lg - Math.log10(900), 2) / 0.16); + v += 0.2 * (0.6 + 0.4 * Math.sin(t * 3.1 + 2)) * Math.exp(-Math.pow(lg - Math.log10(3500), 2) / 0.06); + v += 0.16 * Math.max(0, Math.sin(t * 4.4)) * Math.exp(-Math.pow(lg - Math.log10(9500), 2) / 0.035); + if (hz > 12000) v *= Math.max(0.25, 1 - (hz - 12000) / 16000); + v += (Math.sin(i * 7.3 + t * 5) * 0.02); + values.push(Math.max(0.02, Math.min(0.95, v))); + } + return values; + } + + // The same envelope and the same level-to-knob chain the capture runs + // (offscreen.js), so the demo's cursor and readout are the model's too. + const envelopes = [0, 1, 2, 3].map(() => PFMap.newEnvelope()); + + function demoFrame() { + const spectrum = demoSpectrum(); + const auto = demoConfig.autoRange; + const levels = [], pos = [], outputs = []; + demoConfig.bands.forEach((band, i) => { + const lo = Math.log10(band.hzMin / 20) / 3 * 64; + const hi = Math.log10(band.hzMax / 20) / 3 * 64; + let sum = 0, n = 0; + for (let k = Math.max(0, Math.floor(lo)); k <= Math.min(63, Math.ceil(hi)); k++) { sum += spectrum[k]; n++; } + levels[i] = n ? sum / n : 0; + PFMap.trackEnvelope(envelopes[i], levels[i], 0.01); + pos[i] = PFMap.position(levels[i], band, auto ? envelopes[i] : null); + outputs[i] = PFMap.output(band, pos[i]); + }); + const state = { + running: true, connected: false, demo: true, + levels, pos, outputs, spectrum, + env: auto ? learned(envelopes.map((e) => ({ lo: e.lo, hi: e.hi }))) : [], + autoRange: auto + }; + drawBar(state, HINT); + if (frameFn) frameFn(state); + } + + window.PFAdapter = { + caps() { return CAPS; }, + // Nothing is stored: the editor starts as a first run does. + loadConfig() { return Promise.resolve(null); }, + saveConfig(config) { demoConfig = config; return Promise.resolve(); }, + resetBand(index, config) { + config.bands[index] = PFMap.defaultBand(index, CAPS); + demoConfig = config; + return Promise.resolve(config); + }, + onFrame(fn) { frameFn = fn; demoFrame(); setInterval(demoFrame, 120); } + }; + drawBar({ demo: true }, HINT); +})(); diff --git a/web/public/guide/audio-editor/editor.css b/web/public/guide/audio-editor/editor.css new file mode 100644 index 000000000..da75d6c44 --- /dev/null +++ b/web/public/guide/audio-editor/editor.css @@ -0,0 +1,333 @@ +/* Patternflow Audio — mapping editor. + + Two parts. Down to the line that says EXTENSION ONLY these are the editor's + own rules, the same on every surface: the panel's /audio-in page takes + exactly that part (firmware/toolchain/build_audio_in_page.py stops reading + at the line) and brings its own colours and its own bar. Below the line is + what only the extension's page has: the cream instrument palette and the + source bar. Every colour above the line is a variable, so that works. + + Three things the part above the line leaves to the surface, because a + console page has them already and would carry them twice: the page itself + (body: margin, background, type), --mono, and that the hidden attribute + hides ([hidden] { display: none !important }: three of the editor's parts + are flex boxes that come and go by that attribute, and a display of their + own would otherwise keep them on screen). + + The layout rules are here too, and not in either surface's own sheet: the + editor is used in a full tab, in half a screen beside the tab that is + playing, and on a phone, and it has to hold in all three wherever it is + shown. Three steps: everything in a row; at 1100 px the output range drops + under the curve; at 700 px each row of controls becomes a column. */ +* { box-sizing: border-box; } + +.page { + max-width: 1280px; + margin: 0 auto; + padding: 24px; + display: flex; + flex-direction: column; + gap: 14px; +} + +.mono { font-family: var(--mono); } +.dimText { color: var(--muted); } +.hint { font-size: 12px; color: var(--muted); } +.hint:empty { display: none; } +.spacer { flex-grow: 1; } + +.ghostBtn { + font: inherit; font-size: 11px; cursor: pointer; + border: 1px solid var(--rule); border-radius: 2px; + background: var(--field); color: var(--muted); padding: 6px 12px; +} +.ghostBtn[aria-pressed=true] { border-color: var(--led); color: var(--ink); } +.ghostBtn.dan { color: var(--bad); } + +/* ── toolbar / toggles ── */ +/* The hint belongs to the switch and takes what the row has left; the two + glide sliders stay together and drop to a line of their own before the + hint is squeezed. */ +.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; } +#autoHint { flex: 1 1 22em; } +.glide { display: flex; flex-wrap: wrap; gap: 10px 18px; } +.damping { display: flex; align-items: center; gap: 8px; } +.damping input { width: 130px; accent-color: var(--led); } +.damping .mono { font-size: 11px; min-width: 56px; } +.toggleWrap { + font: inherit; color: inherit; cursor: pointer; display: flex; align-items: center; gap: 7px; + border: 1px solid var(--ink); border-radius: 2px; background: var(--field); padding: 5px 11px; +} +.toggleLabel { font-size: 11px; font-weight: 500; } +.toggle { + width: 22px; height: 12px; background: var(--rule); border-radius: 7px; + position: relative; flex: none; pointer-events: none; +} +.dotK { + position: absolute; left: 1px; top: 1px; width: 10px; height: 10px; + background: var(--field); border-radius: 50%; transition: left 0.12s; +} +.toggleWrap.on .toggle { background: var(--ok); } +.toggleWrap.on .dotK { left: 11px; } +/* Mute is the same switch as Auto range, the whole button its target; in its + column it is as wide as its words, not as the column. */ +#muteToggle { align-self: flex-start; } + +/* ── main plot ── */ +/* Clipped: the canvas, and the Preview layer that lies exactly over it, are + square, and a surface may round this box's corners; and a tag has no place + of its own until editor.js gives it one (until then it is below the canvas, + out of the box). Isolated: what is stacked in here (the selected tag over + its neighbours, Preview over them all) is stacked against nothing outside + it. */ +.plotWrap { + position: relative; isolation: isolate; background: var(--field); + border: 1px solid var(--rule); border-radius: 2px; overflow: hidden; +} +/* No touch-action here or on #curve, on purpose. A finger that lands on a box + or a handle drags it and one that misses scrolls the page, and that is + decided per touch, by editor.js, in a touchstart listener. touch-action is + per element: `none` trapped every swipe that began on the plot, and `pan-y` + would hand the browser the vertical drag of a box's top or bottom edge. + + EVERY CANVAS HAS ITS BOX HERE, not from editor.js: the plot, the curve + (#curve) and the three kinds of glyph. A canvas is 300 x 150 until somebody + says otherwise, and editor.js says nothing until the mapping has been read, + which on the panel's own hotspot is seconds after the first paint: sized + there, the page came up with a 150 px plot and 131 px tabs and then jumped. + The plot is as wide as the page and 45% of the window tall, between 240 and + 400 px, so the plot, the knobs and the top of the card share one screen (a + phone gets 260 at the most: see the 700 px step). editor.js only makes the + pixels for the box it finds. */ +#plot { display: block; cursor: default; width: 100%; height: clamp(240px, 45vh, 400px); } +.tag canvas, .chipCard canvas { width: 18px; height: 12px; } +.tag { + position: absolute; display: flex; align-items: center; gap: 6px; + background: var(--cream-2); border: 1px solid var(--rule); border-radius: 2px; + padding: 2px 7px; pointer-events: none; white-space: nowrap; +} +.tag.sel { border-color: var(--led); z-index: 1; } +/* A muted knob's tag says so, in the room its curve glyph took. */ +.tag.mutedTag { opacity: 0.55; } +.mutedTag canvas { display: none; } +.tagText { font-family: var(--mono); font-size: 10px; } + +/* ── preview: the scope, in the plot's place while it runs ── */ +.pv { + position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column; + background: var(--field); +} +.pvHead { + display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; + gap: 8px 14px; padding: 10px 12px; +} +.pvChips { display: flex; flex-wrap: wrap; gap: 8px; } +.pvSig { + font: inherit; font-size: 11px; cursor: pointer; + border: 1px solid var(--rule); border-radius: 2px; + background: var(--field); color: var(--ink); padding: 5px 11px; +} +.pvSig.on { border-color: var(--led); font-weight: 600; } +/* Sized by the column it is in, never by a style of its own: editor.js reads + the box it was given and draws to that. */ +#pvScope { display: block; flex: 1 1 0; min-height: 0; width: 100%; } +.pvLegend { display: flex; gap: 7px; align-items: center; } +.pvKeyIn, .pvKeyOut { width: 16px; height: 3px; display: inline-block; } +.pvKeyIn { background: var(--faint); } +.pvKeyOut { background: var(--led); } + +/* ── the knobs, as tabs under the plot ── */ +.knobRow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } +.chips { display: flex; gap: 8px; flex: 1 1 auto; } +.chipCard { + font: inherit; color: inherit; cursor: pointer; + display: flex; align-items: center; gap: 8px; + background: var(--field); border: 1px solid var(--rule); border-radius: 2px; + padding: 7px 12px; +} +.chipCard.sel { border-color: var(--led); } +.chipCard.sel .chipName { font-weight: 600; } +.chipCard.mutedChip { opacity: 0.55; } +.chipDot { width: 6px; height: 6px; background: var(--led); border-radius: 1px; } +.chipCard .mono { font-size: 11px; } + +/* ── the selected knob's card ── */ +.panel { + display: flex; flex-wrap: wrap; gap: 14px 20px; background: var(--field); + border: 1px solid var(--rule); border-radius: 2px; padding: 16px 18px; +} +.bandHead { flex: 0 0 100%; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; } +.bandName { font-size: 13px; font-weight: 600; white-space: nowrap; } +.col { display: flex; flex-direction: column; gap: 8px; min-width: 0; } +.col.grow { flex: 1 1 0; gap: 12px; } +.curveCol { flex: none; } +.colKicker { font-family: var(--mono); font-size: 10px; letter-spacing: 0.09em; color: var(--muted); } +.vr { width: 1px; background: var(--rule); } +/* The curve, and its shapes beside it while there is room for both. The + curve is 356 px wide where it has that, its row's width where it has not. */ +.curveRow { display: flex; flex-wrap: wrap; gap: 12px 20px; } +#curve { display: block; flex: none; width: 356px; max-width: 100%; height: 218px; } + +.presetsCol { width: 268px; flex: none; display: flex; flex-direction: column; gap: 8px; } +.presetGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; } +.preset { + font: inherit; cursor: pointer; + display: flex; flex-direction: column; align-items: center; gap: 5px; + border: 1px solid var(--rule); border-radius: 2px; background: var(--cream); + padding: 9px 4px 7px; +} +.preset canvas { width: 40px; height: 24px; } +.preset span { font-size: 10px; font-weight: 500; color: var(--ink); } +.preset.on { border-color: var(--led); } +.preset.on span { font-weight: 600; } +.stepsRow { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--muted); } +/* Minus and plus, at 32 px: the size of a small button on the panel's + console, and a finger's. (The extension keeps its instrument's smaller + ones: below the line.) */ +.stepsRow button { + font: inherit; cursor: pointer; width: 32px; height: 32px; padding: 0; line-height: 1; + border: 1px solid var(--rule); border-radius: 2px; background: var(--cream); color: var(--ink); +} + +.field { display: flex; flex-direction: column; gap: 6px; } +.fieldRow { display: flex; align-items: center; justify-content: space-between; gap: 8px; } +.fieldLabel { font-size: 11px; color: var(--muted); } + +/* A sideways drag is the track's, an up-and-down one is the page's. */ +.outTrack { position: relative; height: 18px; cursor: pointer; touch-action: pan-y; } +.outRail { position: absolute; left: 5px; right: 5px; top: 50%; height: 2px; margin-top: -1px; background: var(--rule); } +.outFill { position: absolute; top: 50%; height: 3px; margin-top: -1.5px; background: var(--led); } +.outHandle { + position: absolute; top: 50%; width: 9px; height: 10px; margin: -5px 0 0 -4.5px; + background: var(--field); border: 1px solid var(--ink); +} +.outPresets { display: flex; flex-wrap: wrap; gap: 6px; } +.outPreset { + font: inherit; font-size: 10px; cursor: pointer; + border: 1px solid var(--rule); border-radius: 2px; + background: var(--cream); color: var(--muted); padding: 3px 8px; +} + +/* ── the resets, at the foot ── */ +.acts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } + +/* The editor body has no box of its own: its parts are laid out by .page. + Until editor.js has the mapping it is inert, and looks it. + + And until the FIRST mapping is in, the page says nothing it does not know: + no switch position, no slider thumb, no output handles, and the toolbar's + hint keeps its lines without showing its words. Everything keeps its box, + so nothing moves when the mapping arrives. That state is `inert="inert"`, + as editor.html writes it; a source that makes the editor inert again later + (the panel's adapter, around a reset) sets the property, which leaves the + attribute empty, so what is on screen then stays on screen. */ +#ed { display: contents; } +#ed[inert] > * { opacity: 0.5; } +#ed[inert=inert] :is(.toggle, input, .outHandle, .hint) { visibility: hidden; } + +/* ── narrower than a full tab ── */ +/* Half a screen: the output range goes under the curve, across the card. The + shapes stay beside the curve for as long as both fit, then wrap under it. */ +@media (max-width: 1100px) { + .vr { display: none; } + .curveCol, .col.grow { flex: 1 1 100%; } + .presetsCol { flex: 1 1 200px; width: auto; } +} +/* A phone: a 16 px gutter all round (never edge to edge: the gutter is where + a thumb scrolls from), one control to a row, the knobs four across. */ +@media (max-width: 700px) { + .page { padding: 16px; gap: 12px; } + #plot { max-height: 260px; } + .panel { padding: 14px 12px; } + .glide, .damping { flex: 1 1 100%; } + .damping .fieldLabel { min-width: 56px; } + .damping input { flex: 1; width: auto; min-width: 0; } + .chips { flex-basis: 100%; gap: 6px; } + .chipCard { flex: 1 1 0; min-width: 0; flex-direction: column; gap: 0; padding: 6px 2px; } + .chipDot, .chipCard canvas { display: none; } + #openPreview { margin-left: auto; } +} +/* Touch: what a finger drags is drawn larger, and what it presses gets a hit + area that grows invisibly while the control keeps its size. */ +@media (pointer: coarse) { + .outTrack { height: 36px; } + .outHandle { width: 16px; height: 22px; margin: -11px 0 0 -8px; } + .damping input { min-height: 32px; } + :is(.toggleWrap, .ghostBtn, .pvSig, .chipCard, .outPreset, .stepsRow button) { position: relative; } + :is(.toggleWrap, .ghostBtn, .pvSig, .chipCard, .outPreset, .stepsRow button)::after { + content: ""; + position: absolute; inset: -8px -2px; + } +} + +/* ── EXTENSION ONLY ── */ + +/* The page itself and its type: a surface's own. The panel's /audio-in is a + console page and brings the console's (console/_audio_in.css), and with it + the rule that hidden means hidden. */ +[hidden] { display: none !important; } +/* Keyboard focus, as the popup has it: the browser's own ring is near black, + and on the filled Stop that is black on black. (The console's base block + has the same rule for the panel's page.) */ +:focus-visible { outline: 2px solid var(--led); outline-offset: 2px; } +body { + margin: 0; + background: var(--cream); + color: var(--ink); + font: 12px/1.45 Inter, ui-sans-serif, system-ui, sans-serif; +} + +/* The cream instrument palette, the same as the popup's (popup.css). */ +:root { + --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --cream: #f4efe6; + --cream-2: #ede7db; + --ink: #141414; + --muted: #6b655a; + --faint: #a69f90; + --rule: #d9d1c0; + --led: #e8552e; + --ok: #267a43; + --bad: #b43b2d; + --field: #fbf7ef; +} + +/* The instrument's small capitals, on the two headings inside the card, and + its small steppers. */ +.colKicker { text-transform: uppercase; } +.stepsRow button { width: 18px; height: 18px; } + +/* A button under the mouse goes orange here. (On the panel orange is for what + is selected or on, never a hover, so this is not the shared part's to say.) + :where keeps it weaker than a button's own state: a pressed Preview stays + ink, as it did when this rule stood above the pressed one. */ +:where(.ghostBtn, .outPreset):hover { border-color: var(--led); color: var(--led); } + +/* ── the source bar ── */ +header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 12px; } +.brand { display: flex; align-items: center; gap: 9px; } +.led { width: 8px; height: 8px; background: var(--led); border-radius: 1px; } +.title { font-size: 14px; font-weight: 600; white-space: nowrap; } +.kicker { font-family: var(--mono); font-size: 10px; letter-spacing: 0.09em; color: var(--muted); } +.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } + +.chip { + border: 1px solid var(--rule); border-radius: 2px; background: var(--field); + padding: 5px 10px; font-family: var(--mono); font-size: 11px; color: var(--ink); + white-space: nowrap; +} +.chip.dim { color: var(--muted); } +.okChip { border-color: var(--ok); color: var(--ok); } +/* An error looks like one: the chip, and the sentence beside it. */ +.badChip { border-color: var(--bad); color: var(--bad); } +.badText { color: var(--bad); } + +/* Stop is the popup's Stop: the same filled button, in the instrument's small + capitals. */ +.primaryBtn { + font: inherit; font-family: var(--mono); font-size: 10px; letter-spacing: .1em; + text-transform: uppercase; cursor: pointer; min-height: 32px; + border: 1px solid var(--ink); border-radius: 0; + background: var(--ink); color: var(--cream); padding: 0 14px; +} +.primaryBtn:disabled { opacity: 0.35; cursor: default; } diff --git a/web/public/guide/audio-editor/editor.js b/web/public/guide/audio-editor/editor.js new file mode 100644 index 000000000..6cd2030c5 --- /dev/null +++ b/web/public/guide/audio-editor/editor.js @@ -0,0 +1,1177 @@ +// Patternflow Audio — the mapping editor. +// +// One module, no chrome.* and no fetch. It stands on two things: +// +// PFMap (mapping.js) the model: defaults, what a stored mapping is turned +// into, the curves, the level-to-knob chain. +// window.PFAdapter the SOURCE: the extension's is editor-adapter.js, the +// panel's is console/_audio_in_adapter.js. This file +// moves to the panel verbatim; only the adapter differs. +// +// The model, in one sentence: a band is a BOX drawn on the live spectrum +// (width = the frequencies it listens to, height = the level window it maps), +// and a RESPONSE CURVE that turns position-in-window into position-in-output- +// range. Curves bake to a 33-point table on save; the analysis side — and +// the firmware — only interpolates, and never learns what a bezier is. +// +// THE ADAPTER CONTRACT. The source owns its limits, its axis, its defaults +// and where a level sits in a window; the editor draws what it is told and +// asks rather than assumes. +// +// loadConfig() -> Promise +// { autoRange, smoothing, attack, bands[4] }, each band { hzMin, hzMax, +// inMin, inMax, gain, outMin, outMax, muted, curve, lut }. Levels are +// 0..1 along the source's own level axis. null means NOTHING IS STORED +// (a first run): the editor then starts from PFMap's defaults. A source +// that could not read must not answer null - it keeps the promise open +// until it can; the editor stays inert meanwhile. +// caps() -> asked once, after loadConfig() has settled +// hzMin, hzMax the ends of the frequency axis +// db [bottom, top]: the dB at 0 and at 1 of the level axis +// levelMax the highest a window's top edge is stored, 0..1 +// hzGap the narrowest band the source stores, in Hz +// top(lo) the lowest top edge it stores over a bottom at `lo` +// bottom(hi) the highest bottom edge it stores under a top at `hi` +// Only hzMin and hzMax are required; the rest default to an axis with no +// dB printed on it, the whole axis, and PFMap.MIN_WINDOW. +// saveConfig(config) -> Promise store it and hand it to what is mapping +// resetBand(i, config) -> Promise +// band i back to the SOURCE's default. Answers the whole mapping as it +// now stands (as loadConfig would); the editor takes that and does not +// save it back. +// resetAll(button) optional: the source's own "reset everything", where it +// has one. The editor then shows Reset mapping beside +// the knob's own Reset and calls this with the button +// pressed (for PFConfirm); what comes back goes in +// through onConfig. A source without it has no such +// button. +// onConfig(fn) optional: fn(config) replaces the editor's copy without +// saving it (after a reset the source did by itself) +// onFrame(fn) fn(frame), about ten times a second while there is +// something to show. Called once, when the editor is up. +// levels[4] each band's level, 0..1 on the level axis. Empty or +// missing = nothing is being heard: no level line, no +// cursor. +// spectrum[64] log-spaced over hzMin..hzMax, same axis +// env[4] { lo, hi } per band while auto range has a learned +// envelope; missing, or null for that band, otherwise +// (the stored window is drawn, dashed). "Learned" is +// the source's to say: what a band has while it has +// heard nothing is not an envelope and is not sent, for +// a drag that takes the windows over by hand seeds each +// window from what is sent here. +// pos[4] where each level sits in its window, 0..1, AS THE +// MAPPER COMPUTED IT: the curve's input +// outputs[4] what each knob is being sent +// db optional [bottom, top] when this frame's levels are on +// another axis than caps().db (the phone app's) +// pos and outputs are what make the cursor and the readout true. A frame +// without them (one the source did not map itself) gets the editor's own +// arithmetic: PFMap.position / PFMap.output on the frame's levels. + +'use strict'; + +const A = window.PFAdapter; +const { clamp01, PRESETS, evalCurve, bakeLut } = PFMap; + +// What the source says about itself. Read once the mapping is in (the panel +// learns its frequency range from the same reply), so nothing above init() +// may use it at load. +let CAPS = null; +let DECADES = 1; +let TICKS = []; + +function readCaps() { + const c = A.caps(); + CAPS = { + hzMin: c.hzMin, hzMax: c.hzMax, + db: c.db || null, + levelMax: c.levelMax || 1, + hzGap: c.hzGap || 0, + top: c.top || ((lo) => lo + PFMap.MIN_WINDOW), + bottom: c.bottom || ((hi) => hi - PFMap.MIN_WINDOW) + }; + DECADES = Math.log10(CAPS.hzMax / CAPS.hzMin); + // Inner gridlines only — the endpoints are drawn from CAPS, so the same + // axis code serves the extension's 20–20k and the microphone's 31–8k. + TICKS = [[50, '50'], [100, '100'], [200, '200'], [500, '500'], + [1000, '1k'], [2000, '2k'], [5000, '5k'], [10000, '10k']] + .filter(([hz]) => hz > CAPS.hzMin * 1.15 && hz < CAPS.hzMax * 0.87); +} + +const clampHz = (v) => Math.max(CAPS.hzMin, Math.min(CAPS.hzMax, Number(v) || CAPS.hzMin)); + +// What the editor DRAWS: the curve itself where there is one (a table would +// round a step's corner), the model's answer otherwise. +function bandCurveValue(band, u) { + return band.curve ? evalCurve(band.curve, u) : PFMap.curveValue(band, u); +} + +// ── state ─────────────────────────────────────────────────────────────── + +// The mapping being edited (null until init() has it), and the knob selected. +let cfg = null; +let sel = 0; +let frame = {}; +let saveTimer = 0; + +// Every edit saves itself, 150 ms after the last one of a burst. +function persist() { + clearTimeout(saveTimer); + saveTimer = setTimeout(flush, 150); +} + +// Save now what is waiting to be saved. Also on the way out of the page, so +// an edit made in the last 150 ms is not left behind. +function flush() { + if (!saveTimer) return null; + clearTimeout(saveTimer); + saveTimer = 0; + return A.saveConfig(cfg); +} +window.addEventListener('pagehide', flush); + +// The source's copy replaces the editor's: after a reset. Not saved back. +function applyConfig(raw) { + cfg = PFMap.normalizeConfig(raw, CAPS); + syncAutoToggle(); + syncDamping(); + selectBand(sel); +} + +function touchCurve(band, curve) { + band.curve = curve; + band.lut = bakeLut(curve); +} + +// ── main plot geometry ────────────────────────────────────────────────── + +const $ = (id) => document.getElementById(id); +const plotCanvas = $('plot'); +const pctx = plotCanvas.getContext('2d'); +const M = { l: 48, r: 6, t: 26, b: 40 }; +// The plot area inside those margins, css px. +let PW = 0, PH = 0; + +// A finger, not a mouse: what it has to land on is made larger, here and in +// the stylesheet. EDGE is how far from a box's edge still grabs it, GRIP the +// square drawn on a corner or a curve handle, REACH how far from a curve +// handle still grabs that. +const COARSE = matchMedia('(pointer: coarse)').matches; +const EDGE = COARSE ? 18 : 7; +const GRIP = COARSE ? 11 : 7; +const REACH = COARSE ? 22 : 12; + +// What the toolbar says beside the Auto range switch, in each of its two +// states (syncAutoToggle). The first, as a mouse has it, is also written in +// editor.html in the hint's own place: unseen until the mapping is in, and +// there so that its lines are already taken and the plot under it does not +// move when the words arrive. +const HINT_AUTO = 'Each box follows the loudness of what it hears. ' + + (COARSE ? 'Switch Auto range off to set a height by hand.' : 'Drag a top or bottom edge to take all four over by hand.'); +const HINT_HAND = 'Width: the frequencies a knob listens to. Height: the quiet-to-loud range mapped onto it.'; + +// The plot's box is the stylesheet's (#plot in editor.css: as wide as the +// page, 45% of the window tall between 240 and 400 px, 260 at the most at the +// phone width), so it has its size before this file has run and nothing below +// it moves when the mapping arrives. That is the one rule for its height +// (Preview's scope is the same box). Here the height is only snapped to a +// whole pixel, so the canvas is not resampled, and the pixels are made. +function sizePlot() { + plotCanvas.style.height = ''; + const cssW = plotCanvas.clientWidth, cssH = plotCanvas.clientHeight; + const dpr = window.devicePixelRatio || 1; + plotCanvas.width = Math.round(cssW * dpr); + plotCanvas.height = Math.round(cssH * dpr); + plotCanvas.style.height = cssH + 'px'; + pctx.setTransform(dpr, 0, 0, dpr, 0, 0); + PW = cssW - M.l - M.r; + PH = cssH - M.t - M.b; +} + +const xOf = (hz) => M.l + PW * (Math.log10(clampHz(hz) / CAPS.hzMin) / DECADES); +const hzOf = (x) => clampHz(CAPS.hzMin * Math.pow(10, DECADES * ((x - M.l) / PW))); +const yOf = (v) => M.t + (1 - clamp01(v)) * PH; +const vOf = (y) => clamp01(1 - (y - M.t) / PH); + +// The level axis is the source's own dB: [bottom, top], or null when it gave +// none. The frame may be on another axis than the source's usual one (see the +// contract). Every level the editor prints goes through these, so the axis, +// the knob's heading and the curve's labels are in one unit. +const axisDb = () => frame.db || CAPS.db; +const minus = (n) => String(Math.round(n)).replace('-', '−'); + +// A height on the level axis, 0..1, as dB: '−43'. '' on an axis without dB. +function dbText(v) { + const db = axisDb(); + return db ? minus(db[0] + v * (db[1] - db[0])) : ''; +} + +// A line every 10 dB: [[v, label]]. +function dbLines() { + const db = axisDb(); + const lines = []; + if (!db) return lines; + for (let d = Math.ceil(db[0] / 10) * 10; d <= db[1]; d += 10) { + lines.push([(d - db[0]) / (db[1] - db[0]), minus(d)]); + } + return lines; +} + +// Canvas colors come from the page's CSS variables, read fresh each paint so +// a theme toggle repaints correctly. The extension page is the cream +// instrument; the panel's console wraps the same module in its own tokens. +// Three of them are also painted see-through, which takes the colour apart: +// --led, --rule and --ink have to be written #rrggbb on every surface. +function theme() { + const s = getComputedStyle(document.body); + const v = (name) => s.getPropertyValue(name).trim(); + const alpha = (name) => { + const n = parseInt(v(name).slice(1), 16); + return (a) => `rgba(${n >> 16}, ${n >> 8 & 255}, ${n & 255}, ${a})`; + }; + return { + field: v('--field'), + cream: v('--cream'), + ink: v('--ink'), + muted: v('--muted'), + faint: v('--faint'), + rule: v('--rule'), + led: v('--led'), + ledA: alpha('--led'), + ruleA: alpha('--rule'), + inkA: alpha('--ink') + }; +} + +// The envelope auto range is mapping band `index` through right now, or null: +// auto range is off, the source has not learned one, or what it sent has no +// height (a panel that has never heard anything). +function liveEnv(index) { + const env = cfg.autoRange && frame.env && frame.env[index]; + return env && env.hi > env.lo ? { lo: clamp01(env.lo), hi: clamp01(env.hi) } : null; +} + +// The window a band is drawn with: the breathing envelope while auto range +// has one, the band's own hand-set window otherwise. Auto range without an +// envelope yet shows the stored window (dashed), never a made-up one. +function bandWindow(index) { + const band = cfg.bands[index]; + return liveEnv(index) || { lo: band.inMin, hi: band.inMax }; +} + +// Is anything being heard? Without levels there is no line and no cursor. +const hearing = () => !!(frame.levels && frame.levels.length); + +// Where band `index`'s level sits in its window, 0..1: the mapper's own +// figure when the frame carries it, the model's arithmetic when it does not. +function bandPos(index) { + if (frame.pos && frame.pos.length) return clamp01(frame.pos[index]); + return PFMap.position(frame.levels[index] || 0, cfg.bands[index], liveEnv(index)); +} + +// A window the source would store as it is: the top no higher than it keeps +// one, the two edges no closer than it keeps them. +function fitWindow(band) { + band.inMin = Math.max(0, Math.min(band.inMin, CAPS.bottom(CAPS.levelMax))); + band.inMax = Math.min(CAPS.levelMax, Math.max(band.inMax, CAPS.top(band.inMin))); +} + +function formatHz(hz) { + return hz >= 1000 ? (hz / 1000).toFixed(hz >= 9950 ? 0 : 1).replace(/\.0$/, '') + 'k' : String(Math.round(hz)); +} + +// ── main plot drawing ─────────────────────────────────────────────────── + +// Everything written on a canvas: 11 px, in --muted. (10 px in --faint, as it +// was, is 2.3:1 on the extension's cream; the axis and the one number that +// says what the knob gets were the least legible text on the page.) +const MONO = '11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'; + +function drawPlot() { + const T = theme(); + const W = M.l + PW + M.r, H = M.t + PH + M.b; + pctx.clearRect(0, 0, W, H); + pctx.fillStyle = T.field; + pctx.fillRect(0, 0, W, H); + + // grid + pctx.font = MONO; + pctx.strokeStyle = T.ruleA(0.55); + pctx.fillStyle = T.muted; + pctx.lineWidth = 1; + for (const [v, label] of dbLines()) { + const y = Math.round(yOf(v)) + 0.5; + if (v > 0 && v < 1) { + pctx.beginPath(); pctx.moveTo(M.l, y); pctx.lineTo(M.l + PW, y); pctx.stroke(); + } + pctx.textAlign = 'right'; + pctx.fillText(label, M.l - 8, y + 3); + } + // The two units, once each, in the margin the level labels are in: dB heads + // their column, Hz starts the row of frequencies. + pctx.textAlign = 'right'; + if (axisDb()) pctx.fillText('dB', M.l - 8, M.t - 9); + pctx.fillText('Hz', M.l - 8, M.t + PH + 16); + pctx.textAlign = 'center'; + for (const [hz, label] of TICKS) { + const x = Math.round(xOf(hz)) + 0.5; + pctx.beginPath(); pctx.moveTo(x, M.t); pctx.lineTo(x, M.t + PH); pctx.stroke(); + pctx.fillText(label, x, M.t + PH + 16); + } + pctx.textAlign = 'left'; + pctx.fillText(formatHz(CAPS.hzMin), M.l, M.t + PH + 16); + pctx.textAlign = 'right'; + pctx.fillText(formatHz(CAPS.hzMax), M.l + PW, M.t + PH + 16); + pctx.strokeStyle = T.rule; + pctx.beginPath(); pctx.moveTo(M.l, M.t + PH + 0.5); pctx.lineTo(M.l + PW, M.t + PH + 0.5); pctx.stroke(); + + // spectrum staircase + const spec = frame.spectrum || []; + if (spec.length > 1) { + pctx.fillStyle = T.inkA(0.14); + pctx.beginPath(); + pctx.moveTo(M.l, yOf(spec[0])); + const bw = PW / spec.length; + for (let i = 0; i < spec.length; i++) { + const y = yOf(spec[i]); + pctx.lineTo(M.l + i * bw, y); + pctx.lineTo(M.l + (i + 1) * bw, y); + } + pctx.lineTo(M.l + PW, M.t + PH); + pctx.lineTo(M.l, M.t + PH); + pctx.closePath(); + pctx.fill(); + } + + // boxes + cfg.bands.forEach((band, i) => { + const win = bandWindow(i); + const x0 = xOf(band.hzMin), x1 = xOf(band.hzMax); + const yTop = yOf(win.hi), yBot = yOf(win.lo); + const isSel = i === sel; + const stroke = isSel ? T.led : (band.muted ? T.faint : T.ink); + const alpha = band.muted ? 0.04 : (isSel ? 0.12 : 0.07); + + pctx.fillStyle = T.ledA(alpha); + pctx.fillRect(x0, yTop, x1 - x0, yBot - yTop); + + pctx.strokeStyle = stroke; + pctx.lineWidth = isSel ? 1.5 : 1; + pctx.beginPath(); pctx.moveTo(x0, yTop); pctx.lineTo(x0, yBot); pctx.stroke(); + pctx.beginPath(); pctx.moveTo(x1, yTop); pctx.lineTo(x1, yBot); pctx.stroke(); + if (cfg.autoRange) pctx.setLineDash([5, 4]); + pctx.beginPath(); pctx.moveTo(x0, yTop); pctx.lineTo(x1, yTop); pctx.stroke(); + pctx.beginPath(); pctx.moveTo(x0, yBot); pctx.lineTo(x1, yBot); pctx.stroke(); + pctx.setLineDash([]); + + // The live level, as a line across the box: how far through its window + // the band is, which is what the curve is fed and so what the knob gets. + // It is the mapper's position and not the level's height on the axis: the + // two differ wherever the mapper is not linear in this axis (the panel + // maps linear amplitude on a dB axis, and auto range squelches the floor), + // and a line drawn at the height says "fires" where the knob does not. + // Resting, it sits on the bottom edge; at full scale, on the top. + if (hearing()) { + const ly = Math.max(yTop + 1.5, Math.min(yBot - 1.5, yBot - bandPos(i) * (yBot - yTop))); + pctx.strokeStyle = T.led; + pctx.lineWidth = 2; + pctx.beginPath(); + pctx.moveTo(x0 + 1, ly); + pctx.lineTo(x1 - 1, ly); + pctx.stroke(); + } + + if (isSel) { + pctx.fillStyle = T.field; + pctx.strokeStyle = T.ink; + pctx.lineWidth = 1; + for (const [cx, cy] of [[x0, yTop], [x1, yTop], [x0, yBot], [x1, yBot]]) { + pctx.fillRect(cx - GRIP / 2, cy - GRIP / 2, GRIP, GRIP); + pctx.strokeRect(cx - GRIP / 2, cy - GRIP / 2, GRIP, GRIP); + } + } + }); + + placeTags(); +} + +// Band N drives knob N, and is named after it everywhere: K1..K4. A tag sits +// ON its box's top edge, outside the box: auto range can make a box thinner +// than the tag, which would otherwise cover it and its level line. It starts +// at the box's left edge, and stops at the plot's right one: on a phone the +// highest knob's box is narrower than its tag, which was cut off there. +function placeTags() { + const T = theme(); + cfg.bands.forEach((band, i) => { + const tag = $('tag' + i); + const win = bandWindow(i); + tag.classList.toggle('sel', i === sel); + tag.classList.toggle('mutedTag', band.muted); + tag.querySelector('.tagText').textContent = 'K' + (i + 1) + (band.muted ? ' muted' : ''); + drawGlyph(tag.querySelector('canvas'), band, 18, 12, T.ink); + tag.style.left = Math.round(Math.min(xOf(band.hzMin), M.l + PW + M.r - tag.offsetWidth - 1)) + 'px'; + tag.style.top = Math.round(yOf(win.hi) - 21) + 'px'; + }); +} + +// The tiny curve glyph on tags, tabs and shapes. Its box (w x h css px) is the +// stylesheet's, so a glyph that is not drawn yet already has its size; here +// only the pixels are made. +function drawGlyph(canvas, band, w, h, color) { + const dpr = window.devicePixelRatio || 1; + if (canvas.width !== w * dpr) { + canvas.width = w * dpr; canvas.height = h * dpr; + } + const g = canvas.getContext('2d'); + g.setTransform(dpr, 0, 0, dpr, 0, 0); + g.clearRect(0, 0, w, h); + g.strokeStyle = color; + g.lineWidth = 1.4; + g.lineCap = 'round'; + g.beginPath(); + for (let i = 0; i <= 16; i++) { + const u = i / 16; + const v = bandCurveValue(band, u); + const x = 2 + u * (w - 4); + const y = h - 2 - v * (h - 4); + if (i === 0) g.moveTo(x, y); else g.lineTo(x, y); + } + g.stroke(); +} + +// ── main plot interaction ─────────────────────────────────────────────── + +let drag = null; + +function hitTest(x, y) { + // Selected band's edges win first — reaching for a handle should not + // reselect the neighbour underneath it. + const order = [sel, ...cfg.bands.map((_, i) => i).filter((i) => i !== sel)]; + for (const i of order) { + const band = cfg.bands[i]; + const win = bandWindow(i); + const x0 = xOf(band.hzMin), x1 = xOf(band.hzMax); + const yTop = yOf(win.hi), yBot = yOf(win.lo); + const insideX = x > x0 - EDGE && x < x1 + EDGE; + const insideY = y > yTop - EDGE && y < yBot + EDGE; + if (!insideX || !insideY) continue; + // An edge is grabbed from EDGE px outside it, and from inside too, but + // never from further in than a third of the box: the middle third of + // even the smallest box stays "move", so a fat edge cannot swallow it. + const inX = Math.min(EDGE, (x1 - x0) / 3), inY = Math.min(EDGE, (yBot - yTop) / 3); + const nearL = x <= x0 + inX, nearR = x >= x1 - inX; + // Under a finger, in auto range, a top or bottom edge is not a handle: + // with a finger's EDGE those two zones are a quarter of the plot, and a + // thumb that only meant to scroll the page took all four knobs over (see + // takeOver). There the touch scrolls, or moves the box if it is inside it; + // a height is set by hand after the Auto range switch is put off. + const tall = !(COARSE && cfg.autoRange); + const nearT = tall && y <= yTop + inY, nearB = tall && y >= yBot - inY; + // Corners first — a grab there resizes both axes at once. + if (nearL && nearT) return { band: i, mode: 'tl' }; + if (nearR && nearT) return { band: i, mode: 'tr' }; + if (nearL && nearB) return { band: i, mode: 'bl' }; + if (nearR && nearB) return { band: i, mode: 'br' }; + if (nearL) return { band: i, mode: 'l' }; + if (nearR) return { band: i, mode: 'r' }; + if (nearT) return { band: i, mode: 't' }; + if (nearB) return { band: i, mode: 'b' }; + if (x > x0 && x < x1 && y > yTop && y < yBot) return { band: i, mode: 'move' }; + } + return null; +} + +// Dragging a breathing edge is the gesture that says "I'll take it from +// here": auto range switches off, and every band's window is seeded from the +// envelope it was just breathing at, so nothing jumps. A band with no live +// envelope keeps the window it had (and a source sends none for a band that +// has learned nothing: seeded from silence, a window is a sliver at the floor +// that any sound pegs at its top). This is the ONLY thing that writes an +// envelope into a window: the Auto range switch itself just goes back to the +// hand-set windows, so trying auto costs nothing. With a mouse only: see +// hitTest. +function takeOver() { + cfg.bands.forEach((band, i) => { + const env = liveEnv(i); + if (!env) return; + band.inMin = env.lo; + band.inMax = env.hi; + fitWindow(band); + }); + cfg.autoRange = false; + syncAutoToggle(); +} + +function plotPointer(event) { + const rect = plotCanvas.getBoundingClientRect(); + return { x: event.clientX - rect.left, y: event.clientY - rect.top }; +} + +// A FINGER ON A CANVAS. One that lands on something draggable is a drag, and +// the page must not scroll under it; one that misses is the browser's, so the +// page still scrolls from anywhere else on the plot. That cannot be said with +// touch-action, which is per element and not per touch (`none` made the plot +// a trap no swipe got out of; `pan-y` would give the browser the vertical +// drag of a top or bottom edge, and it takes it with a pointercancel that +// preventDefault on pointerdown does not stop). What does decide it per touch +// is a touchstart listener that is not passive: prevented, that touch never +// scrolls and its pointer events run to the end. `hits(x, y)` says whether +// the point, in the canvas's own px, is on something. +function dragOnHit(canvas, hits) { + canvas.addEventListener('touchstart', (event) => { + const rect = canvas.getBoundingClientRect(); + const touch = event.changedTouches[0]; + if (hits(touch.clientX - rect.left, touch.clientY - rect.top)) event.preventDefault(); + }, { passive: false }); +} +dragOnHit(plotCanvas, hitTest); + +plotCanvas.addEventListener('pointerdown', (event) => { + const { x, y } = plotPointer(event); + const hit = hitTest(x, y); + if (!hit) return; + if (hit.band !== sel) selectBand(hit.band); + // Armed, not moving: a press selects. Nothing is moved, resized or taken + // over until the pointer has travelled SLOP px, so a tap (or the jitter of + // one) changes nothing. + drag = { ...hit, startX: x, startY: y, live: false }; + plotCanvas.setPointerCapture(event.pointerId); + event.preventDefault(); +}); + +const SLOP = 4; + +plotCanvas.addEventListener('pointermove', (event) => { + const { x, y } = plotPointer(event); + if (!drag) { + const hit = hitTest(x, y); + plotCanvas.style.cursor = !hit ? 'default' + : hit.mode === 'move' ? 'move' + : (hit.mode === 'tl' || hit.mode === 'br') ? 'nwse-resize' + : (hit.mode === 'tr' || hit.mode === 'bl') ? 'nesw-resize' + : (hit.mode === 'l' || hit.mode === 'r') ? 'ew-resize' : 'ns-resize'; + return; + } + const band = cfg.bands[drag.band]; + if (!drag.live) { + if (Math.hypot(x - drag.startX, y - drag.startY) < SLOP) return; + // A top or bottom edge of a box in auto range (or a corner, which is one + // too): this drag takes over. ('move' has neither letter in it.) + if (cfg.autoRange && /[tb]/.test(drag.mode)) takeOver(); + const win = bandWindow(drag.band); + Object.assign(drag, { live: true, hzMin: band.hzMin, hzMax: band.hzMax, lo: win.lo, hi: win.hi }); + } + // The narrowest box, as a frequency ratio. + const MINR = 1.12; + + if (drag.mode === 'move') { + // Translate in log-frequency: the box keeps its RATIO width, which is + // what a log axis means by "same size somewhere else". + const shift = (x - drag.startX) / PW * DECADES; + let ratio = Math.pow(10, shift); + ratio = Math.max(CAPS.hzMin / drag.hzMin, Math.min(CAPS.hzMax / drag.hzMax, ratio)); + band.hzMin = drag.hzMin * ratio; + band.hzMax = Math.max(drag.hzMax * ratio, band.hzMin + CAPS.hzGap); + if (!cfg.autoRange) { + const dv = vOf(y) - vOf(drag.startY); + const span = drag.hi - drag.lo; + band.inMin = Math.max(0, Math.min(CAPS.levelMax - span, drag.lo + dv)); + band.inMax = band.inMin + span; + fitWindow(band); + } + } else { + // Edges and corners share the same four moves; a corner just does two. + // Each edge stops where the source would stop it, so what is drawn is + // what is stored and nothing moves on the next load. + if (drag.mode.includes('l')) band.hzMin = Math.max(CAPS.hzMin, Math.min(hzOf(x), band.hzMax / MINR, band.hzMax - CAPS.hzGap)); + if (drag.mode.includes('r')) band.hzMax = Math.min(CAPS.hzMax, Math.max(hzOf(x), band.hzMin * MINR, band.hzMin + CAPS.hzGap)); + if (drag.mode.includes('t')) band.inMax = Math.min(CAPS.levelMax, Math.max(vOf(y), CAPS.top(band.inMin))); + if (drag.mode.includes('b')) band.inMin = Math.max(0, Math.min(vOf(y), CAPS.bottom(band.inMax))); + } + drawPlot(); + drawCurve(); + renderChips(); + renderSettings(); +}); + +const releasePlot = () => { + if (!drag) return; + const moved = drag.live; + drag = null; + if (moved) persist(); +}; +plotCanvas.addEventListener('pointerup', releasePlot); +plotCanvas.addEventListener('pointercancel', releasePlot); + +// ── curve panel ───────────────────────────────────────────────────────── + +const curveCanvas = $('curve'); +const cctx = curveCanvas.getContext('2d'); +const CM = { l: 34, r: 10, t: 12, b: 26 }; +let CW = 0, CH = 0; +let curveDrag = null; + +// The curve's box is the stylesheet's too (#curve: 356 px wide where there is +// room, the width of its row where there is not, 218 tall). The pointer maths +// below go by CW and CH, so any size works. +function sizeCurve() { + const dpr = window.devicePixelRatio || 1; + const cssW = curveCanvas.clientWidth, cssH = 218; + curveCanvas.width = cssW * dpr; curveCanvas.height = cssH * dpr; + cctx.setTransform(dpr, 0, 0, dpr, 0, 0); + CW = cssW - CM.l - CM.r; CH = cssH - CM.t - CM.b; +} + +const cxOf = (u) => CM.l + clamp01(u) * CW; +const cyOf = (v) => CM.t + (1 - clamp01(v)) * CH; + +function drawCurve() { + const T = theme(); + const band = cfg.bands[sel]; + const W = CM.l + CW + CM.r, H = CM.t + CH + CM.b; + cctx.clearRect(0, 0, W, H); + + cctx.fillStyle = T.cream; + cctx.fillRect(CM.l, CM.t, CW, CH); + cctx.strokeStyle = T.rule; + cctx.lineWidth = 1; + cctx.strokeRect(CM.l + 0.5, CM.t + 0.5, CW - 1, CH - 1); + cctx.strokeStyle = T.ruleA(0.5); + cctx.beginPath(); cctx.moveTo(cxOf(0.5), CM.t); cctx.lineTo(cxOf(0.5), CM.t + CH); cctx.stroke(); + cctx.beginPath(); cctx.moveTo(CM.l, cyOf(0.5)); cctx.lineTo(CM.l + CW, cyOf(0.5)); cctx.stroke(); + + // curve + cctx.strokeStyle = T.ink; + cctx.lineWidth = 2; + cctx.lineCap = 'round'; + cctx.beginPath(); + const steps = band.curve && band.curve.type === 'steps'; + const N = steps ? 200 : 64; + for (let i = 0; i <= N; i++) { + const u = i / N; + const v = bandCurveValue(band, u); + const x = cxOf(u), y = cyOf(v); + if (i === 0) cctx.moveTo(x, y); else cctx.lineTo(x, y); + } + cctx.stroke(); + + // bezier handles + if (band.curve && band.curve.type === 'bezier') { + const c = band.curve; + cctx.strokeStyle = T.faint; + cctx.setLineDash([2, 3]); + cctx.beginPath(); cctx.moveTo(cxOf(0), cyOf(c.y0)); cctx.lineTo(cxOf(c.p1x), cyOf(c.p1y)); cctx.stroke(); + cctx.beginPath(); cctx.moveTo(cxOf(1), cyOf(c.y1)); cctx.lineTo(cxOf(c.p2x), cyOf(c.p2y)); cctx.stroke(); + cctx.setLineDash([]); + cctx.fillStyle = T.led; + cctx.strokeStyle = T.ink; + cctx.lineWidth = 1; + for (const [hx, hy] of [[c.p1x, c.p1y], [c.p2x, c.p2y]]) { + cctx.fillRect(cxOf(hx) - GRIP / 2, cyOf(hy) - GRIP / 2, GRIP, GRIP); + cctx.strokeRect(cxOf(hx) - GRIP / 2, cyOf(hy) - GRIP / 2, GRIP, GRIP); + } + } + + // live cursor: where the music is on this curve right now, and (top + // left) what that is: the level heard, in the axis's dB, -> the knob's + // value. The position and the value are the mapper's own figures when the + // frame has them. + const win = bandWindow(sel); + if (hearing()) { + const u = bandPos(sel); + const v = bandCurveValue(band, u); + cctx.strokeStyle = T.led; + cctx.setLineDash([3, 3]); + cctx.lineWidth = 1; + cctx.beginPath(); cctx.moveTo(cxOf(u), CM.t + CH); cctx.lineTo(cxOf(u), cyOf(v)); cctx.stroke(); + cctx.setLineDash([]); + cctx.fillStyle = T.led; + cctx.beginPath(); cctx.arc(cxOf(u), cyOf(v), 4.5, 0, Math.PI * 2); cctx.fill(); + const out = frame.outputs && frame.outputs.length ? frame.outputs[sel] : PFMap.output(band, u); + const heard = dbText(frame.levels[sel] || 0); + cctx.font = MONO; + cctx.fillStyle = T.muted; + cctx.textAlign = 'left'; + cctx.fillText((heard && heard + ' dB ') + '→ ' + Number(out).toFixed(2), CM.l + 6, CM.t + 12); + } + + // labels: along the bottom the window this curve is spread over, quiet end + // to loud end, in the plot's dB (auto range keeps moving it, so it gets the + // word); up the side the two ends of the output range. + const lo = dbText(win.lo); + cctx.font = MONO; + cctx.fillStyle = T.muted; + cctx.textAlign = 'left'; + cctx.fillText(cfg.autoRange ? 'auto range' : lo, CM.l, CM.t + CH + 16); + cctx.textAlign = 'right'; + cctx.fillText(cfg.autoRange || !lo ? '' : dbText(win.hi) + ' dB', CM.l + CW, CM.t + CH + 16); + cctx.fillText(band.outMax.toFixed(2), CM.l - 6, CM.t + 8); + cctx.fillText(band.outMin.toFixed(2), CM.l - 6, CM.t + CH); +} + +// Which handle of the selected knob's curve is at this point: 'p1', 'p2', or +// nothing (only a bezier has handles). +function curveHit(x, y) { + const c = cfg.bands[sel].curve; + if (!c || c.type !== 'bezier') return null; + const near = (hx, hy) => Math.hypot(x - cxOf(hx), y - cyOf(hy)) < REACH; + return near(c.p1x, c.p1y) ? 'p1' : near(c.p2x, c.p2y) ? 'p2' : null; +} +dragOnHit(curveCanvas, curveHit); + +curveCanvas.addEventListener('pointerdown', (event) => { + const rect = curveCanvas.getBoundingClientRect(); + curveDrag = curveHit(event.clientX - rect.left, event.clientY - rect.top); + if (!curveDrag) return; + curveCanvas.setPointerCapture(event.pointerId); + event.preventDefault(); +}); + +curveCanvas.addEventListener('pointermove', (event) => { + if (!curveDrag) return; + const rect = curveCanvas.getBoundingClientRect(); + const u = clamp01((event.clientX - rect.left - CM.l) / CW); + const v = clamp01(1 - (event.clientY - rect.top - CM.t) / CH); + const band = cfg.bands[sel]; + const c = band.curve; + if (curveDrag === 'p1') { c.p1x = u; c.p1y = v; } + else { c.p2x = u; c.p2y = v; } + c.id = 'custom'; + band.lut = bakeLut(c); + drawCurve(); + renderPresetChips(); + placeTags(); +}); + +const releaseCurve = () => { + if (!curveDrag) return; + curveDrag = null; + persist(); + renderChips(); +}; +curveCanvas.addEventListener('pointerup', releaseCurve); +curveCanvas.addEventListener('pointercancel', releaseCurve); + +// ── presets, steps, settings column ───────────────────────────────────── + +function renderPresetChips() { + const band = cfg.bands[sel]; + const id = band.curve ? (band.curve.id || 'custom') : null; + document.querySelectorAll('.preset').forEach((el) => { + el.classList.toggle('on', el.dataset.p === id); + el.setAttribute('aria-pressed', el.dataset.p === id); + }); + // One line under the shapes, and only when it is true: a knob that has no + // shape is on the source's default response (nothing to fix, so nothing is + // asked), and only a bezier has handles to drag. + $('curveHint').textContent = !band.curve ? 'This knob uses the default response.' + : band.curve.type === 'bezier' ? 'Drag the two handles for a custom shape.' : ''; + $('stepsRow').hidden = !(band.curve && band.curve.type === 'steps'); + if (band.curve && band.curve.type === 'steps') $('stepsN').textContent = String(band.curve.n); + document.querySelectorAll('.preset canvas').forEach((canvas) => { + const preset = PRESETS[canvas.parentElement.dataset.p]; + drawGlyph(canvas, { curve: preset, gain: 1 }, 40, 24, theme().ink); + }); +} + +document.querySelectorAll('.preset').forEach((el) => { + el.addEventListener('click', () => { + const preset = PRESETS[el.dataset.p]; + if (!preset) return; + touchCurve(cfg.bands[sel], structuredClone(preset)); + drawCurve(); + renderPresetChips(); + placeTags(); + renderChips(); + persist(); + }); +}); + +$('stepsMinus').addEventListener('click', () => stepsAdjust(-1)); +$('stepsPlus').addEventListener('click', () => stepsAdjust(1)); +function stepsAdjust(delta) { + const band = cfg.bands[sel]; + if (!band.curve || band.curve.type !== 'steps') return; + band.curve.n = Math.max(2, Math.min(8, band.curve.n + delta)); + band.curve.id = band.curve.n === 2 ? 'gate' : 'steps'; + band.lut = bakeLut(band.curve); + drawCurve(); + renderPresetChips(); + placeTags(); + renderChips(); + persist(); +} + +// The selected knob's card: which knob, what it listens to (its frequencies, +// and its window in the plot's dB unless auto range is setting that), its +// output range and its mute. The knob's name is also on its Reset and in +// Preview's legend. +function renderSettings() { + const band = cfg.bands[sel]; + const lo = dbText(band.inMin); + $('bandName').textContent = 'Knob ' + (sel + 1); + $('bandMeta').textContent = formatHz(band.hzMin) + ' – ' + formatHz(band.hzMax) + ' Hz' + + (cfg.autoRange ? ' · auto range' : lo && ' · ' + lo + ' to ' + dbText(band.inMax) + ' dB'); + $('outLabel').textContent = band.outMin.toFixed(2) + ' – ' + band.outMax.toFixed(2); + const w = $('outTrack').clientWidth - 10; + $('outLo').style.left = (5 + band.outMin * w) + 'px'; + $('outHi').style.left = (5 + band.outMax * w) + 'px'; + $('outFill').style.left = (5 + band.outMin * w) + 'px'; + $('outFill').style.width = Math.max(0, (band.outMax - band.outMin) * w) + 'px'; + $('muteToggle').classList.toggle('on', band.muted); + $('muteToggle').setAttribute('aria-pressed', band.muted); + $('resetBand').textContent = 'Reset K' + (sel + 1); + $('pvOut').textContent = 'K' + (sel + 1) + ' output'; +} + +document.querySelectorAll('.outPreset').forEach((el) => { + el.addEventListener('click', () => { + const band = cfg.bands[sel]; + band.outMin = Number(el.dataset.lo); + band.outMax = Number(el.dataset.hi); + renderSettings(); drawCurve(); renderChips(); persist(); + }); +}); + +// The track is dragged sideways and lets the page scroll up and down +// (touch-action: pan-y). A touch that turns out to be a scroll ends in +// pointercancel after it has already moved the handle a little: that puts +// back what the press found and saves nothing. +let outDrag = null; +$('outTrack').addEventListener('pointerdown', (event) => { + const band = cfg.bands[sel]; + const rect = $('outTrack').getBoundingClientRect(); + const v = clamp01((event.clientX - rect.left - 5) / (rect.width - 10)); + outDrag = { lo: Math.abs(v - band.outMin) < Math.abs(v - band.outMax), was: [band.outMin, band.outMax] }; + $('outTrack').setPointerCapture(event.pointerId); + event.preventDefault(); +}); +$('outTrack').addEventListener('pointermove', (event) => { + if (!outDrag) return; + const band = cfg.bands[sel]; + const rect = $('outTrack').getBoundingClientRect(); + const v = clamp01((event.clientX - rect.left - 5) / (rect.width - 10)); + if (outDrag.lo) band.outMin = Math.min(v, band.outMax - PFMap.MIN_OUT); + else band.outMax = Math.max(v, band.outMin + PFMap.MIN_OUT); + renderSettings(); drawCurve(); +}); +$('outTrack').addEventListener('pointerup', () => { + if (outDrag) { outDrag = null; persist(); } +}); +$('outTrack').addEventListener('pointercancel', () => { + if (!outDrag) return; + [cfg.bands[sel].outMin, cfg.bands[sel].outMax] = outDrag.was; + outDrag = null; + renderSettings(); drawCurve(); +}); + +$('muteToggle').addEventListener('click', () => { + const band = cfg.bands[sel]; + band.muted = !band.muted; + renderSettings(); renderChips(); drawPlot(); persist(); +}); + +// Reset band asks the SOURCE for its default: the editor has no table of its +// own, so this and the source's own "reset everything" cannot disagree. +// Whatever is waiting to be saved goes first, so the reset is the last word. +$('resetBand').addEventListener('click', async () => { + await flush(); + const next = await A.resetBand(sel, cfg); + if (next) applyConfig(next); +}); + +// The source's own "reset everything", where it has one (see the contract): +// the button is in the page, hidden, and only such a source brings it out. +if (A.resetAll) { + $('resetMap').hidden = false; + $('resetMap').addEventListener('click', function () { A.resetAll(this); }); +} + +// ── the knobs, as tabs under the plot ─────────────────────────────────── + +function renderChips() { + cfg.bands.forEach((band, i) => { + const chip = $('chip' + i); + chip.classList.toggle('sel', i === sel); + chip.setAttribute('aria-pressed', i === sel); + chip.classList.toggle('mutedChip', band.muted); + chip.querySelector('.chipHz').textContent = formatHz(band.hzMin) + '–' + formatHz(band.hzMax); + drawGlyph(chip.querySelector('canvas'), band, 18, 12, theme().muted); + }); +} + +for (let i = 0; i < 4; i++) { + $('chip' + i).addEventListener('click', () => selectBand(i)); +} + +function selectBand(index) { + sel = Math.max(0, Math.min(3, index)); + drawPlot(); + drawCurve(); + renderPresetChips(); + renderSettings(); + renderChips(); +} + +// ── preview ───────────────────────────────────────────────────────────── +// +// Ableton-style: synthetic test signals through the SELECTED knob's whole +// chain - window, glide ballistics, curve, output range - drawn as a +// scrolling scope. What the knob will do, audible music not required. +// +// The scope takes the plot's place while it runs (#pv lies over it: a test +// signal needs no spectrum), so everything it demonstrates - attack and +// damping above it, the knobs, the curve and the output range below - stays +// live around it. The Preview button, right under it, closes it again; so +// does Escape. + +const pv = $('pv'); +const pvScope = $('pvScope'); +const pvCtx = pvScope.getContext('2d'); +// The scope's box, css px: whatever the layer gives it (see pvSize). +let pvW = 0, pvH = 0; +let pvSig = 'pulse'; +let pvRaf = 0; +let pvTimer = 0; +let pvT = 0; +let pvLast = 0; +let pvLevel = 0; +const PV_SECONDS = 4; +const PV_STEP = 1 / 30; +let pvIn = []; +let pvOut = []; + +function pvSignal(t) { + if (pvSig === 'pulse') { + // A kick every 0.8 s: one sharp burst, then near-silence. The tail the + // output shows comes from the glide, which is the point of the demo. + const phase = t % 0.8; + const spike = phase < 0.06 ? 0.85 : 0; + return clamp01(spike + 0.07 + (Math.random() - 0.5) * 0.03); + } + if (pvSig === 'noisy') { + const spike = (t % 1.7) < 0.06 ? 0.5 : 0; + return clamp01(0.30 + 0.10 * Math.sin(t * 1.3) + (Math.random() - 0.5) * 0.22 + spike); + } + // swell + return clamp01(0.45 + 0.35 * Math.sin(t * Math.PI * 2 * 0.35) + (Math.random() - 0.5) * 0.04); +} + +function pvChain(sig) { + // Mirror the live pipeline: asymmetric glide on the LEVEL, then window, + // curve, output range. + // (Both alphas are already inside 0.05..0.9: the model holds them there on + // load and the two sliders cannot leave it.) + const a = pvLevel < sig ? cfg.attack : cfg.smoothing; + pvLevel += (sig - pvLevel) * a; + const band = cfg.bands[sel]; + // The model's own two functions. In auto range the test signal is taken + // as already sitting in its envelope. + return PFMap.output(band, PFMap.position(pvLevel, band, cfg.autoRange ? { lo: 0, hi: 1 } : null)); +} + +function pvDraw() { + const T = theme(); + const w = pvW, h = pvH; + pvCtx.clearRect(0, 0, w, h); + pvCtx.strokeStyle = T.ruleA(0.5); + pvCtx.lineWidth = 1; + for (const v of [0.25, 0.5, 0.75]) { + const y = Math.round(h - v * h) + 0.5; + pvCtx.beginPath(); pvCtx.moveTo(0, y); pvCtx.lineTo(w, y); pvCtx.stroke(); + } + const trace = (arr, color, width) => { + pvCtx.strokeStyle = color; + pvCtx.lineWidth = width; + pvCtx.beginPath(); + for (let i = 0; i < arr.length; i++) { + const x = (i / (PV_SECONDS / PV_STEP)) * w; + const y = h - clamp01(arr[i]) * h; + if (i === 0) pvCtx.moveTo(x, y); else pvCtx.lineTo(x, y); + } + pvCtx.stroke(); + }; + trace(pvIn, T.faint, 1.2); + trace(pvOut, T.led, 2); +} + +function pvTick(now) { + if (pv.hidden) return; + if (!pvLast) pvLast = now; + // Wall-clock scheduling, not per-frame increments: a throttled tab still + // simulates the RIGHT amount of time when frames do arrive (capped per + // frame so a background stint cannot demand thousands of steps at once). + const target = pvT + (now - pvLast) / 1000; + pvLast = now; + let budget = 60; + while (pvT < target && budget-- > 0) { + pvT += PV_STEP; + const sig = pvSignal(pvT); + pvIn.push(sig); + pvOut.push(pvChain(sig)); + const cap = PV_SECONDS / PV_STEP; + if (pvIn.length > cap) { pvIn.shift(); pvOut.shift(); } + } + if (budget < 0) pvT = target; + pvDraw(); + pvSchedule(); +} + +// rAF when the surface is live, a timer backstop when it is throttled or +// hidden - the preview keeps moving either way. +function pvSchedule() { + pvRaf = requestAnimationFrame(pvTick); + clearTimeout(pvTimer); + pvTimer = setTimeout(() => { + cancelAnimationFrame(pvRaf); + pvTick(performance.now()); + }, 300); +} + +// The scope draws into the box the stylesheet gives it: what is left of the +// plot's box under the row of test signals. No size of its own. +function pvSize() { + const dpr = window.devicePixelRatio || 1; + pvW = pvScope.clientWidth; pvH = pvScope.clientHeight; + pvScope.width = pvW * dpr; pvScope.height = pvH * dpr; + pvCtx.setTransform(dpr, 0, 0, dpr, 0, 0); +} + +// On, off: the one switch. The button says which it is (aria-pressed). +function pvSet(on) { + pv.hidden = !on; + $('openPreview').setAttribute('aria-pressed', on); + cancelAnimationFrame(pvRaf); + clearTimeout(pvTimer); + if (!on) return; + pvSize(); + pvIn = []; pvOut = []; pvT = 0; pvLast = 0; pvLevel = 0; + pvSchedule(); +} + +$('openPreview').addEventListener('click', () => pvSet(pv.hidden)); +document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && !pv.hidden) pvSet(false); +}); +document.querySelectorAll('.pvSig').forEach((el) => { + el.addEventListener('click', () => { + pvSig = el.dataset.s; + document.querySelectorAll('.pvSig').forEach((x) => x.classList.toggle('on', x === el)); + }); +}); + +// ── auto toggle, status, wiring ───────────────────────────────────────── + +// The switch, and beside it the one thing to know about the boxes in the mode +// it is in. +function syncAutoToggle() { + $('autoToggle').classList.toggle('on', cfg.autoRange); + $('autoToggle').setAttribute('aria-pressed', cfg.autoRange); + $('autoHint').textContent = cfg.autoRange ? HINT_AUTO : HINT_HAND; +} + +// The switch only switches: off goes back to the windows set by hand, which +// auto range never touched. (Dragging an edge is what keeps an envelope.) +$('autoToggle').addEventListener('click', () => { + cfg.autoRange = !cfg.autoRange; + syncAutoToggle(); + drawPlot(); drawCurve(); renderSettings(); + persist(); +}); + +// Damping = the level smoothing the analysis already runs (an EMA on each +// band, before the window and curve). Right means calmer: the slider's 0..1 +// maps to alpha 0.9..0.05, so a gentle word scale replaces a number nobody +// would know the direction of. It smooths the INPUT level, not the output — +// gate and steps curves stay crisp, their trigger just stops flickering. +const DAMP_WORDS = [[0.25, 'tight'], [0.5, 'balanced'], [0.75, 'smooth'], [1.01, 'glassy']]; +const ATK_WORDS = [[0.25, 'snap'], [0.5, 'quick'], [0.75, 'soft'], [1.01, 'lazy']]; + +const dampWord = (v) => DAMP_WORDS.find(([top]) => v < top)[1]; + +function syncDamping() { + const v = clamp01((0.9 - cfg.smoothing) / 0.85); + $('damping').value = v; + $('dampingWord').textContent = dampWord(v); + const k = clamp01((0.9 - cfg.attack) / 0.85); + $('attack').value = k; + $('attackWord').textContent = ATK_WORDS.find(([top]) => k < top)[1]; +} + +$('attack').addEventListener('input', () => { + const v = clamp01($('attack').value); + cfg.attack = 0.9 - v * 0.85; + $('attackWord').textContent = ATK_WORDS.find(([top]) => v < top)[1]; + persist(); +}); + +$('damping').addEventListener('input', () => { + const v = clamp01($('damping').value); + cfg.smoothing = 0.9 - v * 0.85; + $('dampingWord').textContent = dampWord(v); + persist(); +}); + +// Everything drawn is sized from the page, so a window made narrower (half a +// screen beside the music, a phone turned round) is measured again. +window.addEventListener('resize', () => { + if (!cfg) return; + sizePlot(); + sizeCurve(); + if (!pv.hidden) pvSize(); + drawPlot(); + drawCurve(); + renderSettings(); +}); + +// An in-page "are you sure": the first press arms the button and makes it ask, +// a second press within four seconds answers yes. A second press that comes +// within half a second of the first is the same double click or double tap, +// not an answer to a question nobody has had time to read: it is ignored, and +// the button stays armed. window.confirm is not used anywhere on these pages: +// the phone app shows /audio-in in a WebView that has none (it answers "no" +// without showing anything). Use: +// if (!PFConfirm(button, 'Reset everything? Press again')) return; +window.PFConfirm = function (button, ask) { + if (button._armed) { + if (Date.now() - button._at < 500) return false; + clearTimeout(button._armed); + button._armed = 0; + button.textContent = button._label; + return true; + } + button._label = button.textContent; + button.textContent = ask; + button._at = Date.now(); + button._armed = setTimeout(() => { + button._armed = 0; + button.textContent = button._label; + }, 4000); + return false; +}; + +(async function init() { + const stored = await A.loadConfig(); + readCaps(); + cfg = PFMap.normalizeConfig(stored, CAPS); + sizePlot(); + sizeCurve(); + syncAutoToggle(); + syncDamping(); + selectBand(0); + // The attribute, not the property: a browser too old to know `inert` would + // keep the attribute, and with it what the stylesheet hides before the + // first mapping. + $('ed').removeAttribute('inert'); + if (A.onConfig) A.onConfig(applyConfig); + A.onFrame((state) => { + const was = String(axisDb()); + frame = state || {}; + // A frame on another level axis (`db` in the contract) changes the dB the + // card prints, not only the plot's labels. + if (String(axisDb()) !== was) renderSettings(); + drawPlot(); + drawCurve(); + }); +})(); diff --git a/web/public/guide/audio-editor/index.html b/web/public/guide/audio-editor/index.html new file mode 100644 index 000000000..f7fb6b865 --- /dev/null +++ b/web/public/guide/audio-editor/index.html @@ -0,0 +1,185 @@ + + + + + + +Patternflow Audio — Mapping + + + +
+ + +
+
+ + Patternflow Audio + MAPPING +
+
+ + idle + + +
+
+ + + +
+ +
+ + Each box follows the loudness of what it hears. Drag a top or bottom edge to take all four over by hand. +
+
+ + + +
+
+ + + +
+
+
+ + +
+ +
K1
+
K2
+
K3
+
K4
+ +
+ + +
+
+ + + + +
+ +
+ +
+
+ Knob 1 + +
+ +
+ Response curve +
+ +
+
+ + + + + + +
+ + +
+
+
+ +
+ +
+
+
+ Output range + +
+
+
+
+
+
+
+
+ + + + + + +
+
+ + +
+
+ + +
+ + +
+ +
+ + +
+ + + + + + diff --git a/web/public/guide/audio-editor/mapping.js b/web/public/guide/audio-editor/mapping.js new file mode 100644 index 000000000..975fc629b --- /dev/null +++ b/web/public/guide/audio-editor/mapping.js @@ -0,0 +1,236 @@ +// Patternflow Audio — the mapping model. +// +// What a mapping is, written once. Four bands, one per knob. A band listens +// to a range of frequencies and takes the level it hears there through +// +// level -> window -> response curve -> [rests at .. peaks at] +// +// where the window is the band's own (manual) or the envelope the band has +// recently moved in (auto range). offscreen.js runs that chain for the knobs; +// the editor draws it and previews it; both ask this file, so a box on the +// screen and a knob on the panel cannot be told two different things. +// +// It is ONE object on purpose. These are classic scripts sharing a global +// scope, where a second top-level `clamp01` in any file is a SyntaxError that +// kills the page at load and that `node --check` on each file cannot see. +// Everything here is reached as PFMap.something; nothing else is declared. +// +// Two halves. Down to the line that says so, this is the model: no chrome.*, +// no DOM, none of the extension's own numbers. That half also travels to the +// panel, inside its /audio-in page (firmware/toolchain/build_audio_in_page.py +// stops reading at the line), so every byte of it is sent over the panel's +// Wi-Fi: keep it to what the editor needs on both sides. Below the line is +// what only the extension's audio source knows. + +var PFMap = (function () { + 'use strict'; + + const within = (v, lo, hi) => Math.max(lo, Math.min(hi, Number(v) || 0)); + const clamp01 = (v) => within(v, 0, 1); + + // ── response curves ───────────────────────────────────────────────────── + // + // A curve is authored as one of these shapes and BAKED to a 33-point table. + // Whatever maps for real - offscreen.js here, the firmware on the panel - + // only interpolates the table, and never learns what a bezier is. + + const PRESETS = { + smooth: { type: 'bezier', id: 'smooth', y0: 0, y1: 1, p1x: 0.45, p1y: 0.05, p2x: 0.55, p2y: 0.95 }, + sharp: { type: 'bezier', id: 'sharp', y0: 0, y1: 1, p1x: 0.10, p1y: 0.65, p2x: 0.35, p2y: 1.00 }, + fall: { type: 'bezier', id: 'fall', y0: 1, y1: 0, p1x: 0.45, p1y: 0.95, p2x: 0.55, p2y: 0.05 }, + gate: { type: 'steps', id: 'gate', n: 2 }, + steps: { type: 'steps', id: 'steps', n: 3 }, + arch: { type: 'arch', id: 'arch' } + }; + + function evalCurve(curve, u) { + u = clamp01(u); + if (!curve) return u; + if (curve.type === 'steps') { + const n = within(Math.round(curve.n) || 2, 2, 8); + return Math.min(n - 1, Math.floor(u * n)) / (n - 1); + } + if (curve.type === 'arch') return Math.sin(Math.PI * u); + if (curve.type === 'bezier') { + // Cubic from (0, y0) to (1, y1); x(t) is monotone while both control xs + // stay in [0,1], so a short bisection recovers t for any u. + const { y0 = 0, y1 = 1, p1x, p1y, p2x, p2y } = curve; + const bx = (t) => 3 * (1 - t) * (1 - t) * t * p1x + 3 * (1 - t) * t * t * p2x + t * t * t; + const by = (t) => (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * p1y + 3 * (1 - t) * t * t * p2y + t * t * t * y1; + let lo = 0, hi = 1; + for (let i = 0; i < 24; i++) { + const mid = (lo + hi) / 2; + if (bx(mid) < u) lo = mid; else hi = mid; + } + return clamp01(by((lo + hi) / 2)); + } + return u; + } + + function bakeLut(curve) { + const lut = []; + for (let i = 0; i <= 32; i++) lut.push(Number(evalCurve(curve, i / 32).toFixed(4))); + return lut; + } + + // ── level to knob ─────────────────────────────────────────────────────── + + // The narrowest window and the shortest stretch of knob a band may have. + const MIN_WINDOW = 0.02; + const MIN_OUT = 0.05; + + const api = { + clamp01, PRESETS, evalCurve, bakeLut, MIN_WINDOW, MIN_OUT, + + // The response at a position in the window: the band's curve, or, for a + // band saved before there were curves, its boost exponent (above 1x a + // quiet band reaches its top early, below 1x a loud one holds back). + curveValue(band, u) { + return band.curve ? evalCurve(band.curve, u) + : Math.pow(clamp01(u), 1 / within(Number(band.gain) || 1, 0.2, 4)); + }, + + // Where a level sits in the band's window, 0..1: the input of the curve. + // `env` is the band's { lo, hi } envelope while auto range runs, null for + // the band's own inMin..inMax. Auto range maps a fixed slice of the + // envelope: under 0.10 of it is squelched, so the resting noise at the + // floor does not dance, and 0.95 of it is already full scale. + position(level, band, env) { + if (env) { + return clamp01((clamp01((level - env.lo) / Math.max(0.001, env.hi - env.lo)) - 0.10) / (0.95 - 0.10)); + } + const lo = clamp01(band.inMin); + return clamp01((level - lo) / Math.max(MIN_WINDOW, clamp01(band.inMax) - lo)); + }, + + // The knob value for a position in the window, through the curve. + output(band, u) { + const lo = clamp01(band.outMin); + return lo + api.curveValue(band, u) * (clamp01(band.outMax) - lo); + }, + + // The default band: what Reset gives, and what a first run starts with. + defaultBand(index, caps) { + return normalizeBand(null, index, caps); + }, + + // The whole mapping, made whole: { autoRange, smoothing, attack, bands[4] }. + // `caps` is the source's { hzMin, hzMax }. smoothing and attack are the + // two alphas of the level glide: how fast a level falls, how fast it rises. + // + // Nothing stored is a first run, and starts the way the panel does: auto + // range on, four default bands. Something stored is somebody's mapping + // and is not improved behind their back; in particular one saved before + // auto range existed has no `autoRange` and stays manual. + normalizeConfig(raw, caps) { + const stored = raw && Array.isArray(raw.bands) ? raw : { autoRange: true, bands: [] }; + return { + autoRange: stored.autoRange === true, + smoothing: within(Number(stored.smoothing) || 0.35, 0.05, 0.9), + attack: within(Number(stored.attack) || 0.65, 0.05, 0.9), + bands: [0, 1, 2, 3].map((i) => normalizeBand(stored.bands[i], i, caps)) + }; + } + }; + + const HZ = [[60, 250], [250, 2000], [2000, 5000], [5000, 16000]]; + + // lo and hi inside floor..ceiling, in that order, at least `gap` apart. + function pair(lo, hi, floor, ceiling, gap) { + lo = within(lo, floor, ceiling); + hi = within(hi, floor, ceiling); + if (lo > hi) [lo, hi] = [hi, lo]; + if (hi - lo < gap) { + hi = Math.min(ceiling, lo + gap); + lo = Math.min(lo, hi - gap); + } + return [lo, hi]; + } + + // One band, made whole. No band at all is the default: its slice of the + // spectrum, the full window, the Smooth curve already baked. A band that IS + // stored keeps what it has and gains nothing: saved before curves existed + // it has no curve and keeps its boost exponent, and saved when the output + // range was `base` + `range` it is read as that. + function normalizeBand(raw, index, caps) { + const b = raw && typeof raw === 'object' ? raw + : { outMin: 0.30, outMax: 0.85, curve: { ...PRESETS.smooth }, lut: bakeLut(PRESETS.smooth) }; + const base = b.base ?? 0.30; + const [hzMin, hzMax] = pair(b.hzMin ?? HZ[index][0], b.hzMax ?? HZ[index][1], caps.hzMin, caps.hzMax, 10); + const [inMin, inMax] = pair(b.inMin ?? 0, b.inMax ?? 1, 0, 1, MIN_WINDOW); + const [outMin, outMax] = pair(b.outMin ?? base, b.outMax ?? base + (b.range ?? 0.55), 0, 1, MIN_OUT); + return { + hzMin, hzMax, + // Four bands, four knobs, one line between them: band i drives knob i. + knob: index, + muted: b.muted === true, + inMin, inMax, + gain: within(Number(b.gain) || 1, 0.2, 4), + outMin, outMax, + curve: b.curve && typeof b.curve === 'object' ? b.curve : null, + lut: Array.isArray(b.lut) && b.lut.length > 1 ? b.lut : null + }; + } + + return api; +})(); + +// ── EXTENSION ONLY ──────────────────────────────────────────────────────── +// +// What the extension's audio source knows and the panel's does not. + +(function (M) { + 'use strict'; + + // Where the mapping is stored. The panel's address is not in it: that is the + // popup's, under a key of its own, so the editor and the popup never write + // the same value and neither can put the other's old one back. + M.CONFIG_KEY = 'patternflowAudioConfig'; + + // Tab audio through an AnalyserNode: the frequencies worth drawing, and the + // dB the level axis runs between, bottom then top. Levels everywhere in the + // extension are 0..1 along that axis. + M.SOURCE = { hzMin: 20, hzMax: 20000, db: [-80, -10] }; + + M.normalizeDb = function (db) { + return M.clamp01((db - M.SOURCE.db[0]) / (M.SOURCE.db[1] - M.SOURCE.db[0])); + }; + + // The knob gets the band's TABLE where it has one, interpolated the way the + // firmware interpolates it, and not the curve the table was baked from: a + // table rounds a step's corner, and what is sent should be what a panel + // running the same mapping would do. Whatever in the extension asks for a + // knob value gets this: PFMap.output() reaches it, so offscreen.js and the + // editor's Preview do. + const drawn = M.curveValue; + M.curveValue = function (band, u) { + const lut = band.lut; + if (!lut || lut.length < 2) return drawn(band, u); + const at = M.clamp01(u) * (lut.length - 1); + const i = Math.floor(at); + const j = Math.min(lut.length - 1, i + 1); + return M.clamp01(lut[i]) * (1 - (at - i)) + M.clamp01(lut[j]) * (at - i); + }; + + // Auto-range envelope, one per band: the floor and peak the band has + // recently seen, in the same units as its level. Both edges jump to a new + // extreme at once and drift back slowly (ENV_RELEASE per 33 ms tick is about + // 8 s, the panel's own time constant). ENV_MIN_SPAN stops silence from + // collapsing the window into a noise amplifier. There is no microphone gate + // here, unlike the panel: tab audio is digital, and silent means zero. + M.ENV_RELEASE = 0.004; + M.ENV_MIN_SPAN = 0.06; + + M.newEnvelope = function () { + return { lo: 1, hi: 0 }; + }; + + M.trackEnvelope = function (env, level, release) { + const v = M.clamp01(level); + const r = release || M.ENV_RELEASE; + if (v > env.hi) env.hi = v; else env.hi += (v - env.hi) * r; + if (v < env.lo) env.lo = v; else env.lo += (v - env.lo) * r; + if (env.hi < env.lo + M.ENV_MIN_SPAN) env.hi = env.lo + M.ENV_MIN_SPAN; + return env; + }; +})(PFMap); diff --git a/web/public/guide/audio/audio-in-listening.webp b/web/public/guide/audio/audio-in-listening.webp new file mode 100644 index 0000000000000000000000000000000000000000..7d0f6b512efbfa9fce8a9d381b27f6e305959b1f GIT binary patch literal 14526 zcmV;vI6=o!Nk&GtH~;`wMM6+kP&iDfH~;`IZ^MHCW?>ruZ5trS|Nq>3)`*w@baQc) zSyuBfYpCtoTuWk)72-=GP(XbRfu--W3A^Be`T7w@j9n?SJ0N`JyjKUpvuz3_g9`Tu{v|NC0j=vX#G zdZy2KmEIFOCta)b-V>+yvh6vA>9aee_hdiq%=x8#i$5s!lvF2>-LzGT&org?WSwQN ztG1i;-rFhZW!ro2Wz~C61pWf)$*%YM33~6-m+8GNyvnwfXWO=I>pBbqgF#G#iHV^K z6BU6u{}3_f^%H@@Y*m<;7)%5PgTRahkg@GwB;5Kx;}4P~d6yF;(YuTUBSXR2Mh23z zMFYXvq9h2SBuIY!r^H5*BT2GY)uO7SayoT(9}eXe52@MzzpgR|MfF}c=%CS^35dOl zs;;6J*Q@H_Ky{7kpgQc(lRW32{Qtk-J^AV=Hpmb>&)MUSfxkKvP-OOCfd2r@fvsl% zR*1_IC|s{+0FI>hI!PY`i~c>g$oN>6LAVhBfKVQCc6OuPT4NhlyQ$T4yKR5Mt=!BH z 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 literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/audio-in-off.webp b/web/public/guide/audio/audio-in-off.webp new file mode 100644 index 0000000000000000000000000000000000000000..dc3f2734964bebb356c8aeeaf5cbfd4bd775e8fe GIT binary patch literal 15810 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 literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/live-remote.webp b/web/public/guide/audio/live-remote.webp new file mode 100644 index 0000000000000000000000000000000000000000..f33d27485c3b6e95f4fb996154b03dbbda15d3a6 GIT binary patch literal 7968 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 Wx23q9>eyJ&D4b5he+mDc84dtC9C?%g literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/mic-part.webp b/web/public/guide/audio/mic-part.webp new file mode 100644 index 0000000000000000000000000000000000000000..f48a445aba7839b4dd12fda174a14ee2650219a9 GIT binary patch literal 22818 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 my^nSn&_V5m^J5=V;ModlL)0 zXhM}H53pRbjaw~%M*FRpQ8VgFKu-UQh+8FdRFB6LxKw?2++io2#z|U|f9|a@m0ee2 zFOg>9>1}`TA3e2T+Yn6396ap#tPS6|-6~t^k*t+aq*F!G6snC~Z zo{Wq*0V7jXN3vW70~VB6Qi<^`;&j&ixN4r{1o+Ls%gg!(R9K9NJ>CdJZaYR#MW{ao zLgAI%W8{?X+HRakPcEp}%{YUSUOf3y9%v~yd+vLM?2VBbm#-V$Qb#B;nd4tyDOWu+ zK><=tn&p}usqTjJ%0;(MQ$nLeo7q%nt8cv?f?f-P=ol8FnCrtJq%HDClF8$_<{l)g z`OwxCRcY#QogX|m;}0~__wT+xGUK%m>crJGdwyaNB-a)p95aPVn#Ng6l3jqr?yO@> zEDhJk>tY7E)6`wZDcldPNWLM+B6W<*J~<`h{cKz&Oe67$3vnF|f}wRb&mn7?j&u!x zK`bA}+N!;jCo@s?>Hdd>5LZciLxFL>5G6c_k&t^=Q>33tCE&-;oNYqwSpDt;>bI=Z z;k++N6D>5K>o$u#dF6VD1z7QZGckig#Z$zCOhnfapfpgs#gc;Ec~O?7XAlJr{J_+%g*aVBWCtmk*b4E=~0Y z=RnGlSr>CQZ}4XUVBayf zqgWL(-W_ynjy)kx%Sg3{A=w=njj~pj zPvzd%Lt>qB#aIFU$ELbavGs+P?R3Iu!@dWiPSa#kcS;>f+{lQ#o@MT=hsMk8pf07D F005UI9V`F< literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/mic-seated.webp b/web/public/guide/audio/mic-seated.webp new file mode 100644 index 0000000000000000000000000000000000000000..30349dd1d7426b19836124021718410d78f62ffe GIT binary patch literal 67640 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


S8w?I$lFUqo#=jshQ5T8Cl=Jzj`oqE+XuH$Nu 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
{E!nCwI zTJ5mg%s8O5mQ9_s{843n0dU6F8mQW6?qw!x-fr1GEpMJK{WVh0%*yRB@g^()qF}WA z{4E?>4P!!Ais#OWQg)}I%|leL(mplus2+tN58Sb#ZORo9N4g#vSrq#CwGxKnULnW` zC`2a95WlH^F~Hg?bPn$?IIb`XY}FYm7BpwK2r-@*vFW)|-4|3z?XCVB;pS$uPB}f8 zZ>C6gi#!_CT11N)tqNLQ5%ydqv^bIgW0D+KAd_7WGLh#vbYF(>kacMCam(U)ax7M) zW5`+|p2OnFV`t#mg-FZJSD}3wtZ4+ZI~Bl$h@rS4fJ4O_K(dkwkbzRHhMh8Z(;?)s zYc+V%gRmu>5bl|E#wmq0X#n;#@c$sy{bGQz9nDs0>-yfG?e;s5BJY?)9~Y&xPJyNC zo9L=z@!0QNe$7#&{TX@Wlh4(Nf9`XZ6g`!g`*7SC_c&&vnpB%FFyfT%k~daw@I`Fa zzT_VpOnuxak6O=@f2x^_DpR^GwoC+8S{@jTXN#WBTQrvVik3A3Y36@Ch@5>0%!dQ?8u7GRfy z)j>KS{h^#>rI3|9y za6E=tA-LZT)-rP#`wPMd&VPpqfSIcxr!CW6L8*UBCiG_`z^h2ew3o*(YCXbeoAPVI zz#x<=+Ejeo{jU%;!gEhTG8vpmiue!BP`ZG>TB_iw*XsdBmK+8tcP=zlyNSC@DQuB9 zLbZ$d#}9=-^x_08=WEq^F3b&RTAlF7BJ9a&lHGk#^^DJ^Eu)7lu$S+aj!vTE{uN~R z`zuzG%Hhr5A6uc}@#2Kquuj*s+d+#aW%*1ok+J0MD#ssHsu1{W(K&1EWHF5Z4?kylQ0ugiQ*oyAGM9BGN zj%`{3Snv%Ira-D2lJnrcTe-L4%uYz}xcV+c)oZQ>A{ZqO+tF^B=?_-4w_SgH*;d8W zmkdn;A{Fq=tob_R+TtQ;G8x?4M4T>uzZUUw>0yPZU$_jZ^I1W4)S3rGrd^VD7Acm< z`3@-g0HObf5P0#ELLi91a&g99QC1it zoMk9!q05Q@|7G|BJA>Q&onhfS^_W-EK?pP+Qk`q-b_)>|o$-{?3kbRHC253tzRcVp z8AB96;EXS?#oGU|yr>l~$#m0q>JT&pHp#!k7!9DW-^iKXsw_m~kLysp7HK*ykCwS{ zP1g|k*en1_FP62 zR6&_{W;-$hAVz)+uuhe(M?*f=cJrs-w#}es#P3wsbCU>Oy)-X`C(m;~kD7|9rw1x2 zrV|RSogNy#Pj?ej(3@@dyacL6LZ}^jtphUL= zOWFgKdjeF;I32e#wCCPBn%|{PEW*HQmb%@PUa=5Yx8`h1k1~#`)z{ec7da6v42l^~|@2%~)T4 zf+&${r%LK1%-@gNo0I5~LW2Xd#c=os**##xB%eswZseEnAocAm3`zRLsc^NqGvUqu1I-(N`3NZ=JR&rX)ssd zEX~sv(^e4@a~W#;MC6=L?xk$sAAz5PEw(C?GW~pwqL=BAdyyMq(0k?Bl?LMwbh;1X zChI!hq!%N-;D#z2nmLT?U2BAZuE`^sw_D;xs_Mgc^>2RlUUt#xrlZ2ImJwxH(Dks& zMbO5zRAML+lA|?uzunzt2t~YI7?btk(c;rwzwP%YGp%k#HZu;U;+NCz1N*J+zfq+u zSnn&D`ygEqv>4(qm%%Q-3hD^DTySt=>!d-&Mx6#|` zkJB8ckn~DB-{%+I3ECv&x$*71xnN}DlsIRKmg3$z5eJ88{A(r$G%SN$Mn2Go`t*nr z>$bxl%^}U>=36l%D*6HCW%;jz48)m%AOz#XOcWOpOK_H3RcI1L3_ybJw-|MUusV%SYgnYGkv1 z{c-K_LSMIMTr0OHi$d-j2FrEBLp8S4>Ka86Bq>C7UN6RSg5U<+L3gZvsWrlO=qb#B z9dB~!3KO3hQNYCh-=*ZrUeiZe;eu||-3C{3*}K}ryy-1EF6^2+D7s9T=J$gaBAy%@ zw-j;HFwJ%-rP5S9&GV+kXukrnsU>R0?vL+K0Qp!(FoU~{KyEBdZUIM7uR;NqwvISb z$-n=gWiS+ZbMXQbb3PnkCb^)N$hVbf%*oC2N3h6o^S{q%ga$QIj;T4W(e!lk{MMJ? z_*R_K;yE#Xip1^72+!WAL**mcC(u8PLDr%(Yt&1^E9+v7a9$zQ7(=y$&y*818;_`z z2f+CXF6_WhJ4HZ0;5+`bU1M0dR+98{CZd3SrA3Z9ugjBp6|7=5HON>i9rho6@#M$ux4**sT=k!TZ=hQ;Cxq zW2IB;$ENtrF`+`$;Re~GlXZ_XOoha6%#!3)8hdy!|ES=x<*wgC;HX}>)kuF0I%qOYj~H;{EEPRez;#QHL6Pz*k_9YO{0CEY9T&65sZW}#Ss zl)qN)bDQ5`SD&J3*m2XdNAeja61Db_oirc)G=9Gm7Rm#JGEn0hx#z(j)Z?(yRP>zy zUJGdBl7(ITcWa*-^xTUMf~O)lmlQJTYyI_j*o{Yu0Qf0KciRx z%^Q%5v`HE*T{{(}!;u{Q)?l4)5&4fgWWi}czEdx_`~*)(0OTQv9&WDH{R$GqyiWyh zmFYzY8hDkS9_eR222k8*KN#dWfnzK9{I~Rsqe>riHY=ysiC$RRmX04+e*Ge)bJGD< z6%h?x8Oql-Rg|CNldCoTJ)-^+eFU#-jo)g~GEI7-Y12Y%jn#ZjV9m=2Y&PQTAliME zA0e)7#3Un)G+R$EJqbdy(K4cM?k-w@MLbE4r?FKZ=src?coSQlot25P)acw>lY)=P zteLk#Unwx%I77~rA_lyZ$t-kF%ceA<{lxZ9^O2AQ-S-Mhkb+`OU*THfQ}{!7*=n&= zFyAdpkq&iN^KlDWK)HPOs7UMAt6$JuEO9lihhORlp7vRS1~3B?;yxhgo5EkYw1vD1 z)#(J^+5)Gs+Ad{?@mnDp%l@c(*1UMro1lrx2a~IIsW&Ur1}3ISrflUjMf5=2Va5V1 zStl|VHo+xz4``KQNa*8Tdz|9wnSf?eMBssz837@!@(zj&{2*X$^;C`e3>`t;%JiJ_ zin?S_4)%j%(Vsna3udIJJ*0FK)j zzPl)NwSEvPzJ|42?4Ouk%~#>@wL@naIn2QYRb^j!e&d5Hp>BDs6o#oge~n$r8}NZ`EesUs#1BB-(gA8FG$>3QciY0m+=doYy{yF)Kh=Nh-BEP6Mdbhe)_6;vpbP2c@`3l51JSrzuw}0S2 zZOGIG_QE5)QZ&8Nc)BGtueK;LP`L5m;w6ux4eh%aBUm5u`>^s4wrzZb=t7Ossgn+O z)m1=i-xkKYSRJ2Ssm4H6m1cNycf?}!V!=}@t$`1w^yP8^N=^~v-c*Op1RIR+wuX8_ z|8o*9ERXJpss4o;qay=??*W{-2Q$X7Vzk%pkRk%wHE%qGcKIm}9O>n_BqQh*mE2h& z+{v`{q~fMu!JEzR!gkFsuauSuK8NllqW{f;>>4U90A8k{2Wu`IO10N>59+FD~ zFL1o1vUgLk;5sTH@ropEbumjPu2RU1ELXLp551zho}Js^m)93`4;wy)35^Q}TxMsF zg3f^RtU6s2PLC0V4rj|cyWkP|;G9S1&a1;f?$&GJv#PThm`Y)_sP8pn!5bcVpv;%8fFfd0ovZ3>OsjfvND$au-Rw*DZrQtQ_ZK4+Et#BAo6$Vz54O&Vb? zHeZzABi-Vjd=6b-G1*h~v`*8RJtUFCr)*OG*qH+~&_&SaXH8w6)~||U2JiAs05!zk z9LJ_gJwjEWscbzR*9lJ8tP8K{m#B&*$qMq1xm|h|S?uySm+E9?x*YZmqFa*!9!@`P zJ<#5#`@bINRpLe1wD1aCz40(5!btktm8_(tSxAs!6e|t5eCOsf8n}TipxXg;NyA^v zlq66;O*%|00si<+SYXshtYCE!34Adt;DBRNo9ySYHi3K?4F|+Dfrx!Aq4Cj0wQZXv z=WtQ~_nOi+QfuVS)G5y-7>3zdQm>2KK>6+$*Mn8cz1aO;hR({ZD*7;hZlbBo_z9ik zc&+38RzJ~=_y4i77`BY{6A`q}uU-m^rb+UgKvw_7n43_qc=ki5&Vqp1HBt%yxeKL0 z1hn}bIFewv3P+}%Pnhu5)K}bu9-Tm}vxvym4KgwUk9ol;qO7h2Y`zevF>52aZdul- z_5^q#xHx;Ut22n@1#XU`xTY}K1n~jI`y)Ywu7J$shIhb)YEzrKH~3n|AOeoJu8VZ= zrd|llCBT!o7s7AtL}nk&Uh5!1y1C<~2PxT=b_0V9Y8;;5ZQG+_Bv0Gn1Gb?vE{J4O zl_X^42(4rk58%>=1248ikjH9HQ{J?rdc;!Vbm(45$IkPmXhkTmfNv|F%a%0r!bjDo z?w9veGf5-PXWh&`T_SlG#}5Lc!)0|?J51^K)Hr6OurhY>_f&>3lzIrq-_b1`W}nt| z?W8t|)|pdX!SFzC7_g3k!xoxp%grH)rG|5|xl?A2%{42N-Zmr&cD~M>4c!gql})|> zGbGucv99|*X4Ue*y-p$=JcICG=%-Bwv8|~p|L;Do49l_cGA;RabKQZz=zzk`#$2ps zF2BqCe^1x5;`0iBzEIxFqa6ixpP0Ku(V8aSO#BAVMz+UM*MZw-563O*(f1GVQ38b5 zqmFyxwnj`QFF|`~7VMeaTY&VF*W%6}U&DCvbZ5QuCh(2XK%dn8DtSe@RURE(1-B@VgivuZFh)my=xIV*~;M;=Wp-@~uw1O(BadT!|gEER^95ou^jJ1kE9V|r7P)b_NiNTY5x71dLjbl28 zi~qgIED^Uk1&|h?G1CKhd$ZBmo65Hc{1tCCR`WO}lvoEOz@aZsNhf&)zY8;;Fnb_8 zR04bC=-#8fJJp{`u(-zDc?I2bdF1#ts4f$XpLUOL4y0x*wA|c!7nWyZVTMb&o zIYR_QJMO}551O#J;AX<|3QG*Fhb*u*BdRkF$WL;OkEnd@-LAdL^9We&Z&AjH&hNN9 z|8ino6e;O3S|vKWW|)8JkPeAfIi1M=dDD zy!&MAB4s3IN;%xkg;22ItW{-(F%n!CPn_p{?d_GOQo@EP~j= zoIt&Ryh-0yR@%q*ImCOL-J9Q67w7*SGwrtYj4{R3B;3K`mxqhg05H$Y!0Hjw^)*mikO%! z1Aqk5+s#&VfEJ-ATnWAZ&Kmjxi^AGJejGpJQ})V-h92Y^ac@B@tB7#@$qb~;CAem1-rzEHXpzFEmYsc05s_I=Kjuxoe8LJaYKTN;t}yz z1v(|_@~&Q;tzD@mUwaF!&f6E@Z&%V#QVBWc$+n$9EP7eWlV_I|JmOPn#GId+x31NMCtvMTp%(K{mx( z{U<6Uc0C2yOS`~&^R_^}DyqeP-*J;3&f%5)Ma zK0_a-GPd3^xvX+SnRg^0M)TE3r3lPtF(iWdKbWk!q%Go73e4>&#ymCsil-Wqz;}iZ z9vyk%b_rR%>fE?FBNR^Rt3h={B9*ovmHGxW+!0naWB|KRaSo|(&ZuO6pTwo0oR+8McZXb5FS|?>sEy|Wj92Pf z&2ju{l;Ju$8XFa-)G_0^B-J~q{QrEl=aDHiJ(@&u82fK_ypElaRD(rp6b-G4tJq{ueYyb>`36ajm6E@zSOP=&+YLkG> zN!I!;iNfK#hl`wY7EcR5k;hgJ4`}^70hb+a5Eo}V2EinXH<_HH6-n-jkHmbowy^#5 zzIBA+9tH>}-{Ifhi$Ai=C4M+Gvu>NS1cpL#xgTgW&IW?jp-d=X>^ z@Jan#i0{;s_Ksr6Y5OkNR|AeTnqlGf8!q%ixY)3NW}x2@moBAU?8nm?xNGx#hMxsQ zZ#i3%8BbQ6W&#?&S_GRAKgLy6k-mCcuQb-19lqdr5d(0}LsRcxLc0J}^=$96ZjZaL zKi+<)DSuiJ)rp0~F=!j43yNYe>wk5Ju?>uUu*{%N&b`4U0)Y7XwdlOL!g0iPL+aHB~=*mk(4<3i@NqyFaRhiK*Wu_*^i}E15MaaK!%PM%=MlL(uOXzg#b+nUoHaZX z@h@${%C7AC|R8NRiIA^Tm+1ej}n0U_w-&>P|9v(QgXD z3l+`?h4Ur+?XqxdTfRQZ20dpLVDo++&%&#l!T8<#3~wA!KCTnz)Zc!_0)cHAM}+TF z?6xil{aUSS^!1isXUsAhdU^6eC4d%zF=1y-B7`G8sQ6&y#|6e$x5y5$=IoLmJmOSg znLK*&_J&y&Xh+@0Vy<5yw2$4X6-MC)^5tW{_|w{7IyF5)AUexH5lG8<^Pps0Vqg{c z##hePjNW!Ms5PA*%t3EA??bTW6!B;@H~z2?;fm=A{wuCGmXDo#(=IOhy%bUpulG#M}1FSc3aLNfDgaU+1m zjbD%|=j92i-!$L~G{B}rq4})Vd)a^H8>CvUhwzfW&Gz8nMaPcUIb=Di)3mT(t#T=R zokdzZpA*hL=%;1zuo+jNX7x7fQgenkso#!B7vy(o`Zx0!HH4q=3JA50Gc(|^v!eOz zV_9yotKCt;LvkRcF`^y7=Wfd)-QVW_l>&1;rfNb^hI>Om!qjMp`O4bTg&jDvI3oo&IyTNJNW86vzEHA*T)3zc%!4c>~R+`sW(@RNa z0>9|0;9i}_X@qGW#s9Sqf-_#o8o{M^9*7Eu&U3t*##QGWU_3;f_RqK@YnwH}n2Puh zOagneASKe>^lwPfhU#9I&PnYsJ$-Wy<1c+qKjeWSS5P&mW}l zKEql>O832I%Wo|x^P8p0l51trmmn0vIJ( zC%1vY2z3qVT-kwqaL4^fL!&CY`1ZpAk4V-XC`SDtdX zRy0`B+f=bJ2T-L~s$_S{Ctr!r2NGfokmKJ0hRZAn84id$BaESi?~0t{_#B`fKnBWf zI}-2+JKJx!=PsI;z7-}3DI2^7fBzf!qtgLz5Ac4jG)< zkG(w#HTSmG&?fi*KI00&pz9}Gw0oV;?7)eJ5Juf4TNe_D%^S+@!VJLRdU<@cX!2T} z+Jb#e`xY9LUV7j)qlojkFjISTrrkZwFh95k>UCBQfHe-eI}bNXoYTCACTK0|Ni!W? zuZV~a!Xad-0XRaLEI)>&>a;@?pA(WaFXAAcH+oYbqPfkBb>1Sa_vv{A$LJtSlCMrC z%L8PAGlq4!zYvntwLIY;V7dn`Jxp)_SAF4?>x$C*-S+h?$B!t5B+$Gm<|WfpqE!P z4EohRSK}k_mUyyGxvZH{^~vd+q)5~5F3fFT-vdi z;+#;6U2@*~DlacmePZ4~j5l4J>4bkvP<>*B(&3A4#xuwIn1H+%0QimRK1)6o4aE9c z7fo^oOgf6l^j{gL!q;YykWqf|)~37yviMBV3G_#tb8_7j0L%$K-HhVs-lvH)wVZJ3 zd?b%W&fbMcpoy(vI)PA#jzrnOwc`bt3)@n(l?(ZBs*Z>gkZlMmO3heDd{CVUi6BM9 z1Hm|QA>;?#ofWYph!7QC30H?6&VSk6Hm!0cu8?Egm2(OJgf>d#|w(siry-3I^SW;TzAn*e)n#drEl^em5Ml;)Xx~f2&C4yfA ziLO6FGQ4$$$C;;QGc7?x;S?m%Q(Z(*NbCKQz?`h+&giGIo?|MC~Lg zI}iLTlbw@(jGgJAX{`2Es!Q`md~N6C(}%k`hgCJi_+D zdXHGUUpot%i&vrXpLuw%8P>shBf4a>luClL$EI97TrrO)Ez6wQ(#Oiw*`fK4)Chq%gSS7E zyHi#9Gd1%rpJn;E_pNsD$G_g-ud6ufZheE%sRs4ca|g}3t6`+fX$rmP5c3D31Uh!*xBbd1vQU{Wu#D@)_wyXR6NyokC( z7`>s(0A>N;3sn`P$dC19ftS;j_g1OG!Tx^R5c0)yqQ5S(1OZA;7*YBveY)^_`iIR6 z$5Q};>v4yf>~J*-1;i0kW>x*5VwwLHuov(5U%zi~ZUj6KXOd z8zAkx#R16ra#hF#*h-vgKmHMHUheCOAT0!HGDF5SWsAxa&6SZ`0RZsQy}#c06I7Ne z%l@eevofZ90PPG}r!y8>AAi#&8tf~%7psX2|JoqHc)Y3b(7%ypb7JpS!@}Q1l8GzH z|Fe(HFxow;bd-|VOtShvMY&bklrwV(KeRQWbQkoY372Xx?w^Mey3HZ8IgHh3NuR`x zW^LiepwX6gAxBCLSi|BpIFcq%iWYTx?|`OIlzjP#9At21N5FSqXcnC$ zioCx4`e7c+Y7Vh--d4sG9b859qYAlkp!!{FbBp<3BBtOFP<^Cnyr`?}w>+Cnv|@rI z?Z5L6F<^t#ALB^N_O@g#PX__GO}sr09C)ROH_0Zh6G-|>-ws(!Hld<$l;|lxlO*;e~v1&5mr9V&hE>}l+4@w0I<}#^-$2hik68xF6AAD zc(;BbdFYuTPCTa%JI+5#WGVs?>852pWJZ(YGbKZn{0*3IHRK+HShK(l~pChA%ojLJ|MMiuX!z2lXXNj^32gYuxXQ_xPY3-8FS9}md~-J z^VGJ7Q`lLRz#H`Ln%C`zo{ZaXieMLDdR?XVn!XPz3(F=0##vhO-gjUl)DF*(>&0utN(u?h-xjw zJ-eTV=+w-Ufv@YG%r)!$$khueY*|1RsJiAVw>%JxC*gtwj}j7lsRpC${oILe9{~xX zB%?SYjq(V3nROL0!%K)8R)MF$_v6@==-IOL5z`hMg$_ixe`sR;Rf3rS!@>3b13Y@( z8^V78cE^PxXv!G$vBCMT z+|CnBPC%u^ajIG{b?>(^JEHr&)kwhP`QSa9zpA*2IAvjC7QHXv zYGY2>|ZHn~O zBkF$y#bOcjVcpf(ljdb4hm_KdG(pr((G5t@!`$lYDmI0QZLu&Kt`$dtan6i<^xzK+ za$Qy){Yb^3yn|s2wgxLfq~nktDZm;~1Zr?BXBpeIjC{E-GhDZC)*w1J%vsPugtAY} z(TKD=RsX>eIMHKoqWgJAR={ zA&Pw>&bOT0Ke(m)Z0gOb7h7iqxtQoVf8f(!n6Su=SC5WYWptwhtW~-JfB|Rw@M}gJ z3&9TA7c`uFeU6og#j`oY1w1J?5@_cD`;fCWRnr?0GBBoBzuzP^^T_uak!-ym(_NdHZF5UirYzZ;{eFF*R5s ztFLaq0(plb92tIAd5%(L5y_|CETQ z;dfAGWVJh|5sz>^LmZ^fs`cNS6Ez;^Yny>x7sdlJt3DULNa1=^YTLP2;Ax;`LfYcm zaT1VdPsH0gW=2{t=TqOX*BUU)%QJ7|F|1lZrj{KZaRq;b=y3vx+9J~0*I&r(vCZFu{tCmFX{qAs71E-(z;9|ki<1kl|ucja*N z$~A(2-l$H5E@eVy6%%@U*9KfJk+cm@xd(zaTw9QW;uQm&6#$Xtk`GYBhqMe@Ntc*I z`C|_D@)vBVl*yKMV2^R@q_(ea0oW3P16^RnL3GQ18VV|BzY9ea20tCtDJ+!0senZD zs6x?=+0Ab}JR<@p8_IBH6syv)W$vH@+!_&I7nq4|q)k}7zNJtR0$~>268;Za2@?>Z zLkYWPqUoA>yxC&_{iB+7901a`E~0Sw=)CD7(FVElRST!VGiGM`e~QQeJJ^v~GHJfH zWqD5N(;6)J(UJ#{t$xxFQa;Guq%$uwCl?zrnG=WFrY)C%QYZi(2UB97L?oQ%H;$mh zmWMeo*T;v-^LXKkw+OQ-Hm?I@Bx|I*)hktuC7J2%M7{fQffg%m2UXL?pKVOkixdNW z-#A4Zpz0h-P00EalNmr?v5#j!fT5k5OwLPE-Ljy!RD#|SFXk&%X`NLmQ#d;{ePig$ zbHx_dJ~l^3KeS|-^>Yb9DM;dTaEfxL6-$AuMuj6PE_uyIxdkO!Girq{-RUApqgL=GXQAocbK@D*d@m>bBy2=8TP(V{SqO-1d_&aYiU}>}0ufYk{jz9AC&&Spv7?BiH zsix(DUArMAV1X-u6Lf`Bc5nSvkL$is9Doyx1s_trwg2<5Gz5dvRywbX0ljEj8V5yQ z6}dU?je7Q7R^Tij*vn$_2IF*ZRxa@9EaO-li?T*s%V=aAg=csp$)_k4MLzt8F5hyt z4V9rqGc3|UyOwB>tg|sH%kY^W)72Tegiwrw(EX$g>&`=;l{44g&?7{9AnSPepf=|x43U#M+h%9`#&e_^l zfxqI2ljVb~*k&Zw(D<4rI_aIJu)U`YdAjBqzFNyY4gB5Z##rQb@pv&GD`7u$74Ocg zVWi=5`sY#e)sW(k1NRWN9VCQx#`J%#)BrvyX)O)YO=^eHF{QZU9 z*y=!M*B|8iTjQQrDzG_CFGI%jsQbAJ^$K`DNEaN|teuVUt>?c8i=I_6^1dLemF|=0 zuVqnk4q&}Fiiv>s1>eF$gYobv7LUpVlXjej7SiDfTH6sS8a<+>ZN|#|q%kyy?Nd06 z@e1KMhcvDIC++|~Qm?0YsbUeb>P|i`EHqxakYS++eaZe z*IP6a2K;-Fa7ZH~8a`Pz{NOZNAlrhp-S`1AGA}1W7$n?`20*C$*Hc3a0~L?A7OyN4 zSpQ{#!bQX8E-zNAm2RD#`!y`_SQ(IOC?l^yxl?jJ_c1O2wH{+a)+W67@!tfw=kO*> zX0XW6|H;dA#lRC-p+5;eEC%Lo^XR``5itPV;9qfjN%N1>U_=|boxxUFfZ&C>HtMgM zva`Nwr3qFGu)B{sd?DDr(0Y8OIaO&Y-SD?8Ldca_Z|7Yya7u8Q0z4k(gO!3XiWuzj zEMIkx-Ryix*L+xv>?2RWpDdpi%FaxKS&o%MyjOaLz(HHW*3`Q+^J`#;Po_}}2#H8v3G;xt{7M4xm!vQ;Ru>TGO!Qy_af*g( zi;`z*${#5)ei~|#eT#5IsLq74u*>}lapNFkj3};^X~CLzdla~QRv*}hm_33eDv#xW zSX|$Gx;`BpiI( zj1WUbbMva0pSq<~d+PdcUSS9qosFlATiC8#!>&~X>6(#l)$3xSZG#rwoosHT)m^8} zK=YpamODYuKX!K)rr+oV_g{3+o0nuaAC{iTjz!V9LoJUZ?ux(NjZ zlZ0_i2WMqcF%Bml3tF9sXeX*ge`hQ{Di$ih)3dtAzy87QCd`slC^F>f+*!5WNRf@> zn7+K(CXA3W7a4(gg}@HsK?mP0BC^kc_wRW79sSRsi7yeL>cJ*X{vEu_r>{WLV%(Y1 zanAlgsjR0R!yw6sJRw$5&xb@cwpQJw+QP13H63ORv1QbIchZd zK_gKkQ!;6MGPZu~M~n{|pO_oKyF{pMs4Ael8s}kxc5EF~xTHe&_!`}MR3XwE6-D|a z1qlf_r(BCK>uvBM^!@i*lnEc142uAa2aL%rIdcO|F;+JcCP$s9s7ckYG;Ex(J%%&% z z#TTcpRgRqU`z8!x;ODX;H*{|jv$3ir>#1^vRA4uDO8H5k;3-oAZgsW>N{T z>**medKg{k*X*8V+DFR!JfEndx0Hf?vPr2s)5}t1{?X9^)UMwnmKx(#cJGiMc$osW zGnp}A-=IUdRpV>Gms+ang`^^~%4pL30T%{U7M7{TUy6Nm)0#_z9Q!e01V{Fz5(!mI zzcwmd<(nG_Ep1oZy>PKwVZLcXL6n*Ih!q%NVKMry7)uz&&`t8dN%=xsPL~0uM?8Qq z#`M;v6oP=J`lr&@Hc{A77(Ae=W5FZCG2C{8RZ?j2Dix4RtI0lsKTBp^hj@|{@R+T;(bXx#q6L{`n) zo11PDVw0n*`Jv5^SLCZH@#61P+n5^iXggeY58MT_f!B{+=rFt|#ye#F4997}8u|rf z>e7%Yqh=9wE_|x}6Dj!o=(ecLTa{Atu=2^lRkAherZYzx=up@m-pUxW{roR+IE2At z?$A1aU$Aa{cp<*r#K~VV;Yw&R(qVDnLiWQm4Blj6UZ2m$*W@olK~(Gl-Y7w5}X=Zu_Fm52P=Z(!%g3{ff&h)=D782M0000wj6A3S literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/mic-wiring.webp b/web/public/guide/audio/mic-wiring.webp new file mode 100644 index 0000000000000000000000000000000000000000..f4a921349df928e4deea3ae274ac0587dc7c0b88 GIT binary patch literal 39598 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 literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/midi-knobs.webp b/web/public/guide/audio/midi-knobs.webp new file mode 100644 index 0000000000000000000000000000000000000000..c0ddafd89242090f127e100e11a59703b9f7b96a GIT binary patch literal 23314 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 literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/midi-session.webp b/web/public/guide/audio/midi-session.webp new file mode 100644 index 0000000000000000000000000000000000000000..77c5ee973f34add1e1abd85a659be1d5266b6c40 GIT binary patch literal 18742 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 H5e$?74Y(x@ literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/popup-audoff.webp b/web/public/guide/audio/popup-audoff.webp new file mode 100644 index 0000000000000000000000000000000000000000..41b2e80c7029fb3d4c87bee2f217d36153a34d12 GIT binary patch literal 3290 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 GZ3X~lZHs9D literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/popup-live.webp b/web/public/guide/audio/popup-live.webp new file mode 100644 index 0000000000000000000000000000000000000000..490416c48b5de8d14c3bbd0e051c551482b8eba0 GIT binary patch literal 2544 zcmV<|BMM6+kP&iE72><{u%fT!F-eBBDlA?D0X)nXUFCr!&&sSp& zqJ7j*&6xykzcVCQPU{ky^PZLL@TBS5wxvjpe8HEIg)XI-Im;7sqbFu&W@ct)<}l0d zh)9rBT9qM6c`U1AroCav!K)l*Ki3)_QFGQXbBY13ZQI74{>N+FxA`5nDee4!LHutyr?C9IJS9-e8e@BQUDN-~vtk>?j-aVR>R{S6 zsh}tA8gO@38qB`4Ch#myzYi^TD(-XND?CabH9tBl+JnL6aJDpv+q z8CQoR4u`{GoL1XbH%okb#PJUi)de^wHy4MhjOy*L#G&d?`CVKU4hIT{!m>jD9k^{9 zNgCklwHnl&nf(GGgtBb^*zhb9r`(z%4fU7r9Ju)w!N>nqtpl8=?ezmxOMzZn#K@iPYjfLVXrY?oQ zYn)lzyNC9Ee+!~LU*GNOhnAn5p-0!u?2QDBbmo{^003K5Bo1W_1=YN?Xp2`6TN+;f z0m4Br%3WFed!Sh`nOm;X6{$ozEtjnqcK9 zMoHR)cce4hCI)AVrLLvqLXD{W0fS2y3hU*YAD@g(zTw#wVM;U+gAg8{rKy%9g?D7e z5{WjRM*0aEu#&J1@1&F>+u>Pi8c8YKF(c%LXRDSmIY$$n=U8ho8NT7%nnHqo_0|8U z!}6|m4eKld>FFbGoFP#Yp5t7Mw<5-fsjVX3npYMd9F+(#v?*%c{qbprn98Y%Ey`1B z0wJb3uBH^K%}evcm?3^FH>|YUvS^Wyl7PeXxoYzx9vy_yIr>Q7A}2#SHb0W5Yo@)G z&W%rx7})y!`~kMTyzKt`f+?RxI+~XjqZEkYS@GmJq&+F*hovLA;zcSZJD+|wkuE|_ z*yKeT5Ju;y<@u1#xuu4OWgryEf=I)ciP7RE$xmVt7#{$bd?Q9m!y8g!C{8p!8({y> zcXHCykJ2Y^fZq#~Z~XJQ_akL+gzj#>tf3>hfd>)I5ScMRgek}_)?cTetB00{VV9Sx zjXqOpDT=w!&{}-31?N_Jbi&8zT*KZ<*R;r|En1FB60FJO8^Q2w3Mxo(G*%r@N^zqt z`th69TFbzYsYJU2@v`qFi_< z+LHxocuh_4t~TEtc@s&#|I$emY0lQ_ajjTBr!SVTwVmpeSiWE0_Wv7GPQ?Dk6rm~z zW!e6-{dX#8D?XxQ9p15ycfrkvz(yYWzQ1DS zhuZjkICjPR*uE>UX#QA7bmtkwNYAU{_UYcSj-5uWdA*Y_0m5Jnw2U40QfakYt2d7W zX|-D$b~=F6v5w^$s2+LG_- z2FdH>4qCX>phu?$ey4$SU=xZAha-ba?G6;o3K*f_e|JLQ-+4qbzSY9^Nna4Az?;7=O@3bdCe z0MFQfLz7{g^Q^GfGg}56a7$pKrLZF4N(*-mR6#XVk%=?N*ura!%y}g{407nxuo7oB zhT7-7_U8U2j!9@w7m_n(b zUv+`}mHKD_oSo}IKSIG|T}nTJdQ&do%v8=q(?xdgtffp?Ys&^6J4_X8!)KHP;q}77Q%f@KzR!*rZEL&7>nnVhqswogM1_WDQ7rm)lPH#KNHE+^(yrsY|;9Oy#)$rmu; zFaUbu`{J4S{K|=VHfNZwbX3&@ANw z>P_p&rT+ZycxqLls~`d}GM9g6ZGfFEA8#bnh|O4Xc^b7ocB0dG9|tVLFY8Y=LF3q% zzF{$3*8kN4F)X@Bu}hi1pTa+FanMvu8rBW`&JRUJBwop~p31FJrtq57j zih~%L3xT2k=dAkU+NZOBh}7o}Pbie0o^Jb3ED7dk>xHsx{~goN^Qu@0vPvbWgDk)( zNzeF8U4)lvpLG&oHY|&45@Aq5hi)GBi*puQ+=4YOC`E|aMUVNTzn9KXmHec zB3Ix}cLi1{T|zi<@${*@9sDk(8bqtECbEmS33*OI}V4LAN7z;T9C$?LZX$&OM4f6;Ijz!|~4l=pa-D GyUhYUS_EJK literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/popup-nosound.webp b/web/public/guide/audio/popup-nosound.webp new file mode 100644 index 0000000000000000000000000000000000000000..0def11ca729b7cd3a1e04135d096e69c72515686 GIT binary patch literal 2576 zcmV+r3h(t&Nk&Ep3IG6CMM6+kP&iEc2><{u%fT!F-ca1OjbrY=_x1XVhzZDvYdB6u zg0k-n35HWT++nMqAQ|thCGG!bNX~te-1?o&%ZQz$&Gl~IV%xUuWNq8FRmV9`P4}|) znXafe_K7zqC;ek5@4#Ni$;}n#dEi*vcFgG~N%yVc@BbBK1$NS=v~62s+qNgRZ6{;T z+kFijYuk=TebBx8_!se2!FZ;$5j)k`wr$(C6_0lUu5+}_Z*d#n=PeQ>Go=Mj|3N#)J604soQdY-Mg-P2gD)Q~y9v=1dZinT3_)^I66Wr6QN$sx~WiaR%Kr zJ0e07J5m5U;73O6HI%!&BS((lUC3mQmCaB_Xw$!o&TLy~oFx{6e#b8DPV+p|?5=91 z#;E^T*2d?2x)4l-BBBv8y<<9RT&aH-W2``w^%R!ggaI}J+y33kXk^mAQ5qjT0ICi+=lVp#db1zX|?_VD5N0woUh<+6#yeqrIVuMD0jQ~J4}j7!8==3cE+ zm2}GM?!G~NTEDgWr1*b!z)?>2ZVaQSi3A+;a8?;KTiGfMJDTugxt=rIjqQN=7y%qi z)0yoIqz*#sta%hh<6*vpxpnYD(Y#BAg6W0CfSAzd4TueYB_%}2mMKKX%2q=eMWS~M znd9?mq|h0bHu#H&s2B%tJ1)=#)WD`AXh3M4HI|cnNk%l6EAc+x!5C?T$;M=ONpdEJ zfC+(!jL%`EB{5BuXqpR!sRs6+-fc_MJc>Tt0KXT;=i!k4g#jjec-hlBYYuo*R;O9p?&msA%Lm{H@nuGGUDU0!Qj-zmDh zUtaeAjVWtmzcGcX0#q5 z1P9vGIupwdc2G~|k8%JG>dC4SHUWE0Hs`ddeiP9SW*KkN>ufr-baT>s6YA3Fi6s?~ z4j1jYP3Q=)&6qlgJ^%nP@!qsxItHFF(7*2h01&(x>zmLX0G=4@{Sg+3O9P-LPmJ~U zKs{92P~AgT+J`XPt<^CAZy4>=w}e8NcyD?sw#|S#ie4bprLVnU2y+SDgPt6@EAAY; zli+Ce!E&dfm>BEP zIJ{@1yrN2@K!$;H6CW(%YtmeizinT{5&jG!BCpwigQf=hy%o-0ukD#gRkTSZnyrqK z+-YKRKnK{Bd{1RIvzYa<4VJ*-?(&y7i zPmhH>;2iAXiitzy#upb|4NsTvC4GMCdx#7l{aO6C4R9Z2&$)&3N+V)>ttqglNTN{{-){-@)jFITtInlxul5j6>!5+q##hf_( z(ZD$5jP?YYT8X*wKR2=!{hup>PC`1D)>2GZYtJ-nRl%jT{nr!;;I&nSTq^Wg@Qj6g zN&{L`XC19f+gZ~aYfvDQn^ufYT#xjac8TEasi$LANN1o`?0rlG`I{@EqY4tCj30{E zaQ$kFv?6QJ7W1SwFdmWv}f_A znAh&k?YIdM>W@ z=BkJvL5d%i^5?C^wIx|7DI%d0FO$TzA^dH)H4m7V%Yf?(t7J)1_W?Y1eMzo^ zjJ5h&JVU?&IaAMGM^8RBSGZV`g(98g;K{nXj!gYSlB$yJ09p(2Wjk0b$q4rk*U#a^;mC zCox^myS}oVm`^rEsd#J2DN!qMrRy2AsFe;(Hns2;lz4%LYkWa03|8xF2&liBKR%U= zI04|0)EK-`rFcyWSnROuPzzSs(HDwbR!ld^HQ*MEA%7lHH&F4^kgAgK7oeG>wDDi} mc78+vs!KOFtHVc`xO${aa`P)GkL*8K)A^4MR27ih3;+OIvibD@ literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/rtpmidi-1.webp b/web/public/guide/audio/rtpmidi-1.webp new file mode 100644 index 0000000000000000000000000000000000000000..6a4d4d429051abd3cc8b3758966191719132a25c GIT binary patch literal 9558 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 aFPH%A%h>1yUE$-caT3$YF~g!$v<(1y@^(Q0 literal 0 HcmV?d00001 diff --git a/web/public/guide/audio/rtpmidi-3.webp b/web/public/guide/audio/rtpmidi-3.webp new file mode 100644 index 0000000000000000000000000000000000000000..95543473f46d612069c0ab406a0e0b7953a09bc8 GIT binary patch literal 13540 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 Iifx?%0Ct*vvH$=8 literal 0 HcmV?d00001 diff --git a/web/public/llms.txt b/web/public/llms.txt index b984c2fe1..7536b4f6c 100644 --- a/web/public/llms.txt +++ b/web/public/llms.txt @@ -25,6 +25,7 @@ It is not a single luminous object but an open system. The hardware files, firmw - [Build](https://patternflow.work/guide/build): Assembling one from the PCB, the printed case and the parts list, step by step in 3D. - [Play](https://patternflow.work/guide/play): The first hour with a finished board — flashing the ESP32, the four knobs, installing patterns, the device console. - [Make](https://patternflow.work/guide/make): A hands-on tutorial for the community and the Pattern Lab, from a prompt to a pattern on the device. + - [Audio](https://patternflow.work/guide/audio): The Audio edition — sound from a browser tab (the extension and its mapping editor), the on-board PDM microphone, and the board as a network MIDI device with a DAW (Ableton Live as the example). - [Build your own](https://patternflow.work/build): Build the device from scratch — firmware, PCB, 3D-printed enclosure, browser flasher, and the full build guide. - [Inside](https://patternflow.work/inside): How Patternflow works — the electronics, encoders, and pattern architecture. - [Journal](https://patternflow.work/journal): Long-form notes and the story behind each step (English and Korean). diff --git a/web/src/app/editions/editions-data.ts b/web/src/app/editions/editions-data.ts index 8bd042ae6..3f59a6e58 100644 --- a/web/src/app/editions/editions-data.ts +++ b/web/src/app/editions/editions-data.ts @@ -228,7 +228,7 @@ export const EDITIONS: Edition[] = [ 'sensitivity on the console, the session reconnects itself', 'OSC — Ableton, Max and TouchDesigner, both directions', 'Browser and tab audio through the Chrome extension', - 'On-board PDM microphone — four wires to GPIO43/44, no computer needed', + 'On-board PDM microphone — five leads, the two signals on GPIO43/44, no computer needed', 'The mapping editor: bands as boxes on the live spectrum, response ' + 'curves from rise to gate to hand-drawn', 'Wi-Fi transmit power raised for rooms full of access points', @@ -242,7 +242,7 @@ export const EDITIONS: Edition[] = [ source: 'https://github.com/engmung/Patternflow/tree/main/firmware/bundles/audio', note: 'Six of seven people asked for on-board sound and it was the most ' + - 'wanted thing in the survey by a wide margin — but it needs four wires ' + + 'wanted thing in the survey by a wide margin — but it needs five leads ' + 'soldered to the DevKit and the radio setting here is not the ' + 'conformance-tested one, so neither belongs in the firmware everybody ' + 'gets. It is also where that work happens, which is why it carries ' + @@ -250,7 +250,7 @@ export const EDITIONS: Edition[] = [ 'experiment with sound, not to run a room. When the microphone is a ' + 'part on the board, on-board audio moves into the default. ' + 'Installing this without the microphone is safe: the panel can tell a ' + - 'missing mic from a quiet room, says so on the Mic page, and lets ' + + 'missing mic from a quiet room, says so on the Audio page, and lets ' + 'nothing drive the knobs until you turn it on.', }, ]; diff --git a/web/src/app/guide/audio/ko/page.tsx b/web/src/app/guide/audio/ko/page.tsx new file mode 100644 index 000000000..34f691a37 --- /dev/null +++ b/web/src/app/guide/audio/ko/page.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; +import GuideExperience from "@/components/guide/GuideExperience"; +import { guideMetadata } from "@/components/guide/meta"; + +// Audio: the Audio edition, then a browser tab, the microphone, MIDI and a DAW. + +export const metadata: Metadata = guideMetadata("audio", "ko"); + +export const dynamic = "force-static"; + +export default function GuideAudioPageKo() { + return ; +} diff --git a/web/src/app/guide/audio/page.tsx b/web/src/app/guide/audio/page.tsx new file mode 100644 index 000000000..eb349953c --- /dev/null +++ b/web/src/app/guide/audio/page.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; +import GuideExperience from "@/components/guide/GuideExperience"; +import { guideMetadata } from "@/components/guide/meta"; + +// Audio: the Audio edition, then a browser tab, the microphone, MIDI and a DAW. + +export const metadata: Metadata = guideMetadata("audio", "en"); + +export const dynamic = "force-static"; + +export default function GuideAudioPage() { + return ; +} diff --git a/web/src/app/guide/ko/page.tsx b/web/src/app/guide/ko/page.tsx index d365505c0..9eac0efea 100644 --- a/web/src/app/guide/ko/page.tsx +++ b/web/src/app/guide/ko/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import GuideHub from "@/components/guide/GuideHub"; import { guideMetadata } from "@/components/guide/meta"; -// The guide's front door: pick Build, Play or Make by where your Patternflow +// The guide's front door: pick Build, Play, Make or Audio by where your Patternflow // is (GuideHub). /guide used to be the Play guide; its old anchors // (/guide#flash …) are sent on to /guide/play (components/guide/legacy.ts). diff --git a/web/src/app/guide/layout.tsx b/web/src/app/guide/layout.tsx index 3a48a1193..9a21d8084 100644 --- a/web/src/app/guide/layout.tsx +++ b/web/src/app/guide/layout.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; import GuideWorld from "@/components/guide/world/GuideWorld"; -// Everything under /guide — the hub, Build, Play and Make, in both languages +// Everything under /guide — the hub, Build, Play, Make and Audio, 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 diff --git a/web/src/app/guide/page.tsx b/web/src/app/guide/page.tsx index 52505f1fc..bb7203369 100644 --- a/web/src/app/guide/page.tsx +++ b/web/src/app/guide/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import GuideHub from "@/components/guide/GuideHub"; import { guideMetadata } from "@/components/guide/meta"; -// The guide's front door: pick Build, Play or Make by where your Patternflow +// The guide's front door: pick Build, Play, Make or Audio by where your Patternflow // is (GuideHub). /guide used to be the Play guide; its old anchors // (/guide#flash …) are sent on to /guide/play (components/guide/legacy.ts). diff --git a/web/src/app/sitemap.ts b/web/src/app/sitemap.ts index 295495f93..5ff658f3e 100644 --- a/web/src/app/sitemap.ts +++ b/web/src/app/sitemap.ts @@ -13,9 +13,10 @@ export default function sitemap(): MetadataRoute.Sitemap { { url: `${siteUrl}/build`, lastModified: now, changeFrequency: "monthly", priority: 0.8 }, { url: `${siteUrl}/inside`, lastModified: now, changeFrequency: "monthly", priority: 0.7 }, { url: `${siteUrl}/roadmap`, lastModified: now, changeFrequency: "weekly", priority: 0.6 }, - // The guide: the hub, then its three guides in order — Build (soldering + // The guide: the hub, then its four guides in order — Build (soldering // one from bare parts), Play (01 Flash to 04 Console), Make (the - // community and Pattern Lab). + // community and Pattern Lab), Audio (the Audio edition: a browser tab, + // the microphone, MIDI and a DAW). { url: `${siteUrl}/guide`, lastModified: now, changeFrequency: "monthly", priority: 0.8 }, { url: `${siteUrl}/guide/ko`, lastModified: now, changeFrequency: "monthly", priority: 0.7 }, { url: `${siteUrl}/guide/build`, lastModified: now, changeFrequency: "monthly", priority: 0.7 }, @@ -24,6 +25,8 @@ export default function sitemap(): MetadataRoute.Sitemap { { url: `${siteUrl}/guide/play/ko`, lastModified: now, changeFrequency: "monthly", priority: 0.7 }, { url: `${siteUrl}/guide/make`, lastModified: now, changeFrequency: "monthly", priority: 0.7 }, { url: `${siteUrl}/guide/make/ko`, lastModified: now, changeFrequency: "monthly", priority: 0.6 }, + { url: `${siteUrl}/guide/audio`, lastModified: now, changeFrequency: "monthly", priority: 0.7 }, + { url: `${siteUrl}/guide/audio/ko`, lastModified: now, changeFrequency: "monthly", priority: 0.6 }, // The shelf of firmwares built on core. A device running an edition links // here from its own console, so this needs to be findable from outside too. // (/variants only redirects here.) diff --git a/web/src/components/guide/AudioShots.module.css b/web/src/components/guide/AudioShots.module.css new file mode 100644 index 000000000..ebe565d92 --- /dev/null +++ b/web/src/components/guide/AudioShots.module.css @@ -0,0 +1,472 @@ +/* The Audio guide's captures and photos inside a guide card (AudioShots.tsx). + Three looks. A "crop" is a piece of a page — the console, the extension's + popup, Live's settings — and its frame takes that page's own ground, so a + crop that stops short reads as more of the same page and not as a hole in + the card. A "window" (rtpMIDI) is a whole window with a rim of dark + desktop round it, at the card's width. A "photo" fills its frame. Tokens + come from .page in Guide.module.css; the fallbacks keep it legible + anywhere else. + Shaped after FlasherShots, LabShots and CommunityShots. */ + +.shots { + margin: 18px 0 4px; + padding: 0; +} + +.frame { + position: relative; + width: 100%; + /* Its shape is the set's largest frame (--ar: AudioShots.tsx). */ + aspect-ratio: var(--ar); + margin: 0 auto; + border-radius: 12px; + border: 1px solid var(--g-hair-2, rgba(239, 233, 221, 0.18)); + background: var(--ground, #0b0b0c); + overflow: hidden; + isolation: isolate; +} +/* The dimmed desktop a window stands on: a rim of it, no more. The window + has a program's worth of small type and wants the width. */ +.shots[data-look="window"] .frame { + border-radius: 10px; + border-color: var(--g-hair, rgba(239, 233, 221, 0.1)); + background: + radial-gradient(120% 100% at 50% 40%, rgba(239, 233, 221, 0.075), rgba(239, 233, 221, 0.03) 75%), + #171614; +} + +/* On a wide screen the frame may take a little of the card's padding — the + screens are small type — and is never taller than its share of the + viewport (--cap: AudioShots.tsx LOOKS). */ +@media (min-width: 900px) { + .shots { + margin-inline: -10px; + } + .frame { + width: min(100%, var(--cap, 100%)); + } + .caption { + padding-inline: 10px; + } +} +@media (max-width: 899px) { + .frame { + width: min(100%, var(--cap-narrow, 100%)); + } +} +/* A laptop's short screen (under 880 px of page): everything in the card + sets closer (Guide.module.css), and the frame draws a little smaller + (--cap-short: AudioShots.tsx LOOKS). */ +@media (min-width: 900px) and (max-height: 879px) { + .shots { + margin-top: 12px; + } + .frame { + width: min(100%, var(--cap-short, 100%)); + } +} + +/* One frame of the set: the image and, over it, the ring. Every frame is + drawn at the same scale and hangs from the top of the frame; a window + stands in its middle. */ +.shot { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, 0); + opacity: 0; + pointer-events: none; + user-select: none; + /* The next frame comes up over the last one, which only goes once it is + covered: never a frame with nothing in it. */ + z-index: 1; + transition: opacity 0.12s linear 0.34s; +} +.shot[data-on="1"] { + z-index: 2; + opacity: 1; + transition: opacity 0.34s ease; +} +.shot img { + display: block; + width: 100%; + height: 100%; + max-width: none; +} +/* A photo fills its frame, whatever shape the frame has taken. */ +.shots[data-look="photo"] .shot { + height: 100%; +} +.shots[data-look="photo"] .shot img { + object-fit: cover; +} +.shots[data-look="window"] .shot { + top: 50%; + transform: translate(-50%, -50%) scale(0.985); + filter: drop-shadow(0 10px 26px rgba(0, 0, 0, 0.55)); + transition: + opacity 0.12s linear 0.3s, + transform 0s linear 0.42s; +} +.shots[data-look="window"] .shot[data-on="1"] { + transform: translate(-50%, -50%); + transition: + opacity 0.3s ease, + transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1); +} +/* A Windows window's own corners. */ +.shots[data-look="window"] .shot img { + border-radius: 7px; +} + +/* The control a frame is about. Remounted per frame: it arrives just after + the frame does, then breathes. The captures are light pages, so the ring + keeps the guide's fixed orange — the panel's own colour can be one that + does not read on cream. */ +.ring { + position: absolute; + z-index: 3; + box-sizing: border-box; + border: 2px solid var(--g-led-fixed, #ff6a3d); + border-radius: 7px; + pointer-events: none; + box-shadow: + 0 0 0 3px rgb(255 106 61 / 0.2), + 0 0 16px rgb(255 106 61 / 0.32); + animation: + ringIn 0.42s cubic-bezier(0.2, 0.7, 0.2, 1) 0.22s both, + ringBreathe 1.9s ease-in-out 0.7s infinite; +} +@keyframes ringIn { + from { + opacity: 0; + transform: scale(1.18); + } + to { + opacity: 1; + transform: scale(1); + } +} +@keyframes ringBreathe { + 0%, + 100% { + box-shadow: + 0 0 0 3px rgb(255 106 61 / 0.2), + 0 0 16px rgb(255 106 61 / 0.32); + } + 50% { + box-shadow: + 0 0 0 6px rgb(255 106 61 / 0.08), + 0 0 24px rgb(255 106 61 / 0.45); + } +} + +.caption { + margin-top: 12px; +} + +/* A set of more than one frame: its dots, and beside them the words of the + frame that is up — one row, so the caption costs the card as little + height as it can. */ +.bar { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 7px; +} +.dots { + display: flex; + align-items: center; + gap: 6px; + flex: none; +} +.dot { + position: relative; + display: grid; + place-items: center; + width: 24px; + height: 24px; + padding: 0; + border: 1px solid var(--g-hair-2, rgba(239, 233, 221, 0.18)); + border-radius: 999px; + background: transparent; + color: var(--g-ink-3, rgba(239, 233, 221, 0.52)); + font: 600 10.5px/1 var(--pf-mono, ui-monospace, monospace); + cursor: pointer; + transition: + background 0.2s, + border-color 0.2s, + color 0.2s; +} +/* A bigger hit area than the dot itself, without spreading the row. */ +.dot::before { + content: ""; + position: absolute; + inset: -6px -3px; +} +.dot[data-done="1"] { + 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"] { + border-color: var(--g-led, #ff6a3d); + background: var(--g-led, #ff6a3d); + color: #150a06; +} +.dot:hover:not([data-on="1"]) { + border-color: var(--g-ink-3, rgba(239, 233, 221, 0.52)); + color: var(--g-ink, #efe9dd); +} +.dot:focus-visible { + outline: 2px solid var(--g-led, #ff6a3d); + outline-offset: 2px; +} + +/* The words of each frame share one cell, as the sentences under them do: + nothing in the caption moves when the frame changes. */ +.heads { + display: grid; + flex: 1; + min-width: 0; +} +.head { + grid-area: 1 / 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 13px; + line-height: 1.5; + font-weight: 600; + color: var(--g-ink, #efe9dd); + opacity: 0; + visibility: hidden; + transition: + opacity 0.25s ease, + visibility 0s linear 0.25s; +} +.head[data-on="1"] { + opacity: 1; + visibility: visible; + transition: + opacity 0.3s ease 0.12s, + visibility 0s; +} + +/* Every caption sits in the same grid cell, so the block is as tall as the + longest one and nothing below it jumps when the frame changes. */ +.texts { + display: grid; +} +.text { + text-wrap: pretty; + grid-area: 1 / 1; + margin: 0; + font-size: 13px; + line-height: 1.5; + color: var(--g-ink-2, rgba(239, 233, 221, 0.78)); + opacity: 0; + visibility: hidden; + transition: + opacity 0.25s ease, + visibility 0s linear 0.25s; +} +.text[data-on="1"] { + opacity: 1; + visibility: visible; + transition: + opacity 0.3s ease 0.12s, + visibility 0s; +} +/* A single frame: the screen's own words run in at the head of its sentence. */ +.text b { + margin-right: 0.5em; + font-weight: 600; + color: var(--g-ink, #efe9dd); +} + +/* ── mic · 4: an upright photo, its caption beside it ───────────────────── */ +.shots[data-beside] { + display: grid; + align-items: center; + column-gap: 16px; +} +.shots[data-beside="caption"] { + grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr); +} +.shots[data-beside="caption"] .caption { + margin-top: 0; + padding-inline: 0; +} +@media (min-width: 900px) { + .shots[data-beside] { + padding-inline: 0 10px; + } +} +/* A card at its full width has the room to give the photo the larger half: + the caption still sets in lines long enough to read (a narrower card's, + or a phone's, would break inside "USB-C"). */ +@media (min-width: 1100px) { + .shots[data-beside="caption"] { + grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); + } +} + +/* ── mic · 3: the photo a reader solders by ─────────────────────────────── + It takes the card's whole width, at any size, and hangs from its foot: + the pins the leads land on. Where the screen has the room the frame is + the whole capture, the breakout's row of pads at its top. Where it has + not, the frame is shorter and what goes is the top of the picture, never + its scale: the silkscreen by the pins is what has to be read. */ +.shots[data-foot] .frame { + width: 100%; +} +.shots[data-foot] .shot img { + object-position: 50% 100%; +} +/* A wide screen under 900 px tall, or one whose card is narrower than its + full 440 px (the words run to more lines): from under the breakout down + (--ar-short), and less of the leads still as the screen gets shorter. + The card holds under the header while its block passes (Guide.module.css, + Audio's dwell cards), so the frame may take what the rest of the card + leaves of the screen (--rest: AudioShots.tsx). */ +@media (min-width: 900px) and (max-height: 899px), (min-width: 900px) and (max-width: 1099px) { + .shots[data-foot] .frame { + aspect-ratio: var(--ar-short); + max-height: max(150px, calc(100vh - var(--rest, 570px))); + } +} +/* A phone: the rows of pins and the leads' ends (--ar-narrow). The card is + already most of the screen. */ +@media (max-width: 899px) { + .shots[data-foot] .frame { + aspect-ratio: var(--ar-narrow); + } +} + +/* The leads under it, pad to pin: the pads across the top, in the order + they stand on the breakout, and under each the pin it goes to. These are + names a reader solders by, digits and all: the body face, never the pixel + one. A place two leads share is one cell under both — five leads, four + places. */ +.leads { + width: 100%; + margin-top: 10px; + border-collapse: collapse; + font-family: var(--pf-sans, system-ui, sans-serif); + font-size: 14px; + line-height: 1.2; + font-variant-numeric: tabular-nums; + text-align: center; + color: var(--g-ink, #efe9dd); +} +.leads th, +.leads td { + padding: 0; + vertical-align: baseline; +} +/* What each row is: as wide as its word and no wider; the five leads share + what is left. */ +.leads th[scope="row"] { + width: 1%; + padding-right: 12px; + text-align: left; + white-space: nowrap; + font-size: 11.5px; + font-weight: 500; + color: var(--g-ink-3, rgba(239, 233, 221, 0.52)); +} +.leads th[scope="col"] { + width: 19.8%; + font-weight: 500; + color: var(--g-ink-2, rgba(239, 233, 221, 0.78)); +} +.leads tbody th, +.leads tbody td { + padding-top: 24px; +} +.leads tbody td { + position: relative; + font-weight: 600; +} +.leads tbody td::before { + content: "↓"; + position: absolute; + top: 9px; + left: 0; + right: 0; + font-size: 12px; + line-height: 1; + font-weight: 400; + color: var(--g-ink-3, rgba(239, 233, 221, 0.52)); +} +/* Two leads, one place: a bracket under the pair of pads, from the middle + of one to the middle of the other. */ +.leads tbody td[data-shared]::after { + content: ""; + position: absolute; + top: 3px; + left: 25%; + right: 25%; + height: 4px; + border: 1px solid var(--g-ink-3, rgba(239, 233, 221, 0.52)); + border-top: 0; + border-radius: 0 0 3px 3px; +} +/* On a wide screen the frame takes a little of the card's padding; the + table keeps to the column the caption and the card's words are in. */ +@media (min-width: 900px) { + .leads { + width: calc(100% - 20px); + margin-inline: 10px; + } +} + +.srOnly { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} + +/* The short laptop screen again, after the rules it tightens. */ +@media (min-width: 900px) and (max-height: 879px) { + .caption { + margin-top: 9px; + } + .bar { + margin-bottom: 5px; + } +} +@media (max-width: 899px) { + .caption { + margin-top: 10px; + } + .text, + .head { + font-size: 12.5px; + line-height: 1.45; + } +} + +@media (prefers-reduced-motion: reduce) { + .shot, + .shot[data-on="1"], + .shots[data-look="window"] .shot, + .shots[data-look="window"] .shot[data-on="1"], + .text, + .text[data-on="1"], + .head, + .head[data-on="1"], + .dot { + transition: none; + } + .shots[data-look="window"] .shot { + transform: translate(-50%, -50%); + } + .ring { + animation: none; + } +} diff --git a/web/src/components/guide/AudioShots.tsx b/web/src/components/guide/AudioShots.tsx new file mode 100644 index 000000000..4242d2cd9 --- /dev/null +++ b/web/src/components/guide/AudioShots.tsx @@ -0,0 +1,715 @@ +"use client"; + +// The Audio guide's real screens and photos, a set a step (copy.ts +// AudioShotSet; a step's copy names one as extra: "audioShots:"): the +// extension's popup, the two microphone photos, the console's Audio and MIDI +// pages, rtpMIDI and Live. Nothing is redrawn. Every image in /guide/audio/ +// is a capture of the real thing or a photo that already existed: +// +// popup-* the extension's popup against a real board, cropped to its +// top (the corner badge, the address, the line under it) +// audio-in-* the console's /audio-in from the mock (console_serve.py, +// /mock?audio=fresh|music|nomic), with the pages an Audio +// board has, at a phone's width, 2x +// midi-* the console's /midi from the mock, its fixture set for the +// shot: the board at the guide's address (deviceSim SIM_IP), +// the computer at the page's own example, factory sensitivity +// rtpmidi-* docs/images/midi/rtpmidi-1..4, whole +// live-remote docs/images/midi/ableton-5-remote.png, cropped to the ports +// mic-* docs/images/audio-guide/pdm-mic-wiring.jpg (framed on the +// breakout; and from the breakout's row of pads down to the +// pins the leads land on, at the photo's own pixels) and the +// journal's photo of the wired module seated in a board +// +// The crops are tight on purpose: the cards these stand in are full, and a +// crop keeps the capture's own words at a size that can be read. If a page +// changes, re-shoot it rather than editing the image: the sizes and rings +// below (GEO) are what the capture script prints. +// +// A set with more than one frame is stepped the way FlasherShots is: on its +// own only while its step is on stage (useGuideStore: page "audio", the +// set's scene, step `step`) and the frame is on screen, never under the +// pointer or keyboard focus, and not at all for reduced motion. The bold +// words of a caption are the screen's own, as it writes them (English in +// both languages); the sentence is this file's, in both. + +import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; +import type { AudioShotSet } from "./copy"; +import { useGuideStore, type GuideLang } from "./store"; +import styles from "./AudioShots.module.css"; + +/** Where a ring sits: [left, top, width, height] as fractions of the image. */ +type Ring = [number, number, number, number]; + +/** An image in /guide/audio/: its size in CSS px as captured, and the control it is about. */ +type Geo = { w: number; h: number; rings: Ring[] }; + +// The capture script's own numbers; re-run it, don't edit by hand. +const GEO = { + "popup-idle": { w: 460, h: 180, rings: [[0.0239, 0.5167, 0.9522, 0.2556]] }, + "popup-live": { w: 460, h: 180, rings: [[0.7761, 0.0611, 0.2, 0.2056]] }, + "popup-audoff": { w: 460, h: 220, rings: [[0.0239, 0.7364, 0.187, 0.1909]] }, + "popup-nosound": { w: 460, h: 180, rings: [[0.0239, 0.7611, 0.3283, 0.1389]] }, + "rtpmidi-1": { w: 612, h: 405, rings: [] }, + "rtpmidi-2": { w: 611, h: 403, rings: [] }, + "rtpmidi-3": { w: 611, h: 405, rings: [] }, + "rtpmidi-4": { w: 610, h: 403, rings: [] }, + "live-remote": { w: 491, h: 180, rings: [] }, + "mic-part": { w: 640, h: 480, rings: [] }, + "mic-wiring": { w: 1000, h: 805, rings: [] }, + "mic-seated": { w: 900, h: 1200, rings: [] }, + "audio-in-off": { w: 440, h: 289, rings: [[0.0273, 0.673, 0.3115, 0.1384]] }, + "audio-in-listening": { + w: 440, + h: 264, + rings: [ + [0.3433, 0.7557, 0.1909, 0.1136], + [0.5071, 0.4242, 0.4656, 0.0833], + ], + }, + "audio-in-nomic": { w: 440, h: 202, rings: [[0.3433, 0.5569, 0.2349, 0.1485]] }, + "audio-in-mapping": { w: 520, h: 282, rings: [] }, + "midi-knobs": { w: 520, h: 324, rings: [] }, + "midi-session": { w: 520, h: 297, rings: [[0.1504, 0.7555, 0.4094, 0.1347]] }, +} satisfies Record; + +type Shot = { + /** The frame's short name: its dot's label, and AUDIO_SHOT_FRAMES. */ + name: string; + /** web/public/guide/audio/.webp */ + file: keyof typeof GEO; + /** The screen's own words, quoted exactly (English in both languages). A photo has none. */ + title?: string; + en: string; + ko: string; + /** What the picture shows, for a reader who cannot see it. */ + alt: Record; + /** How long this frame stays up before the next one (ms). */ + ms?: number; +}; + +/** + * How a set is drawn. "crop" is a piece of a page: it fills the frame from + * the top, and the frame takes the page's own ground, so a shorter crop reads + * as more of the same page. "window" is a whole window at the card's width, + * a rim of dark desktop round it. "photo" fills its frame. + */ +type Look = "crop" | "window" | "photo"; + +type ShotSet = { + /** The chapter the set's step is in (scenes/audio.ts). */ + scene: "browser" | "mic" | "midi"; + look: Look; + /** A crop's ground: the colour of the page it was cut from. */ + ground?: string; + /** What stands beside the picture instead of under it: the caption. */ + beside?: "caption"; + /** What stands between the picture and its caption: the lead table. */ + under?: "leads"; + /** + * A photo that takes the card's whole width and hangs from its foot. On a + * screen with no room for all of it the frame keeps this many of the + * capture's rows, counted from the foot, and what goes is the top: `short` + * on a wide screen under 900 px tall (and fewer still where `rest` leaves + * fewer), `narrow` on a phone. + */ + foot?: { short: number; narrow: number }; + /** More of the screen's height than its look allows (LOOKS), for a set whose card has the room. */ + vh?: number; + /** + * For a set whose card holds under the header while its block passes + * (scenes/audio.ts dwell; Guide.module.css stops it 72 px down): what the + * rest of a short wide screen comes to, in px. The header, the card's + * words in English at their short-screen size, the set's own caption (and + * table) and a margin. The frame may take what that leaves of the screen + * where it is more than its look's share (a `foot` photo takes just that), + * so the card's foot stays on screen and the picture is as large as that + * allows. Measured, not derived: if the step's copy grows by a paragraph, + * measure again. + */ + rest?: number; + /** What the set is, for a screen reader. */ + label: Record; + shots: Shot[]; +}; + +// The console's and the popup's page colour; Live's settings pane. +const CREAM = "#f4efe6"; +const LIVE_GREY = "#4f4f4f"; + +const SETS: Record = { + // browser · 2 — the extension's popup before and after Start + popup: { + scene: "browser", + look: "crop", + ground: CREAM, + label: { en: "The extension's popup, before and after Start", ko: "확장 프로그램 팝업, Start 전과 후" }, + shots: [ + { + name: "Idle", + file: "popup-idle", + title: "Idle", + en: "Before Start. The address goes under Device.", + ko: "Start 전이에요. 주소는 Device 아래에 넣어요.", + alt: { + en: "The top of the extension's popup: the corner badge reads Idle, and patternflow.local is in the Device field beside a Start button.", + ko: "확장 프로그램 팝업의 윗부분. 구석 배지는 Idle, Device 칸엔 patternflow.local, 그 옆에 Start 버튼이 있다.", + }, + }, + { + name: "Live", + file: "popup-live", + title: "Live", + en: "Start is now Stop; the line under it names the tab.", + ko: "Start가 Stop이 되고, 그 아래 줄에 탭 이름이 나와요.", + alt: { + en: "The top of the extension's popup while capturing: the corner badge reads Live, the button reads Stop, and the line under the address begins with Capturing.", + ko: "캡처 중인 확장 프로그램 팝업의 윗부분. 구석 배지는 Live, 버튼은 Stop, 주소 아래 줄은 Capturing으로 시작한다.", + }, + }, + ], + }, + // browser · 3 — why it is not Live + popupStates: { + scene: "browser", + look: "crop", + ground: CREAM, + label: { en: "The extension's popup when the board is not moving", ko: "보드가 움직이지 않을 때의 확장 프로그램 팝업" }, + shots: [ + { + name: "Aud off", + file: "popup-audoff", + title: "Aud off", + en: "The board is ignoring the sound. Turn on brings it back.", + ko: "보드가 소리를 무시하는 중이에요. Turn on으로 다시 켜요.", + alt: { + en: "The top of the extension's popup: the corner badge reads Aud off, the line under the address says Audio-React is off, and a Turn on button sits under it.", + ko: "확장 프로그램 팝업의 윗부분. 구석 배지는 Aud off, 주소 아래 줄은 Audio-React가 꺼져 있다고 하고, 그 아래 Turn on 버튼이 있다.", + }, + }, + { + name: "No sound", + file: "popup-nosound", + title: "No sound from this tab yet.", + en: "Still Live: the tab has been silent for two seconds.", + ko: "여전히 Live예요. 탭이 2초 동안 조용했어요.", + alt: { + en: "The top of the extension's popup: the corner badge reads Live, and the line under the address reads No sound from this tab yet.", + ko: "확장 프로그램 팝업의 윗부분. 구석 배지는 Live, 주소 아래 줄은 No sound from this tab yet.", + }, + }, + ], + }, + // mic · 1 — the breakout (the wiring photo, framed on the part) + micPart: { + scene: "mic", + look: "photo", + label: { en: "The microphone breakout", ko: "마이크 브레이크아웃" }, + shots: [ + { + name: "The breakout", + file: "mic-part", + en: "The breakout's silkscreen: PDM Mic, and a pad each for 3V, GND, SEL, CLK and DAT.", + ko: "브레이크아웃의 실크 인쇄예요. PDM Mic, 그리고 3V, GND, SEL, CLK, DAT 패드가 하나씩.", + alt: { + en: "A small blue breakout board marked PDM Mic, with five soldered pads labelled 3V, GND, SEL, CLK and DAT and a black lead on each.", + ko: "PDM Mic이라고 적힌 작은 파란 브레이크아웃 보드. 3V, GND, SEL, CLK, DAT 패드 다섯 개에 검은 선이 하나씩 납땜돼 있다.", + }, + }, + ], + }, + // mic · 3 — the wiring photo at the card's width, the five-lead table under + // it. This is the picture a reader solders by, so it is drawn as large as + // the card allows and hangs from its foot, where the leads land: all of it + // (the breakout's row of pads, the leads, the pins) where the screen has the + // room; from under the breakout (the capture's last 655 rows) on a shorter + // one; the pin rows alone (its last 400) on a phone. + micWiring: { + scene: "mic", + look: "photo", + under: "leads", + foot: { short: 655, narrow: 360 }, + rest: 570, + label: { en: "The five leads, from the microphone to the ESP32", ko: "마이크에서 ESP32로 가는 선 다섯 가닥" }, + shots: [ + { + name: "Five leads, four places", + file: "mic-wiring", + en: "In the photo, 3V3 is the top pin on the left. GND, TX and RX are the top three on the right; GND holds two leads.", + ko: "사진에서 3V3은 왼쪽 줄 맨 위 핀이에요. GND, TX, RX는 오른쪽 줄 위에서 셋이고, GND엔 선이 둘 붙어요.", + alt: { + en: "Five black leads run from the microphone breakout's pads down to the ESP32 module's header pins: one to 3V3 at the top of the left pin row, two to GND, and one each to TX and RX at the top of the right row.", + ko: "마이크 브레이크아웃의 패드에서 ESP32 모듈의 헤더 핀으로 검은 선 다섯 가닥이 내려온다. 하나는 왼쪽 핀 줄 맨 위 3V3로, 둘은 GND로, TX와 RX로 하나씩 오른쪽 줄 위쪽에 닿는다.", + }, + }, + ], + }, + // mic · 4 — the wired module seated in a board + micSeated: { + scene: "mic", + look: "photo", + beside: "caption", + label: { en: "The wired ESP32 back in the board", ko: "선을 단 ESP32를 보드에 다시 꽂은 모습" }, + shots: [ + { + name: "Seated", + file: "mic-seated", + en: "The ESP32 back in its sockets, the microphone above it on its leads. The two USB-C ports at the bottom are the ESP32's own; UART is the right one.", + ko: "ESP32를 소켓에 다시 꽂았어요. 마이크는 선에 달린 채 그 위에 있어요. 아래 USB-C 포트 둘은 ESP32의 것이고, 오른쪽이 UART예요.", + alt: { + en: "The back of a Patternflow board in its case: the ESP32 module seated in its sockets, the microphone breakout above it on five short leads, and the module's two USB-C ports at the bottom, both empty.", + ko: "케이스 안 패턴플로우 보드의 뒷면. ESP32 모듈이 소켓에 꽂혀 있고, 그 위에 마이크 브레이크아웃이 짧은 선 다섯 가닥으로 달려 있다. 모듈 아래쪽 USB-C 포트 둘은 비어 있다.", + }, + }, + ], + }, + // mic · 5 — the console's Audio page: Microphone off, then listening + audioIn: { + scene: "mic", + look: "crop", + ground: CREAM, + // The step's words are few and the crop is tall (the header's two rows of + // pages are in it): on a short laptop screen it would be the smallest. + vh: 36, + label: { en: "The console's Audio page: the Microphone switch", ko: "콘솔 Audio 페이지의 Microphone 스위치" }, + shots: [ + { + name: "Off", + file: "audio-in-off", + title: "Microphone", + en: "Under Sources. Off, the chip beside it reads off.", + ko: "Sources 아래에 있어요. 꺼져 있으면 옆의 칩이 off예요.", + alt: { + en: "The console's Audio page: under Sources the Microphone switch is off, the chip beside it reads off, and Input gain is at 8.0.", + ko: "콘솔의 Audio 페이지. Sources 아래 Microphone 스위치가 꺼져 있고, 옆의 칩은 off, Input gain은 8.0이다.", + }, + }, + { + name: "Listening", + file: "audio-in-listening", + title: "listening", + en: "On, and the top of the page says who has the knobs.", + ko: "켜면, 페이지 위쪽 줄이 누가 노브를 쥐었는지 알려 줘요.", + alt: { + en: "The console's Audio page: the Microphone switch is on, the chip beside it reads listening, and the line at the top right reads the microphone has the knobs.", + ko: "콘솔의 Audio 페이지. Microphone 스위치가 켜져 있고, 옆의 칩은 listening, 오른쪽 위 줄은 the microphone has the knobs.", + }, + }, + ], + }, + // mic · 6 — the Mapping half of the Audio page: its plot + audioInMap: { + scene: "mic", + look: "crop", + ground: CREAM, + label: { en: "The console's Audio page: Mapping", ko: "콘솔 Audio 페이지의 Mapping" }, + shots: [ + { + name: "Mapping", + file: "audio-in-mapping", + title: "Mapping", + en: "K1 to K4, boxed on the spectrum the microphone hears.", + ko: "마이크가 듣는 스펙트럼 위에 K1부터 K4까지 상자가 놓여 있어요.", + alt: { + en: "The Mapping plot on the console's Audio page: a spectrum from 31 Hz to 8 kHz, level in dB, with four boxes on it, K1 to K4 from left to right.", + ko: "콘솔 Audio 페이지의 Mapping 그래프. 31 Hz부터 8 kHz까지, 세로는 dB인 스펙트럼 위에 왼쪽부터 K1–K4 상자 네 개가 있다.", + }, + }, + ], + }, + // mic · 7 — the chip reads "not detected" + audioInNoMic: { + scene: "mic", + look: "crop", + ground: CREAM, + label: { en: "The console's Audio page with no microphone found", ko: "마이크를 찾지 못한 콘솔 Audio 페이지" }, + shots: [ + { + name: "Not detected", + file: "audio-in-nomic", + title: "not detected", + en: "Under the slider, the page's own reason: the data pin (RX) is idle, check the DAT lead.", + ko: "슬라이더 아래에 페이지가 이유를 적어 줘요. 데이터 핀(RX)이 조용하니 DAT 선을 보라고요.", + alt: { + en: "The console's Audio page: the Microphone switch is on, the chip beside it reads not detected, and the line under the Input gain slider reads No microphone found: the data pin (RX) is idle. Check the DAT lead.", + ko: "콘솔의 Audio 페이지. Microphone 스위치는 켜져 있지만 옆의 칩은 not detected이고, Input gain 슬라이더 아래 줄은 No microphone found: the data pin (RX) is idle. Check the DAT lead.", + }, + }, + ], + }, + // midi · 1 — rtpMIDI: new session, enable, connect, connected + rtpmidi: { + scene: "midi", + look: "window", + rest: 598, + label: { en: "rtpMIDI, screen by screen", ko: "rtpMIDI 화면 순서" }, + shots: [ + { + name: "New session", + file: "rtpmidi-1", + title: "My Sessions", + en: "Press +. patternflow is already listed in Directory.", + ko: "+를 누르세요. Directory엔 patternflow가 이미 있어요.", + alt: { + en: "The rtpMIDI window: My Sessions is empty with a + button under it, and Directory lists patternflow.", + ko: "rtpMIDI 창. My Sessions는 비어 있고 아래에 + 버튼이 있다. Directory엔 patternflow가 있다.", + }, + }, + { + name: "Enabled", + file: "rtpmidi-2", + title: "Enabled", + en: "Tick it on the new session; leave the port at 5004.", + ko: "새 세션에서 체크하고, 포트는 5004 그대로 두세요.", + alt: { + en: "The rtpMIDI window: a session named after the computer is listed under My Sessions, the Enabled box is still empty, and the port reads 5004.", + ko: "rtpMIDI 창. My Sessions에 컴퓨터 이름으로 된 세션이 있고, Enabled 칸은 아직 비어 있으며, 포트는 5004다.", + }, + }, + { + name: "Connect", + file: "rtpmidi-3", + title: "Connect", + en: "Select patternflow in Directory and press Connect.", + ko: "Directory에서 patternflow를 고르고 Connect.", + alt: { + en: "The rtpMIDI window: the session is enabled, patternflow is selected in Directory, and the Connect button under it is marked.", + ko: "rtpMIDI 창. 세션이 Enabled 상태이고, Directory에서 patternflow가 선택돼 있으며, 그 아래 Connect 버튼에 표시가 있다.", + }, + }, + { + name: "Connected", + file: "rtpmidi-4", + title: "Participants", + en: "Patternflow is listed. The window can be closed now.", + ko: "Patternflow가 올라왔어요. 이제 창은 닫아도 돼요.", + alt: { + en: "The rtpMIDI window: Patternflow is listed under Participants with a latency of 0 ms.", + ko: "rtpMIDI 창. Participants에 Patternflow가 지연 시간 0 ms로 올라와 있다.", + }, + ms: 4200, + }, + ], + }, + // midi · 2 — Live's Link, Tempo & MIDI: Remote ticked on the session's input + liveRemote: { + scene: "midi", + look: "crop", + ground: LIVE_GREY, + label: { en: "Ableton Live's MIDI ports", ko: "Ableton Live의 MIDI 포트" }, + shots: [ + { + name: "Remote", + file: "live-remote", + title: "Input Ports · Remote", + en: "Ticked on the row named after your computer, in Live's Link, Tempo & MIDI settings.", + ko: "Live의 Link, Tempo & MIDI 설정에서, 내 컴퓨터 이름으로 된 줄에 체크돼 있어요.", + alt: { + en: "The MIDI ports in Live's Link, Tempo & MIDI settings: under Input Ports, the row named after the computer has its Remote box ticked; Track, Sync and MPE are empty.", + ko: "Live의 Link, Tempo & MIDI 설정에 있는 MIDI 포트. Input Ports에서 컴퓨터 이름으로 된 줄의 Remote 칸이 체크돼 있고 Track, Sync, MPE는 비어 있다.", + }, + }, + ], + }, + // midi · 4 — the console's MIDI page: Knobs → MIDI (the value mode, Sensitivity) + midiPage: { + scene: "midi", + look: "crop", + ground: CREAM, + label: { en: "The console's MIDI page: Knobs → MIDI", ko: "콘솔 MIDI 페이지의 Knobs → MIDI" }, + shots: [ + { + name: "Knobs → MIDI", + file: "midi-knobs", + title: "Knobs → MIDI", + en: "The value mode on top, Sensitivity under it.", + ko: "위는 값을 보내는 방식, 아래는 Sensitivity예요.", + alt: { + en: "The Knobs → MIDI section of the console's MIDI page: Knob value is selected beside Relative steps, and under them Sensitivity has a slider each for K1 to K4, all at 1 : 1, with move all four together ticked.", + ko: "콘솔 MIDI 페이지의 Knobs → MIDI 섹션. Relative steps 옆의 Knob value가 선택돼 있고, 아래 Sensitivity엔 K1–K4 슬라이더가 모두 1 : 1이며 move all four together가 체크돼 있다.", + }, + }, + ], + }, + // midi · 5 — the MIDI page's Session card: Use this computer + midiSession: { + scene: "midi", + look: "crop", + ground: CREAM, + label: { en: "The console's MIDI page: Session", ko: "콘솔 MIDI 페이지의 Session" }, + shots: [ + { + name: "Session", + file: "midi-session", + title: "Use this computer", + en: "Pressed: this computer's address is in Computer to invite, and the page says saved — inviting.", + ko: "누르면 이 컴퓨터의 주소가 Computer to invite에 들어가고 saved — inviting이 떠요.", + alt: { + en: "The Session section of the console's MIDI page: Connected to a computer, an address in the Computer to invite field, the buttons Save, Use this computer and Clear, and under them the words saved — inviting.", + ko: "콘솔 MIDI 페이지의 Session 섹션. 컴퓨터에 연결돼 있고, Computer to invite 칸에 주소가 있으며, Save, Use this computer, Clear 버튼 아래에 saved — inviting이 떠 있다.", + }, + }, + ], + }, +}; + +/** How many frames each set has, in the order shown. */ +export const AUDIO_SHOT_FRAMES = Object.fromEntries( + (Object.keys(SETS) as AudioShotSet[]).map((set) => [set, SETS[set].shots.map((s) => s.name)]), +) as Record; + +/** + * mic · 3's table: each lead from the microphone's pad to its place on the + * ESP32 module. Five leads, four places — GND and SEL share GND. + */ +export const MIC_LEADS: [string, string][] = [ + ["3V", "3V3"], + ["GND", "GND"], + ["SEL", "GND"], + ["CLK", "TX"], + ["DAT", "RX"], +]; + +const UI = { + en: { step: "Screen", leads: "Each lead, from the microphone's pad to the pin on the ESP32", from: "Microphone", to: "ESP32" }, + ko: { step: "화면", leads: "마이크 패드에서 ESP32 핀까지, 선 하나씩", from: "마이크", to: "ESP32" }, +} as const; + +const BASE = "/guide/audio/"; +const STEP_MS = 3600; +const LAST_HOLD_MS = 1200; + +// Per look: the space around a shot inside the frame (the capture's CSS px), +// and how much of the screen's height the frame may take, wide and narrow — +// on a phone the card sits under the stage and has to leave it room. `short` +// is what a wide screen under 880 px tall leaves of `vh`. +// +// A window is a whole program's worth of small type, so it gets the card's +// width: 31vh is what lets rtpMIDI's 612 px reach it on a 900 px screen, and +// the thin rim (pad) is all the desktop it stands on. On a short screen its +// card is the page's fullest, and the frame stays as tall as it was at 27vh. +const LOOKS: Record = { + crop: { pad: 0, vh: 30, vhNarrow: 27, short: 0.86 }, + window: { pad: 3, vh: 31, vhNarrow: 27, short: 0.75 }, + photo: { pad: 0, vh: 32, vhNarrow: 30, short: 0.86 }, +}; + +function subscribeReducedMotion(cb: () => void) { + const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); + mq.addEventListener("change", cb); + return () => mq.removeEventListener("change", cb); +} +const getReducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches; +// On the server nothing moves; the client decides after hydration. +const getReducedMotionServer = () => true; + +/** + * The lead table, lying under the photo: the microphone's pads across the + * top, in the order they stand on the breakout, and under each the pin it + * goes to. A place two leads share is one cell under both. + */ +function Leads({ lang }: { lang: GuideLang }) { + const ui = UI[lang]; + return ( + + + + + + {MIC_LEADS.map(([pad]) => ( + + ))} + + + + + + {MIC_LEADS.map(([pad, pin], i) => { + if (i > 0 && MIC_LEADS[i - 1][1] === pin) return null; + let span = 1; + while (MIC_LEADS[i + span]?.[1] === pin) span += 1; + return ( + + ); + })} + + +
{ui.leads}
{ui.from} + {pad} +
{ui.to} 1 ? span : undefined} data-shared={span > 1 ? "" : undefined}> + {pin} +
+ ); +} + +export default function AudioShots({ set, lang, step }: { set: AudioShotSet; lang: GuideLang; step: number }) { + const def = SETS[set]; + const { shots, scene } = def; + const n = shots.length; + const ui = UI[lang]; + const look = LOOKS[def.look]; + + const rootRef = useRef(null); + const [index, setIndex] = useState(0); + const [visible, setVisible] = useState(false); + const [hovered, setHovered] = useState(false); + const [focused, setFocused] = useState(false); + const reduced = useSyncExternalStore(subscribeReducedMotion, getReducedMotion, getReducedMotionServer); + + // This step on stage. Leaving it puts the set back to its first frame, so + // a reader who comes back sees it from the start. + const onStage = useGuideStore((s) => s.page === "audio" && s.scene === scene && s.step === step); + const [wasOnStage, setWasOnStage] = useState(onStage); + if (wasOnStage !== onStage) { + setWasOnStage(onStage); + if (!onStage) setIndex(0); + } + + const active = index % n; + const running = n > 1 && onStage && visible && !hovered && !focused && !reduced; + + useEffect(() => { + const el = rootRef.current; + if (!el || n < 2) return; + const io = new IntersectionObserver(([entry]) => setVisible(entry.isIntersecting && entry.intersectionRatio >= 0.35), { + threshold: [0, 0.35], + }); + io.observe(el); + return () => io.disconnect(); + }, [n]); + + // One timeout per frame, so a click restarts the clock on the new one. + useEffect(() => { + if (!running) return; + const ms = (shots[active].ms ?? STEP_MS) + (active === n - 1 ? LAST_HOLD_MS : 0); + const t = window.setTimeout(() => setIndex((i) => (i + 1) % n), ms); + return () => window.clearTimeout(t); + }, [running, active, n, shots]); + + const onBlur = useCallback((e: React.FocusEvent) => { + if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setFocused(false); + }, []); + + // The frame is sized for the largest frame of the set, and every frame is + // drawn at the same scale: a short crop stays short, as it is on the page. + const unitW = Math.max(...shots.map((s) => GEO[s.file].w)) + look.pad * 2; + const unitH = Math.max(...shots.map((s) => GEO[s.file].h)) + look.pad * 2; + // A short wide screen: the look's share of its height, or what the rest of + // a held card leaves of it (`rest`), whichever is more. + const shortShare = `${((unitW / unitH) * (def.vh ?? look.vh) * look.short).toFixed(2)}vh`; + const capShort = def.rest ? `max(${shortShare}, calc((100vh - ${def.rest}px) * ${(unitW / unitH).toFixed(4)}))` : shortShare; + + return ( +

setHovered(true)} + onMouseLeave={() => setHovered(false)} + onFocus={() => setFocused(true)} + onBlur={onBlur} + > +
+ {shots.map((s, i) => { + const geo: Geo = GEO[s.file]; + const on = i === active; + return ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {on + {/* The control this frame is about. Remounted per frame: it + arrives just after the frame does. */} + {on && + geo.rings.map(([x, y, w, h], r) => ( +
+ ); + })} +
+ + {def.under === "leads" && } + +
+ {/* More than one frame: the dots, and beside them the words of the + frame that is up. */} + {n > 1 && ( +
+
+ {shots.map((s, i) => ( + + ))} +
+
+ {shots.map((s, i) => ( + + {s.title} + + ))} +
+
+ )} + {/* Every caption in one grid cell: the block is as tall as the longest. */} +
+ {shots.map((s, i) => ( +

+ {n === 1 && s.title && {s.title}} + {s[lang]} +

+ ))} +
+
+
+ ); +} diff --git a/web/src/components/guide/EditorWindow.module.css b/web/src/components/guide/EditorWindow.module.css new file mode 100644 index 000000000..b1f2ea105 --- /dev/null +++ b/web/src/components/guide/EditorWindow.module.css @@ -0,0 +1,389 @@ +/* The Audio guide's editor window (EditorWindow.tsx): a browser the guide's + colours, around the extension's own editor page. The shell follows + ConsoleWindow.module.css (desktop half) and is a copy on purpose — that + sheet is Play's. Tokens are local because the lifted window is portalled to + , outside the guide's .page where --g-* live. */ + +.root, +.layer { + --ew-ink: #efe9dd; + --ew-ink-2: rgba(239, 233, 221, 0.72); + --ew-ink-3: rgba(239, 233, 221, 0.46); + --ew-hair-2: rgba(239, 233, 221, 0.18); + --ew-led: #ff6a3d; + /* The editor's own ground (editor.css --cream), so a frame that has not + painted yet is already the right colour. */ + --ew-page: #f4efe6; + --ew-chrome: #1b1a18; + --ew-chrome-2: #131210; + --ew-field: #0f0e0d; + --ew-shadow: 0 22px 60px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.35); +} + +.root { + margin: 18px 0 4px; +} + +.caption { + margin-top: 10px; + font-size: 12.5px; + line-height: 1.45; + color: var(--ew-ink-3); +} + +/* ── the miniature: one button, the whole window ── */ + +.opener { + appearance: none; + display: block; + width: 100%; + margin: 0; + padding: 0; + border: 0; + background: none; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; + border-radius: 12px; + transition: + opacity 0.22s ease, + transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1); +} +.opener:focus-visible { + outline: 2px solid var(--ew-led); + outline-offset: 4px; +} +/* It has left the card while it floats. */ +.opener[data-lifted="1"] { + opacity: 0; +} +@media (hover: hover) and (prefers-reduced-motion: no-preference) { + .opener:hover { + transform: translateY(-2px); + } +} + +.frame { + display: block; + border: 0; + background: var(--ew-page); + color-scheme: light; +} + +.mini { + position: relative; + display: block; + overflow: hidden; + background: var(--ew-page); +} +.mini .frame { + position: absolute; + left: 0; + top: 0; + transform-origin: 0 0; + pointer-events: none; +} +/* A phone, or a short screen: the card and the board both have to fit, so the + miniature shows the editor down to its plot. Lifted, it shows all of it. */ +@media (max-width: 899px), (max-height: 879px) { + .opener .mini { + aspect-ratio: 760 / 400 !important; + } +} + +/* ── tab strip, toolbar, page ── */ + +.window { + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid var(--ew-hair-2); + border-radius: 11px; + background: var(--ew-chrome); + box-shadow: var(--ew-shadow); + transition: border-color 0.25s; +} +.opener:hover .window, +.opener:focus-visible .window { + border-color: rgba(255, 106, 61, 0.45); +} + +.bar { + display: block; + flex: none; + font-family: var(--pf-sans, system-ui, sans-serif); +} +.tabs { + display: flex; + align-items: flex-end; + height: 28px; + padding: 0 8px 0 10px; + background: var(--ew-chrome-2); +} +.lights { + display: flex; + gap: 5px; + align-self: center; + margin-right: 12px; +} +.lights i { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgba(239, 233, 221, 0.16); +} +.tab { + display: flex; + align-items: center; + gap: 7px; + min-width: 0; + max-width: 220px; + height: 22px; + padding: 0 14px 0 10px; + border-radius: 7px 7px 0 0; + background: var(--ew-chrome); + font-size: 11px; + color: var(--ew-ink-2); +} +/* The extension's mark: the square LED of its own source bar. */ +.favicon { + flex: none; + width: 7px; + height: 7px; + border-radius: 1px; + background: #e8552e; +} +.tabTitle { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.barEnd { + display: flex; + align-items: center; + align-self: center; + margin-left: auto; + padding-left: 8px; +} +.toolbar { + display: flex; + align-items: center; + gap: 10px; + height: 30px; + padding: 0 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.5); + background: var(--ew-chrome); +} +.navIcons { + display: flex; + gap: 6px; + color: var(--ew-ink-3); +} +.navIcons svg { + width: 13px; + height: 13px; +} +.navIcons svg:nth-child(2) { + opacity: 0.45; +} +.address { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + height: 21px; + padding: 0 10px; + border-radius: 999px; + background: var(--ew-field); + font-family: var(--pf-mono, ui-monospace, monospace); + font-size: 10.5px; + letter-spacing: 0.01em; + color: var(--ew-ink); +} +.url { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.scheme { + color: var(--ew-ink-3); +} + +.openChip { + display: inline-flex; + align-items: center; + gap: 4px; + height: 18px; + padding: 0 7px 0 8px; + border: 1px solid var(--ew-hair-2); + border-radius: 999px; + font-family: var(--pf-mono, ui-monospace, monospace); + font-size: 9.5px; + letter-spacing: 0.08em; + text-transform: uppercase; + white-space: nowrap; + color: var(--ew-ink-2); + transition: + color 0.2s, + border-color 0.2s, + background 0.2s; +} +.openChip svg { + width: 10px; + height: 10px; +} +.opener:hover .openChip, +.opener:focus-visible .openChip { + color: var(--ew-led); + border-color: var(--ew-led); + background: rgba(255, 106, 61, 0.1); +} + +/* ── lifted ── */ + +/* The layer itself takes no pointer: the window does, and the guard, which is + the page that must not be touched while the window is up. On a wide screen + the guard is cut (its clip-path, EditorWindow.tsx) so the stage left of the + window is still under the pointer: the board there is to be played. */ +.layer { + position: fixed; + inset: 0; + z-index: 1000; + pointer-events: none; +} +.guard { + position: absolute; + inset: 0; + pointer-events: auto; +} +.backdrop { + position: absolute; + inset: 0; + /* The board and the readouts at its knobs, on the left of a wide stage, + stay in the light: the window is there to be used beside them. */ + background: linear-gradient(90deg, rgba(8, 7, 6, 0.05) 0%, rgba(8, 7, 6, 0.14) 48%, rgba(8, 7, 6, 0.8) 62%, rgba(8, 7, 6, 0.86) 100%); + opacity: 0; + transition: opacity 0.4s ease; +} +.layer[data-narrow="1"] .backdrop { + background: rgba(8, 7, 6, 0.84); +} +.layer[data-shown="1"] .backdrop { + opacity: 1; +} + +.lift { + position: absolute; + pointer-events: auto; + transform-origin: 0 0; + transition: + transform 0.44s cubic-bezier(0.22, 0.8, 0.2, 1), + opacity 0.26s ease; +} +.layer[data-shown="1"] .lift { + transition: + transform 0.44s cubic-bezier(0.22, 0.8, 0.2, 1), + opacity 0.18s ease; +} + +.big.window { + width: 100%; + height: 100%; + border-radius: 12px; + border-color: rgba(239, 233, 221, 0.22); + box-shadow: + 0 40px 120px rgba(0, 0, 0, 0.62), + 0 4px 16px rgba(0, 0, 0, 0.4); +} +.big .tabs { + height: 36px; + padding: 0 10px 0 14px; +} +.big .lights { + gap: 7px; + margin-right: 16px; +} +.big .lights i { + width: 11px; + height: 11px; +} +.big .tab { + gap: 8px; + max-width: 280px; + height: 28px; + padding: 0 18px 0 12px; + border-radius: 9px 9px 0 0; + font-size: 12.5px; +} +.big .favicon { + width: 8px; + height: 8px; +} +.big .toolbar { + height: 40px; + gap: 14px; + padding: 0 12px; +} +.big .navIcons { + gap: 10px; +} +.big .navIcons svg { + width: 16px; + height: 16px; +} +.big .address { + height: 28px; + padding: 0 14px; + font-size: 13px; +} +.viewport { + position: relative; + flex: 1; + min-height: 0; + background: var(--ew-page); +} +/* While a press that began on the stage is down (a knob being turned), the + frame takes no pointer: the stage hears the moves and the release. */ +.layer[data-away="1"] .frame { + pointer-events: none; +} + +.close { + appearance: none; + display: grid; + place-items: center; + width: 28px; + height: 28px; + margin: 0; + padding: 0; + border: 1px solid var(--ew-hair-2); + border-radius: 50%; + background: rgba(239, 233, 221, 0.04); + color: var(--ew-ink-2); + cursor: pointer; + transition: + color 0.2s, + border-color 0.2s, + background 0.2s; +} +.close svg { + width: 14px; + height: 14px; +} +.close:hover { + color: var(--ew-ink); + border-color: var(--ew-led); + background: rgba(255, 106, 61, 0.14); +} +.close:focus-visible { + outline: 2px solid var(--ew-led); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + .lift, + .layer[data-shown="1"] .lift { + transition: opacity 0.18s ease; + } +} diff --git a/web/src/components/guide/EditorWindow.tsx b/web/src/components/guide/EditorWindow.tsx new file mode 100644 index 000000000..9e3fe498c --- /dev/null +++ b/web/src/components/guide/EditorWindow.tsx @@ -0,0 +1,448 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState, type CSSProperties, type ReactNode, type RefObject } from "react"; +import { createPortal } from "react-dom"; +import { getSim, useGuideStore, type GuideLang } from "./store"; +import styles from "./EditorWindow.module.css"; + +// Audio · A browser tab, "Each knob is a box on the spectrum." — the +// extension's real mapping editor (tools/patternflow-audio-extension), on its +// own demo source, in a browser window in the card; what it maps drives the +// board on the stage. +// +// The window holds public/guide/audio-editor/ (written by +// firmware/toolchain/editor_demo.py): the extension's editor files as they +// are, plus bridge.js, whose header comment documents this file's other end. +// Each tick the frame posts {type:"pf-audio", outputs:[v|null ×4]} — what the +// extension would send the board, 0…1 per knob, null for a muted band — and +// this hands them on: getSim().setLane("editor", knob, v). The board takes +// them only on the step that declares `lanes: "editor"` (scenes/audio.ts), so +// nothing here knows which step is on stage; a lane no longer written lets go +// by itself, as a real board's does. +// +// In the card the window is a live miniature and takes no input (the card +// scrolls with the story). A click lifts it: a full-size window over the +// page, beside the board, with a frame of its own, until Esc or the close +// button. Beside it the board is still the reader's: the card says to take a +// knob, so the stage left of the window takes the pointer as it does with no +// window up, and the rest of the page (the top bar, what shows round the +// window) does not. On a phone the window covers the screen, and a click +// beside it closes it too. The two frames are one editor: the mapping is kept +// per tab (bridge.js), so an edit made in the window is the miniature's too, +// and goes on driving the board after the window is closed — on a phone, +// where the window covers the board, that is when the reader sees it. +// +// The window's shell (bar, lift, placement, focus) follows ConsoleWindow's, +// and is a copy on purpose: that file is Play's console, wired to its own +// demo and load queue, and stays as it is. +// +// Used by a step whose copy has extra: "editorLive" (copy/audio.ts, browser +// step 4). Its words are this file's own, as LabWindow's are. The editor's +// own text is the extension's, and English on both pages. + +type Words = { label: string; open: string; close: string; caption: string }; + +export const EDITOR_WINDOW_WORDS: Record = { + en: { + label: "The mapping editor", + open: "Open", + close: "Close", + caption: "The editor on its own demo source. Opened from the extension, it shows what your tab is playing.", + }, + ko: { + label: "매핑 에디터", + open: "열기", + close: "닫기", + caption: "에디터가 자체 데모 신호로 돌고 있어요. 확장 프로그램에서 열면 내 탭에서 나는 소리가 보여요. 에디터 안의 글자는 영어 그대로예요.", + }, +}; + +const SRC = "/guide/audio-editor/index.html"; +// The tab and the address the extension's page has. The part between is the +// extension's id, which is each installation's own. +const TITLE = "Patternflow Audio — Mapping"; +const ADDRESS = ["chrome-extension://…", "/editor.html"]; +// The miniature's viewport in CSS pixels, scaled into the card: the editor +// down to its four knob tabs — the source bar, the toolbar and the plot. +const VIEW = { w: 760, h: 480 }; +const LIFT_MS = 440; + +type Msg = { type?: string; action?: string; outputs?: unknown }; + +// ── browser chrome ────────────────────────────────────────────────────────── + +function Bar({ title, end }: { title: string; end: ReactNode }) { + return ( + + + + +