Audio | Part 2 — Playing Accurately Timed Beats with Tone.js

Creating music in a web browser has never been as accessible as it is now, thanks to the power of libraries like Tone.js. This JavaScript framework allows us to work with audio in the browser, providing a level of control and flexibility that opens a whole new world of possibilities. In this blog post, we'll dive into how to play a beat with accurate timing using Tone.js.

Setting the Stage

First and foremost, we need to ensure Tone.js is included in our project. You can either include it directly in your HTML file from a CDN or install it using npm for a more development-oriented setup.

Here's how to include it via a CDN:

<script src="https://cdn.tonejs.io/14.7.58/Tone.js"></script>

Or install it with npm:

npm install tone

And import it in your JavaScript file:

import * as Tone from 'tone';

Building and Timing a Beat

To create a beat, we'll need samples of the sounds we wish to play, such as a kick, snare, or hi-hat. For the sake of this tutorial, we'll assume you have three audio samples available: kick.mp3, snare.mp3, and hihat.mp3. We'll load these files into Tone.Player objects and create a function to play them at the correct times.

Here's a simple implementation:

const kick = new Tone.Player("./path/to/your/kick.mp3").toDestination();
const snare = new Tone.Player("./path/to/your/snare.mp3").toDestination();
const hihat = new Tone.Player("./path/to/your/hihat.mp3").toDestination();

const playBeat = () => {
    Tone.Transport.scheduleRepeat((time) => {
        kick.start(time);
        hihat.start(time + 0.5);
        snare.start(time + 1);
        hihat.start(time + 1.5);
    }, "2n");
    
    Tone.Transport.start();
};

document.querySelector('button').addEventListener('click', () => {
    Tone.start().then(() => {
        playBeat();
    });
});

Let's break this down:

  1. new Tone.Player("x").toDestination();: We're creating new Player objects for each sample and routing their output to the default audio output.

  2. Tone.Transport.scheduleRepeat(t => {...}, "2n");: Tone.js provides us with a Transport object, which we can think of as our song's timeline. The scheduleRepeat method allows us to schedule repeating events. Here, we're telling it to play our samples at different times within a 2-beat (or "2n") cycle.

  3. sample.start(time + [offset]);: We're starting each sample at a specific time within our cycle. The start method accepts a time parameter, which can be an offset from the current Transport time. This allows us to create our beat pattern.

  4. Tone.Transport.start();: Finally, we're starting the Transport, which will begin our cycle.

  5. The addEventListener on the button: Since browsers typically require a user interaction before starting audio playback, we've wrapped our playBeat function in a button click event.

Demo

That's it! You've created your first accurately timed beat using Tone.js! Try out the demo below. Download the native HTML / JS code below as well to run the demo locally.

Wrapping Up

And there you have it! You've created a simple beat in the browser with accurate timing. Tone.js offers much more to explore, so don't hesitate to dive deeper and create complex rhythms, melodies, and even complete songs.