Record Audio Output from Tone.js to a File

This article provides a quick guide on how to capture and save the audio output generated by a Tone.js project directly into an audio file. You will learn how to set up the Tone.Recorder class, connect it to your audio graph, start and stop the recording, and trigger a browser download of the resulting audio file.

Step 1: Create and Connect the Recorder

Tone.js includes a built-in Tone.Recorder utility designed specifically for capturing audio. To record everything playing in your project, you must connect the global output (Tone.Destination) to a new instance of Tone.Recorder.

// Create a new instance of the Tone.js Recorder
const recorder = new Tone.Recorder();

// Connect the main output to the recorder
Tone.Destination.connect(recorder);

If you only want to record a specific instrument or effect instead of the entire project, connect that specific node directly to the recorder instead of Tone.Destination.

Step 2: Start the Recording

Before you start playing any audio or scheduling events, you must start the recorder. The start() method prepares the recorder to capture incoming audio signals.

// Start recording audio
recorder.start();

// Start your synthesizers, players, or transport
synth.triggerAttackRelease("C4", "8n");

Step 3: Stop the Recording and Download the File

The stop() method is an asynchronous operation that stops the recording and returns a Promise. This Promise resolves to a Blob containing the recorded audio data (typically in WebM or WAV format, depending on browser support).

To save this file directly to the user’s device, convert the Blob into an object URL and trigger a virtual click on an anchor (<a>) element.

async function stopAndSaveRecording() {
  // Stop the recorder and retrieve the audio Blob
  const recording = await recorder.stop();

  // Create a temporary URL pointing to the Blob
  const url = URL.createObjectURL(recording);

  // Create a temporary anchor element to trigger the download
  const anchor = document.createElement("a");
  anchor.download = "tonejs-recording.webm";
  anchor.href = url;
  
  // Trigger the download in the browser
  anchor.click();

  // Clean up the object URL from memory
  URL.revokeObjectURL(url);
}

Complete Implementation Example

Here is how to combine these steps into a simple, functional implementation with start and stop buttons:

import * as Tone from "tone";

const synth = new Tone.Synth().toDestination();
const recorder = new Tone.Recorder();
Tone.Destination.connect(recorder);

document.getElementById("start-btn").addEventListener("click", async () => {
  await Tone.start(); // Ensure the audio context is active
  recorder.start();
  
  // Play a demo note
  synth.triggerAttackRelease("E4", "1m");
});

document.getElementById("stop-btn").addEventListener("click", async () => {
  const recording = await recorder.stop();
  const url = URL.createObjectURL(recording);
  const anchor = document.createElement("a");
  anchor.download = "my-tonejs-track.webm";
  anchor.href = url;
  anchor.click();
  URL.revokeObjectURL(url);
});