diff --git a/.github/workflows/firmware-build.yml b/.github/workflows/firmware-build.yml index 2cb8c939..f0441056 100644 --- a/.github/workflows/firmware-build.yml +++ b/.github/workflows/firmware-build.yml @@ -1,6 +1,6 @@ name: Firmware builds -# The three editions, compiled, on every firmware change. +# Every composition, compiled, on every firmware change - one job each. # # For two months this repository shipped a firmware whose CI compiled nothing: # the boundary and ABI checks in firmware-checks.yml finish in seconds and @@ -8,9 +8,13 @@ name: Firmware builds # was found by whoever next ran `firmware/bundles/build.sh all` at their desk. # The 3.9.1 cycle moved the network onto its own core across a dozen files # and reached dev without a compiler ever seeing it in CI. That is what this -# workflow closes: the same `build.sh all` — default, audio, performance, clock, then -# the marker scan that proves each image carries exactly its own features — -# on a runner, with the PlatformIO packages cached so a warm run is minutes. +# workflow closes: `build.sh one ` - default, audio, performance, +# clock, midi - each in its own job, so the wall clock is one build rather +# than five in a row, and each image scanned for the marker strings that prove +# it carries exactly its features. Not every composition is on the shelf +# (clock and midi are compositions the tree keeps buildable); every one of +# them has to compile, because the point of the feature seam is that a core +# change breaks nothing it cannot see. # # No secrets file exists on the runner, so every image here is built the way # a published one is (the placeholder SSID, not somebody's Wi-Fi). Nothing is @@ -34,8 +38,13 @@ permissions: {} jobs: build: + name: ${{ matrix.composition }} runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + composition: [default, audio, performance, clock, midi] steps: - uses: actions/checkout@v4 @@ -44,8 +53,9 @@ jobs: python-version: "3.11" # The xtensa toolchain and the Arduino core are ~700 MB; the build tree - # under ~/pf-build keeps the framework's objects between editions and - # between runs. Keyed on platformio.ini, which names both. + # under ~/pf-build keeps the framework's objects between compositions + # and between runs. Keyed on platformio.ini, which names both. Five jobs + # share the key: the first to finish saves it, the rest find it saved. - name: Cache PlatformIO packages and the build tree uses: actions/cache@v4 with: @@ -59,16 +69,13 @@ jobs: - name: Install PlatformIO run: pip install --quiet platformio==6.1.19 - - name: Build every edition and scan the images for their features - run: bash firmware/bundles/build.sh all - - - name: Image sizes - run: ls -l "$HOME/pf-build-editions"/*.bin + - name: Build ${{ matrix.composition }} and scan the image for its features + run: bash firmware/bundles/build.sh one ${{ matrix.composition }} # Flash is not the scarce resource here; internal DRAM is, and a loadable - # pattern's code budget is whatever is left of it. The step above has always - # measured the one that does not matter. This reads each edition's elf and - # fails on a static-RAM move, per edition, because the whole point of the - # edition split is that the blast radius differs. - - name: Internal RAM each edition has already spent - run: python firmware/toolchain/check_footprint.py + # pattern's code budget is whatever is left of it. The size printed above + # measures the one that does not matter. This reads the composition's elf + # and fails on a static-RAM move, per composition, because the whole point + # of the split is that the blast radius differs. + - name: Internal RAM this composition has already spent + run: python firmware/toolchain/check_footprint.py --only ${{ matrix.composition }} diff --git a/AGENTS.md b/AGENTS.md index 4a8d17e4..7749a233 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,9 +9,9 @@ It is standalone (not Eurorack), powered from a 5 V power bank through a screw t ## Repository map - `docs/` — contracts and long-form docs: `EDITIONS.md` (the firmware seam — read it before touching firmware), `rest-api.md` / `osc-spec.md` / `midi-spec.md` / `audio-ws-spec.md` (wire contracts integrations are built against), `pfst-v2-spec.md` (show files), `panel-compatibility.md`, `RELEASING.md`, `SERVICES.md` (production server ops), the assembly map (`docs/assembly/`), the manifesto, investigations, and media (build-guide images in `docs/build-guide/`). Root-level guides: `BUILD_GUIDE.md` (the **v3.0 board**), `BUILD_GUIDE_v2.md` (legacy v2.x board), `PATTERN_GUIDE.md` (community → deck → Pattern Lab → device), `FEATURE_GUIDE.md` (writing a firmware feature or edition), `AUDIO_GUIDE.md` (sound into the panel). The live roadmap is patternflow.work/journal and the `/roadmap` page. -- `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) — 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 **editions**: `audio`, `performance`, `clock`; 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). +- `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) — 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), 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), 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), 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. @@ -27,14 +27,14 @@ It is standalone (not Eurorack), powered from a 5 V power bank through a screw t ## Common commands - Web dev server: `npm run dev` (inside the `web/` directory) - Web production build: `npm run build` (inside the `web/` directory); the smoke suites are `npm run check:*` and CI runs all of them. -- Firmware compilation: `./firmware/bundles/build.sh` (PlatformIO, bundled toolchain — not the Arduino IDE; the IDE path cannot build the editions' library set). That builds the default: the device with no features. Named editions are `./firmware/bundles/build.sh audio`, `performance` and `clock`. `./firmware/bundles/build.sh all` builds every composition and scans each binary for per-feature marker strings, proving each image carries exactly its features — run it before pushing anything that touches firmware. +- Firmware compilation: `./firmware/bundles/build.sh` (PlatformIO, bundled toolchain — not the Arduino IDE; the IDE path cannot build the editions' library set). That builds the default: the device with no features. Named editions are `./firmware/bundles/build.sh audio`, `performance`, `clock` and `midi` (the last builds in its own PlatformIO env, named in `bundles/midi/env`, because its USB port must be the OTG controller — a build flag, not a setting). `./firmware/bundles/build.sh all` builds every composition and scans each binary for per-feature marker strings, proving each image carries exactly its features — run it before pushing anything that touches firmware. - **Changing anything in the firmware core means building every composition** — `build.sh all` is that rule as one command. A hook change that compiles against the default is not tested; the default has no features to break. The boundary rule itself is enforced by `firmware/toolchain/check_boundaries.py` in CI: core referencing a feature namespace, including from `features/`, branching on a feature flag, or naming a feature in a core console page fails the build. `check_presets.py` (also CI) keeps `web/src/lib/presets/` and `firmware/patternflow/presets/` in step. - Console pages: edit `firmware/patternflow/console/*.html`, preview with `python firmware/toolchain/console_serve.py`, then `python firmware/toolchain/console_pages.py build` (CI checks the headers stayed in sync). - KiCad exports: Export Gerbers from `hardware/pcb/kicad/patternflow.kicad_pcb`. Export STLs from `hardware/case/source/patternflow_case.blend`. ## Versioning - Project: v3.10.2 (current, released 2026-09-11), using unified semantic versioning across firmware, hardware, web, and docs — the firmware reports it as `PF_IMPROV_FW_VERSION` in `net_config.h`, and `CHANGELOG.md` gets a section per release. Current hardware is the v3.9 board — the v3.0 board with the USB-C footprint removed, same enclosure, pin map and guide; every v3.x release has been firmware/web on it. -- Editions carry their own version line, independent of the project version (`PF_VARIANT_VERSION` in `firmware/bundles//overrides.h`): Audio v0.6.2, Performance v0.2.7, Clock v0.1.5 at the time of writing. The shelf (`web/src/app/editions/editions-data.ts`) and the flasher manifest (`web/public/flash/manifest.json`) name the images that are live; only those images are kept in `web/public/flash/bin/` — older ones live on their release tags. +- Editions carry their own version line, independent of the project version (`PF_VARIANT_VERSION` in `firmware/bundles//overrides.h`): Audio v0.6.2, Performance v0.2.7 at the time of writing. The shelf (`web/src/app/editions/editions-data.ts`) and the flasher manifest (`web/public/flash/manifest.json`) name the images that are live; only those images are kept in `web/public/flash/bin/` — older ones live on their release tags. `clock` and `midi` are bundles the tree keeps building (CI compiles every composition) but does not publish: the clock is a feature, and the MIDI bundle is the composition that proves the USB-OTG build. Neither is in the `EDITIONS` tuples of `release.py`/`check_versions.py`; an edition joins those, the cards and the line above only when it goes on the shelf, and its maintainer cuts it with `release.py edition vA.B.C` (docs/RELEASING.md). - Firmware source lives in `firmware/patternflow/`; use release tags for versioning instead of encoding the release in the folder name. Tags are `vX.Y.Z`. - Conventions: filenames lowercase with underscores; commit messages start with the area (`firmware:`, `web:`, `docs:`, `hardware:`) then a short present-tense summary. diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md index 8664611a..b5810190 100644 --- a/BUILD_GUIDE.md +++ b/BUILD_GUIDE.md @@ -299,7 +299,7 @@ OSC, network MIDI and audio-react ship in the **Audio** edition: open [patternfl - **USB-C input removed in v3.9.** A USB-C-powered v3.0 board ran normally for 20–30+ minutes, then smoked at a connector pin and destroyed the receptacle and surrounding power path ([#221](https://github.com/engmung/Patternflow/issues/221)). Whether that was a soldering defect on the tight-pitch THT pins or a structural limit of the part under the matrix's peak current was never settled — so the footprint came off the board instead of shipping an input that must not be populated. **Every build powers through the `J4` screw terminal** (Section 2); on a v3.0 board, leave `USB1`/`R1`/`R2` bare. - **C11 (1000µF bulk cap) retained** — Patternflow is power-bank-powered; the cap stabilizes the boot transient. Designing a desktop-USB derivative? Drop it to ≤50µF. - **GPIO0 left floating by design** — most modules don't need the pullup; if yours does, it's a one-resistor fix (Section 5 note, [#16](https://github.com/engmung/Patternflow/issues/16)). -- **Encoder direction is handled in firmware** — the default suits the Bourns PEC11R; if your encoders read backwards, set `INVERT_ENCODER` to `1` in `config.h` instead of touching hardware. +- **Encoder direction is handled in firmware** — the default suits the Bourns PEC11R; if your encoders read backwards, or one click moves two steps, fix it per knob on the device's `/knobs` page — no rebuild; `INVERT_ENCODER` in `config.h` is only the compile-time default. - **No LED-matrix bump trimming.** The enclosure recesses the panel's two alignment bumps, so the old nipper step ([#19](https://github.com/engmung/Patternflow/issues/19)) is gone. The design also gives you a snap-fit back panel and two wall-mount holes. --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 639c27de..6974b51e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ All notable changes to Patternflow will be documented in this file, newest first - **v3.9 board** (2026-09-04). The USB-C power receptacle `USB1` and its CC pull-downs `R1`/`R2` — which v3.0 shipped and every document told you to leave unpopulated after the delayed burnout in [#221](https://github.com/engmung/Patternflow/issues/221) — come off the board. `J4`, the screw terminal, is the only power input there is. Edge cuts and mounting holes are byte-identical to v3.0, so the v3 cases fit both. The 330 mm case gains a pass-through to the DevKit's USB port (data, for wired MIDI/OSC and flashing — not power) and a cable exit slot. Gerbers, BOM, renders and schematic regenerated; `bom_v3.9.csv` is the BOM source of truth. +### Firmware + +- **Knobs page.** `/knobs` on the device console, and `GET`/`POST /api/knobs` behind it: which way each encoder counts and how many edges make one click, per knob, with a live readout to check against. These are properties of the part soldered in - some encoders count the other way round, some give two edges per detent - and until now the answer was `INVERT_ENCODER` in `config.h` and a rebuild. Stored on the device; a saved change does not move the knob. The Live guide also gains the one setting that made a wired or wireless MIDI knob pin itself and tick: *Remote* on the port's Output row sends every mapped value straight back. +- **The Clock edition leaves the shelf.** The clock is a feature; `bundles/clock` stays in the tree as a composition CI keeps compiling, and its last image, `clock-v0.1.5`, stays on the v3.10.2 tag. The shelf is what its maintainer stands behind — Core and Audio, and Performance from its own — and it is short on purpose. +- **An edition ships on its own.** `release.py edition vA.B.C` bumps, shelves, points the card and commits: one command for an edition's maintainer to cut from a pull request, with no core release in between. Firmware CI builds the five compositions in parallel, one job each, and pins each one's static RAM. +- **MIDI edition** (`firmware/bundles/midi`, v0.1.0): the DevKit's USB port as a class-compliant MIDI device, beside RTP-MIDI — plug the panel into a computer with the cable it is flashed over and it is a MIDI port named `Patternflow`, no driver and no session. Same map as every transport (`docs/midi-spec.md`); `midiUsb` in `/api/status`. The port has to be the S3's USB-OTG controller for a MIDI device to exist, which is a build flag the other images cannot carry (it is what makes the port their serial console), so the edition builds in its own PlatformIO env — `env`, the third file a bundle may now have. Data only: the panel is powered from `J4` as always. In the tree and built by CI; not on the shelf yet. +- **Storage errors say why.** An upload whose destination will not open reports `errno`, so a missing `/patterns` (ENOENT) reads differently from a volume that will not write (EIO), and storage is diagnosed again after a successful mount, so a later failure carries a reason instead of an empty `fsError`. From RayWolters' investigation on [#424](https://github.com/engmung/Patternflow/issues/424). + +### Web + +- **The features page.** `/features` is the catalogue the shelf never was: every feature as one row — name, one line, and a tag saying where it lives (every firmware, Audio, Performance, a recipe in the tree, or an attempt that did not work) — so the whole list fits on a screen, and a row opens to what it needs, which firmware carries it, the links and a reel of it running. No headings between rows: which firmware carries a feature is a fact on its row, never a category over it, or the shelf would be back one level up. Reels are Instagram embeds by permalink, loaded only when a row is opened. The shelf's lede links here. + ### Docs - **The repository explains itself to a contributor.** One table in `CONTRIBUTING.md` of what you have → where it goes → what CI runs on it; `docs/REPOSITORY.md` as the folder-by-folder map for people; `SUPPORT.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `CITATION.cff` and a root `LICENSE` pointer; a README in every contributor-facing subtree (`tools/`, `integrations/`, `firmware/modules/`, `firmware/toolchain/`, both `presets/`); `docs/mqtt-spec.md` beside the other contracts; `hardware/case/remixes/` as the named home for community enclosure variants, with a one-step CI check of its READMEs. Dead pointers removed: GitHub Discussions (never enabled), pattern pull requests (patterns are published from the Pattern Lab to the Community), the `.agents/` folder, `release.yml`. Stale facts fixed in place: the assembly map and `hardware/README.md` now name the v3.9 board, `firmware/README.md` no longer teaches the console pause that ended in 3.6.3, and the Ableton bridge says "install the Audio edition" instead of "rebuild with OSC". diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b6aa98a..c7d695cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,14 @@ Use `wip(area): …` for work that isn't finished yet. Nothing enforces this; it A pin on the map is one entry in [`web/src/components/sections/InsideGlobe/builds.ts`](web/src/components/sections/InsideGlobe/builds.ts) and a folder of photos in `web/public/builds//`. The type at the top of that file documents every field; the comments next to the fields say how precise a location to give and what a `collaboration` is. The `slug` becomes the pin's URL (`/inside/`), so pick it once. Photos you add are CC BY-SA 4.0 unless the entry says otherwise. If you would rather not touch the file, the [Share your build](../../issues/new?template=share_build.yml) form asks for the same things and the maintainer files it. +### Adding an Inside entry + +Inside includes builds, projects derived from Patternflow, and places where it is used. Add one entry to `web/src/components/sections/InsideGlobe/builds.ts`: a short `title`, `maker`, public `location`, `date`, `description`, and any external `links`. Photos are optional. Keep existing `id` and `slug` values stable so shared links keep working. + +Choose one primary `category`: `builds` for assembled devices and enclosure changes, `projects` for ports and new tools, or `in-use` for installations, performances and other uses. This is separate from `kind`, which preserves the existing build/collaboration relationship and marker style. A maker can have several entries: a new tool and a later exhibition are different records. Link to the maker's own documentation for ongoing updates; a short introduction here is enough. Do not add unannounced work or assume an event happened from a sale or a planned appearance. + +Classify the subject of the entry, not its surroundings. A completed device photographed in a studio or DJ booth is still a `builds` entry. Use `in-use` when the entry documents actual use in an exhibition, performance or installation; a sale or delivery alone is not enough. Leave a category empty until a qualifying case is available. + ## How the maintainer works The `dev` → `main` routine, release cutting and what the workflows attach live in [docs/RELEASING.md](docs/RELEASING.md). Governance is one person: the maintainer decides what merges, in the open, in the pull request. Two collaborators have write access to the areas they built. diff --git a/README.md b/README.md index 0681b7a1..47beffef 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ It's already moving in directions I didn't choose. One contributor is building o | **Firmware** | Arduino-compatible C++, modular pattern architecture, runtime switching (no reflash) | | **Flashing** | Everything from the browser: one USB flash the first time, then it's all Wi-Fi. Patterns install as modules in seconds, full firmware builds land wirelessly too. A local build (PlatformIO) only for firmware development or other matrix resolutions | | **Connectivity** | Wi-Fi and USB. Network MIDI (a MIDI port in any DAW — Ableton, Logic, Bitwig), bidirectional OSC (Max/TouchDesigner/Resolume), MQTT and audio-react each ship in an [edition](docs/EDITIONS.md) you install in one click — the way patterns do — and switching keeps your patterns, networks and settings | -| **Editions** | One image ships on the board; others are a click away on [the shelf](https://patternflow.work/editions). Writing a feature or cutting your own firmware starts at **[docs/EDITIONS.md](docs/EDITIONS.md)** | +| **Editions** | One image ships on the board; others are a click away on [the shelf](https://patternflow.work/editions); what each feature does, with a reel of it, is on [the features page](https://patternflow.work/features). Writing a feature or cutting your own firmware starts at **[docs/EDITIONS.md](docs/EDITIONS.md)** | | **Build** | ~1 h hands-on (≈30 min soldering + ≈30 min assembly, first-build friendly) + ~10 h 3D printing · US$100–200 in parts ([BOM](BUILD_GUIDE.md#1-bill-of-materials-bom)) | | **License** | MIT (firmware & web code) · CC-BY-SA 4.0 (hardware, docs, bundled patterns) · community patterns are licensed by their authors ([summary](docs/LICENSE-SUMMARY.md)) | diff --git a/docs/EDITIONS.md b/docs/EDITIONS.md index 687e567f..c50b0588 100644 --- a/docs/EDITIONS.md +++ b/docs/EDITIONS.md @@ -295,6 +295,14 @@ Two files under `firmware/bundles//`: #define PF_WIFI_TX_POWER WIFI_POWER_17dBm ``` +A third file, `env`, is optional: one line naming the PlatformIO env to build +in, for the rare edition whose difference is a build flag rather than a +setting. The MIDI edition is the one so far — its USB port has to be the OTG +controller for a MIDI device to exist, and the Arduino core decides that at +compile time (`ARDUINO_USB_MODE`). Everything else about it is the two files +above; `build.sh` and `shelf.sh` read the env name and look for the outputs +under it. + Then: ```bash @@ -303,7 +311,7 @@ Then: ./firmware/bundles/build.sh all # every composition, proven ``` -`all` builds default, audio and performance, then scans each binary for one +`all` builds default, audio, performance, clock and midi, then scans each binary for one marker string per feature — a literal that lives only in that feature's sources. An edition must contain its own features' markers and none of the others'. That is the composition checked in the shipped bytes: `features.h` @@ -360,6 +368,12 @@ notes, because that is what makes a build reproducible later. The shelf has two tiers, and the difference is who to ask when it breaks, not quality. +A bundle in the tree is not on the shelf by itself. `firmware/bundles/clock` +and `firmware/bundles/midi` are compositions CI keeps compiling, with no card +and no image: the clock is a feature, and the MIDI bundle proves the USB-OTG +build. An edition is listed when its maintainer cuts it (`release.py edition`, +see [`RELEASING.md`](RELEASING.md)) and a card names the image. + **Official** — built from this repository. A core change has to compile against it before that change lands, so it cannot silently rot. Its image is served from patternflow.work, which is also what makes one-click install possible: the diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 9404ff17..18ee4811 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -16,7 +16,7 @@ Since 3.9.3 the checklist below is a script. On `dev`, with a clean tree and the `[Unreleased]` section of `CHANGELOG.md` written: ```bash -python firmware/toolchain/release.py cut v3.9.4 --audio v0.5.4 --performance v0.2.4 --clock v0.1.1 +python firmware/toolchain/release.py cut v3.9.4 --audio v0.5.4 --performance v0.2.4 ``` bumps the versions, dates the changelog section, updates `AGENTS.md`, runs @@ -35,6 +35,28 @@ the edition images under their release names, and waits for the workflow that attaches the core images. `README.md`'s *Moving fast* note is prose and stays yours. `cut --no-build --no-commit` is the dry run. +## An edition on its own + +An edition's maintainer ships between core releases, from a pull request, +with one command on a clean tree: + +```bash +python firmware/toolchain/release.py edition performance v0.2.8 +``` + +It bumps that edition's `PF_VARIANT_VERSION`, runs `shelf.sh` for it (a clean +build — the image is refused if it carries credentials or the wrong version), +points its `/editions` card at the new folder, updates its clause in +`AGENTS.md` and commits `release: performance v0.2.8`. No tag, no changelog +section, no manifest — those are the core's. Push and open the pull request; +CI checks that the six places agree and compiles every composition, and the +shelf serves the image once `main` deploys. The core's maintainer merges and +does nothing else. `--no-build --no-commit` is the dry run. + +Only editions in `release.py`'s `EDITIONS` tuple are on the shelf. A bundle in +the tree that is not there (`clock`, `midi`) is a composition CI keeps +compiling, and nothing more; there is no card to point and nothing to cut. + ## Release checklist What the two commands do, step by step - and the way to do it by hand. @@ -84,4 +106,4 @@ Work happens on `dev`, then lands on `main` through a pull request. `main` is pr - `v1.x` -- first public buildable release, then the multi-pattern firmware and browser flasher. - `v2.x` -- the v2.0 board (GPIO0 cold-boot fix, cleaned silkscreen), custom pattern workflow, the web platform. `v2.1.0` is the last release for v2.x hardware. - `v3.0.0` -- the v3.0 board; `v3.9` (2026-09, unreleased as a tag) removed its USB-C footprint and changed nothing else. Every later `v3.x` is firmware/web on that hardware: `.pfm` modules over Wi-Fi (3.2), shows and the Director (3.6), the feature seam (3.7), editions (3.8). -- Editions (`audio`, `performance`, `clock`) carry their own version lines, independent of the project version — see `docs/EDITIONS.md`. +- Editions on the shelf (`audio`, `performance`) carry their own version lines, independent of the project version — see `docs/EDITIONS.md`. `clock` and `midi` are bundles CI builds but the shelf does not carry. diff --git a/docs/director-midi.md b/docs/director-midi.md index a14c7ea9..22d39c63 100644 --- a/docs/director-midi.md +++ b/docs/director-midi.md @@ -103,6 +103,10 @@ where the `midi` feature lives. 2. Live → *Preferences → Link, Tempo & MIDI*. On that port's **Output** row, switch **Track** on. The **Remote** switch on its *Input* row is the other direction, the Bridge's; both can be on at once. + **Leave Remote on the Output row off.** With it on, Live sends every mapped + parameter's value back to the panel on the same CC, and the panel reads that + position as a relative turn: the knob pins itself to one value and ticks + whenever you move it. The same applies to the USB port on the MIDI edition. **Then, to send a lane:** diff --git a/docs/midi-spec.md b/docs/midi-spec.md index 8d343f1d..cc2ee64f 100644 --- a/docs/midi-spec.md +++ b/docs/midi-spec.md @@ -26,6 +26,7 @@ The mapping below is transport-independent. What ships today: | | | |---|---| | **RTP-MIDI** (AppleMIDI, RFC 6295) over Wi-Fi | The panel is a session **listener** on UDP **5004** (control) / **5005** (data) — and an initiator toward one remembered host, see Settings —, session name `Patternflow`, advertised over Bonjour as `_apple-midi._udp` so it appears by name. macOS/iOS: *Audio MIDI Setup → Network* (or any CoreMIDI app). Windows: the free [rtpMIDI](https://www.tobias-erichsen.de/software/rtpmidi.html) driver — add the panel by name or IP, connect, and it is a MIDI port in Live. Linux: `rtpmidid`. Two participants at most. | +| **USB** (a USB-MIDI class device — the MIDI edition) | The DevKit's USB port, the one the panel is flashed over, plugged into the computer. No driver and no session: the panel enumerates as a MIDI device named `Patternflow` (a virtual serial port comes with it — that is the console's log and Improv, leave it be). MIDI edition only: the port has to be the S3's USB-OTG controller for a MIDI device to exist, and that is a build-time choice ([`EDITIONS.md`](EDITIONS.md)). **Data only** — the panel is powered from `J4` as always, never from that cable. Both transports share the map below and can be up at once. | Channel: **1** by default (`PF_MIDI_CHANNEL`; `0` listens on every channel and sends on 1). MIDI Thru is off — the panel never echoes a host's messages @@ -98,6 +99,15 @@ received events, so re-enabling does not replay an accumulating disabled queue. ## Settings + +The MIDI edition adds `midiUsb` beside `midi` in `/api/status`: `mounted` (a host has +configured the device and is awake), `rx` (packets read that carried a channel +message), `rxUnhandled` (sysex, realtime and pitch bend — read and ignored), `tx`, +and `txDropped` (the 16-packet transmit FIFO was full: nobody reading, or more than +16 messages in one frame). This transport has no worker and no queue: USB flow +control makes the host wait while the panel's FIFO is full, so the frame task reads +it directly, one batch per frame, and nothing is lost — only delayed, by a frame at +most. | | | |---|---| | `GET /api/midi` | channel, `outDiv`, `outMul`, `outMode`, `host`, session state, counters | @@ -117,6 +127,7 @@ received events, so re-enabling does not replay an accumulating disabled queue. - **Program Change** is what a pattern list *is* in MIDI's vocabulary. The limit of 128 covers every panel anyone has filled. +- **1.3** (unreleased) — a second transport: the DevKit's USB port as a USB-MIDI class device, in the MIDI edition; `midiUsb` in `/api/status`. The map, the channel rule and precedence are unchanged, and both transports can be up at once. ## Version history - **1.2** (unreleased) — additive receive diagnostics, transport service diff --git a/docs/rest-api.md b/docs/rest-api.md index 36bec52f..7ceac8de 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -268,6 +268,20 @@ Both directions work over HTTP. This section used to say the opposite, and it wa `/api/knob` and `/remote` existed and were removed as unused alongside the `/api/frame` incident; do not look for them. +### `GET /api/knobs` and `POST /api/knobs` + +Since 1.5. Which way each encoder counts and how many edges make one click — +properties of the part soldered in, which is why they are settings rather than a +rebuild. `GET` returns `inv` (four booleans: reversed or not), `sub` (edges per +click per knob: 4, 2 or 1 — most encoders are 4), `clicks` (each knob's click +count as the frame sees it) and `raw` (the edge counters underneath, for telling a +2-edge part from a 4-edge one). `POST` takes any subset of `inv0`..`inv3` (`0`/`1`) +and `sub0`..`sub3` (`4`/`2`/`1`), or `inv=` / `sub=` for all four, form-encoded; +a change is applied at a frame boundary without moving the knob, stored on the +device (NVS `pf_knobs`) and answered as `GET`. The `/knobs` page is this endpoint +with a live readout. `INVERT_ENCODER` in `config.h` is only the compile-time +default a fresh device starts from. + ### `GET /api/mqtt` ```json @@ -436,7 +450,7 @@ In short: HTTP is the management and state transport, OSC and MIDI are the low-l ## Version history -- **1.5** (unreleased) — status gains `network` and `thumbs` diagnostics, `fsError` (why storage is not mounted) and `flashId`; a failed `POST /api/patterns/format` returns the reason as its `error`; `POST /api/wifi/reconnect` reconnects without a reboot; core name registration retries partial failures and preserves feature-owned services. +- **1.5** (unreleased) — status gains `network` and `thumbs` diagnostics, `fsError` (why storage is not mounted) and `flashId`; a failed `POST /api/patterns/format` returns the reason as its `error`; `POST /api/wifi/reconnect` reconnects without a reboot; core name registration retries partial failures and preserves feature-owned services; the MIDI edition adds a `midiUsb` block to status ([`midi-spec.md`](midi-spec.md)); `GET`/`POST /api/knobs` and the `/knobs` page (encoder direction and edges per click, per knob, persisted). - **1.4** (2026-09-06) — `GET /api/patterns/file` gains `ext=thumb`; `GET /api/display` takes `brightness` and status reports it; status gains `resetReason` and `load.internal`/`load.psram`; console pages are served gzip-compressed (`Content-Encoding: gzip`); the page sender no longer truncates on a slow link; the server no longer trips the Core-0 watchdog on a request that stalls mid-header. - **1.3** (2026-09-04) — `GET`/`POST /api/clock` (Utility edition) and the `clock` block in status; `caps` gains `"clock"`. - **1.2** (2026-09-03) — the server is serviced on Core 0 (the one-connection rule stands; the render-pays rule is history); status gains `httpCore`, `netStackMin`, `loopSyncServed`/`loopSyncMaxUs`; `POST /api/params` documents `d1`..`d4` and how a held value reaches a legacy pattern; `GET /api/patterns/select` gains `step`; `GET`/`POST /api/audio` (Audio-React) are documented; `featureNav`'s microphone label is *Audio*. diff --git a/firmware/README.md b/firmware/README.md index c0061fa5..ff53c383 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -168,7 +168,7 @@ firmware/ │ ├── console/ # The device's web pages as plain HTML — edit these │ └── toolchain/ # PlatformIO extra_scripts (must stay sketch-relative) ├── bundles/ # Editions: two files each naming a feature set -│ ├── audio/ performance/ clock/ # features_local.h + overrides.h +│ ├── audio/ performance/ clock/ midi/ # features_local.h + overrides.h (+ env) │ ├── build.sh # build the default, a named edition, or `all` │ └── shelf.sh # stage a publishable image for the site ├── modules/ # The module toolchain's working folder, not an inbox — see modules/README.md diff --git a/firmware/bundles/README.md b/firmware/bundles/README.md index f2c703da..cacf25ab 100644 --- a/firmware/bundles/README.md +++ b/firmware/bundles/README.md @@ -15,6 +15,8 @@ and which settings differ. bundles/audio/ features_local.h which features this firmware has, and in what order overrides.h what it calls itself, and any setting it changes + env (optional) the PlatformIO env to build in — only when + the difference is a build flag, not a setting ``` That is the whole of it. The default build needs no bundle at all — and it @@ -24,6 +26,7 @@ lives in an edition built from these two files. ```bash ./firmware/bundles/build.sh # the default: no features ./firmware/bundles/build.sh audio # the audio bundle +./firmware/bundles/build.sh midi # the MIDI edition, in its own env (bundles/midi/env) ./firmware/bundles/build.sh audio flash patternflow.local ./firmware/bundles/build.sh all # every composition + the marker scan # proving each binary carries exactly @@ -47,7 +50,8 @@ A bundle earns its place when it carries something the default **cannot**: |---|---| | **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 | | **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 | +| **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 | Three shapes of reason, and they are not the same: @@ -62,6 +66,11 @@ The card on [patternflow.work/editions](https://patternflow.work/editions) says which of the three each bundle is, because a person choosing needs to know whether they are picking up something unfinished or something frozen. +A bundle can also be in the tree and not on the shelf at all — `clock` and +`midi` are. Then it is a composition CI keeps compiling so a core change +cannot break it silently, and there is no card, no image and nothing to cut. +The shelf is what its maintainer stands behind, and it is short on purpose. + ## Graduating A bundle is not a permanent home. When the microphone works and the board has diff --git a/firmware/bundles/build.sh b/firmware/bundles/build.sh index 752504e9..126fdfac 100755 --- a/firmware/bundles/build.sh +++ b/firmware/bundles/build.sh @@ -5,6 +5,8 @@ # ./build.sh the default — no features # ./build.sh audio the audio bundle # ./build.sh audio flash build it, then push it to a panel +# ./build.sh one audio build ONE composition and scan its image +# (what CI runs, one job per composition) # ./build.sh all every composition + a marker scan # proving each binary carries exactly # its features — run this before pushing @@ -13,56 +15,110 @@ # A bundle is two files. This copies them next to the feature list, builds, and # takes them away again — the tree is left exactly as it was found, so the # next build is the default unless you ask for a bundle. +# +# A bundle may add a third file, `env`, naming the PlatformIO env it builds +# in (default: firmware). The MIDI edition needs one: its USB port has to be +# the OTG controller, which is a build flag, not a setting - see +# platformio.ini. The build tree is per env, so the outputs move with it. + set -euo pipefail cd "$(dirname "$0")/../.." SKETCH=firmware/patternflow FEATURES="$SKETCH/features" + # xtensa's linker cannot open output files under a path with non-ASCII in it, # and this repository lives under one. The build tree goes somewhere plain. BUILD_DIR="${PF_BUILD_DIR:-$HOME/pf-build}" +# Outside PF_BUILD_DIR: PlatformIO prunes directories it does not know +# from its own build root, and it does not know this one. +OUTDIR="${BUILD_DIR}-editions" + +# The PlatformIO env a bundle builds in: `env` beside its two files, else the +# default. PlatformIO writes each env's outputs under its own name. +env_of() { + local f="firmware/bundles/$1/env" + if [ -f "$f" ]; then tr -d '[:space:]' < "$f"; else echo firmware; fi +} -# ── all: every composition, and proof each binary carries exactly its -# features ──────────────────────────────────────────────────────────────── +# ── The compositions, and proof each binary carries exactly its features ── # -# "Changing the core means building all three" was a rule in prose, and a -# rule in prose gets one of the three. This is that rule as one command: +# "Changing the core means building every composition" was a rule in prose, +# and a rule in prose gets one of them. `all` is that rule as one command, and +# `one` is the same thing for a single composition, so CI can run them in +# parallel, one job each, instead of in a row: # # ./firmware/bundles/build.sh all +# ./firmware/bundles/build.sh one clock # -# It builds default, audio, performance and clock, then scans each image for one -# marker string per feature — a literal that lives only in that feature's -# sources, verified by grep before it was trusted here. An edition must -# contain its own features' markers and NONE of the others'. That checks the -# composition in the shipped bytes, where features.h's #error guard cannot see: -# the guard catches a misspelled macro, this catches the right macro building -# the wrong thing. It is how the NETWORK screen fix was proven — "OSC / AUD" -# absent from the default image because the compiler folded the branch away — -# done every time instead of once by hand. +# Each image is scanned for one marker string per feature — a literal that +# lives only in that feature's sources, verified by grep before it was trusted +# here. A composition must contain its own features' markers and NONE of the +# others'. That checks the composition in the shipped bytes, where features.h's +# #error guard cannot see: the guard catches a misspelled macro, this catches +# the right macro building the wrong thing. It is how the NETWORK screen fix +# was proven — "OSC / AUD" absent from the default image because the compiler +# folded the branch away — done every time instead of once by hand. +# +# Not every composition is on the shelf. `clock` and `midi` are bundles the +# tree keeps buildable so a core change cannot break them silently; the shelf +# (release.py's EDITIONS) is the maintainer's shorter list. +COMPOSITIONS="default audio performance clock midi" +declare -A MARK=( + [osc]='/patternflow/knob' + [audio]='[AUDIO] Ready' + [audio_in]='PDM up: CLK' + [mqtt]='[MQTT] ' + [show]='[SHOW] ' + [weather]='openweathermap' + [midi]='[MIDI] rtp listening' + [midi_usb]='[MIDI] usb device' + [ble]='[BLE] setup advertising' + [clock]='[CLOCK] /clock ready' +) +declare -A WANT=( + [default]='' + [audio]='osc audio audio_in midi' + [performance]='mqtt show weather' + [clock]='clock' + [midi]='midi midi_usb' +) + +known_composition() { [ -n "${1:-}" ] && [ -n "${WANT[$1]+x}" ]; } + +# stage : the env's outputs, copied under OUTDIR as .bin/.elf. +# The elf as well, because the bin cannot answer the question that actually +# governs this product. A .bin's size is flash, and flash is not scarce here: +# internal DRAM is, and the module code budget is whatever is left of it once +# .data, .bss and IRAM have taken their share. check_footprint.py reads these. +stage() { + local ed="$1" env + env="$(env_of "$ed")" + mkdir -p "$OUTDIR" + cp "$BUILD_DIR/$env/firmware.bin" "$OUTDIR/$ed.bin" + cp "$BUILD_DIR/$env/firmware.elf" "$OUTDIR/$ed.elf" +} + +# scan_image : prints the verdict; fails unless the image +# carries exactly its composition's markers. +scan_image() { + local ed="$1" bin="$2" verdict=ok detail="" f want have + for f in osc audio audio_in mqtt show weather midi midi_usb ble clock; do + case " ${WANT[$ed]} " in *" $f "*) want=1 ;; *) want=0 ;; esac + if grep -qaF -- "${MARK[$f]}" "$bin"; then have=1; else have=0; fi + if [ "$want" != "$have" ]; then + verdict=FAIL + if [ "$want" = 1 ]; then detail="$detail missing:$f"; else detail="$detail carries:$f"; fi + fi + done + printf '%-12s %8s bytes %s%s\n' "$ed" "$(stat -c%s "$bin")" "$verdict" "$detail" + [ "$verdict" = ok ] +} + if [ "${1:-}" = "all" ]; then - declare -A MARK=( - [osc]='/patternflow/knob' - [audio]='[AUDIO] Ready' - [audio_in]='PDM up: CLK' - [mqtt]='[MQTT] ' - [show]='[SHOW] ' - [weather]='openweathermap' - [midi]='[MIDI] rtp listening' - [ble]='[BLE] setup advertising' - [clock]='[CLOCK] /clock ready' - ) - declare -A WANT=( - [default]='' - [audio]='osc audio audio_in midi' - [performance]='mqtt show weather' - [clock]='clock' - ) - # Outside PF_BUILD_DIR: PlatformIO prunes directories it does not know - # from its own build root, and it does not know this one. - OUTDIR="${BUILD_DIR}-editions" mkdir -p "$OUTDIR" overall=0 - for ed in default audio performance clock; do + for ed in $COMPOSITIONS; do printf '%-12s building… ' "$ed" t0=$(date +%s) log="$OUTDIR/$ed.log" @@ -75,25 +131,9 @@ if [ "${1:-}" = "all" ]; then tail -15 "$log" exit 1 fi - cp "$BUILD_DIR/firmware/firmware.bin" "$OUTDIR/$ed.bin" - # The elf as well, because the bin cannot answer the question that actually - # governs this product. A .bin's size is flash, and flash is not scarce here: - # internal DRAM is, and the module code budget is whatever is left of it once - # .data, .bss and IRAM have taken their share. check_footprint.py reads these. - cp "$BUILD_DIR/firmware/firmware.elf" "$OUTDIR/$ed.elf" - verdict=ok - detail="" - for f in osc audio audio_in mqtt show weather midi ble clock; do - case " ${WANT[$ed]} " in *" $f "*) want=1 ;; *) want=0 ;; esac - if grep -qaF -- "${MARK[$f]}" "$OUTDIR/$ed.bin"; then have=1; else have=0; fi - if [ "$want" != "$have" ]; then - verdict=FAIL - overall=1 - if [ "$want" = 1 ]; then detail="$detail missing:$f"; else detail="$detail carries:$f"; fi - fi - done - printf '%3ss %8s bytes %s%s\n' "$(( $(date +%s) - t0 ))" \ - "$(stat -c%s "$OUTDIR/$ed.bin")" "$verdict" "$detail" + stage "$ed" + printf '%3ss ' "$(( $(date +%s) - t0 ))" + scan_image "$ed" "$OUTDIR/$ed.bin" || overall=1 done if [ "$overall" != 0 ]; then echo "" @@ -102,11 +142,28 @@ if [ "${1:-}" = "all" ]; then exit $overall fi +if [ "${1:-}" = "one" ]; then + ed="${2:-}" + if ! known_composition "$ed"; then + echo "usage: build.sh one <$(echo "$COMPOSITIONS" | tr ' ' '|')>" >&2 + exit 2 + fi + # In the foreground: on a CI runner the compiler's own output is the log. + if [ "$ed" = default ]; then bash "$0"; else bash "$0" "$ed"; fi + stage "$ed" + echo "" + scan_image "$ed" "$OUTDIR/$ed.bin" + exit $? +fi + BUNDLE="${1:-}" +ENV=firmware if [ -n "$BUNDLE" ] && [ "$BUNDLE" != "flash" ]; then DIR="firmware/bundles/$BUNDLE" [ -d "$DIR" ] || { echo "no such bundle: $BUNDLE" >&2; exit 1; } echo "bundle: $BUNDLE" + ENV="$(env_of "$BUNDLE")" + [ "$ENV" = firmware ] || echo "env: $ENV" cp "$DIR"/*.h "$FEATURES/" # Whatever happens next, the tree goes back to the default. Leaving a # bundle's files behind would make the following build silently wrong. @@ -137,9 +194,9 @@ for pair in \ [ -d "$SKETCH/lib/$name" ] || git clone -q --depth 1 "$url" "$SKETCH/lib/$name" done -( cd "$SKETCH" && PLATFORMIO_BUILD_DIR="$BUILD_DIR" python -m platformio run -e firmware ) +( cd "$SKETCH" && PLATFORMIO_BUILD_DIR="$BUILD_DIR" python -m platformio run -e "$ENV" ) -BIN="$BUILD_DIR/firmware/firmware.bin" +BIN="$BUILD_DIR/$ENV/firmware.bin" echo "" echo "built: $BIN ($(stat -c%s "$BIN") bytes)" diff --git a/firmware/bundles/midi/env b/firmware/bundles/midi/env new file mode 100644 index 00000000..95d0670a --- /dev/null +++ b/firmware/bundles/midi/env @@ -0,0 +1 @@ +firmware_midi diff --git a/firmware/bundles/midi/features_local.h b/firmware/bundles/midi/features_local.h new file mode 100644 index 00000000..97549e5a --- /dev/null +++ b/firmware/bundles/midi/features_local.h @@ -0,0 +1,17 @@ +// ═══════════════════════════════════════════════════════════ +// Patternflow MIDI — which features this edition compiles in +// +// The MIDI feature alone: the map, the page, RTP-MIDI over Wi-Fi - and, in +// this edition, the DevKit's USB port as a class-compliant MIDI device. The +// USB transport is not a second feature but a part of this one that compiles +// in when the build's USB port is the OTG stack, which is what the +// firmware_midi env sets (see `env` next to this file, and platformio.ini): +// whether that port is a serial console or any device class is a compile-time +// choice of the Arduino core, not a setting. +// +// License: MIT +// ═══════════════════════════════════════════════════════════ +#pragma once +#include "midi/feature_midi.h" + +#define PF_FEATURE_LIST &PFFeatureMidi::descriptor diff --git a/firmware/bundles/midi/overrides.h b/firmware/bundles/midi/overrides.h new file mode 100644 index 00000000..3f6a907f --- /dev/null +++ b/firmware/bundles/midi/overrides.h @@ -0,0 +1,23 @@ +// ═══════════════════════════════════════════════════════════ +// Patternflow MIDI — what this edition calls itself, and what it sets +// +// Included from config.h before any default, so anything `#ifndef`-guarded +// anywhere in the tree can be set here. Nothing in this file is a core +// file, and the build script puts it back the way it found it. +// +// License: MIT +// ═══════════════════════════════════════════════════════════ +#pragma once + +// Reported in /api/status as `variant` and `variantVersion`, worn as a badge +// on every console page, and shown on the shelf card. This version is the +// edition's own and moves at whatever pace suits it - it has nothing to say +// about the core version, which is reported separately. +#define PF_VARIANT "midi" +#define PF_VARIANT_VERSION "v0.1.0" + +// Nothing else is changed. The radio stays at the conformance-tested setting, +// no preset rides along, and a panel switching to this edition keeps its +// Wi-Fi, its brightness and its patterns - that is the rule for being on the +// shelf at all, not a courtesy. What does change is outside this file: the +// firmware_midi env puts the USB port on the OTG stack (platformio.ini). diff --git a/firmware/bundles/shelf.sh b/firmware/bundles/shelf.sh index 04c0bbc9..b03ac539 100755 --- a/firmware/bundles/shelf.sh +++ b/firmware/bundles/shelf.sh @@ -58,6 +58,12 @@ if [ "$NAME" != "core" ]; then [ -d "firmware/bundles/$NAME" ] || { echo "no such edition: $NAME" >&2; exit 1; } BUNDLE_ARG=("$NAME") fi +# An edition may build in its own PlatformIO env (bundles//env); the +# build tree is per env, so every output below is read from there. +ENV=firmware +if [ "$NAME" != "core" ] && [ -f "firmware/bundles/$NAME/env" ]; then + ENV="$(tr -d '[:space:]' < "firmware/bundles/$NAME/env")" +fi # ── The control build ─────────────────────────────────────────────────── # @@ -106,7 +112,7 @@ scan() { # scan -> 0 clean, 1 leaking if [ "$HAVE_SECRETS" = 1 ]; then echo "control build (with secrets) — the scanner must FAIL this" ./firmware/bundles/build.sh "${BUNDLE_ARG[@]+"${BUNDLE_ARG[@]}"}" >/dev/null - if scan "$BUILD_DIR/firmware/firmware.bin"; then + if scan "$BUILD_DIR/$ENV/firmware.bin"; then echo " the scanner passed an image that HAS the credentials. It is broken." >&2 echo " Refusing to build a shelf image with a scanner that proves nothing." >&2 exit 1 @@ -128,7 +134,7 @@ fi echo "shelf build: $NAME $VERSION" ./firmware/bundles/build.sh "${BUNDLE_ARG[@]+"${BUNDLE_ARG[@]}"}" >/dev/null -BIN="$BUILD_DIR/firmware/firmware.bin" +BIN="$BUILD_DIR/$ENV/firmware.bin" echo " built $(stat -c%s "$BIN") bytes" if ! scan "$BIN"; then echo " refusing to stage it" >&2 @@ -174,8 +180,8 @@ fi [ -f "$BOOT_APP0" ] || { echo "boot_app0.bin not found at $BOOT_APP0" >&2; exit 1; } mkdir -p "$OUT" cp "$BIN" "$OUT/patternflow.ino.bin" -cp "$BUILD_DIR/firmware/bootloader.bin" "$OUT/patternflow.ino.bootloader.bin" -cp "$BUILD_DIR/firmware/partitions.bin" "$OUT/patternflow.ino.partitions.bin" +cp "$BUILD_DIR/$ENV/bootloader.bin" "$OUT/patternflow.ino.bootloader.bin" +cp "$BUILD_DIR/$ENV/partitions.bin" "$OUT/patternflow.ino.partitions.bin" cp "$BOOT_APP0" "$OUT/boot_app0.bin" # ── One live image per name ───────────────────────────────────────────── diff --git a/firmware/patternflow/console/README.md b/firmware/patternflow/console/README.md index cdda7d64..1c3a7c75 100644 --- a/firmware/patternflow/console/README.md +++ b/firmware/patternflow/console/README.md @@ -30,6 +30,7 @@ CI checks the two stay in sync. | `patterns.html` | `/patterns` | `src/core_patterns_http.h` | `src/patterns_index.h` | | `status.html` | `/status` | `src/core_status_http.h` | `src/status_index.h` | | `wifi.html` | `/wifi` | `src/core_wifi_http.h` | `src/wifi_index.h` | +| `knobs.html` | `/knobs` | `src/core_knobs_http.h` | `src/knobs_index.h` | | `update.html` | `/update` | `src/core_web_update.h` | `src/web_update_index.h` | | `show.html` | `/show` | `features/show/core_show_http.h` | `features/show/show_index.h` | | `weather.html` | `/weather` | `features/weather/core_weather_http.h` | `features/weather/weather_index.h` | diff --git a/firmware/patternflow/console/home.html b/firmware/patternflow/console/home.html index 0a094ec2..99e8a234 100644 --- a/firmware/patternflow/console/home.html +++ b/firmware/patternflow/console/home.html @@ -264,6 +264,11 @@
Wi-Fi
Remember several networks — the device joins whichever one it finds.
+ + 04 +
Knobs
+
Which way each encoder counts, and how many edges make a click — for the parts that differ. Nothing to rebuild.
+
Device diff --git a/firmware/patternflow/console/knobs.html b/firmware/patternflow/console/knobs.html new file mode 100644 index 00000000..c4095bcb --- /dev/null +++ b/firmware/patternflow/console/knobs.html @@ -0,0 +1,133 @@ + + + + + +Patternflow - Knobs +
+

Knobs

-
+
+

Direction and click size

+ + + +
PositionDirectionEdges per click
+
+ + +
+
+

Turn a knob clockwise. Its position should go up by one per click. + Goes down: set that knob to Reversed. Moves two per click, or needs two clicks to + move one: change Edges per click until one click is one step (most encoders are 4; + the edge counter under the position shows how many one click really makes). + These are properties of the encoder soldered in, stored on the device — nothing to + rebuild, and a saved change does not move the knob.

+
+ +
+ diff --git a/firmware/patternflow/features/README.md b/firmware/patternflow/features/README.md index 2f1c6b7f..703c965b 100644 --- a/firmware/patternflow/features/README.md +++ b/firmware/patternflow/features/README.md @@ -180,7 +180,7 @@ Then one line in the bundle that carries it — `firmware/bundles//feat | `audio/` | FFT bands over a websocket, HTTP page | The one with a server of its own, and a row in the device's own menu. | | `audio_in/` | the on-board PDM microphone: FFT on a Core-0 task, four bands onto the knob lanes, `/audio-in` | The first feature that did not already exist in the core; four hooks, no core edits. Its console page is assembled from the audio extension's editor (`toolchain/build_audio_in_page.py`). | | `osc/` | Max / TouchDesigner / Ableton, both directions | The fifth, and the first that did not fit — see below. | -| `midi/` | RTP-MIDI in and out: CC 20–23 absolute, 24–27 relative, notes, Program Change | Transport-agnostic mapping in `core_midi.h`; the network transport beside it. `docs/midi-spec.md`. | +| `midi/` | MIDI in and out over RTP-MIDI (Wi-Fi) and, in the MIDI edition, USB: CC 20–23 absolute, 24–27 relative, notes, Program Change | Transport-agnostic mapping in `core_midi.h`; the network transport beside it (`core_midi_rtp.h`), and the USB one (`core_midi_usb.h`) that compiles in only when the build's USB port is the OTG stack — a build flag, which is why the MIDI edition has its own env (`bundles/midi/env`). `docs/midi-spec.md`. | | `clock/` | the time cut out of the running pattern, `/clock`, `/api/clock` | Settings in its own NVS namespace, a page with a live preview, and the first user of `composeFrame`: huge anti-aliased digits the pattern shows through (typefaces rasterised offline by `toolchain/build_clock_glyphs.py`, several faces), with what fills the digits and what surrounds them chosen separately. Owns the time zone as a POSIX string with its DST rule; the core keeps the time (`src/core_clock.h`), this shows it. | | `ble/` | Wi-Fi provisioning from a phone over Improv-BLE | **In no edition.** The lifecycle works on hardware (advertise only while the panel cannot join, release the controller's memory after) but linking it costs ~12 KB of internal RAM and the one phone it was tried with never listed the panel. Opt-in for whoever finishes it; the header records what was measured. | diff --git a/firmware/patternflow/features/midi/core_midi_usb.h b/firmware/patternflow/features/midi/core_midi_usb.h new file mode 100644 index 00000000..b39f78b5 --- /dev/null +++ b/firmware/patternflow/features/midi/core_midi_usb.h @@ -0,0 +1,202 @@ +// ═══════════════════════════════════════════════════════════ +// PatternFlow - MIDI over the DevKit's USB port (a USB-MIDI class device) +// +// The wired transport, beside core_midi_rtp.h. The panel plugs into a +// computer with the same cable it is flashed over and shows up as a +// class-compliant MIDI device: no driver, no session, no Wi-Fi in the path - +// the DAW sees "Patternflow" the way it sees a keyboard. The map is +// core_midi.h's, shared with every transport: CC 20-23 hold the knobs, +// CC 24-27 nudge them, notes 60-63 press the buttons, a program change picks +// a pattern, and the encoders go out as CC 24-27. +// +// It compiles in only when the build's USB port is the OTG stack +// (ARDUINO_USB_MODE=0 - the firmware_midi env, see platformio.ini). In every +// other build this file is a handful of empty functions and the feature is +// the RTP transport alone. That is the one build-time switch in this feature, +// and it is the Arduino core's, not a feature flag: the chip has ONE USB PHY, +// and which of its two owners gets it - the fixed USB-Serial/JTAG every other +// build uses for flashing, Improv and the log, or the USB-OTG controller that +// can be any device class - is decided before setup() runs. With OTG the +// core presents CDC (still `Serial`, so Improv and the log survive) and this +// adds the MIDI interface beside it. The ROM's download mode does not change, +// so the browser flasher flashes such an image like any other. +// +// Built on the TinyUSB device stack the Arduino core already carries for the +// S3, whose MIDI class the SDK compiles in. +// +// Why there is no worker task and no queue, unlike core_midi_rtp.h: USB has +// flow control. When the class FIFO is full the device NAKs the host's next +// packet and the host waits; nothing is dropped, only delayed. So the frame +// task reads the FIFO itself, one budgeted batch per frame, and the worst +// case on a torrent of controllers is a frame of latency. RTP had to be +// serviced between frames because UDP drops whatever nobody reads in time. +// +// Threading: everything here runs on the frame task, which is where +// core_midi.h's handlers must be called from. TinyUSB's own task services the +// bus; reading and writing the class FIFOs from another task is what USBCDC +// does for Serial, and is the supported pattern. +// +// License: MIT +// ═══════════════════════════════════════════════════════════ +#pragma once + +#include +#include "core_midi.h" + +#if !ARDUINO_USB_MODE && CONFIG_TINYUSB_ENABLED && CONFIG_TINYUSB_MIDI_ENABLED +#define PF_MIDI_USB 1 +#else +#define PF_MIDI_USB 0 +#endif + +// The MIDI interface's name: what macOS and Linux show as the port. Windows +// names the port after the USB product string instead, which the +// firmware_midi env sets to the same word. +#ifndef PF_MIDI_USB_NAME +#define PF_MIDI_USB_NAME "Patternflow" +#endif + +// Packets read per frame. The class FIFO holds 16; a torrent beyond that is +// not lost - USB makes the host wait until the next frame reads more - so +// this bounds the frame's work, not the throughput. +#ifndef PF_MIDI_USB_RX_BUDGET +#define PF_MIDI_USB_RX_BUDGET 32 +#endif + +#if PF_MIDI_USB +#include "esp32-hal-tinyusb.h" +#include "tusb.h" +#endif + +namespace PatternflowMidiUsb { + +constexpr bool available = PF_MIDI_USB; + +#if PF_MIDI_USB + +// ── The interface, declared before the stack starts ────────────────────── +// The core starts TinyUSB from app_main(), before setup() runs, so the MIDI +// interface is registered from a static initializer - the same way the core's +// own USBCDC registers Serial. tinyusb_init() then calls loadDescriptor() +// once while it assembles the configuration descriptor. +inline bool descriptorLoaded = false; + +inline uint16_t loadDescriptor(uint8_t* dst, uint8_t* itf) { + if (descriptorLoaded) return 0; + descriptorLoaded = true; + const uint8_t str = tinyusb_add_string_descriptor(PF_MIDI_USB_NAME); + const uint8_t epIn = tinyusb_get_free_in_endpoint(); + const uint8_t epOut = tinyusb_get_free_out_endpoint(); + if (!epIn || !epOut) return 0; + // Full-speed bulk endpoints are 64 bytes; the class FIFO behind them holds + // 16 packets each way (CONFIG_TINYUSB_MIDI_RX/TX_BUFSIZE). + const uint8_t desc[TUD_MIDI_DESC_LEN] = { + TUD_MIDI_DESCRIPTOR(*itf, str, epOut, (uint8_t)(0x80 | epIn), 64) + }; + *itf += 2; // audio control + MIDI streaming + memcpy(dst, desc, TUD_MIDI_DESC_LEN); + return TUD_MIDI_DESC_LEN; +} + +struct Registrar { + Registrar() { tinyusb_enable_interface(USB_INTERFACE_MIDI, TUD_MIDI_DESC_LEN, loadDescriptor); } +}; +inline Registrar registrar; + +// ── Counters, for /api/status ──────────────────────────────────────────── +inline uint32_t rxCount = 0; // packets read that carried a channel message +inline uint32_t rxUnhandled = 0; // sysex, realtime, pitch bend: read and ignored +inline uint32_t txCount = 0; +inline uint32_t txDropped = 0; // FIFO full: nobody reading, or 16 in one frame +inline bool hostSeen = false; + +// Configured by a host, and that host is awake. A cable pulled out leaves the +// device suspended rather than unmounted, and the FIFO would fill for nobody. +inline bool hostReady() { return tud_midi_mounted() && !tud_suspended(); } + +inline void apply(uint8_t status, uint8_t d1, uint8_t d2) { + const uint8_t ch = (status & 0x0F) + 1; + switch (status & 0xF0) { + case 0xB0: PatternflowMidi::onControlChange(ch, d1, d2); break; + case 0xC0: PatternflowMidi::onProgramChange(ch, d1); break; + case 0x90: PatternflowMidi::onNoteOn(ch, d1, d2); break; + case 0x80: PatternflowMidi::onNoteOff(ch, d1, d2); break; + default: break; + } +} + +// One batch per frame, on the frame task. USB-MIDI packs every message into +// four bytes: a header whose low nibble (the Code Index Number) says what +// follows, then up to three MIDI bytes. For channel voice messages the CIN +// equals the status high nibble, and those are all the map listens to. +inline void poll() { + const bool ready = hostReady(); + if (ready != hostSeen) { + hostSeen = ready; + // The host that pressed a key is gone; its note-off is never coming. + // Same reasoning as the RTP transport's disconnect handler. + if (!ready) PatternflowMidi::clearNotes(); + Serial.printf("[MIDI] usb host %s\n", ready ? "connected" : "gone"); + } + if (!ready) return; + uint8_t packet[4]; + for (int i = 0; i < PF_MIDI_USB_RX_BUDGET && tud_midi_packet_read(packet); ++i) { + switch (packet[0] & 0x0F) { + case 0x8: case 0x9: case 0xB: case 0xC: + rxCount++; + apply(packet[1], packet[2], packet[3]); + break; + default: + rxUnhandled++; + break; + } + } +} + +// The sink core_midi.h calls with what the encoders and buttons did. Cable 0; +// program change and channel pressure are two-byte messages, so their fourth +// byte is zero whatever the map handed over. +inline void sink(uint8_t status, uint8_t d1, uint8_t d2) { + if (!hostReady()) return; + const uint8_t cin = status >> 4; + const bool twoBytes = (cin == 0xC || cin == 0xD); + const uint8_t packet[4] = { cin, status, d1, twoBytes ? (uint8_t)0 : d2 }; + if (tud_midi_packet_write(packet)) txCount++; + else txDropped++; +} + +// From the feature's setup(): a cable needs no network, so this does not +// wait for onNetwork the way the RTP transport does. +inline void begin() { + PatternflowMidi::registerSink(sink); + // "[MIDI] usb device" is this transport's marker for build.sh's scan: the + // MIDI edition must carry it and no other image may. + Serial.printf("[MIDI] usb device \"%s\" ready - plug the DevKit's USB port into the computer\n", + PF_MIDI_USB_NAME); +} + +// A sibling of the `midi` block, so a page or a site can tell a wired panel +// from a wireless one by its presence. +inline void appendStatus(String& json) { + json += ",\"midiUsb\":{\"mounted\":"; + json += hostReady() ? "true" : "false"; + json += ",\"rx\":"; + json += rxCount; + json += ",\"rxUnhandled\":"; + json += rxUnhandled; + json += ",\"tx\":"; + json += txCount; + json += ",\"txDropped\":"; + json += txDropped; + json += "}"; +} + +#else // the USB port is the serial console in this build: nothing here + +inline void begin() {} +inline void poll() {} +inline void appendStatus(String&) {} + +#endif // PF_MIDI_USB + +} // namespace PatternflowMidiUsb diff --git a/firmware/patternflow/features/midi/feature_midi.h b/firmware/patternflow/features/midi/feature_midi.h index ebac4549..7d218a6d 100644 --- a/firmware/patternflow/features/midi/feature_midi.h +++ b/firmware/patternflow/features/midi/feature_midi.h @@ -3,8 +3,11 @@ // // Sound integration's third dialect after OSC and audio: a DAW, a controller // or a phone drives the four knobs with control changes, and the knobs drive -// them back. The mapping is core_midi.h; the transport shipped here is -// RTP-MIDI over Wi-Fi (core_midi_rtp.h). +// them back. The mapping is core_midi.h; the transports are RTP-MIDI over +// Wi-Fi (core_midi_rtp.h) and, when the build's USB port is the OTG stack, +// the DevKit's USB port as a MIDI device (core_midi_usb.h) - that build is +// the MIDI edition, bundles/midi, and the switch is the Arduino core's +// ARDUINO_USB_MODE, decided per env in platformio.ini rather than here. // // Hooks: setup, onNetwork, loop, fillInput, observeFrame, takePattern, the // NETWORK-screen toggle trio, appendStatus. No core edits. @@ -12,17 +15,18 @@ // License: MIT // ═══════════════════════════════════════════════════════════ #pragma once - #include "../pf_feature.h" #include "core_midi.h" #include "core_midi_http.h" #include "core_midi_rtp.h" +#include "core_midi_usb.h" namespace PFFeatureMidi { inline void setup() { PatternflowMidi::loadSettings(); PatternflowMidiRtp::prepare(); + PatternflowMidiUsb::begin(); // a cable needs no network; empty unless the port is OTG } inline void onNetwork() { @@ -32,6 +36,7 @@ inline void onNetwork() { inline void loop(const PFFeatureFrame&) { PatternflowMidiRtp::handle(); + PatternflowMidiUsb::poll(); } inline void fillInput(InputFrame& input) { PatternflowMidi::fillInput(input); } @@ -73,6 +78,8 @@ inline void appendStatus(String& json) { json += PatternflowMidi::txCount; PatternflowMidiRtp::appendDiagnostics(json); json += "}"; + // `midiUsb`, a sibling block, only in a build whose USB port is a MIDI device. + PatternflowMidiUsb::appendStatus(json); } inline const PFFeature descriptor = { diff --git a/firmware/patternflow/patternflow.ino b/firmware/patternflow/patternflow.ino index 394fe4a1..c9619b6b 100644 --- a/firmware/patternflow/patternflow.ino +++ b/firmware/patternflow/patternflow.ino @@ -105,6 +105,7 @@ #include "src/core_status_http.h" #include "src/core_display_http.h" #include "src/core_wifi_http.h" +#include "src/core_knobs_http.h" #include "src/core_loop_sync.h" #include "src/core_net_task.h" @@ -1337,6 +1338,7 @@ void loop() { PatternflowStatusHttp::begin(); PatternflowDisplayHttp::begin(); PatternflowWifiHttp::begin(); + PatternflowKnobsHttp::begin(); PatternflowNames::announce(); PFFeatures::onNetwork(); Serial.println("[NET] services started"); diff --git a/firmware/patternflow/platformio.ini b/firmware/patternflow/platformio.ini index 025bf7c7..01fa6cec 100644 --- a/firmware/patternflow/platformio.ini +++ b/firmware/patternflow/platformio.ini @@ -122,3 +122,30 @@ build_flags = -DPANEL_GEOMETRY=PANEL_GEOM_64x64_CHAIN2 -DPF_IMPROV_FW_SUFFIX=\"-64x2\" + +; ── MIDI edition: the DevKit's USB port as a MIDI device ────────────────── +; Same source, one flag: ARDUINO_USB_MODE=0. The S3 has one USB PHY and two +; owners for it - the fixed USB-Serial/JTAG every other build uses for +; flashing, Improv and the log, and the USB-OTG controller that can be any +; device class. A MIDI device needs OTG, and the Arduino core decides at +; compile time which of the two owns `Serial`, so the choice is an env, not +; a bundle setting; features/midi's USB transport compiles in only here. +; With ARDUINO_USB_MODE=0 the OTG stack presents CDC (still `Serial`: Improv +; and the log survive) plus MIDI; the product string is what Windows names +; the MIDI port after; the ROM's download mode is untouched, so the browser +; flasher flashes this image like any other. What a person notices: after a +; flash or a reset the port comes back as a different USB device (a +; composite), so a flasher session may need one reconnect. Built by +; `bundles/build.sh midi`, which reads this env's name from bundles/midi/env. +; The board's own extra_flags carry ARDUINO_USB_MODE=1, hence the unflag. +; Output: .pio/build/firmware_midi/firmware_midi.bin +[env:firmware_midi] +extends = env:firmware +build_unflags = + ${env:firmware.build_unflags} + -DARDUINO_USB_MODE=1 +build_flags = + ${env:firmware.build_flags} + -DARDUINO_USB_MODE=0 + -DUSB_PRODUCT=\"Patternflow\" + -DUSB_MANUFACTURER=\"Patternflow\" diff --git a/firmware/patternflow/src/core_encoders.h b/firmware/patternflow/src/core_encoders.h index 6060101c..84cd93e8 100644 --- a/firmware/patternflow/src/core_encoders.h +++ b/firmware/patternflow/src/core_encoders.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "config.h" volatile long encPos[4] = {0, 0, 0, 0}; // sub-steps; 4 per detent @@ -64,7 +65,64 @@ void IRAM_ATTR isr2() { handleEncoder(1, ENC2_A, ENC2_B); } void IRAM_ATTR isr3() { handleEncoder(2, ENC3_A, ENC3_B); } void IRAM_ATTR isr4() { handleEncoder(3, ENC4_A, ENC4_B); } -inline long getClicks(int idx) { return encPos[idx] / 4; } +// ── Knob settings a person can change at runtime (/knobs) ──────────────── +// Direction and click size are properties of the encoder soldered in, not +// of the firmware: some parts count the other way round, some give two +// edges per detent instead of four. INVERT_ENCODER in config.h is the +// compile-time default; these override it per knob and persist in NVS +// (namespace "pf_knobs"), set from the /knobs page (core_knobs_http.h). +inline bool knobInvert[4] = {INVERT_ENCODER != 0, INVERT_ENCODER != 0, + INVERT_ENCODER != 0, INVERT_ENCODER != 0}; +inline int knobSubSteps[4] = {4, 4, 4, 4}; // edges per click: 4, 2 or 1 +// A settings change must not move a knob. The click count carries on from +// where it was, counting the edges since the change in the new direction +// and at the new size, so the frame's delta stays zero across a save. +inline long knobClickBase[4] = {0, 0, 0, 0}; +inline long knobPosBase[4] = {0, 0, 0, 0}; + +inline long getClicks(int idx) { + long rel = encPos[idx] - knobPosBase[idx]; + if (knobInvert[idx]) rel = -rel; + return knobClickBase[idx] + rel / knobSubSteps[idx]; +} + +inline int clampSubSteps(int v) { return (v == 1 || v == 2 || v == 4) ? v : 4; } + +// Call on the frame task (the frame reads the same counters). +inline void setKnobSettings(int idx, bool invert, int subSteps) { + if (idx < 0 || idx > 3) return; + knobClickBase[idx] = getClicks(idx); // rebase on the settings being replaced + knobPosBase[idx] = encPos[idx]; + knobInvert[idx] = invert; + knobSubSteps[idx] = clampSubSteps(subSteps); +} + +inline void loadKnobSettings() { + Preferences p; + if (!p.begin("pf_knobs", true)) return; // nothing saved yet: the compile-time defaults + char key[6]; + for (int i = 0; i < 4; i++) { + snprintf(key, sizeof(key), "inv%d", i); + knobInvert[i] = p.getBool(key, knobInvert[i]); + snprintf(key, sizeof(key), "sub%d", i); + knobSubSteps[i] = clampSubSteps(p.getInt(key, knobSubSteps[i])); + } + p.end(); +} + +inline bool saveKnobSettings() { + Preferences p; + if (!p.begin("pf_knobs", false)) return false; + char key[6]; + for (int i = 0; i < 4; i++) { + snprintf(key, sizeof(key), "inv%d", i); + p.putBool(key, knobInvert[i]); + snprintf(key, sizeof(key), "sub%d", i); + p.putInt(key, knobSubSteps[i]); + } + p.end(); + return true; +} struct Button { int pin; @@ -163,4 +221,5 @@ inline void initEncoders() { btn2.begin(ENC2_SW); btn3.begin(ENC3_SW); btn4.begin(ENC4_SW); + loadKnobSettings(); // before the first frame reads a click } diff --git a/firmware/patternflow/src/core_knobs_http.h b/firmware/patternflow/src/core_knobs_http.h new file mode 100644 index 00000000..f36971d1 --- /dev/null +++ b/firmware/patternflow/src/core_knobs_http.h @@ -0,0 +1,123 @@ +// ═══════════════════════════════════════════════════════════ +// PatternFlow - Knob settings over HTTP +// +// Which way each encoder counts, and how many edges make one click. These +// are properties of the part soldered in, not of the firmware: some +// encoders count the other way round, some give two edges per detent +// instead of four, and a builder who got either should not have to rebuild. +// The compile-time INVERT_ENCODER in config.h stays as the default; what a +// person sets here overrides it per knob and persists (core_encoders.h). +// +// Routes (on the shared console server): +// GET /knobs the page +// GET /api/knobs {"ok":true,"inv":[..],"sub":[..],"clicks":[..],"raw":[..]} +// POST /api/knobs form fields invN=0|1 and subN=4|2|1 (N = 0..3), or +// inv= / sub= for all four; any subset. Answers as GET. +// +// Changes are applied at a frame boundary (PFLoopSync): the handler runs on +// the network core and the frame is reading the same counters. +// License: MIT +// ═══════════════════════════════════════════════════════════ +#pragma once + +#include "webserver/WebServer.h" // vendored: fixes the 5 s final-chunk stall (see src/webserver/VENDORED.md) +#include + +#include "core_http.h" +#include "core_send.h" +#include "core_loop_sync.h" +#include "core_encoders.h" +#include "core_patterns_http.h" // noteConsolePageOpened - the console's activity clock +#include "knobs_index.h" + +namespace PatternflowKnobsHttp { + +inline WebServer& server() { return PatternflowHttp::server(); } +inline bool initialized = false; + +inline void sendJson(int code, const String& body) { + server().sendHeader("Cache-Control", "no-store"); + server().send(code, "application/json", body); +} + +inline String settingsJson() { + String json = "{\"ok\":true,\"inv\":["; + for (int i = 0; i < 4; i++) { if (i) json += ','; json += knobInvert[i] ? "true" : "false"; } + json += "],\"sub\":["; + for (int i = 0; i < 4; i++) { if (i) json += ','; json += knobSubSteps[i]; } + json += "],\"clicks\":["; + for (int i = 0; i < 4; i++) { if (i) json += ','; json += (long)getClicks(i); } + json += "],\"raw\":["; + for (int i = 0; i < 4; i++) { if (i) json += ','; json += (long)encPos[i]; } + json += "]}"; + return json; +} + +inline void handleGet() { + PatternflowPatternsHttp::noteConsoleApiCall(); + sendJson(200, settingsJson()); +} + +inline bool truthy(const String& v) { return v == "1" || v == "true" || v == "on"; } + +inline void handlePost() { + PatternflowPatternsHttp::noteConsoleApiCall(); + bool inv[4], changed[4] = {false, false, false, false}; + int sub[4]; + bool any = false; + for (int i = 0; i < 4; i++) { + inv[i] = knobInvert[i]; + sub[i] = knobSubSteps[i]; + char key[6]; + snprintf(key, sizeof(key), "inv%d", i); + if (server().hasArg(key)) { inv[i] = truthy(server().arg(key)); changed[i] = true; } + else if (server().hasArg("inv")) { inv[i] = truthy(server().arg("inv")); changed[i] = true; } + snprintf(key, sizeof(key), "sub%d", i); + if (server().hasArg(key)) { sub[i] = (int)server().arg(key).toInt(); changed[i] = true; } + else if (server().hasArg("sub")) { sub[i] = (int)server().arg("sub").toInt(); changed[i] = true; } + if (changed[i] && clampSubSteps(sub[i]) != sub[i]) { + sendJson(400, "{\"ok\":false,\"error\":\"sub must be 4, 2 or 1\"}"); + return; + } + any = any || changed[i]; + } + if (!any) { + sendJson(400, "{\"ok\":false,\"error\":\"nothing to set: invN, subN, inv or sub\"}"); + return; + } + // On the frame task: the rebase reads the counters the frame is reading. + PFLoopSync::run([&] { + for (int i = 0; i < 4; i++) { + if (changed[i]) setKnobSettings(i, inv[i], sub[i]); + } + }); + if (!saveKnobSettings()) { + sendJson(500, "{\"ok\":false,\"error\":\"applied, but could not save to NVS\"}"); + return; + } + Serial.printf("[KNOBS] set inv=%d%d%d%d sub=%d,%d,%d,%d\n", + knobInvert[0], knobInvert[1], knobInvert[2], knobInvert[3], + knobSubSteps[0], knobSubSteps[1], knobSubSteps[2], knobSubSteps[3]); + sendJson(200, settingsJson()); +} + +inline void handleIndex() { + if (PatternflowPatternsHttp::noteConsolePageOpened()) { + PatternflowPatternsHttp::sendConsoleWakePage(); + return; + } + PFSend::gz(server(), KNOBS_INDEX_HTML_GZ, KNOBS_INDEX_HTML_GZ_LEN); +} + +inline void begin() { + if (initialized) return; + if (WiFi.status() != WL_CONNECTED) return; + server().on("/knobs", HTTP_GET, handleIndex); + server().on("/api/knobs", HTTP_GET, handleGet); + server().on("/api/knobs", HTTP_POST, handlePost); + PatternflowHttp::begin(); // idempotent; whoever is first starts it + initialized = true; + Serial.println("[KNOBS] Ready - http://patternflow.local/knobs"); +} + +} // namespace PatternflowKnobsHttp diff --git a/firmware/patternflow/src/home_index.h b/firmware/patternflow/src/home_index.h index c7d50912..90ab63f9 100644 --- a/firmware/patternflow/src/home_index.h +++ b/firmware/patternflow/src/home_index.h @@ -295,6 +295,11 @@ const char HOME_INDEX_HTML[] PROGMEM = R"HTML(
Wi-Fi
Remember several networks — the device joins whichever one it finds.
+ + 04 +
Knobs
+
Which way each encoder counts, and how many edges make a click — for the parts that differ. Nothing to rebuild.
+
Device @@ -639,13 +644,13 @@ document.addEventListener('visibilitychange',function(){if(!document.hidden)tick )HTML"; // GENERATED by firmware/toolchain/console_pages.py build — do not edit. -// gzip -9 of HOME_INDEX_HTML (29647 bytes → 9513 bytes), served with Content-Encoding: gzip. +// gzip -9 of HOME_INDEX_HTML (29924 bytes → 9594 bytes), served with Content-Encoding: gzip. const uint8_t HOME_INDEX_HTML_GZ[] PROGMEM = { 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xcd,0x7d,0xdd,0x76,0xdb,0x48, 0x92,0xe6,0xbd,0x9e,0x22,0x8b,0x9e,0x32,0xc8,0x12,0x09,0x92,0xfa,0xb3,0x4d,0x8a, - 0x72,0xdb,0x2e,0x7b,0xc6,0x53,0xe5,0x2a,0xb7,0xed,0xea,0x9a,0x3d,0x3e,0xde,0x3e, - 0x20,0x91,0x24,0x51,0x02,0x01,0x0c,0x12,0x14,0xad,0x96,0xb5,0xa7,0xaf,0xe6,0x7a, - 0xcf,0x9c,0x79,0x86,0x79,0x85,0xbd,0xdf,0x47,0xa9,0x27,0xd9,0x2f,0x22,0x33,0x81, + 0x72,0xdb,0x2e,0x7b,0xd7,0x53,0xe5,0x2a,0xb7,0xed,0xea,0x9a,0x3d,0x3e,0xde,0x3e, + 0x20,0x91,0x24,0x51,0x02,0x01,0x34,0x12,0x14,0xad,0x96,0x35,0xa7,0xaf,0xe6,0x7a, + 0xce,0x9e,0x79,0x86,0x7d,0x85,0xbd,0xdf,0x47,0xa9,0x27,0xd9,0x2f,0x22,0x33,0x81, 0x04,0x7f,0x24,0xd9,0xee,0x3d,0xdd,0xae,0x92,0x45,0x02,0x99,0x91,0x91,0xf1,0x1f, 0x91,0x01,0xf8,0xf4,0x9b,0x30,0x9d,0x14,0x97,0x99,0x14,0xf3,0x62,0x11,0x9f,0xed, 0x9d,0xd2,0x2f,0x11,0x07,0xc9,0x6c,0xd4,0x90,0x49,0x83,0x2e,0xc8,0x20,0xc4,0xaf, @@ -692,7 +697,7 @@ const uint8_t HOME_INDEX_HTML_GZ[] PROGMEM = { 0xde,0x2b,0x25,0x41,0x90,0x24,0x30,0x0c,0xba,0xa1,0x32,0x70,0xb5,0xfd,0xf6,0x05, 0x5d,0xec,0xbc,0x91,0xb3,0x65,0x1c,0xe4,0xed,0x57,0x32,0x89,0xd3,0x76,0x79,0xdb, 0x05,0x56,0xf1,0xa1,0xc6,0xfe,0x3b,0xa0,0x44,0x34,0xff,0xee,0x6a,0x9c,0x7e,0xec, - 0xa8,0xe8,0x2f,0x51,0x32,0x1b,0x8c,0xd3,0x3c,0x94,0x79,0x07,0x57,0x86,0x8b,0x20, + 0xa8,0xe8,0xaf,0x51,0x32,0x1b,0x8c,0xd3,0x3c,0x94,0x79,0x07,0x57,0x86,0x8b,0x20, 0x87,0x06,0x0c,0x7a,0xc3,0x2c,0x08,0x43,0xba,0xd7,0xa3,0xd1,0xe3,0x34,0xbc,0xbc, 0x5a,0x44,0x49,0x47,0x6b,0xf1,0xa0,0xdf,0xeb,0x5d,0xcc,0x87,0xe3,0x60,0x72,0x3e, 0xcb,0xd3,0x65,0x12,0x0e,0x2e,0x82,0xbc,0x49,0x0c,0x6c,0x0d,0x27,0x69,0x9c,0xe6, @@ -710,9 +715,9 @@ const uint8_t HOME_INDEX_HTML_GZ[] PROGMEM = { 0xaf,0x8d,0x01,0xb8,0x29,0x99,0xc2,0x35,0x13,0x70,0x02,0x13,0x10,0x4b,0xf2,0x25, 0x1d,0x92,0x21,0x22,0x9e,0xdf,0xeb,0xcb,0x45,0xc5,0x6a,0xda,0xf2,0xa0,0xcf,0xe8, 0xa8,0xe5,0xb8,0x66,0x58,0xf4,0xd2,0x24,0x7f,0xad,0xa1,0xb3,0x06,0x16,0xae,0x31, - 0x9a,0xd5,0x0f,0xba,0x0c,0x10,0xdd,0xef,0xc4,0xef,0xff,0xf5,0x57,0xfc,0x2f,0xbe, + 0x9a,0xd5,0x0f,0xba,0x0c,0x10,0xdd,0xef,0xc4,0xef,0xff,0xf5,0x37,0xfc,0x2f,0xbe, 0x67,0x37,0x27,0x26,0x90,0x4a,0xd1,0x4c,0x13,0x29,0x14,0x6c,0x4f,0x2c,0xc8,0x4a, - 0x2c,0x95,0xc8,0x52,0x7c,0xfe,0xfd,0xaf,0xff,0x25,0x8a,0xb9,0x14,0xda,0x23,0x8a, + 0x2c,0x95,0xc8,0x52,0x7c,0xfe,0xfd,0x6f,0xff,0x25,0x8a,0xb9,0x14,0xda,0x23,0x8a, 0x44,0x5e,0xc8,0x1c,0x03,0x72,0x19,0x2c,0x30,0x22,0xfa,0x28,0x63,0xd5,0xb2,0xe0, 0xbe,0xeb,0x32,0x82,0x93,0x5c,0xca,0xe4,0x6a,0x83,0x3a,0xcc,0x7c,0x4b,0x9f,0x1d, 0x2c,0x28,0x65,0xa7,0x47,0xb2,0x43,0x7f,0x9d,0x18,0x2e,0x24,0xe9,0xaa,0x33,0x0f, @@ -732,510 +737,515 @@ const uint8_t HOME_INDEX_HTML_GZ[] PROGMEM = { 0x4b,0x56,0xb7,0x61,0x48,0x10,0xd4,0x1d,0x92,0xed,0xab,0x8c,0xf7,0xc1,0x23,0x7c, 0xa7,0xdd,0x51,0x68,0xd9,0x59,0xe5,0x10,0x95,0x20,0xb9,0x5c,0xcd,0x65,0x2e,0xcb, 0x6d,0x7c,0xa1,0x1d,0x30,0xd2,0xe1,0x7a,0x0a,0x23,0x85,0x95,0x59,0x78,0x4d,0x9a, - 0x2a,0xd4,0x2a,0x2a,0x26,0x73,0x73,0xed,0xef,0xfe,0xbf,0xf6,0x2e,0x4f,0x84,0x92, + 0x2a,0xd4,0x2a,0x2a,0x26,0x73,0x73,0xed,0x1f,0xfe,0xbf,0xf6,0x2e,0x4f,0x84,0x92, 0xb3,0x05,0x89,0x49,0x08,0x3f,0x13,0xe5,0x6d,0x91,0xa4,0x85,0x08,0x84,0x82,0x42, - 0x80,0xd8,0xa2,0x48,0x67,0x88,0xfd,0x06,0xa2,0xbb,0xf8,0xf7,0x02,0xd7,0x63,0x98, - 0xa9,0xf0,0x12,0x66,0x6a,0x72,0xae,0x84,0x31,0x0b,0x1a,0x10,0x99,0x38,0xa9,0x60, - 0xd9,0x22,0x25,0x56,0xc1,0xa5,0x68,0xfa,0xd8,0x6a,0x57,0xf8,0x39,0x12,0x06,0x84, - 0xc4,0xc5,0x5c,0x04,0x89,0xf0,0xd3,0x44,0x4c,0xe2,0x40,0xc1,0xc2,0x05,0x49,0xa8, - 0x07,0x47,0x8a,0xcd,0xa1,0x42,0xfe,0xa0,0x21,0xcd,0xa4,0x2a,0x96,0xb9,0x6c,0x43, - 0x2b,0xf9,0x8e,0xc9,0x3a,0xc4,0xb9,0x94,0x99,0x12,0x64,0x54,0x2f,0xd2,0x49,0x30, - 0xa6,0x58,0xe1,0xd2,0x17,0x2f,0x09,0x2b,0x8c,0x54,0xf3,0x74,0x85,0x95,0xe7,0x11, - 0x96,0x65,0x5c,0x34,0xb0,0xcb,0x74,0x29,0xa0,0xee,0x22,0x4a,0x44,0x1e,0x00,0x5a, - 0x0e,0x90,0x40,0x24,0x50,0xe7,0xb4,0x3b,0xba,0x0b,0x9f,0x48,0x9b,0xc2,0x9e,0xe3, - 0x60,0x0c,0x2e,0x11,0x62,0x51,0x32,0xc5,0xc8,0xa8,0xf0,0x8d,0x05,0xce,0x56,0x77, - 0x73,0x96,0xac,0xfb,0xc6,0xeb,0x15,0x69,0xc6,0x6e,0x77,0xdd,0x8d,0xeb,0x1b,0x87, - 0x95,0x0b,0xe5,0xef,0xdb,0x8c,0xd0,0xb5,0x59,0xfa,0x2b,0x8c,0x2b,0xf4,0xc4,0x2e, - 0xff,0x19,0x36,0xd6,0xf5,0x8e,0xb4,0x3e,0x14,0x5c,0x6d,0xda,0x5a,0xeb,0x48,0x78, - 0xc4,0x5d,0x35,0x67,0xc3,0x2b,0x3f,0xbc,0x0b,0x8a,0xd6,0x8b,0x51,0x14,0xd3,0x3f, - 0xda,0xb4,0x91,0x16,0xc0,0x0e,0x4b,0xbf,0x45,0x5d,0x6f,0x34,0xfe,0x25,0x3e,0x95, - 0xdd,0x64,0x10,0x95,0xe5,0x6c,0xbb,0x36,0xb1,0xba,0x5c,0xd1,0xe3,0x33,0x6c,0xa9, - 0xc6,0xa8,0x9a,0x0a,0x25,0xb9,0xb3,0x0d,0x1e,0x72,0x44,0x3b,0x0f,0xc2,0x74,0x05, - 0x07,0x81,0xec,0x5d,0xf4,0xf8,0x3f,0xda,0xd7,0x9a,0xa5,0x26,0xd8,0xd0,0x6d,0x79, - 0x27,0x59,0xf2,0x8f,0xb7,0x46,0x3b,0x25,0x65,0x5c,0x6b,0x77,0x54,0x97,0xf9,0x07, - 0x8e,0x58,0xd0,0x7a,0xbe,0xcc,0xeb,0x74,0xb0,0x18,0x55,0xe6,0xf1,0x69,0x4e,0x90, - 0x12,0xa9,0x94,0xbd,0x02,0x0e,0xb2,0xaa,0x4a,0x63,0x34,0x07,0xfc,0x39,0x35,0x0a, - 0x4c,0xaa,0x1b,0x88,0x39,0x69,0x6a,0x50,0xf0,0x1d,0x0e,0x85,0x34,0x72,0x61,0x0a, - 0x33,0xd4,0xfc,0xa1,0xef,0x29,0x11,0xa7,0x50,0x37,0xe4,0x0c,0x80,0xab,0xf0,0x17, - 0xf8,0xd8,0xda,0xb0,0x2a,0xf3,0x00,0x26,0x08,0x40,0xd2,0xd4,0x2a,0xfb,0x38,0x8f, - 0xbe,0x4c,0xd9,0x7b,0x66,0xe7,0x00,0x50,0xea,0x2d,0x29,0x52,0x02,0x9b,0x65,0x6f, - 0x74,0x2e,0xbe,0xc2,0xc7,0xe8,0x38,0xe2,0x90,0x08,0xce,0xaa,0xc2,0x88,0x0d,0x98, - 0x78,0x43,0x67,0xa5,0x1a,0x6d,0x7f,0x84,0x5b,0x06,0x79,0x72,0x98,0xd7,0x82,0x02, - 0x4d,0xc8,0xba,0xcc,0x99,0x40,0xbe,0xff,0xc3,0x51,0xeb,0x6f,0xec,0x8d,0x0c,0x09, - 0x27,0x45,0x1e,0xab,0xab,0x75,0x43,0x58,0x33,0x80,0x47,0x77,0x35,0x80,0x0c,0xeb, - 0x6b,0x22,0xd5,0x31,0x14,0x93,0x82,0x85,0xb5,0x74,0x84,0xd8,0xe7,0x2c,0xf0,0xe5, - 0x36,0xf6,0xe0,0x0b,0x02,0x58,0xcd,0x86,0x4e,0x8e,0xe0,0xe7,0x73,0x45,0x6d,0x1d, - 0xfd,0x0a,0xd4,0x00,0x9e,0xb5,0xe8,0x4c,0xe6,0x51,0x1c,0x5e,0xd5,0x07,0xf7,0xf4, - 0xc8,0x0e,0x7b,0xb8,0xbb,0x0a,0xe0,0x7a,0x18,0xb6,0x53,0x20,0x29,0xad,0x18,0xd6, - 0x25,0xbd,0x42,0x0a,0xbe,0x34,0x5b,0x16,0xef,0xa9,0x0a,0x39,0x02,0x7d,0x66,0xf2, - 0x43,0x9b,0x15,0x64,0xe3,0xb2,0x8e,0xd5,0xb5,0xe7,0xb1,0x61,0x32,0x49,0x4d,0x00, - 0x5a,0x42,0x7c,0x93,0x89,0x64,0xf0,0x65,0xe2,0xbc,0x7e,0x7d,0x57,0xe8,0xb9,0x25, - 0xca,0x26,0xe7,0x9b,0x2e,0x0b,0x12,0x0a,0x3d,0x77,0xcd,0x9d,0x04,0x13,0xa2,0xfd, - 0xa6,0xb1,0x2d,0xd2,0xe5,0x64,0xde,0xa1,0x50,0x17,0x1e,0x81,0x26,0xda,0x00,0xfa, - 0xa6,0xed,0x0e,0x06,0x16,0x63,0x33,0xaa,0x98,0x2f,0x17,0xe3,0x1d,0x44,0xd8,0x3e, - 0x58,0x93,0xe6,0xae,0x84,0x30,0xd5,0x00,0x27,0xdb,0xe0,0xcf,0x75,0x12,0x1c,0xf7, - 0xbe,0x1d,0xae,0x27,0x47,0x1b,0xee,0x86,0xd2,0x91,0x9a,0x56,0x32,0x15,0xd6,0x88, - 0xe5,0x38,0xc9,0x52,0x01,0xe0,0x11,0xeb,0x19,0xc6,0x8d,0x04,0xd2,0xb9,0xc3,0xe7, - 0xd1,0xe9,0xa6,0x39,0x9a,0x5c,0xeb,0x29,0x47,0xdf,0x3f,0x68,0x6d,0xaf,0x30,0xdc, - 0x91,0x8b,0x8b,0xf4,0x2f,0x1d,0xfe,0x72,0x1b,0x07,0xd7,0x06,0x6a,0x74,0xee,0xcc, - 0x95,0xaf,0x65,0x08,0xef,0x86,0xb7,0x83,0x28,0xb8,0x0a,0xdb,0x66,0x79,0x14,0x0e, - 0xe9,0xaf,0x0e,0x8c,0x0b,0xae,0x14,0x92,0xe4,0x7b,0xb9,0x48,0xd4,0xa0,0x3f,0xcd, - 0x05,0x7e,0xd8,0xd2,0xf4,0xb8,0x48,0xb0,0x1e,0xc3,0x5e,0x5b,0x78,0x57,0x36,0x12, - 0x23,0x53,0x88,0x38,0xe3,0xd1,0x5d,0xad,0x38,0x4d,0x16,0xfe,0x57,0x05,0xb2,0x9f, - 0x6d,0x63,0x79,0xc9,0x3b,0xf9,0xdb,0x83,0x8d,0x78,0x87,0x89,0xee,0x50,0xe1,0xc8, - 0x09,0xe4,0x91,0x64,0x80,0x7a,0xec,0x70,0xa0,0x71,0x94,0x4c,0x96,0xa9,0xe5,0x60, - 0x1e,0x85,0x21,0xfc,0x0f,0xe3,0x5a,0x5e,0x94,0x71,0x1c,0x65,0x2a,0x52,0x0e,0x5a, - 0xe3,0x20,0xbf,0x32,0x72,0x70,0xb8,0xad,0xf2,0x65,0x8c,0xd7,0x66,0x5c,0x05,0xcf, - 0xfe,0x0e,0xe1,0x8b,0x0c,0x59,0xe5,0xc4,0x38,0x4e,0x27,0xe7,0x94,0x43,0x21,0x7d, - 0x01,0xce,0x1c,0xde,0x20,0x9c,0x59,0xc0,0x3b,0x72,0x26,0x17,0x25,0x08,0x99,0x70, - 0x81,0x8b,0xfb,0x54,0x10,0xe7,0xd8,0x29,0x09,0xf5,0x5e,0xca,0x61,0x71,0x9a,0x9e, - 0x23,0x33,0x43,0x5a,0x17,0x8c,0x63,0xce,0x96,0x28,0x46,0x5a,0x60,0x33,0xf8,0x96, - 0x4e,0x19,0x3a,0x86,0xe2,0x13,0xe5,0x4a,0xb0,0x9d,0xca,0x27,0x34,0x4c,0xea,0xb7, - 0x1c,0x77,0xc8,0x96,0x12,0x1a,0x9c,0x52,0xeb,0x60,0x2d,0x91,0x26,0x54,0x5b,0xa5, - 0x39,0xd2,0x3f,0x15,0x5c,0xd2,0x97,0x19,0xc2,0x33,0x13,0x69,0x05,0x5a,0xa4,0xac, - 0x84,0xf2,0x56,0x34,0xe5,0x42,0x39,0x49,0x91,0xae,0x59,0x33,0x6b,0x38,0x63,0xf6, - 0x72,0x5d,0x4e,0xd5,0x41,0x36,0xf1,0x78,0x5b,0x7c,0x59,0x1f,0x43,0xd9,0xfd,0x3a, - 0x83,0x1d,0x7e,0x94,0xc9,0xbf,0x93,0xaa,0xd4,0x0a,0x0c,0x47,0xc7,0xdb,0xe2,0x61, - 0x87,0x3f,0xc7,0x3b,0x25,0x84,0xce,0x1f,0xea,0xac,0x17,0xd1,0xda,0xae,0xab,0x1a, - 0xf3,0xb7,0xc6,0x7e,0xf7,0x76,0x54,0x43,0x1d,0x73,0xcb,0x03,0x85,0x7f,0xa8,0xd6, - 0x42,0xbe,0x5f,0xb2,0x10,0xea,0x0d,0xcb,0x9e,0x24,0xd8,0x79,0x53,0x8b,0x24,0x42, - 0xea,0x22,0x8a,0x99,0x33,0x63,0x58,0x38,0x85,0x3b,0x59,0x0e,0xd2,0x50,0xf4,0xbb, - 0x11,0x05,0x6a,0xfe,0xdc,0x5b,0x66,0x61,0x89,0x27,0xf3,0xc1,0x35,0x0b,0x55,0x00, - 0xb7,0xa1,0xf6,0x8c,0x68,0x69,0x29,0xfa,0x36,0x69,0x73,0xab,0xe2,0x86,0xce,0x87, - 0xdb,0xd4,0x6e,0xab,0x08,0xd8,0xc9,0x35,0x7c,0xb6,0x86,0x79,0x1c,0x30,0x99,0x58, - 0x9c,0xb6,0x20,0xfc,0x22,0x98,0x7d,0xa9,0xe1,0xe1,0xfc,0x74,0x23,0x10,0xb8,0x35, - 0x61,0xdd,0x34,0x11,0x16,0x1b,0x93,0x18,0x3a,0xcc,0xcd,0x67,0xe3,0xa0,0x79,0x70, - 0x7c,0xdc,0x7e,0x74,0xd0,0x3e,0x3a,0x69,0xfb,0xbd,0x93,0x56,0x85,0xfa,0x2c,0xb5, - 0xa1,0x5c,0x2c,0xa7,0xc5,0x80,0x4a,0xf8,0xc3,0xaf,0x48,0x20,0xb6,0xa0,0x55,0x13, - 0x9d,0x17,0x32,0xa0,0xda,0x8b,0x20,0x09,0xf9,0xfb,0x17,0xaa,0x4c,0x36,0x41,0xc8, - 0xb8,0xc9,0xc4,0x61,0x59,0x79,0x98,0x76,0xce,0x61,0xb2,0x9c,0x93,0x0c,0xad,0x4d, - 0x5f,0x58,0x8b,0xb8,0xb9,0x14,0x6d,0x79,0xbe,0xdb,0x0a,0x98,0x60,0x7b,0x03,0x3b, - 0xb1,0x2f,0xa8,0x58,0xbd,0xfd,0x34,0x84,0xee,0xdc,0xc5,0x83,0x62,0x9c,0x8e,0xef, - 0xd3,0x69,0x87,0xc2,0x8d,0xad,0x11,0x3e,0x96,0x74,0x53,0x0a,0x4d,0x8d,0x2c,0x35, - 0xf6,0x22,0x97,0x70,0xfb,0x88,0x9a,0x2a,0xd5,0xa4,0x7a,0xca,0x23,0x8a,0x29,0xf8, - 0x53,0xef,0x4e,0x87,0x3a,0xdb,0xe8,0x60,0xc4,0x6b,0xab,0xe2,0x6e,0xb8,0xc6,0x9d, - 0x45,0x15,0x6d,0xb4,0xf5,0x1e,0x76,0xd4,0x4f,0x36,0x87,0xf0,0xa6,0xd9,0x5b,0x5e, - 0xed,0x2a,0xaa,0xd8,0xfb,0x25,0x21,0x82,0x31,0xb6,0x85,0x01,0x43,0x4e,0x9d,0x07, - 0x1d,0xca,0xae,0xd8,0x84,0xc3,0xfa,0x56,0xac,0xe7,0x4f,0x14,0x28,0xfd,0x8f,0x66, - 0x07,0x77,0xea,0x67,0x02,0x6e,0x72,0x51,0x33,0x69,0xc7,0x07,0x6b,0xa9,0xd3,0xe1, - 0xd6,0x0a,0xf6,0xd1,0x7a,0x05,0x7b,0x68,0xc2,0xb7,0x8e,0xbc,0x40,0xee,0xa1,0x34, - 0xed,0x6e,0xa6,0x95,0xa1,0x43,0xa7,0x70,0x3d,0xd7,0x83,0xb5,0xe5,0x8f,0xb7,0x2e, - 0xdf,0xdf,0x28,0xa0,0x1f,0x0e,0xb7,0x7a,0x47,0xbd,0x84,0xaf,0x52,0xc4,0x1a,0xd5, - 0x7a,0xbb,0x29,0x4d,0xf7,0x29,0xfb,0xb4,0xa7,0x90,0x87,0x27,0x93,0xb9,0xeb,0x36, - 0x0e,0xb7,0x1b,0xa6,0x35,0x97,0xb0,0xee,0x7a,0xd7,0x51,0x69,0x6f,0xc5,0x2b,0xbc, - 0xda,0x11,0x09,0x92,0xfd,0x5f,0x3b,0x46,0xb9,0xb3,0xa1,0xd8,0xe9,0x14,0x6e,0x35, - 0x14,0x3b,0x9c,0xa3,0xb1,0x19,0x5b,0x88,0x60,0xd5,0x12,0x14,0x10,0x0f,0xe9,0xb8, - 0xae,0x0a,0xc4,0xd9,0xf8,0x33,0x2e,0x10,0xf9,0x22,0x42,0x42,0x63,0x8a,0x3f,0xa6, - 0x0a,0x30,0x4d,0x53,0x60,0x58,0x33,0x92,0xe4,0x71,0x3f,0xa3,0x62,0xc2,0x5e,0xf3, - 0x64,0xdd,0x4b,0x7f,0x61,0x41,0xf7,0x33,0x43,0x75,0x8d,0xbd,0x08,0xae,0xee,0x68, - 0x53,0xdc,0x39,0x37,0x18,0x8a,0x3f,0x2c,0x10,0x25,0x07,0xcd,0xb5,0x23,0xf1,0xd6, - 0x15,0x9f,0xec,0x97,0x36,0xf0,0xc0,0x84,0x27,0xe2,0xd8,0xd0,0x92,0xff,0x54,0x56, - 0xa3,0xda,0xef,0x11,0x0d,0xb0,0x36,0xd6,0x16,0xb3,0x72,0xd3,0x73,0xe2,0xcc,0x35, - 0x61,0xfa,0xf7,0x52,0x9d,0x13,0x27,0x38,0xe6,0x5a,0xcd,0xa9,0xd8,0x68,0x8b,0x8e, - 0x10,0x5a,0x3e,0x1c,0xe6,0x83,0x5d,0x3e,0x11,0xe6,0x02,0xa6,0xc8,0x22,0x84,0x6c, - 0xa1,0x20,0x6e,0xb7,0xd9,0x07,0x6b,0x90,0xbc,0x06,0x0f,0x8b,0x12,0x55,0xd0,0x61, - 0x05,0x87,0xe4,0xd9,0x9c,0x80,0xe8,0x2c,0x4e,0xc0,0xc4,0x82,0x40,0x08,0xb0,0x11, - 0xbb,0x23,0xc1,0x2b,0x2e,0x05,0xf3,0xd7,0x84,0xda,0x96,0x14,0x10,0x0e,0x4d,0x8a, - 0x47,0x9a,0x14,0xd5,0x76,0xb9,0x91,0xa0,0x22,0x55,0xbf,0x7f,0xe0,0x6e,0xc9,0xa7, - 0xcc,0xf1,0x2e,0xb9,0x24,0xcc,0x0d,0x48,0x89,0x75,0x00,0xaa,0xd9,0x6b,0x23,0xad, - 0x6c,0xe9,0xc3,0x02,0x92,0x5d,0x37,0x5c,0x43,0x2c,0x9c,0x17,0x15,0x7c,0xda,0x72, - 0x65,0xa0,0x21,0xa8,0x93,0xf3,0x4b,0xb6,0xc9,0x47,0x47,0x2e,0x1a,0xeb,0xa1,0x40, - 0xef,0xfa,0x7a,0xef,0xb4,0xab,0x5b,0x85,0x4e,0xbb,0xdc,0xcd,0x76,0x4a,0xec,0x3d, - 0xdb,0xc3,0x9f,0xd3,0x05,0x24,0x4c,0x9f,0x37,0x8d,0x1a,0xbc,0xc1,0x06,0xf5,0x13, - 0x9d,0xea,0xee,0x87,0xb3,0x53,0x10,0xa8,0xbc,0x1d,0xa6,0x05,0xf7,0xb4,0xe1,0xd2, - 0xd9,0xe9,0xbc,0x7f,0xf6,0x4c,0x73,0x0a,0x30,0xfb,0xf5,0x81,0x48,0x14,0x1a,0x67, - 0xfa,0xec,0xde,0x0e,0xef,0x1a,0x80,0x14,0x4a,0x9d,0x86,0xd1,0x85,0x1d,0x4a,0x14, - 0xd2,0x2b,0x3a,0x17,0x69,0xa3,0x1b,0x17,0xb5,0x20,0xf0,0xe5,0xfa,0x0d,0x73,0x48, - 0x6f,0xee,0xe0,0x9e,0x8b,0x0a,0x9f,0xbb,0x37,0xce,0x7e,0x4a,0x57,0x82,0xd8,0x02, - 0xe6,0x1b,0x8c,0xec,0xe0,0x35,0x40,0x08,0x21,0x4a,0x40,0xb8,0x3b,0x5e,0xc2,0xcb, - 0xd7,0x80,0x8d,0x8b,0xa4,0x21,0xa2,0x90,0x88,0x55,0x50,0xdd,0xfc,0xa2,0x21,0xb8, - 0x09,0x6f,0xd4,0x78,0x8d,0x2f,0x51,0x4a,0x2d,0x0c,0xba,0x43,0x0f,0x37,0xa8,0xe4, - 0xd1,0xd0,0x30,0x1a,0x67,0xf7,0xe3,0x20,0xcf,0x87,0xa7,0x5d,0xfd,0xfd,0xee,0x8b, - 0x24,0xd0,0xee,0x72,0x91,0x9f,0xf0,0x65,0xe7,0x02,0xf9,0xb6,0x05,0x4e,0xbb,0xd8, - 0xa2,0xa1,0x9a,0xf3,0xb1,0xbe,0x6f,0xbd,0x1a,0x7d,0x38,0xbb,0xbf,0x08,0x03,0x35, - 0x1f,0xee,0x1e,0x4b,0x87,0xc0,0xe5,0x78,0x66,0xf5,0x1d,0xc7,0xc2,0xc0,0x4d,0x1b, - 0x42,0x87,0x38,0x76,0xce,0xc6,0xa4,0x6c,0xb5,0x83,0x93,0x7c,0x58,0xd0,0x38,0xe3, - 0x63,0xe2,0xdd,0x3c,0xb4,0x47,0x71,0xbb,0x99,0xc8,0x23,0x0c,0x75,0x3b,0xe9,0x06, - 0x11,0x7f,0x4e,0x6e,0xe5,0x50,0x0d,0x82,0x8a,0xa5,0xcc,0xd6,0x81,0xbc,0xa5,0x8b, - 0x9f,0xcf,0x08,0x7b,0x1c,0x64,0x60,0xaf,0x72,0xfe,0xb2,0x8b,0xbc,0xe3,0x3c,0xba, - 0x91,0x52,0xd5,0x89,0xd1,0x1a,0xb9,0xb8,0x1e,0x67,0x30,0xe6,0x1a,0x5c,0xa3,0x9c, - 0x39,0x35,0x45,0x42,0x8d,0x01,0xad,0x40,0x76,0x6a,0xd4,0x38,0xc0,0xef,0xe0,0xe3, - 0xa8,0x81,0xe4,0xbb,0x21,0x60,0x5e,0x33,0x7c,0x6c,0xc0,0x59,0xc7,0x4b,0xc0,0x78, - 0x88,0x6b,0x41,0x1e,0x05,0xba,0x74,0x0e,0x45,0x60,0x3b,0x3d,0x2e,0x97,0xdf,0x8e, - 0x25,0x1f,0xf4,0x94,0xeb,0xe0,0x63,0x25,0x79,0x15,0xb6,0xbb,0xa4,0x84,0xcf,0x23, - 0xf4,0x64,0xfd,0x71,0x9b,0x2c,0x94,0xa7,0x22,0xae,0x30,0xb8,0x28,0x98,0x53,0x8d, - 0xc6,0x19,0x1f,0x00,0xfd,0x90,0xa4,0xe3,0x75,0x52,0x55,0x94,0x5f,0x33,0x46,0x65, - 0x19,0x74,0x17,0x6c,0x73,0x90,0xd0,0x38,0xfb,0xa1,0x5f,0x87,0xf9,0x39,0x0c,0x50, - 0x71,0xa7,0x67,0x38,0xd0,0xbb,0x89,0x03,0xc7,0xbd,0xc6,0xdf,0x14,0xe5,0x83,0xaf, - 0x42,0xb9,0xff,0xf7,0x40,0xf9,0xf0,0xab,0x50,0x3e,0xf8,0x7b,0xa0,0x7c,0xf4,0x55, - 0x28,0x1f,0x7e,0x39,0xca,0xbb,0xb4,0x8a,0xab,0xe1,0x5b,0x55,0x89,0xee,0x34,0xea, - 0x4e,0x9e,0x6d,0x31,0xfb,0x21,0xab,0x34,0xee,0x04,0xa3,0xd9,0xaa,0x93,0xd1,0xc4, - 0x9a,0x4f,0xd9,0x4d,0xbb,0xad,0x8b,0xbc,0x2d,0x10,0xd6,0xce,0xe4,0x4d,0x6b,0x4c, - 0xd5,0x36,0xb7,0xb5,0x61,0x2f,0x03,0x18,0x82,0xd3,0xa8,0x9c,0xa3,0xbf,0x77,0xa3, - 0xb3,0x2f,0x42,0xeb,0xd7,0xa8,0xf3,0x22,0xba,0x09,0xa9,0x55,0x34,0x8d,0xbe,0x6e, - 0xe7,0x4f,0xc2,0x30,0xaf,0xac,0xf7,0xd6,0x45,0xa2,0xec,0xeb,0x96,0x78,0x25,0x17, - 0x69,0x7e,0x79,0xd3,0x0a,0x88,0xdb,0xbe,0x72,0x8d,0x5f,0xb2,0x22,0x5a,0xdc,0xc8, - 0xbf,0xe5,0x57,0xae,0xf0,0x22,0xca,0x17,0xab,0x20,0xbf,0x59,0x46,0x56,0x37,0xae, - 0xf1,0x4d,0xa7,0x23,0x7e,0xe5,0xc6,0x28,0x7b,0x6a,0xc0,0x8d,0x57,0x3a,0xe5,0xc0, - 0x87,0x7c,0x99,0x24,0x08,0x1e,0xb9,0x97,0x6a,0x15,0x98,0xa3,0x04,0x2e,0xfb,0xeb, - 0xee,0xd5,0x2c,0x47,0xa2,0x86,0xbc,0xa2,0x94,0x3c,0xaa,0x7c,0x22,0xa7,0xd1,0x29, - 0x58,0xdb,0x34,0x5d,0x95,0xc5,0x7f,0x6a,0xaa,0x22,0x55,0x57,0xe9,0x42,0xea,0x03, - 0x00,0x38,0x1f,0xc4,0xe4,0x22,0x91,0x32,0x54,0x9c,0xc6,0xd8,0xe6,0x2f,0x17,0xa2, - 0xe9,0x03,0x9b,0x46,0xb9,0x2a,0xcc,0xc1,0x41,0x91,0x8a,0x73,0xc4,0x56,0x04,0x37, - 0x01,0x56,0x53,0x53,0x9c,0xa4,0x05,0xe4,0xc7,0x4c,0x4e,0xa8,0x69,0x0d,0xd3,0xe8, - 0x00,0x83,0x56,0xf6,0x5d,0x70,0x6f,0x83,0x85,0x34,0xa7,0x23,0xd8,0x12,0x35,0xd9, - 0x5e,0x6a,0xa0,0x32,0x56,0xb2,0xcd,0x18,0xe8,0x23,0x13,0x21,0x23,0x6e,0x2b,0x59, - 0x21,0xb1,0xa1,0xc4,0x0c,0xd4,0x37,0x64,0xaa,0x61,0x57,0xf0,0xc9,0x05,0xa1,0x54, - 0xcc,0x83,0xc2,0x0e,0xf1,0x14,0xf7,0xfa,0xb6,0x45,0x9a,0x98,0xa6,0xde,0x69,0xb0, - 0x8c,0x8b,0xfa,0x70,0x24,0x7a,0x73,0x19,0x4f,0xdb,0x2e,0xbc,0xb1,0x9c,0x04,0x4b, - 0x25,0x45,0x43,0xd3,0x2c,0xb0,0x87,0x26,0x8c,0x0a,0xd2,0x3a,0x0a,0x00,0x34,0x41, - 0xfe,0x7d,0x29,0x95,0x61,0x5a,0x09,0xdf,0x85,0x94,0x07,0x91,0xa2,0x23,0x95,0x4e, - 0xa7,0x64,0x78,0x50,0x13,0x29,0x23,0x24,0x32,0xd4,0x45,0x8f,0xf5,0xe7,0x42,0xb2, - 0xea,0x99,0x1b,0x7f,0x95,0xe6,0xe7,0x5d,0xb3,0x35,0xb5,0xcb,0xc8,0x43,0x28,0x9f, - 0xeb,0x21,0x1b,0x36,0x7e,0x9b,0x70,0xca,0xf0,0x2e,0x06,0xac,0x8c,0xa5,0x19,0xd1, - 0xf5,0xc8,0x9b,0xcc,0x7a,0xb0,0x16,0x5e,0x56,0x86,0x9e,0x05,0xfc,0x35,0x95,0xcf, - 0x94,0xed,0x07,0x7a,0xfb,0xf2,0xdd,0x73,0xdd,0xca,0xc8,0xa2,0xae,0xf3,0x35,0x30, - 0x2b,0x5d,0x25,0xa2,0xbb,0xe4,0xf3,0x91,0x81,0x66,0x64,0x06,0xfb,0xcb,0x87,0x5a, - 0x4c,0x6d,0x8b,0x5b,0x2e,0x63,0xaa,0xb2,0xc1,0xfb,0x24,0xd1,0x14,0xf4,0x67,0x79, - 0xa1,0x86,0x23,0xcd,0x93,0x44,0xae,0x04,0x3f,0x7b,0xa4,0x19,0x8c,0x15,0xc6,0x29, - 0xb5,0x7c,0x73,0x41,0x92,0xad,0x27,0x1d,0xc4,0xed,0x55,0xba,0x92,0x4b,0x2d,0xa8, - 0xc2,0x1f,0xd3,0xd9,0x5a,0x0a,0x31,0x27,0x01,0xc4,0x4f,0x92,0x96,0xb2,0x1e,0x02, - 0x3b,0x64,0xf1,0xa1,0x18,0x5f,0xf2,0x5a,0x96,0xa7,0xe0,0x27,0x51,0x06,0x68,0xdf, - 0xca,0x3b,0xbd,0x35,0x84,0xec,0x48,0x91,0xe9,0x51,0xae,0x3f,0x8f,0xe3,0x20,0x39, - 0x37,0xcf,0xd7,0x24,0x69,0x9a,0xc9,0x44,0xda,0x90,0xb1,0xc6,0xd4,0x22,0x98,0x35, - 0xce,0xf4,0xec,0x5a,0x8c,0xca,0x63,0xcc,0xea,0x54,0x8a,0xa9,0x92,0x64,0x77,0xf6, - 0x2c,0x6d,0x9c,0xcd,0xa1,0xa8,0xd8,0x84,0x86,0x21,0x6c,0xbe,0x66,0x32,0xe4,0x60, - 0x8d,0x63,0x0e,0xe7,0x29,0xa5,0xd7,0x09,0x71,0x2d,0xce,0xb7,0xc5,0xf3,0x0d,0x4f, - 0x4c,0x23,0x91,0xc9,0x1a,0xfc,0x02,0x67,0x02,0x05,0x26,0x86,0x40,0x96,0x32,0x3b, - 0xe2,0x73,0x5b,0xdc,0x69,0x9c,0xf5,0xfa,0x37,0xe4,0x5b,0xa6,0xdc,0x59,0xa2,0x00, - 0x7b,0x31,0xc6,0xbe,0x2e,0x77,0x9a,0x70,0x5b,0x88,0x6c,0x9c,0x7d,0x9f,0xa7,0x19, - 0x95,0x62,0x02,0x24,0x58,0x67,0x7e,0x36,0x5d,0x20,0x63,0x3a,0x13,0x8b,0x34,0x5c, - 0x52,0xf9,0x27,0xc7,0x75,0x5d,0x11,0xa2,0xbb,0x7f,0x89,0x32,0xbe,0x9b,0x05,0x30, - 0x56,0x46,0x55,0x38,0xa3,0x87,0x99,0xca,0x39,0x54,0x6a,0x43,0x82,0x63,0x59,0x48, - 0xbf,0x16,0xe8,0x18,0xa2,0x32,0x31,0xf6,0x4a,0x3b,0x0f,0xea,0x93,0x14,0x1a,0x63, - 0x69,0x3a,0x72,0xa7,0xc6,0x8b,0x90,0xb5,0xca,0x23,0xa9,0xda,0x5c,0x7f,0xa2,0x4e, - 0x07,0x19,0x4c,0xe6,0x7c,0x96,0xab,0xcf,0x7c,0x30,0xc6,0x8a,0x2d,0x69,0xcf,0x2a, - 0x8f,0xb0,0x73,0x48,0x40,0x62,0x05,0x9d,0xf4,0x85,0x4b,0x5a,0x97,0x84,0x1c,0xe5, - 0x16,0xd3,0x3c,0x5d,0x88,0x6e,0x90,0x45,0x5d,0xf3,0x98,0x83,0x59,0xfa,0xa7,0xe0, - 0xa2,0x5d,0x1d,0x16,0x24,0x45,0x1e,0x8d,0xa9,0xa4,0x47,0xd2,0x4d,0x3a,0x44,0x92, - 0x8e,0x6f,0x16,0x4f,0x3e,0xb9,0x0e,0xb4,0x3a,0xea,0xfe,0xbc,0x5c,0x37,0xe7,0xe9, - 0x07,0x24,0xe0,0xaa,0xf3,0xd0,0x42,0x23,0xcf,0x54,0x44,0xf0,0x4c,0x90,0x71,0x72, - 0x57,0xb4,0x0f,0x6d,0xd0,0xc7,0xcb,0x28,0x0e,0x75,0xbb,0x31,0x15,0x0a,0xf5,0x00, - 0xa8,0x1d,0x1d,0xb5,0x65,0xb4,0x4d,0xb3,0x03,0x0b,0x09,0x26,0x38,0x24,0xa9,0x25, - 0x75,0xeb,0xd0,0x66,0x0d,0x19,0xa6,0x60,0x51,0x97,0x5a,0x8a,0xdb,0xb6,0xf3,0x19, - 0xe0,0xbb,0x2b,0xa9,0x5b,0x88,0x3b,0x20,0x63,0x16,0x8c,0xa3,0x38,0x2a,0x2e,0x3b, - 0x16,0xd4,0x0c,0x84,0x0f,0x6d,0x7b,0xe1,0x25,0x18,0x36,0xcb,0x69,0x83,0x6d,0xe0, - 0x54,0xf0,0xd3,0x91,0xa1,0x96,0x07,0xde,0x18,0x6f,0x93,0x7c,0xab,0x6d,0x7d,0x6e, - 0x57,0x87,0xf2,0xc2,0xf1,0x83,0xd6,0xe5,0x95,0x64,0xe1,0x63,0xf8,0x30,0x62,0xbb, - 0xa1,0x9d,0x63,0x30,0x4e,0xb1,0x00,0x9f,0xe5,0x86,0xba,0x17,0x92,0x2c,0x21,0xa8, - 0x6c,0xa1,0xcd,0x83,0x0b,0xed,0x84,0x35,0x9d,0xa8,0x5f,0x3a,0x2e,0x4d,0xcb,0x76, - 0x9d,0x63,0x8d,0xa7,0xb5,0x7f,0x88,0xd8,0x61,0xe8,0xb2,0x86,0x39,0x1c,0x5c,0x53, - 0xc4,0x72,0xec,0x1b,0x52,0xe4,0xaa,0x04,0x52,0x8a,0xe5,0x0e,0xb5,0x7e,0x2b,0x8b, - 0x65,0xf6,0x59,0x3a,0xad,0xa3,0xce,0x5b,0xf4,0xf9,0xf0,0x2e,0xfa,0x6c,0x02,0xdc, - 0x5b,0xb5,0xf8,0x8d,0x5c,0xc8,0xc5,0x98,0xda,0x26,0x49,0x08,0x83,0x18,0xc2,0x58, - 0x90,0x9d,0x55,0xa5,0x9a,0x3a,0x4f,0xf2,0xfc,0x96,0xd2,0xf3,0x6d,0x1c,0x0c,0xb1, - 0xc8,0x92,0xfc,0x21,0x0e,0x20,0x4b,0xaf,0x6e,0x56,0xdc,0x1d,0x14,0xfa,0xde,0xad, - 0x32,0xde,0x8d,0x44,0xc6,0xfe,0xdf,0x46,0xa4,0xe3,0xbb,0x10,0xc9,0x46,0x9e,0xc2, - 0xba,0x85,0xbb,0x19,0xbd,0x40,0xfb,0xb8,0x2d,0x04,0x9a,0x62,0xfc,0x9c,0x1b,0x0e, - 0x14,0xc2,0x21,0xed,0x2c,0xe9,0xf6,0x8f,0x4f,0x7e,0x62,0xfd,0xca,0xe5,0x18,0x11, - 0x25,0x42,0x19,0x6b,0xf1,0xf9,0x2a,0xf3,0x8a,0x03,0xa4,0x65,0xc2,0x0d,0x78,0x32, - 0xdc,0x42,0xcd,0x1b,0x68,0xa2,0xad,0xd2,0xad,0x34,0x39,0xb9,0x0b,0x4d,0xde,0x32, - 0xac,0x3b,0x90,0xe2,0x05,0x35,0xdc,0xd2,0x23,0x07,0xd0,0xec,0x05,0x27,0x22,0x30, - 0x0d,0x3a,0xdb,0xd3,0x6e,0x5f,0x0b,0x52,0x49,0xa6,0xd5,0xdc,0x44,0x2e,0x86,0x56, - 0xd4,0xd1,0x33,0x29,0x96,0x50,0x55,0x98,0x92,0x94,0xa2,0xf3,0x1b,0x25,0x68,0x4b, - 0x50,0xa4,0xa3,0xf3,0xb3,0xca,0x83,0xeb,0x7d,0xd6,0xbc,0x7b,0x70,0x4b,0x3c,0x71, - 0x6b,0x20,0xb1,0x3e,0x81,0x30,0x3b,0xed,0x9a,0xa5,0xf7,0x4e,0xbb,0x54,0x81,0x07, - 0x3a,0xa7,0xf6,0x59,0xf1,0xe9,0x32,0xe1,0xee,0x49,0xf1,0x4f,0xcd,0xa8,0x75,0x95, - 0xc3,0x04,0xc0,0xab,0x86,0xe9,0x64,0x49,0x8f,0xa0,0xf8,0x58,0xe9,0x79,0x2c,0xe9, - 0xe3,0xd3,0xcb,0x97,0x21,0x46,0x5c,0xef,0xfd,0x53,0xd3,0x23,0xbc,0xbd,0x96,0x4f, - 0xd1,0xc7,0x33,0xf3,0x50,0x39,0xe2,0x58,0x3e,0x0d,0xf2,0xe9,0x1e,0xd9,0xd6,0xe1, - 0xde,0x5e,0xb7,0x6b,0x9b,0x18,0x7e,0xd5,0xb4,0xa4,0xa8,0x8c,0xdd,0x41,0x49,0x48, - 0x72,0x27,0xff,0x7f,0x9f,0xa9,0x01,0x12,0xb5,0x16,0x0a,0x12,0xdb,0xdd,0x7e,0xd4, - 0xdf,0xea,0xae,0x65,0xe9,0xad,0x09,0x9c,0x71,0xd8,0x20,0x3c,0x3d,0x57,0x4f,0xb7, - 0xa3,0x9c,0x27,0x2b,0x72,0x27,0xdb,0xdd,0xae,0x39,0x51,0x2a,0xe6,0x6d,0xfd,0x4c, - 0x0b,0x7b,0x7b,0x02,0xc6,0x79,0x5d,0x95,0x80,0x49,0x8b,0xac,0xd2,0x0e,0x52,0x18, - 0x92,0x15,0xda,0xa7,0x53,0x7c,0xec,0x8b,0x27,0x5b,0x9d,0x2a,0x41,0xb3,0x7e,0xd5, - 0x66,0x53,0x37,0xfa,0xee,0x6d,0x4e,0x5b,0x3b,0x64,0x02,0x65,0xfd,0x30,0xeb,0x44, - 0x95,0x08,0xd1,0x83,0x44,0xba,0x49,0xa6,0x72,0xcb,0x9c,0xca,0x95,0x8e,0xd1,0x76, - 0xaa,0x05,0xf9,0x39,0x10,0x21,0x50,0x3a,0x52,0x0a,0x75,0xd2,0xc8,0xd8,0x94,0xbe, - 0x5a,0x67,0xb7,0x63,0x05,0x11,0xb2,0x59,0xeb,0x42,0x06,0x49,0x51,0xf3,0xca,0x10, - 0x27,0xa8,0x1b,0x41,0x2a,0x79,0xa2,0xb7,0x47,0x41,0xbf,0xb9,0xe4,0x69,0xfe,0xd1, - 0x56,0x8a,0x4d,0xdf,0x8a,0xb9,0x34,0xfd,0x8d,0x74,0x68,0x09,0x05,0x08,0xe8,0xe1, - 0x2f,0x88,0xbe,0xde,0x80,0x61,0x5a,0x93,0xc2,0x06,0xa0,0x98,0x10,0xd3,0x41,0xbd, - 0x15,0x29,0x7f,0x50,0x70,0x74,0xd4,0x6a,0x13,0xfe,0x09,0xc1,0x72,0xf2,0xd7,0x5c, - 0x26,0x4b,0xf2,0x49,0xca,0x3e,0xc9,0xc0,0x21,0x81,0x26,0xad,0xce,0x63,0x7a,0xfd, - 0xb6,0xe8,0x1d,0xe0,0xe7,0xd0,0xaf,0x14,0x8e,0xd6,0x69,0x2a,0x3e,0xf1,0xbb,0xa0, - 0x06,0xb4,0x36,0x6c,0xc7,0xa8,0xa9,0xee,0xdf,0x57,0x7e,0x25,0x38,0xad,0x4f,0x9f, - 0xde,0x7f,0x18,0x9a,0x21,0xd3,0x11,0x94,0xcf,0x3a,0x76,0xaf,0xd5,0x9e,0x9e,0xdb, - 0x0b,0x14,0x15,0x78,0xdc,0xfc,0x10,0x4d,0x9b,0x53,0x73,0x8a,0x38,0xf5,0xe9,0xf0, - 0x32,0xff,0x97,0x77,0xaf,0x7e,0x1c,0x79,0x9e,0x7d,0x5a,0x32,0x6f,0x46,0xa3,0xde, - 0x30,0x22,0xd7,0xe5,0xc7,0x32,0x99,0x15,0xf3,0x61,0xb4,0xbf,0x5f,0x1e,0x3c,0xd2, - 0x3a,0x72,0x84,0x9b,0xef,0xa3,0x0f,0x43,0x00,0xfb,0x46,0x7e,0xfa,0x24,0xcd,0xc0, - 0xd3,0x83,0x16,0x85,0x8d,0x51,0xb2,0x2c,0xcf,0xd4,0x69,0x78,0x30,0x2a,0xed,0xc5, - 0x04,0xfb,0x2d,0xa4,0x31,0x19,0x4d,0x2f,0xf0,0xca,0x86,0x8c,0xc0,0x67,0x8b,0xfc, - 0x13,0xbd,0x94,0xc2,0xd3,0x56,0xd9,0x1b,0x06,0x3e,0x5b,0x3b,0xf9,0xbe,0xf7,0xa1, - 0x1a,0xe7,0x20,0xbd,0xcb,0x33,0xf4,0x8c,0xcd,0xf4,0xf6,0xcb,0xd4,0xd5,0xdb,0xee, - 0x1e,0x4c,0xf5,0x65,0xab,0x47,0xd0,0xf7,0xbc,0x6a,0x65,0x24,0xf6,0xf9,0xe5,0x5b, - 0x48,0xeb,0x04,0x7e,0xa1,0xe9,0x95,0xdd,0x15,0x6b,0xb6,0x4e,0xbe,0xef,0x7f,0xb8, - 0x75,0x52,0xb8,0x31,0xe9,0xe0,0xc3,0xa7,0x4f,0x5e,0xb9,0xda,0xd4,0xa7,0x2e,0xf1, - 0x24,0x7c,0x46,0x0f,0x07,0x34,0x03,0x43,0xa7,0x6b,0xc3,0x38,0x1d,0xb3,0x8d,0xa6, - 0xe7,0xf6,0xd3,0x37,0xd3,0x1d,0x0b,0x69,0x0a,0x5f,0x1b,0x09,0x61,0x02,0xa9,0x8a, - 0x1f,0xb5,0x39,0x4f,0xe2,0x18,0xd3,0x58,0x6d,0xcb,0xc3,0x74,0x3d,0xbd,0x92,0x0a, - 0x0d,0xc0,0x15,0x0c,0xc6,0x48,0x5f,0x86,0x48,0xd4,0xf6,0xd4,0x8c,0xf6,0xfb,0xa7, - 0xfd,0xde,0x63,0xaf,0xe7,0x0d,0x3c,0xaf,0xb5,0x4f,0xdf,0x01,0xef,0x7a,0xaf,0x5c, - 0x3f,0x08,0xc3,0xe7,0xd4,0x3d,0xf3,0x23,0x34,0x82,0xfc,0x53,0x93,0x58,0xaf,0x15, - 0xcd,0x6b,0x5b,0x4d,0x68,0xca,0xd6,0x15,0x2b,0x83,0xf4,0x43,0x59,0x04,0x51,0xdc, - 0xba,0x06,0x14,0xc8,0xde,0x0a,0x81,0x1a,0x6c,0x52,0x36,0xd5,0x4e,0xde,0x8c,0x5a, - 0xbf,0x7a,0xed,0x7a,0x1a,0xf7,0x71,0xef,0x7f,0x90,0xc7,0x3a,0x2b,0x47,0xf4,0x33, - 0x75,0x18,0x68,0x2b,0xf3,0xaf,0x6f,0x7f,0xfe,0xa9,0x2d,0xfe,0x57,0xdf,0x7f,0x20, - 0x7e,0x78,0x6a,0x0c,0xd0,0xa1,0xd0,0xb5,0x38,0xc7,0xb4,0xa4,0xd6,0x60,0x62,0x3b, - 0x93,0x74,0x01,0xcb,0x47,0xa9,0x45,0x3a,0x25,0x68,0xf4,0x30,0xe4,0xbb,0x2a,0x3e, - 0xb1,0x0f,0xac,0xc3,0xf2,0xe9,0x67,0xd6,0x07,0x54,0xa0,0xa3,0xa0,0x87,0x0f,0x8e, - 0x23,0xd8,0x4a,0x88,0x9b,0x4e,0x45,0xe4,0x47,0xe2,0x47,0xc8,0xd6,0xdb,0x38,0xa2, - 0x10,0x86,0x9d,0xac,0x2a,0x6c,0x33,0x99,0x1e,0xed,0x8c,0x60,0xd9,0xe8,0xa9,0xaf, - 0x18,0xd1,0x05,0x63,0xa6,0xa4,0x76,0x27,0x74,0x52,0x48,0x86,0x9e,0x6c,0xf4,0x9f, - 0x6d,0x62,0xff,0x67,0x8a,0x59,0xfc,0xb9,0x63,0xe1,0xc2,0x65,0xee,0x18,0xb8,0x70, - 0xf4,0x0a,0x0e,0xd0,0x47,0x64,0x02,0x69,0x53,0xdd,0x87,0x27,0x47,0xbd,0x5e,0xab, - 0x3d,0xaf,0x5d,0xfd,0x96,0xaf,0x76,0x0f,0x4f,0xe8,0xd6,0xa2,0x7e,0x8b,0x2e,0x76, - 0x4f,0x7a,0xd6,0xca,0x85,0x2d,0x1b,0xac,0xec,0x7b,0xa1,0xf0,0xf6,0xe7,0xfb,0xde, - 0xdc,0x23,0x8b,0x35,0xb7,0x37,0xe8,0x0a,0x6e,0x2c,0xf6,0xbd,0x85,0x67,0x26,0x2d, - 0xec,0x3d,0x7d,0xd1,0x7c,0x51,0xfb,0x9e,0xf2,0x48,0x6e,0x89,0x47,0x19,0xa1,0x1e, - 0x94,0x6f,0x08,0x98,0xcc,0xa3,0x4c,0x69,0x8f,0x61,0x3d,0x9c,0x56,0xdc,0xb7,0xc6, - 0xc7,0x47,0x32,0x46,0x36,0x21,0x7e,0x24,0xb7,0xae,0x68,0xd6,0xa5,0xe3,0xa9,0x3a, - 0xc1,0x2c,0x49,0xa9,0x77,0x42,0xbb,0x06,0x78,0x73,0xc7,0xad,0xb1,0x2b,0xa6,0x58, - 0x49,0x39,0xa5,0xd4,0xe6,0x9b,0xe3,0x96,0x43,0xc2,0x8c,0x5a,0x72,0x5e,0xcb,0x7c, - 0xea,0x10,0x12,0x01,0x1f,0xcc,0xbe,0x39,0xe2,0x86,0x02,0xb3,0x95,0x8e,0xcd,0xce, - 0xac,0xaf,0x28,0x46,0xda,0x6f,0xe0,0xa6,0xf2,0x29,0x61,0x7e,0x93,0xc6,0x72,0x34, - 0x82,0xed,0x5d,0x8e,0xe1,0x9f,0xe0,0xcb,0xbd,0x56,0xe1,0x67,0x4b,0x35,0x6f,0x7a, - 0xaf,0x97,0x63,0x6d,0x07,0xa8,0x12,0xbb,0x39,0x43,0x2d,0xc7,0x14,0x2d,0x8e,0xdd, - 0x29,0x6f,0xed,0x14,0x8c,0xa6,0x13,0x24,0x8a,0x25,0x7c,0x25,0x61,0x6f,0x92,0x89, - 0x7c,0x95,0x86,0x3c,0x11,0xc9,0x43,0x0c,0x6f,0xee,0x69,0x1b,0x62,0xa6,0xd6,0x87, - 0x3d,0xf6,0x5e,0xc7,0xc1,0x25,0x39,0x4c,0x98,0x90,0xa7,0xdc,0x06,0x5d,0xc1,0x35, - 0x66,0xa8,0x75,0x25,0xe3,0xd2,0x22,0x06,0x40,0x71,0x88,0xef,0xae,0x1d,0x2a,0x7c, - 0xca,0xf4,0x9a,0x9e,0xf8,0xbf,0xff,0x47,0xe8,0x97,0xb9,0xd0,0x46,0x9c,0x59,0x45, - 0xbe,0xdc,0x98,0xe4,0x79,0xd7,0xe0,0x78,0x49,0x68,0xea,0x6f,0x69,0x32,0x89,0x49, - 0xb6,0xac,0xfd,0xd2,0xf3,0x1d,0xd2,0x4e,0x25,0x62,0x86,0xa6,0xe7,0x44,0x79,0x5e, - 0xfb,0x6a,0x12,0x20,0x07,0x1a,0x80,0x23,0x1d,0x4a,0x28,0xa4,0x77,0x0d,0xd3,0x8f, - 0x30,0xa1,0x59,0x1a,0xb8,0xbc,0x8c,0xac,0x73,0xff,0x37,0x85,0x0b,0xad,0x8d,0x21, - 0xca,0x78,0x60,0xa6,0x3e,0x04,0x83,0x9f,0x8d,0xbc,0x22,0x4e,0x66,0x17,0xa3,0x5d, - 0xf1,0x38,0xac,0xe9,0x9f,0x88,0x2b,0x24,0x02,0xd9,0x45,0x2b,0xbb,0xa8,0xef,0xf0, - 0xc2,0xdb,0x2f,0x61,0x69,0xcf,0x02,0xc9,0x7c,0x07,0x97,0x2b,0xcd,0xc3,0xe1,0x6d, - 0x1b,0xad,0x71,0x63,0x9c,0x9b,0xed,0x20,0xc8,0x4a,0xe3,0x0b,0x1d,0xfd,0x2e,0x10, - 0x72,0x72,0xe3,0x01,0x19,0x9f,0x1c,0x49,0x87,0xb2,0xb0,0x32,0xaa,0x98,0x87,0x03, - 0x7e,0x3a,0x1d,0xf7,0xc9,0x62,0x39,0xd9,0x92,0x9e,0xb3,0xb2,0x01,0x75,0x19,0x64, - 0xd3,0x4d,0xce,0xa0,0x28,0xbe,0x44,0xea,0x69,0xa1,0xd9,0xac,0xcc,0x86,0xc2,0xd3, - 0x32,0x63,0x13,0xd0,0x2d,0x3a,0x75,0x48,0x97,0x08,0x7b,0xc7,0x92,0x42,0x3f,0x8e, - 0xb8,0xb4,0x5e,0x8e,0xe5,0x94,0x94,0x0a,0x99,0xfd,0x0a,0xdb,0xb6,0xd0,0x42,0x04, - 0xa0,0xce,0x49,0x82,0x5f,0x51,0x97,0xf1,0x2a,0x03,0x1e,0xad,0x4a,0x6b,0xde,0xda, - 0xd3,0x1b,0x2e,0xbd,0xb5,0x1e,0xa4,0x48,0xe8,0x6b,0xe3,0x94,0xaf,0x1f,0x7b,0x7a, - 0xdc,0xb4,0x9f,0xf6,0x3d,0xa2,0xdd,0x92,0x4d,0x75,0x82,0x85,0xcf,0x21,0xcf,0x03, - 0x4f,0x9f,0xec,0xa4,0xd3,0xa9,0x81,0x78,0x5d,0x69,0x9a,0xe9,0x40,0x7b,0xcd,0xa4, - 0x2c,0xb1,0xc2,0x06,0x7e,0x45,0x64,0x20,0xeb,0x54,0xa3,0x24,0xcf,0x3c,0x5d,0xab, - 0xcb,0x9f,0x5c,0x49,0x8f,0x49,0x35,0x41,0x25,0xe4,0x53,0x9a,0x21,0xa2,0x59,0x15, - 0xce,0x29,0x61,0x31,0x3d,0x6e,0xe3,0x34,0x67,0xe7,0x0f,0x92,0x8b,0x37,0x4f,0x5e, - 0xb5,0xb4,0x49,0x87,0xdd,0xa7,0x27,0x54,0xaa,0x36,0x36,0x2a,0xb2,0xe9,0xe7,0x79, - 0x17,0xc0,0xbe,0x03,0xd2,0x47,0xe0,0x5f,0x05,0xce,0x61,0x8b,0x49,0x4a,0xa4,0x5d, - 0x97,0x1c,0x85,0x7f,0x23,0x5d,0xf5,0x36,0x6f,0xa5,0xab,0x67,0x69,0xc8,0x39,0x03, - 0x1d,0x2f,0x4d,0xe2,0x54,0x49,0xf7,0x39,0x61,0x97,0x92,0x37,0xf2,0xd2,0x72,0x06, - 0xd1,0x17,0x36,0x7c,0xeb,0xd2,0x60,0x09,0xef,0xf0,0x17,0xf5,0x98,0xdd,0x0f,0x67, - 0x3b,0xcd,0xbe,0x3c,0xe9,0x96,0x37,0x5a,0x60,0xf2,0x14,0x5e,0x81,0xcc,0x0d,0x07, - 0x3e,0x65,0x14,0x5a,0x8a,0xc1,0x4b,0xf5,0x8a,0x2b,0xcb,0x8f,0x3d,0x5d,0x61,0xc6, - 0x38,0x9d,0x7b,0x44,0x89,0x57,0x8b,0xf5,0x5c,0x03,0x3f,0x74,0xae,0xa4,0x2b,0x44, - 0x4b,0x56,0x56,0x9d,0x1b,0x55,0x9f,0x0d,0xee,0x56,0x5d,0x2f,0x66,0x08,0x36,0xc5, - 0x87,0xe0,0x1b,0x24,0xb0,0x4e,0x1a,0x98,0x8b,0x26,0x99,0x05,0x7a,0x08,0x1b,0x69, - 0x1f,0xbe,0x5b,0xbc,0xc4,0xbe,0xa0,0x1b,0x1a,0x5f,0xba,0x61,0x3e,0xb5,0x0c,0xc9, - 0x58,0x5a,0xa7,0xd8,0xd7,0x92,0xde,0x4d,0xe1,0xea,0x0f,0x9d,0x6f,0xaf,0xad,0xe8, - 0x90,0x8e,0x67,0xbd,0x4b,0x0b,0x7a,0xbd,0x1c,0x3e,0xfd,0x42,0x62,0xde,0xed,0xf7, - 0x8e,0x1e,0x1e,0x3f,0x38,0xf9,0xae,0xdf,0xa3,0xcf,0x58,0xec,0xd5,0x53,0x48,0x93, - 0x94,0x2e,0x7b,0xcc,0xb1,0x39,0x20,0x73,0x3b,0xa1,0xaf,0x5f,0xe3,0x56,0x81,0x7b, - 0x6c,0xc1,0x75,0xcb,0x4b,0x80,0xd7,0x1b,0xf4,0xc0,0x9f,0x6f,0x6b,0xe2,0xb1,0x1d, - 0x47,0x8f,0xea,0x00,0x0b,0xbd,0x1d,0xef,0xba,0xa2,0x1f,0x55,0x35,0x37,0x08,0x48, - 0x17,0x49,0xcb,0x95,0x8a,0x10,0x70,0x30,0xeb,0x41,0xac,0x1c,0x5f,0xf1,0x2d,0x7c, - 0xba,0x20,0x2d,0x87,0x7e,0x53,0x3e,0xef,0x39,0xbc,0x88,0xb2,0xed,0x90,0x94,0x1f, - 0x65,0x03,0x47,0x1e,0x79,0x30,0x9d,0x7e,0xef,0xa6,0xa3,0xf2,0xe9,0x3e,0xbf,0xf2, - 0x0b,0xf1,0x09,0xa8,0x76,0x70,0x84,0x8d,0xfe,0xc0,0x6c,0x33,0x44,0xdb,0x18,0xfd, - 0x5a,0x41,0x62,0x2d,0xb1,0x31,0xfa,0x95,0xc8,0xe8,0x8a,0xbb,0xe8,0x72,0x7d,0x49, - 0x8e,0xda,0xfc,0x25,0x9f,0xa3,0xbb,0x62,0x35,0xdd,0x50,0x66,0xd7,0xbf,0x0c,0xf7, - 0xac,0xe1,0x35,0x47,0x91,0x36,0x38,0xd5,0xc7,0xaf,0xba,0x62,0x01,0x63,0xa3,0x74, - 0x61,0x0d,0xc6,0x3a,0xd2,0x69,0xbc,0xf3,0x7c,0x1a,0xd9,0x6a,0xae,0xd0,0xb4,0x2d, - 0xac,0xf2,0x50,0x96,0xfd,0x81,0x09,0x91,0x5e,0xc2,0xac,0x26,0xe6,0xa4,0x79,0x11, - 0x81,0x03,0xc9,0x6c,0xf3,0x6c,0x36,0xd2,0x15,0xf8,0xf4,0x5c,0xaa,0xa1,0x85,0x56, - 0x1d,0x24,0x5b,0x4c,0x22,0x2e,0x00,0xe8,0x02,0x20,0x1d,0x3d,0x2b,0xe3,0x92,0xf8, - 0x58,0x92,0xa2,0x6e,0x6d,0xcd,0x38,0xea,0x0a,0xc1,0xbb,0x0b,0xea,0x3c,0x4b,0x0a, - 0xca,0xd1,0xcd,0xc7,0x6f,0x10,0xe7,0x50,0x4c,0xe7,0x3d,0x2e,0x2f,0x0d,0xbc,0x4a, - 0x63,0xd6,0x15,0x45,0x6e,0xe4,0x85,0xe1,0x7e,0xb3,0x9c,0xf9,0x27,0x4d,0xcb,0xc7, - 0x1e,0x0b,0x57,0xfd,0x22,0x19,0x9a,0x61,0x1d,0x54,0x67,0x8b,0xe1,0x7c,0x57,0x35, - 0x12,0xe4,0xcb,0x44,0x13,0xc5,0xdb,0x97,0x24,0xb3,0xd2,0x32,0xc6,0x4d,0x9b,0xdf, - 0x05,0x19,0xa7,0x01,0x96,0x2b,0xd5,0x71,0x54,0x4e,0xb4,0x98,0xa5,0xfc,0x3c,0x72, - 0x79,0x6c,0x5e,0x04,0xfc,0x82,0xb7,0xb2,0x20,0xe6,0x7b,0xeb,0x38,0xf1,0x91,0x36, - 0xb0,0xe2,0xcc,0xde,0xbb,0xf5,0x4c,0xfb,0x1e,0x90,0x4b,0x26,0x88,0x04,0x7f,0x79, - 0xf3,0xf2,0x59,0xba,0xc8,0xc0,0x8d,0xa4,0x68,0x96,0x8f,0x74,0xad,0x9b,0xf7,0x2d, - 0x14,0xf4,0x9c,0x77,0x54,0x7a,0x77,0xa2,0xd0,0xb6,0x7d,0x88,0x9f,0x39,0x52,0xb0, - 0x68,0x31,0x1d,0x2e,0x6b,0x94,0x5a,0x95,0x45,0x4c,0x92,0x18,0x7e,0xa9,0x05,0x19, - 0x10,0x7e,0x7b,0x18,0x88,0x08,0x0a,0x99,0xf4,0x08,0xf1,0xd2,0x4b,0xb8,0xd3,0x20, - 0x8e,0x75,0x0a,0xa7,0xa3,0x21,0xfd,0x30,0x67,0x0d,0x22,0x49,0x9d,0x7e,0x87,0x0e, - 0x3c,0x5c,0x6e,0x9d,0xba,0x56,0x0b,0xa5,0x0b,0x59,0xea,0xab,0x09,0xec,0xb9,0xde, - 0x86,0x6d,0x38,0xbf,0x7b,0x42,0xb5,0xf4,0x21,0xc2,0x5b,0xfd,0xfa,0x89,0xea,0xb2, - 0x1e,0x8e,0x80,0x76,0x72,0xae,0x1f,0x5b,0xac,0xe2,0xd1,0x76,0x29,0x93,0xba,0xe8, - 0xd0,0xf2,0x27,0x54,0x42,0xab,0x82,0xd8,0xd6,0xd5,0x4e,0x87,0x3e,0x09,0x12,0xa2, - 0x56,0x4e,0xa7,0x9b,0xf6,0xf4,0x7f,0xc8,0xe1,0xed,0x46,0x32,0x83,0xd8,0x2c,0xab, - 0x45,0xee,0xd9,0x7a,0xe0,0x7e,0xcd,0x55,0x06,0x27,0xed,0xff,0x47,0x7c,0x9d,0x93, - 0x49,0xfa,0xdf,0x95,0xaf,0x4c,0xd1,0x62,0xf0,0xa7,0x97,0xcf,0x7f,0xa5,0x30,0xab, - 0x0a,0xb6,0x3d,0xa5,0xc3,0x71,0xdd,0x1e,0x61,0xad,0x0f,0x76,0x5e,0xcc,0x07,0xe2, - 0x87,0xbe,0x29,0x02,0xe8,0x4a,0x2a,0x07,0xd7,0x5c,0xfd,0x4c,0xc4,0xab,0x3f,0xbe, - 0x7b,0x87,0xa0,0x59,0x29,0x8a,0x0e,0x27,0x73,0x3a,0x96,0x2e,0x4f,0x2f,0x75,0xbf, - 0x4e,0x15,0x4b,0x53,0x6d,0x81,0x5f,0x76,0x17,0x69,0x03,0x4c,0xd0,0xb4,0xe8,0xb1, - 0x44,0xd3,0x73,0x0b,0xaa,0x28,0x2b,0xb4,0x74,0xc2,0x61,0x8b,0xa6,0xef,0xcc,0x93, - 0xc3,0xf2,0xe3,0x44,0x66,0xda,0xb4,0x6a,0xab,0xb2,0x48,0x29,0x0f,0x11,0xc1,0x94, - 0x9f,0xfa,0xd0,0xe2,0xed,0x8b,0xd7,0x3f,0xbf,0x7d,0x67,0x8a,0xe0,0x3a,0x65,0x48, - 0x10,0x96,0xc2,0x2a,0xc3,0x2e,0x13,0x34,0x9a,0x58,0x3d,0x43,0xc5,0xca,0x43,0x41, - 0x63,0xb3,0x25,0x88,0xff,0x69,0xbe,0xe0,0x77,0xbf,0xb0,0xfd,0xa6,0x62,0x35,0xb7, - 0x9f,0x2b,0x5d,0x16,0xd1,0xa7,0xd1,0xb4,0x87,0x18,0xbb,0xd2,0x0f,0x56,0xec,0xb1, - 0x8f,0xe0,0xe7,0x65,0xca,0x82,0x2b,0x55,0xe5,0x35,0x86,0x69,0x1c,0xea,0xd6,0x44, - 0xed,0x68,0x38,0x7b,0x50,0x09,0xe9,0x6a,0xc5,0x13,0xb6,0x6e,0xe5,0xcb,0x6d,0xb8, - 0xbe,0xcf,0x0f,0xef,0xfb,0xac,0x81,0xb1,0xfc,0x85,0x1f,0xd2,0x55,0xcb,0x8c,0xdf, - 0x5a,0xc3,0x09,0x11,0xcc,0xc0,0x94,0x1b,0x06,0xc6,0x70,0x48,0x4e,0xb3,0x15,0xbd, - 0xdc,0x46,0xc8,0x24,0x5d,0xce,0xe6,0x3c,0xa0,0xce,0x34,0x3e,0xd3,0xbe,0x90,0xd5, - 0xd9,0xc7,0x82,0xce,0x87,0xfd,0x3d,0xd2,0x01,0x67,0xa9,0x51,0x6f,0xb8,0x9e,0xfd, - 0x73,0x28,0xa8,0xb3,0xa9,0x36,0xe0,0x4e,0xa4,0x4d,0x53,0xbf,0xe1,0x6f,0xf7,0xef, - 0x7f,0x0f,0xe1,0xa1,0x17,0xa6,0x35,0x5b,0xa7,0x0e,0x24,0x27,0x6b,0x85,0x86,0x51, - 0xf7,0x39,0x74,0xd2,0x2d,0xc2,0x52,0x63,0xb9,0xb7,0x6f,0x20,0x3f,0xf6,0x10,0xa2, - 0x8a,0xd4,0x44,0xa7,0x3c,0x41,0xa7,0x40,0x37,0xce,0xa1,0x09,0xda,0x31,0x39,0xa9, - 0x34,0x55,0x87,0x9a,0x45,0x7b,0x12,0x2b,0xb6,0x07,0xa6,0xb3,0x7c,0xcd,0x20,0x14, - 0x5c,0x0c,0xad,0xdd,0x5e,0x5b,0x87,0xaf,0xee,0x37,0x01,0x86,0xfd,0x21,0x7e,0xd3, - 0x4a,0xce,0x3a,0xd8,0xab,0x4b,0x1b,0xa6,0x8a,0x4b,0xc9,0x8a,0x2c,0xfb,0x7d,0x7a, - 0xe8,0x6e,0x4f,0x6c,0x21,0x28,0x19,0x18,0xde,0x32,0x63,0x6d,0xf7,0x65,0xd3,0xd9, - 0xdf,0xff,0xfa,0xdf,0xd8,0x1f,0xb2,0x38,0xf3,0xb9,0xb5,0x51,0x03,0x60,0x1a,0xb5, - 0xaf,0xa8,0xb1,0x2e,0xa5,0x77,0xa0,0x42,0xfc,0xbd,0xf6,0x7a,0x41,0xc0,0x76,0x5d, - 0xe8,0xc7,0x52,0xd4,0xe0,0xca,0x33,0x64,0xe8,0xbc,0xbb,0xcc,0x28,0x37,0x08,0xb2, - 0x0c,0xea,0xc3,0x27,0x68,0xdd,0x8f,0x9d,0xd5,0x6a,0xd5,0x21,0x5d,0xe8,0x2c,0xf3, - 0x58,0x3b,0x47,0xc4,0xa7,0x16,0x06,0x35,0xee,0x21,0xd4,0x4c,0x46,0x0e,0x1b,0xfa, - 0x00,0xd1,0x03,0x6d,0x74,0xa1,0x65,0xb3,0xf8,0x40,0xb2,0x92,0xfb,0xe9,0x79,0xab, - 0x98,0xd3,0xa9,0x48,0x6f,0xb8,0x51,0x8d,0xd8,0x36,0xb1,0x8c,0x5c,0x6a,0xa4,0x29, - 0x53,0x59,0x73,0x6c,0x75,0x49,0x4d,0x0f,0x63,0x0a,0x17,0xcc,0x73,0x0b,0x94,0xf3, - 0x92,0x16,0x7b,0x6e,0xc4,0x02,0x3d,0xc0,0x96,0xc9,0xdb,0x56,0x49,0xa3,0x2d,0x35, - 0x38,0x6f,0x81,0x03,0x37,0x14,0x9f,0xdf,0xb0,0xfa,0x52,0x08,0x1a,0x71,0xf5,0x8c, - 0xf9,0x66,0x33,0x4b,0xf0,0xf8,0x1d,0x62,0xd3,0x74,0x59,0xb8,0xb8,0xd6,0x75,0x48, - 0x97,0x73,0xae,0xdb,0x54,0x48,0x34,0x0e,0xd0,0xec,0x71,0xc3,0x65,0x55,0x50,0x5d, - 0x1d,0x74,0x76,0xbe,0xd5,0x77,0xb5,0x3d,0x99,0xe7,0xd5,0xfe,0xf4,0x7a,0x76,0x25, - 0x52,0x87,0x4a,0xe7,0x36,0x8b,0xe0,0x6c,0x2b,0x9d,0x02,0x38,0x63,0xaf,0xc5,0x92, - 0x0b,0x5c,0x5c,0x00,0xaf,0xe9,0xe0,0x67,0xc1,0x60,0x99,0x26,0x10,0x8e,0x87,0xdc, - 0x78,0xa3,0xd7,0x3f,0x96,0x97,0x7c,0xc2,0x6f,0x00,0xdf,0xf0,0x8b,0xda,0x7a,0xc7, - 0xd1,0xb9,0x74,0x6d,0x76,0x30,0x86,0xe9,0x24,0xd7,0x58,0x7b,0xa3,0x18,0x81,0x31, - 0x2f,0x15,0x63,0xdb,0x5a,0xbd,0xc5,0xd0,0x54,0x8b,0xb6,0x78,0x43,0xeb,0x02,0xa5, - 0x79,0x0d,0x97,0x71,0x84,0x7c,0x00,0x9a,0xc4,0x44,0x2c,0xf3,0x56,0x33,0x8e,0xfa, - 0xb8,0xe4,0xfe,0xcf,0xcf,0x8d,0x7f,0x33,0xcf,0xde,0x3d,0xae,0x92,0xf0,0x91,0x75, - 0x8d,0xba,0x6f,0xa9,0x63,0x6b,0xeb,0xb9,0xe4,0xb4,0x84,0x72,0x6c,0x24,0x88,0x18, - 0xdc,0xb6,0x3d,0x8b,0x74,0x55,0xef,0x71,0x15,0x44,0x2c,0xf8,0x63,0x39,0xa7,0x46, - 0xc4,0xa8,0x30,0xed,0x57,0x61,0x1e,0xcc,0x84,0x16,0x40,0x02,0xc5,0x9e,0x54,0x2c, - 0x33,0x7b,0x32,0x80,0x15,0x3a,0xe6,0xf5,0xe2,0xda,0x24,0xe6,0x88,0xd5,0xb4,0x53, - 0x01,0x5e,0x4f,0xb8,0x16,0xa1,0x8b,0xa6,0x6d,0x7c,0x7f,0x99,0xbc,0xe0,0xf5,0xab, - 0x2b,0x7f,0x24,0x78,0xe1,0xa8,0xd3,0xa7,0x2f,0x6f,0x77,0xb8,0x21,0xdc,0x7a,0x3d, - 0x29,0x9a,0xe3,0xb2,0x8a,0xc9,0x79,0x26,0x3d,0x4f,0x78,0xd0,0xd6,0x1f,0xa3,0xa4, - 0x89,0xcc,0xbb,0xed,0xe4,0x9f,0x63,0x4a,0xc5,0xbb,0x07,0xc7,0xc7,0xad,0x96,0x6b, - 0xb3,0xd7,0x6b,0x18,0x63,0xeb,0xcb,0x4a,0x6c,0x3f,0x7d,0x72,0xdc,0x59,0x1d,0xa9, - 0x4f,0x9f,0x4c,0xa9,0x79,0x4c,0x59,0x97,0x66,0xaa,0xb7,0x56,0xf7,0xce,0x46,0x25, - 0xb6,0xe4,0x5d,0xf0,0x05,0xda,0xa3,0x9f,0x4f,0xc8,0xcc,0x85,0xce,0xc5,0x9a,0x2f, - 0xca,0x74,0xbd,0xc0,0x45,0x73,0xb9,0xc8,0x80,0x65,0xd3,0xc1,0xce,0xd2,0xee,0xd3, - 0xa7,0x92,0x6c,0xa7,0xbd,0x6d,0x8b,0xeb,0x7b,0x43,0x97,0xb8,0x43,0x97,0xf6,0x1c, - 0xd2,0x9a,0x09,0x17,0xa3,0x92,0x94,0xc7,0x15,0x29,0xe9,0xd5,0x13,0x0e,0x29,0xb3, - 0xef,0x70,0x01,0x49,0x7c,0x0f,0xa4,0x5c,0xf7,0x3f,0x5b,0x84,0xd0,0xdb,0xbf,0xd8, - 0x56,0x92,0xde,0x61,0xfd,0x6e,0xb5,0x70,0x76,0xe6,0x34,0x4a,0x28,0x6c,0x71,0xe7, - 0x6e,0x48,0xd4,0x50,0x93,0x4a,0xef,0xfb,0x6c,0xd4,0x6b,0x95,0x74,0x2c,0x6d,0xa2, - 0x66,0xc8,0xa6,0x39,0xe3,0x87,0x51,0x6a,0xe6,0xac,0xa4,0x69,0x46,0xff,0x0a,0xc2, - 0x4b,0x64,0x85,0x14,0xaa,0x6a,0x5e,0xb6,0xfb,0x3d,0x4e,0x15,0x22,0xc4,0x0b,0x3f, - 0x21,0x61,0xa8,0xc7,0x3a,0xbb,0x79,0x5c,0x31,0x25,0x1b,0xae,0x09,0xfc,0x7a,0xb4, - 0x50,0xa2,0x0e,0xc4,0xf1,0xd3,0xdc,0xc4,0x4c,0xf1,0x39,0x0c,0x6f,0xc8,0xf2,0x13, - 0x6e,0xb9,0x4e,0x1f,0xa3,0x81,0xc4,0xf3,0xeb,0x36,0x9c,0xe6,0xf6,0xdb,0x9a,0x78, - 0x37,0x23,0x74,0x4d,0x6b,0xa8,0x78,0x0b,0xe9,0xb8,0x07,0x8b,0x9f,0xde,0xf5,0xda, - 0x48,0xd2,0xae,0x28,0x62,0xa6,0x57,0x71,0xf1,0xa2,0xad,0xe1,0xee,0x39,0x32,0x09, - 0x3d,0x42,0x6a,0x63,0xca,0xae,0x95,0x16,0x29,0x55,0x6f,0x91,0x9d,0xd0,0x42,0xf4, - 0xb2,0x39,0x3e,0x89,0xdd,0x31,0x6e,0x99,0x31,0x70,0xa6,0x5f,0xb3,0xee,0x8c,0xcc, - 0xfb,0x70,0x38,0xc9,0x14,0x4d,0xae,0x53,0x98,0x83,0x08,0xf3,0x26,0x9c,0xb6,0x39, - 0x1d,0x2b,0x9b,0xec,0x49,0x22,0x9d,0x97,0xe2,0xb0,0x81,0x43,0x02,0xfb,0x8c,0x20, - 0x80,0x9b,0x9a,0x80,0x36,0x5d,0xa9,0x7a,0xc4,0xcb,0xa7,0x57,0xcb,0xbc,0x18,0xb0, - 0xf4,0x61,0x96,0x12,0xcf,0x7e,0x7e,0xf3,0xdc,0xf6,0x95,0x2b,0x5f,0xfc,0x3c,0x9d, - 0xca,0xdc,0x64,0xec,0x04,0x89,0x7d,0x11,0x17,0x9d,0x75,0x1b,0xac,0x29,0xa9,0xd0, - 0x83,0x19,0xfc,0x24,0x05,0x3f,0x41,0x41,0xe5,0x09,0xcf,0xe9,0xe6,0xd5,0x49,0x06, - 0x72,0xff,0x73,0x11,0x10,0x10,0x64,0x35,0x2a,0x35,0x5d,0x46,0xdc,0x6d,0xc7,0x95, - 0x75,0xea,0xa2,0xe1,0x4c,0x68,0x6e,0x62,0x9d,0xcb,0x7a,0x19,0x7f,0x42,0xeb,0xb6, - 0xcd,0x4b,0x7a,0x09,0x8c,0x49,0xc0,0xe9,0x0c,0x18,0xda,0x10,0x29,0xa6,0x0b,0x76, - 0x47,0xcd,0x32,0xaa,0xec,0x58,0x0f,0xf8,0x9f,0x8f,0x10,0xc5,0x2a,0x2d,0xdb,0xe5, - 0xa9,0x1e,0x89,0xbd,0xbd,0x4d,0xb1,0x2b,0x02,0x14,0x08,0x93,0xc1,0xeb,0xbc,0x8f, - 0x8e,0x2b,0x15,0x9c,0x4a,0x24,0x0b,0xd3,0x7d,0x4f,0xfd,0x61,0x05,0x7e,0x28,0xa7, + 0x80,0xd8,0xa2,0x48,0x67,0x88,0xfd,0x06,0xa2,0xbb,0xf8,0x4b,0x81,0xeb,0x31,0xcc, + 0x54,0x78,0x09,0x33,0x35,0x39,0x57,0xc2,0x98,0x05,0x0d,0x88,0x4c,0x9c,0x54,0xb0, + 0x6c,0x91,0x12,0xab,0xe0,0x52,0x34,0x7d,0x6c,0xb5,0x2b,0xfc,0x1c,0x09,0x03,0x42, + 0xe2,0x62,0x2e,0x82,0x44,0xf8,0x69,0x22,0x26,0x71,0xa0,0x60,0xe1,0x82,0x24,0xd4, + 0x83,0x23,0xc5,0xe6,0x50,0x21,0x7f,0xd0,0x90,0x66,0x52,0x15,0xcb,0x5c,0xb6,0xa1, + 0x95,0x7c,0xc7,0x64,0x1d,0xe2,0x5c,0xca,0x4c,0x09,0x32,0xaa,0x17,0xe9,0x24,0x18, + 0x53,0xac,0x70,0xe9,0x8b,0x97,0x84,0x15,0x46,0xaa,0x79,0xba,0xc2,0xca,0xf3,0x08, + 0xcb,0x32,0x2e,0x1a,0xd8,0x65,0xba,0x14,0x50,0x77,0x11,0x25,0x22,0x0f,0x00,0x2d, + 0x07,0x48,0x20,0x12,0xa8,0x73,0xda,0x1d,0xdd,0x85,0x4f,0xa4,0x4d,0x61,0xcf,0x71, + 0x30,0x06,0x97,0x08,0xb1,0x28,0x99,0x62,0x64,0x54,0xf8,0xc6,0x02,0x67,0xab,0xbb, + 0x39,0x4b,0xd6,0x7d,0xe3,0xf5,0x8a,0x34,0x63,0xb7,0xbb,0xee,0xc6,0xf5,0x8d,0xc3, + 0xca,0x85,0xf2,0xf7,0x6d,0x46,0xe8,0xda,0x2c,0xfd,0x15,0xc6,0x15,0x7a,0x62,0x97, + 0xff,0x0c,0x1b,0xeb,0x7a,0x47,0x5a,0x1f,0x0a,0xae,0x36,0x6d,0xad,0x75,0x24,0x3c, + 0xe2,0xae,0x9a,0xb3,0xe1,0x95,0x1f,0xde,0x05,0x45,0xeb,0xc5,0x28,0x8a,0xe9,0x1f, + 0x6d,0xda,0x48,0x0b,0x60,0x87,0xa5,0xdf,0xa2,0xae,0x37,0x1a,0xff,0x12,0x9f,0xca, + 0x6e,0x32,0x88,0xca,0x72,0xb6,0x5d,0x9b,0x58,0x5d,0xae,0xe8,0xf1,0x19,0xb6,0x54, + 0x63,0x54,0x4d,0x85,0x92,0xdc,0xd9,0x06,0x0f,0x39,0xa2,0x9d,0x07,0x61,0xba,0x82, + 0x83,0x40,0xf6,0x2e,0x7a,0xfc,0x1f,0xed,0x6b,0xcd,0x52,0x13,0x6c,0xe8,0xb6,0xbc, + 0x93,0x2c,0xf9,0xc7,0x5b,0xa3,0x9d,0x92,0x32,0xae,0xb5,0x3b,0xaa,0xcb,0xfc,0x03, + 0x47,0x2c,0x68,0x3d,0x5f,0xe6,0x75,0x3a,0x58,0x8c,0x2a,0xf3,0xf8,0x34,0x27,0x48, + 0x89,0x54,0xca,0x5e,0x01,0x07,0x59,0x55,0xa5,0x31,0x9a,0x03,0xfe,0x9c,0x1a,0x05, + 0x26,0xd5,0x0d,0xc4,0x9c,0x34,0x35,0x28,0xf8,0x0e,0x87,0x42,0x1a,0xb9,0x30,0x85, + 0x19,0x6a,0xfe,0xd0,0xf7,0x94,0x88,0x53,0xa8,0x1b,0x72,0x06,0xc0,0x55,0xf8,0x0b, + 0x7c,0x6c,0x6d,0x58,0x95,0x79,0x00,0x13,0x04,0x20,0x69,0x6a,0x95,0x7d,0x9c,0x47, + 0x5f,0xa6,0xec,0x3d,0xb3,0x73,0x00,0x28,0xf5,0x96,0x14,0x29,0x81,0xcd,0xb2,0x37, + 0x3a,0x17,0x5f,0xe1,0x63,0x74,0x1c,0x71,0x48,0x04,0x67,0x55,0x61,0xc4,0x06,0x4c, + 0xbc,0xa1,0xb3,0x52,0x8d,0xb6,0x3f,0xc2,0x2d,0x83,0x3c,0x39,0xcc,0x6b,0x41,0x81, + 0x26,0x64,0x5d,0xe6,0x4c,0x20,0xdf,0xff,0xe1,0xa8,0xf5,0x77,0xf6,0x46,0x86,0x84, + 0x93,0x22,0x8f,0xd5,0xd5,0xba,0x21,0xac,0x19,0xc0,0xa3,0xbb,0x1a,0x40,0x86,0xf5, + 0x35,0x91,0xea,0x18,0x8a,0x49,0xc1,0xc2,0x5a,0x3a,0x42,0xec,0x73,0x16,0xf8,0x72, + 0x1b,0x7b,0xf0,0x05,0x01,0xac,0x66,0x43,0x27,0x47,0xf0,0xf3,0xb9,0xa2,0xb6,0x8e, + 0x7e,0x05,0x6a,0x00,0xcf,0x5a,0x74,0x26,0xf3,0x28,0x0e,0xaf,0xea,0x83,0x7b,0x7a, + 0x64,0x87,0x3d,0xdc,0x5d,0x05,0x70,0x3d,0x0c,0xdb,0x29,0x90,0x94,0x56,0x0c,0xeb, + 0x92,0x5e,0x21,0x05,0x5f,0x9a,0x2d,0x8b,0xf7,0x54,0x85,0x1c,0x81,0x3e,0x33,0xf9, + 0xa1,0xcd,0x0a,0xb2,0x71,0x59,0xc7,0xea,0xda,0xf3,0xd8,0x30,0x99,0xa4,0x26,0x00, + 0x2d,0x21,0xbe,0xc9,0x44,0x32,0xf8,0x32,0x71,0x5e,0xbf,0xbe,0x2b,0xf4,0xdc,0x12, + 0x65,0x93,0xf3,0x4d,0x97,0x05,0x09,0x85,0x9e,0xbb,0xe6,0x4e,0x82,0x09,0xd1,0x7e, + 0xd3,0xd8,0x16,0xe9,0x72,0x32,0xef,0x50,0xa8,0x0b,0x8f,0x40,0x13,0x6d,0x00,0x7d, + 0xd3,0x76,0x07,0x03,0x8b,0xb1,0x19,0x55,0xcc,0x97,0x8b,0xf1,0x0e,0x22,0x6c,0x1f, + 0xac,0x49,0x73,0x57,0x42,0x98,0x6a,0x80,0x93,0x6d,0xf0,0xe7,0x3a,0x09,0x8e,0x7b, + 0xdf,0x0e,0xd7,0x93,0xa3,0x0d,0x77,0x43,0xe9,0x48,0x4d,0x2b,0x99,0x0a,0x6b,0xc4, + 0x72,0x9c,0x64,0xa9,0x00,0xf0,0x88,0xf5,0x0c,0xe3,0x46,0x02,0xe9,0xdc,0xe1,0xf3, + 0xe8,0x74,0xd3,0x1c,0x4d,0xae,0xf5,0x94,0xa3,0xef,0x1f,0xb4,0xb6,0x57,0x18,0xee, + 0xc8,0xc5,0x45,0xfa,0xd7,0x0e,0x7f,0xb9,0x8d,0x83,0x6b,0x03,0x35,0x3a,0x77,0xe6, + 0xca,0xd7,0x32,0x84,0x77,0xc3,0xdb,0x41,0x14,0x5c,0x85,0x6d,0xb3,0x3c,0x0a,0x87, + 0xf4,0x57,0x07,0xc6,0x05,0x57,0x0a,0x49,0xf2,0xbd,0x5c,0x24,0x6a,0xd0,0x9f,0xe6, + 0x02,0x3f,0x6c,0x69,0x7a,0x5c,0x24,0x58,0x8f,0x61,0xaf,0x2d,0xbc,0x2b,0x1b,0x89, + 0x91,0x29,0x44,0x9c,0xf1,0xe8,0xae,0x56,0x9c,0x26,0x0b,0xff,0xab,0x02,0xd9,0xcf, + 0xb6,0xb1,0xbc,0xe4,0x9d,0xfc,0xed,0xc1,0x46,0xbc,0xc3,0x44,0x77,0xa8,0x70,0xe4, + 0x04,0xf2,0x48,0x32,0x40,0x3d,0x76,0x38,0xd0,0x38,0x4a,0x26,0xcb,0xd4,0x72,0x30, + 0x8f,0xc2,0x10,0xfe,0x87,0x71,0x2d,0x2f,0xca,0x38,0x8e,0x32,0x15,0x29,0x07,0xad, + 0x71,0x90,0x5f,0x19,0x39,0x38,0xdc,0x56,0xf9,0x32,0xc6,0x6b,0x33,0xae,0x82,0x67, + 0x7f,0x87,0xf0,0x45,0x86,0xac,0x72,0x62,0x1c,0xa7,0x93,0x73,0xca,0xa1,0x90,0xbe, + 0x00,0x67,0x0e,0x6f,0x10,0xce,0x2c,0xe0,0x1d,0x39,0x93,0x8b,0x12,0x84,0x4c,0xb8, + 0xc0,0xc5,0x7d,0x2a,0x88,0x73,0xec,0x94,0x84,0x7a,0x2f,0xe5,0xb0,0x38,0x4d,0xcf, + 0x91,0x99,0x21,0xad,0x0b,0xc6,0x31,0x67,0x4b,0x14,0x23,0x2d,0xb0,0x19,0x7c,0x4b, + 0xa7,0x0c,0x1d,0x43,0xf1,0x89,0x72,0x25,0xd8,0x4e,0xe5,0x13,0x1a,0x26,0xf5,0x5b, + 0x8e,0x3b,0x64,0x4b,0x09,0x0d,0x4e,0xa9,0x75,0xb0,0x96,0x48,0x13,0xaa,0xad,0xd2, + 0x1c,0xe9,0x9f,0x0a,0x2e,0xe9,0xcb,0x0c,0xe1,0x99,0x89,0xb4,0x02,0x2d,0x52,0x56, + 0x42,0x79,0x2b,0x9a,0x72,0xa1,0x9c,0xa4,0x48,0xd7,0xac,0x99,0x35,0x9c,0x31,0x7b, + 0xb9,0x2e,0xa7,0xea,0x20,0x9b,0x78,0xbc,0x2d,0xbe,0xac,0x8f,0xa1,0xec,0x7e,0x9d, + 0xc1,0x0e,0x3f,0xca,0xe4,0xdf,0x49,0x55,0x6a,0x05,0x86,0xa3,0xe3,0x6d,0xf1,0xb0, + 0xc3,0x9f,0xe3,0x9d,0x12,0x42,0xe7,0x0f,0x75,0xd6,0x8b,0x68,0x6d,0xd7,0x55,0x8d, + 0xf9,0x5b,0x63,0xbf,0x7b,0x3b,0xaa,0xa1,0x8e,0xb9,0xe5,0x81,0xc2,0x3f,0x54,0x6b, + 0x21,0xdf,0x2f,0x59,0x08,0xf5,0x86,0x65,0x4f,0x12,0xec,0xbc,0xa9,0x45,0x12,0x21, + 0x75,0x11,0xc5,0xcc,0x99,0x31,0x2c,0x9c,0xc2,0x9d,0x2c,0x07,0x69,0x28,0xfa,0xdd, + 0x88,0x02,0x35,0x7f,0xee,0x2d,0xb3,0xb0,0xc4,0x93,0xf9,0xe0,0x9a,0x85,0x2a,0x80, + 0xdb,0x50,0x7b,0x46,0xb4,0xb4,0x14,0x7d,0x9b,0xb4,0xb9,0x55,0x71,0x43,0xe7,0xc3, + 0x6d,0x6a,0xb7,0x55,0x04,0xec,0xe4,0x1a,0x3e,0x5b,0xc3,0x3c,0x0e,0x98,0x4c,0x2c, + 0x4e,0x5b,0x10,0x7e,0x11,0xcc,0xbe,0xd4,0xf0,0x70,0x7e,0xba,0x11,0x08,0xdc,0x9a, + 0xb0,0x6e,0x9a,0x08,0x8b,0x8d,0x49,0x0c,0x1d,0xe6,0xe6,0xb3,0x71,0xd0,0x3c,0x38, + 0x3e,0x6e,0x3f,0x3a,0x68,0x1f,0x9d,0xb4,0xfd,0xde,0x49,0xab,0x42,0x7d,0x96,0xda, + 0x50,0x2e,0x96,0xd3,0x62,0x40,0x25,0xfc,0xe1,0x57,0x24,0x10,0x5b,0xd0,0xaa,0x89, + 0xce,0x0b,0x19,0x50,0xed,0x45,0x90,0x84,0xfc,0xe3,0x0b,0x55,0x26,0x9b,0x20,0x64, + 0xdc,0x64,0xe2,0xb0,0xac,0x3c,0x4c,0x3b,0xe7,0x30,0x59,0xce,0x49,0x86,0xd6,0xa6, + 0x2f,0xac,0x45,0xdc,0x5c,0x8a,0xb6,0x3c,0xdf,0x6d,0x05,0x4c,0xb0,0xbd,0x81,0x9d, + 0xd8,0x17,0x54,0xac,0xde,0x7e,0x1a,0x42,0x77,0xee,0xe2,0x41,0x31,0x4e,0xc7,0xf7, + 0xe9,0xb4,0x43,0xe1,0xc6,0xd6,0x08,0x1f,0x4b,0xba,0x29,0x85,0xa6,0x46,0x96,0x1a, + 0x7b,0x91,0x4b,0xb8,0x7d,0x44,0x4d,0x95,0x6a,0x52,0x3d,0xe5,0x11,0xc5,0x14,0xfc, + 0xa9,0x77,0xa7,0x43,0x9d,0x6d,0x74,0x30,0xe2,0xb5,0x55,0x71,0x37,0x5c,0xe3,0xce, + 0xa2,0x8a,0x36,0xda,0x7a,0x0f,0x3b,0xea,0x27,0x9b,0x43,0x78,0xd3,0xec,0x2d,0xaf, + 0x76,0x15,0x55,0xec,0xfd,0x92,0x10,0xc1,0x18,0xdb,0xc2,0x80,0x21,0xa7,0xce,0x83, + 0x0e,0x65,0x57,0x6c,0xc2,0x61,0x7d,0x2b,0xd6,0xf3,0x27,0x0a,0x94,0xfe,0x47,0xb3, + 0x83,0x3b,0xf5,0x33,0x01,0x37,0xb9,0xa8,0x99,0xb4,0xe3,0x83,0xb5,0xd4,0xe9,0x70, + 0x6b,0x05,0xfb,0x68,0xbd,0x82,0x3d,0x34,0xe1,0x5b,0x47,0x5e,0x20,0xf7,0x50,0x9a, + 0x76,0x37,0xd3,0xca,0xd0,0xa1,0x53,0xb8,0x9e,0xeb,0xc1,0xda,0xf2,0xc7,0x5b,0x97, + 0xef,0x6f,0x14,0xd0,0x0f,0x87,0x5b,0xbd,0xa3,0x5e,0xc2,0x57,0x29,0x62,0x8d,0x6a, + 0xbd,0xdd,0x94,0xa6,0xfb,0x94,0x7d,0xda,0x53,0xc8,0xc3,0x93,0xc9,0xdc,0x75,0x1b, + 0x87,0xdb,0x0d,0xd3,0x9a,0x4b,0x58,0x77,0xbd,0xeb,0xa8,0xb4,0xb7,0xe2,0x15,0x5e, + 0xed,0x88,0x04,0xc9,0xfe,0xaf,0x1d,0xa3,0xdc,0xd9,0x50,0xec,0x74,0x0a,0xb7,0x1a, + 0x8a,0x1d,0xce,0xd1,0xd8,0x8c,0x2d,0x44,0xb0,0x6a,0x09,0x0a,0x88,0x87,0x74,0x5c, + 0x57,0x05,0xe2,0x6c,0xfc,0x19,0x17,0x88,0x7c,0x11,0x21,0xa1,0x31,0xc5,0x1f,0x53, + 0x05,0x98,0xa6,0x29,0x30,0xac,0x19,0x49,0xf2,0xb8,0x9f,0x51,0x31,0x61,0xaf,0x79, + 0xb2,0xee,0xa5,0xbf,0xb0,0xa0,0xfb,0x99,0xa1,0xba,0xc6,0x5e,0x04,0x57,0x77,0xb4, + 0x29,0xee,0x9c,0x1b,0x0c,0xc5,0x1f,0x16,0x88,0x92,0x83,0xe6,0xda,0x91,0x78,0xeb, + 0x8a,0x4f,0xf6,0x4b,0x1b,0x78,0x60,0xc2,0x13,0x71,0x6c,0x68,0xc9,0x7f,0x2a,0xab, + 0x51,0xed,0xf7,0x88,0x06,0x58,0x1b,0x6b,0x8b,0x59,0xb9,0xe9,0x39,0x71,0xe6,0x9a, + 0x30,0xfd,0x7b,0xa9,0xce,0x89,0x13,0x1c,0x73,0xad,0xe6,0x54,0x6c,0xb4,0x45,0x47, + 0x08,0x2d,0x1f,0x0e,0xf3,0xc1,0x2e,0x9f,0x08,0x73,0x01,0x53,0x64,0x11,0x42,0xb6, + 0x50,0x10,0xb7,0xdb,0xec,0x83,0x35,0x48,0x5e,0x83,0x87,0x45,0x89,0x2a,0xe8,0xb0, + 0x82,0x43,0xf2,0x6c,0x4e,0x40,0x74,0x16,0x27,0x60,0x62,0x41,0x20,0x04,0xd8,0x88, + 0xdd,0x91,0xe0,0x15,0x97,0x82,0xf9,0x6b,0x42,0x6d,0x4b,0x0a,0x08,0x87,0x26,0xc5, + 0x23,0x4d,0x8a,0x6a,0xbb,0xdc,0x48,0x50,0x91,0xaa,0xdf,0x3f,0x70,0xb7,0xe4,0x53, + 0xe6,0x78,0x97,0x5c,0x12,0xe6,0x06,0xa4,0xc4,0x3a,0x00,0xd5,0xec,0xb5,0x91,0x56, + 0xb6,0xf4,0x61,0x01,0xc9,0xae,0x1b,0xae,0x21,0x16,0xce,0x8b,0x0a,0x3e,0x6d,0xb9, + 0x32,0xd0,0x10,0xd4,0xc9,0xf9,0x25,0xdb,0xe4,0xa3,0x23,0x17,0x8d,0xf5,0x50,0xa0, + 0x77,0x7d,0xbd,0x77,0xda,0xd5,0xad,0x42,0xa7,0x5d,0xee,0x66,0x3b,0x25,0xf6,0x9e, + 0xed,0xe1,0xcf,0xe9,0x02,0x12,0xa6,0xcf,0x9b,0x46,0x0d,0xde,0x60,0x83,0xfa,0x89, + 0x4e,0x75,0xf7,0xc3,0xd9,0x29,0x08,0x54,0xde,0x0e,0xd3,0x82,0x7b,0xda,0x70,0xe9, + 0xec,0x74,0xde,0x3f,0x7b,0xa6,0x39,0x05,0x98,0xfd,0xfa,0x40,0x24,0x0a,0x8d,0x33, + 0x7d,0x76,0x6f,0x87,0x77,0x0d,0x40,0x0a,0xa5,0x4e,0xc3,0xe8,0xc2,0x0e,0x25,0x0a, + 0xe9,0x15,0x9d,0x8b,0xb4,0xd1,0x8d,0x8b,0x5a,0x10,0xf8,0x72,0xfd,0x86,0x39,0xa4, + 0x37,0x77,0x70,0xcf,0x45,0x85,0xcf,0xdd,0x1b,0x67,0x3f,0xa5,0x2b,0x41,0x6c,0x01, + 0xf3,0x0d,0x46,0x76,0xf0,0x1a,0x20,0x84,0x10,0x25,0x20,0xdc,0x1d,0x2f,0xe1,0xe5, + 0x6b,0xc0,0xc6,0x45,0xd2,0x10,0x51,0x48,0xc4,0x2a,0xa8,0x6e,0x7e,0xd1,0x10,0xdc, + 0x84,0x37,0x6a,0xbc,0xc6,0x97,0x28,0xa5,0x16,0x06,0xdd,0xa1,0x87,0x1b,0x54,0xf2, + 0x68,0x68,0x18,0x8d,0xb3,0xfb,0x71,0x90,0xe7,0xc3,0xd3,0xae,0xfe,0x7e,0xf7,0x45, + 0x12,0x68,0x77,0xb9,0xc8,0x4f,0xf8,0xb2,0x73,0x81,0x7c,0xdb,0x02,0xa7,0x5d,0x6c, + 0xd1,0x50,0xcd,0xf9,0x58,0xdf,0xb7,0x5e,0x8d,0x3e,0x9c,0xdd,0x5f,0x84,0x81,0x9a, + 0x0f,0x77,0x8f,0xa5,0x43,0xe0,0x72,0x3c,0xb3,0xfa,0x8e,0x63,0x61,0xe0,0xa6,0x0d, + 0xa1,0x43,0x1c,0x3b,0x67,0x63,0x52,0xb6,0xda,0xc1,0x49,0x3e,0x2c,0x68,0x9c,0xf1, + 0x31,0xf1,0x6e,0x1e,0xda,0xa3,0xb8,0xdd,0x4c,0xe4,0x11,0x86,0xba,0x9d,0x74,0x83, + 0x88,0x3f,0x27,0xb7,0x72,0xa8,0x06,0x41,0xc5,0x52,0x66,0xeb,0x40,0xde,0xd2,0xc5, + 0xcf,0x67,0x84,0x3d,0x0e,0x32,0xb0,0x57,0x39,0x7f,0xd9,0x45,0xde,0x71,0x1e,0xdd, + 0x48,0xa9,0xea,0xc4,0x68,0x8d,0x5c,0x5c,0x8f,0x33,0x18,0x73,0x0d,0xae,0x51,0xce, + 0x9c,0x9a,0x22,0xa1,0xc6,0x80,0x56,0x20,0x3b,0x35,0x6a,0x1c,0xe0,0x77,0xf0,0x71, + 0xd4,0x40,0xf2,0xdd,0x10,0x30,0xaf,0x19,0x3e,0x36,0xe0,0xac,0xe3,0x25,0x60,0x3c, + 0xc4,0xb5,0x20,0x8f,0x02,0x5d,0x3a,0x87,0x22,0xb0,0x9d,0x1e,0x97,0xcb,0x6f,0xc7, + 0x92,0x0f,0x7a,0xca,0x75,0xf0,0xb1,0x92,0xbc,0x0a,0xdb,0x5d,0x52,0xc2,0xe7,0x11, + 0x7a,0xb2,0xfe,0xb8,0x4d,0x16,0xca,0x53,0x11,0x57,0x18,0x5c,0x14,0xcc,0xa9,0x46, + 0xe3,0x8c,0x0f,0x80,0x7e,0x48,0xd2,0xf1,0x3a,0xa9,0x2a,0xca,0xaf,0x19,0xa3,0xb2, + 0x0c,0xba,0x0b,0xb6,0x39,0x48,0x68,0x9c,0xfd,0xd0,0xaf,0xc3,0xfc,0x1c,0x06,0xa8, + 0xb8,0xd3,0x33,0x1c,0xe8,0xdd,0xc4,0x81,0xe3,0x5e,0xe3,0xef,0x8a,0xf2,0xc1,0x57, + 0xa1,0xdc,0xff,0x47,0xa0,0x7c,0xf8,0x55,0x28,0x1f,0xfc,0x23,0x50,0x3e,0xfa,0x2a, + 0x94,0x0f,0xbf,0x1c,0xe5,0x5d,0x5a,0xc5,0xd5,0xf0,0xad,0xaa,0x44,0x77,0x1a,0x75, + 0x27,0xcf,0xb6,0x98,0xfd,0x90,0x55,0x1a,0x77,0x82,0xd1,0x6c,0xd5,0xc9,0x68,0x62, + 0xcd,0xa7,0xec,0xa6,0xdd,0xd6,0x45,0xde,0x16,0x08,0x6b,0x67,0xf2,0xa6,0x35,0xa6, + 0x6a,0x9b,0xdb,0xda,0xb0,0x97,0x01,0x0c,0xc1,0x69,0x54,0xce,0xd1,0xdf,0xbb,0xd1, + 0xd9,0x17,0xa1,0xf5,0x6b,0xd4,0x79,0x11,0xdd,0x84,0xd4,0x2a,0x9a,0x46,0x5f,0xb7, + 0xf3,0x27,0x61,0x98,0x57,0xd6,0x7b,0xeb,0x22,0x51,0xf6,0x75,0x4b,0xbc,0x92,0x8b, + 0x34,0xbf,0xbc,0x69,0x05,0xc4,0x6d,0x5f,0xb9,0xc6,0x2f,0x59,0x11,0x2d,0x6e,0xe4, + 0xdf,0xf2,0x2b,0x57,0x78,0x11,0xe5,0x8b,0x55,0x90,0xdf,0x2c,0x23,0xab,0x1b,0xd7, + 0xf8,0xa6,0xd3,0x11,0xbf,0x72,0x63,0x94,0x3d,0x35,0xe0,0xc6,0x2b,0x9d,0x72,0xe0, + 0x43,0xbe,0x4c,0x12,0x04,0x8f,0xdc,0x4b,0xb5,0x0a,0xcc,0x51,0x02,0x97,0xfd,0x75, + 0xf7,0x6a,0x96,0x23,0x51,0x43,0x5e,0x51,0x4a,0x1e,0x55,0x3e,0x91,0xd3,0xe8,0x14, + 0xac,0x6d,0x9a,0xae,0xca,0xe2,0x3f,0x35,0x55,0x91,0xaa,0xab,0x74,0x21,0xf5,0x01, + 0x00,0x9c,0x0f,0x62,0x72,0x91,0x48,0x19,0x2a,0x4e,0x63,0x6c,0xf3,0x97,0x0b,0xd1, + 0xf4,0x81,0x4d,0xa3,0x5c,0x15,0xe6,0xe0,0xa0,0x48,0xc5,0x39,0x62,0x2b,0x82,0x9b, + 0x00,0xab,0xa9,0x29,0x4e,0xd2,0x02,0xf2,0x63,0x26,0x27,0xd4,0xb4,0x86,0x69,0x74, + 0x80,0x41,0x2b,0xfb,0x2e,0xb8,0xb7,0xc1,0x42,0x9a,0xd3,0x11,0x6c,0x89,0x9a,0x6c, + 0x2f,0x35,0x50,0x19,0x2b,0xd9,0x66,0x0c,0xf4,0x91,0x89,0x90,0x11,0xb7,0x95,0xac, + 0x90,0xd8,0x50,0x62,0x06,0xea,0x1b,0x32,0xd5,0xb0,0x2b,0xf8,0xe4,0x82,0x50,0x2a, + 0xe6,0x41,0x61,0x87,0x78,0x8a,0x7b,0x7d,0xdb,0x22,0x4d,0x4c,0x53,0xef,0x34,0x58, + 0xc6,0x45,0x7d,0x38,0x12,0xbd,0xb9,0x8c,0xa7,0x6d,0x17,0xde,0x58,0x4e,0x82,0xa5, + 0x92,0xa2,0xa1,0x69,0x16,0xd8,0x43,0x13,0x46,0x05,0x69,0x1d,0x05,0x00,0x9a,0x20, + 0x7f,0x59,0x4a,0x65,0x98,0x56,0xc2,0x77,0x21,0xe5,0x41,0xa4,0xe8,0x48,0xa5,0xd3, + 0x29,0x19,0x1e,0xd4,0x44,0xca,0x08,0x89,0x0c,0x75,0xd1,0x63,0xfd,0xb9,0x90,0xac, + 0x7a,0xe6,0xc6,0x5f,0xa5,0xf9,0x79,0xd7,0x6c,0x4d,0xed,0x32,0xf2,0x10,0xca,0xe7, + 0x7a,0xc8,0x86,0x8d,0xdf,0x26,0x9c,0x32,0xbc,0x8b,0x01,0x2b,0x63,0x69,0x46,0x74, + 0x3d,0xf2,0x26,0xb3,0x1e,0xac,0x85,0x97,0x95,0xa1,0x67,0x01,0x7f,0x4d,0xe5,0x33, + 0x65,0xfb,0x81,0xde,0xbe,0x7c,0xf7,0x5c,0xb7,0x32,0xb2,0xa8,0xeb,0x7c,0x0d,0xcc, + 0x4a,0x57,0x89,0xe8,0x2e,0xf9,0x7c,0x64,0xa0,0x19,0x99,0xc1,0xfe,0xf2,0xa1,0x16, + 0x53,0xdb,0xe2,0x96,0xcb,0x98,0xaa,0x6c,0xf0,0x3e,0x49,0x34,0x05,0xfd,0x59,0x5e, + 0xa8,0xe1,0x48,0xf3,0x24,0x91,0x2b,0xc1,0xcf,0x1e,0x69,0x06,0x63,0x85,0x71,0x4a, + 0x2d,0xdf,0x5c,0x90,0x64,0xeb,0x49,0x07,0x71,0x7b,0x95,0xae,0xe4,0x52,0x0b,0xaa, + 0xf0,0xc7,0x74,0xb6,0x96,0x42,0xcc,0x49,0x00,0xf1,0x93,0xa4,0xa5,0xac,0x87,0xc0, + 0x0e,0x59,0x7c,0x28,0xc6,0x97,0xbc,0x96,0xe5,0x29,0xf8,0x49,0x94,0x01,0xda,0xb7, + 0xf2,0x4e,0x6f,0x0d,0x21,0x3b,0x52,0x64,0x7a,0x94,0xeb,0xcf,0xe3,0x38,0x48,0xce, + 0xcd,0xf3,0x35,0x49,0x9a,0x66,0x32,0x91,0x36,0x64,0xac,0x31,0xb5,0x08,0x66,0x8d, + 0x33,0x3d,0xbb,0x16,0xa3,0xf2,0x18,0xb3,0x3a,0x95,0x62,0xaa,0x24,0xd9,0x9d,0x3d, + 0x4b,0x1b,0x67,0x73,0x28,0x2a,0x36,0xa1,0x61,0x08,0x9b,0xaf,0x99,0x0c,0x39,0x58, + 0xe3,0x98,0xc3,0x79,0x4a,0xe9,0x75,0x42,0x5c,0x8b,0xf3,0x6d,0xf1,0x7c,0xc3,0x13, + 0xd3,0x48,0x64,0xb2,0x06,0xbf,0xc0,0x99,0x40,0x81,0x89,0x21,0x90,0xa5,0xcc,0x8e, + 0xf8,0xdc,0x16,0x77,0x1a,0x67,0xbd,0xfe,0x0d,0xf9,0x96,0x29,0x77,0x96,0x28,0xc0, + 0x5e,0x8c,0xb1,0xaf,0xcb,0x9d,0x26,0xdc,0x16,0x22,0x1b,0x67,0xdf,0xe7,0x69,0x46, + 0xa5,0x98,0x00,0x09,0xd6,0x99,0x9f,0x4d,0x17,0xc8,0x98,0xce,0xc4,0x22,0x0d,0x97, + 0x54,0xfe,0xc9,0x71,0x5d,0x57,0x84,0xe8,0xee,0x5f,0xa3,0x8c,0xef,0x66,0x01,0x8c, + 0x95,0x51,0x15,0xce,0xe8,0x61,0xa6,0x72,0x0e,0x95,0xda,0x90,0xe0,0x58,0x16,0xd2, + 0xaf,0x05,0x3a,0x86,0xa8,0x4c,0x8c,0xbd,0xd2,0xce,0x83,0xfa,0x24,0x85,0xc6,0x58, + 0x9a,0x8e,0xdc,0xa9,0xf1,0x22,0x64,0xad,0xf2,0x48,0xaa,0x36,0xd7,0x9f,0xa8,0xd3, + 0x41,0x06,0x93,0x39,0x9f,0xe5,0xea,0x33,0x1f,0x8c,0xb1,0x62,0x4b,0xda,0xb3,0xca, + 0x23,0xec,0x1c,0x12,0x90,0x58,0x41,0x27,0x7d,0xe1,0x92,0xd6,0x25,0x21,0x47,0xb9, + 0xc5,0x34,0x4f,0x17,0xa2,0x1b,0x64,0x51,0xd7,0x3c,0xe6,0x60,0x96,0xfe,0x29,0xb8, + 0x68,0x57,0x87,0x05,0x49,0x91,0x47,0x63,0x2a,0xe9,0x91,0x74,0x93,0x0e,0x91,0xa4, + 0xe3,0x9b,0xc5,0x93,0x4f,0xae,0x03,0xad,0x8e,0xba,0x3f,0x2f,0xd7,0xcd,0x79,0xfa, + 0x01,0x09,0xb8,0xea,0x3c,0xb4,0xd0,0xc8,0x33,0x15,0x11,0x3c,0x13,0x64,0x9c,0xdc, + 0x15,0xed,0x43,0x1b,0xf4,0xf1,0x32,0x8a,0x43,0xdd,0x6e,0x4c,0x85,0x42,0x3d,0x00, + 0x6a,0x47,0x47,0x6d,0x19,0x6d,0xd3,0xec,0xc0,0x42,0x82,0x09,0x0e,0x49,0x6a,0x49, + 0xdd,0x3a,0xb4,0x59,0x43,0x86,0x29,0x58,0xd4,0xa5,0x96,0xe2,0xb6,0xed,0x7c,0x06, + 0xf8,0xee,0x4a,0xea,0x16,0xe2,0x0e,0xc8,0x98,0x05,0xe3,0x28,0x8e,0x8a,0xcb,0x8e, + 0x05,0x35,0x03,0xe1,0x43,0xdb,0x5e,0x78,0x09,0x86,0xcd,0x72,0xda,0x60,0x1b,0x38, + 0x15,0xfc,0x74,0x64,0xa8,0xe5,0x81,0x37,0xc6,0xdb,0x24,0xdf,0x6a,0x5b,0x9f,0xdb, + 0xd5,0xa1,0xbc,0x70,0xfc,0xa0,0x75,0x79,0x25,0x59,0xf8,0x18,0x3e,0x8c,0xd8,0x6e, + 0x68,0xe7,0x18,0x8c,0x53,0x2c,0xc0,0x67,0xb9,0xa1,0xee,0x85,0x24,0x4b,0x08,0x2a, + 0x5b,0x68,0xf3,0xe0,0x42,0x3b,0x61,0x4d,0x27,0xea,0x97,0x8e,0x4b,0xd3,0xb2,0x5d, + 0xe7,0x58,0xe3,0x69,0xed,0x1f,0x22,0x76,0x18,0xba,0xac,0x61,0x0e,0x07,0xd7,0x14, + 0xb1,0x1c,0xfb,0x86,0x14,0xb9,0x2a,0x81,0x94,0x62,0xb9,0x43,0xad,0xdf,0xca,0x62, + 0x99,0x7d,0x96,0x4e,0xeb,0xa8,0xf3,0x16,0x7d,0x3e,0xbc,0x8b,0x3e,0x9b,0x00,0xf7, + 0x56,0x2d,0x7e,0x23,0x17,0x72,0x31,0xa6,0xb6,0x49,0x12,0xc2,0x20,0x86,0x30,0x16, + 0x64,0x67,0x55,0xa9,0xa6,0xce,0x93,0x3c,0xbf,0xa5,0xf4,0x7c,0x1b,0x07,0x43,0x2c, + 0xb2,0x24,0x7f,0x88,0x03,0xc8,0xd2,0xab,0x2d,0x8a,0x7b,0xc3,0x46,0xcf,0x29,0x5b, + 0xbf,0x75,0xa7,0x47,0x77,0xd9,0xa9,0x49,0xfc,0x6f,0xdd,0xa9,0x8e,0x0f,0xa9,0x71, + 0x9f,0x0c,0x82,0x90,0xc9,0x24,0xa5,0x0e,0xdc,0x49,0xba,0x84,0x57,0xd5,0xe1,0x12, + 0x19,0x78,0xf8,0x43,0x8c,0x08,0x67,0xb0,0x2b,0x8b,0xe0,0x5c,0x92,0x38,0x53,0xd3, + 0x48,0x49,0x0e,0xd2,0x1b,0xdd,0x8f,0x9b,0x17,0x4a,0x3b,0xd8,0x30,0x9a,0x4e,0x65, + 0xee,0x8b,0x9f,0x2a,0x47,0x97,0x4b,0xd6,0xd3,0x9b,0xad,0xd9,0x0e,0xb1,0xf9,0xde, + 0x2d,0xbd,0xde,0x4d,0x6e,0x8c,0x53,0xbc,0x8d,0x9e,0xc7,0x77,0xa1,0xa7,0x0d,0xc7, + 0x85,0xf5,0x95,0x77,0xf3,0x04,0x81,0x76,0xfc,0x5b,0xa4,0x66,0x8a,0xf1,0x73,0xee, + 0xc2,0x50,0x88,0x11,0x75,0x04,0x41,0xb7,0x7f,0x7c,0xf2,0x13,0x53,0x1d,0xb4,0x42, + 0x98,0x8d,0xf8,0xce,0xba,0x41,0xbe,0xca,0x02,0xcc,0x51,0x23,0xf8,0x43,0x5d,0x89, + 0x32,0xfc,0x3c,0x11,0xd3,0xa6,0xfa,0x56,0x9a,0x9c,0xdc,0x85,0x26,0x6f,0x19,0xd6, + 0x1d,0x48,0xf1,0x82,0xba,0x90,0xe9,0x39,0x0c,0x98,0xbb,0x05,0x67,0x67,0xb0,0x97, + 0x3a,0x05,0xd6,0xb1,0x90,0xd6,0xae,0x92,0x4c,0xab,0xb9,0x09,0xe7,0x0c,0xad,0xa8, + 0xcd,0x69,0x52,0x2c,0x61,0xbf,0x60,0x5f,0x53,0x4a,0x59,0x6e,0x94,0xa0,0x2d,0x91, + 0xa2,0x4e,0x59,0xce,0xaa,0xb0,0x46,0xef,0xb3,0x16,0xf2,0x04,0xb7,0x04,0x59,0xb7, + 0x46,0x57,0xeb,0x13,0x08,0xb3,0xd3,0xae,0x59,0x7a,0xef,0xb4,0x4b,0xc7,0x12,0x40, + 0xe7,0xd4,0x3e,0x40,0x3f,0x5d,0x26,0xdc,0x52,0x2a,0xfe,0xa5,0x19,0xb5,0xae,0x72, + 0xd8,0x45,0x84,0x1a,0x61,0x3a,0x59,0xd2,0x73,0x39,0x3e,0x56,0x7a,0x1e,0x4b,0xfa, + 0xf8,0xf4,0xf2,0x65,0x88,0x11,0xd7,0x7b,0xff,0xd2,0xf4,0x08,0x6f,0xaf,0xe5,0x53, + 0x48,0xf6,0xcc,0x3c,0x69,0x8f,0xe0,0x9e,0x8f,0xc8,0x7c,0xba,0x47,0x0e,0x67,0xb8, + 0xb7,0xd7,0xed,0xda,0xce,0x8e,0x5f,0x35,0x2d,0x29,0x54,0x65,0x1f,0x59,0x12,0x92, + 0x7c,0xec,0xff,0xdf,0x07,0x8d,0x80,0x44,0xad,0xaf,0x84,0xc4,0x76,0x77,0x70,0xe1, + 0x6f,0x8d,0x61,0x64,0x19,0xc2,0x10,0x38,0x13,0xc5,0x80,0xf0,0xf4,0xb2,0x01,0xba, + 0x1d,0xe5,0x3c,0x59,0x91,0x8f,0xdd,0x1e,0x8b,0x98,0x63,0xb6,0x62,0xde,0xd6,0x0f, + 0xfa,0x70,0x08,0x44,0xc0,0x38,0xd9,0xad,0xb2,0x52,0x69,0x91,0x55,0x3a,0x6a,0x10, + 0x86,0x64,0x85,0x0e,0x74,0xc8,0xa6,0xf9,0xe2,0xc9,0xd6,0x48,0x83,0xa0,0xd9,0x60, + 0xc3,0xa6,0x98,0x37,0x06,0x34,0xdb,0x22,0x19,0x1d,0xa5,0x10,0x28,0x1b,0x9c,0xb0, + 0x4e,0x54,0xd9,0x21,0x19,0x69,0xdd,0x39,0x54,0xc5,0x2a,0x9c,0xdf,0x96,0xd1,0x82, + 0x6d,0xdf,0x0b,0xf2,0x73,0x20,0x42,0xa0,0x74,0xf8,0x18,0xea,0x4c,0x9a,0xb1,0x29, + 0x03,0x18,0x9d,0xf2,0x8f,0x15,0x44,0xc8,0xa6,0xf2,0x0b,0x19,0x24,0x45,0x2d,0x54, + 0x81,0x38,0x41,0xdd,0x08,0x52,0xc9,0x13,0xbd,0x3d,0xca,0x84,0xcc,0x25,0x4f,0xf3, + 0x8f,0xb6,0x52,0x6c,0x06,0x1c,0x98,0x4b,0xd3,0xdf,0x48,0x87,0x96,0x50,0x80,0x80, + 0x9e,0x88,0x83,0xe8,0xeb,0x0d,0x18,0xa6,0x35,0x29,0x96,0x02,0x8a,0x09,0x31,0x1d, + 0xd4,0x5b,0x91,0xf2,0x07,0x05,0x87,0x8c,0xad,0x36,0xe1,0x9f,0x10,0x2c,0x27,0xa9, + 0xcf,0x65,0xb2,0x24,0x47,0xad,0xec,0xe3,0x1d,0x1c,0x27,0x69,0xd2,0xea,0xe4,0xae, + 0xd7,0x6f,0x8b,0xde,0x01,0x7e,0x0e,0xfd,0x4a,0xe1,0x68,0x9d,0xa6,0xe2,0x63,0xd0, + 0x0b,0xea,0xca,0x6b,0xc3,0x76,0x8c,0x9a,0xea,0xfe,0x7d,0xe5,0x57,0x82,0xd3,0xfa, + 0xf4,0xe9,0xfd,0x87,0xa1,0x19,0x32,0x1d,0x41,0xf9,0x6c,0xb4,0xe3,0xb5,0xda,0xd3, + 0x73,0x7b,0x81,0x42,0x25,0x8f,0x3b,0x42,0xa2,0x69,0x73,0x6a,0x8e,0x56,0xa7,0x3e, + 0x9d,0xe8,0xe6,0xff,0xfd,0xdd,0xab,0x1f,0x47,0x9e,0x67,0x1f,0x21,0xcd,0x9b,0xd1, + 0xa8,0x37,0x8c,0xc8,0x75,0xf9,0xb1,0x4c,0x66,0xc5,0x7c,0x18,0xed,0xef,0x97,0xa7, + 0xb1,0xb4,0x8e,0x1c,0xe1,0xe6,0xfb,0xe8,0xc3,0x10,0xc0,0xbe,0x91,0x9f,0x3e,0x49, + 0x33,0xf0,0xf4,0xa0,0x45,0xb1,0x74,0x94,0x2c,0xcb,0x46,0x03,0x1a,0x1e,0x8c,0x4a, + 0x7b,0x31,0xc1,0x7e,0x0b,0x69,0x4c,0x46,0xd3,0x0b,0xbc,0xb2,0x4b,0x25,0xf0,0xd9, + 0x22,0xff,0x44,0x6f,0xea,0xf0,0xb4,0x55,0xf6,0x86,0x81,0xcf,0xd6,0x4e,0xbe,0xef, + 0x7d,0xa8,0xc6,0x39,0x48,0xef,0xf2,0x0c,0x3d,0x63,0x33,0xbd,0xfd,0x32,0x9f,0xf7, + 0xb6,0xbb,0x07,0x53,0x92,0xda,0xea,0x11,0xf4,0x3d,0xaf,0x5a,0xf9,0x2f,0x4b,0xf0, + 0xf4,0x2d,0xa4,0x75,0x02,0xbf,0xd0,0xf4,0xca,0x96,0x93,0x35,0x5b,0x27,0xdf,0xf7, + 0x3f,0xdc,0x3a,0x29,0xdc,0x98,0x74,0xf0,0xe1,0xd3,0x27,0xaf,0x5c,0x6d,0xea,0x53, + 0xeb,0x7c,0x12,0x3e,0xa3,0x27,0x26,0x9a,0x81,0xa1,0xd3,0xb5,0x61,0x9c,0x0e,0x64, + 0x47,0xd3,0x73,0xfb,0xe9,0x9b,0xe9,0x8e,0x85,0x34,0x85,0xaf,0x8d,0x84,0x30,0x81, + 0x54,0xc5,0x8f,0xda,0x9c,0x27,0x71,0x8c,0x69,0xac,0xb6,0x65,0x87,0x81,0x9e,0x5e, + 0x49,0x85,0x06,0xe0,0x0a,0x06,0x63,0xa4,0x2f,0x43,0x24,0x6a,0x7b,0x6a,0x46,0xfb, + 0xfd,0xd3,0x7e,0xef,0xb1,0xd7,0xf3,0x06,0x9e,0xd7,0xda,0xa7,0xef,0x80,0x77,0xbd, + 0x57,0xae,0x1f,0x84,0xe1,0x73,0x6a,0x29,0xfa,0x11,0x1a,0x41,0xfe,0xa9,0x49,0xac, + 0xd7,0x8a,0xe6,0xb5,0xad,0x26,0x34,0x65,0xeb,0x8a,0x95,0x41,0xfa,0xa1,0x2c,0x82, + 0x28,0x6e,0x5d,0x03,0x0a,0x64,0x6f,0x85,0xe8,0x15,0x36,0x29,0x9b,0x6a,0x27,0x6f, + 0x46,0xad,0x5f,0xbd,0x76,0x3d,0x8d,0xfb,0x0c,0xfc,0x3f,0xc9,0xb3,0xae,0x95,0x23, + 0xfa,0x99,0xda,0x2e,0xb4,0x95,0xf9,0xd7,0xb7,0x3f,0xff,0xd4,0x16,0xff,0xde,0xf7, + 0x1f,0x88,0x1f,0x9e,0x1a,0x03,0x74,0x28,0x74,0x81,0xd2,0x31,0x2d,0xa9,0x35,0x98, + 0xd8,0xce,0x24,0x5d,0xc0,0xf2,0x51,0xbe,0x95,0x4e,0x09,0x1a,0x3d,0x21,0xfa,0xae, + 0x8a,0x4f,0xec,0x53,0xfc,0xb0,0x7c,0xfa,0x41,0xfe,0x01,0x55,0x2d,0x29,0xe8,0xe1, + 0xd3,0xf4,0x08,0xb6,0x12,0xe2,0xa6,0xf3,0x33,0xf9,0x91,0xf8,0x11,0xb2,0xf5,0x36, + 0x8e,0x28,0x84,0x61,0x27,0xab,0x0a,0xdb,0x4c,0xa6,0x47,0x3b,0x23,0x58,0x36,0x0a, + 0xc4,0x63,0x44,0x17,0x8c,0x99,0x92,0xda,0x9d,0xd0,0xf1,0x29,0x19,0x7a,0xb2,0xd1, + 0x7f,0xb6,0xd5,0x8e,0x3f,0x53,0xcc,0xe2,0xcf,0x1d,0x0b,0x17,0x2e,0x73,0xc7,0xc0, + 0x85,0xa3,0x57,0x70,0x80,0x3e,0x22,0x13,0x48,0x9b,0xea,0x3e,0x3c,0x39,0xea,0xf5, + 0x5a,0xed,0x79,0xed,0xea,0xb7,0x7c,0xb5,0x7b,0x78,0x42,0xb7,0x16,0xf5,0x5b,0x74, + 0xb1,0x7b,0xd2,0xb3,0x56,0x2e,0x6c,0xd9,0x60,0x65,0xdf,0x0b,0x85,0xb7,0x3f,0xdf, + 0xf7,0xe6,0x1e,0x59,0xac,0xb9,0xbd,0x41,0x57,0x70,0x63,0xb1,0xef,0x2d,0x3c,0x33, + 0x69,0x61,0xef,0xe9,0x8b,0xe6,0x8b,0xda,0xf7,0x94,0x47,0x72,0x4b,0x3c,0xca,0x08, + 0xf5,0xa0,0x7c,0x6d,0xc2,0x64,0x1e,0x65,0x4a,0x7b,0x0c,0xeb,0xe1,0xb4,0xe2,0xbe, + 0x35,0x3e,0x3e,0x92,0x31,0x52,0x2c,0xf1,0x23,0xb9,0x75,0x45,0xb3,0x2e,0x1d,0x4f, + 0xd5,0x09,0x66,0x49,0x4a,0x0d,0x25,0xda,0x35,0xc0,0x9b,0x3b,0x6e,0x8d,0x5d,0x31, + 0xc5,0x4a,0xca,0xa9,0x2f,0x37,0xdf,0x1c,0xb7,0x1c,0x12,0x66,0xd4,0xa7,0xf4,0x5a, + 0xe6,0x53,0x87,0x90,0x08,0xf8,0x60,0xf6,0xcd,0xb9,0x3f,0x14,0x98,0xad,0x74,0x6c, + 0x76,0x66,0x7d,0x45,0x31,0xd2,0x7e,0x03,0x37,0x95,0x4f,0x55,0x84,0x37,0x69,0x2c, + 0x47,0x23,0xd8,0xde,0xe5,0x18,0xfe,0x09,0xbe,0xdc,0x6b,0x15,0x7e,0xb6,0x54,0xf3, + 0xa6,0xf7,0x7a,0x39,0xd6,0x76,0x80,0xca,0xd3,0x9b,0x33,0xd4,0x72,0x4c,0xd1,0xe2, + 0xd8,0x9d,0xf2,0xd6,0x4e,0xc1,0x68,0x3a,0x56,0xa3,0x58,0xc2,0x57,0x12,0xf6,0x26, + 0x99,0xc8,0x57,0x48,0xdf,0x68,0x22,0x92,0x87,0x18,0xde,0xdc,0xd3,0x36,0xc4,0x4c, + 0xad,0x0f,0x7b,0xec,0xbd,0x8e,0x83,0x4b,0x72,0x98,0x30,0x21,0x4f,0xb9,0x37,0xbc, + 0x82,0x6b,0xcc,0x50,0xeb,0x4a,0xc6,0xa5,0x45,0x0c,0x80,0xe2,0x10,0xdf,0x5d,0x3b, + 0x54,0xf8,0x94,0xfe,0x36,0x3d,0xf1,0x7f,0xff,0x8f,0xd0,0x6f,0xb8,0xa1,0x8d,0x38, + 0xb3,0x8a,0x7c,0xb9,0x31,0xc9,0xf3,0xae,0xc1,0xf1,0x92,0xd0,0xd4,0xf4,0xd3,0x64, + 0x12,0x93,0x6c,0x59,0xfb,0xa5,0xe7,0x3b,0xa4,0x9d,0x4a,0xc4,0x0c,0x4d,0xcf,0x89, + 0xf2,0xbc,0xf6,0xd5,0x04,0x59,0xab,0x1c,0x80,0x23,0x1d,0x4a,0x28,0xa4,0x77,0x0d, + 0xd3,0x8f,0x30,0xa1,0x59,0x1a,0xb8,0xbc,0x8c,0xac,0x73,0xff,0x37,0x85,0x0b,0xad, + 0x8d,0x21,0xca,0x78,0x60,0xa6,0x3e,0x04,0x83,0x1f,0x18,0xbd,0x22,0x4e,0x66,0x17, + 0xa3,0x5d,0xf1,0x38,0xac,0xe9,0x9f,0x88,0x2b,0x24,0x02,0xd9,0x45,0x2b,0xbb,0xa8, + 0xef,0xf0,0xc2,0xdb,0x2f,0x61,0x69,0xcf,0x02,0xc9,0x7c,0x07,0x97,0x2b,0xcd,0x13, + 0xf3,0x6d,0x1b,0xad,0x71,0xb7,0xa0,0x9b,0xed,0x20,0xc8,0x4a,0xe3,0x0b,0x1d,0xfd, + 0x2e,0x10,0x72,0x72,0x37,0x06,0x19,0x9f,0x1c,0x49,0x87,0xb2,0xb0,0x32,0x3a,0x46, + 0x08,0x07,0xfc,0xc8,0x3e,0xee,0x93,0xc5,0x72,0xb2,0x25,0x3d,0x67,0x65,0x03,0xea, + 0x32,0xc8,0xa6,0x9b,0x9c,0x41,0x51,0x7c,0x89,0xd4,0xd3,0x42,0xb3,0x59,0x99,0x0d, + 0x85,0xa7,0x65,0xc6,0x26,0xa0,0x5b,0x74,0x14,0x93,0x2e,0x11,0xf6,0x8e,0x29,0xf3, + 0xd7,0x11,0x97,0xd6,0xcb,0xb1,0x9c,0x92,0x52,0x45,0x08,0xea,0xb1,0x6d,0x0b,0x2d, + 0x44,0x00,0xea,0x1c,0xaf,0xf8,0x15,0x75,0x19,0xaf,0x32,0xe0,0xd1,0xaa,0xb4,0xe6, + 0xad,0x3d,0xbd,0xe1,0xd2,0x5b,0xeb,0x41,0x8a,0x84,0xbe,0x36,0x4e,0xf9,0xfa,0x59, + 0xb0,0xc7,0x4d,0xfb,0x69,0xdf,0x23,0xda,0x2d,0xd9,0x54,0x27,0x58,0xf8,0x1c,0xf2, + 0x3c,0xf0,0xf4,0x71,0x57,0x3a,0x9d,0x1a,0x88,0xd7,0x95,0xa6,0x99,0xb6,0xbc,0xd7, + 0x4c,0xca,0x12,0x2b,0x6c,0xe0,0x57,0x44,0x06,0xb2,0x4e,0x35,0x4a,0xf2,0x4c,0x89, + 0x43,0xd7,0x84,0xf9,0x78,0x21,0x26,0xd5,0x04,0x95,0x90,0x4f,0x69,0x86,0x88,0x66, + 0x75,0x9a,0x40,0x09,0x8b,0x69,0xfc,0x1b,0xa7,0x39,0x3b,0x7f,0x90,0x5c,0xbc,0x79, + 0xf2,0xaa,0xa5,0x4d,0x3a,0xec,0x3e,0x3d,0xb6,0x53,0xf5,0xf6,0x51,0xe5,0x51,0x3f, + 0xe4,0x4c,0x55,0x96,0x0e,0x48,0x1f,0x81,0x7f,0x15,0x38,0x87,0x2d,0x26,0x29,0x91, + 0x76,0x5d,0x72,0x14,0xfe,0x8d,0x74,0xd5,0xdb,0xbc,0x95,0xae,0x9e,0xa5,0x21,0xe7, + 0x0c,0x74,0xe6,0x36,0x89,0x53,0x25,0xdd,0x87,0xa7,0x5d,0x4a,0xde,0xc8,0x4b,0xcb, + 0x19,0x44,0x5f,0xd8,0xf0,0xad,0x4b,0x83,0x25,0xbc,0xc3,0x5f,0xd4,0x63,0x76,0x3f, + 0x9c,0xed,0x34,0xfb,0xf2,0xa4,0x5b,0xde,0x68,0x81,0xc9,0x53,0x78,0x05,0x32,0x37, + 0x1c,0xf8,0x94,0x51,0x68,0x29,0x06,0x2f,0xd5,0x2b,0x2e,0xb7,0x3f,0xf6,0x74,0xd9, + 0x1d,0xe3,0x74,0xee,0x11,0x25,0x5e,0x2d,0xd6,0x73,0x0d,0xfc,0xd0,0xb9,0x92,0xae, + 0x10,0x2d,0x59,0x59,0x75,0x6e,0x54,0xcd,0x47,0xb8,0x5b,0xb5,0x02,0x99,0x21,0xd8, + 0x14,0x77,0x06,0x6c,0x90,0xc0,0x3a,0x69,0x60,0x2e,0x9a,0x64,0x16,0xe8,0xc9,0x74, + 0xa4,0x7d,0xf8,0x6e,0xf1,0x12,0xfb,0x82,0x6e,0x68,0x7c,0xe9,0x86,0xf9,0xd4,0x32, + 0x24,0x63,0x69,0x9d,0x62,0x5f,0x4b,0x7a,0x61,0x87,0xab,0x3f,0x74,0xe8,0xbf,0xb6, + 0xa2,0x43,0x3a,0x9e,0xf5,0x2e,0x2d,0xe8,0x9d,0x7b,0xf8,0xf4,0x0b,0x89,0x79,0xb7, + 0xdf,0x3b,0x7a,0x78,0xfc,0xe0,0xe4,0xbb,0x7e,0x8f,0x3e,0x63,0xb1,0x57,0x4f,0x21, + 0x4d,0x52,0xba,0xec,0x31,0xbd,0x04,0x80,0xcc,0x3d,0x96,0xbe,0x7e,0xb7,0x5d,0x05, + 0xee,0xb1,0x05,0xd7,0x2d,0x2f,0x01,0x5e,0x6f,0xd0,0x03,0x7f,0xbe,0xad,0x89,0xc7, + 0x76,0x1c,0x3d,0xaa,0x03,0x2c,0xf4,0x76,0xbc,0xeb,0x8a,0x7e,0x54,0xea,0xdd,0x20, + 0x20,0x5d,0x24,0x2d,0x57,0x2a,0x42,0xc0,0xc1,0xac,0x07,0xb1,0x72,0x7c,0xc5,0xb7, + 0xf0,0xe9,0x82,0xb4,0x1c,0xfa,0x4d,0xf9,0xbc,0xe7,0xf0,0x22,0xca,0xb6,0x43,0x52, + 0x7e,0x94,0x0d,0x1c,0x79,0xe4,0xc1,0xd4,0x12,0xb0,0x9b,0x8e,0xca,0xa7,0xfb,0xfc, + 0x1e,0x34,0xc4,0x27,0xa0,0xda,0xc1,0x11,0x36,0xfa,0x03,0xb3,0xcd,0x10,0x6d,0x63, + 0xf4,0x6b,0x05,0x89,0xb5,0xc4,0xc6,0xe8,0x57,0x22,0xa3,0x2b,0xee,0xa2,0xcb,0xf5, + 0x25,0x39,0x6a,0xf3,0x97,0xdc,0x5c,0xe0,0x8a,0xd5,0x74,0x43,0x99,0x5d,0xff,0x32, + 0xdc,0xb3,0x86,0xd7,0x9c,0xcf,0xda,0xe0,0x54,0x9f,0x49,0xeb,0x8a,0x05,0x8c,0x8d, + 0xd2,0x85,0x35,0x18,0xeb,0x48,0xa7,0xf1,0xce,0x43,0x7b,0x64,0xab,0xb9,0x42,0xd3, + 0xb6,0xb0,0xca,0x93,0x6a,0xf6,0x07,0x26,0x44,0x7a,0x09,0xb3,0x9a,0x98,0xe3,0xf7, + 0x45,0x04,0x0e,0x24,0xb3,0xcd,0x03,0xeb,0x48,0x1f,0x4b,0xa4,0xe7,0x52,0x0d,0x2d, + 0xb4,0xea,0x74,0xdd,0x62,0x12,0x71,0x01,0x40,0x17,0x00,0xe9,0x3c,0x5e,0x19,0x97, + 0xc4,0x67,0xb5,0x14,0x75,0x6b,0x6b,0xc6,0x51,0x57,0x08,0xde,0x5d,0x50,0x3b,0x5e, + 0x52,0x50,0x8e,0x6e,0x3e,0x7e,0x83,0x38,0x87,0x62,0x3a,0xef,0x71,0x79,0x69,0xe0, + 0x55,0x1a,0xb3,0xae,0x28,0x72,0x23,0x2f,0x0c,0xf7,0x9b,0xe5,0xcc,0x3f,0x69,0x5a, + 0x3e,0xf6,0x58,0xb8,0xea,0x17,0xc9,0xd0,0x0c,0xeb,0xa0,0x3a,0x5b,0x0c,0xe7,0xbb, + 0xaa,0xbb,0x22,0x5f,0x26,0x9a,0x28,0xde,0xbe,0x24,0x99,0x95,0x96,0x31,0x6e,0xda, + 0xfc,0x2e,0xc8,0x38,0x0d,0xb0,0x5c,0xa9,0xce,0xe8,0x72,0xa2,0xc5,0x2c,0xe5,0x87, + 0xb4,0xcb,0x5e,0x82,0x22,0xe0,0xb7,0xde,0x95,0x05,0x31,0xdf,0x5b,0xc7,0x89,0xcf, + 0xf9,0x81,0x15,0x67,0xf6,0xde,0xad,0x07,0xfd,0xf7,0x80,0x1c,0xd7,0xfb,0x7f,0x79, + 0xf3,0xf2,0x59,0xba,0xc8,0xc0,0x8d,0xa4,0x68,0x96,0xcf,0xb9,0xad,0x9b,0xf7,0x2d, + 0x14,0xf4,0x9c,0x17,0x77,0x7a,0x77,0xa2,0xd0,0xb6,0x7d,0x88,0x9f,0x39,0x52,0xb0, + 0x68,0x31,0x1d,0x2e,0x6b,0x94,0x5a,0x95,0x45,0x4c,0x92,0x18,0x7e,0xd3,0x07,0x19, + 0x10,0x7e,0xa5,0x1a,0x88,0x08,0x0a,0x99,0xf4,0x08,0xf1,0xd2,0x4b,0xb8,0xd3,0x20, + 0x8e,0x75,0x0a,0xa7,0xa3,0x21,0x73,0x58,0xe1,0x42,0x24,0xa9,0xd3,0x2f,0x16,0x82, + 0x87,0xcb,0xad,0x53,0xd7,0x6a,0xa1,0x74,0x21,0x4b,0x7d,0x35,0x81,0x3d,0xd7,0xdb, + 0xb0,0x0d,0xe7,0x17,0x72,0xa8,0x96,0x3e,0x44,0x78,0xab,0xdf,0xc9,0x51,0x5d,0xd6, + 0xc3,0x11,0xd0,0x4e,0xce,0xf5,0xb3,0x9c,0x55,0x3c,0xda,0x2e,0x65,0x52,0x17,0x1d, + 0x5a,0xfe,0x84,0x4a,0x68,0x55,0x10,0xdb,0xba,0xda,0xe9,0xd0,0x27,0x41,0x42,0xd4, + 0xca,0xf9,0x84,0xc7,0xb4,0x44,0x0c,0x39,0xbc,0xdd,0x48,0x66,0x10,0x9b,0x65,0xb5, + 0xc8,0x3d,0x5b,0x0f,0xdc,0xaf,0xb9,0xca,0xe0,0xa4,0xfd,0xff,0x8c,0xef,0xb8,0x32, + 0x49,0xff,0xbb,0xf2,0x3d,0x32,0x5a,0x0c,0xfe,0xf4,0xf2,0xf9,0xaf,0x14,0x66,0x55, + 0xc1,0xb6,0xa7,0x74,0x38,0xae,0x7b,0x46,0xac,0xf5,0xc1,0xce,0x8b,0xf9,0x40,0xfc, + 0xd0,0x37,0x45,0x00,0x5d,0x49,0xe5,0xe0,0x9a,0xab,0x9f,0x89,0x78,0xf5,0xc7,0x77, + 0xef,0x10,0x34,0x2b,0x45,0xd1,0xe1,0x64,0x4e,0x67,0xf5,0xe5,0x91,0xae,0x6e,0x62, + 0xaa,0x62,0x69,0xaa,0x2d,0xf0,0x1b,0x00,0x23,0x6d,0x80,0x09,0x9a,0x16,0x3d,0x96, + 0x68,0x7a,0x98,0x43,0x15,0x65,0x85,0x96,0x4e,0x38,0x6c,0xd1,0xf4,0x9d,0x79,0x9c, + 0x5a,0x7e,0x9c,0xc8,0x4c,0x9b,0x56,0x6d,0x55,0x16,0x29,0xe5,0x21,0x22,0x98,0xf2, + 0xa3,0x30,0x5a,0xbc,0x7d,0xf1,0xfa,0xe7,0xb7,0xef,0x4c,0x11,0x5c,0xa7,0x0c,0x09, + 0xc2,0x52,0x58,0x65,0xd8,0x65,0x82,0x46,0x13,0xab,0x07,0xcb,0x58,0x79,0x28,0x68, + 0x6c,0xb6,0x04,0xf1,0x3f,0xcd,0x17,0xfc,0x42,0x1c,0xb6,0xdf,0x54,0xac,0xe6,0x9e, + 0x7c,0xa5,0xcb,0x22,0xfa,0x88,0x9e,0xf6,0x10,0x63,0x57,0xfa,0x69,0x93,0x3d,0xf6, + 0x11,0xfc,0x10,0x51,0x59,0x70,0xa5,0xaa,0xbc,0xc6,0x30,0x8d,0x43,0xdd,0xaf,0xa9, + 0x1d,0x0d,0x67,0x0f,0x2a,0x21,0x5d,0xad,0x78,0xc2,0xd6,0xad,0x7c,0xe3,0x0f,0xd7, + 0xf7,0xf9,0x8d,0x06,0x3e,0x6b,0x60,0x2c,0x7f,0xe1,0x27,0x97,0xd5,0x32,0xe3,0x57, + 0xf9,0x48,0x73,0xae,0x38,0xe5,0x2e,0x8a,0x31,0x1c,0x92,0xd3,0x81,0x46,0x6f,0xfc, + 0x11,0x32,0x49,0x97,0xb3,0xb9,0x3d,0x8b,0x74,0x98,0xc6,0x07,0xfd,0x17,0xb2,0x3a, + 0xfb,0x58,0xd0,0xa1,0xb9,0xbf,0x47,0x3a,0xe0,0x2c,0x35,0xea,0x0d,0xd7,0xb3,0x7f, + 0x0e,0x05,0x75,0x36,0xd5,0x06,0xdc,0x89,0xb4,0x69,0xea,0x37,0xfc,0xed,0xfe,0xfd, + 0xef,0x21,0x3c,0xf4,0x16,0xb9,0x66,0xeb,0xd4,0x81,0xe4,0x64,0xad,0xd0,0x30,0x6a, + 0xc9,0x87,0x4e,0xba,0x45,0x58,0xea,0xb6,0xf7,0xf6,0x0d,0xe4,0xc7,0x1e,0x42,0x54, + 0x91,0x9a,0xe8,0x94,0x27,0xe8,0x14,0xe8,0xc6,0x39,0x34,0x41,0x3b,0x26,0x27,0x95, + 0xa6,0xea,0x50,0xb3,0x68,0x4f,0x62,0xc5,0xf6,0xc0,0xb4,0xdb,0xaf,0x19,0x84,0x82, + 0x8b,0xa1,0xb5,0xdb,0x6b,0xeb,0xf0,0xd5,0xfd,0x26,0xc0,0xb0,0x3f,0xc4,0x6f,0x5a, + 0xc9,0x59,0x07,0x7b,0x75,0x69,0xc3,0x54,0x71,0x29,0x59,0x91,0x65,0xbf,0x4f,0x4f, + 0x22,0xee,0x89,0x2d,0x04,0x25,0x03,0xc3,0x5b,0x66,0xac,0xed,0xbe,0x6c,0x3a,0xfb, + 0xfb,0xdf,0xfe,0x37,0xf6,0x87,0x2c,0xce,0x7c,0x6e,0x6d,0xd4,0x00,0x98,0x46,0xed, + 0x2b,0xea,0x36,0x4c,0xe9,0xc5,0xb0,0x10,0x7f,0xaf,0xbd,0x5e,0x10,0xb0,0xad,0x28, + 0xfa,0x59,0x1d,0x35,0xb8,0xf2,0x0c,0x19,0x3a,0xef,0x2e,0x33,0xca,0x0d,0x82,0x2c, + 0x83,0xfa,0xf0,0x09,0x5a,0xf7,0x63,0x67,0xb5,0x5a,0x75,0x48,0x17,0x3a,0xcb,0x3c, + 0xd6,0xce,0x11,0xf1,0xa9,0x85,0x41,0xdd,0x8c,0x08,0x35,0x93,0x91,0xc3,0x86,0x3e, + 0x40,0xf4,0x40,0x1b,0x5d,0x68,0xd9,0x2c,0x3e,0x90,0xac,0xe4,0x7e,0x7a,0xde,0x2a, + 0xe6,0x74,0x2a,0xd2,0x1b,0x6e,0x54,0x23,0xb6,0x4d,0x2c,0x23,0x97,0x1a,0x69,0xca, + 0x54,0xd6,0x1c,0x5b,0x5d,0x52,0x27,0xc8,0x98,0xc2,0x05,0xf3,0x30,0x07,0xe5,0xbc, + 0xa4,0xc5,0x9e,0x1b,0xb1,0x40,0x0f,0xb0,0x65,0xf2,0xb6,0x55,0xd2,0x68,0x4b,0x0d, + 0xce,0xab,0xf1,0xc0,0x0d,0xc5,0xe7,0x37,0xac,0xbe,0x14,0x82,0x46,0x5c,0x3d,0x63, + 0xbe,0xd9,0xcc,0x12,0x3c,0x7e,0x87,0xd8,0x34,0x5d,0x16,0x2e,0xae,0x75,0x1d,0xd2, + 0xe5,0x9c,0xeb,0x36,0x15,0x12,0x8d,0x03,0x34,0x7b,0xdc,0x70,0x59,0x15,0x54,0x57, + 0x07,0x9d,0x9d,0x6f,0xf5,0x5d,0x6d,0x4f,0xe6,0x79,0xb5,0x3f,0xbd,0x9e,0x5d,0x89, + 0xd4,0xa1,0xd2,0xb9,0xcd,0x22,0x38,0xdb,0x4a,0xa7,0x00,0xce,0xd8,0x6b,0xb1,0xe4, + 0x02,0x17,0x17,0xc0,0x6b,0x3a,0xf8,0x59,0x30,0x58,0xa6,0x09,0x84,0xe3,0x21,0x37, + 0x5e,0x73,0xf6,0xcf,0xe5,0x25,0x9f,0xf0,0x6b,0xd1,0x37,0xfc,0xa2,0xb6,0xde,0x71, + 0x74,0x2e,0x5d,0x9b,0x1d,0x8c,0x61,0x3a,0xc9,0x35,0xd6,0x5e,0xb3,0x46,0x60,0xcc, + 0x9b,0xd6,0xd8,0xb6,0x56,0xaf,0x76,0x34,0xd5,0xa2,0x2d,0xde,0xd0,0xba,0x40,0x69, + 0xde,0x4d,0x66,0x1c,0x21,0x1f,0x80,0x26,0x31,0x11,0xcb,0xbc,0xea,0x8d,0xa3,0x3e, + 0x2e,0xb9,0xff,0xb7,0xe7,0xc6,0xbf,0x99,0x07,0x12,0x1f,0x57,0x49,0xf8,0xc8,0xba, + 0x46,0xdd,0xcc,0xd5,0xb1,0xb5,0xf5,0x5c,0x72,0x5a,0x42,0x39,0x36,0x12,0x44,0x0c, + 0x6e,0xdb,0x46,0x4e,0xba,0xaa,0xf7,0xb8,0x0a,0x22,0x16,0xfc,0xb1,0x9c,0x53,0x77, + 0x66,0x54,0x98,0x9e,0xb4,0x30,0x0f,0x66,0x42,0x0b,0x20,0x81,0x62,0x4f,0x2a,0x96, + 0x99,0x3d,0x19,0xc0,0x0a,0x1d,0xf3,0xce,0x75,0x6d,0x12,0x73,0xc4,0x6a,0xda,0xa9, + 0x00,0xaf,0x27,0x5c,0x8b,0xd0,0x45,0xd3,0x36,0xbe,0xbf,0x4c,0x5e,0xf0,0xfa,0xd5, + 0x95,0x3f,0x12,0xbc,0x70,0xd4,0xe9,0xd3,0x97,0xb7,0x3b,0xdc,0x10,0x6e,0xbd,0x9e, + 0x14,0xcd,0x71,0x59,0xc5,0xe4,0x3c,0x93,0x1e,0xb2,0x3c,0x68,0xeb,0x8f,0x51,0xd2, + 0x44,0xe6,0xdd,0x76,0xf2,0xcf,0x31,0xa5,0xe2,0xdd,0x83,0xe3,0xe3,0x56,0xcb,0xb5, + 0xd9,0xeb,0x35,0x8c,0xb1,0xf5,0x65,0x25,0xb6,0x9f,0x3e,0x39,0xee,0xac,0x8e,0xd4, + 0xa7,0x4f,0xa6,0xd4,0x3c,0xa6,0xac,0x4b,0x33,0xd5,0x5b,0xab,0x7b,0x67,0xa3,0x12, + 0x5b,0xf2,0x2e,0xf8,0x02,0xed,0xd1,0x0f,0x6d,0x64,0xe6,0x42,0xe7,0x62,0xcd,0x17, + 0x65,0xba,0x5e,0xe0,0xa2,0xb9,0x5c,0x64,0xc0,0xb2,0xe9,0x60,0x67,0x69,0xf7,0xe9, + 0x53,0x49,0xb6,0xd3,0xde,0xb6,0xc5,0xf5,0xbd,0xa1,0x4b,0xdc,0xa1,0x4b,0x7b,0x0e, + 0x69,0xcd,0x84,0x8b,0x51,0x49,0xca,0xe3,0x8a,0x94,0xf4,0x3e,0x0e,0x87,0x94,0xd9, + 0x77,0xb8,0x80,0x24,0xbe,0x07,0x52,0xae,0xfb,0x9f,0x2d,0x42,0xe8,0xed,0x5f,0x6c, + 0x2b,0x49,0xef,0xb0,0x7e,0xb7,0x5a,0x38,0x3b,0x73,0x1a,0x25,0x14,0xb6,0xb8,0x73, + 0x37,0x24,0x6a,0xa8,0x49,0xa5,0xf7,0x7d,0x36,0xea,0xb5,0x4a,0x3a,0x96,0x36,0x51, + 0x33,0x64,0xd3,0x9c,0xf1,0x13,0x3a,0x35,0x73,0x56,0xd2,0x34,0xa3,0x7f,0x1a,0xe2, + 0x25,0xb2,0x42,0x0a,0x55,0x35,0x2f,0xdb,0xfd,0x1e,0xa7,0x0a,0x11,0xe2,0x85,0x9f, + 0x90,0x30,0xd4,0x63,0x9d,0xdd,0x3c,0xae,0x98,0x92,0x0d,0xd7,0x04,0x7e,0x3d,0x5a, + 0x28,0x51,0x07,0xe2,0xf8,0x69,0x6e,0x62,0xa6,0xf8,0x1c,0x86,0x37,0x64,0xf9,0x09, + 0xb7,0x5c,0xa7,0x8f,0xd1,0x40,0xe2,0xf9,0x75,0x1b,0x4e,0x73,0xfb,0x6d,0x4d,0xbc, + 0x9b,0x11,0xba,0xa6,0x35,0x54,0xbc,0x85,0x74,0xdc,0x83,0xc5,0x8f,0x34,0x7b,0x6d, + 0x24,0x69,0x57,0x14,0x31,0xd3,0xfb,0xc9,0x78,0xd1,0xd6,0x70,0xf7,0x1c,0x99,0x84, + 0x1e,0x21,0xb5,0x31,0x65,0xd7,0x4a,0x8b,0x94,0xaa,0xb7,0xc8,0x4e,0x68,0x21,0x7a, + 0x03,0x1f,0x9f,0xc4,0xee,0x18,0xb7,0xcc,0x18,0x38,0xd3,0xaf,0x59,0x77,0x46,0xe6, + 0x25,0x41,0x9c,0x64,0x8a,0x26,0xd7,0x29,0xcc,0x41,0x84,0x79,0x3d,0x50,0xdb,0x9c, + 0x8e,0x95,0x4f,0x1e,0x90,0x44,0x3a,0x6f,0x0a,0x62,0x03,0x87,0x04,0xf6,0x19,0x41, + 0x00,0x37,0x35,0x01,0x6d,0xba,0x52,0x35,0xce,0x97,0x8f,0xf4,0x96,0x79,0x31,0x60, + 0xe9,0xc3,0x2c,0x25,0x9e,0xfd,0xfc,0xe6,0xb9,0x6d,0xb6,0x57,0xbe,0xf8,0x99,0x5a, + 0x05,0x4d,0xc6,0x4e,0x90,0xd8,0x17,0x71,0xd1,0x59,0xf7,0x06,0x9b,0x92,0x0a,0x3d, + 0xad,0xc2,0x8f,0x97,0xf0,0x63,0x25,0x54,0x9e,0xf0,0x9c,0x16,0x67,0x9d,0x64,0x20, + 0xf7,0x3f,0x17,0x01,0x01,0x41,0x56,0xa3,0x52,0xd3,0x65,0xc4,0xdd,0x76,0x5c,0x59, + 0xa7,0x2e,0x1a,0xce,0x84,0xe6,0x26,0xd6,0xb9,0xac,0x97,0xf1,0x27,0xb4,0x6e,0xdb, + 0xbc,0xb9,0x98,0xc0,0x98,0x04,0x9c,0xce,0x80,0xa1,0x0d,0x91,0x62,0xba,0x60,0x77, + 0xd4,0x2c,0xa3,0xca,0x36,0xfe,0x80,0xff,0x4d,0x0d,0x51,0xac,0xd2,0xf2,0x19,0x02, + 0xaa,0x47,0x62,0x6f,0x6f,0x53,0xec,0x8a,0x00,0x05,0xc2,0x64,0xf0,0x3a,0xef,0xa3, + 0xe3,0x4a,0x05,0xa7,0x12,0xc9,0xc2,0x3c,0x92,0x40,0xfd,0x61,0x05,0x7e,0x28,0xa7, 0x83,0x4d,0x80,0xb4,0x6a,0x47,0x1a,0xe5,0x94,0x95,0xb2,0x63,0x64,0xe6,0x29,0xe7, - 0x9c,0xc6,0x3c,0xf8,0x6f,0x5e,0x45,0x5c,0xd6,0xd1,0xb8,0x14,0x52,0xd9,0x54,0xb7, - 0xa4,0xa0,0x19,0x6a,0x2a,0x5b,0x6d,0x5b,0x54,0x30,0xc6,0xb6,0xe2,0xeb,0xa7,0x4f, - 0xdf,0xd4,0x46,0x3a,0x6a,0x8e,0x71,0x65,0x31,0x6e,0xbd,0x14,0xe7,0x0c,0x73,0x64, - 0xc4,0xda,0x5d,0x63,0x3e,0x77,0x16,0x4e,0x98,0x4d,0x5d,0x2b,0x43,0x1c,0x21,0x7b, - 0xbb,0x0d,0xea,0xdf,0x28,0xd6,0x5e,0xd4,0x1a,0x70,0xe8,0x01,0x7e,0x55,0x8c,0xde, - 0x16,0x24,0x8f,0xcd,0x85,0xad,0xbf,0x50,0xca,0xd4,0xf2,0x91,0xe1,0xc6,0xc1,0x44, - 0x36,0xbb,0xff,0xf3,0xa2,0xdb,0x76,0x42,0x6c,0x2c,0xac,0xe7,0xdd,0xbf,0xaf,0x7f, - 0x83,0x1e,0x75,0xea,0x95,0x2f,0x17,0x20,0x23,0x69,0x1e,0x1f,0xd8,0x52,0xd3,0xd5, - 0xb3,0xf7,0x3d,0x0e,0x7a,0x96,0xa6,0xc2,0x55,0x3d,0xb4,0xa1,0xeb,0x8a,0x18,0x57, - 0x03,0xbe,0xef,0x55,0x95,0x2a,0x0b,0xbf,0xac,0xdc,0xdb,0x7f,0x5a,0xc4,0xa3,0xd7, - 0x5e,0x94,0xc3,0xae,0x4d,0xb4,0xbc,0xe9,0x94,0xba,0xdf,0x09,0x53,0x4b,0xe7,0x56, - 0x54,0xe4,0x16,0x49,0x4a,0xea,0xfa,0xb6,0x3c,0xb2,0xb2,0x22,0xff,0x5d,0x77,0xa3, - 0x16,0x74,0xf3,0xfb,0x55,0xa9,0x72,0xdf,0xf3,0x7d,0xf2,0xa6,0x5f,0x18,0x08,0xb3, - 0xed,0xd1,0xa7,0x3c,0xba,0x78,0x26,0x46,0x02,0x2e,0x9e,0x2f,0x47,0xc6,0x1d,0xe2, - 0x92,0x31,0x49,0x74,0x95,0x83,0xb6,0xf0,0x35,0xd7,0xd7,0x70,0xe7,0xea,0x5a,0x5f, - 0xa6,0x97,0x07,0xbd,0xe1,0x72,0x85,0x0c,0x71,0xf9,0xfd,0x71,0xaf,0x2d,0xaa,0x9f, - 0x0f,0xb0,0x98,0xa5,0xee,0xd4,0x6b,0x75,0xa6,0x52,0x27,0xb4,0xbe,0x88,0xe6,0x37, - 0xfa,0x82,0xf8,0xf4,0x49,0xef,0xdb,0xf6,0xef,0x88,0x53,0x71,0xd4,0x12,0xce,0xe1, - 0x35,0x08,0xd9,0x64,0x34,0xb1,0x5c,0x6f,0x88,0x5f,0x18,0x80,0x5f,0xfb,0xfb,0x1a, - 0x96,0x86,0x56,0xdf,0xda,0x08,0x41,0x6d,0x4b,0xd4,0xdb,0xbe,0x38,0x72,0x01,0x08, - 0x37,0x46,0xe1,0x75,0xdf,0x47,0x1f,0x44,0x57,0xf4,0x6d,0xca,0x45,0xd0,0x2e,0xb0, - 0x06,0x48,0x7d,0x61,0x56,0xe4,0x2b,0x67,0x82,0xc9,0x4f,0xd7,0xfa,0xbd,0x5e,0x05, - 0x53,0xc5,0xb8,0x42,0x65,0xce,0xb8,0xe3,0x89,0x7d,0xac,0x5b,0x81,0x51,0xb1,0x45, - 0x91,0x5d,0x93,0x8e,0x99,0x47,0xe2,0xc2,0xca,0x92,0x4b,0x4b,0xc2,0xa2,0xbc,0x75, - 0xcd,0xdd,0x51,0xd7,0x7b,0xf5,0xd8,0x4e,0xf3,0xa2,0x59,0x91,0xd0,0x32,0xae,0x24, - 0x17,0x65,0xac,0xdc,0x3d,0x9d,0xcb,0x10,0x1b,0x24,0x8b,0x69,0x0c,0x1f,0x15,0x7c, - 0xf8,0x8d,0x7c,0x1d,0x1d,0xcc,0x0f,0x39,0x8b,0xb0,0xd1,0x3c,0xfc,0x18,0xb3,0xdb, - 0x04,0x04,0xe7,0xf2,0x92,0x58,0xfe,0xf3,0xf8,0x37,0x44,0xe8,0x3e,0x7d,0x6b,0xba, - 0xd2,0x60,0x5a,0x1d,0xc0,0x42,0xba,0x65,0xfb,0x1d,0x4a,0x96,0x19,0x20,0xe4,0x69, - 0x30,0xbe,0x20,0x48,0xba,0xab,0x63,0x1b,0x27,0x1d,0x08,0x35,0x9e,0x32,0x1a,0x18, - 0x46,0xf7,0xa9,0xa9,0x6f,0xcf,0x56,0x11,0x18,0xa4,0x6e,0xca,0x38,0xa7,0x53,0xa4, - 0x11,0x11,0x7d,0x4b,0x31,0xde,0x45,0xf8,0xfd,0xf9,0x87,0x56,0xd9,0x71,0xb6,0x45, - 0xae,0x85,0xab,0x02,0xda,0xe2,0xae,0x45,0xac,0x5a,0x52,0xbc,0xb6,0xc1,0xce,0x14, - 0x4e,0x84,0xa9,0x9c,0xe8,0xe2,0x33,0xdb,0x5a,0xb1,0x5e,0x3f,0xb1,0xd5,0x13,0x71, - 0x25,0xd6,0xea,0x27,0xe2,0x0e,0x05,0x14,0x61,0x2a,0x28,0x5c,0x3f,0x71,0xf6,0xaf, - 0x5b,0x47,0xee,0x73,0x93,0xca,0x16,0x5b,0xc4,0x68,0xe2,0xfa,0x66,0xf8,0x6b,0x55, - 0x66,0x43,0xe5,0xad,0xcc,0xee,0x64,0x7a,0xc9,0x67,0x2b,0xd1,0xae,0x48,0xb2,0xe4, - 0xbd,0x88,0xc1,0x15,0x41,0x6d,0x46,0x64,0xe4,0xb4,0x41,0xb6,0xf2,0x57,0x89,0xb5, - 0xb6,0x7c,0xa5,0x60,0xa7,0x89,0x56,0xda,0x97,0x14,0x4a,0x37,0xa3,0xf0,0x63,0x9b, - 0x72,0x4b,0xbd,0x8a,0x55,0xd9,0x32,0x96,0xc6,0x9d,0xb6,0xd5,0x54,0x56,0x00,0x8e, - 0xa4,0x2f,0x5a,0xae,0xbd,0xa8,0x2b,0x55,0xf8,0xd1,0xa8,0xd5,0x9e,0xee,0xfd,0xf8, - 0x37,0x1b,0x9d,0x69,0x73,0xda,0x13,0xe3,0xa5,0xaa,0x75,0xa4,0x01,0x95,0xc8,0xa6, - 0xe1,0x5c,0xad,0xe0,0x07,0x59,0xe5,0x2c,0x98,0x5c,0xda,0xb0,0x4c,0x83,0xd2,0x4d, - 0xb2,0x65,0x5d,0xc8,0x94,0xb2,0x4d,0x44,0x38,0x97,0xb6,0x8a,0xcb,0x19,0x31,0x1c, - 0x41,0xf8,0x6f,0x88,0x35,0x90,0xf6,0x2b,0x6c,0xd6,0xb4,0xa5,0x31,0x9d,0x34,0xb0, - 0x31,0x3c,0x03,0xec,0x15,0x9f,0xea,0x9a,0xc6,0x3a,0xaa,0x06,0xe8,0x46,0x5e,0x7f, - 0x4d,0x6a,0xdf,0x7b,0x19,0x09,0x3d,0x11,0x0b,0xbf,0xfa,0x2d,0xde,0xa1,0xf8,0x0e, - 0x84,0xe1,0x6d,0xd6,0xf8,0x52,0xa3,0x35,0xfd,0x83,0x69,0xd6,0x1c,0x6b,0x0a,0xdf, - 0x6e,0x5c,0x2b,0xd9,0xc1,0x72,0x15,0xf3,0xb7,0x59,0x3e,0xdc,0xaf,0xdc,0x3a,0xac, - 0x03,0x99,0xbf,0x8a,0x2f,0x76,0x16,0x18,0x4e,0x51,0x3f,0x49,0x9e,0x23,0x95,0xeb, - 0xbe,0x09,0xc0,0x86,0xe2,0xba,0x3e,0xef,0x79,0x12,0xae,0xcd,0x2a,0x5d,0xf7,0x16, - 0xcf,0x66,0x6f,0xd5,0xc9,0x61,0x9c,0xf8,0xb0,0xb2,0xca,0x37,0xa7,0x26,0x16,0x5d, - 0x68,0xbe,0x4d,0x37,0x44,0x95,0x6f,0xdc,0x0c,0x82,0x33,0x15,0x8d,0xf7,0x67,0x4e, - 0x77,0x52,0x16,0x8b,0x40,0x39,0xe1,0xf6,0xfc,0x45,0x2f,0x79,0xf3,0x0a,0x26,0x73, - 0xdd,0x4e,0x4d,0xab,0x77,0xd6,0x67,0x55,0xc4,0xdc,0xa6,0xad,0x15,0x55,0xcb,0x52, - 0x62,0x33,0x32,0xe6,0xf6,0x7a,0xaf,0x26,0x73,0xb5,0x7c,0xea,0x8f,0x4b,0x3a,0x5a, - 0xb4,0x4d,0x41,0xf4,0x5a,0x06,0xee,0xc5,0x6a,0xfe,0xfe,0x1f,0xff,0x1b,0xfe,0xf8, - 0xf7,0xff,0xf8,0xcf,0xd6,0xdf,0xbe,0xe8,0xe7,0xd4,0xe1,0xb1,0x9e,0x39,0x83,0x6d, - 0x86,0x51,0x5e,0x19,0x1c,0xea,0x3b,0xd5,0x72,0xbd,0xd1,0x95,0x63,0x32,0xf5,0x8d, - 0xeb,0x18,0x0e,0x10,0x88,0x10,0x7a,0xe2,0xb1,0xf0,0xe8,0xfd,0x43,0x54,0x76,0x17, - 0xe4,0x1f,0x00,0x8c,0x3e,0x0f,0x37,0xfc,0x89,0x3e,0x35,0xed,0x2a,0x6e,0x73,0x7e, - 0xcc,0x2f,0xa5,0x20,0x25,0x0a,0xe9,0xdf,0x4e,0xb9,0xda,0xf0,0x25,0xbb,0x02,0x77, - 0x28,0xce,0x5a,0xa4,0x3e,0x14,0xdb,0xc7,0x86,0x15,0x83,0x49,0x39,0x43,0x04,0xfb, - 0xe2,0xfe,0x7d,0x11,0xfa,0xd4,0xcb,0xe9,0x32,0x7f,0xd7,0x06,0x7d,0xfd,0x80,0x4c, - 0x7d,0xd8,0xc6,0x99,0x29,0x46,0x7a,0xba,0xe0,0xe9,0x34,0x4c,0x5d,0x6f,0xd6,0xc0, - 0xa9,0xf4,0xdc,0x16,0x87,0xb7,0x16,0xba,0xc5,0x4d,0x5d,0x52,0xe4,0x1a,0x40,0xe0, - 0xf5,0x02,0xb6,0x79,0xeb,0xd4,0x4d,0x05,0xe8,0xba,0xed,0x71,0x65,0xa1,0xd3,0x67, - 0x12,0x0e,0x2d,0xa4,0x44,0xa7,0x1b,0x5f,0x00,0xc9,0x02,0xda,0xb3,0x65,0x76,0x6c, - 0x9f,0x1b,0x61,0xa0,0x53,0x7a,0xff,0xd8,0x3e,0xed,0xff,0x86,0xc6,0xf3,0x8b,0x48, - 0x45,0xfa,0xa1,0x13,0xed,0x5f,0x6a,0xb5,0x26,0x4a,0xda,0xd6,0x9b,0x3e,0xcd,0x11, - 0x02,0xa0,0x3a,0xff,0xb2,0x66,0x97,0xdf,0x65,0x76,0xda,0xd5,0xff,0x7c,0xe7,0xff, - 0x03,0xf8,0xc9,0xf9,0xc4,0xcf,0x73,0x00,0x00, + 0x9c,0xc6,0xbc,0x0d,0xc1,0xbc,0x9f,0xb9,0xac,0xa3,0x71,0x29,0xa4,0xb2,0xa9,0x6e, + 0x49,0x41,0x33,0xd4,0x54,0xb6,0xda,0xb6,0xa8,0x60,0x8c,0x6d,0xc5,0xd7,0x4f,0x9f, + 0xbe,0xa9,0x8d,0x74,0xd4,0x1c,0xe3,0xca,0x62,0xdc,0x7a,0x29,0xce,0x19,0xe6,0xc8, + 0x88,0xb5,0xbb,0xc6,0x7c,0xee,0x2c,0x9c,0x30,0x9b,0xba,0x56,0x86,0x38,0x42,0xf6, + 0x76,0x1b,0xd4,0xbf,0x53,0xac,0xbd,0xa8,0x35,0xe0,0xd0,0x5b,0x0d,0x54,0x31,0x7a, + 0x5b,0x90,0x3c,0x36,0x17,0xb6,0xfe,0x42,0x29,0x53,0xcb,0x47,0x86,0x1b,0x07,0x13, + 0xd9,0xec,0xfe,0xcf,0x8b,0x6e,0xdb,0x09,0xb1,0xb1,0xb0,0x9e,0x77,0xff,0xbe,0xfe, + 0x0d,0x7a,0xd4,0xa9,0x57,0xbe,0x71,0x81,0x8c,0xa4,0x79,0xa6,0x62,0x4b,0x4d,0x57, + 0xcf,0xde,0xf7,0x38,0xe8,0x59,0x9a,0x0a,0x57,0xf5,0x24,0x8b,0xae,0x2b,0x62,0x5c, + 0x0d,0xf8,0xbe,0x57,0x55,0xaa,0x2c,0xfc,0xb2,0x72,0x6f,0xff,0xbd,0x15,0x8f,0xde, + 0x05,0x52,0x0e,0xbb,0x36,0xd1,0xf2,0xa6,0x53,0xea,0x7e,0x27,0x4c,0x2d,0x9d,0x5b, + 0x51,0x91,0x5b,0x24,0x29,0xa9,0xeb,0xdb,0xf2,0xc8,0xca,0x8a,0xfc,0x77,0xdd,0x8d, + 0x5a,0xd0,0xcd,0x2f,0x9d,0xa5,0xca,0x7d,0xcf,0xf7,0xc9,0x9b,0x7e,0x61,0x20,0xcc, + 0xb6,0x47,0x9f,0xf2,0xe8,0xe2,0x99,0x18,0x09,0xb8,0x78,0xbe,0x1c,0x19,0x77,0x88, + 0x4b,0xc6,0x24,0xd1,0x55,0x0e,0xda,0xc2,0xd7,0x5c,0x5f,0xc3,0x9d,0xab,0x6b,0x7d, + 0x99,0xde,0xa8,0xf4,0x86,0xcb,0x15,0x32,0xc4,0xe5,0xf7,0xc7,0xbd,0xb6,0xa8,0x7e, + 0x3e,0xc0,0x62,0x96,0xba,0x53,0xaf,0xd5,0x99,0x4a,0x9d,0xd0,0xfa,0x22,0x9a,0xdf, + 0xe8,0x0b,0xe2,0xd3,0x27,0xbd,0x6f,0xdb,0xbf,0x23,0x4e,0xc5,0x51,0x4b,0x38,0x87, + 0xd7,0x20,0x64,0x93,0xd1,0xc4,0x72,0xbd,0x21,0x7e,0x61,0x00,0x7e,0xed,0xef,0x6b, + 0x58,0x1a,0x5a,0x7d,0x6b,0x23,0x04,0xb5,0x2d,0x51,0x6f,0xfb,0xe2,0xc8,0x05,0x20, + 0xdc,0x18,0x85,0xd7,0x7d,0x1f,0x7d,0x10,0x5d,0xd1,0xb7,0x29,0x17,0x41,0xbb,0xc0, + 0x1a,0x20,0xf5,0x85,0x59,0x91,0xaf,0x9c,0x09,0x26,0x3f,0x5d,0xeb,0xf7,0x7a,0x15, + 0x4c,0x15,0xe3,0x0a,0x95,0x39,0xe3,0x8e,0x27,0xf6,0xb1,0x6e,0x05,0x46,0xc5,0x16, + 0x45,0x76,0x4d,0x3a,0x66,0x1e,0x89,0x0b,0x2b,0x4b,0x2e,0x2d,0x09,0x8b,0xf2,0xd6, + 0x35,0x77,0x47,0x5d,0xef,0xd5,0x63,0x3b,0xcd,0x8b,0x66,0x45,0x42,0xcb,0xb8,0x92, + 0x5c,0x94,0xb1,0x72,0xf7,0x74,0x2e,0x43,0x6c,0x90,0x2c,0xa6,0x31,0x7c,0x54,0xf0, + 0xe1,0xd7,0x14,0x76,0x74,0x30,0x3f,0xe4,0x2c,0xc2,0x46,0xf3,0xf0,0x63,0xcc,0x6e, + 0x13,0x10,0x9c,0xcb,0x4b,0x62,0xf9,0xcf,0xe3,0xdf,0x10,0xa1,0xfb,0xf4,0xad,0xe9, + 0x4a,0x83,0x69,0x75,0x00,0x0b,0xe9,0x96,0xed,0x77,0x28,0x59,0x66,0x80,0x90,0xa7, + 0x79,0xcd,0x4d,0xed,0x90,0x92,0x0f,0xbb,0x38,0xe9,0x40,0xa8,0xf1,0x94,0xd1,0xc0, + 0x30,0xba,0x4f,0x4d,0x7d,0x7b,0xb6,0x8a,0xc0,0x20,0x75,0x53,0xc6,0x39,0x9d,0x22, + 0x8d,0x88,0xe8,0x5b,0x8a,0xf1,0x2e,0xc2,0xef,0xcf,0x3f,0xb4,0xca,0x8e,0xb3,0x2d, + 0x72,0x2d,0x5c,0x15,0xd0,0x16,0x77,0x2d,0x62,0xd5,0x92,0xe2,0xb5,0x0d,0x76,0xa6, + 0x70,0x22,0x4c,0xe5,0x44,0x17,0x9f,0xd9,0xd6,0x8a,0xf5,0xfa,0x89,0xad,0x9e,0x88, + 0x2b,0xb1,0x56,0x3f,0x11,0x77,0x28,0xa0,0x08,0x53,0x41,0xe1,0xfa,0x89,0xb3,0x7f, + 0xdd,0x3a,0x72,0x9f,0x9b,0x54,0xb6,0xd8,0x22,0x46,0x13,0xd7,0x37,0xc3,0x5f,0xab, + 0x32,0x1b,0x2a,0x6f,0x65,0x76,0x27,0xd3,0x4b,0x3e,0x5b,0x89,0x76,0x45,0x92,0x25, + 0xef,0x45,0x0c,0xae,0x08,0x6a,0x33,0x22,0x23,0xa7,0x0d,0xb2,0x95,0xbf,0x4a,0xac, + 0xb5,0xe5,0x2b,0x05,0x3b,0x4d,0xb4,0xd2,0xbe,0xa4,0x50,0xba,0x19,0x85,0x1f,0xdb, + 0x94,0x5b,0xea,0x55,0xac,0xca,0x96,0xb1,0x34,0xee,0xb4,0xad,0xa6,0xb2,0x02,0x70, + 0x24,0x7d,0xd1,0x72,0xed,0x45,0x5d,0xa9,0xc2,0x8f,0x46,0xad,0xf6,0x74,0xef,0xc7, + 0xbf,0xd9,0xe8,0x4c,0x9b,0xd3,0x9e,0x18,0x2f,0x55,0xad,0x23,0x0d,0xa8,0x44,0x36, + 0x0d,0xe7,0x6a,0x05,0x3f,0xdd,0x2b,0x67,0xc1,0xe4,0xd2,0x86,0x65,0x1a,0x94,0x6e, + 0x92,0x2d,0xeb,0x42,0xa6,0x94,0x6d,0x22,0xc2,0xb9,0xb4,0x55,0x5c,0xce,0x88,0xe1, + 0x08,0xc2,0x7f,0x43,0xac,0x81,0xb4,0x5f,0x61,0xb3,0xa6,0x2d,0x8d,0xe9,0xa4,0x81, + 0x8d,0xe1,0x19,0xf4,0xa3,0x23,0xb0,0xaa,0xba,0xb1,0x8e,0xaa,0x01,0xba,0x91,0xd7, + 0x5f,0x93,0xda,0xf7,0x5e,0x46,0x42,0x4f,0xc4,0xc2,0xaf,0x7e,0x8b,0x77,0x28,0xbe, + 0x03,0x61,0x78,0x9b,0x35,0xbe,0xd4,0x68,0x4d,0xff,0x8a,0x9c,0x35,0xc7,0x9a,0xc2, + 0xb7,0x1b,0xd7,0x4a,0x76,0xb0,0x5c,0xc5,0xfc,0x6d,0x96,0x0f,0xf7,0x2b,0xb7,0x0e, + 0xeb,0x40,0xe6,0xaf,0xe2,0x8b,0x9d,0x05,0x86,0x53,0xd4,0x4f,0x92,0xe7,0x48,0xe5, + 0xba,0x6f,0x02,0xb0,0xa1,0xb8,0xae,0xcf,0x7b,0x9e,0x84,0x6b,0xb3,0x4a,0xd7,0xbd, + 0xc5,0xb3,0xd9,0x5b,0x75,0x72,0x18,0x27,0x3e,0xac,0xac,0xf2,0xcd,0xa9,0x89,0x45, + 0x17,0x9a,0x6f,0xd3,0x0d,0x51,0xe5,0x1b,0x37,0x83,0xe0,0x4c,0x45,0xe3,0xfd,0x99, + 0xd3,0x9d,0x94,0xc5,0x22,0x50,0x4e,0xb8,0x3d,0x7f,0xd1,0x4b,0xde,0xbc,0x82,0xc9, + 0x5c,0xb7,0x53,0xd3,0xea,0x9d,0xf5,0x59,0x15,0x31,0xb7,0x69,0x6b,0x45,0xd5,0xb2, + 0x94,0xd8,0x8c,0x8c,0xb9,0xbd,0xde,0xab,0xc9,0x5c,0x2d,0x9f,0xfa,0xe3,0x92,0x8e, + 0x16,0x6d,0x53,0x10,0xbd,0xab,0x82,0x7b,0xb1,0x9a,0xbf,0xff,0xc7,0x7f,0xc2,0x1f, + 0xff,0xfe,0x1f,0xff,0xab,0xf5,0xf7,0x2f,0xfa,0x39,0x75,0x78,0xac,0x67,0xce,0x60, + 0x9b,0x61,0x94,0x57,0x06,0x87,0xfa,0x4e,0xb5,0x5c,0x6f,0x74,0xe5,0x98,0x4c,0x7d, + 0xe3,0x3a,0x86,0x03,0x04,0x22,0x84,0x9e,0x78,0x2c,0x3c,0x7a,0x29,0x13,0x95,0xdd, + 0x05,0xf9,0x07,0x00,0xa3,0xcf,0xc3,0x0d,0x7f,0xa2,0x4f,0x4d,0xbb,0x8a,0xdb,0x9c, + 0x1f,0xf3,0x9b,0x3a,0x48,0x89,0x42,0xfa,0x07,0x65,0xae,0x36,0x7c,0xc9,0xae,0xc0, + 0x1d,0x8a,0xb3,0x16,0xa9,0x0f,0xc5,0xf6,0xb1,0x61,0xc5,0x60,0x52,0xce,0x10,0xc1, + 0xbe,0xb8,0x7f,0x5f,0x84,0x3e,0xf5,0x72,0xba,0xcc,0xdf,0xb5,0x41,0x5f,0x3f,0x20, + 0x53,0x1f,0xb6,0x71,0x66,0x8a,0x91,0x9e,0x2e,0x78,0x3a,0x0d,0x53,0xd7,0x9b,0x35, + 0x70,0x2a,0x3d,0xb7,0xc5,0xe1,0xad,0x85,0x6e,0x71,0x53,0x97,0x14,0xb9,0x06,0x10, + 0x78,0xbd,0x80,0x6d,0x5e,0xc5,0x75,0x53,0x01,0xba,0x6e,0x7b,0x5c,0x59,0xe8,0xf4, + 0x99,0x84,0x43,0x0b,0x29,0xd1,0xe9,0xc6,0x17,0x40,0xb2,0x80,0xf6,0x6c,0x99,0x1d, + 0xdb,0xe7,0x46,0x18,0xe8,0x94,0xde,0x3f,0xb6,0x4f,0xfb,0xbf,0xa1,0xf1,0xfc,0x22, + 0x52,0x91,0x7e,0xe8,0x44,0xfb,0x97,0x5a,0xad,0x89,0x92,0xb6,0xf5,0xa6,0x4f,0x73, + 0x84,0x00,0xa8,0xce,0x3f,0x37,0xda,0xe5,0x17,0xbc,0x9d,0x76,0xf5,0xbf,0x69,0xfa, + 0xff,0x00,0xc8,0x28,0x3e,0xf5,0xe4,0x74,0x00,0x00, }; constexpr size_t HOME_INDEX_HTML_GZ_LEN = sizeof(HOME_INDEX_HTML_GZ); // diff --git a/firmware/patternflow/src/knobs_index.h b/firmware/patternflow/src/knobs_index.h new file mode 100644 index 00000000..6e4aeb6d --- /dev/null +++ b/firmware/patternflow/src/knobs_index.h @@ -0,0 +1,314 @@ +// ═══════════════════════════════════════════════════════════ +// PatternFlow - /knobs page (served by core_knobs_http.h) +// Same cream/ink/LED tokens as the other consoles; no external assets. +// License: MIT +// ═══════════════════════════════════════════════════════════ +// The page body below is generated from console/knobs.html — edit that, +// then run: python firmware/toolchain/console_pages.py build +#pragma once + +#include + +static const char KNOBS_INDEX_HTML[] PROGMEM = R"HTML( + + + + +Patternflow - Knobs +
+

Knobs

-
+
+

Direction and click size

+ + + +
PositionDirectionEdges per click
+
+ + +
+
+

Turn a knob clockwise. Its position should go up by one per click. + Goes down: set that knob to Reversed. Moves two per click, or needs two clicks to + move one: change Edges per click until one click is one step (most encoders are 4; + the edge counter under the position shows how many one click really makes). + These are properties of the encoder soldered in, stored on the device — nothing to + rebuild, and a saved change does not move the knob.

+
+ +
+ +)HTML"; +// GENERATED by firmware/toolchain/console_pages.py build — do not edit. +// gzip -9 of KNOBS_INDEX_HTML (6466 bytes → 2599 bytes), served with Content-Encoding: gzip. +static const uint8_t KNOBS_INDEX_HTML_GZ[] PROGMEM = { + 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xa5,0x59,0xfd,0x72,0xdb,0x36, + 0x12,0xff,0x5f,0x4f,0x81,0xca,0xbd,0x52,0x8a,0x45,0x4a,0x54,0x6c,0xc7,0x21,0x25, + 0x75,0xea,0x7c,0xb4,0xb9,0x5e,0x7a,0x99,0x3a,0xff,0x75,0x3a,0x37,0x10,0x09,0x89, + 0x88,0x48,0x82,0x03,0x80,0x96,0x55,0x55,0x33,0xf7,0x10,0xf7,0x84,0xf7,0x24,0xb7, + 0x0b,0x90,0x14,0x2d,0xc9,0x8e,0x3b,0xe7,0x19,0x5b,0xe4,0x62,0x77,0xb1,0x1f,0xbf, + 0x5d,0x2c,0xe4,0xc9,0x37,0xb1,0x88,0xf4,0xa6,0x60,0x24,0xd1,0x59,0x3a,0xeb,0x4c, + 0xf0,0x83,0xa4,0x34,0x5f,0x4e,0xbb,0x2c,0xef,0x22,0x81,0xd1,0x78,0x36,0xc9,0x98, + 0xa6,0x24,0x4a,0xa8,0x54,0x4c,0x4f,0xbb,0xa5,0x5e,0xb8,0xd7,0xb8,0xa8,0x22,0xc9, + 0x0b,0x4d,0x94,0x8c,0xa6,0xdd,0x61,0xb1,0x70,0x23,0x91,0x2b,0x91,0x32,0xef,0x8b, + 0xea,0xce,0x26,0x43,0xbb,0x0a,0x6c,0x46,0x3a,0xa7,0x19,0x9b,0x76,0xef,0x38,0x5b, + 0x17,0x42,0xea,0x2e,0x01,0x5e,0xcd,0x72,0xd0,0xb6,0xe6,0xb1,0x4e,0xa6,0x31,0xbb, + 0xe3,0x11,0x73,0xcd,0xcb,0x80,0xe7,0x5c,0x73,0x9a,0xba,0x2a,0xa2,0x29,0x9b,0xfa, + 0xb8,0x95,0xe6,0x3a,0x65,0xb3,0x4f,0x54,0x6b,0x26,0xf3,0x45,0x2a,0xd6,0xc4,0x25, + 0x3f,0xe7,0x62,0xae,0x26,0x43,0xbb,0x04,0xd6,0xe8,0x0d,0x7e,0x0e,0x5f,0x90,0xb7, + 0x54,0xae,0x08,0xcf,0x95,0x96,0x65,0x06,0x7b,0x10,0x2d,0x56,0x2c,0x57,0xe4,0xbf, + 0xff,0xfe,0x0f,0x51,0x8c,0x91,0xc2,0x6a,0x51,0xff,0xe2,0x79,0xcc,0xee,0xbd,0x84, + 0x2c,0x84,0x24,0x3a,0x61,0x24,0x17,0x9a,0x79,0xe4,0xc5,0xb0,0x13,0x48,0x21,0xf4, + 0xd6,0x75,0x23,0xc9,0x68,0x16,0x9c,0x8d,0xde,0x8c,0x6e,0x46,0xaf,0x43,0xd7,0xe5, + 0xf9,0x2a,0x38,0x7b,0xf7,0xf6,0xdd,0xab,0xb7,0x37,0xf0,0x96,0x95,0x9a,0xc5,0xc1, + 0xd9,0xf5,0x0f,0xd7,0xe3,0x57,0x63,0x78,0x5f,0x50,0x9e,0xeb,0xe0,0xec,0xf2,0x87, + 0xcb,0xcb,0x8b,0xab,0xb0,0xe3,0xba,0xb2,0x4c,0x59,0x70,0x36,0xbe,0x18,0xfb,0xfe, + 0x75,0x68,0x5f,0x5d,0x25,0x16,0xc0,0xe3,0xdf,0xf8,0xaf,0xfd,0x0b,0xa0,0xa5,0xa8, + 0xe1,0xfd,0xfb,0xcb,0x37,0xe3,0x77,0xf0,0x26,0x40,0xfd,0xe5,0xab,0x9b,0xeb,0x57, + 0xef,0xe1,0xa5,0xa0,0x39,0x4b,0x81,0xf5,0xa5,0xef,0xfb,0x23,0x54,0xa7,0x68,0xae, + 0x02,0xe7,0x03,0xc4,0x4d,0x3a,0x83,0x92,0x9b,0x77,0x57,0x31,0xc9,0x17,0x03,0xb5, + 0x51,0x9a,0x65,0x6e,0xc9,0x07,0x7b,0x22,0x8a,0x64,0x22,0x17,0x81,0xf3,0x77,0xa6, + 0x6f,0x24,0x18,0xa7,0xc8,0x47,0x78,0x37,0xb2,0xb8,0xa0,0x0a,0x1a,0xb1,0xc1,0xed, + 0x7b,0x24,0xba,0xbf,0xb2,0x65,0x99,0x52,0x39,0xf8,0xc8,0xf2,0x54,0x0c,0x9a,0xe5, + 0x5d,0xe7,0xc5,0x76,0x2e,0xee,0x5d,0xc5,0xff,0xe0,0xf9,0x32,0x98,0x0b,0x19,0x33, + 0xe9,0x02,0x65,0xd7,0x99,0x8b,0x78,0xb3,0xcd,0xa8,0x5c,0xf2,0x3c,0x18,0x85,0x73, + 0x1a,0xad,0x96,0x52,0x94,0x79,0x1c,0xdc,0x51,0xd9,0xab,0x62,0xd7,0x0f,0x23,0x91, + 0x0a,0x59,0x91,0x20,0x7c,0xfd,0x70,0x01,0x89,0x87,0x50,0x65,0x3c,0xdd,0x54,0x64, + 0x34,0xb9,0x1f,0x76,0x52,0x9e,0x33,0x37,0x61,0x7c,0x99,0xe8,0xc0,0xf7,0x2e,0x43, + 0x77,0xcd,0xe6,0x2b,0x0e,0xbc,0x28,0xa0,0x32,0xc8,0x48,0x82,0x26,0xd0,0x1c,0xb1, + 0xc1,0xa9,0x62,0xf1,0xae,0xe3,0xad,0x25,0x2d,0xc0,0x88,0x7b,0x8b,0x9c,0xe0,0xea, + 0x7a,0x54,0xdc,0x87,0xb5,0x51,0x84,0x96,0x5a,0x84,0x05,0x8d,0x63,0x14,0x7c,0x39, + 0x2e,0xee,0xc9,0x18,0xd6,0xc9,0xd5,0x45,0x01,0xf6,0x23,0xb4,0x99,0xdc,0xc6,0x5c, + 0x15,0x29,0xdd,0x04,0x8b,0x94,0xdd,0x87,0xa0,0x78,0x99,0xbb,0x1c,0x62,0xa9,0x82, + 0x88,0x61,0xa4,0xc3,0x25,0x2d,0x82,0x6b,0x50,0x5a,0xa9,0x01,0xdf,0xb5,0x16,0x59, + 0xe0,0x83,0xb6,0xb0,0xd3,0xc4,0xc3,0xd2,0x40,0x37,0x14,0x00,0x8f,0x89,0x75,0x0c, + 0x13,0xde,0x07,0x23,0x63,0x00,0x93,0xb5,0xef,0x15,0x08,0x55,0x2e,0xe2,0xe3,0x51, + 0xd0,0x00,0x0e,0xfd,0xb0,0x52,0x2a,0x69,0xcc,0x4b,0x85,0x4a,0xc1,0x56,0x7f,0x6b, + 0xc3,0xc0,0xff,0x60,0x81,0x7f,0x09,0xa2,0xe6,0x75,0x6d,0x55,0x5d,0x8d,0x46,0x8d, + 0xcf,0x21,0xfa,0x11,0xf8,0xb0,0xab,0x2a,0xe7,0xdb,0xe3,0x60,0x63,0x66,0xab,0x24, + 0x58,0x65,0xa0,0xff,0x41,0x92,0x0c,0x8a,0xc1,0x6a,0xc5,0x22,0xcd,0x45,0x5e,0x65, + 0xd8,0xd5,0xa2,0x08,0xc6,0x57,0xc6,0x96,0xf1,0xf6,0x40,0xfc,0xd0,0x96,0x94,0x61, + 0x81,0xb9,0x08,0x20,0x0c,0xbc,0x37,0x7a,0xcd,0xb2,0x50,0xb3,0x7b,0xed,0x6a,0x09, + 0xb9,0x86,0x72,0xcb,0x82,0xb2,0x28,0x98,0x8c,0x20,0x8b,0x61,0xa7,0xbd,0xbb,0xa9, + 0xa9,0xfe,0x3e,0x83,0x23,0x72,0x8d,0x7b,0x6a,0x3a,0x4f,0x59,0x15,0x43,0x7f,0x34, + 0xfa,0x5b,0x1d,0x23,0x10,0x4d,0x69,0xa1,0x58,0x50,0x3f,0xd4,0x0b,0x68,0xee,0xa9, + 0x74,0x98,0xfa,0x03,0xef,0x74,0xf2,0x97,0x9d,0xb8,0x7a,0xb6,0x13,0x36,0x84,0x96, + 0xd9,0x20,0x2a,0x48,0xd9,0x42,0x37,0x40,0xbc,0x42,0x1c,0x1a,0xaf,0xe2,0x6d,0x4d, + 0x7b,0x6d,0x69,0xe1,0xd7,0x01,0x65,0x3d,0x68,0x67,0xf0,0x25,0xc8,0xdd,0x31,0xa9, + 0x39,0xb4,0xcb,0x6a,0xbf,0x8c,0xc7,0x71,0xca,0x70,0x07,0x6f,0xf5,0x1c,0x10,0x8c, + 0x0f,0x40,0x50,0xa5,0xc1,0x06,0xfc,0xe5,0x85,0x35,0xd6,0x2b,0x84,0x7a,0x8e,0x32, + 0x84,0xa7,0x95,0x7c,0x3d,0xda,0x4b,0x12,0x95,0xd1,0x34,0x6d,0xca,0x6d,0x9e,0x8a, + 0x68,0xd5,0x96,0x1a,0x3d,0x8a,0xc3,0x14,0x90,0x68,0xf6,0x0d,0x78,0x9e,0x40,0x67, + 0xd3,0xe1,0x33,0x3d,0xaa,0x83,0x0b,0x06,0x21,0x8c,0xa0,0x5c,0x0f,0xeb,0xcd,0xf4, + 0xd8,0x13,0x4d,0xca,0xa6,0xe1,0x74,0x41,0x1f,0xd4,0xe7,0xb8,0xf1,0x16,0x81,0x59, + 0xdb,0x1b,0x2c,0x44,0x54,0xaa,0xad,0x28,0x35,0xb6,0xb5,0x20,0x17,0x39,0x6b,0x61, + 0xb6,0xd9,0x0c,0xeb,0xbd,0x16,0xf1,0x62,0x2e,0xf5,0x66,0xfb,0x28,0x97,0x47,0x4d, + 0x41,0xaa,0x87,0x1d,0xab,0x6e,0x4f,0x27,0x3a,0x57,0xab,0x72,0x7d,0x93,0xc2,0x79, + 0x09,0xa8,0xca,0x1f,0x86,0x32,0x2a,0xa5,0x82,0x8d,0x0a,0xc1,0x51,0xa6,0xe6,0xf1, + 0x0a,0xc9,0x41,0x1c,0xac,0x39,0x8c,0x98,0x09,0x4f,0xdb,0xb8,0xaa,0xcf,0x3f,0x12, + 0x31,0xc3,0xde,0x39,0x8e,0x58,0x9d,0x1a,0xe8,0x82,0xc4,0xbf,0xaa,0xcb,0xaf,0xc1, + 0xf3,0xa1,0x1d,0x41,0x22,0x00,0xe1,0xdb,0x27,0xfb,0xe5,0x71,0xc4,0x0e,0x74,0x40, + 0xdc,0xb0,0x8d,0xc4,0x5b,0x81,0x35,0xad,0x37,0x81,0x77,0x51,0xbb,0x1f,0xb3,0x05, + 0x2d,0x53,0xdd,0x88,0x40,0xf3,0x13,0x79,0x8c,0x01,0x38,0xa8,0xb3,0x63,0xa4,0xec, + 0x4d,0x32,0x49,0xee,0xfc,0x45,0xe8,0x1c,0x06,0xe2,0xd8,0x84,0xda,0xf7,0xd3,0x5e, + 0x86,0xc7,0x6e,0x9f,0x65,0x6a,0xd9,0x6e,0xdb,0xa6,0x16,0x9e,0x79,0x12,0x64,0x20, + 0x53,0x9f,0xc1,0xc6,0x1a,0x54,0xe6,0x31,0x29,0xb7,0x27,0x00,0x89,0xf3,0xd2,0xf6, + 0x89,0x2e,0x52,0xf5,0xc1,0xaa,0x99,0x03,0x4a,0xa1,0x9d,0x43,0x63,0x7d,0x70,0xce, + 0x5f,0x5c,0x56,0x8a,0xc8,0x7c,0x7b,0xa2,0x03,0x1d,0x74,0xe5,0x5d,0x67,0x01,0x73, + 0x00,0x44,0xa3,0xe5,0xde,0xcb,0x56,0x18,0xf7,0xfe,0x7e,0xe5,0x1c,0x00,0x50,0xfe, + 0x1f,0x67,0x23,0x3d,0x61,0xea,0xae,0x03,0x23,0xaf,0x19,0x41,0x27,0x43,0x3b,0x35, + 0xe3,0x80,0x34,0x9b,0xc4,0xfc,0x8e,0x44,0x29,0x55,0x0a,0x46,0x5d,0x18,0x56,0xea, + 0xa1,0x9a,0xc9,0xd9,0x04,0xce,0x96,0xbc,0x5e,0x83,0x19,0xc1,0x0c,0xcd,0x40,0x9a, + 0x4d,0x12,0x7f,0x56,0x0d,0xb7,0xf0,0xf4,0x80,0x0d,0x0e,0xf5,0x2e,0xe1,0x31,0x3c, + 0x00,0xbb,0x5b,0xf3,0x0f,0x2b,0x8d,0x30,0x04,0xdb,0x53,0x7b,0xd6,0x21,0x64,0x92, + 0x8c,0x67,0x6f,0xb9,0xb4,0x04,0x42,0xf3,0x18,0x74,0xf0,0x68,0x45,0xd0,0x35,0x90, + 0x18,0x1b,0x1e,0x73,0xb0,0xe2,0x13,0x3e,0x5b,0xb3,0x35,0x58,0xa6,0x13,0x50,0x8a, + 0x7f,0xe0,0xf7,0x93,0x50,0x1c,0x55,0x34,0x84,0x46,0x69,0x43,0x79,0x17,0x2f,0x99, + 0x22,0x70,0x16,0xda,0x2d,0x2c,0x7d,0x88,0x8a,0x86,0x56,0x69,0xb5,0x01,0x46,0xc4, + 0x58,0x2f,0xc5,0xda,0xdc,0x11,0x0c,0xc5,0x18,0x32,0x6c,0x2c,0x69,0x47,0xac,0x6a, + 0x79,0xdd,0x4a,0x81,0x2d,0x8d,0x7a,0xb1,0xaa,0xeb,0x2e,0xc1,0x1b,0xcc,0xb4,0x6b, + 0x17,0xab,0xe8,0xd0,0x3b,0xd6,0x25,0x75,0xc1,0xcf,0x6e,0xe1,0x75,0x32,0xb4,0x0c, + 0x27,0x55,0x35,0xc5,0x76,0x4a,0x99,0x64,0x78,0xba,0xb6,0xd4,0xfd,0x6a,0x08,0x6d, + 0x85,0x93,0x21,0x18,0xdd,0x18,0x8f,0x42,0x50,0x36,0xe8,0x60,0x4d,0x2e,0xea,0x9d, + 0x10,0xea,0xb0,0x30,0x9f,0x7d,0x2e,0x25,0x64,0x85,0xac,0x20,0xcf,0xb0,0x06,0x87, + 0xe1,0x9a,0x2b,0xe6,0x81,0xd2,0x19,0xf9,0xa0,0x21,0x98,0x55,0xd4,0x89,0x4a,0x44, + 0x99,0xc6,0x64,0x29,0x48,0x59,0x90,0xf9,0x86,0x40,0x93,0xd9,0x47,0xda,0x03,0xdd, + 0x3f,0x0a,0x88,0x7d,0x2c,0xd6,0x79,0x00,0xb7,0x1b,0xb8,0xec,0x24,0x54,0x5b,0xad, + 0x5a,0x90,0x09,0xcb,0xac,0xb5,0x30,0x2d,0x4f,0x86,0xf0,0xe2,0xc1,0x15,0xe0,0x0e, + 0xf8,0xf5,0x5a,0xec,0xb5,0x0c,0x08,0x5c,0x80,0x72,0xc6,0x62,0x4b,0x37,0x34,0x78, + 0x14,0xa0,0x3c,0x03,0x6e,0xdc,0x32,0xc0,0xeb,0x5f,0xbe,0x64,0x46,0xe3,0x51,0xba, + 0x81,0x46,0x4a,0x98,0xcb,0x53,0x63,0x9d,0x85,0x19,0x57,0xe6,0x05,0xee,0x25,0x05, + 0xe9,0x65,0x42,0x69,0xc2,0xf2,0x48,0x00,0x4a,0x15,0xa1,0x92,0x91,0x8b,0x10,0xb4, + 0xe3,0xa5,0x8b,0x81,0x32,0xb8,0x0d,0x96,0x78,0xfc,0x80,0x12,0x60,0x30,0xe4,0xb6, + 0xff,0x6b,0x45,0xe0,0x0f,0xc9,0x68,0xbe,0x69,0x6d,0x00,0xe7,0x4e,0x9a,0x6e,0x80, + 0xba,0x62,0xaa,0x8f,0x81,0xf8,0x9c,0x30,0xc5,0x8c,0xf2,0x42,0x8a,0x02,0x07,0x22, + 0xb0,0x52,0x2c,0xec,0x2e,0x76,0x6f,0xec,0x03,0xf0,0xc1,0x62,0xb8,0x1e,0x0e,0xc0, + 0x36,0x81,0x8f,0xb0,0x09,0xb2,0xd8,0x2b,0x28,0xf9,0x2e,0x8b,0xa9,0x4a,0x42,0xbc, + 0x0c,0xe2,0x7d,0xc3,0x86,0x41,0xb2,0x79,0xc9,0xd3,0x78,0x60,0xca,0x88,0x12,0x84, + 0x57,0x5c,0x47,0x24,0xc6,0xf8,0x03,0xb7,0x8d,0x15,0x6a,0xc2,0xe8,0x43,0x26,0x8b, + 0x19,0xb6,0x84,0xba,0x20,0x27,0xb6,0x73,0xcd,0x26,0x94,0x24,0x92,0x2d,0xe0,0xc6, + 0xdc,0x9d,0xfd,0x24,0x32,0xc0,0x25,0x9d,0xc1,0xa6,0x30,0xb0,0x09,0x1d,0x92,0xfd, + 0xaa,0xd2,0x54,0x97,0x80,0xfc,0x5b,0xf3,0x89,0x5c,0x93,0x61,0xa5,0xa2,0x53,0xe1, + 0x6a,0x52,0x5f,0xb1,0x17,0x65,0x6e,0xab,0xfc,0xdb,0x1e,0xef,0x6f,0x25,0xd3,0x88, + 0x2d,0xb8,0xda,0x9b,0xeb,0xaf,0xb7,0x64,0xfa,0x5d,0xca,0xf0,0xf1,0x66,0xf3,0x21, + 0x06,0x8e,0xdd,0x5e,0x40,0xd1,0x4d,0x4f,0x0f,0xa0,0xbf,0xf7,0xb7,0xdf,0xf6,0x1c, + 0x00,0xad,0xd3,0xf7,0x70,0x6e,0x7d,0x53,0x5d,0xcf,0x75,0xd8,0x90,0x0d,0x80,0x7f, + 0xc1,0x6b,0x3c,0xb0,0x7f,0xef,0xc0,0x1f,0x27,0x70,0x9c,0x5d,0x07,0x9a,0x20,0x81, + 0x63,0x74,0x9a,0x97,0x69,0x1a,0x12,0xf3,0x33,0x1c,0x92,0x35,0xc2,0xb0,0x15,0xd6, + 0x84,0x2a,0xc3,0x69,0x86,0x9c,0xe9,0x82,0xa6,0x30,0x34,0x5b,0x4e,0x68,0x6e,0x65, + 0x5e,0x47,0x54,0x20,0x6f,0x95,0x90,0x82,0x2e,0x59,0xcb,0x52,0x96,0x42,0xe3,0x69, + 0xf9,0x07,0x86,0xf1,0xfc,0xce,0x39,0x7f,0xe8,0x0f,0x4b,0x6f,0xcb,0xf9,0x43,0x2e, + 0xe8,0x99,0x07,0x5c,0x26,0x99,0xbd,0xfe,0x16,0x32,0x8b,0x36,0xe9,0xf9,0x14,0xd8, + 0xb0,0x27,0x39,0x30,0xb5,0xcf,0x3d,0x9e,0xe7,0x4c,0xfe,0xf4,0xf9,0xe3,0x3f,0xa6, + 0x8e,0x83,0x30,0x85,0x59,0xbf,0x87,0x7c,0x7c,0x3a,0x0a,0xf9,0xe4,0x22,0xe4,0xe7, + 0xe7,0x46,0xb6,0x92,0x96,0xd3,0x26,0xd4,0x38,0x0c,0x69,0x56,0x45,0xbb,0xe7,0x68, + 0x09,0x0a,0x0d,0xa3,0x96,0x6d,0xad,0x13,0x1d,0xd7,0xfd,0x60,0xd5,0x9d,0xfd,0xec, + 0x9c,0xf7,0xf8,0xb9,0xdf,0x3f,0x77,0xa0,0x0b,0xc6,0x33,0xe7,0xbc,0x63,0xa3,0xd8, + 0x66,0x83,0x72,0xe8,0x56,0x27,0x01,0x76,0x17,0x78,0x05,0x97,0xce,0x9d,0xd6,0x19, + 0x60,0xc6,0x6b,0xdb,0xaf,0xe8,0xba,0x5a,0x84,0x35,0xa4,0xce,0x8e,0xf5,0x02,0xbf, + 0x19,0x3a,0x8d,0x80,0x0d,0xa3,0x11,0x10,0x85,0x09,0xd0,0x1d,0x4d,0x4b,0xe8,0x82, + 0xa3,0xee,0xec,0x17,0xb8,0xe7,0xd0,0x74,0x32,0xb4,0x0b,0x87,0x0c,0x7e,0xb7,0xd5, + 0x5e,0x6a,0x96,0xa1,0x55,0xfd,0xb5,0x5d,0x6d,0x5a,0x4e,0xec,0x7a,0xd1,0x9d,0x5d, + 0x1c,0xb4,0x8c,0xfe,0x63,0x06,0x8c,0xbb,0xb3,0xf1,0x13,0xc6,0xf9,0x8f,0x59,0x55, + 0x65,0x65,0xee,0x51,0xb8,0xc2,0xe5,0xf1,0x9b,0x04,0x01,0xa1,0x65,0x95,0xad,0x06, + 0x6b,0x9e,0xc8,0x6d,0x95,0x4f,0x1b,0x60,0xed,0x49,0x70,0xf8,0xac,0xde,0x22,0x9c, + 0x51,0x68,0xd7,0x69,0xe1,0xab,0x59,0xb1,0x18,0xb3,0x98,0xd7,0xb2,0x64,0x58,0x4d, + 0x08,0x75,0x28,0xa7,0xfa,0x34,0xa9,0x6a,0x01,0x01,0x68,0x8e,0x95,0xe3,0xa5,0x27, + 0x11,0xb8,0x37,0x75,0x5f,0x9f,0xbd,0x3d,0xd1,0x86,0x62,0x3a,0x75,0x7c,0xa7,0xff, + 0xcd,0x74,0x0a,0x75,0x0a,0x38,0xbc,0xfb,0x8d,0xff,0xfe,0xbd,0x63,0xac,0xc2,0x12, + 0x6e,0x7c,0xae,0x1c,0xdc,0x2b,0x2a,0xf0,0x1b,0xbf,0x0f,0x80,0xe4,0xfd,0xa2,0x51, + 0x38,0xf0,0x47,0xb5,0x36,0xc8,0xe2,0xb1,0xb6,0x07,0xc1,0xc0,0x06,0xde,0x8b,0x8d, + 0xb9,0xd8,0x26,0xe2,0xa7,0xfd,0x81,0x38,0x58,0x70,0x3f,0xec,0x43,0xb1,0x67,0x4f, + 0x25,0xd8,0x2b,0xac,0xf9,0x2c,0xce,0x0f,0xf9,0x80,0x0a,0x4c,0xe7,0x8e,0x39,0x58, + 0x54,0xe5,0x1c,0x5f,0xf4,0xbe,0x31,0x16,0x56,0xbb,0xb4,0xe3,0x66,0x43,0x14,0x37, + 0x71,0xf1,0xc1,0x8b,0x51,0x25,0xd7,0x0e,0x8b,0xe5,0xbb,0xd5,0x12,0x4e,0x85,0x5e, + 0x5c,0x39,0xde,0x0f,0x8f,0xf4,0xed,0xc3,0x77,0x2a,0xa4,0x75,0xb8,0x77,0x1d,0xfb, + 0x8b,0x80,0xd0,0x07,0x4d,0xd7,0x49,0x39,0x40,0x24,0x6c,0x07,0x31,0x15,0xb4,0x6a, + 0x58,0x0b,0xa6,0xa3,0xa4,0xe7,0x0c,0x69,0xc1,0x87,0x78,0xd0,0x28,0x67,0xb0,0x8d, + 0x68,0x94,0xb0,0xc0,0xc9,0x85,0x6b,0x8e,0x34,0x67,0x07,0xfa,0x12,0x96,0xf7,0x6a, + 0xf1,0x9e,0x6c,0xfa,0xa1,0xf4,0xbe,0x28,0x20,0xf4,0x6b,0x16,0xcc,0x4e,0xdf,0x58, + 0xe4,0x45,0x14,0x35,0x37,0x32,0xe6,0x44,0x38,0xb6,0x0d,0xc0,0x09,0xb3,0x52,0x0e, + 0xa5,0xc4,0x62,0xd8,0x08,0xad,0x6c,0x63,0x17,0xaa,0x03,0x13,0x35,0x6d,0xa9,0x69, + 0xb7,0xfc,0x13,0xf8,0xaf,0xeb,0xe2,0x18,0xfe,0x66,0x05,0x52,0x07,0xb0,0xe9,0x1b, + 0x14,0xe1,0x43,0x88,0x47,0x96,0xe3,0xf4,0x77,0x61,0x67,0xaf,0xec,0xc4,0xae,0x55, + 0x6f,0xc7,0x01,0x73,0xfa,0xdb,0xef,0x4f,0x83,0x0e,0x99,0xbc,0xa2,0x54,0x49,0x7d, + 0xa4,0x9c,0x3b,0x53,0xe7,0xfc,0x00,0x22,0x55,0xaa,0x5b,0xbc,0x75,0x07,0xb3,0xbc, + 0x6d,0x98,0xf4,0xc3,0x56,0x7a,0x4f,0xf9,0xfb,0x48,0x22,0x33,0xa6,0x13,0x11,0x07, + 0xce,0xa7,0x7f,0xde,0x7e,0x76,0x06,0x66,0x3f,0x3b,0xda,0xab,0x60,0xeb,0x54,0x29, + 0x70,0x3f,0xc3,0x84,0x0a,0x18,0x85,0xce,0x05,0x3e,0x53,0x74,0x77,0x78,0xef,0xae, + 0xd7,0x6b,0x17,0xbf,0x93,0x72,0x4b,0x99,0xda,0xae,0x09,0xd9,0x19,0xa0,0xb1,0x81, + 0xb1,0xf8,0x0b,0x5c,0xf0,0x7b,0xce,0x77,0x10,0xb7,0x2a,0xdb,0xcf,0x05,0x48,0xc3, + 0x12,0x37,0xb5,0x63,0xaa,0xc9,0x13,0xab,0xfe,0x16,0x73,0x11,0xe3,0xf5,0x50,0xc8, + 0x3f,0xff,0x74,0xe0,0x7a,0x04,0x0e,0x3a,0x03,0xbf,0xff,0x78,0x9f,0xb3,0xbb,0xec, + 0x2a,0x4d,0x07,0xd0,0x78,0x04,0x02,0x55,0x07,0xb1,0x99,0x37,0x93,0x42,0x7d,0xac, + 0x82,0x91,0x47,0xa8,0x35,0x5c,0xcf,0x30,0xc5,0x60,0x17,0xee,0xeb,0x76,0x16,0x08, + 0x6d,0x85,0x85,0x30,0x42,0x9b,0x6f,0xd8,0x21,0x8d,0x3d,0x24,0x0d,0x2e,0x47,0x23, + 0x60,0x6c,0xfe,0x9d,0x01,0x03,0xba,0xb9,0xd6,0x0d,0xed,0x7f,0x4c,0xfe,0x07,0x12, + 0x67,0x90,0x92,0x42,0x19,0x00,0x00, +}; +constexpr size_t KNOBS_INDEX_HTML_GZ_LEN = sizeof(KNOBS_INDEX_HTML_GZ); +// diff --git a/firmware/toolchain/check_footprint.py b/firmware/toolchain/check_footprint.py index 0886c8f4..f8c934cd 100644 --- a/firmware/toolchain/check_footprint.py +++ b/firmware/toolchain/check_footprint.py @@ -1,11 +1,11 @@ """Pin how much internal SRAM each edition's image has already spent. -python firmware/toolchain/check_footprint.py [--update] [--dir DIR] +python firmware/toolchain/check_footprint.py [--update] [--dir DIR] [--only EDITION ...] Run `firmware/bundles/build.sh all` first; it leaves one .elf per edition next to the .bin in ~/pf-build-editions. -WHY. CI builds four editions and then prints `ls -l *.bin`, which is flash — and +WHY. CI builds five compositions and then prints `ls -l *.bin`, which is flash — and flash is not the scarce resource on this board. Internal DRAM is. A loadable pattern's code has to fit in one contiguous internal executable block, and that block is the residual of the internal pool after .data, .bss and IRAM have taken @@ -55,10 +55,11 @@ # Arduino core 2.0.17 -> xtensa-esp32s3-elf-gcc 8.4.0 at -Os. A toolchain bump # moves every row; re-pin it in the same commit as the bump. PINS = { - "default": (141080, 72311), - "audio": (160096, 72791), - "performance": (153448, 72467), - "clock": (141352, 72311), + "default": (141424, 72467), + "audio": (160960, 72947), + "performance": (153536, 72467), + "clock": (141704, 72467), + "midi": (154000, 71667), } # Enough that an intentional, well-understood adjustment does not fire the check @@ -132,6 +133,8 @@ def main() -> int: formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--update", action="store_true", help="rewrite PINS in this file from what was just built") + parser.add_argument("--only", action="append", metavar="EDITION", + help="measure this composition only (repeatable); CI builds one per job") parser.add_argument("--dir", default=os.environ.get("PF_BUILD_DIR", str(Path.home() / "pf-build")) + "-editions", help="where build.sh all left its .elf files") args = parser.parse_args() @@ -149,7 +152,9 @@ def main() -> int: # account for 19,016 B. Read a movement here as a reason to go and look at # /api/status, never as a runtime number. print(f"{'edition':<13} {'static DRAM':>12} {'IRAM':>9} {'window left':>12} pinned") - for edition in PINS: + for edition in (args.only or list(PINS)): + if edition not in PINS: + raise SystemExit(f"{edition}: not pinned - add a row to PINS and run --update") elf = outdir / f"{edition}.elf" if not elf.is_file(): missing.append(edition) @@ -176,7 +181,7 @@ def main() -> int: if args.update: text = Path(__file__).read_text(encoding="utf-8") block = "PINS = {\n" + "".join( - f' "{e}": ({measured[e]["static_dram"]}, {measured[e]["iram"]}),\n' for e in PINS + (f' "{e}": ({measured[e]["static_dram"]}, {measured[e]["iram"]}),\n' if e in measured else f' "{e}": ({PINS[e][0]}, {PINS[e][1]}),\n') for e in PINS ) + "}" text = re.sub(r"PINS = \{.*?\n\}", block, text, count=1, flags=re.S) Path(__file__).write_text(text, encoding="utf-8") diff --git a/firmware/toolchain/check_versions.py b/firmware/toolchain/check_versions.py index 61cf942a..248be221 100644 --- a/firmware/toolchain/check_versions.py +++ b/firmware/toolchain/check_versions.py @@ -20,7 +20,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] -EDITIONS = ("audio", "performance", "clock") +EDITIONS = ("audio", "performance") def text(rel: str) -> str: diff --git a/firmware/toolchain/console_pages.py b/firmware/toolchain/console_pages.py index ce817f29..28e9d3d1 100644 --- a/firmware/toolchain/console_pages.py +++ b/firmware/toolchain/console_pages.py @@ -81,6 +81,7 @@ ("patterns", "src/patterns_index.h"), ("status", "src/status_index.h"), ("wifi", "src/wifi_index.h"), + ("knobs", "src/knobs_index.h"), ("update", "src/web_update_index.h"), ("show", "features/show/show_index.h"), ("weather", "features/weather/weather_index.h"), diff --git a/firmware/toolchain/console_serve.py b/firmware/toolchain/console_serve.py index 530e86c1..a895837c 100644 --- a/firmware/toolchain/console_serve.py +++ b/firmware/toolchain/console_serve.py @@ -48,6 +48,7 @@ "/patterns": "patterns", "/status": "status", "/wifi": "wifi", + "/knobs": "knobs", "/update": "update", "/show": "show", "/weather": "weather", @@ -441,6 +442,26 @@ def api(self, path, q, p, delete=False): "paramActive": [False, False, False, False], }) + # -- knobs: encoder direction and edges per click, with a readout that moves -- + if path == "/api/knobs": + inv = getattr(d, "knob_inv", [False, False, False, False]) + sub = getattr(d, "knob_sub", [4, 4, 4, 4]) + if self.command == "POST": + for k in range(4): + if ("inv%d" % k) in p: + inv[k] = p["inv%d" % k] in ("1", "true", "on") + elif "inv" in p: + inv[k] = p["inv"] in ("1", "true", "on") + if ("sub%d" % k) in p: + sub[k] = int(p["sub%d" % k]) + elif "sub" in p: + sub[k] = int(p["sub"]) + d.knob_inv, d.knob_sub = inv, sub + d.knob_tick = getattr(d, "knob_tick", 0) + 1 + raw = [(d.knob_tick * (k + 1)) % 400 for k in range(4)] + clicks = [(-r if inv[k] else r) // sub[k] for k, r in enumerate(raw)] + return self.send_json({"ok": True, "inv": inv, "sub": sub, "clicks": clicks, "raw": raw}) + if path == "/api/params" or path == "/api/display": return self.send_json({"ok": True}) diff --git a/firmware/toolchain/release.py b/firmware/toolchain/release.py index 18f06bd6..6c6e77fe 100644 --- a/firmware/toolchain/release.py +++ b/firmware/toolchain/release.py @@ -3,6 +3,7 @@ python firmware/toolchain/release.py cut v3.9.4 --audio v0.5.4 --performance v0.2.4 python firmware/toolchain/release.py publish v3.9.4 --notes notes.md + python firmware/toolchain/release.py edition performance v0.2.8 `cut` (on dev, clean tree, written [Unreleased] section): 1. bumps PF_IMPROV_FW_VERSION and each named edition's PF_VARIANT_VERSION, @@ -17,6 +18,15 @@ README.md's "Moving fast" note is prose; it says what changed, so it is left to the author and named in the summary. +`edition` (any branch, clean tree) cuts ONE edition without a core release - + the way an edition's maintainer ships from a pull request: + 1. bumps that edition's PF_VARIANT_VERSION and its clause in AGENTS.md, + 2. runs shelf.sh for it and points its /editions card at the image, + 3. commits "release: vA.B.C" - no tag, no changelog section, no + manifest; those belong to the core. Only editions in EDITIONS are on + the shelf; a bundle the tree keeps building but does not publish + (clock, midi) is not cut by anything here. + `publish` (after `cut`): pushes dev and the tag, opens the dev -> main pull request from the changelog section, waits for its checks, merges, creates the GitHub release @@ -44,7 +54,7 @@ MANIFEST = ROOT / "web/public/flash/manifest.json" EDITIONS_TS = ROOT / "web/src/app/editions/editions-data.ts" BIN_DIR = ROOT / "web/public/flash/bin" -EDITIONS = ("audio", "performance", "clock") +EDITIONS = ("audio", "performance") CO_AUTHOR = "Co-Authored-By: Claude Opus 5 " TRAILER = "🤖 Generated with [Claude Code](https://claude.com/claude-code)" @@ -133,12 +143,7 @@ def vcheck(v: str, what: str) -> str: # ── cut ──────────────────────────────────────────────────────────────────── -def bump_firmware(core: str, editions: dict[str, str]) -> None: - text, crlf = read(NET_CONFIG) - text = sub_once(text, r'#define PF_IMPROV_FW_VERSION "[^"]+"', - f'#define PF_IMPROV_FW_VERSION "{core[1:]}"', "PF_IMPROV_FW_VERSION in net_config.h") - write(NET_CONFIG, text, crlf) - print(f" net_config.h: PF_IMPROV_FW_VERSION -> {core[1:]}") +def bump_editions(editions: dict[str, str]) -> None: for name, version in editions.items(): path = ROOT / f"firmware/bundles/{name}/overrides.h" text, crlf = read(path) @@ -148,6 +153,17 @@ def bump_firmware(core: str, editions: dict[str, str]) -> None: print(f" bundles/{name}/overrides.h: PF_VARIANT_VERSION -> {version}") +def bump_firmware(core: str, editions: dict[str, str]) -> None: + text, crlf = read(NET_CONFIG) + text = sub_once(text, r'#define PF_IMPROV_FW_VERSION "[^"]+"', + f'#define PF_IMPROV_FW_VERSION "{core[1:]}"', "PF_IMPROV_FW_VERSION in net_config.h") + write(NET_CONFIG, text, crlf) + print(f" net_config.h: PF_IMPROV_FW_VERSION -> {core[1:]}") + bump_editions(editions) + + + + def current_edition_versions() -> dict[str, str]: found = {} for name in EDITIONS: @@ -177,19 +193,29 @@ def bump_changelog(core: str, today: str, allow_empty: bool) -> str: return body +def bump_agents_editions(editions_now: dict[str, str]) -> None: + if not all(editions_now.get(n) for n in EDITIONS): + return + text, crlf = read(AGENTS) + # "Audio vA, Performance vB at the time of writing" - one clause per + # edition, in EDITIONS order, so adding an edition to the shelf is one + # tuple entry here and one clause in AGENTS.md. + pattern = ", ".join(rf"{n.capitalize()} v\d+\.\d+\.\d+" for n in EDITIONS) + " at the time of writing" + repl = ", ".join(f"{n.capitalize()} {editions_now[n]}" for n in EDITIONS) + " at the time of writing" + text = sub_once(text, pattern, repl, "the editions' 'at the time of writing' line in AGENTS.md") + write(AGENTS, text, crlf) + print(" AGENTS.md: " + ", ".join(f"{n.capitalize()} {editions_now[n]}" for n in EDITIONS)) + + def bump_agents(core: str, today: str, editions_now: dict[str, str]) -> None: text, crlf = read(AGENTS) text = sub_once(text, r"- Project: v\d+\.\d+\.\d+ \(current, released \d{4}-\d{2}-\d{2}\)", f"- Project: {core} (current, released {today})", "the 'Project: vX (current, released …)' line in AGENTS.md") - if all(editions_now.get(n) for n in EDITIONS): - # "Audio vA, Performance vB, Utility vC at the time of writing" - one - # clause per edition, in EDITIONS order, so adding an edition is one - # tuple entry here and one clause in AGENTS.md. - pattern = ", ".join(rf"{n.capitalize()} v\d+\.\d+\.\d+" for n in EDITIONS) + " at the time of writing" - repl = ", ".join(f"{n.capitalize()} {editions_now[n]}" for n in EDITIONS) + " at the time of writing" - text = sub_once(text, pattern, repl, "the editions' 'at the time of writing' line in AGENTS.md") write(AGENTS, text, crlf) - print(f" AGENTS.md: current {core}, " + ", ".join(f"{n.capitalize()} {editions_now.get(n)}" for n in EDITIONS)) + print(f" AGENTS.md: current {core}") + bump_agents_editions(editions_now) + + def shelf(name: str, version: str) -> None: @@ -198,18 +224,9 @@ def shelf(name: str, version: str) -> None: bash(f'bash firmware/bundles/shelf.sh {name} {version}') -def point_site(core: str, editions: dict[str, str]) -> None: - text, crlf = read(MANIFEST) - data = json.loads(text) - data["version"] = core - for build in data["builds"]: - for part in build["parts"]: - part["path"] = re.sub(r"bin/core-v[\d.]+/", f"bin/core-{core}/", part["path"]) - write(MANIFEST, json.dumps(data, indent=2) + "\n", crlf) - print(f" manifest.json: {core}, bin/core-{core}/") - +def point_cards(versions: dict[str, str]) -> None: text, crlf = read(EDITIONS_TS) - for ident, version in {"core": core, **editions}.items(): + for ident, version in versions.items(): # The card's block runs from its id: to the next id:. Inside it, the # first version:/url: pair is the image the shelf serves. m = re.search(rf"id: '{ident}',(.*?)(?=id: '|\Z)", text, re.S) @@ -224,6 +241,20 @@ def point_site(core: str, editions: dict[str, str]) -> None: write(EDITIONS_TS, text, crlf) +def point_site(core: str, editions: dict[str, str]) -> None: + text, crlf = read(MANIFEST) + data = json.loads(text) + data["version"] = core + for build in data["builds"]: + for part in build["parts"]: + part["path"] = re.sub(r"bin/core-v[\d.]+/", f"bin/core-{core}/", part["path"]) + write(MANIFEST, json.dumps(data, indent=2) + "\n", crlf) + print(f" manifest.json: {core}, bin/core-{core}/") + point_cards({"core": core, **editions}) + + + + def web_checks(quick: bool) -> None: bash("cd web && npm run -s typecheck") if not quick: @@ -292,6 +323,46 @@ def cmd_cut(args: argparse.Namespace) -> None: python firmware/toolchain/release.py publish {core} --notes """) +# ── edition ──────────────────────────────────────────────────────────────── +def cmd_edition(args: argparse.Namespace) -> None: + """One edition, on its own - the way an edition's maintainer ships between + core releases, from a pull request. No tag, no changelog section, no + manifest: those belong to the core.""" + name = args.name + if name not in EDITIONS: + die(f"{name!r} is not on the shelf; the shelf carries {', '.join(EDITIONS)}") + version = vcheck(args.version, "the version") + dirty = [l for l in out(["git", "status", "--porcelain"]).splitlines() if not l.startswith("??")] + if dirty and not args.no_commit: + die("the working tree has uncommitted changes:\n " + "\n ".join(dirty)) + current = current_edition_versions() + if current.get(name) == version: + die(f"{name} is already {version} - bump instead") + print(f"== cut {name} {version}") + bump_editions({name: version}) + bump_agents_editions({**current, name: version}) + if args.no_build: + print(" (shelf skipped: --no-build)") + else: + shelf(name, version) + image = BIN_DIR / f"{name}-{version}" / "patternflow.ino.bin" + if b"YOUR_WIFI_SSID" not in image.read_bytes(): + die(f"{image} has no placeholder SSID - it was not built clean") + point_cards({name: version}) + if args.no_commit: + print(" (commit skipped: --no-commit)") + else: + paths = ["AGENTS.md", "web/src/app/editions/editions-data.ts", f"firmware/bundles/{name}/overrides.h"] + run(["git", "add", "-A", "--", *paths, "web/public/flash/bin"]) + run(["git", "commit", "-q", "-m", f"release: {name} {version}", + "-m", f"{name.capitalize()} {version} staged by shelf.sh from a sketch copy without " + "patternflow_secrets.h; the previous folder retired to its tag.", "-m", CO_AUTHOR]) + print(f""" +== {name} {version} is cut. + Push and open the pull request; CI checks the six places agree, and the shelf + serves the image once main deploys. Nothing else to do.""") + + # ── publish ──────────────────────────────────────────────────────────────── def changelog_section(core: str) -> str: @@ -392,6 +463,13 @@ def main() -> None: pub.add_argument("--no-merge", action="store_true", help="stop after the PR's checks") pub.set_defaults(fn=cmd_publish) + ed = sub.add_parser("edition", help="cut one edition on its own: bump, shelf, card, commit - no tag") + ed.add_argument("name", choices=EDITIONS, help="which edition") + ed.add_argument("version", help="vA.B.C") + ed.add_argument("--no-build", action="store_true", help="skip shelf.sh (a dry run of the edits)") + ed.add_argument("--no-commit", action="store_true", help="leave the edits in the tree") + ed.set_defaults(fn=cmd_edition) + args = ap.parse_args() args.fn(args) diff --git a/web/ARCHITECTURE.md b/web/ARCHITECTURE.md index 552afe7f..2c60a198 100644 --- a/web/ARCHITECTURE.md +++ b/web/ARCHITECTURE.md @@ -15,6 +15,7 @@ The `web/` app is the Patternflow site at [patternflow.work](https://patternflow | `/pattern-lab` | The pattern workspace — see below | | `/community/**` | The community: feed, pattern pages (`/p/[id]`), profiles (`/u/[username]`), decks (`/d/[id]`, `/decks`), the Workshop (`/workshop/[code]`), territories and the atlas, notifications, featured, reports. Renders a pointer to the community host unless `COMMUNITY_ENABLED=1` | | `/editions` | The edition shelf: every firmware you can put on a panel, official and community, with one-click install. `/variants` (the URL until 2026-09, baked into shipped console pages) redirects here permanently | +| `/features` | The catalogue: every feature on its own — what it needs, which firmware carries it, and a reel of it running (Instagram embeds by permalink, lazy). The other axis of the shelf; hand-curated in `features-data.ts` | | `/update` | The device's firmware-update handoff: the browser downloads an image and POSTs it to the panel over the LAN, because the panel cannot fetch over TLS | | `/flash` (static) | esp-web-tools flasher driven by `public/flash/manifest.json` + the images in `public/flash/bin/` — only the currently served ones are committed | | `/journal` · `/journal/[slug]` (+ `/en`) | Bilingual (ko/en) MDX journal with per-article OG image generation | diff --git a/web/content/inside.md b/web/content/inside.md index c7cb6304..a1396d63 100644 --- a/web/content/inside.md +++ b/web/content/inside.md @@ -1,6 +1,6 @@ --- title: Inside the work. -subtitle: Here is every one of them. +subtitle: Builds, projects, and places where Patternflow comes to life. cta: primary: label: "Contributing" diff --git a/web/content/journal/a-strange-dream.en.mdx b/web/content/journal/a-strange-dream.en.mdx new file mode 100644 index 00000000..e5921e1f --- /dev/null +++ b/web/content/journal/a-strange-dream.en.mdx @@ -0,0 +1,30 @@ +--- +title: "A Strange Dream" +date: "2026-09-16T19:00:00+09:00" +excerpt: "Maybe there's something to it" +cover: "/journal/a-strange-dream/certificate.jpg" +coverCaption: "The certificate for the first artist edition" +hideCover: true +draft: false +--- + +I slept properly for the first time in ages. Opening my eyes at nine without waking up in the middle of the night is such a rare thing lately that it was almost moving. I had a feeling I'd had an interesting dream but forgot it quickly and left it at that, and then just now it came back to me. A strange dream with a very large snake and a centipede in it. Maybe it was because I saw a house centipede at home before bed. + +To describe it briefly: I was raising a snake, doting on it. I'd grown it enormous, and at some point a centipede appeared and cut it apart. Cut exactly twice. I don't know about the middle section, but the small remaining tail and the head moved on their own and left for somewhere. The centipede's size was no ordinary thing either. About three times the size of a person, unreal-looking, and we got along quite well. Less like I was raising it and more like we shared a house in symbiosis, and it was so large and magnificent that it never even occurred to me to provoke it. + +I'm not one for superstition or faith, but since it was an interesting dream I asked an AI to interpret it. It's been a while since I last asked for a dream reading, not since that unusual class in the first semester. Apparently both snakes and centipedes are fairly good omens in East Asian symbolism. I knew snakes were considered good for various reasons, connecting sky and earth and so on, and it seems centipedes are similar. It's a completely pointless flight of fancy, but if I connect it to Patternflow! Something I raised with care is going to transform into a new shape… I think. In a fairly good direction? Supposedly. + +Did my mood get reflected in the dream? It's possible. I sold a Patternflow as an artwork, and the money landed in my account at ten at night. It felt great. Right up until then I'd been anxious that something would go wrong midway and the money would end up somewhere else, or that the deposit would be delayed, but seeing it arrive in my account as an actual number was a relief. So this morning I went to the post office and sent the Patternflow off to San Francisco. Shipping plus customs came to around 200,000 won. So expensive… + +Since it's the first Patternflow sold as an artwork, I signed it and made a certificate to send along. Hyunji made the certificate for me, and it came out far cooler and prettier than I'd imagined, which was great. I hadn't thought to shape it like a Patternflow. Fantastic! I'm using this for every certificate I make from now on. + +
+ Front and Patternflow-shaped back of the certificate for the first artist edition +
The certificate Hyunji made. The front, and the back shaped like a Patternflow.
+
+ +The Patternflow I sent gets exhibited right away in October. As a hands-on piece! I looked into it a bit, and during the exhibition period there's an event over there called SF Tech Week. It's apparently a very large event, and it's fun to think of lots of people touching and making things with Patternflow there. As an aside, when I saw "SF" I thought it meant science fiction, but it's short for San Francisco, ha. + +Honestly, these past few days my mood hasn't been good. Bad, if anything. The new patterns I try don't satisfy me at all, and I said the sound side would be fun and that I'd do it and then don't, which makes me feel pathetic. I keep thinking about how to expand it, and how to share what people have made and carry that through to monetization, and it's all too hard. The advertising grant comes at the end of the month, and the mentoring needed to decide on it is running later than expected. Honestly, I still don't know what to do or how. So hard… + +Even knowing the documentation or the website needs work, knowing how exhausting it'll be once I start makes me not start. And it isn't an area where I could hire someone with the grant money… in the end I have to do it. My landlord told me to move out for renovations, so I've been looking for a place, and that's very hard too. Nothing, in anything, is certain. It's a lot of stress, and if I want to live long and healthy I should probably find some way to deal with it. It'd be easier if I could live with a "it'll work out somehow" mindset. diff --git a/web/content/journal/a-strange-dream.mdx b/web/content/journal/a-strange-dream.mdx new file mode 100644 index 00000000..bbb4457f --- /dev/null +++ b/web/content/journal/a-strange-dream.mdx @@ -0,0 +1,30 @@ +--- +title: "신기한 꿈" +date: "2026-09-16T19:00:00+09:00" +excerpt: "진짜 뭐가 있나" +cover: "/journal/a-strange-dream/certificate.jpg" +coverCaption: "첫 작품 에디션의 보증서" +hideCover: true +draft: false +--- + +간만에 정말 푹 잤다. 새벽에 깨지 않고 9시에 눈을 뜬 건 진짜 너무너무 오랜만이라 감격스러울 정도다. 되게 재밌는 꿈을 꾼 것 같긴 한데 금방 잊어버려 그런갑다 하고 있었는데, 방금 그 꿈이 생각났다. 되게 큰 뱀이랑 지네가 나온 특이한 꿈이다. 어쩌면 자기 전에 집에서 그리마를 본 것 때문인가. + +내용을 간단히 묘사해보자면, 뱀을 정말 애지중지하며 키웠다. 아주 커다랗게 키웠는데 어느 순간 나타난 지네가 잘라버리더라. 정확히 2번 잘렸다. 몸통은 모르겠고 조그맣게 남은 꼬리랑 머리가 살아 움직여 어디론가 떠났다. 지네의 크기도 심상치 않았다. 사람의 3배 정도로 비현실적인 모습이었고 꽤나 잘 지냈다. 키운다는 느낌보다는 같은 집에서 공생하는 형태였고, 너무 크고 멋있기도 해서 자극할 생각조차 들지 않았다. + +미신이나 신앙을 믿는 편은 아니지만 꽤 재밌는 꿈이기에 AI한테 해몽을 부탁했다. 꿈 해몽을 부탁한 건 1학기 특이한 수업 때 이후로 오랜만이다. 뱀과 지네 둘 다 동양 쪽에서는 꽤나 좋은 상징이라고 한다. 뭐 뱀이야 하늘과 땅을 잇는다는 그런저런 이유로 좋다는 건 알고 있었는데, 지네도 비슷한가 보다. 되게 쓸데없는 망상이긴 하지만 패턴플로우와 이어본다면! 내가 공들여 키워온 것이 새로운 형태로 탈바꿈하게 될 거라는… 것 같다. 꽤 좋은 쪽으로? 말이다. + +내 기분이 꿈에 반영된 걸까? 그럴 수도 있다. 패턴플로우를 작품으로써 판매했고, 그 돈을 밤 10시에 입금받았다. 기분이 아주 좋더라. 그 직전까지는 혹시나 중간에 문제가 생겨서 다른 곳으로 돈이 빠져버리거나 입금까지 딜레이가 생겨버릴까 계속 불안했는데, 막상 숫자로 내 계좌에 들어온 걸 보니 안심이었다. 그래서 오늘 아침 우체국에 가서 샌프란시스코로 패턴플로우를 보내고 온 참이다. 배송비에 관세까지 하면 약 20만 원 정도 들더라. 너무 비싸… + +작품으로써 판매하는 첫 번째 패턴플로우이기에 서명도 하고 보증서도 만들어 보냈다. 이때 현지님이 보증서 만들어줬는데, 내가 생각했던 것보다 훨씬 멋지고 예쁘게 만들어줘서 좋았다. 패턴플로우 형태로 만들 생각은 못했는데, 최고다! 앞으로 만들 보증서들도 이걸로 써야징. + +
+ 패턴플로우 첫 작품 에디션 보증서의 앞면과 패턴플로우 모양 뒷면 +
현지님이 만들어준 보증서. 앞면과, 패턴플로우 모양 뒷면.
+
+ +이번에 보내준 패턴플로우가 10월에 바로 전시된다. 체험형으로! 조금 알아봤는데, 전시되는 기간 동안 샌프란시스코 그쪽에서 SF TECH WEEK이란 행사가 진행된다고 한다. 되게 커다란 행사여서, 그쪽에서 패턴플로우를 많은 사람들이 만져보고 또 만들 거라 생각하면 재밌다. 여담으로 SF라 해서 나는 공상과학 그쪽인 줄 알았는데 샌프란시스코 약자더라 ㅋㅋ. + +사실 요 며칠간 기분이 좋지는 않았다. 나빴다면 나빴지. 새로 만들어보는 패턴들은 도무지 마음에 안 들고, 사운드 쪽도 재밌다고 하겠다고 해놓곤 안 하는 모습에 자괴감도 들고. 어떤 식으로 확장시키고, 또 사람들이 만들어준 걸 공유하고 수익화까지 이어갈 수 있을지 고민하는데 너무 어렵고. 광고 지원비도 월말에 준다고 하고, 그걸 정하기 위한 멘토링도 생각보다 늦어지고. 사실 아직도 뭘 어떻게 해야 할지 모르겠다. 되게 어렵… + +문서라거나 웹사이트라거나 손봐야 한단 걸 알면서도, 또 한번 하면 너무 힘들 거란 걸 아니까 안 하게 된다. 그렇다고 지원금으로 누굴 고용해서 할 수도 없는 영역이라… 결국에는 내가 해야 한다. 집주인이 공사한다고 나가라 해서 집 찾아보고 있는데, 이것도 되게 어렵다. 뭐든 간에 확실한 게 하나 없다. 되게 스트레스 받는데, 건강하게 오래 살려면 방법을 좀 찾아야 할 듯싶다. 그냥 어떻게든 되겠지 마인드로 살 수 있으면 편할 텐데. diff --git a/web/public/flash/bin/clock-v0.1.5/boot_app0.bin b/web/public/flash/bin/clock-v0.1.5/boot_app0.bin deleted file mode 100644 index 13562cab..00000000 Binary files a/web/public/flash/bin/clock-v0.1.5/boot_app0.bin and /dev/null differ diff --git a/web/public/flash/bin/clock-v0.1.5/patternflow.ino.bin b/web/public/flash/bin/clock-v0.1.5/patternflow.ino.bin deleted file mode 100644 index f50d7edf..00000000 Binary files a/web/public/flash/bin/clock-v0.1.5/patternflow.ino.bin and /dev/null differ diff --git a/web/public/flash/bin/clock-v0.1.5/patternflow.ino.bootloader.bin b/web/public/flash/bin/clock-v0.1.5/patternflow.ino.bootloader.bin deleted file mode 100644 index 247411ea..00000000 Binary files a/web/public/flash/bin/clock-v0.1.5/patternflow.ino.bootloader.bin and /dev/null differ diff --git a/web/public/flash/bin/clock-v0.1.5/patternflow.ino.partitions.bin b/web/public/flash/bin/clock-v0.1.5/patternflow.ino.partitions.bin deleted file mode 100644 index 223d9ebf..00000000 Binary files a/web/public/flash/bin/clock-v0.1.5/patternflow.ino.partitions.bin and /dev/null differ diff --git a/web/public/journal/a-strange-dream/certificate.jpg b/web/public/journal/a-strange-dream/certificate.jpg new file mode 100644 index 00000000..05b489d8 Binary files /dev/null and b/web/public/journal/a-strange-dream/certificate.jpg differ diff --git a/web/src/app/editions/editions-data.ts b/web/src/app/editions/editions-data.ts index 196b1dd9..45591899 100644 --- a/web/src/app/editions/editions-data.ts +++ b/web/src/app/editions/editions-data.ts @@ -252,40 +252,4 @@ export const EDITIONS: Edition[] = [ 'missing mic from a quiet room, says so on the Mic page, and lets ' + 'nothing drive the knobs until you turn it on.', }, - { - id: 'clock', - name: 'Patternflow Clock', - tier: 'community', - reason: 'not-universal', - maintainer: 'SeungHun Lee', - maintainerHref: 'https://github.com/engmung', - status: 'available', - summary: - 'A panel that tells the time: the hours and minutes cut out of ' + - 'whatever pattern is running, in a choice of faces.', - adds: [ - 'Huge digits the pattern shows through — hours over minutes upright, ' + - 'four across on a wide panel — or solid digits over the pattern, or ' + - 'a plain clock on a colour of your choosing', - 'Seven faces, every one open-licensed; adding your own is a TTF and ' + - 'one line in the generator', - 'A live preview on the panel\'s own console page that draws the same ' + - 'glyphs the panel does, and sends every change as you make it', - 'Time zones with their summer-time rule, so the clock moves itself in ' + - 'spring and autumn', - ], - hosted: { - version: 'v0.1.5', - url: 'https://patternflow.work/flash/bin/clock-v0.1.5/patternflow.ino.bin', - }, - source: 'https://github.com/engmung/Patternflow/tree/main/firmware/bundles/clock', - note: - 'For a panel on a shelf or a desk that you glance at. It carries ' + - 'nothing but the clock — no broker, no sequences, no sound, no ' + - 'weather — because a clock should not need any of that to exist. ' + - 'The digits are cut into the frame on its way to the panel, so the ' + - 'edges stay soft and the pattern keeps moving inside them; the clock ' + - 'stays off while the panel\'s own menus are up. It was a request ' + - 'from outside, and this is the edition made for it.', - }, ]; diff --git a/web/src/app/editions/page.tsx b/web/src/app/editions/page.tsx index c99648d4..ea8e1d3c 100644 --- a/web/src/app/editions/page.tsx +++ b/web/src/app/editions/page.tsx @@ -48,7 +48,9 @@ export default function EditionsPage() { One panel, more than one firmware. The first is what ships on the board and does everything; the rest exist for what it cannot carry. Switching is one click, and your patterns, Wi-Fi networks and - settings come with you. + settings come with you. What each firmware carries, feature by + feature and with a reel of each, is on the{" "} + features page.

diff --git a/web/src/app/features/Features.module.css b/web/src/app/features/Features.module.css new file mode 100644 index 00000000..5ef1c61b --- /dev/null +++ b/web/src/app/features/Features.module.css @@ -0,0 +1,156 @@ +/* Features — one list, one row per feature, a row opens to its details. + Shares the document shape, the summary paragraph and the link row with + /editions (that module is imported alongside this one). The list itself + is set as a table of contents rather than a shelf: the whole of it has to + fit on a screen, and the row is the unit — name, one line, a tag saying + where the feature lives. */ + +.list { + list-style: none; + margin: 0; + padding: 0; + border-top: var(--pf-rule-w) solid var(--pf-rule); +} + +.row { border-bottom: var(--pf-rule-w) solid var(--pf-rule-soft); } + +/* The closed row. A native
, so a click and a keyboard both open + it and nothing here needs JavaScript. The browser's own marker goes; the + name carries its own. */ +/* One line per row on a desktop: name, then the one-liner, then the tag at + the right edge. Ten rows fit on a screen, which is the point. On a phone + the one-liner drops under the name. */ +.head { + list-style: none; + cursor: pointer; + display: grid; + grid-template-columns: 13.5em minmax(0, 1fr) auto; + gap: 4px 18px; + align-items: baseline; + padding: 11px 4px 11px 0; +} +.head::-webkit-details-marker { display: none; } +.head:hover .name { color: var(--pf-led); } + +.name { + font-size: 16px; + font-weight: 500; + letter-spacing: var(--pf-track-tight); + line-height: 1.35; +} +.name::before { + content: '+'; + display: inline-block; + width: 20px; + color: var(--pf-led); + font-weight: 700; +} +.row[open] .name::before { content: '\2212'; } + +/* Explicit columns: in the markup the tag comes before the line (so a + phone can put the tag beside the name), which auto-placement would put + in the wrong column here. */ +.line { + grid-column: 2; + grid-row: 1; /* sparse auto-placement would drop it to a second row, after the tag */ + font-size: var(--pf-fs-body); + line-height: 1.45; + color: var(--pf-ink-muted); +} + +.tag { + grid-column: 3; + font-family: var(--pf-mono); + font-size: 10px; + letter-spacing: var(--pf-track-mono); + text-transform: uppercase; + white-space: nowrap; + padding: 2px 7px; + border: var(--pf-rule-w) solid var(--pf-ink-faint); + color: var(--pf-ink-muted); +} +.tag[data-home='every'] { border-color: var(--pf-led); color: var(--pf-led); } +.tag[data-home='audio'], +.tag[data-home='performance'] { border-color: var(--pf-ink-muted); color: var(--pf-ink); } +.tag[data-home='none'] { border-style: dashed; color: var(--pf-ink-faint); } + +@media (max-width: 760px) { + .head { grid-template-columns: minmax(0, 1fr) auto; } + .name { grid-column: 1; } + .tag { grid-column: 2; grid-row: 1; } + .line { grid-column: 1 / -1; grid-row: auto; padding-left: 20px; } +} + +/* The open row: the paragraph and facts on the left, the reel on the right; + one column on a phone. */ +.body { + display: grid; + grid-template-columns: minmax(0, 1fr) 300px; + gap: 16px 28px; + align-items: start; + padding: 4px 4px 24px 20px; +} +.text { min-width: 0; } +@media (max-width: 760px) { + .body { grid-template-columns: 1fr; padding-left: 0; } +} + +/* Two facts a person decides on, in the mono voice: what it needs, where it is. */ +.facts { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 6px 14px; + margin: 0 0 16px; + font-family: var(--pf-mono); + font-size: var(--pf-fs-mono); + line-height: 1.5; +} +.facts dt { + padding-top: 2px; + font-size: 10px; + letter-spacing: var(--pf-track-mono); + text-transform: uppercase; + color: var(--pf-ink-faint); +} +.facts dd { margin: 0; color: var(--pf-ink); } +.facts dd a { color: var(--pf-ink); } +.facts dd a:hover { color: var(--pf-led); } + +/* The reel. Instagram's embed page is tall; 560px shows the reel's frame + with its header and the post's own footer, and scrolls inside itself for + anything longer. It is only in the document once the row is open, so it + only loads then. */ +.reel { margin: 0; max-width: 300px; } +.reelFrame { + display: block; + width: 100%; + height: 560px; + border: var(--pf-rule-w) solid var(--pf-rule); + background: var(--pf-cream-2); +} +.reelCap { + margin: 8px 0 0; + font-family: var(--pf-mono); + font-size: var(--pf-fs-mono); + line-height: 1.5; + color: var(--pf-ink-faint); +} +.reelCap a { color: var(--pf-ink-muted); white-space: nowrap; } +.reelCap a:hover { color: var(--pf-led); } +.noReel { + margin: 0; + max-width: 300px; + padding: 16px; + border: var(--pf-rule-w) dashed var(--pf-rule); + font-family: var(--pf-mono); + font-size: var(--pf-fs-mono); + line-height: 1.5; + color: var(--pf-ink-faint); +} + +.embedNote { + margin: 14px 0 0; + font-family: var(--pf-mono); + font-size: var(--pf-fs-mono); + color: var(--pf-ink-faint); +} diff --git a/web/src/app/features/Reel.tsx b/web/src/app/features/Reel.tsx new file mode 100644 index 00000000..863d54ee --- /dev/null +++ b/web/src/app/features/Reel.tsx @@ -0,0 +1,31 @@ +import styles from "./Features.module.css"; +import type { Reel as ReelData } from "./features-data"; + +// An Instagram post, embedded by its permalink. +// +// Instagram serves a self-contained page at embed/ that needs no +// script of theirs on this site, which keeps the page static and keeps their +// JavaScript out of it. The frame is lazy, so nothing is fetched from +// Instagram until the reel scrolls into view — and the permalink under it is +// the way out when an embed is blocked, or when a phone would rather open +// the app. +export default function Reel({ reel }: { reel: ReelData }) { + return ( +
+