Skip to content

Lower music under speech - #30

Open
pallaoro wants to merge 6 commits into
mainfrom
audio-ducking
Open

pallaoro wants to merge 6 commits into
mainfrom
audio-ducking

Conversation

@pallaoro

@pallaoro pallaoro commented Oct 6, 2026

Copy link
Copy Markdown
Member

Music now dips while someone on the main track speaks and comes back up in the pauses, so the voice stays clear without hand-drawn volume changes.

What you get

  • Inspector → Audio → Lower under speech: Off, −6 dB, −12 dB or −18 dB. An agent sets "duck": 12 (dB, 1 to 40) on an audio element; a depth that is none of the three shows as "Lowered by N dB".
  • On the timeline, each dip is drawn on the music clip, the same way fades are.
  • In the preview, the music follows the same envelope the export renders, so what you hear while editing is what you get.
  • When nothing can dip, the inspector says why: no video on the main track, transcripts still coming, the clips with speech are muted, or no speech in the transcripts.

How it works

Not a compressor: a browser can't reproduce one, so preview and export would differ. src/shared/duck.ts places the dips from the transcripts' timing instead, the way captions are placed:

  • the speech spans are each clip's cues cut to the part it plays and moved to its place on the cut, counting only clips whose sound is in the mix;
  • each dip starts 0.15 s before speech (so the first word is already clear), holds, and comes back over 0.5 s; a pause under a second stays down instead of pumping;
  • the export turns duck into the edit service's volume envelope (keyframes, linear between them), and the preview evaluates the same function. Only the setting is stored, so the dips follow every trim, split and reorder.

The transcript language is the captions' language, English when captions are off. Footage kept in the app's own storage has no transcript, so nothing dips under it.

Deploy order

Needs the edit service's envelope field, which is in review alongside this. Until it is live, exporting a project with ducking on is refused with a validation error; projects without it are unaffected.

Verified

  • tsc, vite build, vitest: 100 pass (new: duck.test.ts for spans, envelopes, the point cap and gain; duck-export.test.ts for the document the export sends).
  • 2000 random talk patterns over 290 s: every envelope passes the edit service's own validator (most points 190, cap 200).
  • The edit service renders the envelope exactly: a 0.25 dip measures −12 dB with real ffmpeg (8.0.1 and 4.4.2).
  • Local wrangler dev with a seeded transcript (speech 2–5 s and 9–11 s): the dips draw in the right places, and while playing the music's volume reads 0.80, then 0.201 from 2.0 to 5.0 s and 9.0 to 11.0 s, then back up over 0.5 s. Off removes the field; −6 dB saves duck: 6; muting the speaking clip shows the muted hint.

Not done: the Ask box can't set ducking (it has no audio-track operations yet), and there is no manual keyframe editing, although the service field supports it.

An audio clip can dip while someone on the main track speaks and come
back up in the pauses: Off, -6, -12 or -18 dB in the inspector, the
duck field (dB) for agents.

The dips are placed from the transcripts' timing (0.15 s before speech,
0.5 s back up, pauses under a second stay down) and sent to the edit
service as a volume envelope. The preview evaluates the same envelope,
so what plays while editing is what exports, and the timeline shows
each dip on the clip. Like captions, only the setting is stored, so the
dips follow every trim, split and reorder.
The media service transcribes in the language it is asked for and does
not detect the one spoken, so non-English footage with captions never
set would be transcribed as English. The setting is the captions'
language, which can be changed while captions are off.
@pallaoro

pallaoro commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Added: the ducking control now says which language it listens for ("Listens for speech in Italiano. Change it under Captions."). The media service transcribes in the language it is asked for and does not detect the spoken one, so without this, non-English footage in a project whose captions were never set would be transcribed as English and nothing would dip, with no visible reason. tsc, vitest (100) and build pass; checked on local dev.

# Conflicts:
#	src/client/edit.tsx
#	src/server/edl.ts
#	src/server/export.ts
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