diff --git a/src/lib/components/lyrics/lyricLine.svelte b/src/lib/components/lyrics/lyricLine.svelte
index 89da493..aa48317 100644
--- a/src/lib/components/lyrics/lyricLine.svelte
+++ b/src/lib/components/lyrics/lyricLine.svelte
@@ -7,7 +7,6 @@
export let line: ScriptItem;
export let index: number;
export let debugMode: Boolean;
- export let initPos: LyricPos;
let ref: HTMLDivElement;
let time = 0;
@@ -21,25 +20,18 @@
let springX: Spring | undefined = undefined;
let isCurrentLyric = false;
- $: {
- if (initPos) {
- positionX = initPos.x;
- positionY = initPos.y;
- }
- }
-
function updateY(timestamp: number) {
if (lastUpdateY === undefined) {
- lastUpdateY = timestamp;
+ lastUpdateY = new Date().getTime();
}
if (springY === undefined) return;
- time = (timestamp - lastUpdateY) / 1000;
+ time = (new Date().getTime() - lastUpdateY) / 1000;
springY.update(time);
positionY = springY.getCurrentPosition();
if (!springY.arrived()) {
requestAnimationFrame(updateY);
}
- lastUpdateY = timestamp;
+ lastUpdateY = new Date().getTime();
}
function updateX(timestamp: number) {
@@ -47,7 +39,7 @@
lastUpdateX = timestamp;
}
if (springX === undefined) return;
- time = (timestamp - lastUpdateX) / 1000;
+ time = (new Date().getTime() - lastUpdateX) / 1000;
springX.update(time);
positionX = springX.getCurrentPosition();
if (!springX.arrived()) {
@@ -77,16 +69,18 @@
};
export const update = (pos: LyricPos, delay: number = 0) => {
- if (lastPosX === undefined) {
+ if (lastPosX === undefined || lastPosY === undefined) {
lastPosX = pos.x;
- }
- if (lastPosY === undefined) {
lastPosY = pos.y;
}
- springY = createSpring(lastPosY, pos.y, 0.12, 0.7, delay);
- springX = createSpring(lastPosX, pos.x, 0.12, 0.7, delay);
+ springY = createSpring(lastPosY, pos.y, .126, .85, delay);
+ springX = createSpring(lastPosX, pos.x, .126, .85, delay);
+ lastUpdateY = new Date().getTime();
+ lastUpdateX = new Date().getTime();
requestAnimationFrame(updateY);
requestAnimationFrame(updateX);
+ lastPosX = pos.x;
+ lastPosY = pos.y;
};
export const getInfo = () => {
@@ -109,7 +103,7 @@
{#if debugMode}
-
Line idx: {index}
+
Line idx: {index}, duration: {(line.end - line.start).toFixed(3)}
{/if}
{line.text}
diff --git a/src/lib/components/lyrics/newLyrics.svelte b/src/lib/components/lyrics/newLyrics.svelte
index 97b6349..d6611e4 100644
--- a/src/lib/components/lyrics/newLyrics.svelte
+++ b/src/lib/components/lyrics/newLyrics.svelte
@@ -3,7 +3,8 @@
import { onMount } from 'svelte';
import type { ScriptItem } from '$lib/lyrics/type';
import LyricLine from './lyricLine.svelte';
- import type { LyricPos } from './type';
+ import type { LyricLayout, LyricPos } from './type';
+ import createLyricsSearcher from '$lib/lyrics/lyricSearcher';
// Props
export let originalLyrics: LrcJsonData;
@@ -14,23 +15,28 @@
let lyricLines: ScriptItem[] = [];
let lyricExists = false;
let lyricsContainer: HTMLDivElement | null;
+ let lyricLayouts: LyricLayout[] = [];
let debugMode = false;
let showTranslation = false;
- // Exlpaination:
- // The hot module reloading makes the each lyric component position to be re-initialized,
- // which causes the lyrics are all at {x: 0, y: 0},
- // instead of the value calculated in the initLyricComponents.
- // So, we need to store the initial position of each lyric component and restore it.
- let lyricComponentInitPos = Array(lyricLines.length).fill({ x: 0, y: 0 });
-
// References to lyric elements
let lyricElements: HTMLDivElement[] = [];
let lyricComponents: LyricLine[] = [];
+ let lyricTopList: number[] = [];
+ let nextUpdate = 0;
+ const marginY = 48;
+
+ $: getLyricIndex = createLyricsSearcher(originalLyrics);
function initLyricComponents() {
+ initLyricTopList();
+ for (let i = 0; i < lyricComponents.length; i++) {
+ lyricComponents[i].init({ x: 0, y: lyricTopList[i] });
+ }
+ }
+
+ function initLyricTopList() {
let cumulativeHeight = 0;
- const marginY = 48;
for (let i = 0; i < lyricLines.length; i++) {
const c = lyricComponents[i];
lyricElements.push(c.getRef());
@@ -38,10 +44,37 @@
const elementHeight = e.getBoundingClientRect().height;
const elementTargetTop = cumulativeHeight;
cumulativeHeight += elementHeight + marginY;
- lyricComponentInitPos[i] = { x: 0, y: elementTargetTop };
+ lyricTopList.push(elementTargetTop);
}
}
+ function computeLayout(progress: number) {
+ if (!originalLyrics.scripts) return;
+ const currentLyricIndex = getLyricIndex(progress);
+ const currentLyricDuration =
+ originalLyrics.scripts[currentLyricIndex].end - originalLyrics.scripts[currentLyricIndex].start;
+ const relativeOrigin = lyricTopList[currentLyricIndex];
+ for (let i = 0; i < lyricElements.length; i++) {
+ const currentLyricComponent = lyricComponents[i];
+ lyricLayouts[i] = {
+ blur: 0,
+ scale: 1,
+ pos: {
+ y: lyricTopList[i] - relativeOrigin,
+ x: 0
+ }
+ };
+ let delay = 0;
+ if (i <= currentLyricIndex) {
+ delay = 0;
+ } else {
+ delay = 0.013 + Math.min(Math.min(currentLyricDuration, 0.3), 0.075 * (i - currentLyricIndex));
+ }
+ currentLyricComponent.update({ x: 0, y: lyricTopList[i] - relativeOrigin }, delay);
+ }
+ nextUpdate = originalLyrics.scripts[currentLyricIndex + 1].start;
+ }
+
$: {
if (originalLyrics && originalLyrics.scripts) {
lyricExists = true;
@@ -55,6 +88,12 @@
}
}
+ $: {
+ if (lyricsContainer && lyricComponents.length > 0) {
+ if (progress >= nextUpdate) computeLayout(progress);
+ }
+ }
+
onMount(() => {
// Initialize
if (localStorage.getItem('debugMode') == null) {
@@ -72,13 +111,7 @@
bind:this={lyricsContainer}
>
{#each lyricLines as lyric, i}
-
+
{/each}
diff --git a/src/lib/graphics/spring/spring.ts b/src/lib/graphics/spring/spring.ts
index 511bce5..bbbbf5f 100644
--- a/src/lib/graphics/spring/spring.ts
+++ b/src/lib/graphics/spring/spring.ts
@@ -1,158 +1,147 @@
-import { getVelocity } from "./derivative";
+import { getVelocity } from './derivative';
/** MIT License github.com/pushkine/ */
export interface SpringParams {
- mass: number; // = 1.0
- damping: number; // = 10.0
- stiffness: number; // = 100.0
- soft: boolean; // = false
+ mass: number; // = 1.0
+ damping: number; // = 10.0
+ stiffness: number; // = 100.0
+ soft: boolean; // = false
}
type seconds = number;
export class Spring {
- private currentPosition = 0;
- private targetPosition = 0;
- private currentTime = 0;
- private params: Partial = {};
- private currentSolver: (t: seconds) => number;
- private getV: (t: seconds) => number;
- private getV2: (t: seconds) => number;
- private queueParams:
- | (Partial & {
- time: number;
- })
- | undefined;
- private queuePosition:
- | {
- time: number;
- position: number;
- }
- | undefined;
- constructor(currentPosition = 0) {
- this.targetPosition = currentPosition;
- this.currentPosition = this.targetPosition;
- this.currentSolver = () => this.targetPosition;
- this.getV = () => 0;
- this.getV2 = () => 0;
- }
- private resetSolver() {
- const curV = this.getV(this.currentTime);
- this.currentTime = 0;
- this.currentSolver = solveSpring(
- this.currentPosition,
- curV,
- this.targetPosition,
- 0,
- this.params,
- );
- this.getV = getVelocity(this.currentSolver);
- this.getV2 = getVelocity(this.getV);
- }
- arrived() {
- return (
- Math.abs(this.targetPosition - this.currentPosition) < 0.01 &&
- this.getV(this.currentTime) < 0.01 &&
- this.getV2(this.currentTime) < 0.01 &&
- this.queueParams === undefined &&
- this.queuePosition === undefined
- );
- }
- setPosition(targetPosition: number) {
- this.targetPosition = targetPosition;
- this.currentPosition = targetPosition;
- this.currentSolver = () => this.targetPosition;
- this.getV = () => 0;
- this.getV2 = () => 0;
- }
- update(delta = 0) {
- this.currentTime += delta;
- this.currentPosition = this.currentSolver(this.currentTime);
- if (this.queueParams) {
- this.queueParams.time -= delta;
- if (this.queueParams.time <= 0) {
- this.updateParams({
- ...this.queueParams,
- });
- }
- }
- if (this.queuePosition) {
- this.queuePosition.time -= delta;
- if (this.queuePosition.time <= 0) {
- this.setTargetPosition(this.queuePosition.position);
- }
- }
- if (this.arrived()) {
- this.setPosition(this.targetPosition);
- }
- }
- updateParams(params: Partial, delay = 0) {
- if (delay > 0) {
- this.queueParams = {
- ...(this.queuePosition ?? {}),
- ...params,
- time: delay,
- };
- } else {
- this.queuePosition = undefined;
- this.params = {
- ...this.params,
- ...params,
- };
- this.resetSolver();
- }
- }
- setTargetPosition(targetPosition: number, delay = 0) {
- if (delay > 0) {
- this.queuePosition = {
- ...(this.queuePosition ?? {}),
- position: targetPosition,
- time: delay,
- };
- } else {
- this.queuePosition = undefined;
- this.targetPosition = targetPosition;
- this.resetSolver();
- }
- }
- getCurrentPosition() {
- return this.currentPosition;
- }
+ private currentPosition = 0;
+ private targetPosition = 0;
+ private currentTime = 0;
+ private params: Partial = {};
+ private currentSolver: (t: seconds) => number;
+ private getV: (t: seconds) => number;
+ private getV2: (t: seconds) => number;
+ private queueParams:
+ | (Partial & {
+ time: number;
+ })
+ | undefined;
+ private queuePosition:
+ | {
+ time: number;
+ position: number;
+ }
+ | undefined;
+ constructor(currentPosition = 0) {
+ this.targetPosition = currentPosition;
+ this.currentPosition = this.targetPosition;
+ this.currentSolver = () => this.targetPosition;
+ this.getV = () => 0;
+ this.getV2 = () => 0;
+ }
+ private resetSolver() {
+ const curV = this.getV(this.currentTime);
+ this.currentTime = 0;
+ this.currentSolver = solveSpring(this.currentPosition, curV, this.targetPosition, 0, this.params);
+ this.getV = getVelocity(this.currentSolver);
+ this.getV2 = getVelocity(this.getV);
+ }
+ arrived() {
+ return (
+ Math.abs(this.targetPosition - this.currentPosition) < 0.01 &&
+ this.getV(this.currentTime) < 0.01 &&
+ this.getV2(this.currentTime) < 0.01 &&
+ this.queueParams === undefined &&
+ this.queuePosition === undefined
+ );
+ }
+ setPosition(targetPosition: number) {
+ this.targetPosition = targetPosition;
+ this.currentPosition = targetPosition;
+ this.currentSolver = () => this.targetPosition;
+ this.getV = () => 0;
+ this.getV2 = () => 0;
+ }
+ update(delta = 0) {
+ this.currentTime += delta;
+ this.currentPosition = this.currentSolver(this.currentTime);
+ if (this.queueParams) {
+ this.queueParams.time -= delta;
+ if (this.queueParams.time <= 0) {
+ this.updateParams({
+ ...this.queueParams
+ });
+ }
+ }
+ if (this.queuePosition) {
+ this.queuePosition.time -= delta;
+ if (this.queuePosition.time <= 0) {
+ this.setTargetPosition(this.queuePosition.position);
+ }
+ }
+ if (this.arrived()) {
+ this.setPosition(this.targetPosition);
+ }
+ }
+ updateParams(params: Partial, delay = 0) {
+ if (delay > 0) {
+ this.queueParams = {
+ ...(this.queuePosition ?? {}),
+ ...params,
+ time: delay
+ };
+ } else {
+ this.queuePosition = undefined;
+ this.params = {
+ ...this.params,
+ ...params
+ };
+ this.resetSolver();
+ }
+ }
+ setTargetPosition(targetPosition: number, delay = 0) {
+ if (delay > 0) {
+ this.queuePosition = {
+ ...(this.queuePosition ?? {}),
+ position: targetPosition,
+ time: delay
+ };
+ } else {
+ this.queuePosition = undefined;
+ this.targetPosition = targetPosition;
+ this.resetSolver();
+ }
+ }
+ getCurrentPosition() {
+ return this.currentPosition;
+ }
}
function solveSpring(
- from: number,
- velocity: number,
- to: number,
- delay: seconds = 0,
- params?: Partial,
+ from: number,
+ velocity: number,
+ to: number,
+ delay: seconds = 0,
+ params?: Partial
): (t: seconds) => number {
- const soft = params?.soft ?? false;
- const stiffness = params?.stiffness ?? 100;
- const damping = params?.damping ?? 10;
- const mass = params?.mass ?? 1;
- const delta = to - from;
- if (soft || 1.0 <= damping / (2.0 * Math.sqrt(stiffness * mass))) {
- const angular_frequency = -Math.sqrt(stiffness / mass);
- const leftover = -angular_frequency * delta - velocity;
- return (t: seconds) => {
- t -= delay;
- if (t < 0) return from;
- return to - (delta + t * leftover) * Math.E ** (t * angular_frequency);
- };
- }
- const damping_frequency = Math.sqrt(4.0 * mass * stiffness - damping ** 2.0);
- const leftover =
- (damping * delta - 2.0 * mass * velocity) / damping_frequency;
- const dfm = (0.5 * damping_frequency) / mass;
- const dm = -(0.5 * damping) / mass;
- return (t: seconds) => {
- t -= delay;
- if (t < 0) return from;
- return (
- to -
- (Math.cos(t * dfm) * delta + Math.sin(t * dfm) * leftover) *
- Math.E ** (t * dm)
- );
- };
+ const soft = params?.soft ?? false;
+ const stiffness = params?.stiffness ?? 100;
+ const damping = params?.damping ?? 10;
+ const mass = params?.mass ?? 1;
+ const delta = to - from;
+ if (soft || 1.0 <= damping / (2.0 * Math.sqrt(stiffness * mass))) {
+ const angular_frequency = -Math.sqrt(stiffness / mass);
+ const leftover = -angular_frequency * delta - velocity;
+ return (t: seconds) => {
+ t -= delay;
+ if (t < 0) return from;
+ return to - (delta + t * leftover) * Math.E ** (t * angular_frequency);
+ };
+ }
+ const damping_frequency = Math.sqrt(4.0 * mass * stiffness - damping ** 2.0);
+ const leftover = (damping * delta - 2.0 * mass * velocity) / damping_frequency;
+ const dfm = (0.5 * damping_frequency) / mass;
+ const dm = -(0.5 * damping) / mass;
+ return (t: seconds) => {
+ t -= delay;
+ if (t < 0) return from;
+ return to - (Math.cos(t * dfm) * delta + Math.sin(t * dfm) * leftover) * Math.E ** (t * dm);
+ };
}
diff --git a/src/routes/play/[id]/+page.svelte b/src/routes/play/[id]/+page.svelte
index 96be32f..fae5122 100644
--- a/src/routes/play/[id]/+page.svelte
+++ b/src/routes/play/[id]/+page.svelte
@@ -214,7 +214,7 @@
-
+