You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
No per-region gain task; ducking requires a five-step slice/gain/rejoin chain #187
tool/endpoint:list_tasks / audio task family (slice_audio, mix_audio, normalize_audio, pair_audio) repro: duck one scene under another — e.g. lower a dialogue track between two timestamps. normalize_audio is whole-track; mix_audio layers everything from sample 0 with no offset/region. The only way is slice_audio → gain (a full-track normalize on the slice) → mix_audio → rejoin → re-pair_audio — five extra steps for what one gain_audio(start_frame, num_frames, gain_dbfs) would do. expected: a gain_audio (or a mix_audio region/gain parameter) task that applies a gain to a region of a track, expressed in frames or seconds, so a scene can be ducked in one step. actual: no such task exists. list_tasks shows the slice/loop/mix/pair family but nothing that applies a per-region gain. impact: ducking is an ordinary editing operation (C-F016/C-F018/C-F024-adjacent); five-step chains accumulate arithmetic errors and are easy for an agent to get wrong. This is the lowest-effort, highest-value task addition after ASR.
triage: batch with #185 — same files (dw/tasks/audio_utils.py, dw/tasks/task.py, dw/task_domains.py, docs/TASKS.md) and one deploy; #185 is the lowest number so its session ships both. Not a duplicate — no existing task applies a gain to a region (normalize_audio is whole-track, mix_audio has no offset). A new gain_audio(audio, gain_db, start_frame, num_frames, sample_rate=None) alongside slice_audio is an ordinary task addition, not a step-7 decision; register it in task.py, declare its domains (num_frames > 0, start_frame >= 0) in task_domains.py so tests/test_task_domains.py pins them, and give it the same sample_rate reinterpretation caveat slice_audio carries (#180). Not batched with #188 — that one pulls a helper model onto lem and is a session of its own. Triaged by model opus via provider anthropic.
gain_audio(audio, gain_db, start_seconds=None, duration_seconds=None, start_frame=None, num_frames=None, fps=None, sample_rate=None) applies a gain (dB, negative ducks / positive boosts) to a region of a track in one step, replacing the five-step slice_audio → gain → mix_audio → rejoin → pair_audio chain for ducking a scene.
Region addressing mirrors slice_audio: seconds, or frames+fps, resolved the same way (dw/tasks/audio_utils.py).
Differs from slice_audio in one respect: a region reaching past the end of the track is clipped, not zero-padded — there's no real material there to gain.
Documented in docs/TASKS.md (new ### gain_audio section, same format as slice_audio).
Commit: 1f3c8f1, merged to develop at 09875d9, deployed to lem (get_task("gain_audio") confirms the live schema matches).
Model sonnet via provider anthropic.
Proposed regression case (not added to the suite, for the tester to place): in tests/test_audio_utils.py (or wherever slice_audio's region tests live), build a track, call gain_audio on a middle region with a negative gain_db, and assert only that region's samples changed (scaled by the expected linear gain) while samples outside the region are bit-identical to the source. A second case: a region whose start_frame/num_frames (with fps) extends past the track's end should return output the same length as the input (clipped, not padded).
Verified over MCP against dw 0.4.0-beta.6 on lem, workspace qa-verify-187, fixture asset:qa-cast/ep11-bed.wav (19.667 s, 32 kHz mono, peak −28.886 dBFS). Model opus via provider anthropic.
Repro — duck a middle region (seconds). Job 6751bf30fdb4: gain_audio{gain_db: -12, start_seconds: 5, duration_seconds: 3} → succeeded in 1.1 s. get_gallery_metadata(envelope=true) on the output vs. the source asset, second by second:
Adjacent 1 — frames, positive gain, region past the end. Job bc12d804d750: {gain_db: 6, start_frame: 400, num_frames: 200, fps: 24} (frame 400 = 16.667 s; region would end at 25 s on a 19.67 s track) → succeeded in 0.6 s. Output duration still 19.666656 s — clipped, not padded, as the hand-off says. Seconds 16–18 peak −28.886 → −22.884 (+6.00 dB); the final partial second −50.22 → −44.24 (+5.98); seconds 0–15 identical to the source.
Adjacent 2 — domain pins.validate_workflow with start_frame: -1, num_frames: 0 → valid: false, two errors at steps[0].task.arguments.start_frame / .num_frames, each naming gain_audio and the bound.
Observation (not a bounce).gain_audio with gain_db and no region at all validates (valid: true, plan returned) and then fails at run time in 0.6 s with a clear message: "gain_audio needs either 'start_seconds'/'duration_seconds' or 'start_frame'/'num_frames'/'fps' to address the region to gain". Same class as the validate_workflow-doesn't-know-cross-argument-rules gap noted on #185/S-F041; the runtime refusal is immediate and well-worded, so I'm recording it here rather than filing.
Expected behavior from the issue body is met: a one-step per-region gain, addressable in seconds or frames, in place of the five-step chain. Closing as completed. Regression case added to regression-suite-smoke.md (S-F042) — the implementer's proposed pytest-level sample assertions belong in the dw repo's own suite; the MCP-side case pins the same properties through the per-second envelope.
Activity
dkackman commentedon Sep 17, 2026
triage: batch with #185 — same files (
dw/tasks/audio_utils.py,dw/tasks/task.py,dw/task_domains.py,docs/TASKS.md) and one deploy; #185 is the lowest number so its session ships both. Not a duplicate — no existing task applies a gain to a region (normalize_audiois whole-track,mix_audiohas no offset). A newgain_audio(audio, gain_db, start_frame, num_frames, sample_rate=None)alongsideslice_audiois an ordinary task addition, not a step-7 decision; register it intask.py, declare its domains (num_frames> 0,start_frame>= 0) intask_domains.pysotests/test_task_domains.pypins them, and give it the samesample_ratereinterpretation caveatslice_audiocarries (#180). Not batched with #188 — that one pulls a helper model onto lem and is a session of its own. Triaged by model opus via provider anthropic.fix(tasks): #185 #187 - stop advertising device on non-consuming task…
Merge fix/185-187-task-device-and-gain-audio
dkackman commentedon Sep 17, 2026
Shipped: a new
gain_audiotask.gain_audio(audio, gain_db, start_seconds=None, duration_seconds=None, start_frame=None, num_frames=None, fps=None, sample_rate=None)applies a gain (dB, negative ducks / positive boosts) to a region of a track in one step, replacing the five-stepslice_audio→gain→mix_audio→rejoin→pair_audiochain for ducking a scene.slice_audio: seconds, or frames+fps, resolved the same way (dw/tasks/audio_utils.py).dw/tasks/task.py(register_command("gain_audio", ...), non-model so it goes through the standarddevice-popping path from resample_audio advertises a parameter its implementation does not accept #185 automatically).dw/task_domains.py(start_framenon-negative,num_frames/duration_seconds/fps/sample_ratepositive, etc.) — pinned bytests/test_task_domains.py's registry tests.sample_ratereinterpretation caveat asslice_audio(templates/assemble-and-score feeds the mix sample_rate into slice_audio, which reinterprets the score instead of resampling it: a 44.1 kHz score under a 32 kHz cut plays 37.8% slow, warnings: [] #180): passingsample_raterelabels rather than resamples.slice_audioin one respect: a region reaching past the end of the track is clipped, not zero-padded — there's no real material there to gain.docs/TASKS.md(new### gain_audiosection, same format asslice_audio).Commit: 1f3c8f1, merged to
developat 09875d9, deployed tolem(get_task("gain_audio")confirms the live schema matches).Model sonnet via provider anthropic.
Proposed regression case (not added to the suite, for the tester to place): in
tests/test_audio_utils.py(or whereverslice_audio's region tests live), build a track, callgain_audioon a middle region with a negativegain_db, and assert only that region's samples changed (scaled by the expected linear gain) while samples outside the region are bit-identical to the source. A second case: a region whosestart_frame/num_frames(withfps) extends past the track's end should return output the same length as the input (clipped, not padded).dkackman commentedon Sep 17, 2026
Verified over MCP against dw
0.4.0-beta.6onlem, workspaceqa-verify-187, fixtureasset:qa-cast/ep11-bed.wav(19.667 s, 32 kHz mono, peak −28.886 dBFS). Modelopusvia provideranthropic.Schema.
get_task("gain_audio")listsaudio,gain_db(required),start_seconds/duration_seconds,start_frame/num_frames/fps,sample_rate,device, with domains (start_*non_negative,duration_seconds/num_frames/fps/sample_ratepositive).Repro — duck a middle region (seconds). Job
6751bf30fdb4:gain_audio{gain_db: -12, start_seconds: 5, duration_seconds: 3}→ succeeded in 1.1 s.get_gallery_metadata(envelope=true)on the output vs. the source asset, second by second:rms_dbfsandpeak_dbfsnumerically identical to the source, to full float precisionduration_seconds19.666656 unchanged, sample rate/channels unchangedAdjacent 1 — frames, positive gain, region past the end. Job
bc12d804d750:{gain_db: 6, start_frame: 400, num_frames: 200, fps: 24}(frame 400 = 16.667 s; region would end at 25 s on a 19.67 s track) → succeeded in 0.6 s. Output duration still 19.666656 s — clipped, not padded, as the hand-off says. Seconds 16–18 peak −28.886 → −22.884 (+6.00 dB); the final partial second −50.22 → −44.24 (+5.98); seconds 0–15 identical to the source.Adjacent 2 — domain pins.
validate_workflowwithstart_frame: -1, num_frames: 0→valid: false, two errors atsteps[0].task.arguments.start_frame/.num_frames, each naminggain_audioand the bound.Observation (not a bounce).
gain_audiowithgain_dband no region at all validates (valid: true, plan returned) and then fails at run time in 0.6 s with a clear message: "gain_audio needs either 'start_seconds'/'duration_seconds' or 'start_frame'/'num_frames'/'fps' to address the region to gain". Same class as thevalidate_workflow-doesn't-know-cross-argument-rules gap noted on #185/S-F041; the runtime refusal is immediate and well-worded, so I'm recording it here rather than filing.Expected behavior from the issue body is met: a one-step per-region gain, addressable in seconds or frames, in place of the five-step chain. Closing as completed. Regression case added to
regression-suite-smoke.md(S-F042) — the implementer's proposed pytest-level sample assertions belong in the dw repo's own suite; the MCP-side case pins the same properties through the per-second envelope.Develop (#241)