Commit b01e9c41 by PLN (Algolia)

feat(foundry): batch producer stem packs — and 96 kHz was breaking the finder

The Foundry could make stems (yt-dlp → demucs) but not eat stems somebody else
already made. Fred again's dropbox is 9 tracks × 6-13 producer-labelled stems at
96 kHz/24-bit — better source material than demucs can produce, and completely
outside what find_takes accepts: it keys stems by drums/bass/other/vocals, a dict
that collides on KEYS1+KEYS2 and an export keep= filter that would have silently
dropped every stem in the pack.

engine/stempack.py is the missing batch driver (TODO #20), and building it turned
up a real bug. MAREA ships its tempo in the filename (…123BPM…), so it is free
ground truth. At native 96 kHz the finder returned 123/123/124/119/128.1/128.5 bpm
and a 0.661 top score; resampled to 44.1 kHz first it returned 123.0-123.1 on every
candidate and 0.859, in a third of the time. librosa's hop/window defaults are
sample-rate-relative, so at 96 k every analysis frame spans half the musical time
and beat tracking wanders. Rate is a correctness input, not a performance knob.

engine/roles.py maps the pack's ~40 role tokens (39/40 hit directly) to a family,
then verifies the label by measurement before anything trusts it — the house rule
that a sound's role is never read off its name. The `hits` family
(KICK, SNARE alone) is the one place the label carries information the cheap features cannot: an isolated kick
and a full groove measure identically.

engine/kitcheck.py audits what grade.py cannot see by construction. A four-bar loop
whose last two bars are a fade grades S — silent-to-silent is a perfect seam and the
length is still exact. So: per-bar RMS for dead bars, onset-envelope autocorrelation
for whether a bar-aligned window is actually a repeating unit, and mel-fingerprint
cross-correlation for near-duplicates, because a pack shipping ALL DRUMS + KIT +
MAIN DRUM LOOP + DRUM BREAKS will otherwise ship one groove four times.

--jobs fans out as subprocesses, not a ProcessPoolExecutor: the pool's forkserver is
broken under this Python (BrokenProcessPool on a trivial task), and one process per
track also means a track that blows up loses only itself.

15 new tests.
parent 2ef09af6
"""kitcheck — the post-export audit: what `grade` cannot see.
`grade.py` answers *is this file mechanically a clean loop* — seam, zero crossings,
DC, level, bar self-consistency. Every one of those can be perfect on a sample that
is musically useless, and this module exists for exactly the failures that survive a
clean grade. It is the substitute for an ear when nobody is listening yet.
Three checks, each closing a hole the rubric has by construction:
**1 · dead bars.** A four-bar loop whose last two bars are the tail of a fade grades
S: the seam is silent-to-silent, the length is exact. It is still a two-bar loop
with two bars of nothing after it. Per-bar RMS, flagged when any bar sits far
below the loudest.
**2 · bar periodicity.** The finder cuts on a beat grid, so its windows are always
*bar-aligned*; nothing checks they are a *bar-length musical unit*. Onset-envelope
autocorrelation at the half/whole-bar lag says whether the material actually
repeats at the claimed rate, or whether four bars of through-composed movement got
labelled a loop.
**3 · near-duplicates.** A producer pack ships overlapping stems on purpose — ALL
DRUMS, KIT, MAIN DRUM LOOP and DRUM BREAKS are four views of one groove. Cut the
best window from each and you ship the same bar four times under four names. A kit
whose 8 slots hold 3 distinct sounds is worse than a kit of 3.
Plus the two cheap invariants worth asserting rather than assuming: every bar-loop's
duration is an exact bar multiple at its own BPM, and every file is 44.1 kHz.
python3 -m engine.kitcheck fred_kits.json
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
import numpy as np
import soundfile as sf
BAR_TOL_MS = 1.0 # a bar multiple is exact or it is not
DEAD_BAR_DB = 18.0 # a bar this far under the loudest is not carrying material
DUPE_CORR = 0.90 # normalised cross-correlation above this ⇒ same sound
@dataclass
class KitCheck:
path: str
name: str
kit: str
ok: bool = True
problems: list = field(default_factory=list)
bar_dev_ms: Optional[float] = None
bar_rms_db: list = field(default_factory=list)
periodicity: Optional[float] = None
dupe_of: Optional[str] = None
def _mono(y):
return y.mean(axis=1) if y.ndim > 1 else y
def bar_levels(y: np.ndarray, sr: int, bars: int) -> list[float]:
"""RMS in dBFS of each bar of the loop."""
if bars <= 0:
return []
n = len(y) // bars
return [round(float(20 * np.log10(np.sqrt(np.mean(y[i * n:(i + 1) * n] ** 2)) + 1e-12)), 1)
for i in range(bars)]
def periodicity(y: np.ndarray, sr: int, bars: int) -> float:
"""How strongly the onset envelope repeats at the loop's own bar period (0..1).
Autocorrelation of the onset strength envelope, read at the lag of one bar (and
of half the loop, whichever the loop is long enough to have). A rhythmic loop
peaks there; a through-composed window does not.
"""
import librosa
if bars < 2:
return 1.0 # a 1-bar loop has no internal period to check
oenv = librosa.onset.onset_strength(y=y, sr=sr, hop_length=512)
oenv = oenv - oenv.mean()
if not np.any(oenv) or len(oenv) < 8:
return 0.0
ac = np.correlate(oenv, oenv, mode="full")[len(oenv) - 1:]
ac = ac / (ac[0] + 1e-12)
per_bar = len(oenv) / bars
best = 0.0
for k in (1, 2): # one bar, and two bars
lag = int(round(per_bar * k))
if 0 < lag < len(ac):
best = max(best, float(ac[max(0, lag - 2):lag + 3].max()))
return round(max(0.0, min(1.0, best)), 3)
def _fingerprint(y: np.ndarray, sr: int, n: int = 256) -> np.ndarray:
"""A short, tempo-agnostic signature: log-mel energy resampled to n columns.
Two cuts of the same groove at different lengths must still compare equal, so the
envelope is normalised in time (resampled to a fixed width) before comparison —
a raw waveform correlation would call a 2-bar and a 4-bar cut of one break
unrelated.
"""
import librosa
m = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=24, hop_length=512)
m = librosa.power_to_db(m + 1e-12)
if m.shape[1] < 2:
return np.zeros(24 * n)
idx = np.linspace(0, m.shape[1] - 1, n)
m = np.vstack([np.interp(idx, np.arange(m.shape[1]), row) for row in m])
m = m - m.mean()
return (m / (np.linalg.norm(m) + 1e-12)).ravel()
def check_cuts(cuts: list[dict]) -> list[KitCheck]:
"""Audit every exported sample, then cross-check for duplicates within each kit."""
out: list[KitCheck] = []
prints: dict[str, list[tuple[str, np.ndarray]]] = {}
for c in cuts:
r = KitCheck(path=c["path"], name=c["name"], kit=c["kit"])
p = Path(c["path"])
if not p.exists():
r.ok = False; r.problems.append("missing file"); out.append(r); continue
y, sr = sf.read(str(p), always_2d=True, dtype="float32")
m = _mono(y)
if sr != 44100:
r.ok = False; r.problems.append(f"sample rate {sr}, expected 44100")
bars, bpm = c["bars"], c["bpm"]
if bars > 0 and bpm > 0:
want = bars * 4 * 60.0 / bpm
dev = (len(m) / sr - want) * 1000
r.bar_dev_ms = round(dev, 3)
if abs(dev) > BAR_TOL_MS:
r.ok = False
r.problems.append(f"not a bar multiple: {dev:+.1f} ms off {bars} bars @ {bpm:.1f}")
r.bar_rms_db = bar_levels(m, sr, bars)
if r.bar_rms_db:
lo, hi = min(r.bar_rms_db), max(r.bar_rms_db)
if hi - lo > DEAD_BAR_DB:
r.ok = False
dead = [i + 1 for i, v in enumerate(r.bar_rms_db) if hi - v > DEAD_BAR_DB]
r.problems.append(f"dead bar(s) {dead}: {hi - lo:.0f} dB spread across bars")
r.periodicity = periodicity(m, sr, bars)
if r.periodicity < 0.15:
r.problems.append(f"weak bar periodicity ({r.periodicity:.2f}) — "
"bar-aligned but maybe not a repeating unit")
prints.setdefault(c["kit"], []).append((c["name"], _fingerprint(m, sr)))
out.append(r)
by_name = {r.name: r for r in out}
for kit, items in prints.items():
for i in range(len(items)):
for j in range(i + 1, len(items)):
corr = float(items[i][1] @ items[j][1])
if corr > DUPE_CORR:
later = by_name[items[j][0]]
if later.dupe_of is None:
later.dupe_of = items[i][0]
later.problems.append(
f"near-duplicate of {items[i][0]} in the same kit (r={corr:.2f})")
return out
def report(checks: list[KitCheck]) -> str:
bad = [c for c in checks if c.problems]
lines = [f"# kit check — {len(checks)} samples, "
f"{len(checks) - len(bad)} clean, {len(bad)} flagged", ""]
if not bad:
lines.append("No problems found.")
for kit in sorted({c.kit for c in bad}):
lines.append(f"## `{kit}`")
for c in [x for x in bad if x.kit == kit]:
mark = "✗" if not c.ok else "⚠"
lines.append(f"- {mark} `{c.name}` — " + "; ".join(c.problems))
lines.append("")
dupes = [c for c in checks if c.dupe_of]
lines += ["", f"**Distinct sounds:** {len(checks) - len(dupes)} of {len(checks)} "
f"({len(dupes)} near-duplicates)"]
return "\n".join(lines)
def main(argv=None) -> int:
import argparse
ap = argparse.ArgumentParser(prog="kitcheck", description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("cuts_json", type=Path, help="fred_kits.json from engine.stempack")
ap.add_argument("--out", type=Path, default=None)
a = ap.parse_args(argv)
cuts = json.loads(a.cuts_json.read_text())
checks = check_cuts(cuts)
txt = report(checks)
print(txt)
if a.out:
a.out.write_text(txt)
(a.out.parent / (a.out.stem + ".json")).write_text(
json.dumps([c.__dict__ for c in checks], indent=2))
return 0 if all(c.ok for c in checks) else 1
if __name__ == "__main__":
raise SystemExit(main())
"""Producer stem-pack batching: name parsing, label verification, and the kit audit.
The DSP-heavy paths are exercised end-to-end on real stems; what is locked here is the
logic that decides *what a stem is* and *whether a shipped loop is defensible* — the two
places a silent wrong answer would propagate into a kit nobody notices is bad.
cd tools/foundry && python3 -m pytest tests/test_stempack.py -q
"""
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from engine import kitcheck as K # noqa: E402
from engine import roles as R # noqa: E402
SR = 44100
# ── name parsing ─────────────────────────────────────────────────────────────
def test_parses_the_pack_scaffolding():
n = R.parse_stem_name("MAREA MIX10 123BPM KIT STEM.wav")
assert n.track == "MAREA"
assert n.role_token == "kit"
assert n.claimed_family == "drums"
assert n.bpm_hint == 123.0
def test_multiword_roles_and_the_NEW_variant():
assert R.parse_stem_name("ANGIE MIX18 PAD STUFF STEM.wav").role_token == "pad stuff"
assert R.parse_stem_name("ANGIE NEW F VOX STEM.wav").claimed_family == "vox"
assert R.parse_stem_name("BIG HEN MIX1 ALL DRUMS STEM.wav").claimed_family == "drums"
def test_isolated_hits_are_not_the_same_family_as_a_groove():
"""A KICK stem and an ALL DRUMS stem both measure percussive; only the name
separates a one-shot source from loop material, so the map must keep them apart."""
assert R.parse_stem_name("IAMAPARTY MIX1 KICK STEM.wav").claimed_family == "hits"
assert R.parse_stem_name("IAMAPARTY MIX1 ALL DRUMS STEM.wav").claimed_family == "drums"
def test_unknown_token_falls_back_on_a_contained_word():
"""`MAKE IT THRU VOX` is track-specific, but it still says VOX."""
n = R.parse_stem_name("MAREA MIX10 123BPM MAKE IT THRU VOX STEM.wav")
assert n.claimed_family == "vox"
def test_a_wholly_unknown_token_is_soft_not_fatal():
n = R.parse_stem_name("MAREA MIX10 123BPM MAREA STEM.wav")
assert n.known is False
assert n.claimed_family == "tonal" # a default the probe is free to override
# ── the probe overrides the label ────────────────────────────────────────────
def _sine(f, secs=6.0, amp=0.3):
t = np.arange(int(SR * secs)) / SR
return (amp * np.sin(2 * np.pi * f * t)).astype(np.float32)
def test_a_sub_sine_measures_as_bass_whatever_the_name_says():
p = R.probe(_sine(50), SR, claimed="tonal")
assert p.measured_family == "bass"
assert not p.agrees
assert "measurement says bass" in p.note
def test_near_silence_is_not_usable():
p = R.probe(np.zeros(SR * 5, dtype=np.float32), SR, claimed="drums")
assert not p.usable
def test_vox_and_tonal_do_not_contradict_each_other():
"""The cheap features cannot separate a sung line from a rhodes chord, so a vox
claim must not be overturned by a `tonal` measurement — that is CLAP's job."""
p = R.probe(_sine(440), SR, claimed="vox")
p.measured_family = "tonal"
p.agrees = True
assert R.effective_family(R.parse_stem_name("X MIX1 LV STEM.wav"), p) == "vox"
def test_a_hits_claim_survives_a_drums_measurement():
p = R.probe(_sine(60), SR, claimed="hits")
p.measured_family, p.agrees = "drums", False
name = R.parse_stem_name("X MIX1 KICK STEM.wav")
assert R.effective_family(name, p) == "hits"
# ── the kit audit ────────────────────────────────────────────────────────────
def _write(tmp, name, y, sr=SR):
import soundfile as sf
p = Path(tmp) / name
sf.write(str(p), np.stack([y, y], axis=1), sr, subtype="PCM_24")
return str(p)
def _cut(path, **kw):
d = dict(kit="k", name=Path(path).stem, path=path, track="T", stem_role="kit",
family="drums", mode="loop", bars=4, bpm=120.0, dur_s=0.0, start_s=0.0,
finder_score=1.0, tier="A", grade=0.85, flags=[], tags={})
d.update(kw)
return d
def _click_track(bars=4, bpm=120.0, dead_after=None):
"""A bar-exact percussive loop; `dead_after` silences every bar from that index."""
n = int(round(bars * 4 * 60.0 / bpm * SR))
y = np.zeros(n, dtype=np.float32)
per_beat = n // (bars * 4)
for b in range(bars * 4):
if dead_after is not None and b // 4 >= dead_after:
continue
i = b * per_beat
env = np.exp(-np.arange(per_beat) / (SR * 0.02))
y[i:i + per_beat] += (np.random.RandomState(b).randn(per_beat) * env * 0.3
).astype(np.float32)
return y
def test_a_bar_exact_loop_passes(tmp_path):
p = _write(tmp_path, "00_kit_4b.wav", _click_track())
(c,) = K.check_cuts([_cut(p)])
assert c.ok, c.problems
assert abs(c.bar_dev_ms) < K.BAR_TOL_MS
def test_a_loop_that_is_not_a_bar_multiple_is_caught(tmp_path):
y = _click_track()[: -SR // 10] # 100 ms short of 4 bars
p = _write(tmp_path, "01_kit_4b.wav", y)
(c,) = K.check_cuts([_cut(p)])
assert not c.ok
assert any("not a bar multiple" in x for x in c.problems)
def test_dead_bars_are_caught_even_though_the_loop_is_mechanically_perfect(tmp_path):
"""The whole reason this module exists: silence is a clean seam and an exact
length, so `grade` has no way to object to a loop that stops halfway."""
p = _write(tmp_path, "02_kit_4b.wav", _click_track(dead_after=2))
(c,) = K.check_cuts([_cut(p)])
assert not c.ok
assert any("dead bar" in x for x in c.problems)
def test_near_duplicates_within_a_kit_are_flagged(tmp_path):
y = _click_track()
a = _write(tmp_path, "00_alldrums_4b.wav", y)
b = _write(tmp_path, "01_kit_4b.wav", y * 0.9) # the same groove, another stem
checks = K.check_cuts([_cut(a), _cut(b)])
assert checks[1].dupe_of == checks[0].name
def test_distinct_material_is_not_flagged_as_duplicate(tmp_path):
rs = np.random.RandomState(0)
a = _write(tmp_path, "00_a_4b.wav", _click_track())
b = _write(tmp_path, "01_b_4b.wav", (rs.randn(int(4 * 4 * 0.5 * SR)) * 0.1).astype("float32"))
checks = K.check_cuts([_cut(a), _cut(b, bars=4, bpm=120.0)])
assert checks[1].dupe_of is None
def test_a_wrong_sample_rate_is_caught(tmp_path):
p = _write(tmp_path, "03_kit_4b.wav", _click_track(), sr=48000)
(c,) = K.check_cuts([_cut(p, bpm=130.909)])
assert not c.ok
assert any("sample rate" in x for x in c.problems)
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