dj sets + lyrics: a mix that names its own tracks, and words pulled off the separated voice

This commit is contained in:
KeelrLeek committed 2026-08-24 10:49:10 -05:00
1 parent 4efe85b6bf
commit 1dd8573808
41 files changed
+9288 -647

No files matched your search

+47 -23
View File
@@ -4,14 +4,15 @@
// picker and a paste door for tracks nothing automatic can cover. Also
// rendered by the fullscreen overlay in `big` mode — one component, two
// type scales.
import { player, ui, playLocalFile } from "../lib/state.svelte.js";
import { player, ui, playLocalFile, goToView } from "../lib/state.svelte.js";
import {
lyricsStore,
lyricIdentity,
ensureLyrics,
noteTrackChange,
activeLineIndex,
activeWordIndex,
wordClockMs,
wordfill,
totalOffsetMs,
nudgeOffset,
setAutoSync,
@@ -253,19 +254,26 @@
onclick={() => seekTo(line)}
title="Play from here"
>
{#if index === active && line.words?.length}
<!-- Real word timing from the source: crisp per-word.
This is the ONLY karaoke mode. A line-level source
gets the whole-line highlight and nothing more —
a sweep interpolated across the line's window
tracked the layout, not the voice (it crawled
through instrumental gaps), so it read as broken.
Word times are carried, never invented (the
Noctis rule). -->
{@const sungTo = activeWordIndex(line, player.position ?? 0)}
<span class="wline">
{#if index === active && lyricsStore.wordFill && line.words?.length}
<!-- Karaoke word fill over REAL word times (the
wordfill mock's dialed variant A): each word
wipes across its OWN sung span, silence holds —
carried, never invented (the Noctis rule). A
line-level source still gets the whole-line
highlight and nothing more: the interpolated
sweep that tracked the layout instead of the
voice stays rejected. -->
<span
class="wline"
use:wordfill={{
line,
ms: wordClockMs(line, player.position ?? 0),
endBound: lyricsStore.lines[index + 1]?.time_ms
?? line.words[line.words.length - 1].time_ms + 2500,
}}
>
{#each line.words as word, wi (wi)}
<span class="w" class:sung={wi <= sungTo}>{word.text}</span>
<span class="fw">{word.text}</span>
{/each}
</span>
{:else}
@@ -331,7 +339,14 @@
{#snippet tools()}
<span class="tools">
{#if !big}
<button class="tool" title="Fullscreen lyrics" onclick={() => (ui.lyricsFull = true)}>
<button
class="tool"
title="Open the lyrics page"
onclick={() =>
goToView("lyrics", {
label: player.track?.title ? `${player.track.title} — Lyrics` : "Lyrics",
})}
>
<Icon name="expand" size={13} />
</button>
{/if}
@@ -615,20 +630,29 @@
}
/* ── karaoke rendering inside the active line ──
Word mode: crisp steps on the source's own word times. */
Word fill (the wordfill mock's dialed variant A): each word is its own
gradient-clip raster wiping across the word's real sung span — accent
left of the feathered boundary (±8%, mock-dialed), the pane's quieter
unsung tone right of it. --lyr-p per element (inherits: false;
registered @property in Lyrics.svelte). */
.wline {
display: block;
}
.w {
color: var(--text-secondary);
transition: color 0.12s ease;
.fw {
--lyr-p: -8%;
background-image: linear-gradient(
90deg,
var(--accent-hover) calc(var(--lyr-p) - 8%),
var(--text-secondary) calc(var(--lyr-p) + 8%)
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.w + .w {
.fw + .fw {
margin-left: 0.32em;
}
.w.sung {
color: #fff;
}
.autochip {
flex: none;