npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fft-visualizer/vue

v1.0.0

Published

A high-performance, WebGL-based real-time audio spectrum analyzer and FFT visualizer component for Vue 3 — microphone, tab/system audio, WebSocket, or your own Web Audio data

Readme

@fft-visualizer/vue

A high-performance, WebGL-based real-time audio spectrum analyzer and FFT visualizer component for Vue 3 — visualize the microphone, tab/system audio, a WebSocket stream, or your own Web Audio data.

The entire visual — bars, LED segments, radial layout, gradient, glow, reflection — is drawn by a single fragment shader in one GPU draw call, so it stays smooth even at 120 fps with 80 bands in stereo.

🔗 Live demo: https://demo.fftvisualizer.com/vue/ · Docs: https://fftvisualizer.com/vue/installation

Highlights

  • WebGL fragment-shader rendering — the whole spectrum is one draw call; no per-bar canvas ops
  • Three data sources — capture audio locally (mic or tab/system), stream pre-computed FFT over WebSocket, or feed your own data via props
  • In-browser FFT — optional Rust/WASM FFT processor (lazy-loaded only when you capture audio locally)
  • Rich visual modes — LED segments (two styles), radial/circular, stereo, mirrored reflection, glow, rotation, per-level or per-axis coloring
  • Flexible gradients — 10 built-in presets or custom stops in any CSS color format
  • One peer dependency — just Vue 3; rendering uses native WebGL
  • SSR-safe — all browser access is deferred to onMounted; works with Nuxt via <ClientOnly>

Installation

pnpm add @fft-visualizer/vue
# or: npm install @fft-visualizer/vue / yarn add @fft-visualizer/vue

Quick start

The fastest way to see something is local mode — capture the microphone and visualize it entirely in the browser, no backend required:

<script setup>
import { FFTVisualizer } from '@fft-visualizer/vue'
</script>

<template>
  <div class="viz">
    <FFTVisualizer mode="local" :bands="40" gradient="aurora" />
  </div>
</template>

<style>
/* The component fills its container — give it a height */
.viz { width: 100%; height: 240px; }
</style>

The browser will prompt for microphone permission on mount. To capture tab or system audio instead of the mic, set audio-source="display".

Data modes

The component has one job — render a spectrum — and three ways to get the data, selected with the mode prop:

| Mode | How data arrives | Use when | |------|------------------|----------| | local | Captures mic or display audio and computes the FFT in-browser (Rust/WASM) | You want a zero-backend, client-side visualizer | | websocket (default) | Connects to websocketUrl and reads pre-computed FFT frames | A server/device already produces FFT data (e.g. a Raspberry Pi) | | external | You pass FFT magnitudes via the data / dataLeft / dataRight props | You have your own audio pipeline (Web Audio, another analyser, etc.) |

Local mode

<FFTVisualizer mode="local" audio-source="mic" :bands="80" />

WebSocket mode

<FFTVisualizer mode="websocket" websocket-url="ws://localhost:3001/fft" :bands="40" />

The server streams a small config message then binary FFT frames — see WebSocket protocol. Reference servers for Python, Node.js and Rust are in backend-examples/.

External mode

Pass a Uint8Array of magnitudes (0–255); update it and the visual follows. For stereo, pass dataLeft and dataRight instead of data.

<script setup>
import { ref } from 'vue'
import { FFTVisualizer } from '@fft-visualizer/vue'

const data = ref(new Uint8Array(80))
// ...fill `data.value` from your own analyser each frame (assign a new array,
// or call the exposed feedData() method — see notes below)
</script>

<template>
  <FFTVisualizer mode="external" :data="data" :bands="40" :stereo="false" />
</template>

Note: the data prop is watched by reference. If you mutate the same Uint8Array in place each frame, Vue won't detect the change — either assign a fresh array, or use the component's imperative feed (see Exposed).

Props

Data source

| Prop | Type | Default | Description | |------|------|---------|-------------| | mode | 'websocket' \| 'local' \| 'external' | 'websocket' | Where FFT data comes from | | websocketUrl | string | — | WebSocket URL (used when mode="websocket") | | data | Uint8Array | — | External FFT magnitudes, mono (used when mode="external") | | dataLeft / dataRight | Uint8Array | — | External FFT magnitudes per channel (stereo external mode) | | audioSource | 'mic' \| 'display' | 'mic' | Local capture source (used when mode="local") | | audioDeviceId | string | — | Specific input device for local mic capture | | autoReconnect | boolean | false | Reconnect the WebSocket with exponential backoff (1s→30s) after an unexpected drop |

Data processing

| Prop | Type | Default | Description | |------|------|---------|-------------| | bands | 10 \| 20 \| 40 \| 80 | 80 | Number of frequency bands displayed (server bins are aggregated down to this) | | noiseFloor | number | 0 | Cut magnitudes below this threshold (0–255) | | smoothing | number | 0 | Temporal smoothing (0 = none, 0.9 = heavy) | | showPeaks | boolean | true | Show falling peak indicators | | peakDecay | number | 0.997 | Peak fall speed (0.99 = slow, 0.9 = fast) | | stereo | boolean | false | Stereo mode: left channel top, right channel bottom (mono data is mirrored to both) |

Appearance

| Prop | Type | Default | Description | |------|------|---------|-------------| | ledBars | boolean | false | LED segment effect | | ledShape | 'segment' \| 'meter' | 'segment' | segment = fixed-pixel gap lines (identical at every resolution); meter = short, wide segments sized from bar width, like a classic LED meter | | lumiBars | boolean | false | Full-height bars whose brightness follows the level | | radial | boolean | false | Circular spectrum: angle = frequency, radius = level | | radialInnerRadius | number | 0.35 | Radial: inner hole radius as a fraction of outer radius (0–0.9) | | barSpace | number | 0.25 | Gap between bars as a fraction of bar width (0–0.9) | | reflexRatio | number | 0 | Mirrored reflection (0 = off; max 0.7). Linear mono: fraction of the canvas height given to the reflection. Radial: how far the mirror reaches inward, as a fraction of the bars’ radial length | | reflexAlpha | number | 0.25 | Reflection brightness (0–1) | | glow | number | 0 | Glow above the bar tops (0 = off, 1 = max) | | rotation | 0 \| 90 \| 180 \| 270 | 0 | Rotate the whole visual clockwise, in degrees | | gradient | GradientName \| GradientStop[] | 'classic' | Bar colors: a preset name or custom stops | | gradientDirection | 'vertical' \| 'horizontal' | 'vertical' | Gradient axis | | colorMode | 'gradient' \| 'bar-level' | 'gradient' | bar-level colors each whole bar by its current level instead of by the gradient axis | | background | string | '#0a0a0a' | Background behind and between the bars. Any CSS color, including 'transparent' or rgba(…) for a see-through canvas that blends into your page (opaque vs. transparent is fixed at mount) | | showStats | boolean | true | Show the small connection/fps overlay (also overridable via the stats slot) | | debug | boolean | false | Log connection/config diagnostics to the console (quiet by default) |

Gradients

Bar colors are rasterized into a 256×1 lookup texture, so any CSS color and native gradient interpolation work. Pass a preset name or an array of custom stops:

<!-- Preset -->
<FFTVisualizer mode="local" gradient="sunset" />

<!-- Custom stops (any CSS color format) -->
<FFTVisualizer
  mode="local"
  :gradient="[
    { stop: 0, color: '#001233' },
    { stop: 0.5, color: 'rgb(15, 155, 142)' },
    { stop: 1, color: 'hsl(280, 95%, 75%)' }
  ]"
/>

Built-in presets (exported as gradientPresets / gradientNames): classic, rainbow, blue, prism, orangered, steelblue, sunset, aurora, dusk, mono.

Gradient helpers are exported for building settings UIs or your own rendering: resolveGradientStops, buildGradientLUT, GRADIENT_LUT_SIZE, and the GradientStop / GradientName / GradientInput types.

Events

| Event | Payload | Description | |-------|---------|-------------| | connected | — | Data source became active (WS connected / capture started / external started) | | disconnected | — | Data source stopped | | error | string | Error message (WS error, capture failure, WebGL init failure) | | frame | (data, left, right) | Emitted once per processed audio frame with the display bar magnitudes (see below) |

The frame event

Fires once per processed audio frame with the same bar values the shader draws — useful for driving external hardware (LED strips, flip-dot displays, …) without re-implementing the FFT. It's listener-gated, so it costs nothing when unused.

<FFTVisualizer mode="local" :bands="28" @frame="onFrame" />
// data:  Uint8Array — one 0–255 magnitude per bar (length = `bands`)
// left/right: per-channel arrays in stereo mode, else null
//             (in stereo, `data` is the per-bar max of both channels)
function onFrame(data: Uint8Array, left: Uint8Array | null, right: Uint8Array | null) {
  const rows = 14                       // e.g. dots per column on a flip-dot panel
  data.forEach((v, col) => {
    const lit = Math.round((v / 255) * rows)
    // light dots 0..lit-1 in column `col`
  })
}

Exposed methods

Access via a template ref:

<script setup>
import { ref } from 'vue'
const viz = ref()

// Manual connection control
viz.value.connect()
viz.value.disconnect()
viz.value.isConnected          // Ref<boolean>

// Feed FFT frames imperatively (mode="external") — copies the data, so it works
// even when you reuse one buffer each frame (unlike the reference-watched `data` prop)
viz.value.feedData(mono)                 // Uint8Array
viz.value.feedData(mono, left, right)    // stereo

// Local-audio device management
await viz.value.getAudioDevices()   // Promise<AudioDevice[]> (prompts for mic permission)
viz.value.audioDevices              // Ref<AudioDevice[]>
viz.value.activeAudioDeviceId       // Ref<string | undefined>
</script>

<template>
  <FFTVisualizer ref="viz" mode="local" />
</template>

Slots

| Slot | Props | Description | |------|-------|-------------| | stats | { connected: boolean, bands: number, fps: number } | Replace the default corner overlay with your own (only rendered when showStats is true) |

Composables

The data pipelines are also available standalone, if you want to drive your own rendering or combine them with mode="external".

useLocalAudio(options?)

Captures mic or display audio and runs the WASM FFT.

const {
  fftData,        // Ref<Uint8Array> — magnitudes 0–255
  isActive, sourceType, devices, activeDeviceId,
  getDevices,     // () => Promise<AudioDevice[]>
  start,          // (deviceId?) => Promise<void>  — microphone
  startDisplay,   // () => Promise<void>           — tab/system audio
  stop
} = useLocalAudio({ fftSize: 2048, bins: 80, startFreq: 100, endFreq: 18000 })

useWebSocketFft(options?)

Connects to a WebSocket that streams raw PCM and computes the FFT in-browser via WASM (distinct from the component's websocket mode, which expects pre-computed FFT). Feed its fftData into the component with mode="external".

const {
  fftData, fftDataLeft, fftDataRight, isConnected,
  connect,        // (url) => void
  disconnect,
  processSamples  // (Float32Array) => void — feed PCM manually
} = useWebSocketFft({ fftSize: 2048, bins: 80, overlap: 0.5 })

The raw WASM FFT processor is also exported from @fft-visualizer/vue/wasm if you want to use it directly.

WebSocket protocol

mode="websocket" expects a server that sends pre-computed FFT frames:

1. Config message (JSON), once on connect:

{ "type": "config", "mode": "fft", "bins": 80, "fps": 120 }

2. Binary FFT frames, continuously:

  • One uint8 (0–255) per frequency bin — bins bytes per frame
  • 0 = silence, 255 = maximum amplitude
  • Typically 100 Hz – 18 kHz, exponentially spaced

For best results the server should: capture at 48 kHz+, apply a Hann/Hamming window, compute a 1024–2048-point FFT, map to exponentially-spaced bands, apply A-weighting, convert to dB, normalize to 0–255, and stream at 60–120 fps.

Backend examples

Reference servers that capture system audio, compute FFT, and stream it:

  • Python — pyalsaaudio + numpy (incl. a Raspberry Pi variant)
  • Node.js — node-audiorecorder + fft.js
  • Rust — cpal + rustfft

SSR / Nuxt

The component touches window, navigator, and WebGL only inside onMounted and event handlers, and the gradient rasterizer guards against a missing DOM — so it is safe to import in SSR/Nuxt apps. Because it renders nothing meaningful on the server, wrap it in <ClientOnly>:

<ClientOnly>
  <FFTVisualizer mode="local" />
</ClientOnly>

Why this vs. a canvas visualizer?

Most spectrum visualizers (e.g. audioMotion-analyzer) draw to a 2D canvas. This one renders the entire frame — every bar, LED gap, gradient, glow and reflection — in a single WebGL fragment shader / draw call, which keeps it cheap at high band counts and frame rates. It also ships a WebSocket remote-FFT protocol with reference servers, so the FFT can run on a separate device (a Raspberry Pi, a media server) and the browser only draws — a case canvas visualizers don't cover.

Browser support

Requires WebGL (all modern browsers): Chrome 56+, Firefox 51+, Safari 15+, Edge 79+. Local capture additionally needs getUserMedia / getDisplayMedia.

Development

pnpm install       # install deps
pnpm dev           # playground dev server
pnpm build         # build the library (vue-tsc + vite)
pnpm build:wasm    # rebuild the Rust/WASM FFT processor (needs wasm-pack)
pnpm typecheck     # type-check only
pnpm lint          # ESLint
pnpm test          # all tests (node + real-browser WebGL via Playwright)
pnpm test:node     # fast unit tests only (no browser)

The browser tests render the component in headless Chromium and read back canvas pixels (e.g. to verify transparency). They need the browser installed once:

pnpm exec playwright install chromium

License

MIT © Wouter Vernaillen