Skip to content

assemble-and-score / dissolve-between-shots: expose a world_fade_out_ms so a score's written ending isn't buried under the world track #339

Closed

Description

@dkackman

tool/endpoint: templates/assemble-and-score, templates/dissolve-between-shots

repro: Run templates/assemble-and-score with a score that ends in a decay: the score's per-second envelope reads -28.9 dBFS at 47 s, -45.9 at 48, -51.8 at 49, -53.7 at 50. The shots' world sound runs at a constant level through the tail. Use default world_gain 1.8 and score_gain 1.0. Measure the film with get_gallery_metadata(envelope=true).

expected: Some template argument lets the world track hand off to the score's ending, and ideally a warning when the world buries it.

actual: Film tail -17.9/-19.1/-21.1/-22.9/-22.2/-23.9 dBFS at 45-50 s against the score's -15.8 … -53.7. The world sits ~25 dB over the score's final seconds, so the written ending is inaudible. No warning fired: normalize_audio reported a healthy -2.6 dBFS peak. Lowering world_gain cannot fix it. Exposing a -46 dBFS score needs world_gain ≈ 0.05, which destroys the world everywhere else. What did work was a local variant with one fade_audio step (fade_out_ms: 4500) on the world track between world and mixed. That was a 15.6 s job with no model. The tail became -17.9/-19.3/-23.0/-28.0/-34.0/-53.96, landing on the score's own -53.7.

code:

  • workflows/templates/assemble-and-score.json:51-99: world (resample_audio) feeds mixed (mix_audio, gains [score_gain, world_gain]) directly. No fade is possible, and variables (lines 15-33) have none.
  • workflows/templates/dissolve-between-shots.json:48-90: same world -> mix wiring.
  • dw/tasks/audio_utils.py:1214 fade_audio(audio, fade_in_ms=0, fade_out_ms=0, sample_rate=None): at 0 it multiplies nothing, so a default-0 step is a no-op.

proposed fix:

  • Add a world_fade_out_ms variable (default 0) to both templates.
  • Insert a world_faded fade_audio step (audio: previous_result:world, fade_out_ms: variable:world_fade_out_ms, sample_rate: variable:sample_rate), and point mix_audio's second input at it.
  • Document it in the description next to score_gain/world_gain.
  • Check that the default run's warnings stay [] (regression case S-F057).
  • Optional second part: after mixing, warn when the world's final-second RMS exceeds the score's final-second RMS by more than ~15 dB ("the score's ending is ~N dB under the world; world_fade_out_ms fades it out").

source: dw-findings-ledger DW-02 (R#10), triaged 2026-09-22 against develop f11ac2a


Filed from Don's dw findings ledger (dw-findings-ledger.html, DW-02). First-pass triage for applicability and value against open/closed issues and develop @ f11ac2a by claude-opus-5-5 via anthropic (interactive session with Don; code-checking subagents inherited that model).

Activity

  1. dkackman commented on Sep 22, 2026

    @dkackman
    OwnerAuthor

    Related: #342 touches the same two templates (assemble-and-score, dissolve-between-shots) on the score/world path. One branch and one deploy would cover both. (claude-opus-5-5 via anthropic, filing session)

  2. dkackman commented on Sep 22, 2026

    @dkackman
    OwnerAuthor

    triage: batch with #339, #342 — same two templates (assemble-and-score, dissolve-between-shots) on the score/world path; one branch, one deploy. For #342 prefer a warning inside slice_audio with no new task argument; if a new argument turns out necessary, park that part for approval rather than adding it. (claude-opus-5-5 via anthropic, triage session)

  3. added a commit that references this issue on Sep 22, 2026
  4. dkackman commented on Sep 22, 2026

    @dkackman
    OwnerAuthor

    Fixed on develop, deployed to lem.

    Both `assemble-and-score` and `dissolve-between-shots` templates gained a `world_fade_out_ms` variable (default `0`, a true no-op) and a new `world_faded` step (`fade_audio`) between `world` and `mixed`; `mixed` now mixes the score against `world_faded` instead of the raw `world` track. Each template's `description` documents the new variable. With the default `0`, the fade is a no-op (verified by reading `fade_audio`'s guard, plus the full test suite passing with no changes to existing expectations), so no existing caller's output changes unless they opt in.

    • Commit: `d591404` (on develop, merged from fix/339-342-world-fade-slice-trim)
    • Deploy: `ssh lem 'scripts/deploy.sh develop'`, healthy at `d591404`, 2026-09-22 22:24 UTC
    • Worked as: Claude Sonnet 5 (claude-sonnet-5), provider Anthropic

    Proposed regression case (for the tester to add once verified over MCP) — smoke suite (`regression-suite-smoke.md`), since this is a basic default-behavior guarantee:

    • Run `assemble-and-score` with its stock default assets/arguments (`world_fade_out_ms` left at `0`). Expect the job to succeed with no new warnings introduced by this change, and the `mixed`/`balanced`/`film` steps present as before — confirms the fade step is a genuine no-op by default.
    • Optionally, a second case setting `world_fade_out_ms` to a few thousand ms on a score with a decaying tail and confirming the world track audibly recedes near the ending (this one is more of an edge-case listening check, so it could instead go in `regression-suite-complete.md` if the smoke suite should stay fast).
  5. dkackman commented on Sep 23, 2026

    @dkackman
    OwnerAuthor

    Verified over MCP against lem develop @ e5bfb9e, by the tester running as claude-opus-5-5 via anthropic.

    I ran both templates in workspace regression-smoke. Every run used shots: ["asset:qa-cast/ep6-cold-open.mp4", "asset:qa-cast/ep3-shot2-reply.mp4"] and score: "asset:qa-cast/ep20-score.wav", with no other arguments except the ones named below. Every job succeeded in about 3–5 s, and each manifest shows the new world_faded step between world and mixed. Both templates' get_workflow(variables_only=true) list world_fade_out_ms: 0.

    Film RMS envelope, 1 s buckets, via get_gallery_metadata(envelope=true):

    run job s0–s6 s7 s8 s9 s10
    assemble-and-score, default (fade 0), total_frames 248 1a36cf75d84c — −31.6 −30.2 −31.7 −52.0
    assemble-and-score, world_fade_out_ms: 3000 b007639f60eb identical to default −31.7 −32.0 −39.4 −61.1
    dissolve-between-shots, default, total_frames 236 2cb736b751d8 — −29.2 −32.6 −33.0 (9.83 s)
    dissolve-between-shots, world_fade_out_ms: 3000 a5fe2d863125 identical to default −30.1 −36.2 −44.5
    • The fade only affects the final ~3 s: every earlier bucket is bit-identical to the default run. The world recedes toward the score bed (ep20-score RMS ≈ −50.8, last bucket −56.5), so the handoff the issue asked for is available in both templates.
    • The default 0 is a true no-op in practice. Both default runs' warnings are the same pre-existing ones you get without match_levels (the 12.1 dB level-jump note from concat/dissolve), and neither template adds a new warning.
    • Not done: the proposed fix's optional second part, a warning when the world buries the score's ending. The issue asked for it only as "ideally", so I'm not bouncing on it.

    Side observation, outside this issue's scope: dissolve_videos's warning on both dissolve runs reads "videos carry audio at different sample rates (video 1: 32000 Hz, video 2: 32000 Hz) - resampling them all to 44100 Hz". The two rates are the same. The real mismatch is against the 44.1 kHz target, so the wording is misleading. I'm leaving it here for the next task pass to file if it's worth a ticket.

    Outputs from all four runs are deleted.

  6. added
    status:verifiedTester confirmed the fix via a real MCP call
    and removed on Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    owner:testerTester's turn to actstatus:verifiedTester confirmed the fix via a real MCP call

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions