How to Create Send and Return Channels in Tone.js

This article explains how to set up auxiliary send-and-return effects channels in Tone.js to route multiple audio sources through a single shared effects processor. Using this method allows you to apply effects like reverb or delay to various instruments at different volume levels, optimizing performance and keeping your dry and wet audio signals separate.

In Tone.js, any audio node can send a portion of its signal to a named global bus using the .send() method. An auxiliary effect node can then listen to this channel using the .receive() method. This mirrors the send-and-return architecture found in hardware mixing consoles and Digital Audio Workstations (DAWs).

Step 1: Create the Return Effect Channel

First, instantiate the effect you want to use on your auxiliary channel (for example, a feedback delay) and connect it to the main destination. To turn this effect into a “return” channel, call the .receive() method on it and provide a unique string identifier for the channel.

import * as Tone from 'tone';

// Create the return effect (e.g., a delay)
const delayEffect = new Tone.FeedbackDelay("8n", 0.5).toDestination();

// Set the effect to 100% wet since dry signal is handled by the main channels
delayEffect.wet.value = 1.0;

// Register this node to receive signals from the "delay-bus" channel
delayEffect.receive("delay-bus");

Step 2: Create Instruments and Send Signals

Next, create your sound sources. You can output their dry signals directly to the destination while simultaneously sending a portion of their signals to the "delay-bus" channel using the .send() method. The .send() method accepts the channel name as the first argument and the decibel level of the send as the second argument.

// Instrument 1: Main Synth
const leadSynth = new Tone.Synth().toDestination();

// Instrument 2: Background Synth
const padSynth = new Tone.PolySynth().toDestination();

// Send a strong signal from the lead synth to the delay bus
leadSynth.send("delay-bus", -6); // -6 dB send level

// Send a subtle signal from the pad synth to the delay bus
padSynth.send("delay-bus", -20); // -20 dB send level

Step 3: Trigger the Sounds

With the routing established, triggering these instruments will play their direct (“dry”) sound through the main destination, while routing the specified amounts to the shared delay effect (“wet” sound).

// Start the audio context and play notes
async function playSounds() {
  await Tone.start();
  
  const now = Tone.now();
  
  // Trigger lead synth (will have a prominent delay effect)
  leadSynth.triggerAttackRelease("C5", "8n", now);
  
  // Trigger pad synth (will have a very subtle delay effect)
  padSynth.triggerAttackRelease(["E3", "G3", "B3"], "2n", now + 0.5);
}

By adjusting the decibel value in the .send("channel-name", decibels) method, you can dynamically control how much of each individual instrument’s sound is processed by the shared auxiliary effect.