Files
SoulStream/handoff/rounds/ins-r30-lyr-store.txt
T
KeelrLeek 9f0601027c android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43
to 48 (each patch adversarially verified, 43's transient-notice expectation
fixed), a partial round 49, the pipeline scripts, the Android Auto design and
the icon comparison mock. Also the two harness changes that were never A/B'd —
adaptive waits, which may only shorten a declared wait and never lengthen it,
and a ship that runs the mock walk and the deploy at the same time. The A/B and
its 255-step baseline are in handoff/tools; main stays at what the phone has
actually walked.
2026-09-14 07:57:41 -05:00

58 lines
3.1 KiB
Plaintext

/* ── the measured envelope, and the rough pass over it ───────────────────
The desktop's editor offers the same two things (LyricsSyncEditor.svelte):
a strip showing where the recording has voice in it, and a fill that lays
the lines across the ACTIVE stretches only. src/lib/lyric-sync.js is the
desktop's own generator — pure, shared, unit-tested — so the phone imports
it rather than keeping a second copy of the arithmetic. It refuses instead
of guessing when the measurement shows nothing usable: an estimate is a
starting point for the tap, never finished timing, and the room says so
everywhere it shows. The envelope belongs to the track, not to the store —
it is measured on open and thrown away with the room. */
async function loadActivity(id) {
/* a streamed track is a cache file the engine may still be filling, and
the reducer refuses one on the same decode path for the same reason */
if (!id?.path || player.track?.stream) { tapSync.reading = "none"; return; }
tapSync.reading = "reading";
try {
const a = await api.lyricsVocalActivity(id.path);
if (tapSync.identity?.key !== id.key) return; // the room moved on
tapSync.activity = a?.frames?.length ? a : null;
tapSync.reading = tapSync.activity ? "ready" : "none";
} catch {
if (tapSync.identity?.key === id.key) tapSync.reading = "none";
}
}
/** Lay every line across the stretches the envelope says are sung, then put
* the cursor back at the top: the taps CORRECT the estimate from there. */
export function estimateTimings() {
const a = tapSync.activity;
if (!a || !tapSync.rows.length) return false;
/* the room's rows ARE the lines: openTapSync built them from the document
the sheet is showing. So the generator's two halves are called directly
rather than roughSync's own text parse, which drops a section label a
SYNCED document still carries and would hand back a shorter list that
no longer lines up with the rows the room is asking about. */
const filled = distributeLines(tapSync.rows.map((r) => String(r.text ?? "").trim()), activeSegments(a.frames, a.frameMs));
/* it refuses rather than guesses when there is too little measured voice
to lay the lines across — and that is the only reason it refuses, so
the room can say which one it is */
if (!filled) {
tapSync.error = "There is not enough voice in this song to estimate from.";
return false;
}
for (let i = 0; i < filled.length; i++) tapSync.rows[i].time_ms = filled[i].time_ms;
tapSync.cursor = 0;
tapSync.estimated = true;
tapSync.error = "";
return true;
}
/** Drop it: every line back to no time, ready for the tap. A rough fill
* stamps every row at once, so Undo has nothing left to take back — this
* is the way out of an estimate that is wrong. */
export function clearEstimate() {
for (const r of tapSync.rows) r.time_ms = null;
tapSync.cursor = 0;
tapSync.estimated = false;
tapSync.error = "";
}