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.
| Name |
Last commit
|
Last update |
|---|---|---|
| .gitea/workflows | Loading commit data... | |
| armada | Loading commit data... | |
| blocks | Loading commit data... | |
| blog | Loading commit data... | |
| config/wireplumber | Loading commit data... | |
| copycat | Loading commit data... | |
| docs | Loading commit data... | |
| gifts | Loading commit data... | |
| lib-parvagues | Loading commit data... | |
| live | Loading commit data... | |
| output | Loading commit data... | |
| releases | Loading commit data... | |
| sandbox | Loading commit data... | |
| study | Loading commit data... | |
| test | Loading commit data... | |
| tools | Loading commit data... | |
| utils | Loading commit data... | |
| visuals/slop | Loading commit data... | |
| viz | Loading commit data... | |
| .gitignore | Loading commit data... | |
| BootTidal.hs | Loading commit data... | |
| BootTidal.hs.broken | Loading commit data... | |
| BootTidal.visuals.broken.hs | Loading commit data... | |
| CLAUDE.md | Loading commit data... | |
| EVENING.md | Loading commit data... | |
| FOH_MASTER.md | Loading commit data... | |
| LAST_THIRD.md | Loading commit data... | |
| LICENSE | Loading commit data... | |
| MORNING.md | Loading commit data... | |
| PatchBay_MultiChannel.xml | Loading commit data... | |
| PatchBay_Perf.xml | Loading commit data... | |
| PatchBay_Perf5.1.xml | Loading commit data... | |
| README.md | Loading commit data... | |
| RUNBOOK.md | Loading commit data... | |
| SHOPPING_LIST.md | Loading commit data... | |
| TASKS_DUMP.md | Loading commit data... | |
| backlog.md | Loading commit data... | |
| bullet_train.tidal | Loading commit data... | |
| collide.sh | Loading commit data... | |
| debug.tidal | Loading commit data... | |
| didacticpatternvisualizer.pde | Loading commit data... | |
| gig-up.sh | Loading commit data... | |
| gpu-mode.sh | Loading commit data... | |
| hello.sc | Loading commit data... | |
| init_midi.sh | Loading commit data... | |
| intro.md | Loading commit data... | |
| library.txt | Loading commit data... | |
| link_jack.sh | Loading commit data... | |
| link_samples.sh | Loading commit data... | |
| link_samples_all.txt | Loading commit data... | |
| main_fairbanks.scd | Loading commit data... | |
| notes.md | Loading commit data... | |
| parvagues-sc.service | Loading commit data... | |
| perf-audio.sudoers | Loading commit data... | |
| perf-tray.py | Loading commit data... | |
| perf.sh | Loading commit data... | |
| sample_watch.scd | Loading commit data... | |
| samples.txt | Loading commit data... | |
| setlist_opal2026.txt | Loading commit data... | |
| start_and_midi.scd | Loading commit data... | |
| test.tidal | Loading commit data... |