diff --git a/src/routes/song/[id]/+page.svelte b/src/routes/song/[id]/+page.svelte index bd3d3bd..3685ded 100644 --- a/src/routes/song/[id]/+page.svelte +++ b/src/routes/song/[id]/+page.svelte @@ -5,13 +5,13 @@ import { onMount, tick } from "svelte"; import { typingSettingsDefault, type LyricSegment } from "../../../shared/types.ts"; import { isKana, isKanji, toHiragana, toKatakana, toRomaji } from "wanakana"; - import { composeList, fuzzyEquals, processLrcLine, type ProcLrcLine, type ProcLrcSeg } from "./IMEHelper.ts"; + import { composeList, fuzzyEquals, processLrcLine, dedupLines, type ProcLrcLine, type ProcLrcSeg } from "./IMEHelper.ts"; import MenuItem from "../../../components/material3/MenuItem.svelte"; import "../../../shared/ext.ts" import { API } from "$lib/client.ts"; import { animateCaret } from "./animation.ts"; import { goto } from '$app/navigation'; - import { artistAndAlbum } from "../../../shared/tools.ts"; + import { artistAndAlbum } from "../../../shared/tools.ts"; let { data }: PageProps = $props() @@ -34,11 +34,21 @@ const preprocessKana = (kana: string, state?: string) => (settings.showRomaji || (settings.showRomajiOnError && state === 'wrong')) ? `${_preprocessKana(kana)}` : _preprocessKana(kana) // Process each line into segments with swi (start word index) and kanji/kana - let processedLrc: ProcLrcLine[] = data.lrc.map(line => processLrcLine(line.lyric)) - + let processedLrc: ProcLrcLine[] = $derived(dedupLines(data.lrc, settings.hideRepeated).map(line => processLrcLine(line.lyric))) // State tracking for each kana character: UNSEEN, RIGHT, WRONG - let states = $state(processedLrc.map(line => new Array(line.totalLen).fill('unseen'))) - const allStates = (l: number, seg: ProcLrcSeg) => states[l].slice(seg.swi, seg.swi + seg.kana.length) + let states = $state(dedupLines(data.lrc, settings.hideRepeated).map(line => processLrcLine(line.lyric)).map(line => new Array(line.totalLen).fill('unseen'))) + + $effect(() => { + // Reset when processedLrc changes (settings changed) + states = processedLrc.map(line => new Array(line.totalLen).fill('unseen')) + li = 0 + wi = 0 + inp = "" + startTime = 0 + statsHistory = [] + }) + + const allStates = (l: number, seg: ProcLrcSeg) => states[l]?.slice(seg.swi, seg.swi + seg.kana.length) ?? [] const getKanjiState = (l: number, seg: ProcLrcSeg) => { let sts = allStates(l, seg) if (sts.every(s => s === 'right')) return 'right' @@ -72,6 +82,7 @@ // On input changed: Convert to hiragana, compare with current position, update states function inputChanged(input: string, isComposed: boolean) { + if (!processedLrc[li] || !states[li]) return; if (!startTime && input) startTime = Date.now() console.log(`input changed: ${input}`) // Convert to hiragana @@ -125,7 +136,6 @@ inp = "" } } - $effect(() => inputChanged(inp, false)) // Caret: Typing indicator @@ -175,6 +185,7 @@ + {#if loc} @@ -211,16 +222,16 @@ {#each line.parts as seg} {#if !seg.kanji} {#each seg.kana as char, c} - - {@html preprocessKana(char, states[l][seg.swi + c])} + {@html preprocessKana(char, states[l]?.[seg.swi + c])} {/each} {:else} {seg.kanji}{#if settings.isFuri}{/if} @@ -228,6 +239,7 @@ {/each} {/each} +
diff --git a/src/routes/song/[id]/IMEHelper.ts b/src/routes/song/[id]/IMEHelper.ts index b114f5e..eb289e7 100644 --- a/src/routes/song/[id]/IMEHelper.ts +++ b/src/routes/song/[id]/IMEHelper.ts @@ -1,5 +1,5 @@ import { toHiragana } from "wanakana"; -import type { LyricSegment } from "../../../shared/types"; +import type { LyricLine, LyricSegment } from "../../../shared/types"; export type ProcLrcSeg = { swi: number, kanji?: string, kana: string } export type ProcLrcLine = { parts: ProcLrcSeg[], totalLen: number } @@ -33,4 +33,18 @@ export const composeList = [ 'だ', 'ぢ', 'づ', 'で', 'ど', 'ば', 'び', 'ぶ', 'べ', 'ぼ', 'ぱ', 'ぴ', 'ぷ', 'ぺ', 'ぽ' -] \ No newline at end of file +] + +/** + * Remove duplicate lyric lines based on their content. + */ +export function dedupLines(lrc: LyricLine[], hide: boolean) { + if (!hide) return lrc; + const seen = new Set