Commit d19c1220 by PLN (Algolia)

bleed v2: per-sound spectral novelty on shared orbits (#39)

v1 only saw a foreign ORBIT lighting up on the wrong side of a cut; it was blind
to a foreign SOUND on an orbit active on BOTH sides (e.g. breaks playing through
a transition). v2 (--spectral) closes that:
- per shared orbit, profile tail/head probe windows + each track's interior ref,
  composite timbral distance (band-shape cosine + centroid shift); flag when the
  tail sounds like the NEXT track (incoming) or the head like the PREVIOUS (outgoing).
- gated by MIN_REF_DIST (versions must differ) and SPEC_MIN_RMS=-50 (probe must
  have real sound — the silence gate caught 4 of my own false positives).
- independently flagged the WAP->'Plosive breaks bleed (orbit-08, Delta=0.475) that
  v1 missed = PLN's 'bordel jusqu'a 0342' zone; + Premier Septembre->Techno Orage.
- EDL now 9 ear + 7 orbit + 6 spectral derived rows; round-trips through MasterEDL.

Delta = severity rank; live crossfades blend by design, so PLN's ear gates feel.
parent 91a9ac89
......@@ -21,6 +21,9 @@ import argparse, json, re
from datetime import date
from pathlib import Path
import numpy as np
from audio_lens import load_window, orbit_files, profile
from models import EditKind, EditOp, MasterEdit, Provenance
HERE = Path(__file__).parent
......@@ -44,6 +47,17 @@ EDGE = 0.20 # trim this fraction off each segment edge for the "middle
FAMILY = {1:"kick",2:"snare",3:"hats",4:"bass",5:"bass/riff",6:"bass",7:"synth",
8:"breaks",9:"sub/voice",10:"lead",11:"atmos",12:"atmos"}
# ── v2 spectral (shared-orbit) params ─────────────────────────────────────────
TAKE = "Take89" # interchange stems behind this set (audio_lens)
SPEC_W = 6.0 # tail/head probe window (s)
REF_W = 12.0 # per-track interior reference window (s)
BANDS_ORDER = ["<80", "80-150", "150-500", "500-2k", "2k+"]
SPEC_MARGIN = 0.06 # min distance gap (toward J vs I) to call a spectral leak
MIN_REF_DIST = 0.05 # the two tracks' versions of the orbit must differ this much,
# else the orbit sounds the same both sides → nothing to detect
SPEC_MIN_RMS = -50.0 # a probe/reference window must actually have sound this loud,
# else its "timbre" is silence-noise → skip (no sound = no bleed)
def mmss(t): return f"{int(t)//60}:{int(t)%60:02d}"
......@@ -128,6 +142,93 @@ def detect():
return findings, segs, cores
# ── v2: per-sound spectral novelty on SHARED orbits ──────────────────────────
def _orbit_profile(o, t0, t1):
"""Spectral profile of orbit `o` in [t0,t1), but ONLY if it's actually
audible there (else the 'timbre' is silence-noise → return None, so we never
fabricate a leak from a window where nothing is playing)."""
L, _ = orbit_files(TAKE, o)
if L is None or t1 - t0 < 1.0:
return None
try:
p = profile(load_window(L, t0, t1))
except Exception:
return None
if not p or p["rms_db"] < SPEC_MIN_RMS:
return None
return p
def _timbral_dist(a, b):
"""Composite distance ∈ ~[0,1] between two profiles: band-shape cosine +
centroid shift. 0 = identical timbre, larger = more different sound."""
if not a or not b:
return None
va = np.array([a["bands"][k] for k in BANDS_ORDER], float)
vb = np.array([b["bands"][k] for k in BANDS_ORDER], float)
na, nb = np.linalg.norm(va), np.linalg.norm(vb)
if na == 0 or nb == 0:
return None
cos_d = 1.0 - float(np.dot(va / na, vb / nb))
ca, cb = a["centroid"], b["centroid"]
cen_d = abs(ca - cb) / (ca + cb + 1.0)
return 0.5 * cos_d + 0.5 * cen_d
def _win(s, e, frac, w):
"""A `w`-second window centered `frac` of the way into [s,e], clipped."""
c = s + (e - s) * frac
return max(s, c - w / 2), min(e, c - w / 2 + w)
def spectral_detect(segs, cores, cuts):
"""For each orbit active on BOTH sides of a cut, decide whether the tail
sounds like the NEXT track (incoming leak) or the head like the PREVIOUS
(outgoing ring) — invisible to v1's orbit-level gate. Pure timbre."""
findings = []
for k, c in enumerate(cuts):
b = float(c["t"])
i, j = k, k + 1
shared = sorted(cores[i] & cores[j])
if not shared:
continue
(_, si, ei), (_, sj, ej) = segs[i], segs[j]
name_i, name_j = segs[i][0], segs[j][0]
refI_w = _win(si, ei, 0.45, REF_W)
refJ_w = _win(sj, ej, 0.55, REF_W)
for o in shared:
pI = _orbit_profile(o, *refI_w)
pJ = _orbit_profile(o, *refJ_w)
ref_d = _timbral_dist(pI, pJ)
if ref_d is None or ref_d < MIN_REF_DIST:
continue # same sound both sides — undetectable
pTail = _orbit_profile(o, max(si, b - SPEC_W), b)
pHead = _orbit_profile(o, b, min(ej, b + SPEC_W))
# incoming: tail timbre closer to track J than to its own track I
dTI, dTJ = _timbral_dist(pTail, pI), _timbral_dist(pTail, pJ)
if dTI is not None and dTJ is not None and dTI - dTJ > SPEC_MARGIN:
findings.append({
"cut": k, "boundary_t": b, "mmss": mmss(b), "method": "spectral",
"from": name_i, "into": name_j, "side": "incoming",
"orbit": o, "family": FAMILY.get(o, "?"), "leak_s": SPEC_W,
"score": round(dTI - dTJ, 3), "ref_d": round(ref_d, 3),
"detail": f"orbit-{o:02d} ({FAMILY.get(o,'?')}) timbre in {name_i}'s "
f"tail matches {name_j} (Δ={round(dTI-dTJ,3)}) → {name_j}'s "
f"sound bled into the tail on a SHARED orbit"})
# outgoing: head timbre closer to track I than to its own track J
dHJ, dHI = _timbral_dist(pHead, pJ), _timbral_dist(pHead, pI)
if dHJ is not None and dHI is not None and dHJ - dHI > SPEC_MARGIN:
findings.append({
"cut": k, "boundary_t": b, "mmss": mmss(b), "method": "spectral",
"from": name_i, "into": name_j, "side": "outgoing",
"orbit": o, "family": FAMILY.get(o, "?"), "leak_s": SPEC_W,
"score": round(dHJ - dHI, 3), "ref_d": round(ref_d, 3),
"detail": f"orbit-{o:02d} ({FAMILY.get(o,'?')}) timbre in {name_j}'s "
f"head still matches {name_i} (Δ={round(dHJ-dHI,3)}) → "
f"{name_i}'s sound rings into the next track on a SHARED orbit"})
return findings
def to_edits(findings):
edits = []
for f in findings:
......@@ -137,8 +238,8 @@ def to_edits(findings):
op=op, at=f["mmss"], value=round(f["leak_s"], 1),
kind=EditKind.bad_cut, reason=f["detail"], device="",
provenance=Provenance(source="derived", as_of=date(2026, 6, 5),
locator=f"boundary_bleed cut{f['cut']} orbit-{f['orbit']:02d} "
f"{f['side']} {f['leak_s']}s")))
locator=f"boundary_bleed/{f.get('method','orbit')} cut{f['cut']} "
f"orbit-{f['orbit']:02d} {f['side']} {f['leak_s']}s")))
return edits
......@@ -146,30 +247,44 @@ def main():
ap = argparse.ArgumentParser()
ap.add_argument("--write-edl", action="store_true",
help="append detected bad_cut rows to master_edl_take89.json")
ap.add_argument("--spectral", action="store_true",
help="also run v2 per-sound spectral novelty on SHARED orbits")
a = ap.parse_args()
findings, segs, cores = detect()
print(f"\nboundary bleed scan — {len(segs)} tracks, {len(findings)} leaks\n")
spectral = spectral_detect(segs, cores, cuts=sorted(
json.loads(BOUNDS.read_text())["boundaries"], key=lambda x: x["t"])) \
if a.spectral else []
all_findings = findings + spectral
print(f"\nboundary bleed scan — {len(segs)} tracks, "
f"{len(findings)} v1 (orbit) + {len(spectral)} v2 (spectral) leaks\n")
print("core orbits per track:")
for (nm, s, e), c in zip(segs, cores):
print(f" {mmss(s):>6}-{mmss(e):<6} {nm:<26} {sorted(c)}")
print("\nLEAKS (objective — for PLN to convert to feel-judgements):")
if not findings:
if not all_findings:
print(" (none above threshold)")
for f in findings:
for f in sorted(all_findings, key=lambda x: (x["cut"], x.get("method", "orbit"))):
arrow = "◀" if f["side"] == "incoming" else "▶"
print(f" {f['mmss']:>6} cut{f['cut']:<2} {arrow} {f['detail']}")
tag = "spec" if f.get("method") == "spectral" else "orbt"
print(f" {f['mmss']:>6} cut{f['cut']:<2} [{tag}] {arrow} {f['detail']}")
cov = ("v1 = EXCLUSIVE-orbit ignition (foreign orbit lit on the wrong side of a cut, "
"to a -55dB floor).")
if a.spectral:
cov += (" v2 = per-sound SPECTRAL novelty on SHARED orbits: tail/head timbre "
"compared to each track's interior, so a foreign SOUND on an orbit active "
"both sides is caught (the v1 blind spot).")
else:
cov += (" LIMITATION: a foreign SOUND on a SHARED+active orbit is invisible at "
"orbit level → re-run with --spectral.")
OUT.write_text(json.dumps({"take": "Take89", "params":
{"active_db": ACTIVE_DB, "window_s": W, "core_frac": CORE_FRAC},
"coverage": "v1 = EXCLUSIVE-orbit ignition (foreign orbit lit on the wrong side "
"of a cut, down to a sensitive -55dB floor). LIMITATION: a foreign "
"SOUND on a genuinely SHARED+active orbit is invisible at orbit level "
"→ v2 = spectral/per-sound novelty at the boundary.",
"findings": findings}, ensure_ascii=False, indent=1))
print(f"\n✓ {OUT} ({len(findings)} findings)")
print("⚠ coverage: exclusive-orbit ignition only. A foreign SOUND sharing an active "
"orbit with the neighbour is invisible at orbit level → v2 = spectral novelty.")
{"active_db": ACTIVE_DB, "window_s": W, "core_frac": CORE_FRAC,
"spectral": a.spectral, "spec_margin": SPEC_MARGIN, "min_ref_dist": MIN_REF_DIST},
"coverage": cov, "findings": all_findings}, ensure_ascii=False, indent=1))
print(f"\n✓ {OUT} ({len(all_findings)} findings)")
if not a.spectral:
print("⚠ v1 only — shared-orbit sound-swaps invisible. Re-run with --spectral.")
if a.write_edl:
edits = to_edits(findings)
edits = to_edits(all_findings)
edl = json.loads(EDL.read_text())
# drop prior derived bleed rows, then append fresh
edl["edits"] = [e for e in edl["edits"]
......
......@@ -3,9 +3,12 @@
"params": {
"active_db": -38.0,
"window_s": 10.0,
"core_frac": 0.45
"core_frac": 0.45,
"spectral": true,
"spec_margin": 0.06,
"min_ref_dist": 0.05
},
"coverage": "v1 = EXCLUSIVE-orbit ignition (foreign orbit lit on the wrong side of a cut, down to a sensitive -55dB floor). LIMITATION: a foreign SOUND on a genuinely SHARED+active orbit is invisible at orbit level → v2 = spectral/per-sound novelty at the boundary.",
"coverage": "v1 = EXCLUSIVE-orbit ignition (foreign orbit lit on the wrong side of a cut, to a -55dB floor). v2 = per-sound SPECTRAL novelty on SHARED orbits: tail/head timbre compared to each track's interior, so a foreign SOUND on an orbit active both sides is caught (the v1 blind spot).",
"findings": [
{
"cut": 0,
......@@ -97,6 +100,96 @@
"leak_s": 9.0,
"from_t": "69:24",
"detail": "La Révolution Sera Samplée's orbit-03 (hats) lit 9.0s BEFORE the cut → next track leaked early / cut too late"
},
{
"cut": 4,
"boundary_t": 1631.0,
"mmss": "27:11",
"method": "spectral",
"from": "W.I.P. W.A.P.",
"into": "'Plosive",
"side": "incoming",
"orbit": 1,
"family": "kick",
"leak_s": 6.0,
"score": 0.098,
"ref_d": 0.098,
"detail": "orbit-01 (kick) timbre in W.I.P. W.A.P.'s tail matches 'Plosive (Δ=0.098) → 'Plosive's sound bled into the tail on a SHARED orbit"
},
{
"cut": 4,
"boundary_t": 1631.0,
"mmss": "27:11",
"method": "spectral",
"from": "W.I.P. W.A.P.",
"into": "'Plosive",
"side": "incoming",
"orbit": 8,
"family": "breaks",
"leak_s": 6.0,
"score": 0.475,
"ref_d": 0.744,
"detail": "orbit-08 (breaks) timbre in W.I.P. W.A.P.'s tail matches 'Plosive (Δ=0.475) → 'Plosive's sound bled into the tail on a SHARED orbit"
},
{
"cut": 7,
"boundary_t": 2309.0,
"mmss": "38:29",
"method": "spectral",
"from": "Aria Sans Serif",
"into": "PunkAChien",
"side": "outgoing",
"orbit": 8,
"family": "breaks",
"leak_s": 6.0,
"score": 0.234,
"ref_d": 0.2,
"detail": "orbit-08 (breaks) timbre in PunkAChien's head still matches Aria Sans Serif (Δ=0.234) → Aria Sans Serif's sound rings into the next track on a SHARED orbit"
},
{
"cut": 9,
"boundary_t": 2926.0,
"mmss": "48:46",
"method": "spectral",
"from": "You My Sunshine",
"into": "Desire",
"side": "incoming",
"orbit": 5,
"family": "bass/riff",
"leak_s": 6.0,
"score": 0.167,
"ref_d": 0.115,
"detail": "orbit-05 (bass/riff) timbre in You My Sunshine's tail matches Desire (Δ=0.167) → Desire's sound bled into the tail on a SHARED orbit"
},
{
"cut": 10,
"boundary_t": 3168.0,
"mmss": "52:48",
"method": "spectral",
"from": "Desire",
"into": "L'or Bleu",
"side": "incoming",
"orbit": 5,
"family": "bass/riff",
"leak_s": 6.0,
"score": 0.106,
"ref_d": 0.129,
"detail": "orbit-05 (bass/riff) timbre in Desire's tail matches L'or Bleu (Δ=0.106) → L'or Bleu's sound bled into the tail on a SHARED orbit"
},
{
"cut": 12,
"boundary_t": 3754.0,
"mmss": "62:34",
"method": "spectral",
"from": "Premier Septembre",
"into": "Techno Orage",
"side": "incoming",
"orbit": 5,
"family": "bass/riff",
"leak_s": 6.0,
"score": 0.475,
"ref_d": 0.536,
"detail": "orbit-05 (bass/riff) timbre in Premier Septembre's tail matches Techno Orage (Δ=0.475) → Techno Orage's sound bled into the tail on a SHARED orbit"
}
]
}
\ No newline at end of file
......@@ -197,7 +197,7 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut0 orbit-11 outgoing 5.0s",
"locator": "boundary_bleed/orbit cut0 orbit-11 outgoing 5.0s",
"as_of": "2026-06-05",
"note": ""
}
......@@ -217,7 +217,7 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut0 orbit-12 outgoing 5.0s",
"locator": "boundary_bleed/orbit cut0 orbit-12 outgoing 5.0s",
"as_of": "2026-06-05",
"note": ""
}
......@@ -237,7 +237,7 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut1 orbit-04 incoming 9.0s",
"locator": "boundary_bleed/orbit cut1 orbit-04 incoming 9.0s",
"as_of": "2026-06-05",
"note": ""
}
......@@ -257,7 +257,7 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut3 orbit-08 incoming 10.0s",
"locator": "boundary_bleed/orbit cut3 orbit-08 incoming 10.0s",
"as_of": "2026-06-05",
"note": ""
}
......@@ -277,7 +277,7 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut5 orbit-09 incoming 8.0s",
"locator": "boundary_bleed/orbit cut5 orbit-09 incoming 8.0s",
"as_of": "2026-06-05",
"note": ""
}
......@@ -297,7 +297,7 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut8 orbit-11 incoming 5.0s",
"locator": "boundary_bleed/orbit cut8 orbit-11 incoming 5.0s",
"as_of": "2026-06-05",
"note": ""
}
......@@ -317,7 +317,127 @@
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed cut13 orbit-03 incoming 9.0s",
"locator": "boundary_bleed/orbit cut13 orbit-03 incoming 9.0s",
"as_of": "2026-06-05",
"note": ""
}
},
{
"take": "Take89",
"track_no": null,
"name": "W.I.P. W.A.P. → 'Plosive",
"tidal": null,
"op": "trim_out",
"at": "27:11",
"to": null,
"value": 6.0,
"kind": "bad_cut",
"reason": "orbit-01 (kick) timbre in W.I.P. W.A.P.'s tail matches 'Plosive (Δ=0.098) → 'Plosive's sound bled into the tail on a SHARED orbit",
"device": "",
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed/spectral cut4 orbit-01 incoming 6.0s",
"as_of": "2026-06-05",
"note": ""
}
},
{
"take": "Take89",
"track_no": null,
"name": "W.I.P. W.A.P. → 'Plosive",
"tidal": null,
"op": "trim_out",
"at": "27:11",
"to": null,
"value": 6.0,
"kind": "bad_cut",
"reason": "orbit-08 (breaks) timbre in W.I.P. W.A.P.'s tail matches 'Plosive (Δ=0.475) → 'Plosive's sound bled into the tail on a SHARED orbit",
"device": "",
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed/spectral cut4 orbit-08 incoming 6.0s",
"as_of": "2026-06-05",
"note": ""
}
},
{
"take": "Take89",
"track_no": null,
"name": "Aria Sans Serif → PunkAChien",
"tidal": null,
"op": "trim_in",
"at": "38:29",
"to": null,
"value": 6.0,
"kind": "bad_cut",
"reason": "orbit-08 (breaks) timbre in PunkAChien's head still matches Aria Sans Serif (Δ=0.234) → Aria Sans Serif's sound rings into the next track on a SHARED orbit",
"device": "",
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed/spectral cut7 orbit-08 outgoing 6.0s",
"as_of": "2026-06-05",
"note": ""
}
},
{
"take": "Take89",
"track_no": null,
"name": "You My Sunshine → Desire",
"tidal": null,
"op": "trim_out",
"at": "48:46",
"to": null,
"value": 6.0,
"kind": "bad_cut",
"reason": "orbit-05 (bass/riff) timbre in You My Sunshine's tail matches Desire (Δ=0.167) → Desire's sound bled into the tail on a SHARED orbit",
"device": "",
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed/spectral cut9 orbit-05 incoming 6.0s",
"as_of": "2026-06-05",
"note": ""
}
},
{
"take": "Take89",
"track_no": null,
"name": "Desire → L'or Bleu",
"tidal": null,
"op": "trim_out",
"at": "52:48",
"to": null,
"value": 6.0,
"kind": "bad_cut",
"reason": "orbit-05 (bass/riff) timbre in Desire's tail matches L'or Bleu (Δ=0.106) → L'or Bleu's sound bled into the tail on a SHARED orbit",
"device": "",
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed/spectral cut10 orbit-05 incoming 6.0s",
"as_of": "2026-06-05",
"note": ""
}
},
{
"take": "Take89",
"track_no": null,
"name": "Premier Septembre → Techno Orage",
"tidal": null,
"op": "trim_out",
"at": "62:34",
"to": null,
"value": 6.0,
"kind": "bad_cut",
"reason": "orbit-05 (bass/riff) timbre in Premier Septembre's tail matches Techno Orage (Δ=0.475) → Techno Orage's sound bled into the tail on a SHARED orbit",
"device": "",
"status": "open",
"provenance": {
"source": "derived",
"locator": "boundary_bleed/spectral cut12 orbit-05 incoming 6.0s",
"as_of": "2026-06-05",
"note": ""
}
......
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