Envelope plugin
The Envelope plugin draws a draggable volume automation curve over the waveform. It lets you set per-point volume levels that are interpolated during playback — perfect for fades, ducks, and any dynamic gain shaping. See the live demo to try it interactively.
Setup
Install wavesurfer.js (if you haven’t already — see Getting started), then import and register the plugin:
import WaveSurfer from 'wavesurfer.js'
import EnvelopePlugin from 'wavesurfer.js/dist/plugins/envelope.js'
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
})
const envelope = ws.registerPlugin(EnvelopePlugin.create({
volume: 0.8,
points: [
{ time: 0, volume: 0 }, // fade in from silence
{ time: 2, volume: 1 },
{ time: 8, volume: 1 },
{ time: 10, volume: 0 }, // fade out to silence
],
}))
CDN / UMD:
<script src="https://unpkg.com/wavesurfer.js@7"></script>
<script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/envelope.js"></script>
<script>
const ws = WaveSurfer.create({ container: '#waveform', url: '/audio.mp3' })
const envelope = ws.registerPlugin(WaveSurfer.EnvelopePlugin.create({ volume: 1 }))
</script>
Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
points |
EnvelopePoint[] |
[] |
Initial automation points. Each point is { time, volume } or { id, time, volume }. |
volume |
number |
current WaveSurfer volume | Starting master volume (0–1+). |
lineColor |
string |
'rgba(0, 0, 255, 0.5)' |
Stroke color of the envelope line. |
lineWidth |
string |
4 |
Stroke width of the envelope line. |
dragLine |
boolean |
false |
Enable dragging the entire line up/down to shift all points at once. |
dragPointSize |
number |
10 |
Diameter in pixels of each drag handle circle. |
dragPointFill |
string |
'rgba(255, 255, 255, 0.8)' |
Fill color of drag handle circles. |
dragPointStroke |
string |
'rgba(255, 255, 255, 0.8)' |
Stroke color of drag handle circles. |
Point object shape
type EnvelopePoint = {
id?: string // auto-generated if omitted; do not rely on position in array for identity
time: number // seconds from the start of the audio
volume: number // 0 to 1
}
What it does
The plugin renders an SVG polyline over the waveform. Each EnvelopePoint becomes a draggable circle on that line. During playback the plugin listens to timeupdate and linearly interpolates between the two nearest points, then calls wavesurfer.setVolume() with the result — so volume changes happen automatically without any extra code on your part.
Interacting in the browser:
- Drag a point left/right to change its time, up/down to change its volume.
- Drag a point off-canvas (past any edge) to delete it.
- Double-click (or long-press on touch) anywhere on the envelope to add a new point.
- When
dragLine: true, dragging the line itself shifts every point’s volume uniformly.
Points are always kept sorted by time; you cannot drag a point past its neighbours.
Managing points
Adding and removing programmatically
// Add a point — id is generated automatically if omitted
envelope.addPoint({ time: 5, volume: 0.5 })
// Keep a reference to remove it later
const pt = { time: 3, volume: 0.25 }
envelope.addPoint(pt)
envelope.removePoint(pt) // pass the same object reference
// Replace all points at once
envelope.setPoints([
{ time: 0, volume: 0 },
{ time: 4, volume: 1 },
])
// Read the current list (do not mutate the array directly)
const all = envelope.getPoints()
console.log(all)
// [{ id: 'abc', time: 0, volume: 0 }, { id: 'def', time: 4, volume: 1 }]
Use removePoint with the same object reference you passed to addPoint (or retrieved from getPoints). The plugin uses reference equality — do not reconstruct a new object with the same values and pass it to removePoint.
Volume helpers
// Imperatively set volume (also updates the internal volume state)
envelope.setVolume(0.7)
// Read the current interpolated volume at the playhead position
const vol = envelope.getCurrentVolume()
console.log(vol) // e.g. 0.42
Reacting to changes
points-change fires (debounced ~200 ms) whenever any point is added, removed, or moved — including by the user dragging in the browser. The payload is the full updated points array:
envelope.on('points-change', (points) => {
// points: EnvelopePoint[]
console.log('Automation updated:', points)
// Persist to your backend, for example:
saveToServer(points.map(({ time, volume }) => ({ time, volume })))
})
volume-change fires on every timeupdate tick where the interpolated volume changes. The payload is the new volume value as a number (0–1):
envelope.on('volume-change', (volume) => {
// volume: number
document.querySelector('#vol-display').textContent = volume.toFixed(2)
})
Common pitfalls
Hidden waveform causes Invalid value for <ellipse> attribute ry="Infinity".
The Envelope plugin calculates drag-handle sizes using the container’s clientWidth and clientHeight. If the waveform is hidden with display: none while the envelope is active, those dimensions are 0, which causes a division-by-zero that flows into the SVG ry attribute and triggers this browser console error.
Fix: Keep the waveform visible while the envelope is registered, or destroy the plugin before hiding the element:
// Option A — hide with visibility/opacity instead of display
waveformEl.style.visibility = 'hidden' // preserves layout dimensions
// Option B — destroy the plugin before hiding, recreate when showing
envelope.destroy()
waveformEl.style.display = 'none'
// Later, when showing again:
waveformEl.style.display = ''
const envelope = ws.registerPlugin(EnvelopePlugin.create({ points: savedPoints }))