From 000ddcfca7209d8961fd25ff680de593277ee4b4 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 18 Aug 2026 00:22:19 -0400 Subject: [PATCH] =?UTF-8?q?loner:=20the=20energy=20trim=20was=20eating=20"?= =?UTF-8?q?ly"=20=E2=80=94=20cut=20only=20TRAILING=20silence,=20never=20a?= =?UTF-8?q?=20pause=20inside=20a=20word?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @jeffrey: "ly and for are confused / wrong word boundaries". Not the alignment this time — my trim. energy_end() cut at the FIRST sustained quiet run inside a unit, on the assumption that silence means the word is over. It isn't always: a long word can hold a pause inside itself, and "patiently" has 200 ms of it between "patient" and "ly". The trim was finding that gap, deciding the word had ended, and throwing the last syllable away — so "ly" vanished from patiently and the ear looked for it in "for". It now walks the quiet runs from the BACK and takes the first one with nothing after it but the next word's leak (≤150 ms). Trailing silence still goes; an internal breath no longer ends a word. Verified in the render: patiently holds "pa-tient" at +7.0 st AND "ly" at +5.0 st inside its own block, and "for" is a single +3.0 st note for its whole slot. Worth noting the same fault would have been quietly eating the end of any multi-syllable word with a stop in it — this is the second time today that a rule which is right for a sustained vowel has been wrong for a word with syllables in it. --- pop/loner/bin/halo3.py | 16 +++++++++++++--- pop/loner/vox4/.chart.json | 4 ++-- pop/loner/vox4/.manifest.json | 3 +-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pop/loner/bin/halo3.py b/pop/loner/bin/halo3.py index 5d0037e17f..6dd20a0aad 100644 --- a/pop/loner/bin/halo3.py +++ b/pop/loner/bin/halo3.py @@ -389,7 +389,8 @@ def snap_boundaries(a, words, t0): TRIM_GATE_DB = -36.0 # of the take's peak; keeps quiet fricatives TRIM_MARGIN_S = 0.050 # let the release start before we cut TRIM_MIN_S = 0.080 # below this, not worth the surgery -TRIM_QUIET_RUN_S = 0.120 # silence this long means the word is over +TRIM_QUIET_RUN_S = 0.120 # silence this long may end the word +TRIM_LEAK_S = 0.150 # audio after it that is just the next word bleeding in ATTACK_S = 0.030 # pre-roll kept ahead of every word's onset @@ -434,17 +435,26 @@ def energy_end(x, fs, f0, f1, peak): if not len(on): return f0 + m run = int(round(TRIM_QUIET_RUN_S / FRAME_S)) - k = int(on[0]) # never cut before she starts + leak = int(round(TRIM_LEAK_S / FRAME_S)) + runs, k = [], int(on[0]) # never cut before she starts while k < m: if quiet[k]: j = k while j < m and quiet[j]: j += 1 if j - k >= run: - return f0 + k + runs.append((k, j)) k = j else: k += 1 + # Cut only at TRAILING silence. A long word can hold a pause INSIDE + # it — "patiently" has 200 ms between "patient" and "ly" — and + # cutting at the first quiet run threw that last syllable away. So + # walk the runs from the back and take the first one with nothing + # after it but the next word's leak. + for (a_, b_) in reversed(runs): + if int((~quiet[b_:]).sum()) <= leak: + return f0 + a_ return f0 + int(on[-1]) + 1 diff --git a/pop/loner/vox4/.chart.json b/pop/loner/vox4/.chart.json index 793b5a8c03..f9321d96ac 100644 --- a/pop/loner/vox4/.chart.json +++ b/pop/loner/vox4/.chart.json @@ -25,7 +25,7 @@ ], [ 27.999, - 31.1812 + 31.0998 ], [ 31.4963, @@ -527,7 +527,7 @@ 8.0012 ], [ - 9.0992, + 8.2248, 16.3277 ], [ diff --git a/pop/loner/vox4/.manifest.json b/pop/loner/vox4/.manifest.json index 9f7dda9a02..14c7986513 100644 --- a/pop/loner/vox4/.manifest.json +++ b/pop/loner/vox4/.manifest.json @@ -33,7 +33,7 @@ "myself\u00b7b \u2212265ms", "think \u2212350ms", "stone \u2212675ms", - "just \u2212335ms", + "just \u2212195ms", "very \u2212520ms", "patiently \u2212480ms", "to \u2212855ms" @@ -199,7 +199,6 @@ "trims": [ "A \u2212215ms", "just \u2212265ms", - "waiting \u22121260ms", "very \u2212400ms" ], "words": "of a stone just waiting very patiently" -- 2.51.2