---
title: Auto Pan
description: "An auto pan and tremolo as a device you can plug in: a mode, a waveform, a rate and a depth as a state, a panel that draws the movement, a Web Audio engine and the tools an agent drives it with."
component: true
base: base
---

<ComponentPreview styleName="base-nova" name="block-auto-pan" />

Moves the sound with a slow oscillator: in pan mode from one speaker to the other, in tremolo mode its level up and down, with a sine, triangle, saw or square wave. The panel draws the movement over two seconds. It is a device like the [compressor](/docs/components/compressor): a state, a panel that draws it, an engine that plays it and tools an agent calls, each its own file.

## Installation

<CodeTabs>

<TabsList>
  <TabsTrigger value="cli">CLI</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value="cli">

```bash component
npx shadcn@latest add @audio-ui/block-auto-pan
```

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Install the packages:</Step>

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

<Step>Add the pieces you want. All of them, or only the state and the panel if your engine is elsewhere:</Step>

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

<Step>Copy and paste the demo that wires them, if you want it.</Step>

<ComponentSource
  styleName="base-nova"
  name="block-auto-pan"
  title="components/examples/block-auto-pan.tsx"
/>

</Steps>

</TabsContent>

</CodeTabs>

## Usage

```tsx
import { AutoPanPanel } from "@/components/audio-ui/devices/auto-pan/panel";
import { autoPanDevice } from "@/components/audio-ui/devices/auto-pan/device";

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

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

The state is a plain object keyed by parameter id, so a host stores it, saves it with its project and sends it where it likes. `autoPanWave(shape, phase)` is the wave and `autoPanSweep(state)` what the panel draws.

## The engine

```ts
import { createAutoPanEngine } from "@/components/audio-ui/devices/auto-pan/engine";

const engine = createAutoPanEngine(ctx, state);
source.connect(engine.input);
engine.output.connect(ctx.destination);
engine.update(nextState); // parameters ramp: no click
engine.dispose();
```

One oscillator feeds both a stereo panner and a gain; the mode sets which of the two depths is on. In tremolo mode the level swings around the middle of its range, so a depth of 100 % goes from silence to full level.

## Synced to the tempo

Switch **Sync** on and the rate is a note value instead of hertz: one cycle of the oscillator for each `1/4`, `1/8t` (a triplet), `1/2d` (dotted) and so on, from 1/32 to a whole note, read against the host's tempo. `1/4` at 120 BPM is 2 Hz, and the rate is held between a cycle in 50 seconds and 40 Hz. The tempo is the host's: give it to the engine with the first state or with any update.

```ts
const engine = createAutoPanEngine(ctx, state, { tempo }); // { bpm: 100, beatsPerBar: 4, beatUnit: 4 }
engine.update(nextState, { tempo: { ...tempo, bpm: 120 } }); // the rate follows
engine.update(otherState); // no context: the last tempo holds
```

The panel takes the same tempo as a prop, `tempo`, and draws the oscillator at the rate it really has. With Sync on the rate knob is drawn off, with it off the note list is. The tempo does not come from an agent's tool call: `audioui_auto_pan_set` can switch Sync on and pick a note, and the host decides the tempo it is read against.

## The tools

```ts
import { deviceTools } from "@audio-ui/core/device";
import { autoPanDevice } from "@/components/audio-ui/devices/auto-pan/device";

const [set, reset] = deviceTools(autoPanDevice);
```

`set` and `reset` change the state, with the ranges the device enforces: a value out of range is brought in and reported, a wrong one is refused by name, and a call never throws. A choice such as the curve or the mode takes only the values of its options.

## Accessibility

Every control is a native input: the knobs are sliders named after their parameter, with the value and unit as the text value, the choices are toggle buttons, and everything takes the keyboard.
