@braccato/core 1.15.1 → 1.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +38 -5
- package/dist/stage.d.ts +3 -1
- package/dist/stage.js +11 -3
- package/dist/view.js +6 -1
- package/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -72,6 +72,8 @@ export interface AnimationEngineInstance extends AnimEngineViewState {
|
|
|
72
72
|
animationTimingVisibilityLogUntil: number;
|
|
73
73
|
layout: LyricsLayout;
|
|
74
74
|
stageKey: string;
|
|
75
|
+
stageRemeasured: boolean;
|
|
76
|
+
stageHeight: number;
|
|
75
77
|
stageBox: StageBox | null;
|
|
76
78
|
stageMetrics: Map<HTMLElement, StageMetrics>;
|
|
77
79
|
stageY: Map<HTMLElement, number>;
|
package/dist/engine.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// that never meet. `themeSettings.ts` holds the third thing under that rule.
|
|
19
19
|
import { ANIMATING_CLASS, CREDITS_CLASS, CURRENT_LYRICS_CLASS, FOOTER_CLASS, LINE_CLASS, PAUSED_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, USER_SCROLLING_CLASS, WORD_STATE_ACTIVE, WORD_STATE_ATTR, WORD_STATE_PAST, WORD_STATE_UPCOMING, } from "./constants.js";
|
|
20
20
|
import { INSTRUMENTAL_WAVE_PATH_HIGH, INSTRUMENTAL_WAVE_PATH_LOW } from "./instrumental.js";
|
|
21
|
-
import { layoutStage, planStage } from "./stage.js";
|
|
21
|
+
import { layoutStage, planStage, queuedStageY, } from "./stage.js";
|
|
22
22
|
import { registerThemeSetting } from "./themeSettings.js";
|
|
23
23
|
import { clamp, getRelativeLayoutBounds, positiveModulo, roundedMs, toMs } from "./util.js";
|
|
24
24
|
const NO_LYRICS_ELEMENT_LOG = "No lyrics element found on the page, skipping lyrics injection";
|
|
@@ -156,6 +156,8 @@ export function createAnimationEngineInstance(engineDocument, engineWindow, host
|
|
|
156
156
|
animationTimingVisibilityLogUntil: 0,
|
|
157
157
|
layout,
|
|
158
158
|
stageKey: "",
|
|
159
|
+
stageRemeasured: false,
|
|
160
|
+
stageHeight: 0,
|
|
159
161
|
stageBox: null,
|
|
160
162
|
stageMetrics: new Map(),
|
|
161
163
|
stageY: new Map(),
|
|
@@ -316,6 +318,7 @@ function resetStage(engine) {
|
|
|
316
318
|
engine.stageMetrics.clear();
|
|
317
319
|
engine.stageY.clear();
|
|
318
320
|
engine.stageKey = "";
|
|
321
|
+
engine.stageHeight = 0;
|
|
319
322
|
if (engine.stageBox !== null) {
|
|
320
323
|
engine.stageBox = null;
|
|
321
324
|
engine.host.onStageLayout?.(null);
|
|
@@ -1957,7 +1960,11 @@ function stageElements(engine) {
|
|
|
1957
1960
|
lastEnd = Math.max(lastEnd, end);
|
|
1958
1961
|
elements.push(line.lyricElement);
|
|
1959
1962
|
items.push({
|
|
1960
|
-
kind: line.lyricElement.dataset.instrumental === "true"
|
|
1963
|
+
kind: line.lyricElement.dataset.instrumental === "true"
|
|
1964
|
+
? "instrumental"
|
|
1965
|
+
: line.lyricElement.dataset.blank === "true"
|
|
1966
|
+
? "blank"
|
|
1967
|
+
: "line",
|
|
1961
1968
|
start: line.time,
|
|
1962
1969
|
end,
|
|
1963
1970
|
});
|
|
@@ -1992,12 +1999,18 @@ export function measureStage(engine) {
|
|
|
1992
1999
|
});
|
|
1993
2000
|
}
|
|
1994
2001
|
engine.stageKey = "";
|
|
2002
|
+
engine.stageRemeasured = true;
|
|
1995
2003
|
}
|
|
1996
2004
|
function currentTranslateY(engine, element, fallback) {
|
|
1997
2005
|
const parts = engine.window.getComputedStyle(element).translate.split(" ");
|
|
1998
2006
|
const y = Number.parseFloat(parts[1] ?? "");
|
|
1999
2007
|
return Number.isFinite(y) ? y : fallback;
|
|
2000
2008
|
}
|
|
2009
|
+
function snapStageElement(engine, element, y) {
|
|
2010
|
+
engine.stageMoves.get(element)?.cancel();
|
|
2011
|
+
engine.stageMoves.set(element, element.animate([{ translate: `0 ${y}px` }], { duration: 0, fill: "forwards" }));
|
|
2012
|
+
engine.stageY.set(element, y);
|
|
2013
|
+
}
|
|
2001
2014
|
function placeStageElement(engine, { element, placement, wasVisible, fromY, fromOpacity }, instant, reduced) {
|
|
2002
2015
|
engine.stageMoves.get(element)?.cancel();
|
|
2003
2016
|
engine.stageFades.get(element)?.cancel();
|
|
@@ -2075,12 +2088,21 @@ function applyStage(engine, timeS, instant) {
|
|
|
2075
2088
|
if (key === engine.stageKey && !instant)
|
|
2076
2089
|
return;
|
|
2077
2090
|
engine.stageKey = key;
|
|
2091
|
+
const remeasured = engine.stageRemeasured;
|
|
2092
|
+
engine.stageRemeasured = false;
|
|
2078
2093
|
const metrics = elements.map(element => engine.stageMetrics.get(element) ?? { height: 0, left: 0, width: 0, originX: 0 });
|
|
2079
|
-
const
|
|
2094
|
+
const geometry = {
|
|
2080
2095
|
stageHeight: container.clientHeight,
|
|
2081
2096
|
gap: engine.stageFontSize * STAGE_GAP_EM,
|
|
2082
2097
|
activeScale: getCSSNumber(engine, container, "--blyrics-active-scale", 1),
|
|
2083
|
-
}
|
|
2098
|
+
};
|
|
2099
|
+
// Lines sit on the floor, so a resize moves every place by how far the floor moved.
|
|
2100
|
+
const floorShift = remeasured && engine.stageHeight > 0 ? geometry.stageHeight - engine.stageHeight : 0;
|
|
2101
|
+
engine.stageHeight = geometry.stageHeight;
|
|
2102
|
+
const { placements, box } = layoutStage(roles, items, metrics, elements.map(element => {
|
|
2103
|
+
const y = engine.stageY.get(element);
|
|
2104
|
+
return y === undefined ? null : y + floorShift;
|
|
2105
|
+
}), geometry);
|
|
2084
2106
|
// Only the elements whose place changed move. A line that stays where it is keeps its running
|
|
2085
2107
|
// animations, and one already hidden stays hidden without being animated again.
|
|
2086
2108
|
const moves = [];
|
|
@@ -2089,13 +2111,24 @@ function applyStage(engine, timeS, instant) {
|
|
|
2089
2111
|
const previousY = engine.stageY.get(element);
|
|
2090
2112
|
const wasVisible = element.dataset.stageRole !== undefined && isStageRoleVisible(element.dataset.stageRole);
|
|
2091
2113
|
const unchanged = previousY !== undefined && wasVisible === placement.visible && (!placement.visible || previousY === placement.y);
|
|
2114
|
+
// After a resize a line still on screen, fading out included, jumps to its new place and keeps
|
|
2115
|
+
// the fade and blur it was in the middle of.
|
|
2116
|
+
const onScreen = placement.visible || element.dataset.stageVisible !== undefined;
|
|
2117
|
+
if (remeasured && !instant && onScreen && element.dataset.stageRole === roles[index]) {
|
|
2118
|
+
if (previousY !== placement.y)
|
|
2119
|
+
snapStageElement(engine, element, placement.y);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2092
2122
|
if (unchanged && !instant)
|
|
2093
2123
|
return;
|
|
2094
2124
|
moves.push({
|
|
2095
2125
|
element,
|
|
2096
2126
|
placement,
|
|
2097
2127
|
wasVisible,
|
|
2098
|
-
|
|
2128
|
+
// A queued line rises from below the floor as it is now, never from where an older size put it.
|
|
2129
|
+
fromY: element.dataset.stageRole === "queued" && placement.visible
|
|
2130
|
+
? queuedStageY(geometry.stageHeight, metrics[index].height, geometry.gap)
|
|
2131
|
+
: currentTranslateY(engine, element, previousY ?? placement.y),
|
|
2099
2132
|
fromOpacity: Number(engine.window.getComputedStyle(element).opacity) || 0,
|
|
2100
2133
|
});
|
|
2101
2134
|
});
|
package/dist/stage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type StageItemKind = "line" | "instrumental" | "credits";
|
|
1
|
+
export type StageItemKind = "line" | "blank" | "instrumental" | "credits";
|
|
2
2
|
export type StageRole = "current" | "previous" | "queued" | "gone";
|
|
3
3
|
export interface StageItem {
|
|
4
4
|
kind: StageItemKind;
|
|
@@ -35,6 +35,8 @@ export interface StageBox {
|
|
|
35
35
|
width: number;
|
|
36
36
|
height: number;
|
|
37
37
|
}
|
|
38
|
+
/** Where a line waits below the floor, and where it rises from as it enters. */
|
|
39
|
+
export declare function queuedStageY(stageHeight: number, height: number, gap: number): number;
|
|
38
40
|
export declare function layoutStage(roles: readonly StageRole[], items: readonly StageItem[], metrics: readonly StageMetrics[], previousY: readonly (number | null)[], geometry: StageGeometry): {
|
|
39
41
|
placements: StagePlacement[];
|
|
40
42
|
box: StageBox | null;
|
package/dist/stage.js
CHANGED
|
@@ -3,7 +3,10 @@ export const STAGE_HANDOFF_S = 0.12;
|
|
|
3
3
|
export const STAGE_OVERLAP_S = 0.15;
|
|
4
4
|
export function overlapsPrevious(items, index) {
|
|
5
5
|
const previous = items[index - 1];
|
|
6
|
-
return previous !== undefined &&
|
|
6
|
+
return (previous !== undefined &&
|
|
7
|
+
previous.kind === "line" &&
|
|
8
|
+
items[index].kind !== "blank" &&
|
|
9
|
+
items[index].start < previous.end - STAGE_OVERLAP_S);
|
|
7
10
|
}
|
|
8
11
|
export function stageEnterTimes(items) {
|
|
9
12
|
return items.map((item, index) => {
|
|
@@ -45,6 +48,10 @@ function unionBox(box, next) {
|
|
|
45
48
|
height: Math.max(box.y + box.height, next.y + next.height) - y,
|
|
46
49
|
};
|
|
47
50
|
}
|
|
51
|
+
/** Where a line waits below the floor, and where it rises from as it enters. */
|
|
52
|
+
export function queuedStageY(stageHeight, height, gap) {
|
|
53
|
+
return stageHeight - height + gap;
|
|
54
|
+
}
|
|
48
55
|
export function layoutStage(roles, items, metrics, previousY, geometry) {
|
|
49
56
|
const { stageHeight, gap } = geometry;
|
|
50
57
|
const current = roles.indexOf("current");
|
|
@@ -57,14 +64,15 @@ export function layoutStage(roles, items, metrics, previousY, geometry) {
|
|
|
57
64
|
case "previous":
|
|
58
65
|
return { y: currentY - gap - height, visible: true };
|
|
59
66
|
case "queued":
|
|
60
|
-
return { y: stageHeight
|
|
67
|
+
return { y: queuedStageY(stageHeight, height, gap), visible: false };
|
|
61
68
|
case "gone":
|
|
62
69
|
return { y: previousY[index] ?? currentY, visible: false };
|
|
63
70
|
}
|
|
64
71
|
});
|
|
65
72
|
let box = null;
|
|
66
73
|
roles.forEach((role, index) => {
|
|
67
|
-
|
|
74
|
+
const { kind } = items[index];
|
|
75
|
+
if ((role !== "current" && role !== "previous") || kind === "instrumental" || kind === "blank")
|
|
68
76
|
return;
|
|
69
77
|
const { height, left, width, originX } = metrics[index];
|
|
70
78
|
const scale = geometry.activeScale;
|
package/dist/view.js
CHANGED
|
@@ -95,6 +95,8 @@ export function setLyrics(engine, mount, lyrics, options) {
|
|
|
95
95
|
}
|
|
96
96
|
else {
|
|
97
97
|
buildSungLine(doc, lyricElement, lyricItem, line);
|
|
98
|
+
if (!lyricItem.words?.trim())
|
|
99
|
+
lyricElement.dataset.blank = "true";
|
|
98
100
|
}
|
|
99
101
|
container.appendChild(lyricElement);
|
|
100
102
|
}
|
|
@@ -108,7 +110,10 @@ export function setLyrics(engine, mount, lyrics, options) {
|
|
|
108
110
|
container.dataset.sync = syncType;
|
|
109
111
|
if (engine.layout === "stage") {
|
|
110
112
|
container.dataset.layout = "stage";
|
|
111
|
-
|
|
113
|
+
// Sides only when both are sung, so a song with one singer stays centred whichever voice it is.
|
|
114
|
+
const sung = lyrics.filter(item => !item.isInstrumental && item.words?.trim() && item.agent !== "v1000");
|
|
115
|
+
const onRight = (item) => item.agent === "v2" || item.agent === "v3";
|
|
116
|
+
if (sung.some(onRight) && sung.some(item => !onRight(item))) {
|
|
112
117
|
container.dataset.stageDuet = "";
|
|
113
118
|
}
|
|
114
119
|
}
|