commit 9d2bd76b6f911152667623f503731478bb54d81b
parent 7085e0db117c4329ddae47acc3bd804eef870489
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 15:01:17 -0400
umtool: the bench's mute preview is the build's — one MUTE_FADE (0.04 s, from deck.mjs), a ramp that ends at the mark
The bench faded over 0.05 s starting at the mark; the build fades over 0.04 s
ending at it (silent from muteFrom exactly). A mark set by ear now sounds in
the bench as it will in the cut.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/umtool/lib/report/playback.mjs b/umtool/lib/report/playback.mjs
@@ -38,10 +38,11 @@ export const DECODE_MARGIN = 30;
export const START_LEAD = 0.03;
/**
- * The mute mark's fade, in source seconds: short enough to read as "the sound
- * stops here", long enough not to click.
+ * The mute mark's fade, in source seconds: the BUILD's, so what the bench plays
+ * is what the cut does -- a ramp that ENDS at the mark, silent from the mark on.
*/
-export const MUTE_FADE = 0.05;
+export { MUTE_FADE } from "umtool-report-to-video/deck";
+import { MUTE_FADE } from "umtool-report-to-video/deck";
/** One Web Audio render quantum, in frames. */
export const RENDER_QUANTUM = 128;
@@ -134,7 +135,9 @@ export function playheadAt(from, t0, now, rate, duration) {
* { at: 0, fade: 0 } the range starts at or after the mark: silent
* from the first sample, the picture still plays
* { at, fade } full level until `at`, then a linear ramp to
- * nothing over `fade`
+ * nothing over `fade`, reaching it AT the mark
+ * (as the build's afade does); a range that starts
+ * inside the fade ramps from its first sample
*
* @param {number | null | undefined} muteFrom
* @param {number} from
@@ -148,7 +151,8 @@ export function muteRamp(muteFrom, from, to, rate = 1, fade = MUTE_FADE) {
const r = rate > 0 ? rate : 1;
if (muteFrom >= to) return null;
if (muteFrom <= from) return { at: 0, fade: 0 };
- return { at: (muteFrom - from) / r, fade: fade / r };
+ const st = Math.max(from, muteFrom - fade);
+ return { at: (st - from) / r, fade: (muteFrom - st) / r };
}
/**
diff --git a/umtool/lib/report/playback.test.mjs b/umtool/lib/report/playback.test.mjs
@@ -95,14 +95,20 @@ test("muteRamp: when the mark silences a playback, in wall seconds from its star
// At or before the start: silent throughout, the picture still plays.
assert.deepEqual(muteRamp(10, 10, 20), { at: 0, fade: 0 });
assert.deepEqual(muteRamp(5, 10, 20), { at: 0, fade: 0 });
- // Inside: full level until the mark, then the fade.
+ // Inside: full level until the fade, which ENDS at the mark (the build's).
+ assert.equal(MUTE_FADE, 0.04);
const m = muteRamp(16, 10, 20, 1);
- near(m.at, 6);
+ near(m.at, 6 - MUTE_FADE);
near(m.fade, MUTE_FADE);
+ near(m.at + m.fade, 6);
// At 2x the mark arrives in half the time, and so does the fade.
const f = muteRamp(16, 10, 20, 2);
- near(f.at, 3);
+ near(f.at, (6 - MUTE_FADE) / 2);
near(f.fade, MUTE_FADE / 2);
+ // A range starting inside the fade ramps from its first sample to the mark.
+ const g = muteRamp(16, 15.98, 20, 1);
+ near(g.at, 0);
+ near(g.fade, 0.02);
});
test("elementShouldStop: stops when the end is under half a frame away, at any rate", () => {