# Ducker

> A ducker device: threshold, ratio, knee, range, attack and release, a panel that draws the gain for the key's level, a Web Audio engine with a sidechain, and tools for agents.

```tsx
"use client";

import { useState } from "react";
import {
  type DuckerState,
  duckerDevice,
} from "@/components/audio-ui/devices/ducker/device";
import { createDuckerEngine } from "@/components/audio-ui/devices/ducker/engine";
import { DuckerPanel } from "@/components/audio-ui/devices/ducker/panel";
import { useDeviceLoop } from "@/hooks/use-device-loop";
import { useMasterBus } from "@/hooks/use-master-bus";

const MASTER_VOLUME = 0.7;
/** A deep, quick duck that breathes back between the kicks. */
const START = {
  attackMs: 5,
  rangeDb: 18,
  releaseMs: 180,
  thresholdDb: -36,
};

/**
 * A ducker you can plug in: its state, a panel that draws the gain it puts on
 * the sound for the key's level and an engine that plays it, each its own
 * file. A held chord, ducked by the kick of the loop: lower the threshold or
 * raise the range and it pumps harder, lengthen the release and it breathes.
 */
export function BlockDucker() {
  const [state, setState] = useState<DuckerState>(
    () => duckerDevice.apply(duckerDevice.defaults(), START).state
  );
  const master = useMasterBus(MASTER_VOLUME);
  useDeviceLoop({
    create: createDuckerEngine,
    destination: master,
    key: true,
    source: "chord",
    state,
  });
  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <DuckerPanel onValueChange={setState} value={state} />
    </div>
  );
}
```

Turns a sound down while another one plays: a voice over music, a kick under a pad. The other sound is the key, connected to the sidechain: it is only listened to, never heard. It plays the ducker of [audio.js](https://audio.js.org). It is a [device](/docs/devices): a state, a panel, an engine and tools, each its own file.

## Installation

**CLI**

```bash
npx shadcn@latest add @audio-ui/ducker
```

It installs the device's state, its engine and its panel to `components/audio-ui/devices/ducker/`. The demo above, which wires them to a loop, is `@audio-ui/block-ducker`.

**Manual**

**1. Install the packages:**

```bash
npm install @audio-ui/react @audio-ui/core
```

**2. Add the pieces you want. All of them, or only the state and the panel if your engine is elsewhere:**

```bash
npx shadcn@latest add @audio-ui/ducker-device @audio-ui/ducker-panel @audio-ui/ducker-engine
```

**3. Copy and paste the demo that wires them, if you want it.**

```tsx title="components/examples/block-ducker.tsx"
"use client";

import { useState } from "react";
import {
  type DuckerState,
  duckerDevice,
} from "@/components/audio-ui/devices/ducker/device";
import { createDuckerEngine } from "@/components/audio-ui/devices/ducker/engine";
import { DuckerPanel } from "@/components/audio-ui/devices/ducker/panel";
import { useDeviceLoop } from "@/hooks/use-device-loop";
import { useMasterBus } from "@/hooks/use-master-bus";

const MASTER_VOLUME = 0.7;
/** A deep, quick duck that breathes back between the kicks. */
const START = {
  attackMs: 5,
  rangeDb: 18,
  releaseMs: 180,
  thresholdDb: -36,
};

/**
 * A ducker you can plug in: its state, a panel that draws the gain it puts on
 * the sound for the key's level and an engine that plays it, each its own
 * file. A held chord, ducked by the kick of the loop: lower the threshold or
 * raise the range and it pumps harder, lengthen the release and it breathes.
 */
export function BlockDucker() {
  const [state, setState] = useState<DuckerState>(
    () => duckerDevice.apply(duckerDevice.defaults(), START).state
  );
  const master = useMasterBus(MASTER_VOLUME);
  useDeviceLoop({
    create: createDuckerEngine,
    destination: master,
    key: true,
    source: "chord",
    state,
  });
  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <DuckerPanel onValueChange={setState} value={state} />
    </div>
  );
}
```

**Agent**

Give your agent the skill, and it picks the component, installs it and wires it to your audio:

```bash
npx skills add ouestlabs/audio-ui
```

Then ask it:

```text
Add the ducker from audio/ui to my project and wire it to my audio code.
```

To let the agent read the docs live, connect the [MCP server](/docs/mcp). More in [Agent Skill](/docs/agents).

## Controls

| Control | Range | Default | What it does |
| --- | --- | --- | --- |
| On | On or off | On | Turns the device on. Off, the signal passes through unchanged. |
| Threshold | -60 dB to 0 dB | -30 dB | The level of the key above which the sound is turned down. Drag the handle on the plot across to set it. |
| Ratio | 1:1 to 20:1 | 4:1 | How much the key's level over the threshold turns the sound down: at 4:1, 4 dB of key over the threshold take 3 dB off the sound. |
| Knee | 0 dB to 24 dB | 6 dB | How gradually it starts to duck around the threshold. 0 is a hard knee. |
| Range | 0 dB to 90 dB | 24 dB | The most it turns the sound down. The plot draws it as a dotted line. |
| Attack | 0.1 ms to 200 ms | 20 ms | How fast it ducks when the key comes in. |
| Release | 1 ms to 2000 ms | 300 ms | How fast the sound comes back when the key stops. |

## Panel

The panel draws the gain it puts on the sound for each level of the key, once it has settled, through the package's own compression curve: the threshold, a handle on it, and the range it never goes under.

```tsx
import { DuckerPanel } from "@/components/audio-ui/devices/ducker/panel";
import { duckerDevice } from "@/components/audio-ui/devices/ducker/device";

const [state, setState] = useState(() => duckerDevice.defaults());

<DuckerPanel onValueChange={setState} value={state} />;
```

## Engine

Create it, connect it and update it as for any device: see [The engine](/docs/devices#the-engine). Connect the key to `engine.sidechain`; see [Sidechain](/docs/devices#sidechain).

```ts
engine.reduction; // dB being taken off, read per frame
```

The browser has no ducker node. The engine plays the processor of `@audio/dynamics-ducker`'s manifest as published, in an `AudioWorklet` with two inputs, the sound and the key, fed only while it plays: off, or until the worklet is ready, the sound goes round it. With nothing on the sidechain, it follows the sound itself, as a compressor would. The threshold, the ratio, the knee and the range change as it plays. A new attack or release builds it again: at most every 50 ms, the new one first playing the last 100 ms of the sound and of the key, then taking over in a 30 ms fade.

Measured with a tone under an 80 Hz key, the sound is turned down to within 0.7 dB of the curve, at every level of the key: 7.5 dB drawn for a key at -20 dB, 6.8 dB heard.
