# Drum Synth

> A synthesized drum device: a kick, a snare, a clap, a closed or an open hat, a tom, a rim or a cymbal from oscillators and noise, tuned, with its decay, tone and snap, a panel with a pad, a Web Audio engine and tools for agents.

```tsx
"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { DrumSynthPanel } from "@/components/audio-ui/devices/drum-synth/panel";
import { useMasterBus } from "@/hooks/use-master-bus";
import { useMidiInput } from "@/hooks/use-midi-input";
import {
  type DrumSynthNote,
  type DrumSynthState,
  drumSynthDevice,
} from "@/components/audio-ui/devices/drum-synth/device";
import {
  createDrumSynthEngine,
  type DrumSynthEngine,
} from "@/components/audio-ui/devices/drum-synth/engine";
import { webAudio } from "@/lib/web-audio";

/**
 * A synthesized drum you can plug in: its state, a panel that draws and
 * strikes it, and a Web Audio engine that makes the sound, each its own file.
 * One engine plays every hit, each ringing for its decay. Strike the pad, or
 * play it from a MIDI keyboard or pad controller, as hard as you like.
 */
export function BlockDrumSynth() {
  const [state, setState] = useState<DrumSynthState>(() =>
    drumSynthDevice.defaults()
  );
  const master = useMasterBus(1);
  const [engine, setEngine] = useState<DrumSynthEngine | null>(null);
  const stateRef = useRef(state);
  stateRef.current = state;

  useEffect(() => {
    const ctx = webAudio.getContext();
    if (!(ctx && master)) {
      return;
    }
    const made = createDrumSynthEngine(ctx, stateRef.current);
    made.output.connect(master);
    setEngine(made);
    return () => {
      made.dispose();
      setEngine(null);
    };
  }, [master]);

  // The engine follows the state, whoever changed it: a control or a call.
  useEffect(() => {
    engine?.update(state);
  }, [engine, state]);

  const strike = useCallback(
    (note: DrumSynthNote) => {
      const ctx = webAudio.getContext();
      if (ctx?.state === "suspended") {
        ctx.resume();
      }
      engine?.noteOn(note);
    },
    [engine]
  );
  const midi = useMidiInput({
    onNoteOn: (_pitch: number, velocity: number) => strike({ velocity }),
  });

  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <DrumSynthPanel
        activeNotes={midi.activeNotes}
        onNoteOn={strike}
        onValueChange={setState}
        value={state}
      />
    </div>
  );
}
```

A drum made of sound, not of a sample. Pick its voice, a kick, a snare, a clap, a closed or an open hat, a tom, a rim or a cymbal, and shape it: its tuning, how long it rings, how bright it is and how sharp its attack. Each voice is a sine whose pitch falls, filtered noise, or both, and a harder hit is louder and brighter, so a pattern's accents sound as accents. It is what a [drum rack](/docs/components/base/rack#drum-racks)'s pad plays: one Drum Synth per pad, each with its own chain of effects. 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/drum-synth
```

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

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

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

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

import { useCallback, useEffect, useRef, useState } from "react";
import { DrumSynthPanel } from "@/components/audio-ui/devices/drum-synth/panel";
import { useMasterBus } from "@/hooks/use-master-bus";
import { useMidiInput } from "@/hooks/use-midi-input";
import {
  type DrumSynthNote,
  type DrumSynthState,
  drumSynthDevice,
} from "@/components/audio-ui/devices/drum-synth/device";
import {
  createDrumSynthEngine,
  type DrumSynthEngine,
} from "@/components/audio-ui/devices/drum-synth/engine";
import { webAudio } from "@/lib/web-audio";

/**
 * A synthesized drum you can plug in: its state, a panel that draws and
 * strikes it, and a Web Audio engine that makes the sound, each its own file.
 * One engine plays every hit, each ringing for its decay. Strike the pad, or
 * play it from a MIDI keyboard or pad controller, as hard as you like.
 */
export function BlockDrumSynth() {
  const [state, setState] = useState<DrumSynthState>(() =>
    drumSynthDevice.defaults()
  );
  const master = useMasterBus(1);
  const [engine, setEngine] = useState<DrumSynthEngine | null>(null);
  const stateRef = useRef(state);
  stateRef.current = state;

  useEffect(() => {
    const ctx = webAudio.getContext();
    if (!(ctx && master)) {
      return;
    }
    const made = createDrumSynthEngine(ctx, stateRef.current);
    made.output.connect(master);
    setEngine(made);
    return () => {
      made.dispose();
      setEngine(null);
    };
  }, [master]);

  // The engine follows the state, whoever changed it: a control or a call.
  useEffect(() => {
    engine?.update(state);
  }, [engine, state]);

  const strike = useCallback(
    (note: DrumSynthNote) => {
      const ctx = webAudio.getContext();
      if (ctx?.state === "suspended") {
        ctx.resume();
      }
      engine?.noteOn(note);
    },
    [engine]
  );
  const midi = useMidiInput({
    onNoteOn: (_pitch: number, velocity: number) => strike({ velocity }),
  });

  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <DrumSynthPanel
        activeNotes={midi.activeNotes}
        onNoteOn={strike}
        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 drum synth 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

Every number but the tuning reads 0 to 10, as a panel's sliders do, and the middle is the voice as it comes.

| Control | Range | Default | What it does |
| --- | --- | --- | --- |
| Voice | Kick, Snare, Clap, Closed hat, Open hat, Tom, Rim, Cymbal | Kick | Which drum it is. |
| Tune | -12 st to +12 st | 0 st | How far its pitch moves, or its noise's filter, from an octave down to an octave up. |
| Decay | 0 to 10 | 5 | How long it rings: from an eighth of the voice's own ring to eight times it. |
| Tone | 0 to 10 | 5 | How bright it is: its noise's filter from half as high to twice. On the kick, past the middle, the beater's click. |
| Snap | 0 to 10 | 5 | How sharp its attack: the kick's and the tom's pitch drop, the snare's wires against its body, the clap's extra hands, the rim's crack. |
| Volume | -∞ dB to 0 dB | -8 dB | The level of the output. |

## Panel

The panel lays out the voice and its tuning, the decay, the tone and the snap, the volume, and a pad that strikes it, harder near its top. With `activeNotes`, the notes held now, such as on a MIDI keyboard or from a sequencer, the pad lights up. The panel sends its strikes out and does not make the sound, so it plays through any engine.

```tsx
import { DrumSynthPanel } from "@/components/audio-ui/devices/drum-synth/panel";
import { drumSynthDevice } from "@/components/audio-ui/devices/drum-synth/device";

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

<DrumSynthPanel 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.noteOn({ velocity: 0.9 }); // a hit, hard

// Scheduled ahead on the audio clock, by a host that plays a pattern:
engine.noteOn({ velocity: 0.4 }, { at: time });
```

One engine plays every hit: each is made when it comes, from the state at that moment, and rings for its decay whatever the note's length, so `noteOff` lets nothing go. To cut a hat short, as an open hat is when the closed one is struck, a drum rack puts its pads in a choke group. The noise every hit reads from is made once per audio context, each hit starting somewhere else in it, so no two sound alike. The volume moves what rings; the other controls take the next hit. An agent strikes it with the `play` tool.
