Skip to content

feat(editor): add waveform, audio mastering, and webcam crop - #344

Open
vitaligusatinsky wants to merge 2 commits into
getopenscreen:mainfrom
vitaligusatinsky:agent/editor-audio-crop-waveform
Open

feat(editor): add waveform, audio mastering, and webcam crop#344
vitaligusatinsky wants to merge 2 commits into
getopenscreen:mainfrom
vitaligusatinsky:agent/editor-audio-crop-waveform

Conversation

@vitaligusatinsky

@vitaligusatinsky vitaligusatinsky commented Aug 11, 2026

Copy link
Copy Markdown

What changed

  • Draw the recording's audio waveform directly in the editor timeline.
  • Add a signed ±500 ms audio sync control and ±12 dB output trim.
  • Add one-click voice auto-mastering: 80 Hz high-pass, linked compression, RMS normalization, and peak limiting.
  • Add webcam crop zoom (100–300%) plus horizontal/vertical pan, applied consistently in preview and native export on macOS, Windows, and Linux.
  • Add an accessible Add to timeline action to complement drag-and-drop.
  • Persist and clamp the new scene settings, serialize them to native exports, and localize the UI.

Why

The editor exposed no waveform or practical way to correct residual sync, clean up voice audio, or crop a webcam feed. Those are core finishing controls for a screen recorder and should be fast enough to use without external editing software.

Impact

Users can see where speech occurs, nudge audio without changing duration, master voice quickly, and reframe webcam footage while keeping preview and export behavior aligned.

Validation

  • 129 compositor library tests pass.
  • 121 focused TypeScript tests pass in the combined integration branch.
  • TypeScript, Biome, i18n consistency, whitespace, and production Vite build pass.
  • Packaged macOS app verified with accessibility-driven Computer Use:
    • waveform visibly renders from a real 74-second recording
    • auto-master, sync, and trim values change and reset
    • 300% webcam zoom visibly reframes the preview and resets to 100%
    • Add to timeline doubles the timeline duration and Undo restores it

Summary by CodeRabbit

  • New Features

    • Added audio controls for automatic mastering, synchronization offset, and output gain.
    • Audio settings now apply to previews and exported videos.
    • Added webcam crop and framing controls with zoom and pan adjustments.
    • Added an “Add to timeline” action for selected media assets.
    • Improved preview audio synchronization, including supplemental audio tracks.
  • Bug Fixes

    • Corrected handling of browser-based media URLs.
    • Improved webcam cropping consistency across platforms.
    • Added audio processing safeguards to reduce clipping and unwanted noise.
  • Localization

    • Added labels and messages across supported languages.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@vitaligusatinsky, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 04e42a57-b5d1-4b5c-af5b-c3c05c57be9f

📥 Commits

Reviewing files that changed from the base of the PR and between 3e643d5 and 3d1cd3f.

📒 Files selected for processing (19)
  • crates/compositor/src/audio.rs
  • src/components/ai-edition/VirtualPreview.audio.test.ts
  • src/components/ai-edition/VirtualPreview.tsx
  • src/components/ai-edition/v4/MediaStage.test.ts
  • src/components/ai-edition/v4/MediaStage.tsx
  • src/i18n/locales/ar/settings.json
  • src/i18n/locales/es/settings.json
  • src/i18n/locales/fr/settings.json
  • src/i18n/locales/it/settings.json
  • src/i18n/locales/ja-JP/settings.json
  • src/i18n/locales/ko-KR/settings.json
  • src/i18n/locales/pt-BR/settings.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/tr/settings.json
  • src/i18n/locales/vi/settings.json
  • src/i18n/locales/zh-CN/settings.json
  • src/i18n/locales/zh-TW/settings.json
  • src/lib/ai-edition/store/editorSettings.test.ts
  • src/lib/ai-edition/store/editorSettings.ts
📝 Walkthrough

Walkthrough

The change adds scene-level audio processing, authored webcam cropping, editor controls, synchronized audio preview, and media-stage timeline insertion across the Rust compositor and TypeScript editor.

Changes

Scene media controls

Layer / File(s) Summary
Scene settings contracts and serialization
crates/compositor/src/scene.rs, src/lib/ai-edition/store/editorSettings.ts, src/native/sceneDescription.ts
Scene and editor settings now store webcam crop regions and audio offset, gain, and auto-master values. Scene serialization carries these values into compositor inputs.
Authored webcam cropping
crates/compositor/src/frame_geometry.rs, crates/compositor/src/compositor_*.rs, src/components/ai-edition/RightPanes.tsx, src/i18n/locales/*/settings.json
Compositor source rectangles preserve authored webcam crops before aspect-ratio fitting. The editor provides zoom and pan controls with localized labels.
Audio finalization and export
crates/compositor/src/audio.rs, crates/compositor/src/pipeline_*.rs
PCM finalization applies filtering, compression, mastering, manual gain, clipping, and signed offsets before AAC encoding on Linux, macOS, and Windows.
Audio controls and synchronized preview
src/components/ai-edition/RightPanes.tsx, src/components/ai-edition/VirtualPreview.tsx, src/components/ai-edition/v4/FloatingInspector.tsx, src/components/ai-edition/VirtualPreview.audio.test.ts
The editor exposes audio settings. Preview playback synchronizes primary and supplemental audio through Web Audio processing and offset-aware timing. Tests cover signed offsets and track boundaries.

Media timeline insertion

Layer / File(s) Summary
Add selected media to timeline
src/components/ai-edition/MediaStage.tsx, src/components/ai-edition/NewEditorShell.tsx, src/components/ai-edition/NewEditorShell.module.css, src/i18n/locales/*/editor.json
Selected media can be added to the timeline through the editor callback. The action includes localized labels, success feedback, and secondary button styling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EditorSettings
  participant SceneDescription
  participant CompositorPreview
  participant AACExport
  EditorSettings->>SceneDescription: serialize audio and webcam crop settings
  SceneDescription->>CompositorPreview: provide scene media settings
  CompositorPreview->>CompositorPreview: synchronize and process preview audio
  SceneDescription->>AACExport: provide SceneAudio settings
  AACExport->>AACExport: finalize PCM before AAC encoding
Loading

Possibly related PRs

Suggested reviewers: etiennelescot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the changes and validation, but it omits most required template sections, including issue, change type, release impact, desktop impact, screenshots, and testing. Complete the repository template, mark the change, release, and platform impact checkboxes, add issue information, and include screenshots or video and a Testing section.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Title check ✅ Passed The title clearly summarizes the main editor changes: waveform support, audio mastering, and webcam cropping.
✨ 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.

@vitaligusatinsky
vitaligusatinsky marked this pull request as ready for review August 11, 2026 14:45
@vitaligusatinsky

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (2)
src/components/ai-edition/VirtualPreview.audio.test.ts (1)

14-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for an unknown duration.

src/components/ai-edition/VirtualPreview.tsx passes audio.duration, which is NaN until the media metadata loads. resolveAudioPreviewTime handles that through the Number.isFinite fallback, but no test covers it. A NaN case pins the "play while the duration is still unknown" behavior.

Attribution: the coding guidelines require "Add a test for every new behavior in the same package as the code under test."

💚 Proposed additional test
 	it("stops instead of seeking past the track", () => {
 		expect(resolveAudioPreviewTime(9.9, -160, 10)).toEqual({
 			targetTimeSec: 10,
 			shouldPlay: false,
 		});
 	});
+
+	it("plays while the duration is still unknown", () => {
+		expect(resolveAudioPreviewTime(1, 0, Number.NaN)).toEqual({
+			targetTimeSec: 1,
+			shouldPlay: true,
+		});
+	});
 });
🤖 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 `@src/components/ai-edition/VirtualPreview.audio.test.ts` around lines 14 - 19,
Add a test case alongside the existing resolveAudioPreviewTime tests for an
unknown duration represented by NaN, asserting the fallback behavior allows
playback while metadata is unavailable. Use the existing test structure and
resolveAudioPreviewTime symbol, without changing production logic.

Source: Coding guidelines

crates/compositor/src/audio.rs (1)

120-135: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Shift the PCM in place to avoid a second full-length buffer.

shifted allocates a complete copy of the PCM. For a long export the assembled PCM is already large (48 kHz × channels × duration), so this doubles peak audio memory for the whole finalization step. copy_within plus a zero fill of the vacated region gives the same result without the extra allocation.

♻️ Proposed in-place shift
-    let mut shifted = vec![vec![0.0f32; samples]; pcm.len()];
     if shift > 0 {
         let destination = (shift as usize).min(samples);
         let count = samples - destination;
-        for channel in 0..pcm.len() {
-            shifted[channel][destination..destination + count]
-                .copy_from_slice(&pcm[channel][..count]);
+        for channel in pcm.iter_mut() {
+            channel.copy_within(..count, destination);
+            channel[..destination].fill(0.0);
         }
     } else {
         let source = ((-shift) as usize).min(samples);
         let count = samples - source;
-        for channel in 0..pcm.len() {
-            shifted[channel][..count].copy_from_slice(&pcm[channel][source..source + count]);
+        for channel in pcm.iter_mut() {
+            channel.copy_within(source.., 0);
+            channel[count..].fill(0.0);
         }
     }
-    shifted
+    pcm
🤖 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 `@crates/compositor/src/audio.rs` around lines 120 - 135, Update the PCM shift
logic to operate directly on the existing pcm buffer instead of allocating
shifted in the relevant audio-processing function. Use in-place slice movement
such as copy_within for both shift directions, then zero-fill the vacated
region, while preserving the current clamping and output behavior.
🤖 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 `@src/components/ai-edition/v4/MediaStage.tsx`:
- Around line 98-102: add colocated Vitest coverage for MediaStage’s
addSelectedToTimeline action, verifying the selected asset ID is forwarded to
onAddToTimeline and that no call occurs when selected is absent; if rendering
MediaStage, use the jsdom environment directive, otherwise keep the default Node
environment.
- Around line 98-101: Update addSelectedToTimeline so the success toast uses the
displayed asset name, falling back to basename(selected.originalPath) when
selected.label is empty, matching the media card’s existing name-resolution
behavior.

In `@src/components/ai-edition/VirtualPreview.tsx`:
- Around line 188-216: Update the WebAudio setup effect around audioGraphRef and
createMediaElementSource to cache one MediaElementAudioSourceNode per
HTMLAudioElement in a WeakMap and reuse it across effect reruns, including
StrictMode remounts. Ensure cached nodes remain connected to the active
processing graph without recreating them or leaving elements attached to closed
contexts; preserve the existing cleanup and fallback behavior.
- Around line 175-179: Update the preparePreviewAudioTrack flow in
VirtualPreview so rejected IPC calls are handled and still mark audio probing
complete. Add a rejection path alongside the existing success handler that
clears or preserves the appropriate supplemental audio source, calls
setAudioProbeComplete(true), and prevents an unhandled promise rejection.
- Around line 218-237: Ensure the mastering-parameter effect also runs after the
audio graph is created, rather than relying on the ref update to trigger it.
Track graph creation with state and include that state in the effect
dependencies, or extract the parameter assignments from the effect and invoke
that function immediately after graph creation while preserving the existing
settings behavior.
- Around line 757-780: Stabilize the audio element ref callbacks in
VirtualPreview by wrapping the primary and supplemental ref handlers with
useCallback. Preserve assigning both the corresponding audio ref and state
setter, and ensure dependencies include the referenced setters and refs so
callbacks do not change on each render.

In `@src/i18n/locales/ar/settings.json`:
- Around line 56-59: Translate the English values for layout.webcamFraming,
layout.webcamCropZoom, layout.webcamCropX, layout.webcamCropY, and every entry
in the audio group within the Arabic settings locale, while preserving valid
JSON and the existing key structure. Verify all 13 settings locale files contain
these keys and run the provided i18n check to confirm no required values are
missing or unintentionally untranslated.

In `@src/i18n/locales/es/settings.json`:
- Around line 56-59: Translate the new user-visible webcam framing labels and
audio labels/help text, replacing the English fallback values while preserving
the existing JSON keys. Update src/i18n/locales/es/settings.json at lines 56-59
and 305-311 in Spanish, src/i18n/locales/fr/settings.json at lines 56-59 and
305-311 in French, src/i18n/locales/it/settings.json at lines 56-59 and 305-311
in Italian, src/i18n/locales/ja-JP/settings.json at lines 56-59 and 305-311 in
Japanese, src/i18n/locales/ko-KR/settings.json at lines 56-59 and 305-311 in
Korean, and src/i18n/locales/pt-BR/settings.json at lines 56-59 and 305-311 in
Brazilian Portuguese.

In `@src/i18n/locales/ru/settings.json`:
- Around line 56-59: Translate the new webcam framing/crop labels and audio
labels/help text in all affected locale files: src/i18n/locales/ru/settings.json
lines 56-59 and 304-311, src/i18n/locales/tr/settings.json lines 56-59 and
304-311, src/i18n/locales/vi/settings.json lines 56-59 and 304-311,
src/i18n/locales/zh-CN/settings.json lines 56-59 and 304-311, and
src/i18n/locales/zh-TW/settings.json lines 57-60 and 305-312. Use accurate
Russian, Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese
translations, preserve JSON validity across all 13 locale files, and run the
i18n check.

In `@src/lib/ai-edition/store/editorSettings.ts`:
- Around line 258-269: Update normaliseCropRegion so x and y are clamped to 1 -
MIN_CROP_SIZE instead of 1 before width and height are calculated, preserving
the minimum crop size at the edges. Keep the existing dimension clamping and
fallback behavior unchanged.

---

Nitpick comments:
In `@crates/compositor/src/audio.rs`:
- Around line 120-135: Update the PCM shift logic to operate directly on the
existing pcm buffer instead of allocating shifted in the relevant
audio-processing function. Use in-place slice movement such as copy_within for
both shift directions, then zero-fill the vacated region, while preserving the
current clamping and output behavior.

In `@src/components/ai-edition/VirtualPreview.audio.test.ts`:
- Around line 14-19: Add a test case alongside the existing
resolveAudioPreviewTime tests for an unknown duration represented by NaN,
asserting the fallback behavior allows playback while metadata is unavailable.
Use the existing test structure and resolveAudioPreviewTime symbol, without
changing production logic.
🪄 Autofix

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: 104f41d6-5785-49e1-be79-7bd781cc4bcf

📥 Commits

Reviewing files that changed from the base of the PR and between a6795d2 and 3e643d5.

📒 Files selected for processing (44)
  • crates/compositor/src/audio.rs
  • crates/compositor/src/compositor_linux.rs
  • crates/compositor/src/compositor_macos.rs
  • crates/compositor/src/compositor_windows.rs
  • crates/compositor/src/frame_geometry.rs
  • crates/compositor/src/pipeline_linux.rs
  • crates/compositor/src/pipeline_macos.rs
  • crates/compositor/src/pipeline_windows.rs
  • crates/compositor/src/scene.rs
  • src/components/ai-edition/NewEditorShell.module.css
  • src/components/ai-edition/NewEditorShell.tsx
  • src/components/ai-edition/RightPanes.tsx
  • src/components/ai-edition/VirtualPreview.audio.test.ts
  • src/components/ai-edition/VirtualPreview.tsx
  • src/components/ai-edition/v4/FloatingInspector.tsx
  • src/components/ai-edition/v4/MediaStage.tsx
  • src/i18n/locales/ar/editor.json
  • src/i18n/locales/ar/settings.json
  • src/i18n/locales/en/editor.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/es/editor.json
  • src/i18n/locales/es/settings.json
  • src/i18n/locales/fr/editor.json
  • src/i18n/locales/fr/settings.json
  • src/i18n/locales/it/editor.json
  • src/i18n/locales/it/settings.json
  • src/i18n/locales/ja-JP/editor.json
  • src/i18n/locales/ja-JP/settings.json
  • src/i18n/locales/ko-KR/editor.json
  • src/i18n/locales/ko-KR/settings.json
  • src/i18n/locales/pt-BR/editor.json
  • src/i18n/locales/pt-BR/settings.json
  • src/i18n/locales/ru/editor.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/tr/editor.json
  • src/i18n/locales/tr/settings.json
  • src/i18n/locales/vi/editor.json
  • src/i18n/locales/vi/settings.json
  • src/i18n/locales/zh-CN/editor.json
  • src/i18n/locales/zh-CN/settings.json
  • src/i18n/locales/zh-TW/editor.json
  • src/i18n/locales/zh-TW/settings.json
  • src/lib/ai-edition/store/editorSettings.ts
  • src/native/sceneDescription.ts

Comment thread src/components/ai-edition/v4/MediaStage.tsx Outdated
Comment thread src/components/ai-edition/v4/MediaStage.tsx
Comment thread src/components/ai-edition/VirtualPreview.tsx Outdated
Comment thread src/components/ai-edition/VirtualPreview.tsx
Comment thread src/components/ai-edition/VirtualPreview.tsx
Comment thread src/components/ai-edition/VirtualPreview.tsx
Comment thread src/i18n/locales/ar/settings.json Outdated
Comment thread src/i18n/locales/es/settings.json Outdated
Comment thread src/i18n/locales/ru/settings.json Outdated
Comment thread src/lib/ai-edition/store/editorSettings.ts
@vitaligusatinsky

Copy link
Copy Markdown
Author

All CodeRabbit findings are addressed in 3d1cd3f, including both nitpicks: the PCM shift is in-place and NaN media duration remains playable. Verification: full Vitest suite 1705 passed / 1 skipped, app and test TypeScript checks passed, i18n passed all 12 locales, lint passed with only pre-existing warnings, and Rust passed 129 tests. An independent verifier audited the exact diff and all 12 findings with no blocker.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@vitaligusatinsky I will review the updates in commit 3d1cd3f2, including the PCM shift and non-finite media-duration handling.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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