Skip to content

feat(studio): waveform strip, clip light, peak marks and video beat source (5/8) - #4817

Merged
vanceingalls merged 9 commits into
mainfrom
aov/05-waveform-meters
Oct 2, 2026
Merged

vanceingalls merged 9 commits into
mainfrom
aov/05-waveform-meters

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Seeing sound on the timeline: a waveform strip under video clips that carry sound, a latching CLIP light on the master meter at the export ceiling, peak marks where a clip redlines, and a video with sound as the beat source.

Changes

  • studio-server: peak maps (peakMap) plus a /peaks route.
  • studio: AudibleVideoClipContent waveform strip, ClipPeakMarks / clipPeakRuns, and a CLIP light on AudioMeterStrip (theme tokens). Each peak map is decoded once, and Duck is never offered on a carve source.
  • core: the beat file accepts a video with sound as its source.

Testing

Static gates on the layer tip: bun install, bun run build, tsc --noEmit for every touched package (0 errors), oxlint and oxfmt --check on files changed vs main (clean), gen:skills-manifest --check (in sync), scripts/comment-ratchet.mjs (ok).

  • Scoped vitest: cli 3957, core 3793, studio 6288, studio-server 996. All pass.

Notes

  • The strip draws only at the root composition. Peaks can go stale after an in-place re-encode.
  • Part of the audio-on-video stack. See the bottom PR for the overview.

🤖 Generated with Claude Code

Before

before

After

after

Timeline: strip under sound-bearing videos. At this layer the strip draws flat (1px canvas); the next layer fixes the lane height.

@vanceingalls
vanceingalls added this pull request to stack #4822 October 1, 2026 01:24

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed incremental #4817 at 65b1bf6d3846389aa251407268c23e3fa65c36f7 against a244373ae644e6cf8085f7a7804cfc4a91eb08ef.

The peak accumulator preserves absolute native-channel levels and carries split frames across chunks (packages/studio-server/src/helpers/peakMap.ts:41). The CLIP light reads the program meter upstream of monitor gain, matching export intent (packages/studio/src/components/nle/AudioMeterStrip.tsx:229, existing packages/core/src/runtime/webAudioTransport.ts:147).

Blocker: packages/studio-server/src/helpers/peakMap.ts:90 — The channel probe explicitly selects a:0 (:71), but decoding leaves stream selection to FFmpeg. With a mono first stream and a stereo second stream, FFmpeg can select the second while the accumulator still treats every float as a mono frame. A real generated one-second fixture with no default dispositions returned 40 bins at binSeconds=0.05 (advertising two seconds), with peaks 0.9 from stream two instead of 0.1 from stream one. Consequently peak markers have the wrong level and time positions. Select the same stream in both commands, and add a multistream regression test.

Audited: complete peak decoder/cache/route, peak-run/marks components, audible-video strip and render dispatcher, meter latch integration and cleanup, CLI/Studio beat-source predicates, carve-source exclusion; traced waveform requests and preview program-meter placement.
Trusting: parent audio-runtime implementation and #4816 fixes; full Studio route/layout and packaged renderer; unmodified thumbnail scheduling internals. No prior reviews or comments on this PR.

Verification: 46 existing targeted tests passed at the exact head, including real generated single-track video decode tests; 1 independent real-FFmpeg multistream witness passed. Cached Vitest 3.2.4 differs from declared 4.1.11, with worktree source aliases. Initial two broader UI suites could not collect with the cached dependencies; 50 shared UI tests passed at #4818 (unchanged meter/render dispatcher source); these are supplemental results, not exact-head runs. All observed exact-head CI checks pass. The later browser fixture exercised source components only, not full Studio/backend E2E.

— Magi

Verdict: REQUEST CHANGES
Reasoning: Peak maps violate their per-bin timing/level contract on valid multistream media despite the single-stream tests and green CI.

@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from 65b1bf6 to d2cf398 Compare October 1, 2026 05:46
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Edit accuracy: 953 passing here, 953 on the base branch

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from 8d11a98 to f4b275d Compare October 1, 2026 08:21
@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from f4b275d to a12e2f9 Compare October 1, 2026 09:47

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fix-delta review at a12e2f91b6cb88300ab4569a5bc25637438ba3af, against stacked parent dbcbca85d2e088cd05cb28320015aa3109aec27d; compared with my previous review at 65b1bf6d.

The stream-selection blocker is repaired: the channel probe selects a:0, decoding explicitly maps 0:a:0 (packages/studio-server/src/helpers/peakMap.ts:69, :96), and the peaks-v2 cache invalidates previously incorrect maps (:8). The regression constructs a mono-first, louder-stereo-second fixture with no default dispositions and checks both length and 0.1 peak level.

Verification: all 6 peak-map tests pass with real FFmpeg on stack head 88255f82e; helper/test files are byte-identical to this PR head. Cached Vitest 3.2.4 and source aliases. No waveform browser, live Studio/backend, playback or export acceptance this round. No new code blocker in the two-file repair delta.

The required Studio and player captures check remains red for missing Before/After evidence. Earlier changes-requested state remains; this is not an approval.

— Magi

Verdict: COMMENT
Reasoning: The original multistream defect is fixed and regression-covered, but the required capture gate is unresolved.

@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from a12e2f9 to b9a59e5 Compare October 1, 2026 20:01

@jerrai-bot-heygen jerrai-bot-heygen 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.

At b9a59e5f, the first-audio-stream probe/decode repair remains, but the new peak-map decoder treats partial output followed by FFmpeg failure as success and caches an incomplete map. Inline has the concrete failure and regression request. Current-head checks pass except required Studio/player Before/After captures, which are red; no visual waiver was supplied. No local tests, build, merge or release. — Jerrai

reject(new Error(`ffmpeg produced no audio samples (exit ${code})`));
return;
}
resolvePromise(finishPeaks(acc));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A decode that emits some samples and then exits nonzero resolves here as a complete peak map. The /peaks route caches that array (routes/peaks.ts:53–64), so a truncated/corrupt audio source can permanently show an incomplete clipping map instead of the existing decode-error response. Please reject on a nonzero close code even when acc.frame > 0, and add a partial-output-then-failure regression. The first-stream probe/map fix itself is present.

@jerrai-bot-heygen jerrai-bot-heygen 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.

The 24-file layer patch is preserved at the exact #4816-based head. Peak decoding selects the same first audio stream as probing and rejects a nonzero FFmpeg close before returning/caching any partial bins; peakMap.decodeFailure.test.ts runs a fake FFmpeg that emits samples then exits 1 and asserts rejection. This closes my previous code hold; the capture check passes. Miguel's prior review and remaining CI are independent merge gates. — Jerrai

@jerrai-bot-heygen jerrai-bot-heygen 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.

Exact a0d266da rebase review: all 24 layer paths and patch hunks match the previously approved parent→head diff, with no concurrent parent edit on those paths. At this head FFmpeg peak decoding still rejects a nonzero exit after partial sample output before returning/caching peaks; the regression emits samples and exits 1. Code approval for this layer only; #4815 viewport CI and Miguel's older review remain independent stack gates, and the new head's own checks are still running. No merge/release action. — Jerrai

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed #4817 at a0d266da27da8ed2a114ce5d51e83470502610d4 against #4816 4f68e1720; this supersedes my changes-requested review 5374292610, and confirms Jerrai's decoder repair.

Probe and decode both select the first audio stream (a:0 / -map 0:a:0), with the versioned peak cache. decodePeakMap now rejects every nonzero close before returning bins, including partial output, so the route cannot cache that result as a success. I read the helper/cache route and ran all seven peak tests, including real FFmpeg fixtures and the partial-output/exit-1 binary. Those helper and test files are byte-identical between this layer and the tested #4818 export.

Current capture and other fetched checks pass. Cached Vitest 3.2.4/source aliases; supplemental combined-tip execution, not a full isolated package build. No live Studio, waveform browser, playback or render acceptance was performed.

Verdict: APPROVE
Reasoning: the original stream mismatch and the later incomplete-decode success path are repaired and directly regression-covered. No new blocker found in the reviewed layer.

— Magi

@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from a0d266d to 89fb3b8 Compare October 2, 2026 02:05

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approved at 89fb3b8c16d43352dd00afe060d5b39cb26bb743 against stacked parent eaaf6e1b6d9e243490b0f6dc1d4e5fe366462794, following my review 5387236487.

The parent-to-head patch has identical paths and hunks to the layer I previously approved, ignoring only object indexes and hunk locations. The restack preserves the repaired matching FFmpeg audio-stream selection, cache version and nonzero-exit rejection. This is a rebase verification; I did not repeat the earlier exact-source tests or the full package suite.

Current checks have no failure, but CI remains in progress; approval does not establish merge readiness. Prior evidence limits (cached tooling, no live Studio/audio acceptance) remain. No merge, release or protection bypass performed.

Verdict: APPROVE
Reasoning: The inspected layer is the same reviewed patch on its new stacked parent, with no additional code change or new layer-specific blocker.

— Magi

@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from 89fb3b8 to 044e969 Compare October 2, 2026 02:44

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approved at 044e9696493e9b822d021a0fb38049f4a130b513 against stacked parent 9849ba697393839b2134f5a82b79a694163bcb5c, following my approval 5387641276.

I independently compared the complete old/new parent-to-head patches: same 24-path set and identical hunks, ignoring only object indexes and hunk positions. Every changed-layer source and test blob is also identical between the approved head and this head; there are no differing layer file blobs. The prior repairs, test cases and save/decode behavior, survive unchanged.

This is a rebase verification, not a new local test run. The prior focused tests and their stated cached-tooling limits remain the evidence. No live Studio/playback, full package acceptance or merge/release performed. No task worktree, server or browser was created.

Current-head checks have no failure but are still in progress. Approval does not waive any required check or establish merge readiness.

Verdict: APPROVE
Reasoning: The entire inspected layer patch and its executable sources/tests match the previously approved layer on the new stacked parent.

— Magi

@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from 044e969 to 833259d Compare October 2, 2026 03:22
Base automatically changed from aov/04-loudness-duck to main October 2, 2026 04:16
vanceingalls and others added 2 commits October 2, 2026 04:16
An audible video keeps its thumbnails and gains the audio waveform along the bottom
38% of the clip (also when thumbnails are off); muted and silent video are unchanged.
The waveform route already decodes video audio; a test now proves it on an mp4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iling

The light turns red when the preview master sample peak reaches -1 dBFS (the export
true-peak ceiling) and stays lit until clicked. Mutation-tested threshold and latch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vanceingalls and others added 7 commits October 2, 2026 04:16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
studio-server computes an absolute per-50ms sample-peak map once per media file
(native channels, cached by size+mtime next to the waveform cache); the timeline
paints red marks where the source at the clip's own volume reaches -1 dBFS and
names the loudest point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Studio's isMusicSourceElement accepts an audible, unmuted video for the music track
and the beat fallback (lane zoning unchanged); core findMusicAudioSrc accepts
<video data-has-audio="true"> without muted; the beats CLI message mentions video.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e source

Concurrent peak requests for one file share a single ffmpeg decode; Duck under voice
is withheld on a clip another bed already carves against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The channel probe read stream a:0 while the decode left stream choice to
ffmpeg, which picks a later stereo stream over a mono first one; every frame
was then read at the wrong width, doubling the bins and taking the other
stream's level. Both commands now select a:0, and the cache key moves to
peaks-v2 so maps built from the wrong stream are rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he input

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A decode that emits some samples and then fails no longer resolves as a
complete map, so /peaks cannot cache a truncated clipping map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vanceingalls
vanceingalls force-pushed the aov/05-waveform-meters branch from 833259d to b99c794 Compare October 2, 2026 04:16

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approved at b99c7941333980c0cb0515f0e86e381a151cc25e against 9244c47328e41e59034bd9bbec293fe3f7310cf6, compared with my previously checked 833259d63dfdfae17e01fc47a7f7fd9cae28b31f layer.

Audited: the complete 24-path parent-to-head patch and every layer file's blob. Paths and patch hunks are identical, ignoring object indexes/hunk positions; all file contents (source, tests and manifests) are byte-identical. Independent git range-diff marks all 9 commits =. The matching-stream/nonzero-exit peak decoding repairs remain unchanged.

Trusting / not rerun: the earlier focused tests and their documented environment limits; no new local tests, live Studio/playback or full package build. No new task worktree/server/browser was created. Current checks have no failure but are still running, so this is code approval rather than merge readiness. No merge, enqueue, release or CI bypass performed.

Verdict: APPROVE
Reasoning: The full rebased layer and its file contents match the previously reviewed layer, with no new code change or blocker.

— Magi

@vanceingalls
vanceingalls added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 824e410 Oct 2, 2026
86 checks passed
@vanceingalls
vanceingalls deleted the aov/05-waveform-meters branch October 2, 2026 04:57
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.

3 participants