npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@molecule/app-feature-piano-roll-react

v1.0.1

Published

MIDI piano roll editor — note grid with vertical piano keys + horizontal time grid + draggable/resizable note rectangles for music-daw

Readme

@molecule/app-feature-piano-roll-react

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Piano roll — MIDI note-grid editor for music-daw and other DAW-style surfaces. Renders a vertical piano keyboard on the left, a horizontal time grid on the right, and draggable / resizable note rectangles on top.

  • Click an empty grid cell to paint a new note (snapped to the configured grid resolution).
  • Drag a note body to move it horizontally (time) and vertically (pitch); both axes snap.
  • Drag the right-edge handle on a note to resize it.
  • Right-click a note to delete it.

Quick Start

import { PianoRoll, type MidiNote } from '@molecule/app-feature-piano-roll-react'

const [notes, setNotes] = useState<MidiNote[]>([])

<PianoRoll
  notes={notes}
  onNoteAdd={(n) => setNotes((all) => [...all, n])}
  onNoteMove={(id, startBeat, pitch) =>
    setNotes((all) => all.map((n) => (n.id === id ? { ...n, startBeat, pitch } : n)))
  }
  onNoteResize={(id, durationBeats) =>
    setNotes((all) => all.map((n) => (n.id === id ? { ...n, durationBeats } : n)))
  }
  onNoteDelete={(id) => setNotes((all) => all.filter((n) => n.id !== id))}
  snap="1/16"
/>

Type

feature

Installation

npm install @molecule/app-feature-piano-roll-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react

API

Interfaces

MidiNote

A single MIDI note rendered on the piano roll. pitch is a standard MIDI pitch number in the closed interval [0, 127] where 60 represents middle C (C4). startBeat is the note's start position on the time grid, and durationBeats is its length in beats. id is a stable identifier used as the React key and as the argument passed back to all event handlers. velocity is an optional MIDI velocity in the closed interval [0, 127] (defaults to a neutral mid-range).

interface MidiNote {
  /** Stable identifier for the note (used as a React key + handler arg). */
  id: string
  /** MIDI pitch number, `0..127`. `60` is middle C. */
  pitch: number
  /** Start beat on the timeline (>= 0). */
  startBeat: number
  /** Duration in beats (always positive — minimum is enforced). */
  durationBeats: number
  /** Optional MIDI velocity, `0..127`. */
  velocity?: number
}

PianoRollProps

Props for <PianoRoll>.

interface PianoRollProps {
  /** MIDI notes rendered on the grid. Order is preserved as-is. */
  notes: MidiNote[]
  /** Horizontal scale: pixels rendered per musical beat. Defaults to `80`. */
  pixelsPerBeat?: number
  /** Vertical row height per pitch in pixels. Defaults to `20`. */
  noteHeight?: number
  /** Lowest visible pitch (top-most row excluded — see `highestPitch`). Defaults to `21` (A0). */
  lowestPitch?: number
  /** Highest visible pitch (top-most row inclusive). Defaults to `108` (C8). */
  highestPitch?: number
  /** Snap resolution applied to clicks, drags, and resizes. Defaults to `'1/16'`. */
  snap?: PianoRollSnap
  /**
   * Total beats rendered in the time grid. When omitted, the grid
   * extends to cover all notes plus a small trailing pad (minimum 16
   * beats so empty rolls are still usable).
   */
  beatsCount?: number
  /** Called with a freshly-created note when the user clicks an empty cell. */
  onNoteAdd?: (note: MidiNote) => void
  /** Called with the updated note when a note is dragged horizontally / vertically. */
  onNoteMove?: (noteId: string, startBeat: number, pitch: number) => void
  /** Called with the updated duration when a note's right-edge handle is dragged. */
  onNoteResize?: (noteId: string, durationBeats: number) => void
  /** Called when the user right-clicks a note. */
  onNoteDelete?: (noteId: string) => void
  /** Extra classes merged onto the root element. */
  className?: string
}

Types

PianoRollSnap

Snap resolution for note placement / movement / resize. Each value is a fraction of a beat: '1/16' is sixteenth-notes (a quarter of a beat), '1/4' is quarter-notes (one beat), '1' snaps to whole beats with no sub-divisions, etc.

type PianoRollSnap = '1/16' | '1/8' | '1/4' | '1/2' | '1'

Functions

beatsToPixels(beats, pixelsPerBeat)

Convert a beat value into pixels given the horizontal scale.

function beatsToPixels(beats: number, pixelsPerBeat: number): number
  • beats — Beat value.
  • pixelsPerBeat — Horizontal scale.

Returns: Pixel offset.

isBlackKey(pitch)

Determine whether a MIDI pitch corresponds to a black key on a piano keyboard.

function isBlackKey(pitch: number): boolean
  • pitch — MIDI pitch number.

Returns: true if the pitch is a black key.

PianoRoll(props)

MIDI piano-roll editor. Renders a vertical piano keyboard on the left, a horizontal time grid on the right, and draggable / resizable note rectangles on top. Click an empty cell to paint a new note, drag a note body to move it (snapped), drag the right-edge handle to resize, right-click a note to delete it.

All styling routes through getClassMap() (no Tailwind / raw class names). All user-visible text routes through t() so the roll translates via the companion @molecule/app-locales-feature-piano-roll locale bond.

function PianoRoll(
  props: PianoRollProps,
): ReactElement<unknown, string | JSXElementConstructor<any>>
  • props — Component props.

Returns: The piano-roll element.

pitchLabel(pitch)

Format a MIDI pitch as a scientific-pitch-notation label, e.g. 60 becomes 'C4' and 61 becomes 'C#4'.

function pitchLabel(pitch: number): string
  • pitch — MIDI pitch number.

Returns: Formatted label.

pitchToY(pitch, noteHeight, highestPitch)

Convert a MIDI pitch into a vertical pixel offset (top of the row the pitch sits in).

function pitchToY(pitch: number, noteHeight: number, highestPitch: number): number
  • pitch — MIDI pitch number.
  • noteHeight — Row height per pitch.
  • highestPitch — Top-most pitch in the visible range.

Returns: Vertical pixel offset for the pitch's row.

pixelsToBeats(pixels, pixelsPerBeat)

Convert a pixel offset into beats given the horizontal scale.

function pixelsToBeats(pixels: number, pixelsPerBeat: number): number
  • pixels — Offset in pixels.
  • pixelsPerBeat — Horizontal scale.

Returns: Beats represented by the offset (may be negative).

snapBeat(beat, snap)

Snap a beat value to the nearest multiple of the snap step (rounding down — typical piano-roll behavior so a click at the start of a cell paints a note that starts on that cell).

function snapBeat(beat: number, snap: PianoRollSnap): number
  • beat — Raw beat value (may be fractional).
  • snap — Snap resolution.

Returns: The floored snapped beat (>= 0).

snapBeatRound(beat, snap)

Round a beat value to the nearest snap step (typical drag-move behavior so drags feel symmetric around grid lines).

function snapBeatRound(beat: number, snap: PianoRollSnap): number
  • beat — Raw beat value (may be fractional).
  • snap — Snap resolution.

Returns: The rounded snapped beat (>= 0).

snapToBeats(snap)

Convert a snap setting to its size in beats.

function snapToBeats(snap: PianoRollSnap): number
  • snap — Snap resolution.

Returns: Snap step size in beats.

yToPitch(y, noteHeight, highestPitch, lowestPitch)

Convert a vertical pixel offset (relative to the top of the grid) into a MIDI pitch. Higher pitches sit at the top, so y = 0 maps to highestPitch and y increases as pitch decreases.

function yToPitch(y: number, noteHeight: number, highestPitch: number, lowestPitch: number): number
  • y — Vertical pixel offset from the top of the grid.
  • noteHeight — Row height per pitch.
  • highestPitch — Top-most pitch in the visible range.
  • lowestPitch — Bottom-most pitch in the visible range.

Returns: A MIDI pitch clamped into [lowestPitch, highestPitch].

Constants

DEFAULT_NOTE_VELOCITY

Default MIDI velocity assigned to freshly-painted notes.

const DEFAULT_NOTE_VELOCITY: 96

DRAG_DISTANCE_THRESHOLD_PX

Pointer-distance threshold (px) before a press becomes a drag.

const DRAG_DISTANCE_THRESHOLD_PX: 3

MIN_NOTE_DURATION_BEATS

Minimum note duration in beats — notes can't be resized below this.

const MIN_NOTE_DURATION_BEATS: number

PIANO_KEYS_WIDTH_PX

Pixel width of the leading piano-keys column.

const PIANO_KEYS_WIDTH_PX: 56

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-react ^1.0.1
  • @molecule/app-ui ^1.0.1
  • react ^18.0.0 || ^19.0.0

Runtime Dependencies

  • @molecule/app-react
  • @molecule/app-ui
  • react

Translations

Translation strings are provided by @molecule/app-locales-feature-piano-roll.