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 }))