# Organ

> A tonewheel organ device: nine drawbars, a percussion, a key click, a drive and a rotary speaker, with six registrations, a panel with drawbars and 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 { OrganPanel } from "@/components/audio-ui/devices/organ/panel";
import { useMasterBus } from "@/hooks/use-master-bus";
import { useMidiInput } from "@/hooks/use-midi-input";
import {
  type OrganNote,
  type OrganState,
  organDevice,
} from "@/components/audio-ui/devices/organ/device";
import {
  createOrganEngine,
  type OrganEngine,
} from "@/components/audio-ui/devices/organ/engine";
import { webAudio } from "@/lib/web-audio";

/** How many keys 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: OrganEngine;
  holding: string | null;
  since: number;
}

/**
 * A tonewheel organ 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 key; eight of them play chords. Play the keyboard, or
 * connect a MIDI keyboard and play chords on it.
 */
export function BlockOrgan() {
  const [state, setState] = useState<OrganState>(() => organDevice.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 = createOrganEngine(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: OrganNote, 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: OrganNote) => noteOn(note, PANEL_NOTE),
    [noteOn]
  );
  const releaseKey = useCallback(() => noteOff(PANEL_NOTE), [noteOff]);
  const midi = useMidiInput({
    onNoteOff: (pitch: number) => noteOff(`midi-${pitch}`),
    onNoteOn: (pitch: number, velocity: number) =>
      noteOn({ frequency: noteToFrequency(pitch), velocity }, `midi-${pitch}`),
  });

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

A tonewheel organ. Every key sounds nine sines at the organ's harmonics, from the 16' an octave under the key to the 1' three octaves over it, each at the level its drawbar is pulled to. A percussion on the second or third harmonic and a tick of key click open each note, and the sound goes through a drive and a rotary speaker, whose horns take a moment to reach a new speed. Start from one of its six registrations (Jazz, Gospel, Rock, Church, Flute, Full), then pull the drawbars. 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/organ
```

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

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

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

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

import { noteToFrequency } from "@audio-ui/core/pitch";
import { useCallback, useEffect, useRef, useState } from "react";
import { OrganPanel } from "@/components/audio-ui/devices/organ/panel";
import { useMasterBus } from "@/hooks/use-master-bus";
import { useMidiInput } from "@/hooks/use-midi-input";
import {
  type OrganNote,
  type OrganState,
  organDevice,
} from "@/components/audio-ui/devices/organ/device";
import {
  createOrganEngine,
  type OrganEngine,
} from "@/components/audio-ui/devices/organ/engine";
import { webAudio } from "@/lib/web-audio";

/** How many keys 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: OrganEngine;
  holding: string | null;
  since: number;
}

/**
 * A tonewheel organ 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 key; eight of them play chords. Play the keyboard, or
 * connect a MIDI keyboard and play chords on it.
 */
export function BlockOrgan() {
  const [state, setState] = useState<OrganState>(() => organDevice.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 = createOrganEngine(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: OrganNote, 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: OrganNote) => noteOn(note, PANEL_NOTE),
    [noteOn]
  );
  const releaseKey = useCallback(() => noteOff(PANEL_NOTE), [noteOff]);
  const midi = useMidiInput({
    onNoteOff: (pitch: number) => noteOff(`midi-${pitch}`),
    onNoteOn: (pitch: number, velocity: number) =>
      noteOn({ frequency: noteToFrequency(pitch), velocity }, `midi-${pitch}`),
  });

  return (
    <div className="rounded-xl h-full w-full min-w-0">
      <OrganPanel
        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 organ 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

A drawbar pulls from 0 to 8, each step 3 dB louder; the knobs read 0 to 10.

| Control | Range | Default | What it does |
| --- | --- | --- | --- |
| Drawbar 16' | 0 to 8 | 8 | The 16', an octave under the key. |
| Drawbar 5⅓' | 0 to 8 | 8 | The 5⅓', a fifth over the key. |
| Drawbar 8' | 0 to 8 | 8 | The 8', the key itself. |
| Drawbar 4' | 0 to 8 | 0 | The 4', an octave over it. |
| Drawbar 2⅔' | 0 to 8 | 0 | The 2⅔', an octave and a fifth over it. |
| Drawbar 2' | 0 to 8 | 0 | The 2', two octaves over it. |
| Drawbar 1⅗' | 0 to 8 | 0 | The 1⅗', two octaves and a third over it. |
| Drawbar 1⅓' | 0 to 8 | 0 | The 1⅓', two octaves and a fifth over it. |
| Drawbar 1' | 0 to 8 | 0 | The 1', three octaves over it. |
| Percussion | On or off | Off | A short note on each key's attack. |
| Percussion harmonic | 2nd, 3rd | 3rd | The percussion's pitch: the second or the third harmonic. |
| Percussion decay | Fast, Slow | Fast | How fast the percussion dies away. |
| Key click | 0 to 10 | 3 | The tick of noise as a key's contacts close. |
| Drive | 0 to 10 | 2 | How hard the sound is driven into the overdrive. |
| Rotary | Stop, Slow, Fast | Slow | The rotary speaker: stopped, slow or fast. |
| Volume | -∞ dB to -3 dB | -14.1 dB | The level of the output. |

## Panel

The panel loads a registration from the menu in its title bar, beside the rotary speaker's switch, draws the nine drawbars as faders, the percussion, the key click, the drive and the volume beside them, and has a keyboard of two octaves that plays the organ. `organPreset(name)` gives a registration's whole state, and `ORGAN_PRESETS` lists them. 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 { OrganPanel } from "@/components/audio-ui/devices/organ/panel";
import { organDevice } from "@/components/audio-ui/devices/organ/device";

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

<OrganPanel 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: 261.63 }); // a key now
engine.noteOff(); // stops it

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

An engine plays one key, through its own drive and rotary speaker. 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 key sounds at once and stops at once, as an organ's does, with no click. A note while one is held cuts it and sounds anew, and a `noteOff` whose `id` is not the held note's lets nothing go. A drawbar moved sounds on a held key, and a registration with few drawbars out is lifted part of the way so it is not far quieter than a full one. An agent plays the organ with the `play` tool.
