Commit d11e6c9a by PLN (Algolia)

wip(cut-lens): gate on distributions, not on two smeared means — UNVALIDATED

v3 run 1 returned "no separating orbit" for six of fourteen boundaries,
including #13 and #15, the two the run existed to answer. Diagnosis was that
the gate compared ONE profile averaged over 40 s per side: a mean of band
percentages is a sound that never occurred, and 40 s of a livecoded track
averages toward the same grey for every orbit, so the orbits that actually
change look uninformative.

This replaces that with a distribution test:
  - reference windows are framed (3 s / 1.5 s hop) instead of averaged
  - each side is summarised by its MEDOID — an actual moment of the track —
    plus the median within-side distance, i.e. how varied that side is
  - an orbit votes only if it differs MORE across the switch than within
    either side (effect = sep - mean spread), not merely by a fixed distance
  - REF_MAX 40s -> 24s, so a reference stays one sound

NOT YET VALIDATED. Syntax-checked and smoke-run on boundary #11 only, which
still draws 3 voters and moves only its u0.15 estimate (3553.7 -> 3515.7);
whether #13/#15 recover needs the full 14-boundary run (~10 min) that has not
been done. Committed rather than parked so the reasoning is not lost — the
docstring's RESULT block still describes run 1 and must be rewritten with run
2's numbers before any boundary is taken from this.
parent dab8d38b
......@@ -99,9 +99,11 @@ from audio_lens import load_window, profile # noqa: E402
SEARCH = 45.0 # +/- seconds around the nominal cut
WIN = 3.0 # profiling window
STEP = 0.5 # slide
REF_MAX = 40.0 # cap on a reference window
REF_MAX = 24.0 # cap on a reference window (was 40 — too long to be one sound)
REF_STEP = 1.5 # frame hop inside a reference window
REF_CLEAR = 12.0 # keep references clear of the boundary being measured
MIN_SEP = 0.05 # an orbit must sound this different across the switch to vote
MIN_SEP = 0.03 # medoid-to-medoid distance floor
MIN_EFFECT = 0.01 # ...and it must beat the orbit's own within-side spread
SUSTAIN = 3.0 # seconds a level must hold to count as reached
SR = 44100
......@@ -147,13 +149,45 @@ def ref_window(seg_start, seg_end, boundary, side):
return (lo, hi) if hi - lo >= 6.0 else None
def _prof(path, m2s, t0, t1):
def ref_frames(path, m2s, t0, t1):
"""Frame-level profiles across a reference window.
v3's first run gated on ONE profile averaged over 40 s per side, and six of
fourteen boundaries came back "no separating orbit" — a 40 s average over a
livecoded track washes out precisely the orbits that change, so the two
means converge and the orbit looks uninformative when it is the informative
one. Framing keeps the distribution, which is what the gate below needs.
"""
try:
st = m2s(t0)
sig = load_window(path, st, st + min(t1 - t0, REF_MAX))
return profile(sig) if sig.size else None
except Exception:
return None
return []
n = int(WIN * SR)
hop = int(REF_STEP * SR)
out = []
for a in range(0, max(0, sig.size - n + 1), hop):
p = profile(sig[a:a + n])
if p is not None and p["rms_db"] >= -55:
out.append(p)
return out
def medoid(profs):
"""The most typical frame — robust where a mean spectrum is not.
A mean of band-percentages is a sound that never occurred; the medoid is an
actual moment of the track, so `dist(medoid_prev, medoid_next)` compares two
real timbres instead of two smeared ones.
"""
if not profs:
return None, None
if len(profs) == 1:
return profs[0], 0.0
D = np.array([[timbral(a, b) for b in profs] for a in profs], dtype=float)
i = int(np.argmin(D.sum(axis=1)))
spread = float(np.median(np.delete(D[i], i))) # how varied this side is
return profs[i], spread
def reaches(u, grid, q):
......@@ -176,11 +210,8 @@ def reaches(u, grid, q):
return None
def orbit_series(path, m2s, grid, rp, rn):
def orbit_series(path, m2s, grid, rp, rn, sep):
"""Normalised fraction-through-the-crossfade u(t) for one orbit, or None."""
sep = timbral(rp, rn)
if sep is None or sep < MIN_SEP:
return None, None # same sound both sides: no information
st0 = m2s(float(grid[0]))
try:
sig = load_window(path, st0, st0 + float(grid[-1] - grid[0]) + WIN)
......@@ -228,10 +259,19 @@ def estimate(stems, m2s, seg_prev, seg_next, verbose=False):
per_frac = {q: [] for q in FRACTIONS}
voters = 0
for path in stems:
rp, rn = _prof(path, m2s, *wp), _prof(path, m2s, *wn)
fp, fn = ref_frames(path, m2s, *wp), ref_frames(path, m2s, *wn)
rp, sp = medoid(fp)
rn, sn = medoid(fn)
if rp is None or rn is None:
continue
u, sep = orbit_series(path, m2s, grid, rp, rn)
sep = timbral(rp, rn)
# An orbit votes only if it sounds MORE different across the switch than
# it does within either side — a livecoded track varies constantly, so a
# bare distance floor admits noise and (v3 run 1) rejects real changes.
effect = sep - 0.5 * (sp + sn)
if sep < MIN_SEP or effect < MIN_EFFECT:
continue
u, sep = orbit_series(path, m2s, grid, rp, rn, sep)
if u is None:
continue
voters += 1
......
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