Why Call Tone.start Before Triggering Audio in Tone.js

When working with the Tone.js web audio library, you must call Tone.start() before any audio can be produced. This article explains the underlying browser security policies—specifically Autoplay restrictions—that necessitate this function call, how it interacts with the Web Audio API, and how to properly implement it within a user interaction event handler to ensure seamless audio playback.

Web Audio API and Browser Autoplay Policies

The requirement to call Tone.start() is not an arbitrary limitation of Tone.js, but rather a direct response to modern web browser security standards. To prevent websites from annoying users with unexpected, loud, or intrusive audio upon loading a page, web browsers enforce strict autoplay policies.

Under the hood, Tone.js relies on the browser’s native Web Audio API, which manages sound through an object called the AudioContext. By default, browsers initialize this AudioContext in a suspended state. Until the user explicitly interacts with the page, the browser blocks the audio context from resuming, keeping the application completely silent.

What Tone.start() Does

Tone.start() is a built-in helper function that attempts to resume the suspended AudioContext managed by Tone.js. Calling this function sends a signal to the browser to transition the audio state from “suspended” to “running.”

Once the context is running, Tone.js can successfully synthesize sounds, play audio files, and process effects. If you attempt to trigger a synthesizer, sampler, or player before calling Tone.start(), the underlying Web Audio API will ignore the playback commands, resulting in total silence and warnings in your developer console.

The Necessity of a User Gesture

For Tone.start() to work successfully, it must be triggered directly by a user interaction, such as a click, tap, or keypress. Browsers will reject any attempt to resume the audio context if it is called automatically on page load or inside an asynchronous callback not directly tied to a user event.

The standard way to implement this is to bind Tone.start() to a “Start” or “Play” button. Once the promise returned by Tone.start() resolves, you can safely trigger your synthesizer notes or start your audio transport.

// Example implementation
const button = document.querySelector('button');

button.addEventListener('click', async () => {
    // Resume the AudioContext
    await Tone.start();
    console.log('Audio context is active');
    
    // Play a synth note safely
    const synth = new Tone.Synth().toDestination();
    synth.triggerAttackRelease("C4", "8n");
});

By ensuring Tone.start() is called within a user-initiated event, you satisfy the browser’s security requirements, allowing your Tone.js applications to play audio reliably across all modern devices.