-
feat(cale): a page for hearing whether a kit is any good · ec200263
PLN asked for an SPA to audition the cut samples, "click-based looping not just strudel coding". La Cale — `armada/ui/kits.html`, the hold, where the cargo gets checked before it goes on stage. The reason it needs to exist is that a grade in a manifest is a claim, and this week the claim has been wrong twice in ways no report caught: a loop that fades to silence grades S, and 24 loops of pure digital silence graded A. Both are obvious in two seconds of listening and invisible in a table. **Auditioning is two different tests, so it is two buttons.** Play-once gives you the file untouched — attack, tail, what it actually is. Loop plays it seamlessly at rate 1, which is the only way to hear the seam, and the seam is the thing that decides whether a loop is usable because it is the sound the audience gets every bar. One AudioBufferSourceNode with `loop = true`, so drift cannot exist and nothing is resampled between the file and the speaker. **The rack is where a kit is actually judged**, because the questions that matter — do these two loops sit together, does this vocal survive on that break, is the bass really at this tempo — cannot be answered one file at a time. It schedules every voice from a look-ahead clock against an absolute grid, at rate = buffer.duration / (bars * 4 * 60 / bpm) which is exactly `loopAt bars`. So the layering you hear is the layering the rig plays, and the exported block is not an approximation of the session — it is the same two numbers in Tidal's syntax. Scheduling per bar rather than letting nodes free-run matters more than it sounds: the cutter's bar tolerance is 1 ms, which is 60 ms of skew after a minute. The grid means what Tidal means. A bar-loop row has cells only on multiples of its own length — a 4-bar loop in an 8-bar phrase has two entries, not eight, because firing it on bar 3 plays its bar 1 under the phrase's bar 4 — and those cells become `mask "t f"`. A chop row is 16 steps in a bar and becomes `s "x ~ ~ x …"`. Three things the export gets right that are easy to get wrong, all pinned by tests: the fader is linear but Tidal's gain is quartic, so 0.5 exports as `gain 0.84` and copying it across would be a 12 dB error; families land on their own `dN` with no two voices sharing an orbit; and the mask samples the grid on the loop's stride, not every bar, which otherwise silences an entry that is switched on. Deliberately not wavesurfer, though the project already depends on it: a kit page shows a hundred rows, and a hundred instances means a hundred fetches and decodes to draw something 200 px wide. `kitindex.py` precomputes a 128-point envelope per sample, so a row costs one `<svg>` and no network — audio is fetched only when something is played. Same ship-the-index instinct as the tide-table. `kitindex.py` merges two sources on purpose. The FOLDER is the truth about what exists (via `pvbanks.playable`, so it agrees with kitgate and the watcher), which is why the 111 kits that predate the Foundry are browsable too. A cut MANIFEST is the truth about what a file MEANS — tier, family, bars, bpm, CLAP tags — and none of it is re-derived here, so the page and the gate cannot disagree. `n` comes from the folder listing, never the manifest order, because `n` is what SuperDirt plays. Two staleness traps closed while building it: the envelope cache is keyed on path+size+mtime, so a restaged kit misses it (a stale envelope over fresh audio is a lie that looks like data), and the index is written to `dist/` as well as `public/`, because `npm run build` copies public/ once and the LAN page would otherwise show an index from before the last cut. Also: the dev server hardcoded `Content-Type: audio/flac` for everything under /audio. The masters are FLAC; the kits are WAV. Now by extension, matching serve.py's `guess_type`. 13 vitest cases on the export; `npm test`. Verified end to end through the real path — `serve.py --dir ui/dist` returns 200 on /kits.html and 206 `audio/x-wav` on a Range request through the new `samples` mount.PLN (Algolia) authoredec200263
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| engine | Loading commit data... | |
| packs | Loading commit data... | |
| research | Loading commit data... | |
| tests | Loading commit data... | |
| ui | Loading commit data... | |
| .gitignore | Loading commit data... | |
| PHASE2_LOOPFINDER.md | Loading commit data... | |
| README.md | Loading commit data... | |
| TODO.md | Loading commit data... | |
| autotune.py | Loading commit data... | |
| demos.json | Loading commit data... | |
| foundry.py | Loading commit data... | |
| kitgate.py | Loading commit data... | |
| kitindex.py | Loading commit data... | |
| restage.py | Loading commit data... | |
| server.py | Loading commit data... |