# Phase 2 — The Loop Finder · research charter

> Status: **research pending**. This charter scopes a deep-research pass (Sonnet
> 4.6 agents) that precedes any `engine/loops.py` code. engine1 (dl + stem) is
> live; this is the Audacity-replacement half.

## The goal, in one line

From a separated stem (or the source), surface **bar-aligned, click-free loop
candidates ranked by musical usefulness** — automating the *mechanical* drudgery
so PLN spends ears only on the *creative* call: which loop is the one.

## Design philosophy — automate the mechanical, reserve the ear

The machine is allowed to decide anything **objective and verifiable**; the human
keeps everything **subjective and musical**. This is the house rule already in
force for mastering (machine catches gross errors, ears judge feel —
`feedback_verify_own_renders`, `feedback_mastering_eda`).

| Machine owns (mechanical) | Human owns (creative) |
|---|---|
| tempo / beat / downbeat grid | *which* candidate is musical |
| bar-length region enumeration (1/2/4/8 bars) | the vibe / genre fit |
| zero-crossing snap (click-free in/out) | naming + final trim nudge |
| loopability score (seam discontinuity, self-similarity) | one-shot vs loop intent |
| energy / novelty ranking, dedup | what to actually keep |

A wrong machine guess must cost **zero** to override — candidates are suggestions
on a waveform, never auto-applied. The UI is a *fast chooser*, not an autocropper.

## The real flow this replaces (grounded 2026-06-22)

```
yt  →  ~/Downloads/separated/htdemucs/<track>/   (demucs stems; 27 kits, 9 recent)
    →  hand-cut in Audacity
    →  ~/Work/Sound/Samples/<kit>/               (named loops; 135 kits, 7 recent)
    →  ln -s into Dirt-Samples/<kit>             (so SuperDirt scans it)
    →  used in .tidal as e.g. "crimewave", "diams_dj", "humpty"
```

`~/Work/Sound/Samples/` is the **custom sample root** loops land in — then a
symlink `Dirt-Samples/<kit> → Samples/<kit>` closes the loop so SuperDirt loads
it as `s "<kit>"` (325/508 of Dirt-Samples entries are exactly this). **Both
steps are already implemented mechanically** in `engine/publish.py` +
`foundry.py link <kit>`; the loop-export UI just calls them. Kits are bimodal
(sub-second one-shots ↔ multi-minute stems), so "loop" here spans both — strand
A must characterize the real distribution.

**Ground truth is free:** `crimewave` (24 wavs), `diams_dj` (16), `humpty` (9)
each pair a source stem (in `separated/`) with the loops PLN's ear actually cut
(in `Samples/`). That pairing is the validation set — what did the human keep,
and can the finder surface it in its top candidates?

## What we already have (reuse, don't reinvent)

- **tide-table feature stack** — `sample_features.py`, `sample_classify.py`,
  `sample_semantics.py` (CLAP), `sample_ontology.py`, `audio_lens.py`. The loop
  ranker should sit ON this (`reference_audio_feature_stack`: L0 librosa → L1 MDA
  → L2 semantic), not grow a parallel feature path.
- **Foundry engine1** — stems already land at `<catch>/stems/{drums,bass,other,
  vocals}.wav` with a `Catch` model + Range-serving GUI to hang a waveform on.
- **Prior art notes** — `reference_drops_detection_prior_art` (Foote novelty on
  stem-activity SSM, kick-snap, **livecoded BPM-drift footguns**) is directly
  relevant: the same drift that breaks boundary detection breaks naive looping.

## Research questions (the three strands to fan out)

**A · Empirical — what is a "ParVagues loop"?**
EDA the **real custom sample root `~/Work/Sound/Samples/`** (135 kits, esp. the 7
recent + the demucs-derived ones: crimewave, diams_dj, humpty) — and the paired
source stems in `~/Downloads/separated/htdemucs/`. Characterize: per-kit wav
counts, the one-shot↔loop duration split, level, character, and *what the human
kept* vs the source stem. Cross-ref how the 702 `.tidal` tracks call these named
samples. Output: the target loop profile the finder should aim for, grounded in
what PLN actually cuts and plays.

**B · Theory — sampling & looping fundamentals.**
What makes a loop seamless and usable: zero-crossing & equal-power crossfade
seams, bar/phrase alignment, tempo-locking vs free, mono-sum for bass, sample-rate
/ bit-depth for Dirt, one-shot vs loop conventions. Output: the rulebook the
mechanical layer must honour.

**C · Prior art — loop-detection algorithms & libraries.**
Survey: librosa (beat_track, onset, tempo, plp), madmom (DBN downbeats), aubio,
self-similarity-matrix / Foote novelty for loop points, autocorrelation/beat-sync
for seamless cuts, existing "auto loop extractor" repos. Output: a shortlist of
techniques with accuracy/latency/dep trade-offs on **our livecoded, tempo-drifting
material**, plus the recommended stack for v1.

**D · Synthesis — the `engine/loops.py` design.**
Fold A+B+C into: the candidate-generation pipeline, the loopability score, the
`LoopCandidate` data model (start/end samples, bars, bpm, score, snap method), the
engine API, and the GUI panel (waveform + draggable regions + audition + "send →
`_samples/<pack>/` with a Tidal name"). Output: a design doc + a thin spike.

## Deliverables

1. `PHASE2_FINDINGS.md` — the synthesized research report (A+B+C), cited.
2. `engine/loops.py` design (API + `LoopCandidate` model) — in the report or a stub.
3. A **validation plan**: a few hand-picked ground-truth loops from real catches to
   test the finder against (build the katana before cutting — `feedback_build_katana_first`).

## Constraints & footguns

- **BPM drift is real** (livecoded, no click) — never assume a global constant
  tempo; detect per-region or track drift (`reference_drops_detection_prior_art`).
- **Validate by analysis, never by stem name** — `other.wav` is not "melody",
  `bass.wav` may carry a wobble not a sub (`feedback_mastering_eda`).
- Reuse the feature stack; keep deps to what the tide-table venv already has where
  possible (librosa present); flag any new heavy dep (madmom) explicitly.
- Output WAVs Dirt can eat (sample rate / mono-sum policy decided in strand B).
