android: the room can measure the song, and a cold artist page stops waiting on one call
The tap-to-sync room draws the recording's own vocal band (lyrics_vocal_activity, the desktop's coarse envelope) as a strip under the head, and one button lays every line across the stretches it says are sung — the desktop's own generator, shared and unit-tested, which refuses rather than guesses when there is too little voice to read. The result is labelled an estimate everywhere it shows, the cursor goes back to the top so the taps correct it, and one more tap drops the whole thing back to an empty set, because a rough fill stamps every line at once and leaves Undo nothing to take back. The artist page now fires the hero seed and the overview beside the full page, the way the desktop stages them: a hero Kyle picked on this phone comes back off the file with no network at all, the discography paints from the overview's first page until the exhaustive pass lands with the classified one, and both lanes are dropped with the page when a hero changes. ANDROID.md's "Not done yet" is rewritten as the true map of the port — what is absent by design and why, what is waiting on Rust rather than on a file picker, and one bullet about the artwork viewer that had been done for rounds.
This commit is contained in:
1 parent
5738c2d7bf
commit
44eda8dff5
9 files changed
+374
-39
No files matched your search
@@ -5,11 +5,17 @@
|
||||
large, the line behind and the line ahead faint around it, and the
|
||||
whole middle of the screen is the tap. Saving writes the track's own
|
||||
document; lines never tapped are left out of it, and the button says
|
||||
how many are kept. */
|
||||
how many are kept.
|
||||
|
||||
The strip under the head is the recording's own measured vocal band,
|
||||
and the button under it lays every line across the stretches it says
|
||||
are sung — the desktop's generator (src/lib/lyric-sync.js), never an
|
||||
even spread, and never presented as finished timing: the estimate is
|
||||
labelled one wherever it shows, and one more tap drops it. */
|
||||
import Icon from "../components/Icon.svelte";
|
||||
import { nav } from "./nav.svelte.js";
|
||||
import { player, toggle, seekSecs } from "./player.svelte.js";
|
||||
import { tapSync, saveTapSync, closeTapSync } from "./lyrics.svelte.js";
|
||||
import { tapSync, saveTapSync, closeTapSync, estimateTimings, clearEstimate } from "./lyrics.svelte.js";
|
||||
import { mmss } from "./util.js";
|
||||
|
||||
/* the ✕ asks twice once there is work to lose; the back gesture keeps
|
||||
@@ -44,10 +50,36 @@
|
||||
/* playback moved on: a stamp would land on another song's clock */
|
||||
const wrong = $derived(!!tapSync.identity && !!player.track && player.track.title !== tapSync.identity.title);
|
||||
|
||||
/* the envelope at the width the strip draws: 64 columns, each the
|
||||
loudest frame in its slice — the shape of the singing, not the wave */
|
||||
const cols = $derived.by(() => {
|
||||
const f = tapSync.activity?.frames;
|
||||
if (!f?.length) return [];
|
||||
const per = f.length / 64;
|
||||
return Array.from({ length: 64 }, (_, i) => {
|
||||
let m = 0;
|
||||
for (let j = Math.floor(i * per); j < Math.floor((i + 1) * per) && j < f.length; j++) m = Math.max(m, f[j]);
|
||||
return m;
|
||||
});
|
||||
});
|
||||
const spanMs = $derived((tapSync.activity?.frames?.length ?? 0) * (tapSync.activity?.frameMs ?? 100));
|
||||
const pct = (ms) => (spanMs ? Math.max(0, Math.min(100, (ms / spanMs) * 100)) : 0);
|
||||
/* one button, four states: reading the file, nothing worth reading, the
|
||||
estimate, and the way back out of it */
|
||||
const estLabel = $derived(
|
||||
tapSync.reading === "ready"
|
||||
? (tapSync.estimated ? "Clear the estimate" : "Estimate the timing")
|
||||
: tapSync.reading === "none" ? "No estimate for this one" : "Reading the song…",
|
||||
);
|
||||
|
||||
function stamp() {
|
||||
if (wrong || tapSync.saving || at >= rows.length) return;
|
||||
tapSync.rows[at].time_ms = nowMs();
|
||||
tapSync.cursor = at + 1;
|
||||
/* the cursor only ever walks forward from where the estimate put it,
|
||||
so reaching the end means every line has been tapped over — there
|
||||
is none of the estimate left, and nothing may still call it one */
|
||||
if (tapSync.estimated && tapSync.cursor >= rows.length) tapSync.estimated = false;
|
||||
confirmDiscard = false;
|
||||
}
|
||||
function undo() {
|
||||
@@ -84,12 +116,22 @@
|
||||
<div class="sy-warn">{tapSync.error}</div>
|
||||
{/if}
|
||||
|
||||
{#if cols.length}
|
||||
<div class="sy-strip">
|
||||
{#each cols as v, i (i)}<i class="sy-col" style="height:{Math.max(3, v * 100).toFixed(0)}%"></i>{/each}
|
||||
{#each rows as r, i (i)}
|
||||
{#if r.time_ms != null}<span class="sy-tick" style="left:{pct(r.time_ms).toFixed(2)}%"></span>{/if}
|
||||
{/each}
|
||||
<span class="sy-play" style="left:{pct((player.position ?? 0) * 1000).toFixed(2)}%"></span>
|
||||
</div>
|
||||
{/if}
|
||||
<button class="sy-est" onclick={() => { if (tapSync.estimated) clearEstimate(); else estimateTimings(); }} disabled={tapSync.reading !== "ready"}>{estLabel}</button>
|
||||
<button class="sy-pad" onclick={stamp} disabled={wrong}>
|
||||
<span class="sy-prev">{at > 0 ? rows[at - 1].text : ""}</span>
|
||||
{#if at < rows.length}
|
||||
<span class="sy-now">{rows[at].text}</span>
|
||||
<span class="sy-next">{at + 1 < rows.length ? rows[at + 1].text : ""}</span>
|
||||
<span class="sy-hint">Tap anywhere as the line begins</span>
|
||||
<span class="sy-hint">{tapSync.estimated ? "Estimated — tap to correct each line" : "Tap anywhere as the line begins"}</span>
|
||||
{:else}
|
||||
<span class="sy-now">That’s every line — save the timing.</span>
|
||||
<span class="sy-next"></span>
|
||||
@@ -103,7 +145,7 @@
|
||||
<button class="iconbtn sy-back5" onclick={back5} aria-label="Back five seconds">−5 s</button>
|
||||
<button class="disc" onclick={toggle} aria-label={playing ? "Pause" : "Play"}><Icon name={playing ? "pause" : "play"} size={22} /></button>
|
||||
<button class="btn btn-fill sy-save" onclick={() => { void saveTapSync(); }} disabled={!done || tapSync.saving}>
|
||||
{tapSync.saving ? "Saving…" : (done && done < rows.length ? `Save ${done} line${done === 1 ? "" : "s"}` : "Save timing")}
|
||||
{tapSync.saving ? "Saving…" : (done && done < rows.length ? `Save ${done} line${done === 1 ? "" : "s"}` : (tapSync.estimated && done === rows.length ? "Save the estimate" : "Save timing"))}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user