webdevpuneet.com

Canvas Audio Frequency Bars — Free Web Audio API Visualizer

Canvas Audio Frequency Bars · Media · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="cab-wrap">
  <span class="cab-tag">web audio api · analysernode</span>
  <h1>Frequency bars</h1>
  <p id="cabStatus">Click "Use microphone" to visualize real audio, or watch the simulated preview below.</p>

  <div class="cab-stage">
    <canvas class="cab-canvas" id="cabCanvas" width="640" height="220"></canvas>
  </div>

  <div class="cab-actions">
    <button class="cab-btn primary" id="cabMicBtn">Use microphone</button>
    <button class="cab-btn" id="cabSimBtn">Simulated preview</button>
  </div>
  <p class="cab-note">If microphone access is blocked (common inside a sandboxed preview iframe), the bars automatically fall back to a sine-driven simulation — the demo never sits blank.</p>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real AnalyserNode wiring
getUserMedia into createMediaStreamSource into an analyser.
Genuine frequency data
getByteFrequencyData drives bar heights, not fake randomness.
Built-in smoothing
smoothingTimeConstant eases bars between samples.
Shape-matched fallback
Simulation fills the same Uint8Array shape as real data.
Fail-into-simulation
Any getUserMedia rejection routes to the sine fallback.
Named error status
The status line surfaces the actual DOMException name.
Manual mode toggle
A button to force the simulated preview at any time.
Clean stream teardown
Mic tracks stop on mode switch and on page unload.

About this UI Snippet

Canvas Audio Frequency Bars — Real Mic Input With a Graceful Fallback

Screenshot of the Canvas Audio Frequency Bars snippet rendered live

This snippet is a real audio-reactive visualizer, not a faked one: click "Use microphone" and it asks for getUserMedia, routes the live stream through a Web Audio AnalyserNode, and paints genuine frequency-bin data to canvas every frame. What makes it worth studying isn't just the Web Audio wiring — it's the fallback path that keeps the demo looking intentional no matter what the browser or embedding context allows.

The real path: getUserMedia to AnalyserNode

navigator.mediaDevices.getUserMedia({ audio: true }) requests the microphone and resolves with a MediaStream. That stream is wrapped in audioCtx.createMediaStreamSource(micStream) and piped into an AnalyserNode with fftSize: 256, which exposes 128 frequency bins. Every animation frame, analyser.getByteFrequencyData(freqData) fills a Uint8Array with the current 0-255 magnitude of each bin — that array, sliced down to BAR_COUNT, is what actually drives every bar's height. smoothingTimeConstant: 0.82 is the analyser's own built-in smoothing, so bars ease between frames instead of jittering on every sample.

Why a fallback is not optional here

Microphone access is one of the few browser APIs that can fail in ways entirely outside your code's control: the user can deny the permission prompt, the browser can lack support, or — very commonly for a snippet rendered inside a sandboxed preview <iframe> — the surrounding page's Permissions-Policy can simply never grant microphone to that frame, so getUserMedia rejects immediately with no prompt shown at all. A visualizer that goes blank in that case looks broken, not permission-restricted.

A simulation indistinguishable from motion

simulatedFrame() blends two sine waves per bar — a slow base term and a faster wobble term — multiplied by a slow-moving envelope that swells and recedes like a breathing loudness curve, then packs the result into the exact same Uint8Array shape getByteFrequencyData would produce. Because drawBars() only ever consumes that shape, the rendering code has no idea whether its data came from a real microphone or from math — which is exactly the point: the fallback isn't a "no audio" message, it's a full visual substitute.

Fail-into-simulation, always

startMic() wraps the entire request in a try/catch; any rejection — NotAllowedError from a denied prompt, NotFoundError with no microphone present, or a security error from a Permissions-Policy block — routes to startSimulated() with a message naming the error. The loop itself never branches on whether the *attempt* succeeded, only on the current mode, so there's exactly one code path that decides what's on screen at any moment. Pair this with an audio waveform visualizer for a player UI, or a particle network background reacting to the same analyser data.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA simulated bar pattern animates immediately on load.
  2. 2
    Click "Use microphone"The browser prompts for mic access; allow it to see real audio.
  3. 3
    Talk or play musicBars react to genuine frequency-bin data from AnalyserNode.
  4. 4
    Deny or block accessThe status message explains why, and bars fall back to simulation.
  5. 5
    Click "Simulated preview"Switch back to the sine-driven pattern at any time.
  6. 6
    Tune the visualizerChange fftSize, smoothingTimeConstant, or BAR_COUNT.

Real-world uses

Common Use Cases

Music and podcast apps
Add live frequency bars beside an audio waveform visualiser, driven by real getByteFrequencyData rather than fake random heights.
Voice recording tools
Show live input level while recording, with smoothingTimeConstant easing the bars between samples so they do not flicker.
Microphone testers
Let users confirm that their microphone works before a call, with a clear fallback if permission is denied.
Karaoke and pitch practice
Provide a frequency readout as visual feedback while someone sings, using 32 bars redrawn on every animation frame.
Always-on overlays and demos
Embed safely in sandboxed iframes, since a simulated sine-driven fallback fills the same Uint8Array shape as real data.
Related: Canvas Rainbow Mouse Trail
See the Canvas Rainbow Mouse Trail for a related animations pattern worth pairing with this one.
Related: Layout Switcher Container Morph
See the Layout Switcher Container Morph for a related animations pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Microphone access can fail for reasons entirely outside the page's control: the user denies the browser prompt, no microphone device exists, or — especially common when this snippet is rendered inside a sandboxed preview iframe — the surrounding page's Permissions-Policy never grants the microphone feature to that frame, so getUserMedia rejects immediately without even showing a prompt. Rather than leave the canvas blank in any of those cases, the code catches the rejection and switches to a sine-driven simulated pattern.

It will always show something — either real mic data if the iframe is allowed the microphone permission and the user grants it, or the simulated fallback if not. Many sandboxed preview environments (including embedded code sandboxes) block microphone access by default via their iframe's allow attribute or Permissions-Policy header, which is exactly the scenario startMic()'s catch block is designed for: it fails quietly into a visually complete simulation instead of an error state.

simulatedFrame() blends a slow sine wave and a faster one per bar, modulated by a slowly breathing envelope term, and packs the result into a Uint8Array with the exact same shape (length and 0-255 value range) that analyser.getByteFrequencyData would produce. Because the drawing function only ever reads that shape, it cannot tell whether the values came from a microphone or from math, so the two modes render with identical code.

fftSize (set to 256) determines frequency resolution — the AnalyserNode exposes fftSize / 2 frequency bins, so 256 gives 128 bins, of which the first 64 are drawn as bars here. smoothingTimeConstant (0.82) is the analyser's own exponential smoothing between consecutive samples; higher values make bars ease more gently between frames, lower values make them jump more sharply with each sample.

Move the AudioContext, AnalyserNode, and MediaStream setup into a mount effect, keep them in refs so they persist across renders, and start the requestAnimationFrame loop there. In cleanup, stop every track on the MediaStream and cancelAnimationFrame the loop so a component unmount doesn't leave the microphone active or the audio graph running in the background.