@minagishl/react-piano-roll
v0.1.0
Published
A production-ready React component for piano roll / falling notes visualization with high-quality audio playback
Downloads
89
Maintainers
Readme
React Piano Roll
A production-ready React component library for creating beautiful piano roll / falling notes visualizations with high-quality audio playback.
Features
- Beautiful Piano Roll Visualization - Smooth falling notes animation
- High-Quality Audio - Sample-based piano sound engine using Web Audio API
- Highly Customizable - Extensive theming and configuration options
- External Playback Control - Programmatic play/pause/seek API
- Responsive - Adapts to different keyboard sizes and layouts
- TypeScript - Fully typed API for excellent DX
- Theme Support - Built-in light/dark themes with easy customization
- Performance - Canvas-based rendering for smooth animations
- Zero Dependencies - Only peer dependencies on React
Installation
# Using pnpm
pnpm add @minagishl/react-piano-roll
# Using npm
npm install @minagishl/react-piano-roll
# Using yarn
yarn add @minagishl/react-piano-rollQuick Start
import { PianoRoll } from '@minagishl/react-piano-roll';
import type { Note } from '@minagishl/react-piano-roll';
const notes: Note[] = [
{ pitch: 60, startTime: 0, duration: 0.5, velocity: 80 },
{ pitch: 62, startTime: 0.5, duration: 0.5, velocity: 85 },
{ pitch: 64, startTime: 1.0, duration: 0.5, velocity: 90 },
];
function App() {
return <PianoRoll notes={notes} />;
}Usage
Basic Example
import { useRef } from 'react';
import { PianoRoll, PianoRollHandle } from '@minagishl/react-piano-roll';
function MusicPlayer() {
const pianoRollRef = useRef<PianoRollHandle>(null);
const handlePlay = () => {
pianoRollRef.current?.play();
};
const handlePause = () => {
pianoRollRef.current?.pause();
};
const handleStop = () => {
pianoRollRef.current?.stop();
};
return (
<div>
<div>
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>
<button onClick={handleStop}>Stop</button>
</div>
<PianoRoll
ref={pianoRollRef}
notes={notes}
onTimeUpdate={(time) => console.log('Current time:', time)}
/>
</div>
);
}Custom Theme
<PianoRoll
notes={notes}
theme={{
backgroundColor: '#1a1a1a',
gridColor: 'rgba(255, 255, 255, 0.1)',
noteColor: '#4a9eff',
activeNoteColor: '#66ff66',
whiteKeyColor: '#ffffff',
blackKeyColor: '#000000',
}}
/>Velocity-Based Coloring
<PianoRoll
notes={notes}
theme={{
noteColor: (velocity) => {
const hue = (velocity / 127) * 120;
return `hsl(${hue}, 70%, 50%)`;
},
}}
/>Custom Keyboard Size
<PianoRoll
notes={notes}
keyboardConfig={{
keyCount: 24,
startNote: 48, // C3
whiteKeyWidth: 30,
whiteKeyHeight: 150,
}}
/>Custom Animation Speed
<PianoRoll
notes={notes}
animationConfig={{
fallSpeed: 300, // pixels per second
lookahead: 3, // seconds to show ahead
}}
/>API Reference
PianoRoll Component
Props
| Prop | Type | Default | Description |
| ----------------- | -------------------------- | -------- | ------------------------------------ |
| notes | Note[] | required | Array of notes to display and play |
| theme | Partial<PianoRollTheme> | - | Theme customization options |
| keyboardConfig | Partial<KeyboardConfig> | - | Keyboard configuration |
| animationConfig | Partial<AnimationConfig> | - | Animation configuration |
| audioEngine | AudioEngine | - | Custom audio engine implementation |
| width | number | auto | Width of the component |
| rollHeight | number | 400 | Height of the note roll area |
| onStateChange | (state) => void | - | Callback when playback state changes |
| onTimeUpdate | (time) => void | - | Callback when playback time updates |
Ref Methods (PianoRollHandle)
interface PianoRollHandle {
play: () => void;
pause: () => void;
stop: () => void;
seek: (time: number) => void;
getCurrentTime: () => number;
getState: () => PlaybackState;
currentTime: number;
}Note Data Model
interface Note {
pitch: number; // MIDI note number (0-127)
startTime: number; // Start time in seconds
duration: number; // Duration in seconds
velocity?: number; // Note velocity (0-127), defaults to 64
}MIDI Note Reference:
- Middle C (C4) = 60
- A0 (lowest piano key) = 21
- C8 (highest piano key) = 108
Theme Configuration
interface PianoRollTheme {
backgroundColor?: string;
gridColor?: string;
showGrid?: boolean;
gridSpacing?: number;
noteColor?: string | ((velocity: number) => string);
activeNoteColor?: string;
noteRadius?: number;
whiteKeyColor?: string;
blackKeyColor?: string;
activeWhiteKeyColor?: string;
activeBlackKeyColor?: string;
keyBorderColor?: string;
}Keyboard Configuration
interface KeyboardConfig {
keyCount?: number; // Number of keys (default: 88)
startNote?: number; // Starting MIDI note (default: 21 - A0)
whiteKeyWidth?: number; // Width in pixels (default: 24)
whiteKeyHeight?: number; // Height in pixels (default: 120)
blackKeyWidth?: number; // Width in pixels (default: 14)
blackKeyHeight?: number; // Height in pixels (default: 80)
}Animation Configuration
interface AnimationConfig {
fallSpeed?: number; // Pixels per second (default: 200)
easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
lookahead?: number; // Seconds to show ahead (default: 3)
}Utilities
Piano Helper Functions
import {
isBlackKey,
getNoteNameFromMIDI,
getMIDIFromNoteName,
midiToFrequency,
} from '@minagishl/react-piano-roll';
isBlackKey(61); // true (C#)
getNoteNameFromMIDI(60); // "C4"
getMIDIFromNoteName('A4'); // 69
midiToFrequency(69); // 440 (Hz)Custom Audio Engine
You can provide your own audio engine implementation:
import { AudioEngine } from '@minagishl/react-piano-roll';
class CustomAudioEngine implements AudioEngine {
async init() {
/* ... */
}
playNote(pitch, velocity, duration) {
/* ... */
}
stopNote(pitch) {
/* ... */
}
stopAll() {
/* ... */
}
setVolume(volume) {
/* ... */
}
dispose() {
/* ... */
}
}
const myAudioEngine = new CustomAudioEngine();
<PianoRoll notes={notes} audioEngine={myAudioEngine} />;Advanced Usage
Loading Custom Piano Samples
The default audio engine supports loading custom piano samples:
import { PianoAudioEngine } from '@minagishl/react-piano-roll';
const audioEngine = new PianoAudioEngine();
await audioEngine.init();
// Load samples
const sampleMap = new Map([
[60, '/samples/C4.mp3'],
[61, '/samples/C#4.mp3'],
// ... more samples
]);
await audioEngine.loadSamples(sampleMap);
<PianoRoll notes={notes} audioEngine={audioEngine} />;Note: The engine will automatically pitch-shift samples to cover missing notes.
Development
# Install dependencies
pnpm install
# Start Storybook
pnpm storybook
# Build the library
pnpm build
# Type check
pnpm typecheckBrowser Support
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
Requires Web Audio API support.
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
License
This project is licensed under the MIT License - see the LICENSE file for details.
