Problem Statement
I sing with OpenKara and the lyrics almost always change by the whole line. That is fine for reading along. It is a poor karaoke cue. Word-timed lyrics exist in the AMLL catalog, but OpenKara never looks there. If LRCLIB already filled the cache with line-timed lyrics, that answer sticks forever. I have no way to get a better word-timed match without throwing away lyrics I placed myself.
Solution
OpenKara treats AMLL as the first online source in Lyrics Acquisition. A confident AMLL match that parses to word-timed lyrics wins and is cached. If AMLL has nothing trustworthy, OpenKara falls through to LRCLIB and then LrcApi, same as today. Songs that already have line-timed lyrics from an online source get a silent Word-timed Upgrade after first paint, only when the AMLL match is confident. Manual, sidecar, and embedded lyrics stay. Playback does not wait on AMLL when lyrics already exist on disk.
User Stories
- As a singer, I want word-timed lyrics when AMLL has them, so that the highlight follows the syllable I am singing.
- As a singer, I want line-timed lyrics when AMLL has no confident match, so that I still have something to follow.
- As a singer, I want local sidecar and embedded lyrics to beat every online guess, so that the file I put next to the song is what I sing.
- As a singer, I want the first paint of a song I have already cached to stay instant, so that opening a track does not stall on the network.
- As a singer with an existing LRCLIB cache, I want OpenKara to upgrade that cache to word-timed lyrics when AMLL is confident, so that my old library becomes karaoke-ready without me refetching every song.
- As a singer with an existing LrcApi cache, I want the same Word-timed Upgrade, so that any online line-timed winner can improve.
- As a singer, I want a mid-song upgrade only when the new lyrics are actually word-timed, so that the line does not jump to another line-timed guess.
- As a singer, I want a wrong or fuzzy AMLL search to leave my current line-timed lyrics alone, so that OpenKara does not swap in the wrong song while I am singing.
- As a singer, I do not want a picker in the middle of a performance, so that I can keep singing.
- As a singer who pasted or imported lyrics, I want those lyrics to survive automatic upgrade, so that OpenKara never discards my edit.
- As a catalog owner who shipped a sidecar TTML, LYS, or LRC file, I want that sidecar to survive automatic upgrade, so that the library I published stays authoritative.
- As a catalog owner with unsynced embedded tags, I want the existing full-chain automatic upgrade to still run, so that unsynced tags can still become timed lyrics from any online source.
- As a singer, I want unsynced LRCLIB leftovers to try AMLL only, so that a sticky online miss does not get rewritten by another line-timed provider during Word-timed Upgrade.
- As a singer who calibrated offset on line-timed lyrics, I want that offset to stay if AMLL does not win, so that my timing work is not thrown away by a probe.
- As a singer, I want offset to reset when the winning source changes to AMLL, so that an old LRCLIB trim is not applied to a different timeline.
- As a singer, I want a network failure during upgrade to keep the lyrics already on screen, so that going offline does not blank the panel.
- As a singer, I want OpenKara to remember a confident AMLL miss for a week, so that every play does not search AMLL again.
- As a singer, I want a rate-limit or outage on AMLL to retry next play, so that a 429 does not hide later word-timed lyrics for a week.
- As a singer, I want a later addition to the AMLL catalog to become visible after the probe expires, so that a song that had nothing last month can upgrade this month.
- As a singer playing a new song, I want AMLL to be tried before LRCLIB, so that first paint can already be word-timed when the catalog has the track.
- As a singer, I want line-timed TTML from AMLL to count as a miss, so that OpenKara still falls through to LRCLIB instead of treating a line-only TTML as a karaoke win.
- As a singer, I want album metadata that is wrong or extra not to hide a title-and-artist match, so that a messy tag does not block AMLL.
- As a singer of featured-artist titles, I want
feat./ft.noise not to block a match, so thatME!andME! (feat. …)can still pair. - As a singer of remastered or official-video titles, I want version noise stripped, so that
(Official Video)does not make two recordings look like different songs. - As a singer of CJK titles, I want name matching to survive fullwidth characters and NFKC folds, so that catalog spelling variants still hit.
- As a singer of multi-artist tracks, I want artist overlap rather than exact string equality, so that
Artist A / Artist Bcan match a listing that only names one of them. - As a singer, I want two similar AMLL hits for the same title to stay unused, so that OpenKara prefers my current line-timed lyrics over a coin flip.
- As a singer, I want the one exact title-and-artist hit to win among similars, so that a remix sitting next to the original does not block the original.
- As a singer who clicks “fetch lyrics online”, I want the same online order and confidence rule, so that a manual fetch does not suddenly become sloppier than playback.
- As a singer who clicks “fetch lyrics online” on a song I edited, I want that explicit fetch to be allowed to replace my edit, so that I still have an escape hatch.
- As a singer on a Remote Repository, I want a successful Word-timed Upgrade to Publish Changes with the new lyrics, so that the other device sings the same words.
- As a singer on a Remote Repository, I want a probe stamp to Publish Changes without wiping lyrics, so that the other device does not search AMLL again the same week.
- As a singer, I want the lyrics source to record
amllwhen AMLL won, so that I can tell an online word-timed result from a sidecar I shipped. - As a singer, I want Media G / instrumental-only tracks to keep today’s “no lyrics” behavior, so that AMLL is not searched for songs that should stay quiet.
- As a singer, I want a missing title or artist to skip online lookup, so that an untitled file does not spray the AMLL search box.
- As a singer, I want OpenKara to identify itself to AMLL with its app version, so that the public API can tell desktop karaoke traffic from a broken client.
- As a singer, I want AMLL search never to send a fuzzy catch-all query or a fake duration, so that OpenKara does not lie to the matcher.
- As a singer, I want the lyrics contract and the on-disk cache to agree on the new source and the probe field, so that a future client can read what this client wrote.
- As a maintainer, I want ADR 0015 superseded only for online order, so that local-before-online, sidecar order, and the seven-day negative cache stay written down.
- As a maintainer, I want a dedicated ADR for Word-timed Upgrade, so that a later reader does not “fix” the cache-first rule by deleting the upgrade.
- As a maintainer, I want a wipe test that fails the change if an AMLL miss turns a good LRCLIB row into absent, so that singers do not lose lyrics.
- As a maintainer, I want command payloads after persist to come from the cache row, so that offset on screen matches offset on disk.
- As a singer using AirPlay or the audience view, I want the upgraded lines to flow through the existing lyric payload, so that every surface sees the same winner.
- As a singer, I want karaoke fill to run as soon as word tokens exist, so that I do not need a new lyrics stage to benefit from AMLL.
- As a singer, I want this work not to relicense OpenKara or pull AGPL player packages, so that the app stays Apache-2.0.
Implementation Decisions
- Lyrics Acquisition keeps local-before-online. Final order: cache, embedded, sidecar TTML, sidecar LYS, sidecar LRC, AMLL, LRCLIB, LrcApi. Sidecar priority and the seven-day negative cache stay as in ADR 0015. Record the new online order as ADR 0026, and mark ADR 0015 superseded for chain order only.
- AMLL is consumed only through the native HTTP API (
searchthengetby id). Do not call AMLL’s LrcLib-compatible endpoints. Do not sendq,duration, or platform ids. OpenKara songs have no NCM / Apple / Spotify / ISRC ids today. - Search uses title and artist, plus album when the song has one. Page size is 5. If album was sent and the page is empty, retry once without album. A 429 or 5xx on either attempt is unavailable, not a miss.
- A win requires a confident match and parsed TTML with at least one line that has word tokens. Line-timed TTML is an AMLL miss.
- Confidence (from the design prototype): normalize with NFKC, lowercase, strip featuring clauses and a fixed version-noise list, then filter to title-similar AND artist-overlap. Confident when the filtered set has one item, or exactly one filtered item is an exact normalized title-and-artist match. Otherwise miss. Artist containment of length ≥ 2 is intended (precision over recall: extra hits make the set more often ambiguous). Empty Jaccard token sets do not match.
- Accept-gate: AMLL raw text is timed only when TTML parse succeeds and word tokens exist. Do not run the LRC timed-line check on AMLL TTML or the win is discarded.
- Online result types gain a distinct
WordTimedProbeMiss. That value means “AMLL-only search completed as a miss.” It must not share a type with “every online provider is definitely missing.” - Word-timed Upgrade reuses
automatic_upgrade. First paint the cached online line-timed lyrics. Then call AMLL only. Do not re-query LRCLIB or LrcApi on that path. Apply the follow-up only when the new source is AMLL and the lines have word tokens. automatic_upgrademay replacelrc_lib/lrc_api/lrc_api_ttmlonly with word-timed AMLL. It must not write an absent row over those sources. A mis-wired definite-missing result on those rows stamps the probe and keeps the lyrics. Manual and sidecar stay protected. Unsynced embedded and absent keep today’s full-chain automatic upgrade.- Durable probe: nullable
word_timed_checked_aton the lyrics cache, same clock and seven-day TTL as negative cache. Stamp only after a successful AMLL search that is a miss or ambiguous. Do not stamp on 429, 5xx, timeout, or decode failure. Clock failure treats the probe as fresh (skip network). Stamp-only persist is a real change so Publish Changes copies the stamp. An AMLL win clears the stamp. - Offset is source-specific. Changing the winning source resets offset to 0 or to a best-effort TTML-declared offset. After any persist, the command payload is rebuilt from the cache row so on-screen offset matches disk. A probe miss must not replace a user-tuned offset.
- New lyrics source variant serializes as
amllin both the IPC contract and the cache. Do not “fix” the historicallrclib/lrc_libsplit while adding it. The lyrics contract updates in the same change as the enum. - Record Word-timed Upgrade and the confidence rule as ADR 0027.
- HTTP client mirrors the existing online lyrics clients: OpenKara user-agent, 3s connect, 6s total. Do not put deny-unknown-fields on the AMLL JSON models.
- Do not add
[@applemusic-like-lyrics](/applemusic-like-lyrics)packages. Presentation of the winning lines is the companion spec.
Testing Decisions
A good test asserts observable Lyrics Acquisition behavior: which source won, whether word tokens exist, whether the durable cache still holds the previous lyrics, and whether offset on the command payload matches the cache. Do not assert matcher internals except through named title/artist fixtures. Do not hit the live AMLL network.
Primary seam: the existing fetch_lyrics and fetch_lyrics_online commands plus the lyrics cache they persist. This is the same seam used today for LRCLIB / LrcApi / negative cache. Drive AMLL through a mock HTTP server at that boundary, the same way phase-4 lyrics fetch tests already mock the other providers.
One extra existing seam, kept thin: the lyrics session’s automatic-upgrade follow-up. Assert only that online line-timed lyrics request automatic_upgrade, that a word-timed AMLL payload is applied, and that a non-AMLL follow-up is ignored. Do not re-test matching in the session.
Prior art: lyrics acquisition unit tests, lyrics command tests, phase-4 mocked fetch tests, lyrics-session automatic-upgrade tests, and the IPC contract source-enum test.
Merge gate for the acquisition change: seed an LRCLIB cache row, run the real AMLL-only upgrade persist path against an AMLL 404 and against an empty/ambiguous search, and fail if the row becomes absent.
Also required at the command seam: AMLL word-timed TTML is a win and LRCLIB is not called after get; AMLL unavailable then LRCLIB hit persists without a probe stamp; AMLL miss then LRCLIB hit persists with a probe stamp; a fresh probe makes zero HTTP calls; a declared TTML offset appears on both the cache row and the command payload; an upgrade miss keeps a user-tuned offset.
Out of Scope
- Embedding or relicensing onto the AGPL AMLL player, React bindings, or JS parsers.
- Apple Music center-active layout, blur, hide-past-lines, interlude dots, or dynamic album backgrounds.
- A match picker during playback.
- A translation line.
- Platform-id lookup.
- Using AMLL’s LrcLib-compatible API or treating its
durationfield as audio length. - Changing sidecar order or making AMLL beat local files.
- Hand-editing the changelog.
Further Notes
Companion spec: keep the OpenKara list lyrics stage and surface Supplied Romanization on the existing romanize overlay. That spec does not acquire AMLL. This spec does not restyle the panel.
Domain terms: Lyrics Acquisition, Word-timed Lyrics, Line-timed Lyrics, Word-timed Upgrade, Publish Changes. Do not write “AMLL lyrics” for word-timed, or “auto fetch” for Word-timed Upgrade.