pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog

Ducking

Lower the bot's voice while the user talks over it, with AudioDuckingFilter, on any transport and every runtime.

Overview

When the user starts talking while the bot speaks, the bot normally talks on at full volume until the overlap counts as an interruption (or never, for a short "uh-huh"). AudioDuckingFilter lowers the bot's voice while the user's voice activity lasts, so it sounds like the bot heard them, and brings it back when they stop. This is pipedog only: upstream Pipecat has no ducking.

The output transport queues the bot's audio well before it plays, so the volume can't be changed where the audio is produced. The filter is an output audio filter (audioOutFilter): the transport runs it on each chunk as the chunk leaves, so the change is heard at once, whatever the transport or serializer (WebRTC, WebSocket, Twilio, Telnyx...).

Installation

bun add @pipedog/audio-ducking
import { AudioDuckingFilter } from "@pipedog/audio-ducking";

Runtimes

RuntimeWorksWhat it needs
Bun✅ yesNothing to install.
Node.js✅ yesNothing to install.
Deno✅ yesNothing to install.
Cloudflare Workers✅ yesNothing to install.

Generated from pipecat.runtimes and pipecat.compat in packages/audio-ducking/package.json: ✅ means the compat smoke test passes on that runtime.

Usage

Set the filter as the transport's audioOutFilter, and add its observer to the pipeline worker:

import { AudioDuckingFilter } from "@pipedog/audio-ducking";
import { PipelineWorker } from "@pipedog/core/pipeline/worker";
import { TransportParams } from "@pipedog/core/transports/base_transport";

const ducking = new AudioDuckingFilter({ duckedGain: 0.35 });

const params = new TransportParams({ audioOutEnabled: true, audioOutFilter: ducking });
// ... the transport and pipeline ...
const worker = new PipelineWorker(pipeline, { observers: [ducking.observer] });

The observer watches the pipeline's frames, so no processor goes in the pipeline:

FrameEffect
VADUserStartedSpeakingFrameDuck.
VADUserStoppedSpeakingFrameRestore: the user stopped without interrupting.
InterruptionFrameRestore: the bot's queued speech was flushed, its next words play at full volume.
BotStoppedSpeakingFrameRestore.

The gain moves over the first chunk after a change (a linear ramp from the previous gain), so there is no click. You can also call ducking.duck() and ducking.restore() yourself.

At runtime, change the gain or turn ducking off with filter frames:

import { FilterEnableFrame, FilterUpdateSettingsFrame } from "@pipedog/core/frames/frames";

await worker.queueFrame(new FilterUpdateSettingsFrame({ settings: { duckedGain: 0.2 } }));
await worker.queueFrame(new FilterEnableFrame({ enable: false }));

Configuration

new AudioDuckingFilter(options):

OptionTypeDefaultDescription
duckedGainnumber0.35Gain while ducked, 0.0 to 1.0.
enabledbooleantrueWhether ducking starts enabled.

The transport has one audioOutFilter. To combine ducking with another output filter, call one filter's filter() from the other's.

pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.

Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).

On this page