Commit 830320db by PLN (Algolia)

docs: board + archive — the Fred pack landed, #20b and #21 written up

TODO.md: the Fred section moves from "RESUME HERE" to shipped, with the gate numbers,
the six-cuts-five-discarded history, and the audition-first list of 6 kitcheck
advisories that are now waiting on PLN's ears rather than on any tool.

completed-archive.md: #21 (La Cale) and #20b (the pack) as standalone entries. The
learnings worth carrying: the rack had to BE `loopAt` rather than resemble it; a gate
blocks on defects and reports properties, because blocking on a property is a gate
everybody learns to ignore; `n` is the folder's index, never the manifest's; a rounded
tempo is a stretched loop; and three tests that passed while proving nothing, which was
most of the day's actual value.
parent 42c35f24
...@@ -1703,3 +1703,114 @@ measuring the output, never by reading code. ...@@ -1703,3 +1703,114 @@ measuring the output, never by reading code.
**Deps.** Unblocked #21 (the auditioner SPA PLN asked for), #22 (same driver at the **Deps.** Unblocked #21 (the auditioner SPA PLN asked for), #22 (same driver at the
acapellas + the 27 demucs catches), #23 (kitcheck/restage over the 163 existing kits). acapellas + the 27 demucs catches), #23 (kitcheck/restage over the 163 existing kits).
Runs 1 and 2 were discarded — each predated a fix; run 3 is the deliverable. Runs 1 and 2 were discarded — each predated a fix; run 3 is the deliverable.
---
### #21 — La Cale, the kit auditioner (browse · loop · rack · export)
**Description.** PLN, after the first Fred kits landed: *"can you spawn a reusable SPA so
i can listen to the samples, try each as a loop, quickly sequence? TBH could be a strudel
playground i'm open to this idea lets think :P"*, then *"the SPA allows click-based
looping not just strudel coding"*. The need behind it: a grade in a manifest is a claim,
and the claim had been wrong twice in a week. Nothing in the toolbox could turn 150 cut
loops into a decision.
**Done.** `armada/ui/kits.html` + `src/kits/{engine,tidal,types,Waveform,SampleRow,Rack,
KitAuditioner}` + `tools/foundry/kitindex.py`. Merged to master as part of `0f27184`;
built across `ec20026`, `18ec7b4`, `e2b701e`, `c9e7558`, `1edeecf`, `ee8d4ac`.
Browse every kit under the sample root (the 111 pre-Foundry ones included), audition a
sample untouched or looped, rack several on one bar grid at a chosen tempo, export a
paste-ready `.tidal` block. 24 vitest cases; `npm test` added to the project.
**Learnings.**
- **The rack had to BE `loopAt`, not resemble it.** `rate = dur / (bars × 4 × 60/bpm)` is
exactly what `loopAt bars` does, so what the browser plays is what the rig plays and the
export is the same two numbers in Tidal's syntax. Anything looser would make the tool a
preview instead of a test.
- **Scheduling per bar against an absolute grid, not free-running nodes.** The cutter's
bar tolerance is 1 ms, which is 60 ms of skew after a minute — enough to hear.
- **Two audition modes because they answer different questions.** Once = the file
untouched (attack, tail). Loop = the seam, which is the only thing that decides
usability, because it is the sound the audience gets every bar.
- **Deliberately not wavesurfer**, though the project already depends on it: a hundred
rows would mean a hundred fetches and decodes to draw 200 px. `kitindex.py` ships a
128-point envelope per sample; audio is fetched only on play.
- **Strudel was considered and rejected as the play engine.** It is Tidal syntax in the
browser, but the deck already plays the rack and a second audio graph is a second thing
that can disagree about what the same cells mean. The tab emits text for strudel.cc.
The dependency was uninstalled — one nothing imports is a false statement about the
project.
- **The fader is linear, Tidal's `gain` is quartic.** 0.5 exports as `gain 0.84`. Copying
it across is a 12 dB error. Pinned by a test.
- **Three rounds of "screenshot it rather than assume it renders"** found what typecheck
and lint could not: truncated filenames with no tooltip, a dead `! 0` control, a kit
tempo rounded 132.5→133 (which `loopAt` turns into a 0.4% stretch of every loop in the
kit), three loops off one stem labelled identically (fixed by surfacing `start_s` as
`@4:31`), and CLAP tag chips painted over the first 15% of every waveform — i.e. over
the downbeat, which read as a gap in the audio and nearly sent me hunting a bug in the
envelope generator.
- **It cannot be a published Artifact**: the CSP cannot reach a localhost audio server.
- Two staleness traps closed: the envelope cache keys on path+size+mtime (a restaged kit
must miss 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/`, since `npm run build` copies `public/`
exactly once.
**Deps.** Needed #20's manifests to have anything to browse; unblocks #23 (auditing the
111 pre-Foundry kits) by making a verdict cheap to reach.
---
### #20b — The Fred pack shipped (150 samples, 43 kits)
**Description.** Turn Fred again..'s dropped stem dropboxes into loadable Tidal kits, one
per track × role family, and have PLN able to validate them by loading them.
**Done.** Merged to master (`0f27184`, `831d3f1`, `ebcd566`). 150 samples · 43 kits · 9
tracks at `~/Work/Sound/Samples/Fred/output/<kit>/`, symlinked into Dirt-Samples,
registered `third_party` with provenance. Record in `tools/foundry/packs/fred/`. Python
suite 112 → 128.
**Learnings.**
- **Silence was the grader's optimum.** 24 of 153 loops were digital silence at −104…−75
dBFS, graded 0.707–0.896, tier A and B. Every term in the loop rubric is vacuously
perfect on nothing — the seam between two silences is immaculate, the DC is zero, the
zero crossings trivial, the length bar-exact by construction — and level carried 10%.
Nobody wrote "prefer silence"; it fell out of five correct measures, none of which had
an opinion about whether there was audio. **Presence is a precondition, not a weighted
term.** The floor was PLACED in a measured 28.4 dB gap, not tuned.
- **Rejecting late is not the same as rejecting early.** Filtering at export took BIGHEN's
tonal kit from four silent loops to *one* real one; gating before the finder ranks took
it to two, and brought its fx kit back from nothing. A rejected slot is a slot spent.
- **A relative check cannot see a uniform failure.** The dead-bar test compares bars to
each other, so it catches a fade-out and is blind to a clip that was never there.
- **The gate blocks on defects, reports properties.** It first blocked all 150 samples on
20 `near-silent` and 17 `mono-incompatible` files, none broken: a kit is gain-staged as
a unit and never boosted, so its quiet members are quiet *on purpose*, and low
interchannel correlation in a vox double is the width someone paid for. Blocking on a
property is not the cautious direction — it is a gate nobody can pass, which is a gate
everybody learns to ignore. `mono-incompatible` **does** block on bass, where mono
collapse kills the fundamental and nobody chose that.
- **`n` is the folder's index, not the manifest's.** Export writes into a kit dir it never
empties, so a re-cut that renames one file leaves both and shifts every index after it —
silently, with a correct-looking manifest and cheat sheet. kitgate now compares the two
listings index-for-index.
- **A rounded tempo is a stretched loop.** The cheat sheet printed "133 bpm" over files cut
at 132.51. Each kit now prints its own `setcps`, because `loopAt` squeezes into N bars of
the *current* cps and leaving the ÷60÷4 to the reader is what makes the rounding bite.
- **Restage was not idempotent**: PCM_24 quantization left the peak a hair above the
ceiling, so it asked to restage forever, requantizing every file each round. 0.05 dB
dead band.
- **A background job that takes the desktop down is not a background job.** Nine children
at 88% of a core each → load 15.6/16 on the machine PLN performs on, and he said so
mid-run. Half the cores by default, `nice 19` + `SCHED_IDLE` via a preexec hook.
- **Three tests passed while proving nothing**, and finding that out was most of the day's
value: wrong fixture (a stem gated on its own beat grid cannot generate a window inside
its own silence), wrong scope (a hook looked up via `__globals__` where a nested
function does not live → `None` → no-op lambda), wrong lens (a "sparse" fixture read
−32 dB RMS against a −45 threshold). The habit is now explicit: break the fix, watch the
test fail, put it back.
**Deps.** Built on #20's `engine/stempack.py`; produced the data #21 browses. Unblocks #22
(the acapellas, instrumentals and ~27 demucs catches) with no new code.
...@@ -56,46 +56,47 @@ python3 -m engine.stempack "<pack>" --jobs 9 --no-link --out /tmp/cut ...@@ -56,46 +56,47 @@ python3 -m engine.stempack "<pack>" --jobs 9 --no-link --out /tmp/cut
python3 kitgate.py /tmp/cut/fred_kits.json # then --link python3 kitgate.py /tmp/cut/fred_kits.json # then --link
``` ```
## 🟡 In progress — finish the Fred pack (RESUME HERE) ## 🟢 Landed (2026-08-22) — the Fred pack SHIPPED
**Staging root is `~/Work/Sound/Samples/Fred/output/<kit>/`** (PLN asked for it there, 150 samples · 43 kits · 9 tracks, staged at `~/Work/Sound/Samples/Fred/output/<kit>/`
2026-08-21), passed as `--samples-root`; Dirt-Samples symlinks point into it. (PLN's choice) and symlinked into Dirt-Samples. `s "fred_marea_drums" # n 3` resolves.
Record: `packs/fred/{fred_kits.json,.md,.tidal,kitcheck.md}`. Merged to `master`
(`0f27184`); branch `origin/claude/fred-kits` kept as history.
Runs 1-5 were all discarded, each superseded by a fix found by looking at its output: S×84 A×28 B×38 · 111 loops + 39 chops · 150 of 150 distinct sounds
161 samples (pre-dead-bar-fix), 153 (pre-rotation-fix), 153 (**24 of them digital restage 0/43 · 0 grade mismatches · kitcheck 0 failing · n indices verified
silence graded A/B**), aborted (wrong staging root), 129 (silence filtered at export bank-check: "No shadowed bank names" · rights 43/43 third_party w/ provenance
only, so BIGHEN's tonal kit lost 3 loops instead of gaining 3 real ones). **Run 6** is
the first with the finder itself refusing silent windows. Ground truth held: MAREA declares `123BPM`, grid read **123.05**.
PLEASEMAKEITBETTER at 20% tempo confidence correctly shipped chops only.
**Six cuts, five discarded, each by looking at the output** — 161 (dead bars), 153
(rotation), 153 (**24 files were digital silence graded A/B**), aborted (staging root),
129 (silence filtered at export only, so BIGHEN's tonal kit lost 3 loops instead of
gaining 3 real ones). Full account: `armada/tasks/028-la-cale-and-the-empty-loop.md`,
memory `feedback_presence_is_a_precondition`.
**Audition first** (6 kitcheck advisories, weak bar periodicity — bar-aligned but not
repeating units): `marea_drums/10_marea_4b` and `11_marea_4b` (0.12 / 0.08 vs a 0.30
drums floor — the suspicious pair), then `angie_tonal/03_padstuff_2b`,
`angieinterlude_bass/00_pad_2b`, `bighen_tonal/01_synth_2b`, `yasminah_tonal/00_keys_4b`.
Plus 20 quiet / 17 wide / 2 marginal seams, reported not blocked. **Awaiting PLN's ears.**
To re-cut or cut another pack:
```bash ```bash
cd tools/foundry cd tools/foundry
python3 -u -m engine.stempack "$HOME/Work/Sound/Samples/Fred/fred again stems" \ python3 -u -m engine.stempack "<pack dir>" --no-link \
--no-link --samples-root "$HOME/Work/Sound/Samples/Fred/output" --out <fresh dir> --samples-root "$HOME/Work/Sound/Samples/<pack>/output" --out <fresh dir>
python3 restage.py --cuts <out>/fred_kits.json --dry-run # expect 0
python3 kitgate.py <out>/fred_kits.json # 5 checks
python3 ../../armada/tide-table/register_fred_rights.py <out>/fred_kits.json
python3 kitgate.py <out>/fred_kits.json --link
python3 kitindex.py --root <staging root> --root "$HOME/Work/Sound/Samples" \
--cuts <out>/fred_kits.json # the index La Cale reads
``` ```
`--jobs` now defaults to half the cores (children run at nice 19 / SCHED_IDLE — PLN `--jobs` defaults to half the cores; children run at nice 19 / SCHED_IDLE.
works on this machine). Takes ~20-30 min, fully deterministic.
**Exact steps to finish, in order:**
1. `python3 restage.py --cuts <out>/fred_kits.json --dry-run` — expect **0 kits**
changed (export stages, and restage has a 0.05 dB dead band so it is idempotent).
2. `python3 kitgate.py <out>/fred_kits.json` — five checks, must be green on grade /
kitcheck / shadowing / `n` indices. `rights` is advisory and WILL warn until step 3.
3. `python3 ../../armada/tide-table/register_fred_rights.py <out>/fred_kits.json`
— every `fred_*` bank as `third_party` **with provenance**, so per-track release
blocks by design. Before linking.
4. `python3 kitgate.py <out>/fred_kits.json --link` (takes each kit's root from the
manifest), then `python3 ../bank-check.py --since 1` to confirm 0 shadowed.
5. `python3 kitindex.py --root "$HOME/Work/Sound/Samples/Fred/output" --root \
"$HOME/Work/Sound/Samples" --cuts <out>/fred_kits.json` — the index La Cale reads.
6. Copy `fred_kits.{json,md,tidal}` + `kitcheck.md` into `packs/fred/` and commit
(`packs/README.md` — manifest, not audio).
7. Report: per-kit tier histogram, distinct-sound count, the "worth a second listen"
table, and the paste-ready `n` indices.
**Ground truth:** MAREA declares `123BPM` in its filenames; the grid must read
**123.0**. PLEASEMAKEITBETTER reads 20% tempo confidence and correctly ships
chops only.
## 🟢 Landed (2026-08-21) — #21 La Cale, the kit auditioner ## 🟢 Landed (2026-08-21) — #21 La Cale, the kit auditioner
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment