blackboxanalytics commited on
Commit
1cca94c
·
1 Parent(s): 21ec938

Harden best-of-N selection: reject dropouts and squashed takes

Browse files

The selection metric only scored loud-bursts and whole-tail silence, so two
failure modes that sound bad to a listener slipped through as clean:
- a brief mid-tail near-silent dropout (overall RMS stays high and max/median
stays low, so neither existing term fires) - an audible cut-out;
- dynamics/transient collapse - a perfectly tonal but squashed, attack-less
wash that low-flatness and loudness checks all green-light.
Add a sustained-dropout term and a crest-factor (peak/RMS) floor to the score,
raise the candidate pool to 5 and tighten early-accept to 3.5 so best-of-N keeps
searching past a merely-okay draw for a genuinely good one. Generation is
unchanged; only which draw is selected. Adds unit tests for all four failure
modes plus the natural-ending-taper false-positive guard.

Files changed (2) hide show
  1. engine.py +57 -10
  2. test_engine_score.py +102 -0
engine.py CHANGED
@@ -88,11 +88,21 @@ DEFAULT_CFG = 1.0 # distilled-model guidance; the prompt still conditions
88
 
89
  # Best-of-N: with a random seed each draw differs, so we generate a few and keep
90
  # the cleanest. Bounded so it never blows the ZeroGPU window.
91
- DEFAULT_CANDIDATES = 3 # how many draws to consider when no seed is pinned
 
 
 
 
92
  GPU_BUDGET_SECONDS = 85.0 # stop drawing once this much wall-clock is spent
93
  # (the @spaces.GPU window is 120s; leave slack)
94
- EARLY_ACCEPT_SCORE = 4.0 # a draw this clean is taken immediately, no re-draw
95
- # (heuristic see _tail_artifact_score; tune live)
 
 
 
 
 
 
96
  MAX_TOTAL_SECONDS = 120 # SA3 Small duration cap (sample_size / sample_rate)
97
  MIN_NEW_SECONDS = 5 # below this a "continuation" isn't worth a GPU call
98
  MAX_LEAD_SECONDS = 30 # how much of the clip's TAIL to feed SA3 as run-up.
@@ -215,18 +225,31 @@ def _tail_artifact_score(tail, sr=SR):
215
  """Lower is better. A blind, ear-free quality score for a generated tail,
216
  used to pick the cleanest of several candidate draws.
217
 
218
- It targets the two ways an SA3 draw goes bad:
219
  * "sporadic loud random synth noises" — even a FEW short windows far louder
220
  than the body push the loudest window way above the median. (After the
221
  whole-buffer peak-normalize, a burst that set the peak crushes the body,
222
  making the gap larger still.) Sustained dynamics rarely make any single
223
  50 ms window many times the median, so musical loudness doesn't trip it.
224
- * silence collapse — a near-silent tail (the other known failure) is caught
225
- by the explicit loudness floor below.
226
-
227
- Score = max(window RMS) / median(window RMS) + silence penalty.
 
 
 
 
 
 
 
 
 
 
 
 
228
  Computed on a mono mix over short (~50 ms) windows. A flat, steady signal
229
- scores ~1; isolated loud bursts or a crushed body score high.
 
230
  """
231
  mono = tail.mean(axis=0) if tail.ndim == 2 else np.asarray(tail)
232
  mono = np.asarray(mono, dtype=np.float64)
@@ -241,7 +264,31 @@ def _tail_artifact_score(tail, sr=SR):
241
  spikiness = loudest / median
242
  overall = float(np.sqrt(np.mean(mono ** 2)) + 1e-12)
243
  silence_penalty = 0.0 if overall > 0.02 else (0.02 - overall) * 200.0
244
- return spikiness + silence_penalty
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
 
247
  def continue_audio(clip_path, total_seconds, prompt="", cfg_scale=DEFAULT_CFG,
 
88
 
89
  # Best-of-N: with a random seed each draw differs, so we generate a few and keep
90
  # the cleanest. Bounded so it never blows the ZeroGPU window.
91
+ DEFAULT_CANDIDATES = 5 # how many draws to consider when no seed is pinned.
92
+ # Raised from 3: the quality bar is the BEST draw, not
93
+ # the first clean-ish one, and a fast H200 draw is
94
+ # cheap. Early-accept + the GPU budget still short-
95
+ # circuit when an early draw is already great.
96
  GPU_BUDGET_SECONDS = 85.0 # stop drawing once this much wall-clock is spent
97
  # (the @spaces.GPU window is 120s; leave slack)
98
+ EARLY_ACCEPT_SCORE = 3.5 # a draw this clean is taken immediately, no re-draw.
99
+ # Tightened from 4.0 so a merely-okay draw doesn't
100
+ # short-circuit the search for a genuinely good one.
101
+ DROPOUT_FLOOR = 0.12 # quietest sustained 0.2 s below this fraction of the
102
+ # tail median counts as a mid-tail hole (re-draw it)
103
+ CREST_FLOOR = 4.0 # peak/RMS below this is a squashed, transient-less
104
+ # wash (real music here is ~6-8); penalize it
105
+ CREST_SCALE = 1.5 # how hard a collapsed crest is penalized
106
  MAX_TOTAL_SECONDS = 120 # SA3 Small duration cap (sample_size / sample_rate)
107
  MIN_NEW_SECONDS = 5 # below this a "continuation" isn't worth a GPU call
108
  MAX_LEAD_SECONDS = 30 # how much of the clip's TAIL to feed SA3 as run-up.
 
225
  """Lower is better. A blind, ear-free quality score for a generated tail,
226
  used to pick the cleanest of several candidate draws.
227
 
228
+ It targets the four ways an SA3 draw goes bad:
229
  * "sporadic loud random synth noises" — even a FEW short windows far louder
230
  than the body push the loudest window way above the median. (After the
231
  whole-buffer peak-normalize, a burst that set the peak crushes the body,
232
  making the gap larger still.) Sustained dynamics rarely make any single
233
  50 ms window many times the median, so musical loudness doesn't trip it.
234
+ * silence collapse — a near-silent WHOLE tail is caught by the loudness
235
+ floor below (it keys off the tail's overall RMS).
236
+ * mid-tail dropout — a brief near-silent HOLE inside an otherwise healthy
237
+ tail. This is the gap the first terms miss: overall RMS stays high (so the
238
+ silence floor never fires) and max/median stays low (so spikiness never
239
+ fires), yet a listener plainly hears the music cut out for a beat. We
240
+ detect it as a SUSTAINED quiet stretch — the quietest ~0.2 s envelope
241
+ falling well below the median.
242
+ * dynamics/transient collapse — a draw can be perfectly tonal and steady yet
243
+ sound DULL and lifeless: its transients are smeared, so there's no attack,
244
+ just a wall of mush. Flatness and loudness checks all read "clean". It
245
+ shows up as a collapsed crest factor (peak/RMS): real music here sits at
246
+ crest ~6-8, a squashed draw falls to ~2-3. We penalize a low crest so
247
+ best-of-N prefers the punchy draw over the mushy one.
248
+
249
+ Score = max/median + silence + dropout + dynamics penalties.
250
  Computed on a mono mix over short (~50 ms) windows. A flat, steady signal
251
+ scores ~1; loud bursts, a crushed body, a mid-tail hole, or a smeared,
252
+ transient-less wash all score high.
253
  """
254
  mono = tail.mean(axis=0) if tail.ndim == 2 else np.asarray(tail)
255
  mono = np.asarray(mono, dtype=np.float64)
 
264
  spikiness = loudest / median
265
  overall = float(np.sqrt(np.mean(mono ** 2)) + 1e-12)
266
  silence_penalty = 0.0 if overall > 0.02 else (0.02 - overall) * 200.0
267
+
268
+ # mid-tail dropout: smooth the window energies over ~0.2 s and find how far
269
+ # the quietest SUSTAINED stretch falls below the median. Exclude the final
270
+ # 0.5 s so a natural ending taper (which stitch fades anyway) isn't punished.
271
+ # A clean tail's quietest 0.2 s sits ~0.15-0.4x the median -> no penalty; a
272
+ # real hole drops to <0.1x -> a penalty large enough to lose the early-accept
273
+ # and force another draw, so best-of-N rolls past the glitch.
274
+ dropout_penalty = 0.0
275
+ smooth = np.convolve(energies, np.ones(4) / 4.0, mode="valid")
276
+ guard = int(0.5 / 0.05) # last 0.5 s of windows
277
+ body = smooth[:-guard] if smooth.size > guard + 4 else smooth
278
+ if body.size:
279
+ dropout = float(np.min(body)) / median
280
+ dropout_penalty = min(8.0, max(0.0, DROPOUT_FLOOR / max(dropout, 1e-3)
281
+ - 1.0) * 2.0)
282
+
283
+ # dynamics/transient collapse: crest = peak / RMS. The tail is peak-normalized
284
+ # to ~1.0, so this is essentially 1/RMS — a squashed, attack-less wash reads
285
+ # high RMS (low crest); a punchy, dynamic take reads low RMS (high crest).
286
+ # Penalize only a clearly collapsed crest, so we never punish a naturally
287
+ # dynamic draw.
288
+ peak = float(np.abs(mono).max())
289
+ crest = peak / overall
290
+ crest_penalty = max(0.0, CREST_FLOOR - crest) * CREST_SCALE
291
+ return spikiness + silence_penalty + dropout_penalty + crest_penalty
292
 
293
 
294
  def continue_audio(clip_path, total_seconds, prompt="", cfg_scale=DEFAULT_CFG,
test_engine_score.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for engine._tail_artifact_score — the best-of-N selection metric.
2
+
3
+ The score is "lower is better"; a draw at or below EARLY_ACCEPT_SCORE is taken
4
+ immediately, anything above forces another draw. These tests pin the four
5
+ failure modes the metric must catch (loud burst, whole-tail silence, mid-tail
6
+ dropout, dynamics/transient collapse) and confirm a clean, dynamic take passes.
7
+
8
+ Synthetic signals only — no model, no GPU. All are peak-normalized to ~1.0, the
9
+ state the score actually sees (engine peak-normalizes each draw before scoring).
10
+ """
11
+ import numpy as np
12
+ import types
13
+ import sys
14
+
15
+ sys.modules.setdefault("stable_audio_tools", types.ModuleType("stable_audio_tools"))
16
+ import engine # noqa: E402
17
+
18
+ SR = engine.SR
19
+ DUR = 10.0
20
+
21
+
22
+ def _norm(x):
23
+ p = float(np.abs(x).max())
24
+ return x / p if p > 0 else x
25
+
26
+
27
+ def _stereo(x):
28
+ return np.stack([x, x]).astype(np.float32)
29
+
30
+
31
+ def _t():
32
+ return np.arange(int(DUR * SR)) / SR
33
+
34
+
35
+ def _clean_dynamic():
36
+ """A continuous tonal bed plus short transients: peak set by the transients,
37
+ body level steady (no holes), so crest is high (~real music) and spikiness
38
+ stays moderate — the take the metric should ACCEPT."""
39
+ t = _t()
40
+ bed = 0.12 * (np.sin(2 * np.pi * 220 * t)
41
+ + 0.6 * np.sin(2 * np.pi * 330 * t)
42
+ + 0.4 * np.sin(2 * np.pi * 440 * t))
43
+ sig = bed.copy()
44
+ w = int(0.005 * SR) # 5 ms transients every 0.5 s
45
+ for c in range(int(0.4 * SR), len(sig), int(0.5 * SR)):
46
+ env = np.hanning(2 * w)[:w]
47
+ sig[c:c + w] += 0.7 * env * np.sin(2 * np.pi * 660 * t[c:c + w])
48
+ return _norm(sig)
49
+
50
+
51
+ def test_clean_dynamic_take_is_accepted():
52
+ score = engine._tail_artifact_score(_stereo(_clean_dynamic()), SR)
53
+ assert score <= engine.EARLY_ACCEPT_SCORE, score
54
+
55
+
56
+ def test_loud_burst_is_rejected():
57
+ sig = _clean_dynamic()
58
+ c = int(5 * SR)
59
+ sig[c:c + int(0.05 * SR)] *= 20 # a single loud spike
60
+ score = engine._tail_artifact_score(_stereo(_norm(sig)), SR)
61
+ assert score > engine.EARLY_ACCEPT_SCORE, score
62
+
63
+
64
+ def test_whole_tail_silence_is_rejected():
65
+ sig = _clean_dynamic() * 0.004 # below the 0.02 RMS floor
66
+ score = engine._tail_artifact_score(_stereo(sig), SR)
67
+ assert score > engine.EARLY_ACCEPT_SCORE, score
68
+
69
+
70
+ def test_mid_tail_dropout_is_rejected():
71
+ """A clean take with a 0.5 s near-silent hole in the middle — healthy overall
72
+ RMS and low spikiness, so only the dropout term can catch it."""
73
+ sig = _clean_dynamic()
74
+ sig[int(5.0 * SR):int(5.5 * SR)] *= 0.01
75
+ score = engine._tail_artifact_score(_stereo(sig), SR)
76
+ assert score > engine.EARLY_ACCEPT_SCORE, score
77
+
78
+
79
+ def test_squashed_transientless_take_is_rejected():
80
+ """Dense, near-constant amplitude (crest collapses): tonal and steady, so
81
+ every other term reads clean — only the crest term flags the mush."""
82
+ t = _t()
83
+ sig = np.tanh(5 * (np.sin(2 * np.pi * 220 * t)
84
+ + 0.8 * np.sin(2 * np.pi * 331 * t)
85
+ + 0.7 * np.sin(2 * np.pi * 440 * t)))
86
+ score = engine._tail_artifact_score(_stereo(_norm(sig)), SR)
87
+ assert score > engine.EARLY_ACCEPT_SCORE, score
88
+
89
+
90
+ def test_natural_ending_taper_is_not_a_dropout():
91
+ """A clean take that simply fades over its final ~0.6 s must NOT be read as a
92
+ dropout (stitch fades the end anyway); the back-guard protects it."""
93
+ sig = _clean_dynamic()
94
+ tail = sig[-int(0.6 * SR):]
95
+ sig[-int(0.6 * SR):] = tail * np.linspace(1.0, 0.0, len(tail))
96
+ score = engine._tail_artifact_score(_stereo(sig), SR)
97
+ assert score <= engine.EARLY_ACCEPT_SCORE, score
98
+
99
+
100
+ def test_too_short_tail_is_avoided():
101
+ score = engine._tail_artifact_score(_stereo(np.zeros(int(0.01 * SR))), SR)
102
+ assert score == float("inf")