How to generate an audio waveform png — in-browser, no server
- Step 1Load one audio file — Drop a single file. Decoding relies on the browser's
decodeAudioData, so MP3, WAV, FLAC, M4A/AAC, OGG, and Opus typically load in modern browsers. The buffer is copied before decode (some implementations mutate the input). - Step 2Pick the canvas width — Width (320–3840, step 80) is the column count. Because each column folds
floor(totalSamples / width)samples into one min/max, a wider canvas preserves more transient detail. Default 1600. - Step 3Pick the canvas height — Height (80–1080, step 20) sets the vertical resolution. Amplitude maps to ±height/2 around the centre line, so taller canvases show small peaks more clearly. Default 320.
- Step 4Choose draw mode and colours —
bars/filledfill min→max per column;linestrokes a single peak path. Set Color (wave) and Background (canvas fill). Backgrounds are painted before the wave, so exports are opaque. - Step 5Choose PNG or JPG — PNG via
toBlob('image/png')is lossless and ignores quality. JPG viatoBlob('image/jpeg', q)honours the quality slider (0.1–1, clamped). Pick JPG only when file size matters. - Step 6Export and download — The canvas is converted to a Blob and downloaded as
<name>-waveform.png/.jpg. No audio file is emitted — the result is an image.
Draw behaviour by style
Exactly how each style is rendered from decoded mono PCM. Bars and filled share the same fill path in the current build.
| Style | Per-column computation | Canvas op |
|---|---|---|
bars | min and max of the column's samples | fillRect(x, yMax, 1, max(1, yMin - yMax)) |
filled | min and max (same as bars) | same fillRect min→max fill |
line | largest-magnitude sample in the column | moveTo/lineTo into one stroked path |
Option ranges and defaults
Validated ranges from the tool's number inputs and selects.
| Option | Range / values | Step | Default |
|---|---|---|---|
| width | 320–3840 | 80 | 1600 |
| height | 80–1080 | 20 | 320 |
| style | bars / line / filled | — | bars |
| color | hex | — | #00e5ff |
| background | hex | — | #0a0a14 |
| format | png / jpg | — | png |
| quality (jpg) | 0.1–1 | 0.05 | 0.9 |
Tier limits (audio family)
Per-file limits from lib/tier-limits.ts. One file per render.
| Tier | Max size | Max duration | Files |
|---|---|---|---|
| Free | 50 MB | 30 min | 1 |
| Pro | 200 MB | 120 min | 10 |
| Pro-media | 100 GB | Unlimited | 100 |
| Developer | 100 GB | Unlimited | 100 |
Cookbook
Settings recipes framed around the underlying canvas behaviour. The output is always a raster image named <source>-waveform.png or .jpg.
Retina-sharp doc asset
Render at 2x the intended display size and downscale in CSS for a crisp look on high-DPI screens — there's no built-in devicePixelRatio handling, so do it via dimensions.
Intended display: 600x150 Render at: 1200x300 (2x) Style: line Color: #0ea5e9 Background: #0b1220 Format: png Then display the PNG at 600x150 in your layout.
Small JPG for a web hero
Wide PNGs get heavy. Switch to JPG and tune quality for a lighter asset; the look is near-identical for solid bars.
Width: 3000 Height: 600 Style: bars Format: jpg Quality: 0.85 Result: ~1/4 the bytes of the equivalent PNG, same visual.
Detail-preserving render for transients
Percussive audio has sharp peaks that get averaged away at small widths. Maximise width so fewer samples fold into each column.
Input: drum-loop.wav (4 s, 44.1 kHz) Width: 3840 Height: 400 Style: bars samplesPerPixel = floor(176400 / 3840) ~= 45 samples/col -> individual hits stay visible.
Monochrome ink-on-paper
For print or grayscale docs, use dark ink on white. Colours are arbitrary hex values in the pickers.
Color: #000000 Background: #ffffff Style: line Format: png Output: clean black line wave on white.
Reproducible naming for a manual batch
Output names derive from the source, so processing files one by one yields predictable filenames you can collect.
intro.mp3 -> intro-waveform.png body.mp3 -> body-waveform.png outro.mp3 -> outro-waveform.png (Free = 1 file at a time; Pro batches up to 10.)
Edge cases and what actually happens
decodeAudioData throws on the codec
Decode errorIf the browser can't decode the container/codec, decodeAudioData rejects and no image is drawn. Re-encode to a standard format first — e.g. opus-to-mp3 or mp3-to-wav — then retry.
No 2D canvas context available
Render errorThe render aborts with "Could not acquire 2D canvas context." if getContext('2d') returns null — rare, but possible in locked-down or headless contexts. Use a standard browser tab; the 2D context is required.
Stereo input drawn as mono
By designAll channels are averaged to mono before drawing. There's no stereo/dual-lane rendering. Split channels with channel-splitter if you need separate L/R images.
Very wide canvas with short audio
ExpectedIf width exceeds the sample count, samplesPerPixel clamps to 1 and some columns read past the data (treated as 0). The wave simply spreads out with flat regions — not an error, just sparse data stretched wide.
Expecting transparent PNG (alpha)
Not supportedThe canvas is filled with the background colour first, so the PNG has no transparency. Set the background to your target surface colour, or remove it later in an image editor.
Expecting SVG / vector output
Not supportedOutput is rasterised via canvas.toBlob. There is no SVG export. Render a large PNG if you need to scale the asset up cleanly.
JPG quality out of range
ClampedQuality is clamped to [0,1] before encoding (UI min is 0.1, max 1). A stray value won't crash the encoder — it's bounded defensively. PNG ignores quality entirely.
File exceeds tier size/duration
RejectedFree caps at 50 MB / 30 min, Pro at 200 MB / 120 min. Oversized or over-length files are rejected pre-decode. Trim with audio-trimmer or upgrade for the larger ceilings.
Frequently asked questions
Is the waveform really generated client-side?
Yes. It uses decodeAudioData to get PCM, draws on an HTMLCanvasElement, and exports with canvas.toBlob. No bytes are sent to a server and no audio file is created.
How is the peak-per-pixel computed?
samplesPerPixel = max(1, floor(totalSamples / width)). For each pixel column the tool finds the min and max sample (bars/filled) or the largest-magnitude sample (line) within that window.
Can I get an SVG instead of PNG?
No. The renderer is canvas-based, so output is PNG (lossless) or JPG (lossy). Render a large PNG and downscale if you need apparent scalability.
Does it support transparent backgrounds?
No. The whole canvas is filled with the background colour before drawing, so exports are opaque. Choose a background that matches where the image will sit.
Why is bars the same as filled?
In the current build both draw the same min→max vertical fill per column. They are exposed as separate options but render identically; use line for a visually different, thinner result.
Is there DPI / devicePixelRatio scaling?
No automatic DPI handling. To get retina sharpness, render at 2x your display dimensions (up to 3840×1080) and downscale via CSS or your layout.
What's the largest width I can render?
3840 px (height up to 1080). That's also your best lever for preserving transient detail on longer or percussive audio.
Does generating the image touch my audio file?
No re-encode, no write. The tool reads samples to draw and outputs only the image. The source file is unchanged.
What formats decode reliably?
Browser-native decoding covers MP3, WAV, FLAC, M4A/AAC, OGG, and Opus on modern browsers. If a file won't decode, convert it first with a JAD converter such as flac-to-mp3.
Can I script this through an API?
The waveform render runs in the browser canvas; this page describes the in-browser tool. For batch image work, Pro allows up to 10 files in the audio family — process and collect the predictably named outputs.
How long a file can I render?
Up to 30 minutes on Free and 120 minutes on Pro (with 50 MB / 200 MB size caps respectively). Pro-media and Developer remove the duration limit.
What if I want frequency content, not amplitude?
Use the spectrum analyzer — it runs a radix-2 FFT with a Hann window and renders a viridis spectrogram. The waveform tool only shows amplitude over time.
Privacy first
Every JAD Audio tool runs entirely in your browser via FFmpeg (WebAssembly) and RNNoise. Your audio files never leave your device — verified by zero outbound network requests during processing.