How to Prevent Memory Leaks in Tone.js

Dynamically creating and destroying synth instances in Tone.js can quickly degrade browser performance and cause audio glitching if memory is not managed correctly. Because Tone.js operates on the Web Audio API, simply dereferencing a variable is not enough to trigger garbage collection. This article provides a direct, actionable guide on how to properly clean up synth instances, disconnect audio nodes, and free up system memory when dynamically managing audio in Tone.js.

Why Tone.js Synths Cause Memory Leaks

In standard JavaScript, setting a variable to null makes it eligible for garbage collection. However, Web Audio API nodes (which power Tone.js) are tied to the browser’s audio graph. If a synth is still connected to the master output (toDestination()) or another effect, the browser maintains a reference to it in the audio thread.

To completely remove a synth from memory, you must explicitly disconnect it from the audio graph, release its internal sub-nodes, and then delete your JavaScript references.

The Correct Disposal Workflow

To prevent memory leaks when destroying a dynamically created synth, you must follow a three-step process: stop the audio, dispose of the node, and nullify the variable.

1. Trigger Release (Stop Active Audio)

If the synth is currently playing a note, call triggerRelease() to let the envelope finish. Destroying a synth mid-oscillation can cause audible clicks or pops.

synth.triggerRelease();

2. Dispose of the Synth

Tone.js provides a built-in .dispose() method on almost all of its classes. Calling .dispose() is the most critical step. It automatically disconnects the synth from any output nodes, stops internal oscillators, and cleans up internal Web Audio nodes.

synth.dispose();

3. Clear JavaScript References

After disposing of the synth, clear any references to it in your code so the JavaScript engine’s garbage collector can reclaim the memory.

synth = null;

Code Example: Safe Dynamic Creation and Destruction

Below is a practical implementation of creating a synth on demand and destroying it safely after use.

let activeSynths = [];

// Function to dynamically create and play a synth
function playDynamicSound() {
    const synth = new Tone.Synth().toDestination();
    activeSynths.push(synth);

    // Play a note
    synth.triggerAttackRelease("C4", "8n");

    // Schedule cleanup after the note has finished playing (e.g., after 1 second)
    setTimeout(() => {
        cleanupSynth(synth);
    }, 1000);
}

// Function to safely destroy the synth
function cleanupSynth(synth) {
    // 1. Disconnect from the audio graph
    synth.disconnect();

    // 2. Dispose of internal Tone.js Web Audio nodes
    synth.dispose();

    // 3. Remove from local reference arrays
    activeSynths = activeSynths.filter(s => s !== synth);
}

Best Practices for Dynamic Audio in Tone.js