# The Foundry — TODO

Resume state for the URL→audio→stems→loops sampling tool. Snapshot **2026-08-21**.
Task IDs map to the session task board. Design: memory `project_foundry`.

## 🔵 In progress
- [ ] **#19 — Auto-tune loop** (create samples → grade → iterate sampling settings).
      PLN's closed-feedback main quest. **Harness BUILT & VALIDATED** (commit
      `6f85012`): `engine/autotune.py` (evaluate/search), `autotune.py` CLI,
      `loops.analyze_stem` `weights` override, 4 unit tests + a real run (baseline
      0.690 → best 0.763, **+10.5%** on 2 drum stems). Finder defaults (`loops.W`)
      deliberately UNCHANGED. **Exact next steps to resume cold:**
      1. Get real fuel: `cd tools/foundry && python3 foundry.py demos --all --sep`
         (fetches+separates the 10 sampling classics — SLOW, run as a durable bg
         job). Existing fuel: `~/Downloads/separated/htdemucs/*/{drums,bass,vocals,
         other}.wav` (27 tracks).
      2. Add the anti-gaming 2nd objective: recall-vs-provenance-GT (reuse
         `armada/tide-table/build_finder_eval.py` + `build_provenance.py`) so we
         don't just amplify the seam/zc confound. Make objective multi-objective
         (grade × recall) in `engine/autotune.py`.
      3. Per-stem-ROLE tuning: vocals want chop mode + different weights than drums
         (`--stems …/drums.wav` vs `…/vocals.wav`; analyze_chops path for vocals).
      4. Full campaign: more stems (`--max-stems`↑) + more configs (`--n`↑); factor
         window-feature extraction out of `analyze_stem` so weight sweeps are
         instant (extract-once/score-many — the scarcity-minded speedup).
      5. THEN adopt a VALIDATED profile as the new `loops.W` default, per role,
         with the lift numbers.
      Run: `python3 autotune.py --stems <glob> --n 24 --max-stems 4`.
      Objective so far = mean full-rubric grade × coverage.
      **VERIFY-RERANK now BUILT & validated end-to-end on the Super freak dub kit.**
      The finder scored the seam on the RAW window, but export zc-snaps (B1) — and the
      snap (plus the 3-dp start/end rounding export slices at) changes the wrap. Several
      proxy-seam≈1.0 windows CLICKED after the snap. Fix: `loops._verify_rerank` re-scores
      the ~3×top_n survivors' seam on the TRUE post-snap window (reusing `grade.seam_score`,
      DRY), swaps proxy→true seam in the composite, and hard-demotes true-seam <
      `VERIFY_SEAM_FLOOR`. New kwarg `analyze_stem(..., verify=True)` (default on; False
      reproduces the pre-#19 raw-proxy ranking for autotune baselines). Also fixed the
      sparse-material seam false-positive (`grade.seam_score`): the mean-|2nd-diff|
      denominator collapsed to ≈0 on sparse dub drums (129× click ratio on a clean loop);
      now 90th-percentile, amplitude-floored (2–3× on the same loop). Validation
      (drums+other, bars 1/2/4, BPM 132.5, export-faithful grades):
        drums top-3  BEFORE C/C/A  →  AFTER **S/S/A** (clicking @93.08 & @24.06 demoted)
        other top-3  BEFORE B/B/B  →  AFTER **S/S/S** (bad favorite @56.74 demoted out)
      +2 rerank regression tests + 1 sparse-percussion test; suite 57→60 green.

## 🟢 Landed (2026-08-21) — #20 batch, via producer stem packs

`engine/stempack.py` + `engine/roles.py` + `engine/kitcheck.py` + `kitgate.py` +
`restage.py`, branch `claude/fred-kits`, 14 commits, suite 86 → 112.
Full write-up: `armada/tasks/027-the-label-and-the-lens.md`, memory
`reference_stempack` + `feedback_label_and_lens`.

```bash
cd tools/foundry
python3 -m engine.stempack "<pack>" --jobs 9 --no-link --out /tmp/cut
python3 kitgate.py /tmp/cut/fred_kits.json            # then --link
```

## 🟡 In progress — finish the Fred pack (RESUME HERE)

**Staging root is `~/Work/Sound/Samples/Fred/output/<kit>/`** (PLN asked for it there,
2026-08-21), passed as `--samples-root`; Dirt-Samples symlinks point into it.

Runs 1-5 were all discarded, each superseded by a fix found by looking at its output:
161 samples (pre-dead-bar-fix), 153 (pre-rotation-fix), 153 (**24 of them digital
silence graded A/B**), aborted (wrong staging root), 129 (silence filtered at export
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.

```bash
cd tools/foundry
python3 -u -m engine.stempack "$HOME/Work/Sound/Samples/Fred/fred again stems" \
    --no-link --samples-root "$HOME/Work/Sound/Samples/Fred/output" --out <fresh dir>
```

`--jobs` now defaults to half the cores (children run at nice 19 / SCHED_IDLE — PLN
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

`armada/ui/kits.html` + `src/kits/` + `tools/foundry/kitindex.py`. Browse every kit
under the sample root (the 111 that predate the Foundry included), audition a sample
untouched or looped, rack several on one bar grid at a chosen tempo, export a
paste-ready `.tidal` block. The rack plays `rate = dur / (bars * barlen)` — literally
`loopAt` — so what you hear is what the rig does. 13 vitest cases on the export.

```bash
cd armada/ui && nvm use 22 && npm ci && npm run dev     # /kits.html
python3 ../serve.py --dir dist --port 8792              # LAN / phone
cd ../../tools/foundry && python3 kitindex.py --all     # regenerate the index
```

Open follow-ups, none blocking: the Strudel tab emits text only (no `@strudel/web` —
the deck already plays the rack and two audio graphs could disagree); the rack has no
swing control wired to the UI though the engine supports it; and `?kits=fred_*` narrows
the rail but there is no way to save a rack as a named preset.

## 🔴 Open
- [ ] **#22 — Point stempack at the rest.** Same driver, no new code:
      the 13 ACAPELLAS + 13 INSTRUMENTALS in `~/Work/Sound/Samples/Fred/` (already
      44.1 kHz, so no resample; acapellas want chops mode), and the ~27 demucs catches in
      `~/Downloads/separated/htdemucs/`. Each needs a `--prefix`.
- [ ] **#23 — Run `kitcheck`/`restage` over the whole existing corpus.** The dead-bar and
      periodicity tests are general, and `Samples/` has 163 kits that have never been
      audited; hand-cut kits also carry whatever level their source had.
      `python3 restage.py --all-under ~/Work/Sound/Samples --dry-run` is the first look.
      Expect "grades S, useless" to be non-rare.
- [ ] **#20b — Batch-explore the merged corpus** (the original #20 framing).
      Workflow is one-catch-at-a-time; build batch exploration:
      - Batch driver: `find_takes` over MANY source stems (the ~30 in
        `~/Downloads/separated/htdemucs` + new fetches) → grade → surface the best
        loops/chops corpus-wide ("what's worth sampling across everything pulled").
      - **Stem-role-aware**: skip near-silent stems, mode per role (chops for
        vocals, loops for rhythmic), rank by grade. (e.g. Loituma has no drums —
        bass + high voice are the interesting stems; grid-from-drums already falls
        back drums→bass→first.)
      - Surface in the Hall of Fame / a "fresh catches" view: best ungraded-yet
        candidates corpus-wide, click to audition + forge.
      - Ties to #19: once settings are tuned per role, batch-explore applies them
        at scale. Relates #11 tierlist (showcase pattern), #9 merge.

> Note: the Foundry engines are also productized over HTTP by the **Fourier**
> audio API — `/separate` (#30) wraps `engine.separate`, `/loops /grade /correlate`
> (#31) wrap the rest. See `armada/api/TODO.md`.
