Tutorial: Let’s Make Music with JavaScript and Tone.js
By: Jim Bennett
Tags: JavaScript, Web Audio Api, Tonejs

TL;DR Check out a live demo of the audio here and the complete repo here.

Tone.js makes working with the Web Audio API much easier because it takes care of a lot of tedious tasks for you. If you’ve ever worked with the Web Audio API you may have developed some strong opinions about it, but one thing you are likely to agree with is that it’s a bit tricky to do anything meaningful at first, apart from having a couple of oscillators generate a tone.

Tone.js has an extensive API—rather than explaining them all in detail here, I encourage you to check out the docs as well as some of the demos on the Tone.js site.

In this post, we’ll work through some basic examples of how to use the Tone.js API to make music in your web browser, ultimately putting what we’ve learned together to recreate the Mission: Impossible theme.

As an advanced example of what you can do with Tone.js, I created a project using a few useful pieces from the library to recreate the theme to the 80’s TV show Knight Rider.

Yes, the man, the myth, the legend David Hasselhoff starred in this show from the 80s before he saved lives on Baywatch every week. And if you’re not familiar with the Knight Rider theme song, you may have heard it sampled by artists like Busta Rhymes in the song Turn It Up (Remix) / Fire It Up.

You can check out many more artists who have sampled this iconic TV theme too.

But this story isn’t about the history of sampled TV themes! It’s about creating music with JavaScript, so let’s take a look at a few examples…

Basic Examples

Let’s learn the essentials of Tone.js by looking at some basic examples. Be sure to try these examples out in the embedded CodePens to see them in action.

Important! Turn your device volume down before opening the links.

Creating a Kick Sound

// create a new synth and route the output to master

const synth = new Tone.MembraneSynth().toMaster(); // play a note with the synth we setup

synth.triggerAttackRelease("C2", "8n");

Creating a kick sound

Looping the Kick Sound

We’re going to create a sequence shortly, but to create a simple repeating pattern for now we can use the Tone.Loop function.

Tone.Loop takes two arguments, which are (callback, interval) .

The callback is the function that runs each time the loop repeats—in our example this is where we play a note using synth.triggerAttackRelease .

The interval is the unit of time in which the loop repeats. In this example, the time is set to half notes using 2n:

Half note notation example

// create a new synth

const synth = new Tone.MembraneSynth().toMaster(); // create a new tone loop

const loop = new Tone.Loop(function(time) { // Run once per eighth note, 8n, & log the time

console.log(time); // trigger synth note

synth.triggerAttackRelease("C2", "2n"); }, "2n").start(0); // Start the transport which is the main timeline

Tone.Transport.start();

Looping the kick sound

Create a Sequence with an Array of Notes

You can create a sequence with Tone.Sequence and pass an array of note values to be played.

When you create a new sequence, you can pass in a callback, events, and a subdivision:

Tone.Sequence ( callback , events , subdivision )

The callback is the function that runs for every note, events are the notes in the sequence, and subdivision is the overall timing of playback (e.g. 4n = quarter notes). You can find more info about the timing of playback and its values in the timing options docs.

Building on what we already learned about representing half notes, quarter notes are represented with 4n:

Quarter notes

Eighth notes are represented with 8n:

Eighth notes

Sixteenth notes are represented with 16n:

Sixteenth notes

Let’s combine this into a slightly more complex example now:

// create a synth

const synth = new Tone.MembraneSynth().toMaster(); // create an array of notes to be played

const notes = ["C3", "Eb3", "G3", "Bb3"]; // create a new sequence with the synth and notes

const synthPart = new Tone.Sequence(

function(time, note) {

synth.triggerAttackRelease(note, "10hz", time);

},

notes,

"4n"

); // Setup the synth to be ready to play on beat 1

synthPart.start(); // Note that if you pass a time into the start method

// you can specify when the synth part starts

// e.g. .start('8n') will start after 1 eighth note // start the transport which controls the main timeline

Tone.Transport.start();

Creating a sequence with an array of notes

Music Theory Sidebar: Figuring Out Notes Values

In the examples above, the images with notation are only showing rhythms with a fixed note of “C.” However, you may have noticed that in the previous examples that we included other notes and numbers after them (for example, “C3”).

The letters and numbers represent the note value (e.g. “C”) and the octave of the note (the overall pitch). If you’ve ever heard the term “middle C” before, it would be represented by “C4”, and it’s called “middle C” because it’s roughly in the middle of the piano.

Piano frequencies

You can learn more about assigning note values here.

Adding Rests

You may have noticed how the notes we have in our sequence all play one after the other, but what do you do if you need to have a break between notes?

We’ll need to add “rests” for that! If we revisit our example above—specifically the notes—we can add a value of null to our array to create a rest at a particular index:

const notes = [“C3”, “Eb3”, null, “G3”, “Bb3”];

Notice that we also now have a five-beat repeating pattern—pretty cool if you don’t mind me saying so!

Adding rests

If we wanted to create the iconic note pattern to the Mission: Impossible theme we could try:

const notes = ["G2", null, "G2", "Bb2", "C3"];

Mission: Improbable

The rhythm isn’t quite right yet, so let’s try modifying our notes array:

const notes = ["G2", [null, "G2"], null, "Bb2", "C3"];

Mission: Implausible

OK, almost there! Finally, let’s update the array of notes to achieve the actual Mission: Impossible sequence and rhythm.

const notes = [

"G2",

[null, "G2"],

null,

"Bb2",

"C3",

"G2",

[null, "G2"],

null,

"F2",

"F#2"

];

Mission: Impossible

Now that you know some of the basics and working reproduction of the Mission: Impossible tune, check out the repo I’ve put together which has all of the pieces we’ve looked at so far, as well as effects, sampling, and even simple DOM manipulation used to recreate the Knight Rider theme:

Be sure to check out the demo site and the bonus challenge where you can try to figure out how to write the lead melody as well.