Build a piano with Tone.js

Joe Young
8 min readFeb 17, 2021

--

This article will serve as a beginner’s guide to using Tone.js and together we’ll make a piano! I’ll go through each step including setting your directory and React app up. (If you know that stuff already, then you can jump to step #3.)

  • I’ve included the 3 files used for this project at a Github repo here, for your reference.
  • If you learn better by watching a video, here is the same tutorial I made on YouTube.

What is Tone.js?

Tone.js is a Web Audio framework for creating interactive music in the browser. The architecture of Tone.js aims to be familiar to both musicians and audio programmers creating web-based audio applications. On the high-level, Tone offers common DAW (digital audio workstation) features like a global transport for synchronizing and scheduling events as well as prebuilt synths and effects. Additionally, Tone provides high-performance building blocks to create your own synthesizers, effects, and complex control signals.

Ok, let’s get started!

1. Make your directory and open up your environment.
Open terminal and move to your preferred location and type mkdir tone-piano-folder. (You can name it whatever you want.)
Open that new folder with code tone-piano-folder.
VSCode opens up: game time.

2. Create React App
Open up your terminal in your coding environment and run npx create-react-app tone-piano. This may take a few minutes (or seconds) and when it’s done you’ll have the basic boilerplate for a React app.
When it’s done installing we’ll follow their prompt and cd tone-piano into your new app.
Run npm start to start the app in your browser. Navigate to the App.js file (tone-piano → src → App.js) and delete everything between the two div tags. You can delete the class name in the div and the logo import on line 1, too. (Keep the App.css import.)

3. Install Tone.js
In your editor’s terminal, install Tone.js by typing npm install tone. This is, as you might have guessed by its name, the library that will supply us with all the tones and methods for the piano.
Go to the Tone.js site and scroll down to the first bit of code you see labeled, “Hello Tone.”

This is what we’ll be working with. (If you click “run”, you hear the note C, specifically C4.) I’ll explain this code more later on, but for now copy and paste it into your App.js, above where App() is declared.

For a tone to be rendered with Tone.js, the user has to interact with something on the page, like the “run” button we just clicked. So let’s make a button! (And a function.) We can call this function playC4() and wrap the code we have already inside of it. Make sure we’ve imported Tone.js with import * as Tone from “tone” at the top of the file. Now create a button inside the div. We’ll include an onClick with our new function inside. Save/refresh your code and check out your browser. You should have button that renders the note C when you click it!

4. Create Tone Functions
Now we’ll create all the notes for the piano. To reduce clutter, instead of putting all of our functions in the App.js file, we’ll create a new file especially for them. Create a new file and call it tone.fn.js (You can also call this whatever you want, but it’s best to label it so you know what’s inside.)

Inside App.js let’s remove the Tone import and tone function we wrote and put them inside of the new file. In front of our function, add export. The functions in this file will be exported back to the App.js file.

About this function

new Tone.Synth() declares a new instrument “synth” (synthesizer, a keyboard)

toDestination() sends the tone to the user’s speakers

triggerAttack() starts the tone & triggerRelease() ends the tone; so combining those to triggerAttackRelease() provides an immediate start and stop

the parameters are the note (“C4”) and duration (“8n”) respectively.

Quick music lesson

We are making a one-octave piano, which means there will be 12 notes before we start the sequence over with the next octave.

The “8n” parameter refers to an eighth-note, which indicates its duration. More about note lengths here.

Take the existing function, and copy and paste it 12 more times, for a total of 13 (for the 13 tones we’ll be creating).

The notes we are adding are C4, Db4, D, Eb4, E4, F4, Gb4, G4, Ab4, A4, Bb, B4, C5.

If you’re familiar with music notation or read music, you know that notes have enharmonic spellings, meaning C# and Db are the same note. Same with D# and Eb or F# and Gb, for example. (The # in music is called “sharp” while the♭is called “flat”; e.g., C sharp or D flat. We will use a lowercase “b” instead of the specific character ♭.)

This is only important to mention for naming-continuity with our functions. I prefer the function names and the note parameter (the first parameter in the function, e.g. “C4”) to match.

We could write “C#4” as the note parameter for the function that renders the note C#4, but we can’t call the function playC#4() as the # sign can only be recognized as a string. (It’ll throw an error.) But writing playDb4(), of course, is fine.

For each function and the first parameter, we’ll insert each note name in this order: C4, Db4, D, Eb4, E4, F4, Gb4, G4, Ab4, A4, Bb, B4, C5. We’ll leave the second parameter as “8n.”

  • *UPDATE** A cleaner way to include all of these tone functions, especially if you end up adding many more notes, is to forego the tone.fn.js file altogether and use template literals.

Here’s how: in your App.js file, include the following:

function play(note) {
const piano = new Tone.Sampler({
urls: {
C4: "C4.mp3",
"D#4": "Ds4.mp3",
"F#4": "Fs4.mp3",
A4: "A4.mp3",
},
release: 1,
baseUrl: "https://tonejs.github.io/audio/salamander/", }).toDestination();

You can put that code outside of function App(). Within function App(), include:

Tone.loaded().then(() => {
piano.triggerAttackRelease(`${note}`, 4);
});
console.log(note);
}

Finally, instead of importing a bunch of individual functions from tone.fn.js, you can get rid of those completely. The functional code is now in App.js. For the keys, there is now no need to write playC4, play Db4, etc.

Now for each div you can write play(“__”), where in the quotations you would put the note name. No need for a single note to have it’s own specific function! So a few examples would look like play(“C4”), play(“Gb4”), etc. And that single piece of code is now located in the same file and takes up less space.

**Using this approach makes most of the next step unnecessary.**

5. Build the Piano
In the App.js file, we’ll import all the functions we wrote.

In the template literal approach, this import will not occur.

We can also get rid of the button — we won’t be using that anymore.
Now we’ll build the structure of the piano with a parent div “pianoPage”, a child div “piano” and its 13 child divs which will become our piano keys. In the 13 divs we’ll add the class names “white-key” or “black-key”. Include them in the order you see in the image below. That order is important to make the piano look accurate.

**In the template literal approach, this look identical except the functions look like play(“C4”), play(“Db4”), etc.

CSS time! In the App.css file, delete all the existing code. This is where we will bring the piano to life.

In the piano class by adding the display as flex, that will allow the inner divs (the keys) to stay next to each other. To allow the black-key divs to sit evenly on top of two white keys, we split the width in half (60/2 = 30) and use that number but negative (-30) for both margin-left and margin-right. Adding a z-index of 1 allows us to guarantee we select only the black key when we click on it, and not the white key since part of it is technically living in the same grid space. The z-index of 1 just brings the div closer to the front.
Save and refresh your code, go to the browser, and you have a working piano!

6. Assign Keys
Instead of having to click each note we can use the keyboard. We’ll use the following keys since their order on the keyboard resembles the direction the piano keys occur.

To assign keys to a function, we cannot simply use the key name, such as “A” or “W”. We have to use the key code.
If you go to javascriptkeycode.com, you can find the key code for any key. (e.g., 71 = G, 68 = D, 87 = W)

In the tone.fn.js file, scroll to the bottom and we’ll create a code to assign the key codes to our functions. Let’s call it playNote. This will be an exported function that is fired on an event, the event being when the user selects a key on their keyboard. They’ll hit the “A” key and hear the note C4 rendered. So the function will be playNote(event) with each function being a conditional if statement.

In the App.js file, add playNote to the list of imported functions. Directly after the import, add window.addEventListener(“keydown”, playNote). This listens for the event (“keydown” — a key pressed down) and if a key is connected to a function in playNote() that function will fire, giving us a note!

**Is nothing rendering in your browser? Make sure npm start is still actively running in your terminal. Kill the terminal (CTRL + C)and run npm start again if you have to.**

7. Label the Keys
Now add each key name for each div.

When we refresh and look in the browser we only see some of the note names, because there is black font on black keys. In the App.css file for both white and black keys, we can color and center the text with color: red and text-align: center.

Now you have a one-octave piano that you can play by clicking or using your keyboard. I encourage you to explore all of the amazing things you can do with Tone.js.

Questions? Comments? Suggestions? Please leave them below!

--

--