Skip to main content
Effects hooks provide runtime control over audio effects chains. Master effects apply to the entire mix, while track effects apply to individual tracks.

useDynamicEffects

Manage a dynamic master effects chain with real-time parameter updates.

Import

import { useDynamicEffects } from '@waveform-playlist/browser';
import type { ActiveEffect, UseDynamicEffectsReturn } from '@waveform-playlist/browser';

Usage

function EffectsPanel() {
  const {
    activeEffects,
    availableEffects,
    addEffect,
    removeEffect,
    updateParameter,
    toggleBypass,
    masterEffects,
    analyserRef,
  } = useDynamicEffects(256); // FFT size for analyser

  return (
    <div>
      <select onChange={(e) => addEffect(e.target.value)}>
        <option value="">Add Effect...</option>
        {availableEffects.map((effect) => (
          <option key={effect.id} value={effect.id}>
            {effect.name}
          </option>
        ))}
      </select>

      {activeEffects.map((effect) => (
        <div key={effect.instanceId}>
          <h3>{effect.definition.name}</h3>
          <button onClick={() => toggleBypass(effect.instanceId)}>
            {effect.bypassed ? 'Enable' : 'Bypass'}
          </button>
          <button onClick={() => removeEffect(effect.instanceId)}>Remove</button>

          {effect.definition.parameters.map((param) => (
            <div key={param.name}>
              <label>{param.label}</label>
              <input
                type="range"
                min={param.min}
                max={param.max}
                step={param.step}
                value={effect.params[param.name] as number}
                onChange={(e) =>
                  updateParameter(effect.instanceId, param.name, parseFloat(e.target.value))
                }
              />
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

// Pass masterEffects to WaveformPlaylistProvider
<WaveformPlaylistProvider tracks={tracks} effectsFunction={masterEffects}>
  <EffectsPanel />
</WaveformPlaylistProvider>

Parameters

fftSize
number
default:256
FFT size for the built-in analyser node (used for visualization).

Return Value

activeEffects
ActiveEffect[]
Array of currently active effects in the chain.
interface ActiveEffect {
  instanceId: string;
  effectId: string;
  definition: EffectDefinition;
  params: Record<string, number | string | boolean>;
  bypassed: boolean;
}
availableEffects
EffectDefinition[]
Array of all available effect definitions. Includes 20 Tone.js effects organized by category (Reverb, Delay, Modulation, Filter, Distortion, Dynamics, Spatial).
addEffect
(effectId: string) => void
Add an effect to the end of the chain. Creates a new instance with default parameters.
removeEffect
(instanceId: string) => void
Remove an effect from the chain by instance ID. Disposes the effect and rebuilds the audio graph.
updateParameter
(instanceId: string, paramName: string, value: number | string | boolean) => void
Update an effect parameter in real-time. No audio graph rebuild required.
toggleBypass
(instanceId: string) => void
Bypass or un-bypass an effect. When bypassed, sets wet to 0. When un-bypassed, restores the original wet value.
reorderEffects
(fromIndex: number, toIndex: number) => void
Reorder effects in the chain. Rebuilds the audio graph with the new order.
clearAllEffects
() => void
Remove all effects from the chain. Disposes all effect instances.
masterEffects
EffectsFunction
Effects function to pass to WaveformPlaylistProvider. This is a stable function that reads from refs at call time.
(masterGainNode, destination, isOffline) => void | (() => void)
createOfflineEffectsFunction
() => EffectsFunction | undefined
Creates a fresh effects function for offline rendering (WAV export). Creates new effect instances in the offline AudioContext to avoid context mismatch.Only includes non-bypassed effects. Returns undefined if no effects are active.
analyserRef
React.RefObject<Analyser | null>
Ref to the Tone.js Analyser node for visualization (FFT, waveform, etc.).

Example: Effect Categories

Available effects are organized into categories:
// Reverb (3 effects)
- reverb          // Standard reverb with decay and pre-delay
- freeverb        // Freeverb algorithm
- jcreverb        // JCReverb algorithm

// Delay (2 effects)
- feedbackdelay   // Feedback delay with adjustable time
- pingpongdelay   // Stereo ping-pong delay

// Modulation (5 effects)
- chorus          // Chorus effect
- phaser          // Phaser with frequency and octaves
- tremolo         // Amplitude modulation
- vibrato         // Pitch modulation
- autoPanner      // Stereo auto-panner

// Filter (3 effects)
- autoFilter      // Swept filter with LFO
- eq3             // 3-band EQ (low, mid, high)
- filter          // Configurable filter (lowpass, highpass, etc.)

// Distortion (3 effects)
- distortion      // Distortion with drive and wet/dry
- chebyshev       // Chebyshev waveshaping
- bitcrusher      // Bit depth and sample rate reduction

// Dynamics (3 effects)
- compressor      // Compressor with threshold, ratio, attack, release
- limiter         // Limiter with threshold
- gate            // Noise gate

// Spatial (1 effect)
- stereoPanner    // Stereo panner (-1 to 1)

useTrackDynamicEffects

Manage per-track effects chains with real-time parameter updates.

Import

import { useTrackDynamicEffects } from '@waveform-playlist/browser';
import type { UseTrackDynamicEffectsReturn, TrackActiveEffect } from '@waveform-playlist/browser';

Usage

function TrackEffectsPanel() {
  const {
    trackEffectsState,
    availableEffects,
    addEffectToTrack,
    removeEffectFromTrack,
    updateTrackEffectParameter,
    toggleBypass,
    clearTrackEffects,
    getTrackEffectsFunction,
  } = useTrackDynamicEffects();

  const selectedTrackId = 'track-1';
  const trackEffects = trackEffectsState.get(selectedTrackId) || [];

  return (
    <div>
      <h2>Track Effects</h2>
      <select onChange={(e) => addEffectToTrack(selectedTrackId, e.target.value)}>
        <option value="">Add Effect...</option>
        {availableEffects.map((effect) => (
          <option key={effect.id} value={effect.id}>
            {effect.name}
          </option>
        ))}
      </select>

      {trackEffects.map((effect) => (
        <div key={effect.instanceId}>
          <h3>{effect.definition.name}</h3>
          <button onClick={() => toggleBypass(selectedTrackId, effect.instanceId)}>
            {effect.bypassed ? 'Enable' : 'Bypass'}
          </button>
          <button onClick={() => removeEffectFromTrack(selectedTrackId, effect.instanceId)}>
            Remove
          </button>

          {effect.definition.parameters.map((param) => (
            <div key={param.name}>
              <label>{param.label}</label>
              <input
                type="range"
                min={param.min}
                max={param.max}
                step={param.step}
                value={effect.params[param.name] as number}
                onChange={(e) =>
                  updateTrackEffectParameter(
                    selectedTrackId,
                    effect.instanceId,
                    param.name,
                    parseFloat(e.target.value)
                  )
                }
              />
            </div>
          ))}
        </div>
      ))}

      <button onClick={() => clearTrackEffects(selectedTrackId)}>Clear All</button>
    </div>
  );
}

// Apply track effects to useAudioTracks
const { getTrackEffectsFunction } = useTrackDynamicEffects();

const { tracks } = useAudioTracks(
  trackConfigs.map((config) => ({
    ...config,
    effects: getTrackEffectsFunction(config.id),
  }))
);

Return Value

trackEffectsState
Map<string, TrackActiveEffect[]>
Map of track IDs to their active effects arrays.
interface TrackActiveEffect {
  instanceId: string;
  effectId: string;
  definition: EffectDefinition;
  params: Record<string, number | string | boolean>;
  bypassed: boolean;
}
addEffectToTrack
(trackId: string, effectId: string) => void
Add an effect to a track’s effects chain.
removeEffectFromTrack
(trackId: string, instanceId: string) => void
Remove an effect from a track’s chain.
updateTrackEffectParameter
(trackId: string, instanceId: string, paramName: string, value: number | string | boolean) => void
Update a track effect parameter in real-time.
toggleBypass
(trackId: string, instanceId: string) => void
Toggle bypass for a track effect.
clearTrackEffects
(trackId: string) => void
Remove all effects from a track.
getTrackEffectsFunction
(trackId: string) => TrackEffectsFunction | undefined
Get the effects function for a specific track. Pass this to AudioTrackConfig.effects.This is a stable function that reads from refs at call time to avoid stale closures.
createOfflineTrackEffectsFunction
(trackId: string) => TrackEffectsFunction | undefined
Create a fresh effects function for offline rendering (WAV export). Returns undefined if the track has no active effects.
availableEffects
EffectDefinition[]
Array of all available effect definitions (same as useDynamicEffects).

Effect Chain Architecture

Real-Time Parameter Updates

Effects support real-time parameter updates without rebuilding the audio graph:
// Update parameter - no graph rebuild
updateParameter(instanceId, 'decay', 3.5);

// Only these operations rebuild the graph:
// - Adding effects
// - Removing effects  
// - Reordering effects

Bypass Pattern

When bypassing an effect:
  1. Store the original wet value
  2. Set wet to 0 (fully dry signal)
  3. On un-bypass, restore the original wet value (not always 1)

Offline Rendering

For WAV export, use the offline creator functions:
const { createOfflineEffectsFunction } = useDynamicEffects();
const { createOfflineTrackEffectsFunction } = useTrackDynamicEffects();

const { exportWav } = useExportWav();

await exportWav(tracks, trackStates, {
  effectsFunction: createOfflineEffectsFunction(),
  createOfflineTrackEffects: (trackId) => createOfflineTrackEffectsFunction(trackId),
});
These functions create fresh effect instances in the offline AudioContext to avoid context mismatch errors.

Refs for Stable Functions

Both hooks use refs to avoid stale closures:
const activeEffectsRef = useRef(activeEffects);
activeEffectsRef.current = activeEffects; // Update on every render

const masterEffects = useCallback(
  (masterGainNode, destination, isOffline) => {
    const effects = activeEffectsRef.current; // Read fresh state
    // Build audio graph...
  },
  [fftSize] // Only fftSize - stable function
);

Build docs developers (and LLMs) love