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

@juandinella/audio-bands

v0.6.2

Published

Headless browser audio analysis. Get bass/mid/high bands from music or mic, with an optional React hook.

Readme

audio-bands

npm

Demo: audio-bands.juandinella.com

Headless audio analysis for the browser. Read a consistent frame of low/mid/high energy regions, custom named bands, raw FFT bins, and waveform data without shipping a renderer.

const frame = audio.snapshot();
const { bass, mid, high } = frame.bands;
const custom = frame.customBands;
const fft = frame.fft;

Why

Most audio libraries either only play audio or immediately draw a canvas for you. This one stays lower level: it gives you usable analysis data and lets you decide how to render it.

The intended center of the API is snapshot(): one call, one coherent analysis frame.

Install

npm install @juandinella/audio-bands

For repository development, npm test covers the mocked unit suite and npm run test:browser runs a small browser smoke test against the built bundles.

Entry points

  • @juandinella/audio-bands: main framework-agnostic export
  • @juandinella/audio-bands/core: explicit core-only entry
  • @juandinella/audio-bands/react: React hook

If you use the React hook, install react as well.

Minimal reference examples live in examples/README.md. The Vite app in examples/src/App.tsx is a showcase demo, while examples/snippets/ contains the smallest copyable integrations.

Usage

Vanilla JS

import { AudioBands } from '@juandinella/audio-bands';

const audio = new AudioBands({
  music: {
    fftSize: 512,
    smoothingTimeConstant: 0.7,
  },
  customBands: {
    presence: { from: 0.25, to: 0.5 },
    air: { from: 0.5, to: 1 },
  },
  onLoadError: (error) => console.error('track error', error),
  onMicError: (error) => console.error('mic error', error),
});

await audio.load('/track.mp3');
await audio.play();

function loop() {
  const frame = audio.snapshot();
  const { bass, mid, high, overall } = frame.bands;
  const custom = frame.customBands;
  const fft = frame.fft;
  const waveform = frame.waveform;

  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);

React hook

import { useAudioBands } from '@juandinella/audio-bands/react';

function Visualizer() {
  const {
    isPlaying,
    hasTrack,
    loadError,
    playbackError,
    micError,
    loadTrack,
    play,
    pause,
    setLoop,
    seek,
    getDuration,
    getCurrentTime,
    snapshot,
    togglePlayPause,
    toggleMic,
    getBands,
    getCustomBands,
  } = useAudioBands({
    customBands: {
      presence: { from: 0.25, to: 0.5 },
    },
  });

  const frame = snapshot();

  return (
    <>
      <button onClick={() => loadTrack('/track.mp3')}>load</button>
      <button onClick={play}>play</button>
      <button onClick={pause}>pause</button>
      <button onClick={() => setLoop(true)}>loop</button>
      <button onClick={() => seek(30)}>seek 0:30</button>
      <button onClick={togglePlayPause}>toggle</button>
      <button onClick={toggleMic}>Toggle mic</button>
      <pre>{JSON.stringify({
        hasTrack,
        loadError,
        playbackError,
        micError,
        duration: getDuration(),
        currentTime: getCurrentTime(),
        ...frame.bands,
        ...frame.customBands,
      }, null, 2)}</pre>
    </>
  );
}

Mic input

await audio.enableMic();

const micBands = audio.getBands('mic');
const micCustomBands = audio.getCustomBands('mic');
const waveform = audio.getWaveform('mic');

When To Use Bands Vs FFT

What bass, mid, high, and overall Mean

getBands() returns three coarse analyser regions plus a convenience summary value:

  • bass, mid, and high are normalized slices of the analyser spectrum, not fixed acoustic bands in Hz
  • the default split is percentage-based (0-0.08, 0.08-0.4, 0.4-1) over the available FFT bins
  • those regions therefore depend on analyser resolution and the underlying audio context sample rate
  • overall is a UI-oriented weighted summary (bass * 0.5 + mid * 0.3 + high * 0.2), not a perceptual loudness metric

Use these values as stable control signals for interaction and motion. If you need tighter semantic control, define customBands. If you need physically meaningful bin-level data, use getFftData() or snapshot().

Use snapshot() first when you need a full analysis frame:

  • read bands, customBands, fft, and waveform together
  • avoid multiple analyser reads in one render loop
  • keep derived values synchronized

Use getBands() when you only want stable, simple control signals:

  • pulsing a blob with low-end energy
  • scaling UI based on overall intensity
  • animating typography or CSS variables
  • driving scenes where three broad zones are enough

Use getCustomBands() when the default bass/mid/high split is too coarse, but you still want named, high-level buckets:

  • separate presence, air, or sub
  • tune bands to your own design system or animation logic
  • keep your render code semantic instead of index-based

Use getFftData() when you need bin-level detail:

  • bar visualizers
  • line spectrums
  • log interpolation
  • any renderer that maps directly over bins

Rule of thumb:

  • getBands() for product UI
  • getCustomBands() for art direction
  • getFftData() for visualizers

API

AudioBands

new AudioBands(options?: AudioBandsOptions)

Methods

| Method | Description | | ----------------------- | ----------- | | load(url) | Load a track, connect it to the analyser, and resolve when the media is ready. Rejects with AudioBandsError on load failure. | | play() | Start playback for the current track. Rejects with AudioBandsError on failure. | | pause() | Pause the current track. | | setLoop(loop) | Set whether the current and future loaded tracks should loop. | | seek(seconds) | Seek the current track to a given time in seconds. | | getDuration() | Returns the current track duration in seconds, or null when unavailable. | | getCurrentTime() | Returns the current playback time in seconds, or null when unavailable. | | togglePlayPause() | Toggle the current track. Returns a promise and propagates playback errors when toggling into play. | | enableMic() | Request microphone access and start mic analysis. Rejects with AudioBandsError on failure. | | disableMic() | Stop mic input and clean up the stream. | | snapshot(source?) | Returns { bands, customBands, fft, waveform } from a single analyser read. | | getBands(source?) | Returns normalized analyser-region energy { bass, mid, high, overall }. | | getCustomBands(source?) | Returns normalized values for configured custom bands. | | getFftData(source?) | Returns raw Uint8Array frequency bins. | | getWaveform(source?) | Returns raw time-domain data for 'music' or 'mic'. | | getState() | Returns the current playback/mic/error state. | | destroy() | Stop playback, release the mic and close the AudioContext. |

useAudioBands()

const {
  isPlaying,
  micActive,
  hasTrack,
  audioError,
  loadError,
  playbackError,
  micError,
  state,
  loadTrack,
  play,
  pause,
  setLoop,
  seek,
  getDuration,
  getCurrentTime,
  togglePlayPause,
  toggleMic,
  snapshot,
  getBands,
  getCustomBands,
  getFftData,
  getWaveform,
} = useAudioBands(options);

AudioBandsOptions

type AudioBandsOptions = {
  music?: {
    fftSize?: number;
    smoothingTimeConstant?: number;
  };
  mic?: {
    fftSize?: number;
    smoothingTimeConstant?: number;
  };
  bandRanges?: {
    bass?: { from: number; to: number };
    mid?: { from: number; to: number };
    high?: { from: number; to: number };
  };
  customBands?: Record<string, { from: number; to: number }>;
  onError?: (error: AudioBandsError) => void;
  onLoadError?: (error: AudioBandsError) => void;
  onPlaybackError?: (error: AudioBandsError) => void;
  onMicError?: (error: AudioBandsError) => void;
  onStateChange?: (state: AudioBandsState) => void;
  onPlay?: () => void;
  onPause?: () => void;
  onMicStart?: () => void;
  onMicStop?: () => void;
};

AudioBandsState

type AudioBandsState = {
  isPlaying: boolean;
  micActive: boolean;
  hasTrack: boolean; // a track source is assigned, even if playback later fails
  loadError: AudioBandsError | null;
  playbackError: AudioBandsError | null;
  micError: AudioBandsError | null;
};

Notes

  • AudioContext is created lazily on the first call to load() or enableMic().
  • load() prepares the current track but does not start playback. It resolves only after the media is ready enough for duration/seek reads to be meaningful, then you can call play() or togglePlayPause().
  • togglePlayPause() follows the same playback error contract as play(): if toggling into play fails, the returned promise rejects.
  • hasTrack means the current track finished loading and is ready on the instance. It can still be true if play() fails later due to autoplay policy or another playback error.
  • isPlaying follows the underlying media element events, so it falls back to false when the track pauses or reaches ended.
  • loadError stores track loading failures only.
  • playbackError stores playback failures for the current track, such as autoplay-policy rejections.
  • In the React hook, changing music, mic, bandRanges, or customBands recreates the underlying AudioBands instance.
  • The mic analyser is not connected to AudioContext.destination, so it will not feed back into the speakers.
  • snapshot() is the preferred way to read analysis inside requestAnimationFrame.
  • getBands(), getCustomBands(), getFftData(), and getWaveform() are convenience reads when you only need one view of the current frame.
  • getFftData() returns the same underlying buffer on each call. Copy it if you need frame-to-frame comparisons.
  • fftSize must be a power of two between 32 and 32768.
  • Band ranges are normalized from 0 to 1, where 0 is the start of the analyser spectrum and 1 is the end.
  • The default bass / mid / high labels are convenience names for analyser regions, not fixed Hz buckets.
  • overall is intended as a simple UI summary, not as an acoustically weighted loudness value.

Development

  • npm test builds the package and runs the unit suite.
  • Releases are published from GitHub Actions when a v* tag is pushed.

License

MIT