# piano-roll

> The piano roll's controller from @audio-ui/core: notes held or controlled, their selection and the pitches sounding, every gesture and key that edits them, the note edits as plain functions, and the rows a roll draws.

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

```ts
import { createPianoRollController, DRAG_THRESHOLD, cellStart, changeLevel, changeVelocity, /* … */ } from "@audio-ui/core/piano-roll";
```

- Adapters: `@audio-ui/react/piano-roll`, `@audio-ui/elements/piano-roll`
- Used by: `arrangement`, `dawproject`, `drum-steps`, `recording`, `use-arrangement`, `use-piano-roll-playback`

| Export | Kind | What it is |
| --- | --- | --- |
| `createPianoRollController` | function | A piano roll's behavior with no view attached: its notes, held or controlled, its selection and the pitches sounding, and every gesture and key that edits them. |
| `DRAG_THRESHOLD` | const | How far a pointer moves before a press on the grid or a note becomes a drag. |
| `PianoRollBand` | interface | A selection rectangle, in px from the grid's top left corner. |
| `PianoRollChangeEventDetails` | type | What `onValueChange` receives with each change: the `edit` it made (one for every change of a gesture), the reason, the event behind it, and `cancel()` to refuse the change. |
| `PianoRollChangeEventReason` | type | Why the notes changed: `"draw"`, `"drag"`, `"keyboard"`, `"double-click"` or `"input-change"`. |
| `PianoRollCommitEventDetails` | type | What `onValueCommitted` receives once a gesture ends: the `edit` it made (one for every change of a gesture), the reason and the event behind it. |
| `PianoRollCommitEventReason` | type | Why the notes were committed: the same reasons as a change. |
| `PianoRollController` | interface | What `createPianoRollController` returns: the snapshot to draw from, and a call for each gesture, key and command that edits the notes or the selection. |
| `PianoRollEdit` | type | What a change did to the notes. |
| `PianoRollKeyDetails` | type | A key pressed on the keyboard beside the roll, before it sounds. |
| `PianoRollNoteHandle` | type | Which pointer handle a note gesture takes: its body, or one of its edges. |
| `PianoRollNoteOffEventDetails` | type | What `onNoteOff` receives when a pitch stops: the reason and the event. |
| `PianoRollNoteOnEventDetails` | type | Why a pitch sounds: a key pressed, glided onto or played from the keyboard, or a note drawn, picked up or moved. |
| `PianoRollOptions` | interface | The options of `createPianoRollController`: the notes and the selection, each held or controlled, the timeline, the pitch range and shortest note, the mode, the draw style, the scale and folding, and the callbacks. |
| `PianoRollSelectionEventDetails` | type | What `onSelectionChange` receives: the reason (`"press"`, `"marquee"`, `"draw"`, `"drag"`, `"keyboard"` or `"double-click"`), the event, and `cancel()` to refuse it. |
| `PianoRollSnapshot` | interface | What a view draws the roll from. |
| `PianoRollTimeline` | interface | The timeline the roll sits in: its length, grid, zoom and playback position. |
| `cellStart` | function | The start of the grid cell `value` falls in: where a note drawn there begins. |
| `changeLevel` | function | Changes `level` of every note in `ids` by `delta`, each kept within 0…1, so the notes keep their differences: the velocity, or the chance to play. |
| `changeVelocity` | function | Changes the velocity of every note in `ids` by `delta`, each kept within 0…1: a phrase keeps its dynamics, where setting them all to one value would flatten it. |
| `chopNotes` | function | Chops each note in `ids` into `parts` notes of the same length, as many as fit at `minLength` or longer: a held note becomes a repeated one. |
| `deleteTime` | function | Takes the time from `start` to `end` out of the clip: the notes inside it go, those after it move back by its length, and one crossing it loses the part inside. |
| `duplicateAfter` | function | Copies of the notes in `ids` placed right after them, one selection's length on, so a phrase repeats itself the way it is played. |
| `duplicateNotes` | function | Copies of the notes in `ids`, under new ids from `nextId`, left where the originals are. |
| `duplicateTime` | function | Repeats the time from `start` to `end` right after itself: silence opens at `end` and the notes in the range, cut to it, are copied into it under new ids. |
| `findNotes` | function | The ids of the notes that match `query`, in the order they play: to select every note of a pitch, a range of velocities or lengths, out of a scale, or every other one. |
| `fitNotesToScale` | function | Moves each note in `ids` that is out of `scale` to the nearest pitch in it, the lower of two as near. |
| `humanizeNotes` | function | Loosens the notes in `ids` the way a player would: each start moves by up to `timing` either way and each velocity by up to `velocity`, from `random` (0…1, `Math.random` by default). |
| `insertTime` | function | Opens `length` of silence at `at`: the notes from there on move later by it, one crossing `at` is split there with its second part moved along, and what then runs past the clip's `duration` is cut off. |
| `invertNotes` | function | Turns the notes in `ids` upside down: the highest becomes the lowest, around the middle of their range. |
| `joinNotes` | function | Joins the notes in `ids` that share a pitch into one, from the earliest start to the latest end: the earliest keeps its id and velocity. |
| `keyboardEdit` | function | What an arrow key does to the notes in `ids`: Left and Right move them by `step`, or with Shift change their length; Up and Down move them by a semitone, or with Shift an octave. |
| `legatoNotes` | function | Lengthens or shortens each note in `ids` to end where the next of them starts, so a phrase plays without gaps. |
| `moveNotes` | function | Moves the notes in `ids` by the same amount, in time and in pitch, so a chord keeps its spacing. |
| `Note` | interface | A note on the roll: its pitch, start and length, how hard and how likely it plays, how far its velocity may stray, and whether it is deactivated. |
| `NoteId` | type | A note's id: unique within its clip. |
| `NoteLevel` | type | A note's value a lane draws as a bar, 0…1: how hard it plays, or how likely it is to. |
| `NoteQuery` | interface | What `findNotes` looks for; every condition given must hold. |
| `notesWithin` | function | The ids of the notes overlapping a stretch of time and a band of rows. |
| `pasteNotes` | function | Copies of `clipboard` under new ids, the earliest starting at `at`: a phrase keeps its spacing wherever it lands, and stops where the roll ends. |
| `quantizeNotes` | function | The notes in `ids` with their start on the nearest grid line: their length, and the rest, stay. |
| `RollBounds` | interface | What every edit keeps a note within: the timeline's length, the pitch range and the shortest length. |
| `rampVelocity` | function | Sets the velocity of the notes in `ids` along a line, from `from` on the earliest to `to` on the latest: a crescendo, or a fade. |
| `resizeNotes` | function | Moves one edge of every note in `ids` by `delta`: the end makes them longer or shorter, the start trims them from the front while their end stays put. |
| `reverseNotes` | function | Plays the notes in `ids` backwards: each lands where it would end, mirrored across their span. |
| `sameNotes` | function | Whether two lists hold the same notes, in the same order. |
| `sanitizeNote` | function | Keeps a note on the roll: inside the timeline, in the pitch range, at least `minLength` long. |
| `selectionSpan` | function | The time the notes in `ids` cover, from the earliest start to the latest end, or null with none. |
| `setChance` | function | Sets how likely the notes in `ids` are to play each time round, 0…1: 1 always, 0.5 every other time on average. |
| `setDeviation` | function | Sets how far the velocity of the notes in `ids` may stray each time they play, kept within -1…1. |
| `snapEdgeDelta` | function | How far the edge of `note` moves when dragged by `delta`, on the grid unless `grid.step` is 0. |
| `spatialOrder` | function | Notes in the order the eye reads them: left to right, then top to bottom. |
| `splitNotes` | function | Splits the notes in `ids` at `at`, or at each time in it, wherever a note holds that time: the first piece keeps the note's id, the others take new ones. |
| `stretchNotes` | function | Makes the notes in `ids` `factor` times as long, around `anchor` (where the earliest starts, by default): 2 plays them at half the speed, 0.5 at twice. |
| `toggleMuted` | function | Deactivates the notes in `ids`, or activates them again when every one of them already is: a deactivated note stays on the roll and does not play. |
| `RollScale` | interface | A scale as a piano roll reads it: the pitch classes it keeps and the one it starts on, each from 0 (C) to 11 (B). |
| `Rows` | interface | The rows a piano roll draws, highest pitch first, and the conversions between a pitch and its row. |
| `rollRows` | function | The rows for pitches `min` to `max`: every one, or folded to just those the notes use, and with `"scale"` those in `scale` as well. |

## Usage

A note is a pitch, a start and a length in the timeline's units, with a velocity, a chance, a deviation and a mute. The controller holds the notes, the selection and the pitches sounding, and every gesture and key that edits them; it follows the [contract](/docs/core/controllers) every controller shares:

```ts
import { createPianoRollController } from "@audio-ui/core/piano-roll";

const roll = createPianoRollController({
  timeline: { duration: 16, step: 0.25, grid: { offset: 0, step: 0.25 }, zoom: null, position: () => clock.position },
  defaultValue: [{ id: "n1", pitch: 60, start: 0, length: 1 }],
  minPitch: 36,
  maxPitch: 84,
  onValueCommitted: (notes) => save(notes),
  onNoteOn: (pitch) => synth.noteOn(pitch),
  onNoteOff: (pitch) => synth.noteOff(pitch),
});
```

| Option | Meaning |
| --- | --- |
| `value`, `defaultValue` | The notes, controlled, or the uncontrolled start |
| `selection`, `defaultSelection`, `onSelectionChange` | The selected notes' ids |
| `timeline` | Its length, the grid edits snap to, the zoom and the playback position |
| `minPitch`, `maxPitch`, `minLength` | The bounds every edit keeps a note within: 48 to 72, and the grid step, unless given |
| `mode` | What a press on empty grid does: `"select"` (the default) starts a selection rectangle, `"draw"` draws a note |
| `drawStyle` | In draw mode, `"stretch"` (the default) makes the drawn note as long as the drag; `"paint"` draws a note in each grid cell crossed |
| `scale`, `folded` | The scale the rows shade, as its pitch classes and root (a `RollScale`), and folding the rows to the pitches used or to the scale |
| `activePitches` | Keys lit from elsewhere, such as the notes playing |

## The input

- `pressGrid(event, grid)` starts a selection rectangle, or draws a note in `"draw"` mode; `addAtPlayback` adds a note at the playback position from the keyboard.
- `pressNote(id, handle, event, element, grid)` moves a note by its body, or resizes it by an edge; the selected notes move with it, a chord keeping its spacing. `pressStretch(edge, …)` stretches the selection in time from its other edge.
- `pressVelocity` and `paintLevels` draw the velocity, the deviation or the chance in a lane under the roll.
- `keyNote`, `keyGrid` and `shortcut` take the keyboard: on a note, the arrows move the selection (Shift changes its length), Enter or Space selects it and Delete removes it; Escape clears the selection, Cmd or Ctrl with A selects every note, with C, X, V and D copies, cuts, pastes at the playhead and duplicates, and Q alone quantizes the selection, or every note with none selected. Each returns `true` when it handled the key.
- `keyOn` and `keyOff` sound the keyboard beside the roll.

## Edits

Every edit is also a plain function on a list of notes, for a menu, a command or an engine that keeps the notes itself. Each takes the notes, the ids to change and the bounds to keep them in, and returns new notes:

```ts
import { findNotes, fitNotesToScale, moveNotes, quantizeNotes } from "@audio-ui/core/piano-roll";

const bounds = { duration: 16, minLength: 0.25, minPitch: 0, maxPitch: 127 };
const all = new Set(notes.map((note) => note.id));

moveNotes(notes, all, 1, 12, bounds); // a beat later, an octave up
quantizeNotes(notes, all, (time) => Math.round(time), bounds); // starts on the beat
fitNotesToScale(notes, all, { pitchClasses: [0, 2, 4, 5, 7, 9, 11], root: 9 }, bounds); // into A minor
findNotes(notes, { pitchClasses: [0] }); // the ids of every C
```

The others edit time and shape: `splitNotes`, `joinNotes`, `chopNotes`, `stretchNotes`, `legatoNotes`, `reverseNotes`, `invertNotes`, `duplicateNotes`, `duplicateAfter`, `pasteNotes`, `insertTime`, `deleteTime`, `duplicateTime` and `toggleMuted`; and the dynamics: `changeVelocity`, `rampVelocity`, `humanizeNotes`, `setChance` and `setDeviation`. `findNotes` selects by pitch class, scale, velocity, length, chance or mute, every nth of them, or the inverse.

## Rows

`rollRows(notes, min, max, fold, scale)` gives the rows a roll draws, highest pitch first, with `rowOf` and `pitchAt` to go between a pitch and its row: every pitch, only those the notes use, or only those in a scale.

A scale is a `RollScale`: the pitch classes it keeps and its root, each from 0 (C) to 11 (B). The roll knows no scale by name; [`rollScale`](/docs/engine/pitch#on-a-piano-roll) gives a named one in that shape.

**In React**

The [Piano roll](/docs/components/base/piano-roll) draws this controller in React.

**In Vue, Svelte and Web**

[`<audio-ui-piano-roll>`](/docs/elements/piano-roll) draws this controller in your framework.

