Create Stereo Movement with Tone.AutoFilter in Tone.js

This article explains how to create dynamic stereo filter sweeps in Tone.js by leveraging Tone.AutoFilter. You will learn how to split an audio signal, apply independent auto-filters with offset LFO phases to the left and right channels, and merge them back together to achieve a wide, moving stereo effect.

The Logic Behind Stereo Auto-Filtering

By default, a single Tone.AutoFilter processes both the left and right channels of a stereo signal identically. While this creates a great moving filter effect, it remains monophonic in terms of movement because both ears hear the same filter frequency at the same time.

To create true stereo movement—where the filter sweeps up in the left ear while sweeping down in the right ear—you must split your audio source into separate left and right channels, apply an individual Tone.AutoFilter to each channel with offset LFO phases, and then merge the channels back into a single stereo stream.

Step-by-Step Implementation

To implement this stereo movement, follow these steps:

  1. Create the Sound Source: Generate or load the sound you want to filter.
  2. Create Splitter and Merger Nodes: Use Tone.Split to separate the stereo channels and Tone.Merge to recombine them.
  3. Instantiate Two AutoFilters: Create one filter for the left channel and another for the right channel.
  4. Offset the Phases: Set the phase of the left filter’s LFO to 0 and the right filter’s LFO to 180 (or any offset value of your choice).
  5. Connect the Routing Chain: Route the left split output to the left filter, the right split output to the right filter, and connect both filter outputs back to the merger.

Code Example

Here is a complete, ready-to-use JavaScript example using Tone.js:

import * as Tone from 'tone';

// 1. Create a sound source (e.g., a rich synthesizer sound)
const synth = new Tone.PolySynth(Tone.Synth, {
  oscillator: { type: "sawtooth" }
});

// 2. Create the Split and Merge nodes
const splitter = new Tone.Split();
const merger = new Tone.Merge();

// 3. Create the Left AutoFilter (Phase = 0 degrees)
const autoFilterL = new Tone.AutoFilter({
  frequency: "2n",     // Speed of the sweep (e.g., a half note)
  baseFrequency: 200,  // Lower boundary of the filter
  octaves: 4,          // How far above the base frequency the filter sweeps
  phase: 0             // Starts at the beginning of the LFO cycle
}).start();            // Crucial: AutoFilter's internal LFO must be started

// 4. Create the Right AutoFilter (Phase = 180 degrees)
const autoFilterR = new Tone.AutoFilter({
  frequency: "2n",
  baseFrequency: 200,
  octaves: 4,
  phase: 180           // 180 degrees out of phase for maximum stereo width
}).start();

// 5. Connect the audio routing chain
synth.connect(splitter);

// Left Channel Routing (Index 0)
splitter.connect(autoFilterL, 0, 0); // Split output 0 -> Filter input
autoFilterL.connect(merger, 0, 0);   // Filter output -> Merge input 0 (Left)

// Right Channel Routing (Index 1)
splitter.connect(autoFilterR, 1, 0); // Split output 1 -> Filter input
autoFilterR.connect(merger, 0, 1);   // Filter output -> Merge input 1 (Right)

// Connect the merged stereo signal to the master output
merger.toDestination();

// Play a chord to hear the stereo sweep
Tone.loaded().then(() => {
  synth.triggerAttackRelease(["C4", "E4", "G4", "B4"], "4m");
});

Key Parameters to Customize