# Guitar

> A plucked guitar device: a string a pick strikes, that rings and fades with its highs first, through the body and a tone control, with its decay and palm mute, a panel with a keyboard, a Web Audio engine and tools for agents.

```tsx
"use client";

import { noteToFrequency } from "@audio-ui/core/pitch";
import { useCallback, useEffect, useRef, useState } from "react";
import { GuitarPanel } from "@/components/audio-ui/devices/guitar/panel";
import { useMasterBus } from "@/hooks/use-master-bus";
import { useMidiInput } from "@/hooks/use-midi-input";
import {
  type GuitarNote,
  type GuitarState,
  guitarDevice,
} from "@/components/audio-ui/devices/guitar/device";
import {
  createGuitarEngine,
  type GuitarEngine,
} from "@/components/audio-ui/devices/guitar/engine";
import { webAudio } from "@/lib/web-audio";

/** How many strings sound at once: a ninth takes the voice held longest. */
const VOICES = 8;
/** The note the panel's keyboard holds: one at a time, under its own name. */
const PANEL_NOTE = "panel";

interface Voice {
  engine: GuitarEngine;
  holding: string | null;
  since: number;
}

/**
 * A plucked guitar you can plug in: its state, a panel that draws and plays
 * it, and a Web Audio engine that makes the sound, each its own file. The
 * engine plays one string; eight of them play chords. Play the keyboard, or
 * connect a MIDI keyboard and play chords on it.
 */
export function BlockGuitar() {
  const [state, setState] = useState<GuitarState>(() =>
    guitarDevice.defaults()
  );
  const master = useMasterBus(1);
  const [voices, setVoices] = useState<Voice[] | null>(null);
  const stateRef = useRef(state);
  stateRef.current = state;

  useEffect(() => {
    const ctx = webAudio.getContext();
    if (!(ctx && master)) {
      return;
    }
    const made = Array.from({ length: VOICES }, () => {
      const engine = createGuitarEngine(ctx, stateRef.current);
      engine.output.connect(master);
      return { engine, holding: null, since: 0 };
    });
    setVoices(made);
    return () => {
      for (const voice of made) {
        voice.engine.dispose();
      }
      setVoices(null);
    };
  }, [master]);

  // Every voice follows the state, whoever changed it: a control or a call.
  useEffect(() => {
    for (const voice of voices ?? []) {
      voice.engine.update(state);
    }
  }, [state, voices]);

  /** A note on the voice that holds it, else a free one, else the one held longest. */
  const noteOn = useCallback(
    (note: GuitarNote, id: string) => {
      if (!voices) {
        return;
      }
      const voice =
        voices.find((item) => item.holding === id) ??
        voices.find((item) => item.holding === null) ??
        voices.reduce((a, b) => (a.since <= b.since ? a : b));
      if (voice.holding !== null && voice.holding !== id) {
        voice.engine.noteOff();
      }
      voice.holding = id;
      voice.since = performance.now();
      voice.engine.noteOn(note, { id });
    },
    [voices]
  );
  const noteOff = useCallback(
    (id: string) => {
      const voice = voices?.find((item) => item.holding === id);
      if (voice) {
        voice.holding = null;
        voice.engine.noteOff({ id });
      }
    },
    [voices]
  );

  const playKey = useCallback(
    (note: GuitarNote) => noteOn(note, PANEL_NOTE),
    [noteOn]
  );
  const releaseKey = useCallback(() => noteOff(PANEL_NOTE), [noteOff]);
  const midi = useMidiInput({
    onNoteOff: (pitch: number) => noteOff(`midi-${pitch}`),
    onNoteOn: (pitch: number) =>
      noteOn({ frequency: noteToFrequency(pitch) }, `midi-${pitch}`),
  });

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

A plucked guitar string. A pick strikes it, a burst brighter the harder it is picked, and the string rings and fades, its highs dying first, as a real string's do. It is in tune to the cent at every pitch, so it plays along with the other instruments. The body warms its low end, a tone control darkens the lot, and the palm can mute the strings for short, dry notes. 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/guitar
```

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

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

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

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

import { noteToFrequency } from "@audio-ui/core/pitch";
import { useCallback, useEffect, useRef, useState } from "react";
import { GuitarPanel } from "@/components/audio-ui/devices/guitar/panel";
import { useMasterBus } from "@/hooks/use-master-bus";
import { useMidiInput } from "@/hooks/use-midi-input";
import {
  type GuitarNote,
  type GuitarState,
  guitarDevice,
} from "@/components/audio-ui/devices/guitar/device";
import {
  createGuitarEngine,
  type GuitarEngine,
} from "@/components/audio-ui/devices/guitar/engine";
import { webAudio } from "@/lib/web-audio";

/** How many strings sound at once: a ninth takes the voice held longest. */
const VOICES = 8;
/** The note the panel's keyboard holds: one at a time, under its own name. */
const PANEL_NOTE = "panel";

interface Voice {
  engine: GuitarEngine;
  holding: string | null;
  since: number;
}

/**
 * A plucked guitar you can plug in: its state, a panel that draws and plays
 * it, and a Web Audio engine that makes the sound, each its own file. The
 * engine plays one string; eight of them play chords. Play the keyboard, or
 * connect a MIDI keyboard and play chords on it.
 */
export function BlockGuitar() {
  const [state, setState] = useState<GuitarState>(() =>
    guitarDevice.defaults()
  );
  const master = useMasterBus(1);
  const [voices, setVoices] = useState<Voice[] | null>(null);
  const stateRef = useRef(state);
  stateRef.current = state;

  useEffect(() => {
    const ctx = webAudio.getContext();
    if (!(ctx && master)) {
      return;
    }
    const made = Array.from({ length: VOICES }, () => {
      const engine = createGuitarEngine(ctx, stateRef.current);
      engine.output.connect(master);
      return { engine, holding: null, since: 0 };
    });
    setVoices(made);
    return () => {
      for (const voice of made) {
        voice.engine.dispose();
      }
      setVoices(null);
    };
  }, [master]);

  // Every voice follows the state, whoever changed it: a control or a call.
  useEffect(() => {
    for (const voice of voices ?? []) {
      voice.engine.update(state);
    }
  }, [state, voices]);

  /** A note on the voice that holds it, else a free one, else the one held longest. */
  const noteOn = useCallback(
    (note: GuitarNote, id: string) => {
      if (!voices) {
        return;
      }
      const voice =
        voices.find((item) => item.holding === id) ??
        voices.find((item) => item.holding === null) ??
        voices.reduce((a, b) => (a.since <= b.since ? a : b));
      if (voice.holding !== null && voice.holding !== id) {
        voice.engine.noteOff();
      }
      voice.holding = id;
      voice.since = performance.now();
      voice.engine.noteOn(note, { id });
    },
    [voices]
  );
  const noteOff = useCallback(
    (id: string) => {
      const voice = voices?.find((item) => item.holding === id);
      if (voice) {
        voice.holding = null;
        voice.engine.noteOff({ id });
      }
    },
    [voices]
  );

  const playKey = useCallback(
    (note: GuitarNote) => noteOn(note, PANEL_NOTE),
    [noteOn]
  );
  const releaseKey = useCallback(() => noteOff(PANEL_NOTE), [noteOff]);
  const midi = useMidiInput({
    onNoteOff: (pitch: number) => noteOff(`midi-${pitch}`),
    onNoteOn: (pitch: number) =>
      noteOn({ frequency: noteToFrequency(pitch) }, `midi-${pitch}`),
  });

  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <GuitarPanel
        activeNotes={midi.activeNotes}
        onNoteOff={releaseKey}
        onNoteOn={playKey}
        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 guitar 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 reads 0 to 10, as a panel's sliders do, and shows the value it sets in the unit the sound takes.

| Control | Range | Default | What it does |
| --- | --- | --- | --- |
| Tone | 1.5 kHz to 12 kHz | 4.2 kHz | Where the tone control darkens the sound, from 1.5 kHz to 12 kHz. |
| Decay | 0.4 s to 6 s | 1.5 s | How long an open string rings before it falls silent, from under half a second to six seconds. |
| Palm mute | 0% to 90% | 0% | How much the palm mutes the strings: at the top, a note rings a tenth as long. |
| Body | +0 dB to +10 dB | +5 dB | How much the guitar's body warms the low end, at its resonance. |
| Volume | -∞ dB to -3 dB | -14.1 dB | The level of the output. |

## Panel

The panel lays out the string's tone, decay and palm mute, the body and the volume, and has a keyboard from the open low E up three octaves, E2 to E5, that picks the strings. With `activeNotes`, the notes held now, such as on a MIDI keyboard, light up their keys. The panel sends notes out and does not make the sound, so it plays through any engine.

```tsx
import { GuitarPanel } from "@/components/audio-ui/devices/guitar/panel";
import { guitarDevice } from "@/components/audio-ui/devices/guitar/device";

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

<GuitarPanel 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({ frequency: 196, velocity: 0.8 }); // G3, picked
engine.noteOff(); // the fretting hand lifts off

// Scheduled ahead on the audio clock, by a host that plays notes from a song:
engine.noteOn({ frequency: 329.63 }, { at: time, id: "e4" });
engine.noteOff({ at: time + length, id: "e4" });
```

An engine plays one string. For chords, make as many as the notes you hold and give each note its own: the [arrangement](/docs/components/base/arrangement)'s engine does it for a MIDI track, and the block above keeps eight. A note picks the string at its pitch, louder and brighter the higher its velocity, and it rings for the decay, shortened by the palm mute, however long the note is held; let go before, and the string falls silent at once. A note while one sounds cuts it and picks anew, and a `noteOff` whose `id` is not the sounding note's lets nothing go. The tone, the body and the volume move a ringing string; the decay and the mute take the next. The string is drawn when its note comes, as samples, by `stringSamples`, which the engine exports. An agent plays the guitar with the `play` tool.
