Skip to content

harmonics-audio/fft-visualizer

Repository files navigation

FFT Visualizer

A high-performance, WebGL-based real-time audio spectrum analyzer and FFT visualizer. 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.

This is a monorepo. The rendering engine lives in a framework-agnostic core package; framework integrations are thin wrappers around it.

Packages

Package Description
@fft-visualizer/core Framework-agnostic WebGL renderer + audio/WebSocket engines as a vanilla TypeScript class. Zero framework dependencies. packages/core
@fft-visualizer/vue Vue 3 component wrapping the core. One peer dependency: Vue 3. packages/vue
@fft-visualizer/react React component wrapping the core. One peer dependency: React 18/19. packages/react

The core owns all rendering, audio capture (mic / tab / system via the Web Audio API + a lazy-loaded Rust/WASM FFT), WebSocket streaming, and the WebGL draw. Each framework package is a thin adapter that maps its own reactivity/props onto the core's imperative API.

Which package do I want?

  • Using Vue? Install @fft-visualizer/vue — see its README for props, modes, and examples.

  • Using React? Install @fft-visualizer/react — same options as props, plus callbacks and an imperative ref.

  • Vanilla JS/TS, or another framework? Use @fft-visualizer/core directly:

    import { FFTVisualizer } from '@fft-visualizer/core'
    
    const viz = new FFTVisualizer(canvas, { mode: 'local', bands: 40, gradient: 'aurora' })
    // ...later
    viz.destroy()

Data modes

The renderer has one job — draw a spectrum — and three ways to get the data:

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 a URL and reads pre-computed FFT frames A server/device already produces FFT data (e.g. a Raspberry Pi)
external You feed FFT magnitudes in yourself You have your own audio pipeline

Reference servers that stream FFT frames over WebSocket (Python, Node.js, Rust) are in backend-examples/.

Repository layout

packages/
  core/                 # @fft-visualizer/core — vanilla renderer + engines
    src/                #   FFTVisualizer class, localAudio, webSocketFft, gradients, processing
    wasm/               #   Rust FFT processor (wasm-pack, bundler target; pkg/ is committed)
  vue/                  # @fft-visualizer/vue — Vue 3 wrapper
    src/                #   FFTVisualizer.vue, composables
  react/                # @fft-visualizer/react — React wrapper
    src/                #   FFTVisualizer.tsx
playground/             # the demo site (deployed to demo.fftvisualizer.com)
  shared/               #   presets, control descriptors, radio source, stylesheet
  core/ vue/            #   one app per package, all four at full parity…
  react/ nuxt/          #   …served at /core/, /vue/, /react/, /nuxt/
  site/                 #   switcher landing + the nginx config the image runs
backend-examples/       # reference WebSocket FFT servers (python / nodejs / rust)

Every playground renders the same controls and presets from playground/shared, so the four apps show the same visualizer through four different APIs rather than four drifting demos.

Development

Uses pnpm workspaces.

pnpm install            # install all workspace deps

pnpm dev                # run the Vue playground (dev:core / dev:react / dev:nuxt for the others)
pnpm build              # build every package (pnpm -r build)
pnpm typecheck          # typecheck every package
pnpm test               # run all tests (core: node, vue/react: browser via Playwright)
pnpm lint:fix           # ESLint with auto-fix

pnpm build:wasm         # rebuild the Rust WASM FFT (requires the Rust toolchain + wasm-pack)
pnpm build:playground   # build all four apps into playground/site/dist (what the image serves)
pnpm preview            # serve that assembled site, radio proxy included

The committed packages/core/wasm/pkg/ artifacts mean a normal build needs no Rust toolchain — only pnpm build:wasm does.

License

MIT © Wouter Vernaillen