Bounce a Performance (#64) #121

Merged
crowmaki merged 2 commits from issue-64-bounce-performance into main 2026-10-09 10:18:54 +00:00
Owner

Closes #64. Part of #24 (User Stories 181–182).

What it does

Bounce renders a Scene's Cut audio into its next Performance version.

  • Circled Takes: each unit of the Cut that a circled Take plays gets that Take's audio, cut at its Line marks. The new marks are the Takes' marks laid end to end.
  • Carry forward: a unit that no Take plays gets the current Performance's audio for that unit.
  • Holes: a unit with neither becomes silence, as long as the unit's Cut duration (covered = false). Holes at either end are left out of the audio. Every hole is returned and listed in a warning, so a Bounce never invents dialogue.
  • Provenance: performance_source now records Take sources and a carried-forward Performance alongside imports.
  • Not undoable: a Bounce is append-only, like an import.

How

  • studio-core:
    • ProjectSession::plan_bounce reads the Cut, using a new crate-internal bounce_sources next to the Cut builder. BounceAudio::render decodes the sources with the ffmpeg sidecar and lays them end to end at 48 kHz stereo. ProjectSession::bounce_performance stores the result.
    • The adapter plans under the session lock, renders outside it, and then stores. Storing refuses a plan if the Scene's Performance changed in between.
    • Import and Bounce share store_performance.
  • Migration V35 rebuilds performance_source with kinds import | take | performance and a typed FK column per kind. This keeps the references real under ADR-0004. A later kind, such as local TTS, adds one column and one kind.
  • UI: the Performance bar gets a Bounce button. It is disabled until a circled Take plays or there is a Performance to carry forward. After a Bounce, a dismissible warning lists the silent Lines. Version history shows the sources, for example 12A-3, v1 carried forward.

Tests

  • crates/studio-core/tests/performance_bounce.rs runs through the ProjectSession seam, with real audio from the fake ComfyUI. It covers:
    • Take marks
    • carry-forward, including the audio content
    • interior holes (silent, uncovered, estimate-timed in the Cut)
    • holes at the ends being trimmed
    • "nothing to bounce" and not undoable
    • a stale plan being refused
  • cargo test --workspace --all-features (58 test binaries), bun test src (245) and bun run check all pass.

Screenshots

Scene with a v1 Performance imported over the first two Lines, before the Bounce:

Before

After the Bounce: v2 carries v1 forward, the ghost lane shows v1, and the warning lists the silent third Line:

After

Version history with each version's sources:

Versions

These screenshots don't show Take sources. A circled Take needs a ComfyUI render, and this run had none. The integration tests cover Take sources.

Closes #64. Part of #24 (User Stories 181–182). ## What it does **Bounce** renders a Scene's Cut audio into its next Performance version. - **Circled Takes:** each unit of the Cut that a circled Take plays gets that Take's audio, cut at its Line marks. The new marks are the Takes' marks laid end to end. - **Carry forward:** a unit that no Take plays gets the current Performance's audio for that unit. - **Holes:** a unit with neither becomes silence, as long as the unit's Cut duration (`covered = false`). Holes at either end are left out of the audio. Every hole is returned and listed in a warning, so a Bounce never invents dialogue. - **Provenance:** `performance_source` now records Take sources and a carried-forward Performance alongside imports. - **Not undoable:** a Bounce is append-only, like an import. ## How - `studio-core`: - `ProjectSession::plan_bounce` reads the Cut, using a new crate-internal `bounce_sources` next to the Cut builder. `BounceAudio::render` decodes the sources with the ffmpeg sidecar and lays them end to end at 48 kHz stereo. `ProjectSession::bounce_performance` stores the result. - The adapter plans under the session lock, renders outside it, and then stores. Storing refuses a plan if the Scene's Performance changed in between. - Import and Bounce share `store_performance`. - Migration V35 rebuilds `performance_source` with kinds `import | take | performance` and a typed FK column per kind. This keeps the references real under ADR-0004. A later kind, such as local TTS, adds one column and one kind. - UI: the Performance bar gets a **Bounce** button. It is disabled until a circled Take plays or there is a Performance to carry forward. After a Bounce, a dismissible warning lists the silent Lines. Version history shows the sources, for example `12A-3`, `v1 carried forward`. ## Tests - `crates/studio-core/tests/performance_bounce.rs` runs through the `ProjectSession` seam, with real audio from the fake ComfyUI. It covers: - Take marks - carry-forward, including the audio content - interior holes (silent, uncovered, estimate-timed in the Cut) - holes at the ends being trimmed - "nothing to bounce" and not undoable - a stale plan being refused - `cargo test --workspace --all-features` (58 test binaries), `bun test src` (245) and `bun run check` all pass. ## Screenshots Scene with a v1 Performance imported over the first two Lines, before the Bounce: ![Before](https://git.crowmaki.me/attachments/7fd42ccd-e382-4f40-82f2-977b42a22c84) After the Bounce: v2 carries v1 forward, the ghost lane shows v1, and the warning lists the silent third Line: ![After](https://git.crowmaki.me/attachments/46282673-32ed-4b99-a732-49f2cbbbd9d8) Version history with each version's sources: ![Versions](https://git.crowmaki.me/attachments/db10ba7b-4d46-485f-9266-61e7bd6c4b4d) These screenshots don't show Take sources. A circled Take needs a ComfyUI render, and this run had none. The integration tests cover Take sources.
Bounce renders a Scene's Cut audio into its next Performance version.
Each unit a circled Take plays takes the Take's audio between its Line
marks, so the new marks are the Takes' marks laid end to end. A unit no
Take plays carries forward the current Performance's audio, or else is a
silent hole the version does not cover. Holes at either end are left out
of the audio, and every hole is returned so the app can warn about it.

performance_source becomes an open list of kinds: import, take and
performance (carried forward). A Bounce is planned under the session
lock, rendered by ffmpeg outside it, then stored. Storing is refused if
the Scene's Performance changed in between.

The Performance bar gets a Bounce button and a dismissible warning that
lists the silent Lines.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
crowmaki deleted branch issue-64-bounce-performance 2026-10-09 10:18:54 +00:00
Sign in to join this conversation.
No description provided.