# web/
`index.html` is the browser setup tool for NIBBLE-KO. It is a single
self-contained file — no build step, no dependencies, no server. Open it
locally, or serve the directory over `http://localhost`, and it talks to the
card over WebMIDI SysEx.
## Using it
1. On the card, hold the switch **Down** and press **B + D**. All four pads
glow slowly: the card is on USB. **The loop keeps playing** — assignment
is instant, so a new sound can be judged against the pattern it has to sit
in. Only recording stops. (An upload does silence the card, because
writing flash stops everything; see below.)
2. Open `index.html` in **Chrome or Edge** (Safari and Firefox have no
WebMIDI) and click **Connect USB**.
3. The card appears as a MIDI port named `NIBBLE-KO (Workshop)` — the page
finds it by that name, so if `usb_descriptors.c`'s product string changes,
change `DEVICE_RE` in `index.html` to match.
The card only enumerates once it is in WebUI mode, so "card not found"
usually means step 1 has not happened yet. The page watches for the device
appearing rather than making you press Connect again.
## What is real, and what is not
**The Kit and Samples tabs talk to the card.** Assignment (`MSG_SET_SOURCE`,
instant, no reboot), saving the kit (`MSG_SAVE_MAP`), and the whole sample
library — upload, rename, delete, erase — are all wired up.
**Patterns save and load.** The card keeps its three pattern slots in RAM
only, so they are lost at power-off; the JSON file this page writes is the
permanent copy. Save reads a slot over `MSG_PAT_GET` and offers it as a
download, Load pushes a file back with `MSG_PAT_SET`. Neither touches flash,
so neither reboots the card — a loaded pattern is playable straight away
(hold D, tap A/B/C).
A pattern carries **no audio**: each event names a voice index, not a sample.
So a pattern saved against one kit plays against any other, and re-pointing a
voice changes what an existing pattern sounds like without touching it.
**Mutes and FX are reference displays**, as is everything on the Patterns tab
below the save/load panel. The firmware has no SysEx messages for mute-group
assignment or loop settings yet, so those tabs document the card's gestures
rather than configuring anything. The page says so rather than pretending
otherwise.
## The library model
A sample is **not** tied to a pad. Uploads go into a library of up to 32
numbered entries, and each of the twelve voices names one sound: its own
synth character, a built-in sample, or a library entry. So:
- one recording can be played by several voices, costing the space of one
- re-pointing a voice is one byte, not a second copy of the audio
- patterns are unaffected — they store voice indices, so changing what a
voice plays changes what an existing pattern sounds like without touching
the pattern
Entries are `USER1`, `USER2`… until you name them; uploads take their name
from the filename automatically.
**Deleting frees the slot, not the space.** Uploads append and nothing
compacts the region, so the Samples tab reports live audio and the append
watermark separately — "3 samples totalling 40KB, 900KB consumed" is a real
state, and only Erase All resets it.
## Every write reboots the card
Uploading, saving the kit, renaming, deleting and erasing all write flash,
and writing flash means masking USB — after which TinyUSB cannot be resumed.
So each of those ends with the card restarting, dropping off USB, and coming
back up **playing** rather than in WebUI mode. To carry on: press
switch+B+D again and click Connect.
This is not a limitation of the browser tool but of the hardware; the
reasoning is in `docs/LESSONS.md` and `webui.cpp`'s
`CommitHeaderAndReboot()`. Assignment changes are the exception — they take
effect immediately and only need saving when you want them to survive a
power cycle.
## Audio conversion
Uploads are converted in the browser to the same 8-bit mono 48kHz format
`tools/importwav.py` produces for baked samples: sum to mono, resample, trim
silence (keeping 1ms of pre-roll so attacks survive), loudness-match by RMS
rather than peak, 4ms fade-out, TPDF dither. A file uploaded here should
sound like the same file baked at build time.
The loudness target (0.12) is applied across everything staged in one send,
so slots do not jump in level relative to each other, and it is set *before*
the 8-bit conversion because it decides how many of the eight bits get used.
## Two different size limits
The card reports both, and confusing them sends people deleting samples they
did not need to:
- **per-upload cap** (~160KB) is the card's RAM staging buffer. The whole
transfer is buffered in RAM before any flash is written, because writing
flash takes USB down with it.
- **region size** (~1MB) is storage. Uploads **append**, so audio too big for
one pass usually fits across two.
## Provenance and licence
This directory is licensed under **GPLv3** (see [`LICENSE`](LICENSE)), not
the CC BY 4.0 the rest of the repository uses. Two things were carried over
from elsewhere, and each explains a different half of that:
**The visual design** — dark theme, EB Garamond, the tab/panel language, and
a fair amount of the actual CSS (hex values, spacing figures, component
structure) — was written against
[Johan Eklund's Resonator](https://johaneklund.io/resonator/) web UI
([source](https://github.com/TomWhitwell/Workshop_Computer/tree/main/releases/21_resonator/docs),
GPLv3). That is real values copied across, not an independent
reimplementation from looking at the page, so this counts as a derivative
work under copyright and takes Resonator's own licence rather than this
repo's default.
**The connection handling, ack queueing and upload sequence** are ported
from `../../WorkshopBio/web/index.html` (also Andy Jenkinson), whose
comments record several bugs worth not repeating — in particular the ack
queue, which exists because installing a reply handler per wait drops any
reply arriving between waits and puts every later wait one message behind.