Audio Routing & Custom Effects
Learn how to integrate custom Web Audio effects into your signal chain using the adapter pattern.
The chain every sound flows through, from source to speakers.
Effects wrap and route automatically.
Tip: Try different distortion amounts and mix levels. The bypass toggle lets you A/B compare the processed vs unprocessed signal.
Replicating This Demo
The demo above creates an oscillator, wraps a WaveShaper as an effect, and wires sliders to update parameters in real-time:
import { createOscillator, getAudioContext, wrapEffect } from 'ez-web-audio'
const osc = await createOscillator({ frequency: 200, type: 'sine' })
osc.changeGainTo(0.3)
osc.play()
// Create and wrap a WaveShaper distortion effect
const ctx = await getAudioContext()
const distNode = ctx.createWaveShaper()
distNode.curve = makeDistortionCurve(400)
distNode.oversample = '4x'
const effect = wrapEffect(distNode)
effect.mix = 0.7
osc.addEffect(effect)
// Wire sliders to effect parameters
amountSlider.addEventListener('input', (e) => {
// Update the underlying WaveShaper curve
distNode.curve = makeDistortionCurve(Number(e.target.value))
})
mixSlider.addEventListener('input', (e) => {
effect.mix = Number(e.target.value) // 0 = dry, 1 = wet
})
bypassCheckbox.addEventListener('change', (e) => {
effect.bypass = e.target.checked // Routes audio around the effect
})
// Dynamically remove the effect
osc.removeEffect(effect) // Signal chain reconnects automaticallyThe key pattern: wrapEffect() gives any AudioNode a consistent interface with mix and bypass properties. Update mix and bypass directly — changes apply immediately. For the underlying node's own parameters (like WaveShaper's curve), access it via effect.effect.
How Effects Work
Every sound in ez-web-audio has a signal chain:
Source → [Effects] → Gain → Pan → DestinationThe wrapEffect() function is an adapter that makes any Web Audio AudioNode work with ez-web-audio's effect system. It handles routing, gain mixing, and bypass switching automatically.
The Adapter Pattern
Web Audio nodes have input and output points. The wrapEffect() function wraps any node with a consistent interface:
interface Effect {
input: AudioNode // Connect audio to this
output: AudioNode // Connect this to next node
bypass: boolean // true = route around effect
mix: number // 0-1, wet/dry balance
}This means you can use any Web Audio effect — built-in or from a library — with the same API.
Basic Effect Integration
Here's how to add a WaveShaper distortion effect:
import { createOscillator, getAudioContext, wrapEffect } from 'ez-web-audio'
// Create audio source
const oscillator = await createOscillator({ frequency: 200, type: 'sawtooth' })
// Get the AudioContext
const ctx = await getAudioContext()
// Create a WaveShaper node for distortion
const distortionNode = ctx.createWaveShaper()
distortionNode.curve = makeDistortionCurve(400)
distortionNode.oversample = '4x'
// Wrap it with ez-web-audio's effect interface
const effect = wrapEffect(distortionNode)
effect.mix = 0.7 // 70% wet, 30% dry
// Add to the signal chain
oscillator.addEffect(effect)
// Start playback
oscillator.play()Creating a Distortion Curve
The WaveShaper distortion curve is a mathematical function that shapes the waveform:
function makeDistortionCurve(amount: number): Float32Array {
const samples = 44100
const curve = new Float32Array(samples)
const deg = Math.PI / 180
for (let i = 0; i < samples; i++) {
const x = (i * 2) / samples - 1
curve[i] = ((3 + amount) * x * 20 * deg) / (Math.PI + amount * Math.abs(x))
}
return curve
}Higher amount values create more aggressive distortion by applying a steeper transfer function.
Mix and Bypass Controls
Every wrapped effect has built-in wet/dry mixing and bypass:
// Adjust wet/dry balance (0 = dry only, 1 = wet only)
effect.mix = 0.5 // 50/50 blend
// Bypass the effect entirely (routes audio around it)
effect.bypass = true
// Re-enable
effect.bypass = falseThis makes it easy to implement A/B comparison and effect intensity controls.
Removing Effects
Effects can be dynamically added and removed:
// Add effect
oscillator.addEffect(effect)
// Remove effect
oscillator.removeEffect(effect)The signal chain automatically reconnects when effects are removed.
Beyond Distortion
The adapter pattern works with any Web Audio node:
Reverb (ConvolverNode)
const convolver = ctx.createConvolver()
convolver.buffer = await loadImpulseResponse('/audio/hall-reverb.wav')
const reverb = wrapEffect(convolver)
reverb.mix = 0.3
sound.addEffect(reverb)Delay (DelayNode + Feedback)
const delay = ctx.createDelay()
delay.delayTime.value = 0.5
const feedback = ctx.createGain()
feedback.gain.value = 0.4
// Connect delay output to feedback, feedback to delay input
delay.connect(feedback)
feedback.connect(delay)
const delayEffect = wrapEffect(delay)
sound.addEffect(delayEffect)Third-Party Effect Libraries
You can also integrate external effect libraries like Tuna.js:
import Tuna from 'tunajs'
const tuna = new Tuna(ctx)
const chorus = new tuna.Chorus({
rate: 1.5,
feedback: 0.2,
delay: 0.0045
})
const chorusEffect = wrapEffect(chorus)
sound.addEffect(chorusEffect)API Used
wrapEffect(node)— Wrap any AudioNode with ez-web-audio's effect interfacesound.addEffect(effect)— Add effect to signal chainsound.removeEffect(effect)— Remove effect from signal chaineffect.bypass— Boolean to bypass effecteffect.mix— Number (0-1) for wet/dry balancegetAudioContext()— Get the global AudioContext
Next Steps
- Sampled Drum Kit — Multi-zone velocity-sensitive pads
- Synth Keyboard — Oscillator playground with filter controls
- Why Timing Drifts — Master Web Audio's scheduling system