# Compander

> A compander device: a transfer curve drawn with four points, attack and release, a panel with a handle on each point, a Web Audio engine and tools for agents.

```tsx
"use client";

import { useState } from "react";
import {
  type CompanderState,
  companderDevice,
} from "@/components/audio-ui/devices/compander/device";
import { createCompanderEngine } from "@/components/audio-ui/devices/compander/engine";
import { CompanderPanel } from "@/components/audio-ui/devices/compander/panel";
import { useDeviceLoop } from "@/hooks/use-device-loop";
import { useMasterBus } from "@/hooks/use-master-bus";

const MASTER_VOLUME = 0.7;

/**
 * A compander you can plug in: its state, a panel that draws its curve with a
 * handle on each point and an engine that plays it, each its own file and
 * none knowing the others. Play the loop and drag the points: under the
 * straight line of a clean sound they turn it down, over it up.
 */
export function BlockCompander() {
  const [state, setState] = useState<CompanderState>(() =>
    companderDevice.defaults()
  );
  const master = useMasterBus(MASTER_VOLUME);
  useDeviceLoop({
    create: createCompanderEngine,
    destination: master,
    state,
  });
  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <CompanderPanel onValueChange={setState} value={state} />
    </div>
  );
}
```

Draws its own transfer curve. Four points set the level out at -90, -60, -20 and 0 dB in, and the curve runs between them in straight lines. A point under the straight line of a clean sound turns the sound down at that level, one over it turns it up: one curve brings the loud parts down, lifts the quiet ones or closes on the noise. It plays the compander 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/compander
```

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

**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/compander-device @audio-ui/compander-panel @audio-ui/compander-engine
```

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

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

import { useState } from "react";
import {
  type CompanderState,
  companderDevice,
} from "@/components/audio-ui/devices/compander/device";
import { createCompanderEngine } from "@/components/audio-ui/devices/compander/engine";
import { CompanderPanel } from "@/components/audio-ui/devices/compander/panel";
import { useDeviceLoop } from "@/hooks/use-device-loop";
import { useMasterBus } from "@/hooks/use-master-bus";

const MASTER_VOLUME = 0.7;

/**
 * A compander you can plug in: its state, a panel that draws its curve with a
 * handle on each point and an engine that plays it, each its own file and
 * none knowing the others. Play the loop and drag the points: under the
 * straight line of a clean sound they turn it down, over it up.
 */
export function BlockCompander() {
  const [state, setState] = useState<CompanderState>(() =>
    companderDevice.defaults()
  );
  const master = useMasterBus(MASTER_VOLUME);
  useDeviceLoop({
    create: createCompanderEngine,
    destination: master,
    state,
  });
  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <CompanderPanel 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 compander 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. |
| At -90 dB | -90 dB to 0 dB | -90 dB | The level out for a sound at -90 dB in, and with it the slope from silence. Drag the first handle up or down. |
| At -60 dB | -90 dB to 0 dB | -60 dB | The level out for a sound at -60 dB in. |
| At -20 dB | -90 dB to 0 dB | -20 dB | The level out for a sound at -20 dB in. |
| At 0 dB | -90 dB to 0 dB | -8 dB | The level out for a sound at 0 dB in. Under 0, the loudest parts are brought down. |
| Attack | 0.1 ms to 200 ms | 5 ms | How fast it follows the level as it rises. |
| Release | 1 ms to 2000 ms | 200 ms | How fast it follows the level as it falls. |

## Panel

The panel draws the transfer curve against the straight line of a clean sound, with a handle on each point that moves up and down. The curve is measured from the package's own stream, run on a steady level until it settles.

```tsx
import { CompanderPanel } from "@/components/audio-ui/devices/compander/panel";
import { companderDevice } from "@/components/audio-ui/devices/compander/device";

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

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

## Engine

Create it, connect it and update it as for any device: see [The engine](/docs/devices#the-engine).

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

The browser has no compander node. The engine plays the processor of `@audio/dynamics-compand`'s manifest as published, in an `AudioWorklet` fed only while it plays: off, or until the worklet is ready, the sound goes round it. Every one of its values builds it again: at most every 50 ms, the new one first playing the last 100 ms of sound, then taking over in a 30 ms fade.

Measured on a steady tone from -70 to -3 dB, the output sits within 0.2 dB of the curve.
