Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 0 additions & 69 deletions CHANGELOG.md

This file was deleted.

21 changes: 0 additions & 21 deletions LICENSE

This file was deleted.

120 changes: 9 additions & 111 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,120 +1,18 @@
# react-piano
# react-piano demo site

[![npm version](https://img.shields.io/npm/v/react-piano.svg)](https://www.npmjs.com/package/react-piano)
[![build status](https://travis-ci.com/kevinsqi/react-piano.svg?branch=master)](https://travis-ci.com/kevinsqi/react-piano)
[![bundle size](https://img.shields.io/bundlephobia/min/react-piano.svg)](https://bundlephobia.com/result?p=react-piano)
## Developing

An interactive piano keyboard for React. Supports custom sounds, touch/click/keyboard events, and fully configurable styling. [**Try it out on CodeSandbox**](https://codesandbox.io/s/7wq15pm1n1).
In this repo, run:

<a href="https://www.kevinqi.com/react-piano/"><img width="600" src="/demo/public/images/react-piano-screenshot.png" alt="react-piano screenshot" /></a>

## Installing

```
yarn add react-piano
```

Alternatively, you can download the UMD build from [unpkg](https://unpkg.com/react-piano).

## Usage

You can view or fork the [**CodeSandbox demo**](https://codesandbox.io/s/7wq15pm1n1) to get a live version of the component in action.

Import the component and styles:

```jsx
import { Piano, KeyboardShortcuts, MidiNumbers } from 'react-piano';
import 'react-piano/dist/styles.css';
```

Importing CSS requires a CSS loader (if you're using create-react-app, this is already set up for you). If you don't have a CSS loader, you can alternatively copy the CSS file into your project from [src/styles.css](src/styles.css).

Then to use the component:

```jsx
function App() {
const firstNote = MidiNumbers.fromNote('c3');
const lastNote = MidiNumbers.fromNote('f5');
const keyboardShortcuts = KeyboardShortcuts.create({
firstNote: firstNote,
lastNote: lastNote,
keyboardConfig: KeyboardShortcuts.HOME_ROW,
});

return (
<Piano
noteRange={{ first: firstNote, last: lastNote }}
playNote={(midiNumber) => {
// Play a given note - see notes below
}}
stopNote={(midiNumber) => {
// Stop playing a given note - see notes below
}}
width={1000}
keyboardShortcuts={keyboardShortcuts}
/>
);
}
yarn install
yarn start
```

## Implementing audio playback

react-piano does not implement audio playback of each note, so you have to implement it with `playNote` and `stopNote` props. This gives you the ability to use any sounds you'd like with the rendered piano. The [react-piano demo page](https://www.kevinqi.com/react-piano/) uses @danigb's excellent [soundfont-player](https://github.com/danigb/soundfont-player) to play realistic-sounding soundfont samples. Take a look at the [**CodeSandbox demo**](https://codesandbox.io/s/7wq15pm1n1) to see how you can implement that yourself.

## Props

| Name | Type | Description |
| ---- | ---- | ----------- |
| `noteRange` | **Required** object | An object with format `{ first: 48, last: 77 }` where first and last are MIDI numbers that correspond to natural notes. You can use `MidiNumbers.NATURAL_MIDI_NUMBERS` to identify whether a number is a natural note or not. |
| `playNote` | **Required** function | `(midiNumber) => void` function to play a note specified by MIDI number. |
| `stopNote` | **Required** function | `(midiNumber) => void` function to stop playing a note. |
| `width` | **Conditionally required** number | Width in pixels of the component. While this is not strictly required, if you omit it, the container around the `<Piano>` will need to have an explicit width and height in order to render correctly. |
| `activeNotes` | Array of numbers | An array of MIDI numbers, e.g. `[44, 47, 54]`, which allows you to programmatically play notes on the piano. |
| `keyWidthToHeight` | Number | Ratio of key width to height. Used to specify the dimensions of the piano key. |
| `renderNoteLabel` | Function | `({ keyboardShortcut, midiNumber, isActive, isAccidental }) => node` function to render a label on piano keys that have keyboard shortcuts |
| `className` | String | A className to add to the component. |
| `disabled` | Boolean | Whether to show disabled state. Useful when audio sounds need to be asynchronously loaded. |
| `keyboardShortcuts` | Array of object | An array of form `[{ key: 'a', midiNumber: 48 }, ...]`, where `key` is a `keyEvent.key` value. You can generate this using `KeyboardShortcuts.create`, or use your own method to generate it. You can omit it if you don't want to use keyboard shortcuts. **Note:** this shouldn't be generated inline in JSX because it can cause problems when diffing for shortcut changes. |
| `onPlayNoteInput` | Function | `(midiNumber, { prevActiveNotes }) => void` function that fires whenever a play-note event is fired. Can use `prevActiveNotes` to record notes. |
| `onStopNoteInput` | Function | `(midiNumber, { prevActiveNotes }) => void` function that fires whenever a stop-note event is fired. Can use `prevActiveNotes` to record notes. |

## Recording/saving notes

You can "record" notes that are played on a `<Piano>` by using `onPlayNoteInput` or `onStopNoteInput`, and you can then play back the recording by using `activeNotes`. See [this CodeSandbox](https://codesandbox.io/s/l4jjvzmp47) which demonstrates how to set that up.

<a href="https://codesandbox.io/s/l4jjvzmp47"><img width="300" src="/demo/public/images/recording-demo.gif" alt="demo of recording" /></a>

## Customizing styles

You can customize many aspects of the piano using CSS. In javascript, you can override the base styles by creating your own set of overrides:

```javascript
import 'react-piano/dist/styles.css';
import './customPianoStyles.css'; // import a set of overrides
```

In the CSS file you can do things like:

```css
.ReactPiano__Key--active {
background: #f00; /* Change the default active key color to bright red */
}

.ReactPiano__Key--accidental {
background: #000; /* Change accidental keys to be completely black */
}
```

See [styles.css](/src/styles.css) for more detail on what styles can be customized.

## Upgrading versions

See the [CHANGELOG](CHANGELOG.md) which contains migration guides for instructions on upgrading to each major version.

## Browser compatibility
The demo site will be running at [localhost:3000](http://localhost:3000). Now you can make changes to react-piano and they'll be reflected on the demo site.

To support IE, you'll need to provide an `Array.find` polyfill.
## Deploying

## License
This repo is for testing deployment with Vercel.

MIT
Original version deployed at: https://www.kevinqi.com/react-piano.
25 changes: 25 additions & 0 deletions build/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"files": {
"main.css": "/react-piano/static/css/main.8bb316ed.chunk.css",
"main.js": "/react-piano/static/js/main.e22fcef4.chunk.js",
"main.js.map": "/react-piano/static/js/main.e22fcef4.chunk.js.map",
"runtime-main.js": "/react-piano/static/js/runtime-main.9c47d169.js",
"runtime-main.js.map": "/react-piano/static/js/runtime-main.9c47d169.js.map",
"static/css/2.1e78366a.chunk.css": "/react-piano/static/css/2.1e78366a.chunk.css",
"static/js/2.a060c8a8.chunk.js": "/react-piano/static/js/2.a060c8a8.chunk.js",
"static/js/2.a060c8a8.chunk.js.map": "/react-piano/static/js/2.a060c8a8.chunk.js.map",
"index.html": "/react-piano/index.html",
"precache-manifest.bdf53ad63144d6f095adeb116e40e562.js": "/react-piano/precache-manifest.bdf53ad63144d6f095adeb116e40e562.js",
"service-worker.js": "/react-piano/service-worker.js",
"static/css/2.1e78366a.chunk.css.map": "/react-piano/static/css/2.1e78366a.chunk.css.map",
"static/css/main.8bb316ed.chunk.css.map": "/react-piano/static/css/main.8bb316ed.chunk.css.map",
"static/js/2.a060c8a8.chunk.js.LICENSE": "/react-piano/static/js/2.a060c8a8.chunk.js.LICENSE"
},
"entrypoints": [
"static/js/runtime-main.9c47d169.js",
"static/css/2.1e78366a.chunk.css",
"static/js/2.a060c8a8.chunk.js",
"static/css/main.8bb316ed.chunk.css",
"static/js/main.e22fcef4.chunk.js"
]
}
File renamed without changes.
1 change: 1 addition & 0 deletions build/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/react-piano/manifest.json"><link rel="shortcut icon" href="/react-piano/favicon.ico"><title>react-piano demo page</title><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"><link href="/react-piano/static/css/2.1e78366a.chunk.css" rel="stylesheet"><link href="/react-piano/static/css/main.8bb316ed.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(l){function e(e){for(var r,t,n=e[0],o=e[1],u=e[2],a=0,i=[];a<n.length;a++)t=n[a],Object.prototype.hasOwnProperty.call(c,t)&&c[t]&&i.push(c[t][0]),c[t]=0;for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(l[r]=o[r]);for(s&&s(e);i.length;)i.shift()();return f.push.apply(f,u||[]),p()}function p(){for(var e,r=0;r<f.length;r++){for(var t=f[r],n=!0,o=1;o<t.length;o++){var u=t[o];0!==c[u]&&(n=!1)}n&&(f.splice(r--,1),e=a(a.s=t[0]))}return e}var t={},c={1:0},f=[];function a(e){if(t[e])return t[e].exports;var r=t[e]={i:e,l:!1,exports:{}};return l[e].call(r.exports,r,r.exports,a),r.l=!0,r.exports}a.m=l,a.c=t,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(r,e){if(1&e&&(r=a(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var n in r)a.d(t,n,function(e){return r[e]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/react-piano/";var r=this["webpackJsonpreact-piano-demo"]=this["webpackJsonpreact-piano-demo"]||[],n=r.push.bind(r);r.push=e,r=r.slice();for(var o=0;o<r.length;o++)e(r[o]);var s=n;p()}([])</script><script src="/react-piano/static/js/2.a060c8a8.chunk.js"></script><script src="/react-piano/static/js/main.e22fcef4.chunk.js"></script></body></html>
File renamed without changes.
30 changes: 30 additions & 0 deletions build/precache-manifest.bdf53ad63144d6f095adeb116e40e562.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "5a1a2a38e226fff0cf3486b69e4bc42b",
"url": "/react-piano/index.html"
},
{
"revision": "78db4f71f8511c0f1f95",
"url": "/react-piano/static/css/2.1e78366a.chunk.css"
},
{
"revision": "2c8bea898704a2794cf9",
"url": "/react-piano/static/css/main.8bb316ed.chunk.css"
},
{
"revision": "78db4f71f8511c0f1f95",
"url": "/react-piano/static/js/2.a060c8a8.chunk.js"
},
{
"revision": "f032203ca460334c00de541c30a6078a",
"url": "/react-piano/static/js/2.a060c8a8.chunk.js.LICENSE"
},
{
"revision": "2c8bea898704a2794cf9",
"url": "/react-piano/static/js/main.e22fcef4.chunk.js"
},
{
"revision": "d03279d92ea4b26af533",
"url": "/react-piano/static/js/runtime-main.9c47d169.js"
}
]);
39 changes: 39 additions & 0 deletions build/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
* See https://goo.gl/nhQhGp
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
* See https://goo.gl/2aRDsh
*/

importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");

importScripts(
"/react-piano/precache-manifest.bdf53ad63144d6f095adeb116e40e562.js"
);

self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});

workbox.core.clientsClaim();

/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/react-piano/index.html"), {

blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});
2 changes: 2 additions & 0 deletions build/static/css/2.1e78366a.chunk.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading