Make a Synthesizer With Tone.js
Create a playable synth using only HTML, CSS, and JavaScript with the Tone.js library.
This is the first part of the series "Make Music With Tone.js."
Maybe you want to make music, but you don't want to spend money on a DAW or expensive equipment. Or maybe you're just too lazy to learn an instrument. Either way, I have the perfect solution for you: create a playable synthesizer using Tone.js! For this short guide, all you'll need is basic knowledge of HTML, CSS, and JavaScript.
What Is Tone.js?
Tone.js is a JavaScript library for creating interactive music in the browser. With Tone.js, you can create sound files and play them back, create synthesizers and samplers, and much more. You can even use Tone.js to create entire songs! For our purposes, we'll just be using it to create a simple synthesizer that we can play with our mouse or keyboard.
Create a Project
First, we need to create a new project. We'll do this by creating a new folder on our computer and giving it a name. I'm going to call mine "tone-synth". Inside this folder, we'll create three new files: index.html, style.css, and script.js. These are the files that we'll be working with in this tutorial.
Make the Synth
Let's get to work making each part of a simple monophonic synth. We'll want to make a nice piano UI in HTML, style it with CSS, then make it play notes when we click each piano key as well as map each note to the keyboard.
HTML
First, we'll create our HTML and add links to the stylesheet as well as the . We'll start with a simple structure that looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- our piano will go here -->
<script src="https://cdn.jsdelivr.net/npm/tone@14.7.77/build/Tone.min.js"></script>
<script src="main.js"></script>
</body>
</html>
Now, let's add the HTML for our piano keys. We'll use a simple structure of <div>
s with each representing a key, and give it a class to target in CSS. We'll also give each key a data-note
value to tell Tone.js what note to play, as well as a data-key
to map it to the QWERTY keyboard.
<div class="piano">
<div class="key white" data-note="C3" data-key="a"></div>
<div class="key black" data-note="C#3" data-key="w"></div>
<div class="key white" data-note="D3" data-key="s"></div>
<div class="key black" data-note="D#3" data-key="e"></div>
<div class="key white" data-note="E3" data-key="d"></div>
<div class="key white" data-note="F3" data-key="f"></div>
<div class="key black" data-note="F#3" data-key="t"></div>
<div class="key white" data-note="G3" data-key="g"></div>
<div class="key black" data-note="G#3" data-key="y"></div>
<div class="key white" data-note="A3" data-key="h"></div>
<div class="key black" data-note="A#3" data-key="u"></div>
<div class="key white" data-note="B3" data-key="j"></div>
<div class="key white" data-note="C4" data-key="k"></div>
<div class="key black" data-note="C#4" data-key="o"></div>
<div class="key white" data-note="D4" data-key="l"></div>
<div class="key black" data-note="D#4" data-key="p"></div>
<div class="key white" data-note="E4" data-key=";"></div>
<div class="key white" data-note="F4" data-key="'"></div>
</div>
That's it for our HTML! Now let's move on to CSS.
CSS
Our CSS is fairly simple but results in a nice looking piano. We'll start by setting some globals and styling the piano:
*,
*::before,
*::after {
box-sizing: inherit;
}
html,
body {
box-sizing: border-box;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100vh;
background-color: #282938;
}
.piano {
display: flex;
position: relative;
padding: 40px;
padding-bottom: 0;
background-color: #341304;
border: 1px solid #160801;
border-radius: 16px;
user-select: none;
}
We want our piano keys to look like, well, piano keys! To do this, we'll give the keys a width and height, then offset the black keys' margin. Then, set their background color and change it when a key is active.
Our CSS for that will look like this:
.key {
width: 75px;
height: 350px;
border: 2px outset #dddddd;
border-radius: 0 0 6px 6px;
background: white;
margin-bottom: -8px;
}
.key.black {
z-index: 1;
height: 225px;
margin-left: -24px;
margin-right: -24px;
width: 50px;
border-color: #5f5f5f;
background-color: #222;
}
.key.white.active {
background-color: #ddd
}
.key.black.active {
background-color: #000
}
.key.white + .key.white {
margin-left: 2px;
}
Here's what it should look like:
Lookin' good. Let's move on to the JavaScript portion of our project.
JavaScript
The main.js
file is where we'll put all of our code that makes our synth actually work. Let's start by initializing a Tone.js Synth
and creating an array from our piano key elements that we can access later:
const synth = new Tone.Synth().toDestination();
const keys = Array.from(document.querySelectorAll('.key'));
Now let's add an event listener so our keys make a sound when we click them. We can do this by creating a 'mousedown'
event on document
, checking if the event target element contains the class 'key'
, then triggering an attack with Tone.js. We'll also want to add the 'active'
class to the element so that we have a visual indicator:
document.addEventListener('mousedown', (e) => {
if (!e.target.classList.contains('key')) {
return;
}
synth.triggerAttack(e.target.dataset.note);
e.target.classList.add('active');
});
We don't want our note to play forever, so let's create a 'mouseup'
event listener, where we remove the 'active'
class from all keys and trigger a release:
document.addEventListener('mouseup', () => {
keys.forEach(key => key.classList.remove('active'));
synth.triggerRelease();
});
That's it for our simple synth! You can test it out by opening index.html
in your browser and clicking on each key. You should hear a different note being played back for each key you click on. But what if we want to be able to play our synth with our keyboard instead of just our mouse? Let's do that!
Since each key has a data attribute with its associated QWERTY key, we can create event listeners for 'keydown'
then find the element containing that data attribute in the keys
array. For 'keyup'
, we'll do the same thing we did for 'mouseup'
.
document.addEventListener('keydown', (e) => {
const key = keys.find(key => key.dataset.key === e.key);
if (!key) {
return;
}
key.classList.add('active');
synth.triggerAttack(key.dataset.note);
});
document.addEventListener('keyup', () => {
keys.forEach(key => key.classList.remove('active'));
synth.triggerRelease();
});
Now you can play the notes with your keyboard!
Test It Out
To test out your new synth, open index.html
in your browser and try clicking on each key or pressing the corresponding letter on your keyboard. You should hear a different note being played back for each key you click or press.
Here's a working demo on CodePen:
The GitHub repo can be found here.
Conclusion
If everything sounds good, congratulations! You've just created your very first playable synthesizer with Tone.js!
Of course, this is just scratching the surface. In the next article, we'll dive deeper into Tone.js and expand our synth to be polyphonic, as well as add controls to change the sound, adjust levels, switch octaves, and add effects!
Let me know if this was helpful in the comments or if you have any questions.
Be sure to follow me for more like this!