Commit ecfa108c by PLN (Algolia)

docs(foundry): API integration guide + phase-2 synthesis & research

- README: 'Use as an API' — Python import / CLI subprocess / HTTP API, the
  path+catch.json contract, env overrides (for sibling tools e.g. the #89
  emotional-arc tool that consumes the demucs engine for stems)
- README: add 'link' to CLI reference
- research/{A,B,C}_*.md: the three strand reports (empirical profile,
  fundamentals rulebook, prior-art + recommended zero-dep stack)
- research/PHASE2_FINDINGS.md: strand-D synthesis — corrected per-stem model
  (loops cut per stem, often-but-not-always time-aligned), loopability rubric,
  Take/StemSlice model, correlation engine (§5·0 GT-at-scale), Hall of Fame
  tierlist, section-label grounding ( #89), 80/20 build order
- PHASE2_LOOPFINDER.md charter: grounded in the real flow + ground-truth pairs
parent 20bc875e
...@@ -36,6 +36,7 @@ foundry.py sep <slug|url> # separate an existing catch (or fetch+sep a url) ...@@ -36,6 +36,7 @@ foundry.py sep <slug|url> # separate an existing catch (or fetch+sep a url)
foundry.py catch <url> # fetch + sep (the whole pipeline) foundry.py catch <url> # fetch + sep (the whole pipeline)
foundry.py list # ▣ separated / ▢ raw, per catch foundry.py list # ▣ separated / ▢ raw, per catch
foundry.py backends # ✓/✗ + hint per backend foundry.py backends # ✓/✗ + hint per backend
foundry.py link <kit> # symlink Samples/<kit> → Dirt-Samples (s "<kit>")
foundry.py serve [--port] # launch the GUI foundry.py serve [--port] # launch the GUI
# separation knobs (sep / catch): # separation knobs (sep / catch):
...@@ -56,6 +57,86 @@ cd tools/foundry && python3 -m pytest tests/ -q # pure engine: slug, parse, ar ...@@ -56,6 +57,86 @@ cd tools/foundry && python3 -m pytest tests/ -q # pure engine: slug, parse, ar
--- ---
## Use as an API (from another project)
The Foundry is the shared **demucs engine** for the wider toolbox — e.g. the
emotional-arc / section-analysis tool consumes it to get stems. Three ways to
integrate, cheapest first:
### 1. Python import (in-process — best for a sibling Python tool)
Add the foundry dir to `sys.path`, then call the same steps the CLI does. Runs
under system python3 (needs `pydantic`); the demucs subprocess uses its own venv.
```python
import sys; sys.path.insert(0, "/home/pln/Work/Sound/Tidal/tools/foundry")
from pathlib import Path
from engine import fetch, separate
from engine.model import Catch
ws = Path("~/Downloads/foundry").expanduser() # or set $FOUNDRY_HOME
catch = fetch.fetch("https://youtu.be/…", ws) # download → Catch
catch = separate.separate(catch, ws, backend="demucs", shifts=4,
progress=lambda p: print(f"{p}%"))
# stems are now on disk; resolve absolute paths from the Catch:
stems = {s.name: catch.dir(ws) / s.path for s in catch.stems}
bass_wav = stems["bass"] # → feed your analysis
# already have a catch? skip fetching:
catch = Catch.load(ws / "some_slug") # reads catch.json
```
Idempotency: `fetch` re-probes but re-downloads only if missing; check
`catch.separated` before re-separating. `separate.REGISTRY` lists backends;
`separate.get_backend("demucs").available()``(bool, hint)`.
### 2. CLI subprocess (language-agnostic)
```python
import subprocess, json
from pathlib import Path
F = "/home/pln/Work/Sound/Tidal/tools/foundry/foundry.py"
subprocess.run(["python3", F, "catch", url], check=True) # fetch + separate
# then read the catch.json the run wrote:
ws = Path("~/Downloads/foundry").expanduser()
slug = sorted(ws.iterdir())[-1].name # or derive from title
catch = json.loads((ws / slug / "catch.json").read_text())
```
### 3. HTTP API (for a running server / remote / a non-Python caller)
`python3 foundry.py serve` then poll jobs:
```
POST /api/fetch {url} → {id} # start download job
POST /api/separate {slug,backend,shifts} → {id} # start separation job
GET /api/job/<id> → {state,progress,msg,slug}
GET /api/catches → [Catch, …]
GET /api/backends → [{name,available,hint}]
GET /media/<slug>/<path> → audio (HTTP Range / 206 — seekable)
```
`state` goes `running → done|error`; on `done`, read `/api/catches` or
`/media/<slug>/stems/bass.wav`.
### The contract you can rely on
- Stems always land at `<workspace>/<slug>/stems/{drums,bass,other,vocals}.wav`
(normalized regardless of backend), 44.1 kHz WAV.
- `catch.json` is the source of truth (title, `source_url`, `backend`, `model`,
`stems[]`, `duration`) — parse it, don't scrape filenames.
- Override paths via env: `$FOUNDRY_HOME` (workspace), `$FOUNDRY_DEMUCS` (binary),
`$FOUNDRY_SAMPLES` / `$FOUNDRY_DIRT_SAMPLES` (publish targets).
- **Don't infer a stem's musical role from its name** (`other`≠melody,
`bass`≠sub) — analyze it. (Project rule; see CLAUDE.md.)
> Beta-testers: file what's awkward. The Python import path is the one to harden
> first — tell me if you'd rather have a `pip install -e`-able package.
---
## Engine roadmap — engine1 → engine2 ## Engine roadmap — engine1 → engine2
The separation backend is a **registry** (`engine/separate.py`); the CLI/GUI ask The separation backend is a **registry** (`engine/separate.py`); the CLI/GUI ask
......
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