MIT · Zero dependencies · TypeScript · ESM / CJS / UMD

Audio in, frames out

A small cross-browser library that turns any audio source — mic, an <audio> element, a picked input device, a browser tab, a dropped file — into one clean, smoothed AudioFrame your visualizer reads. The same callback, every browser. No build, no dependencies.

Pairs with any Canvas / WebGL / WebGPU viz — one onFrame callback.

Why it exists

Every music visualizer needs the same boring thing first: a clean, AGC'd, smoothed frequency + waveform + beat frame — cross-browser, without re-solving AudioContext resume quirks, Safari's webkitAudioContext, or each capture API's edge cases.

sj-audio is that layer. Five source adapters share one analysis pipeline and emit one stable AudioFrame, so your viz code never cares where the audio came from — and a unified engine falls back through the chain until one source works.

┌─ source adapter ─────────────────────────────┐ │ mediaElement · microphone · device │ │ displayMedia · file │ └───────────────────────┬──────────────────────┘ │ Web Audio ┌────────────────────────▼──────────────────────┐ │ AudioContext → AnalyserNode → FramePipeline │ │ AGC · 32 mel bands · beat · rolling BPM │ └───────────────────────┬──────────────────────┘ │ ~60 fps ┌────────────────────────▼──────────────────────┐ │ onFrame(frame) → your Canvas / WebGL / WebGPU │ └────────────────────────────────────────────────┘

Drop it in

No bundler required. Pull the UMD build off the CDN for a plain <script>, or import the ESM build directly.

<script src="https://cdn.jsdelivr.net/gh/jayvee6/sj-audio@latest/dist/sj-audio.umd.js"></script>
<script>
  const engine = SJAudio.createAudioEngine({
    mediaElement: document.querySelector('audio'),
  });
  await engine.start();
  engine.onFrame((f) => drawBars(f.magnitudesSmooth));
</script>
import { createAudioEngine } from
  'https://cdn.jsdelivr.net/gh/jayvee6/sj-audio@latest/dist/sj-audio.esm.js';
@latest tracks the newest release tag. Pin a specific version (e.g. @v0.2.0) in production so a future release can't shift behaviour under you.

One callback in the browser

Pick a source directly, or hand the engine a fallback chain and let it choose the first that works. Listeners survive a runtime switchTo().

import { createAudioEngine } from 'sj-audio';

const engine = createAudioEngine({
  fallbackChain: ['mediaElement', 'microphone', 'file'],
  mediaElement: document.querySelector('audio'),
});

await engine.start();              // tries in order; first success wins
console.log(engine.activeKind);    // 'mediaElement' | 'microphone' | …

engine.onFrame((f) => {
  drawBars(f.magnitudesSmooth);    // 32 EMA-smoothed mel bands
  if (f.isBeatNow) flash();        // onset this frame
});

Five sources, one frame

Media element

Analyze any <audio> / <video>. Universal; keeps play / pause / seek.

Microphone

getUserMedia — works everywhere. iOS Safari is quirky; the library handles the resume dance for you.

Device picker + autodetect

Served-site, zero install: enumerate the visitor's inputs, let them pick — or autodetect the one that's actually playing. Safari + Firefox too.

Tab / display audio

getDisplayMedia — Chromium desktop only. The library feature-detects and degrades gracefully elsewhere.

File

Drag-drop or picked File / Blob, analyze-only — one pass through the analyzer.

Unified engine

createAudioEngine wires a fallback chain across all of the above, with switchTo() at runtime and persistent listeners.

What every frame gives you

Same shape from every adapter and the engine. Typed-array buffers are stable references — mutated in place, so a visualizer can cache them once.

Cross-browser, honestly

No silent failures — the library tells you what a browser can't do so your UI can adapt.

More

Source

Zero-dependency TypeScript. Rollup → ESM + CJS + UMD. MIT.

github.com/jayvee6/sj-audio →

Examples

Seven self-contained HTML files — one per source, plus a UMD drop-in and the device picker.

Open the examples →

API reference

Full TypeScript signatures, the AudioFrame spec, per-source recipes and caveats.

Read the README →