@subvo/renderer 1.2.2 → 1.2.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.
@@ -18,6 +18,7 @@ type RenderLine = {
18
18
  startMs: number;
19
19
  endMs: number;
20
20
  mode?: RenderMode;
21
+ motion?: RenderMotion;
21
22
  breakHints?: string[];
22
23
  words: RenderWord[];
23
24
  };
@@ -39,7 +40,13 @@ type RenderMetadata = {
39
40
  };
40
41
  type RenderPosition = "top_safe" | "center" | "bottom_safe";
41
42
  type RenderPreset = "fire" | "clean" | "luxury" | "pop" | "ghost";
42
- type RenderMode = "phrase_highlight" | "progressive_build";
43
+ type RenderMode = "karaoke" | "phrase_highlight" | "static" | "progressive_build";
44
+ type RenderMotion = {
45
+ policy?: string;
46
+ resolvedMode?: RenderMode;
47
+ reason?: string;
48
+ metrics?: Record<string, unknown>;
49
+ };
43
50
  type RenderStyle = {
44
51
  fontFamily: string;
45
52
  fontSize: number;
@@ -70,4 +77,4 @@ declare function renderFrame(ctx: CanvasRenderingContext2D, renderJob: RenderJob
70
77
 
71
78
  declare const VERSION: string;
72
79
 
73
- export { type RenderJob, type RenderLayoutHints, type RenderLine, type RenderMetadata, type RenderPosition, type RenderPreset, type RenderStyle, type RenderWord, type SchemaVersion, VERSION, renderFrame };
80
+ export { type RenderJob, type RenderLayoutHints, type RenderLine, type RenderMetadata, type RenderMotion, type RenderPosition, type RenderPreset, type RenderStyle, type RenderWord, type SchemaVersion, VERSION, renderFrame };
@@ -349,6 +349,7 @@ var BOTTOM_UNSAFE_RATIO = 0.24;
349
349
  var TOP_SAFE_ANCHOR_RATIO = 0.22;
350
350
  var CENTER_ANCHOR_RATIO = 0.5;
351
351
  var BOTTOM_SAFE_ANCHOR_RATIO = 0.68;
352
+ var PHRASE_HIGHLIGHT_MS = 180;
352
353
  function renderFrame(ctx, renderJob, timeMs) {
353
354
  var _a;
354
355
  const { playRes, style } = renderJob;
@@ -394,14 +395,15 @@ function renderFrame(ctx, renderJob, timeMs) {
394
395
  let x = centerX - row.width / 2;
395
396
  const rowY = baselineY + rowIndex * lineStep;
396
397
  for (const { word, text, width } of row.words) {
398
+ const motionMode = resolveLineMotionMode(entry.line);
397
399
  const visibleWord = isWordVisible(entry.line, word, timeMs);
398
400
  if (visibleWord) {
399
- const activeWord = timeMs >= word.startMs && timeMs <= word.endMs;
400
- const emphasis = (_a = word.emphasis) != null ? _a : 0;
401
- const progress = activeWord ? computeWordProgress(word, timeMs) : 0;
402
- const impactProgress = activeWord ? computeImpactProgress(word, timeMs) : 0;
403
- const pulseScale = computePulseScale(style, progress);
404
- const bounceScale = shouldBounceWord(style, emphasis) ? computeImpactScale(impactProgress) : 1;
401
+ const activeWord = isWordActive(entry.line, word, timeMs, motionMode);
402
+ const emphasis = motionMode === "static" ? 0 : (_a = word.emphasis) != null ? _a : 0;
403
+ const progress = activeWord ? computeMotionProgress(entry.line, word, timeMs, motionMode) : 0;
404
+ const impactProgress = activeWord && motionMode === "karaoke" ? computeImpactProgress(word, timeMs) : 0;
405
+ const pulseScale = motionMode === "static" ? 1 : computePulseScale(style, progress);
406
+ const bounceScale = shouldBounceWord(style, emphasis, motionMode) ? computeImpactScale(impactProgress) : 1;
405
407
  const scaleFactor = pulseScale * bounceScale;
406
408
  drawWord(
407
409
  ctx,
@@ -446,7 +448,36 @@ function isWordVisible(line, word, timeMs) {
446
448
  }
447
449
  return timeMs >= word.startMs;
448
450
  }
449
- function shouldBounceWord(style, emphasis) {
451
+ function resolveLineMotionMode(line) {
452
+ var _a;
453
+ const motionMode = (_a = line.motion) == null ? void 0 : _a.resolvedMode;
454
+ if (motionMode === "karaoke" || motionMode === "phrase_highlight" || motionMode === "static") {
455
+ return motionMode;
456
+ }
457
+ if (line.mode === "static" || line.mode === "karaoke" || line.mode === "progressive_build") {
458
+ return line.mode;
459
+ }
460
+ return "phrase_highlight";
461
+ }
462
+ function isWordActive(line, word, timeMs, motionMode) {
463
+ if (motionMode === "static") {
464
+ return false;
465
+ }
466
+ if (motionMode === "phrase_highlight") {
467
+ return timeMs >= line.startMs && timeMs <= line.startMs + PHRASE_HIGHLIGHT_MS;
468
+ }
469
+ return timeMs >= word.startMs && timeMs <= word.endMs;
470
+ }
471
+ function computeMotionProgress(line, word, timeMs, motionMode) {
472
+ if (motionMode === "phrase_highlight") {
473
+ return clamp((timeMs - line.startMs) / PHRASE_HIGHLIGHT_MS);
474
+ }
475
+ return computeWordProgress(word, timeMs);
476
+ }
477
+ function shouldBounceWord(style, emphasis, motionMode) {
478
+ if (motionMode !== "karaoke") {
479
+ return false;
480
+ }
450
481
  if (style.preset === "ghost") {
451
482
  return false;
452
483
  }
@@ -376,6 +376,7 @@ var SubvoRenderer = (() => {
376
376
  var TOP_SAFE_ANCHOR_RATIO = 0.22;
377
377
  var CENTER_ANCHOR_RATIO = 0.5;
378
378
  var BOTTOM_SAFE_ANCHOR_RATIO = 0.68;
379
+ var PHRASE_HIGHLIGHT_MS = 180;
379
380
  function renderFrame(ctx, renderJob, timeMs) {
380
381
  var _a;
381
382
  const { playRes, style } = renderJob;
@@ -421,14 +422,15 @@ var SubvoRenderer = (() => {
421
422
  let x = centerX - row.width / 2;
422
423
  const rowY = baselineY + rowIndex * lineStep;
423
424
  for (const { word, text, width } of row.words) {
425
+ const motionMode = resolveLineMotionMode(entry.line);
424
426
  const visibleWord = isWordVisible(entry.line, word, timeMs);
425
427
  if (visibleWord) {
426
- const activeWord = timeMs >= word.startMs && timeMs <= word.endMs;
427
- const emphasis = (_a = word.emphasis) != null ? _a : 0;
428
- const progress = activeWord ? computeWordProgress(word, timeMs) : 0;
429
- const impactProgress = activeWord ? computeImpactProgress(word, timeMs) : 0;
430
- const pulseScale = computePulseScale(style, progress);
431
- const bounceScale = shouldBounceWord(style, emphasis) ? computeImpactScale(impactProgress) : 1;
428
+ const activeWord = isWordActive(entry.line, word, timeMs, motionMode);
429
+ const emphasis = motionMode === "static" ? 0 : (_a = word.emphasis) != null ? _a : 0;
430
+ const progress = activeWord ? computeMotionProgress(entry.line, word, timeMs, motionMode) : 0;
431
+ const impactProgress = activeWord && motionMode === "karaoke" ? computeImpactProgress(word, timeMs) : 0;
432
+ const pulseScale = motionMode === "static" ? 1 : computePulseScale(style, progress);
433
+ const bounceScale = shouldBounceWord(style, emphasis, motionMode) ? computeImpactScale(impactProgress) : 1;
432
434
  const scaleFactor = pulseScale * bounceScale;
433
435
  drawWord(
434
436
  ctx,
@@ -473,7 +475,36 @@ var SubvoRenderer = (() => {
473
475
  }
474
476
  return timeMs >= word.startMs;
475
477
  }
476
- function shouldBounceWord(style, emphasis) {
478
+ function resolveLineMotionMode(line) {
479
+ var _a;
480
+ const motionMode = (_a = line.motion) == null ? void 0 : _a.resolvedMode;
481
+ if (motionMode === "karaoke" || motionMode === "phrase_highlight" || motionMode === "static") {
482
+ return motionMode;
483
+ }
484
+ if (line.mode === "static" || line.mode === "karaoke" || line.mode === "progressive_build") {
485
+ return line.mode;
486
+ }
487
+ return "phrase_highlight";
488
+ }
489
+ function isWordActive(line, word, timeMs, motionMode) {
490
+ if (motionMode === "static") {
491
+ return false;
492
+ }
493
+ if (motionMode === "phrase_highlight") {
494
+ return timeMs >= line.startMs && timeMs <= line.startMs + PHRASE_HIGHLIGHT_MS;
495
+ }
496
+ return timeMs >= word.startMs && timeMs <= word.endMs;
497
+ }
498
+ function computeMotionProgress(line, word, timeMs, motionMode) {
499
+ if (motionMode === "phrase_highlight") {
500
+ return clamp((timeMs - line.startMs) / PHRASE_HIGHLIGHT_MS);
501
+ }
502
+ return computeWordProgress(word, timeMs);
503
+ }
504
+ function shouldBounceWord(style, emphasis, motionMode) {
505
+ if (motionMode !== "karaoke") {
506
+ return false;
507
+ }
477
508
  if (style.preset === "ghost") {
478
509
  return false;
479
510
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subvo/renderer",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "main": "./dist/renderer.esm.js",
6
6
  "module": "./dist/renderer.esm.js",