Skip to content

perf: draw vu meter and timeline ruler on canvases#44

Open
PlkMarudny wants to merge 1 commit into
ronak-create:mainfrom
PlkMarudny:vu-on-canvas
Open

perf: draw vu meter and timeline ruler on canvases#44
PlkMarudny wants to merge 1 commit into
ronak-create:mainfrom
PlkMarudny:vu-on-canvas

Conversation

@PlkMarudny

@PlkMarudny PlkMarudny commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

VU meter drawing was potentially affecting performance (16 divs per a channel), so in this PR it is being drawn on a canvas - a separate canvas per each channel, as the number of channels is not fixed as per #41 The number of actual draw operations was also reduced, redrawing is called only when lit/hold changes.
Also, timeline ruler is drawn on a canvas to reduce the DOM size. Additionally, the logic was moved to a worker (but there is a fallback to use the main thread in case worker is not available).

Type of change

  • Bug fix
  • New feature (transition / preset / text anim / effect / API)
  • Docs
  • Refactor / internal

How was it verified?

  • node --check server.js && node --check app.js && node --check mcp-server.js passes
  • Opened the editor and confirmed the change in preview
  • Confirmed the change in an export (fast or realtime), if it affects rendering
  • Updated CLAUDE.md / README.md if the schema, props, or API changed

Checklist

  • No new runtime dependencies added
  • Preview and export render identically (single compositor)
  • Commits are focused and messages are descriptive

Summary by CodeRabbit

  • Performance

    • Improved playback and export progress responsiveness.
    • Reduced unnecessary preview processing during playback.
    • Optimized timeline ruler rendering using background processing when supported.
  • Visual Improvements

    • Updated audio level meters for smoother, more efficient rendering.
    • Preserved timeline ruler visuals with a fallback for unsupported browsers.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Playback timing now reuses project duration calculations, ruler drawing can run in a dedicated OffscreenCanvas worker, audio meters render through canvases, and preview property evaluation is limited to the active playhead clip.

Changes

Playback and rendering updates

Layer / File(s) Summary
Playback duration caching
app.js
playStopAt accepts a precomputed duration, while the main loop reuses that value for playback limits and export progress.
Worker-based ruler rendering
app.js, ruler-worker.js
Ruler inputs are sent to an OffscreenCanvas worker when supported, with the existing main-thread drawing path retained as a fallback.
Canvas audio meters
app.js, style.css
Each meter channel uses one canvas for ladder rendering, tracks its previous lit and hold state, and removes the former segment-element styling.
Preview property evaluation
app.js
During playback, evalProps runs only for the clip active under the playhead, and clones c.props without merging defaults.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Timeline
  participant app.js
  participant ruler-worker.js
  participant OffscreenCanvas
  Timeline->>app.js: request ruler update
  app.js->>ruler-worker.js: send scroll, scale, markers, work area, time, and fps
  ruler-worker.js->>OffscreenCanvas: render ruler ticks, labels, markers, and playhead
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: moving VU meter and timeline ruler rendering to canvases for performance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ruler-worker.js`:
- Around line 95-99: Update ensureRulerWorker and the ruler-worker init/draw
protocol so worker activation is confirmed only after successful canvas transfer
and 2D context initialization. Add explicit init acknowledgment/error signaling,
defer or discard worker-only draw messages until readiness, and clear or
recreate rulerWorker on startup or runtime errors so fallback rendering remains
available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 885c4ad2-432e-4069-bffd-bac3d817cd09

📥 Commits

Reviewing files that changed from the base of the PR and between 8aac11f and cce0389.

📒 Files selected for processing (3)
  • app.js
  • ruler-worker.js
  • style.css

Comment thread ruler-worker.js
Comment on lines +95 to +99
if (msg.type === "init") {
cv = msg.canvas;
g = cv.getContext("2d");
} else if (msg.type === "draw") {
draw(msg);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'transferControlToOffscreen|new Worker|rulerWorker\s*=|onerror|onmessage|getContext\("2d"\)' app.js ruler-worker.js

Repository: ronak-create/FableCut

Length of output: 7170


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app.js ruler section =="
sed -n '1585,1705p' app.js | nl -ba -v1585

echo
echo "== ruler-worker.js =="
sed -n '1,130p' ruler-worker.js | nl -ba -v1

echo
echo "== current git diff stat and ruler app.js/workers changes =="
git diff --stat -- app.js ruler-worker.js || true
git diff -- app.js ruler-worker.js | sed -n '1,260p' || true

echo
echo "== all ruler-related occurrences =="
rg -n -C2 'drawRuler|ensureRulerWorker|rulerWorker|ruler|OffscreenCanvas|transferControlToOffscreen' app.js ruler-worker.js

Repository: ronak-create/FableCut

Length of output: 226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app.js ruler section =="
sed -n '1585,1705p' app.js | cat -n | sed 's#^`#app.js`:'

echo
echo "== ruler-worker.js =="
sed -n '1,145p' ruler-worker.js | cat -n | sed 's#^`#ruler-worker.js`:'

echo
echo "== current git diff stat and ruler app.js/workers changes =="
git diff --stat -- app.js ruler-worker.js || true
git diff -- app.js ruler-worker.js | cat -n | sed 's#^`#diff`:' || true

echo
echo "== all ruler-related occurrences =="
rg -n -C3 'drawRuler|ensureRulerWorker|rulerWorker|ruler|OffscreenCanvas|transferControlToOffscreen' app.js ruler-worker.js

Repository: ronak-create/FableCut

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app.js ruler section =="
sed -n '1585,1705p' app.js

echo
echo "== ruler-worker.js =="
sed -n '1,145p' ruler-worker.js

echo
echo "== current git diff stat and ruler app.js/workers changes =="
git diff --stat -- app.js ruler-worker.js || true
git diff -- app.js ruler-worker.js || true

echo
echo "== all ruler-related occurrences =="
rg -n -C3 'drawRuler|ensureRulerWorker|rulerWorker|ruler|OffscreenCanvas|transferControlToOffscreen' app.js ruler-worker.js

Repository: ronak-create/FableCut

Length of output: 14459


Add worker readiness/error signaling before disabling the fallback.

ensureRulerWorker() treats rulerWorker as active immediately after creating the worker and posting the transferred canvas. If the worker fails to load, or cv.getContext("2d") fails inside ruler-worker.js, g remains null, draw messages are dropped, and the fallback can no longer render because the main canvas was already transferred. Add init acknowledgment or error signaling, queue/drop worker-only draws until the worker is ready, and remove/marked-recreate the worker when an error occurs before falling back.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ruler-worker.js` around lines 95 - 99, Update ensureRulerWorker and the
ruler-worker init/draw protocol so worker activation is confirmed only after
successful canvas transfer and 2D context initialization. Add explicit init
acknowledgment/error signaling, defer or discard worker-only draw messages until
readiness, and clear or recreate rulerWorker on startup or runtime errors so
fallback rendering remains available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant