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:
KeelrLeek committed 2026-09-14 00:58:14 -05:00
1 parent 5738c2d7bf
commit 44eda8dff5
9 files changed
+374 -39

No files matched your search

+84 -18
View File
@@ -66,9 +66,12 @@ the desktop's own `color.js` and crossfaded by the compositor through the
registered `--accent`. The glass bar is `GlassBar.svelte`'s recipe and stays
anonymous until the page's large title has scrolled under it. The mini bar
is the desktop's 62px row over a 16px lane strip; tapping it is a container
transform (the small art flies to the big one). The player's scrubber is the
lane itself, no knob. Motion is Material 3 Expressive's springs, sampled into
`linear()`. Reduce motion (You → Appearance, or the OS setting) turns it off.
transform (the small art flies to the big one); the big art opens the artwork
viewer — the cover at full size, pinch to zoom, one finger pans while zoomed,
a tap on the unzoomed cover or the back gesture closes. The player's scrubber
is the lane itself, no knob. Motion is Material 3 Expressive's springs,
sampled into `linear()`. Reduce motion (You → Appearance, or the OS setting)
turns it off.
The activity declares no `screenOrientation` and handles `orientation` and
`screenSize` itself, so turning the phone over is a resize and nothing else —
@@ -141,6 +144,21 @@ into families, the library's copies marked), releases by year, fans also
like, what is on this phone, About. Opened from a search row it carries the
row's identity (`seed`), so a namesake can never take the page.
A cold page no longer waits on one call for everything. Three lanes run off
the artist's id the moment it is known: Popular's ranked rows
(`catalog_artist_popular`), the banner alone (`catalog_artist_hero_seed`) and
the first page of releases (`catalog_artist_overview`) — the desktop's own
staging, for the desktop's own reason, since the identity pass inside the
full page is a MusicBrainz chain behind a one-request-a-second gate. The
overview's releases hold the discography up until the exhaustive page lands
with the classified one, and never the other way round; its `popular_tracks`
are dropped, because Popular already has a ranked lane of its own. The hero
seed pays for itself twice: its first source is the hero Kyle picked on this
phone, read off the file with no network at all, so **Choose hero photo**
shows on the next visit at once instead of behind a whole catalog page — and
both lanes are forgotten with the page when a hero is set or removed, or the
old banner would answer while the new one was still being fetched.
The **catalog record page** is the desktop's album page: the kind, the
artist byline with a portrait, year · tracks · runtime, Last.fm's totals,
Play / Save / Shuffle, the record's other editions (the artist's page folded
@@ -290,7 +308,26 @@ and play/pause under the thumb, and Save writing the track's own document
(`lyrics_save_lines`, the top of the waterfall, where a paste lands too).
Lines never tapped are left out of it and the button says how many are kept;
a set left mid-tap by the back gesture is offered back by the row that
opened it. "Play with reduced vocals" is the desktop's offline DSP
opened it.
The room also measures the recording. `lyrics_vocal_activity` is the
desktop's coarse vocal-band envelope — the centre channel band-passed to
180 Hz – 7.5 kHz, one value per 100 ms, read on a blocking thread — and it
draws as a strip under the head, so the singing ahead is visible while the
thumb waits for it. **Estimate the timing** lays every line across the
stretches the envelope says are sung, weighted by line length, through the
desktop's own generator (`src/lib/lyric-sync.js`, shared and unit-tested):
never an even spread over the duration, and a refusal rather than a guess
when the measurement shows nothing usable. The result is labelled an
estimate everywhere it shows — the hint under the line, the Save button —
the cursor goes back to the top so the taps correct it from there, and one
more tap on the button drops the whole thing back to an empty set (a rough
fill stamps every line at once, so Undo has nothing left to take back).
A streamed track has no finished file to measure, so the button says there
is no estimate for that one — the same rule the reduced-vocals copy follows,
and the same whole-file decode behind both.
"Play with reduced vocals" is the desktop's offline DSP
(`lyrics_prepare_instrumental` — a band-limited centre cancel, not a
separator, and the wording stays honest about that): the copy plays under
the same title, artist and duration, so the lyric identity is still the
@@ -842,17 +879,46 @@ paused) without the walk.
## Not done yet
- Community's private layer (Spaces, holdings, the sealed inbox, friends by
key, Who has it) — Freenet on the phone is a later experiment; the
network side is in. Rooms and chat are built and mock-walked; a live room
is Kyle's to join.
- The desktop-only doors and tools: the playlist-file import and export (a
file picker the phone's storage plugin has still to grow), the listener
door and the lyric alignment models (a desktop runtime), drag reorder
(Move up / down / to the top stand in), catalog track previews, the full
discography page, the artist-artwork upscaler, updates and the account
section (sign-in lives on Community).
- Follows and the play history are per library folder, so the phone's own
folder starts empty of both; they arrive with a shared library.
- The artwork viewer: the cover at full size, pinch to zoom, one finger
pans while zoomed, a tap on the unzoomed cover or the back gesture closes.
Thirty rounds in, this is the honest map: what is deliberately absent, what
is waiting on Rust, and what is simply smaller than the rest. Most of what
the backend exposes and the phone never calls is a whole module it has no
business calling, so the list is by module — that is the reason, not the
symptom.
- **Off by design — the private layer.** Spaces, holdings, the sealed inbox,
friends by key, shared playlists, Who has it, the persona and its profile,
and the Soulseek↔persona link: sixty-six commands across ten modules
(`freenet` 7, `friends` 7, `holdings` 8, `inbox` 3, `persona` 6, `profile`
4, `proposals` 3, `shared_playlists` 13, `slsk_link` 5, `spaces` 10).
Freenet on the phone is a later experiment; the network side is in, and
rooms and chat are built and mock-walked. A live room is Kyle's to join.
- **Off by design — the desktop's own machinery.** The tag-import pipeline
(all sixteen of `import.rs`: its audition, its cover report, its portable
root — which is the import session's root, not the library folder); hover
previews (all eight of `preview.rs`), and with them the three pause/fade
acknowledgements that live there because they exist only to hand silence
between the main player and the preview player; the mini window, the glass
mode, Show in folder and SMTC; the updater (the phone's own build of it
answers honestly that there is no channel here, and the version is in You,
from `app_status`); the hero GPU upscaler, the cover-wordmark OCR and its
debug tools. The phone shows a wordmark the page carries; it does not run
the pipeline that makes one — and it does not fetch a 3000px cover upgrade
over mobile data for a screen that cannot show it, which is why
`catalog_cover` and `invalidate_catalog_cover` stay here too.
- **Off by design — external runtimes.** yt-dlp and deno, the vocal
separator, the lyric alignment models, and the listener door that needs
them.
- **Waiting on Rust, not on a picker.** Playlist EXPORT: `tauri-plugin-dialog`
does open Android's create-document sheet from Rust, but it answers with a
`content://` URI and `playlist_export` writes with `std::fs::write`, which
cannot take one. It needs a ContentResolver write, or the bytes handed to
an outbound share intent — and `crates/tauri-plugin-share` is inbound-only
today. Playlist-FILE import wants a `pick_file` in the storage plugin,
beside the `pick_folder` and `pick_image` it already has.
- **Still desktop-only, smaller.** Drag reorder (Move up / down / to the top
stand in), catalog track previews, the full discography page, and the
fused-artist portrait verdict — that last one needs the identity merge the
phone does not run, so porting it means porting the merge first.
- **Per library folder.** Follows and the play history live in the library
folder, so the phone's own folder starts empty of both; they arrive with a
shared library.