# usePianoRollPlayback

> Plays a piano roll's notes, which you own: play, pause and stop, a loop or once through, a metronome, seeking, and the notes the roll auditions, with a playhead for the timeline and a clock for the transport.

```bash
npx shadcn@latest add @audio-ui/use-piano-roll-playback
```

```ts
import { DEFAULT_NOTE_ENVELOPE, meterOf, usePianoRollPlayback } from "@/hooks/use-piano-roll-playback";
```

- Files: `hooks/use-piano-roll-playback.ts`
- Depends on: `drum-synthesis`, `use-transport-clock`, `use-synth-voices`, `envelope-ramp`, `web-audio`; npm: `@audio-ui/core`, `@audio-ui/engine`, `@audio-ui/react`
- Used by: `block-piano-roll`, `piano-roll-playback-demo`

| Export | Kind | What it is |
| --- | --- | --- |
| `DEFAULT_NOTE_ENVELOPE` | const | ADSR-shaped, matching the Envelope primitive's own default. |
| `Meter` | interface | Where bars and beats fall, in steps, for a signature: 6/8 has beats of two steps and bars of twelve. |
| `meterOf` | function | The meter of a clip `bars` long in `tempo`'s signature. |
| `UsePianoRollPlaybackOptions` | interface |  |
| `usePianoRollPlayback` | function | Plays a piano roll's notes: play on from where it stopped, stop back at the start, the loop or once through, and the metronome. |

## Usage

```tsx
"use client";

import type { Note } from "@audio-ui/core/piano-roll";
import type { Tempo } from "@audio-ui/engine/time";
import { useEffect, useRef, useState } from "react";
import {
  PianoRoll,
  PianoRollGrid,
  PianoRollKeys,
  PianoRollStrip,
  PianoRollViewport,
} from "@/components/audio-ui/piano-roll";
import {
  Timeline,
  TimelinePlayhead,
} from "@/components/audio-ui/timeline";
import { useMasterBus } from "@/hooks/use-master-bus";
import {
  meterOf,
  usePianoRollPlayback,
} from "@/hooks/use-piano-roll-playback";
import { usePlaySwitch } from "@/hooks/use-play-switch";
import { useSynthVoices } from "@/hooks/use-synth-voices";

const TEMPO: Tempo = { beatsPerBar: 4, beatUnit: 4, bpm: 110 };
const METER = meterOf(TEMPO, 1);

const PHRASE: Note[] = [
  { id: "1", length: 2, pitch: 60, start: 0, velocity: 0.9 },
  { id: "2", length: 2, pitch: 64, start: 2, velocity: 0.5 },
  { id: "3", length: 2, pitch: 67, start: 4, velocity: 0.8 },
  { id: "4", length: 2, pitch: 72, start: 6, velocity: 1 },
  { id: "5", length: 1, pitch: 71, start: 9, velocity: 0.4 },
  { id: "6", length: 3, pitch: 67, start: 10, velocity: 0.7 },
  { id: "7", length: 3, pitch: 62, start: 13, velocity: 0.6 },
];

/** A bar on loop: edit the notes while it plays, and the next pass plays them. It plays while the play switch of its session is on. */
export function PianoRollPlaybackDemo() {
  const [notes, setNotes] = useState(PHRASE);
  const output = useMasterBus(0.7);
  const voices = useSynthVoices({ destination: output });
  const playback = usePianoRollPlayback({
    destination: output,
    meter: METER,
    metronome: true,
    notes,
    tempo: TEMPO,
    voices,
  });
  const { playing } = usePlaySwitch();
  // Only the switch starts and stops it: editing a note must not restart it.
  const controls = useRef(playback);
  controls.current = playback;
  useEffect(() => {
    if (playing) {
      controls.current.play();
    } else {
      controls.current.stop();
    }
  }, [playing]);

  return (
    <div className="flex w-full flex-col gap-3">
      <Timeline
        duration={METER.duration}
        onPositionChange={playback.seek}
        snapStep={1}
        source={playback.position}
      >
        <PianoRoll
          activePitches={playback.playingPitches}
          className="h-64 rounded-md border"
          maxPitch={72}
          minPitch={60}
          onNoteOff={playback.onNoteOff}
          onNoteOn={playback.onNoteOn}
          onValueChange={setNotes}
          value={notes}
        >
          <PianoRollViewport>
            <PianoRollStrip className="flex-1" lead={<PianoRollKeys />}>
              <PianoRollGrid
                accentStep={METER.stepsPerBeat}
                aria-label="Notes"
                gridStep={1}
              >
                <TimelinePlayhead />
              </PianoRollGrid>
            </PianoRollStrip>
          </PianoRollViewport>
        </PianoRoll>
      </Timeline>
    </div>
  );
}
```

`usePianoRollPlayback` plays the notes you hold through a synth: play, pause and stop, a loop or once through, a metronome, and the notes the roll auditions.

```tsx
import { useMasterBus } from "@/hooks/use-master-bus";
import { meterOf, usePianoRollPlayback } from "@/hooks/use-piano-roll-playback";
import { useSynthVoices } from "@/hooks/use-synth-voices";

const tempo = { beatsPerBar: 4, beatUnit: 4, bpm: 120 };
const meter = meterOf(tempo, 4); // four bars, in steps

const output = useMasterBus(0.7);
const voices = useSynthVoices({ destination: output });
const playback = usePianoRollPlayback({ meter, notes, tempo, voices });

<Timeline
  duration={meter.duration}
  onPositionChange={playback.seek}
  snapStep={1}
  source={playback.position}
>
  <PianoRoll
    activePitches={playback.playingPitches}
    onNoteOff={playback.onNoteOff}
    onNoteOn={playback.onNoteOn}
    onValueChange={setNotes}
    value={notes}
  >
    {/* … */}
  </PianoRoll>
</Timeline>;
```

The voices and the bus come from [Synthesis](/docs/guides/synthesis) and [Output](/docs/guides/output), and the clock under the hook from [Clock and time](/docs/guides/clock-and-time). The hook plays the notes; it does not hold them, so an undo, a file import or an agent can change them and the next pass plays the change. Time is in steps, four to a quarter note whatever the signature; `meterOf(tempo, bars)` gives a clip's length in steps and where its bars and beats fall. Notes are scheduled ahead on the audio clock, so they keep time when the page is busy, and each plays as the roll draws it: a deactivated note stays silent, a `chance` plays that often, a `deviation` strays its velocity each time round.

It returns `play` (on from the playhead), `pause` (stops where it is), `stop` (back to the start of the loop), `seek`, `isPlaying`, `playingPitches`, `onNoteOn` and `onNoteOff` for the roll, `position` (in steps) for the timeline's `source`, and `clock` (in seconds) for a [Transport](/docs/components/base/transport)'s `source`. It also takes `looping` (on by default), a `loopRegion` of steps, whose `commitLoopRegion` makes a moved loop heard at once, `metronome` with the `destination` it clicks into, and the `envelope` the notes play with.
