Lab

Piano roll

The <audio-ui-piano-roll> custom element of @audio-ui/elements: its attributes, its parts and a live preview.

<audio-ui-piano-roll> is a note editor placed inside an <audio-ui-timeline>, which provides its length, zoom, grid and position. Its entry point also registers the timeline and piano elements. Notes are set through the value property as { id, pitch, start, length, velocity?, chance?, muted? }[], and the selection through the selection property. Rows, notes and velocity bars are generated from them:

Usage

The styles read the theme's CSS variables, which Styling sets.

<style>
  audio-ui-timeline {
    display: block;
    width: 100%;
    max-width: 36rem;
  }

  audio-ui-piano-roll {
    display: grid;
    grid-template-columns: 3rem 1fr;
    overflow: hidden;
    border: 1px solid var(--border, oklch(0.922 0 0));
    border-radius: calc(var(--radius, 0.625rem) - 2px);
  }

  audio-ui-piano-roll-keys {
    height: calc(var(--spacing, 0.25rem) * 48);
    border-inline-end: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-keys audio-ui-piano {
    display: block;
    height: 100%;
  }

  audio-ui-piano-roll-keys audio-ui-piano-key {
    z-index: 0;
    top: auto;
    right: 0;
    left: 0;
    box-sizing: border-box;
    height: 100%;
    border: 0;
    border-bottom: 1px solid var(--border, oklch(0.922 0 0));
    border-radius: 0;
    background: var(--background, oklch(1 0 0));
    cursor: pointer;
    outline: none;
  }

  audio-ui-piano-roll-keys audio-ui-piano-key[data-color="black"] {
    z-index: 1;
    right: 35%;
    height: 62%;
    border-color: var(--foreground, oklch(0.145 0 0));
    background: var(--foreground, oklch(0.145 0 0));
  }

  audio-ui-piano-roll-keys audio-ui-piano-key[data-active] {
    border-color: var(--primary, oklch(0.205 0 0));
    background: var(--primary, oklch(0.205 0 0));
  }

  audio-ui-piano-roll-grid {
    height: calc(var(--spacing, 0.25rem) * 48);
    outline: none;
  }

  audio-ui-piano-roll-row[data-color="black"] {
    background: color-mix(in oklab, var(--muted, oklch(0.97 0 0)) 70%, transparent);
  }

  audio-ui-piano-roll-row[data-octave] {
    border-bottom: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-note {
    border-radius: 2px;
    background: color-mix(
      in oklab,
      var(--primary, oklch(0.205 0 0)) calc(40% + 60% * var(--piano-roll-velocity)),
      transparent
    );
    cursor: grab;
    outline: none;
  }

  audio-ui-piano-roll-note[data-selected] {
    box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
  }

  audio-ui-piano-roll-marquee {
    border: 1px dashed var(--primary, oklch(0.205 0 0));
    background: color-mix(in oklab, var(--primary, oklch(0.205 0 0)) 8%, transparent);
  }

  audio-ui-piano-roll-velocity-lane {
    grid-column-start: 2;
    height: calc(var(--spacing, 0.25rem) * 12);
    border-top: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-velocity {
    width: 3px;
    translate: -1px 0;
    background: var(--primary, oklch(0.205 0 0));
  }

  audio-ui-timeline-viewport {
    scrollbar-width: none;
  }

  audio-ui-timeline-playhead {
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--foreground, oklch(0.145 0 0));
  }
</style>

<audio-ui-timeline duration="8" snap-step="0.5">
  <audio-ui-piano-roll min-pitch="57" max-pitch="74">
    <audio-ui-piano-roll-keys>
      <audio-ui-piano aria-label="Keys">
        <audio-ui-piano-keys></audio-ui-piano-keys>
      </audio-ui-piano>
    </audio-ui-piano-roll-keys>
    <audio-ui-timeline-viewport>
      <audio-ui-piano-roll-grid aria-label="Notes">
        <audio-ui-piano-roll-rows></audio-ui-piano-roll-rows>
        <audio-ui-piano-roll-notes></audio-ui-piano-roll-notes>
        <audio-ui-timeline-playhead></audio-ui-timeline-playhead>
      </audio-ui-piano-roll-grid>
    </audio-ui-timeline-viewport>
  </audio-ui-piano-roll>
</audio-ui-timeline>

<script type="module">
  import "@audio-ui/elements/piano-roll";

  const pianoRoll = document.querySelector("audio-ui-piano-roll");
  pianoRoll.addEventListener("change", (event) =>
    console.log(event.detail.value)
  );
</script>

API reference

pnpm add @audio-ui/elements
import "@audio-ui/elements/piano-roll";
ExportKindWhat it is
PIANO_ROLL_TAGSconstThe names the piano roll's elements are registered under, frozen: a page cannot rename them by accident.
PianoRollElementclassThe root of a note editor, placed inside an <audio-ui-timeline>, which provides its length, zoom, grid and position.
PianoRollElementEventMapinterfaceThe events a piano roll fires, as addEventListener types them.
PianoRollEventDetailinterfaceWhat a roll's input and change events carry, besides the roll itself as their target.
PianoRollGridElementclassThe note surface: a focusable timeline lane with role="group", with the rows stacked vertically.
PianoRollKeysElementclassLays out the <audio-ui-piano> it contains on the roll's rows, low end at the bottom, and lights the sounding pitches.
PianoRollMarqueeElementclassThe selection rectangle during a marquee drag.
PianoRollNoteElementclassA note, positioned at its start in timeline units and on its pitch's row.
PianoRollNoteHandleElementclassResizes its note from its edge (start or end): the end changes the length, the start trims from the front with the end fixed.
PianoRollNotesElementclassGenerates an <audio-ui-piano-roll-note> per note in reading order (by start, then highest pitch first), which is also the tab order, from its <template>, such as with <audio-ui-piano-roll-note-handle>s.
PianoRollRowElementclassThe band of a pitch across the grid, for row shading, scale highlighting and octave lines.
PianoRollRowsElementclassGenerates an <audio-ui-piano-roll-row> per row, highest pitch first, from its <template> when present.
PianoRollSelectionSpanElementclassThe time range of the selected notes, spanning the height of its container: the grid, the velocity lane or the ruler.
PianoRollStretchHandleElementclassAn edge of the selection span.
PianoRollVelocitiesElementclassGenerates an <audio-ui-piano-roll-velocity> per note, from its <template> when present.
PianoRollVelocityElementclassA note's velocity, or chance in a level="chance" lane, rendered as a bar sized by --piano-roll-level.
PianoRollVelocityLaneElementclassA timeline lane of per-note bars for velocity or, with level="chance", probability, each at its note's start.

Props · 9

EventDetailDescription
input, change{ value, reason }The notes change, then the gesture ends
noteselect{ selection, reason }The selection changes. preventDefault() rejects it
noteon, noteoff{ pitch, reason, event }A pitch starts or stops sounding. preventDefault() on noteon keeps it silent

The getNoteLabel(note) property sets each note's accessible name, "C4, 4 to 6" by default.

PartDescription
<audio-ui-piano-roll-grid>The focusable note surface, a timeline lane. Enter adds a note at the position, Escape clears the selection, Cmd/Ctrl+A selects all, and Cmd/Ctrl+D, C, X, V and Q duplicate, copy, cut, paste and quantize
<audio-ui-piano-roll-rows>Generates an <audio-ui-piano-roll-row> per pitch, highest first. Rows carry data-color, data-in-scale, data-out-of-scale, data-scale-root, data-octave and data-active
<audio-ui-piano-roll-notes>Generates an <audio-ui-piano-roll-note> per note in reading order, from its <template>. Each note is a toggle button positioned at its start and pitch, movable by pointer and keyboard, exposing --piano-roll-velocity and --piano-roll-chance
<audio-ui-piano-roll-note-handle>Inside a note, resizes it from its edge
<audio-ui-piano-roll-marquee>The selection rectangle during a marquee drag
<audio-ui-piano-roll-selection-span>The time range of the selection; an <audio-ui-piano-roll-stretch-handle> per edge stretches it
<audio-ui-piano-roll-velocity-lane>A lane of per-note bars, through <audio-ui-piano-roll-velocities>, for velocity or, with level="chance", probability. Each <audio-ui-piano-roll-velocity> is sized by --piano-roll-level and contains a native range input
<audio-ui-piano-roll-keys>Lays out the <audio-ui-piano> it contains on the roll's rows. Keys sound through the roll; Shift + press selects the notes of that row

The same control in React, and its parts' props: PianoRoll.

Built by Ouest Labs, the source code is available on GitHub.
RSS