Building a Piano with React Hooks
Tags: react, javascript, webdev, tutorial
Published: 2020-02-07 03:45:29

---
title: Building a Piano with React Hooks
published: true
description: Building a Piano with React Hooks
tags: react,javascript,webdev,tutorial
canonical_url: https://cloudnweb.dev/2020/02/building-a-piano-with-react-hooks/
---


<p>In this article, we will see how to build a piano with <a href="https://AdvancedReact.com/friend/CLOUDNWEB">react</a> hooks. Building a Piano with React Hooks. if you are completely new to react hooks, check out this <a href="https://www.educative.io/courses/advanced-react-patterns-with-hooks?aff=VMkn">course</a>.</p>

<h3>Recent Articles,</h3>


<p><a href="https://cloudnweb.dev/2020/01/typescript-for-react-developers-in-2020/">TypeScript for React developers in 2020</a></p>



<p><a href="https://cloudnweb.dev/2019/12/building-real-time-api-using-graphql-subscriptions/">Building Real time API using graphql subscriptions</a></p>



<p>Before we proceed further, we will see a demo</p>


{% youtube 9R8zMAMYHWE %}


<p>Things to consider while building a piano is,</p>


<ul><li>How to map the laptop keys to piano notes.</li><li>Map the audio with key press.</li><li>How to render the piano keyboard in react.</li></ul>


<p>Let's try to break it down one by one. Firstly, we will see how to Add the Audio to react application in a button click.</p>

<p>we will be using a library called <a href="https://www.npmjs.com/package/soundfont-player">sound font player</a> for audio in react application.</p>

<pre class="wp-block-code"><code>npx create-react-app piano-hooks
npm i soundfont-player</code></pre>


<p>Once it is done, add a following code for Audio Player and Audio Context.</p>


<figure class="wp-block-image size-large"><img src="https://cloudnweb.dev/wp-content/uploads/2020/02/Screenshot-2020-02-01-at-7.45.43-PM.png" alt="" class="wp-image-2200"/></figure>



<p>Audio context will have the context and Audio Player will have two methods that are <strong>setInstrument</strong> and <strong>playNote</strong>.</p>



<pre class="wp-block-code"><code>import SoundFontPlayer from "soundfont-player";
import AudioContext from "./AudioContext";

const NullSoundFontPlayerNoteAudio = {
  stop() {}
};

const NullSoundFontPlayer = {
  play() {
    return NullSoundFontPlayerNoteAudio;
  }
};
const AudioPlayer = () => {
  //Audio Context
  const audioContext = AudioContext &amp;&amp; new AudioContext();

  //soundPlayer
  let soundPlayer = NullSoundFontPlayer;
  //setInstrument
  const Player = {
    setInstrument(instrumentName) {
      SoundFontPlayer.instrument(audioContext, instrumentName)
        .then(soundfontPlayer => {
          soundPlayer = soundfontPlayer;
        })
        .catch(e => {
          soundPlayer = NullSoundFontPlayer;
        });
    },
    playNote(note) {
      soundPlayer.play(note);
    }
  };
  return Player;
};

export default AudioPlayer;</code></pre>



<p>and AudioContext.js will contain</p>



<pre class="wp-block-code"><code>export default window.AudioContext;</code></pre>



<p>After that, Let's test if it is working properly, add the following code in <strong>App.js</strong></p>



<pre class="wp-block-code"><code>import React, { useEffect } from "react";
import "./App.css";
import AudioPlayer from "./core/AudioPlayer";
function App() {
  const audioPlayer = AudioPlayer();

  useEffect(() => {
    audioPlayer.setInstrument("acoustic_grand_piano");
  }, []);

  const handleClick = () => {
    audioPlayer.playNote("C4");
  };

  return (
    &lt;div className="app-container">
      &lt;button onClick={handleClick}>Play&lt;/button>
    &lt;/div>
  );
}

export default App;
</code></pre>



<p>Basically, we have a button that play the note when we click it. Here, useEffect will run on every component mount and set the instrument with a name.</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://cloudnweb.dev/wp-content/uploads/2020/02/Screenshot-2020-02-01-at-7.54.10-PM.png" alt="" class="wp-image-2201"/></figure></div>


<h4>Keyboard - Render Props</h4>


<!-- wp:paragraph -->
<p>Let's try to use a render props concepts on instrument. if you are not familiar with render props, check out this <a href="https://www.educative.io/courses/advanced-react-patterns-with-hooks?aff=VMkn">course</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Mainly, Instrument has two important parts.they are Instrument itself and instrumentAudio.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Firstly, we will see how to setup the instrumentAudio. we will move our app.js logic to instrumentAudio.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>create a file <strong>InstrumentAudio.js</strong> and add the following code,</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import React, { useEffect, useState } from "react";
import AudioPlayer from "./AudioPlayer";

const InstrumentAudio = ({ instrumentName, notes }) => {
  const [instrumentPlayer, setInstrumentPlayer] = useState(null);
  useEffect(() => {
    setInstrumentPlayer(AudioPlayer());
  }, []);

  useEffect(() => {
    if (instrumentPlayer) {
      setInstrument();
      playNotes();
    }
  }, [instrumentPlayer]);

  useEffect(() => {
    if (notes &amp;&amp; notes.length > 0) {
      playNotes();
    }
  }, [notes]);

  const setInstrument = () => {
    instrumentPlayer.setInstrument(instrumentName);
  };

  const playNotes = () => {
    if (instrumentPlayer) {
      instrumentPlayer.playNote(notes[0]);
    }
  };

  return null;
};

export default InstrumentAudio;</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Here, we are maintaining the instrumentPlayer in state, so that we can have the control of it. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>when the component mount first, it will call the <strong>setInstrument</strong> method which will set the instrument with the name.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>After that, every time the notes props change, it will play the note which is defined in the useEffect which has notes dependancy.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now, it is time to implement the Instrument itself. instrument will have the start note and end note as props. based on that, it will render all the notes in between.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import React, { Fragment } from "react";
import InstrumentAudio from "./Keyboard/InstrumentAudio";
import getNotesBetween from "./utils/getNotesBetween";

const Instrument = ({ instrumentName, startNote, endNote }) => {
  const notes = getNotesBetween(startNote, endNote);
  return (
    &lt;Fragment>
      {notes.map(note => {
        return &lt;Fragment>Note is : {note}&lt;/Fragment>;
      })}
      &lt;InstrumentAudio />
    &lt;/Fragment>
  );
};

export default Instrument;</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Here we get all the notes in between the start note and end note. create a file called <strong>notes.js</strong> and add the following code.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>const TONES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const OCTAVE_NUMBERS = [1, 2, 3, 4, 5, 6, 7];

export default OCTAVE_NUMBERS.reduce((notes, octaveNumber) => {
  const notesInOctave = TONES.map(tone => `${tone}${octaveNumber}`);
  return [...notes, ...notesInOctave];
}, []);</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>After that, create a file <strong>getNotesBetween.js</strong> to get all the notes between start note and end note.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import NOTES from "../constants/note";

export default function getNotesBetween(startNote, endNote) {
  const startingIndex = NOTES.indexOf(startNote);
  const endingIndex = NOTES.indexOf(endNote);
  return NOTES.slice(startingIndex, endingIndex + 1);
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now, it is time to add the instrument and it's state notes in the <strong>Instrument.js</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import React, { Fragment, useState } from "react";
import InstrumentAudio from "./Keyboard/InstrumentAudio";
import getNotesBetween from "./utils/getNotesBetween";
import isAccidentalNote from "./utils/isAccidentalNote";

const Instrument = ({
  instrumentName,
  startNote,
  endNote,
  renderPianoKey,
  keyboardMap
}) => {
  const notes = getNotesBetween(startNote, endNote);

  const [state, setState] = useState({
    notesPlaying: []
  });

  const onPlayNoteStart = note => {
    setState({ ...state, notesPlaying: [...state.notesPlaying, note] });
  };

  const onPlayNoteEnd = note => {
    setState({
      ...state,
      notesPlaying: state.notesPlaying.filter(
        notePlaying => notePlaying !== note
      )
    });
  };

  return (
    &lt;Fragment>
      {notes.map(note => {
        return (
          &lt;Fragment key={note}>
            {renderPianoKey({
              note,
              isAccidentalNote: isAccidentalNote(note),
              isNotePlaying: state.notesPlaying.includes(note),
              startPlayingNote: () => onPlayNoteStart(note),
              stopPlayingNote: () => onPlayNoteEnd(note),
              keyboardShortcut: getKeyboardShortcutsForNote(keyboardMap, note)
            })}
          &lt;/Fragment>
        );
      })}
      &lt;InstrumentAudio
        instrumentName={instrumentName}
        notes={state.notesPlaying}
      />
    &lt;/Fragment>
  );
};

export default Instrument;</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Logic here is, renderPianoKey is a render props with the state from Instrument Component. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>isAccidentalNote checks whether the note is a natural key or <a href="https://en.wikipedia.org/wiki/Accidental_(music)">accidental</a> key. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>isAccidentalNote.js</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import NOTES from '../constants/note'
export default (note) => {
    return NOTES.includes(note) &amp;&amp; note.includes('#')
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>isNotePlaying checks the state whether the note is in the state of playing notes.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>startPlayingNote method gets called when user clicks the the button, when it is gets called, we add the particular note to the state.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>on stopPlayingNote, we remove the note from the state.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>finally, we add the keyboard actions such as keydown and keyup to handle the keyboard actions.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code> useEffect(() => {
    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);
  }, []);

const handleKeyDown = e => {
    if (isRegularKey(e) &amp;&amp; !e.repeat) {
      const note = getNoteFromKeyboardKey(e.key);
      if (note) {
        setState({ ...state, notesPlaying: [...state.notesPlaying, note] });
      }
    }
  };

  const handleKeyUp = e => {
    if (isRegularKey(e) &amp;&amp; !e.repeat) {
      const note = getNoteFromKeyboardKey(e.key);
      if (note) {
        setState({
          ...state,
          notesPlaying: state.notesPlaying.filter(
            notePlaying => notePlaying !== note
          )
        });
      }
    }
  };</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>Piano</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Since the Instrument uses a render props. we need to pass the instrument component from the <strong>Piano.js</strong> file.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here, we have the renderPianoKey function which takes all the argument from that method. if it is a accidental note, it renders the accidental key component.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If it is a natural key note, it renders the natural key component. Also, we need to provide the keyboardmap where each key will be mapped with piano notes.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Complete Source <a href="https://github.com/ganeshmani/react-piano-hooks">code</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://ganeshmani.github.io/react-piano-hooks/">Demo</a></p>
<!-- /wp:paragraph -->