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

@bloxwap/sfx

v0.1.0

Published

Interface sound effects for the web, synthesized live with Web Audio. No audio files, zero runtime dependencies.

Readme

Features

  • 19 sounds for hovers, presses, toggles, confirmations, errors, loading, and money moments.
  • Zero files, zero dependencies. Every sound is a small recipe of oscillators and filtered noise.
  • One node per play. Each sound is rendered once with an OfflineAudioContext and cached. After that, a play is a single AudioBufferSourceNode, 5–75× cheaper on the main thread than building the synth graph each time.
  • A mixed output. One master bus with volume control and a limiter, so overlapping sounds don't clip. Voice stealing and retrigger guards stop sounds from piling up.
  • Declarative binding. Add data-sound-* attributes and call bind() once. It uses delegated listeners with no MutationObserver, handles keyboard press and release, skips disabled controls, and returns an unbind function.
  • Safe everywhere. Importing is SSR-safe. play() never throws, and it waits for the browser's first user gesture before touching audio. Audio resumes on its own when the page comes back from the background or an iOS interruption.
  • Native and video export. The sfx-wav CLI and @bloxwap/sfx/wav render the same sounds to WAV files for native apps, and renderTo() bakes them into a video soundtrack.
  • Tested. 100% line, branch, and function coverage, including real offline renders of every sound.

Installation

npm install @bloxwap/sfx

ESM only. Works in all modern browsers, and is a silent no-op on the server.

Usage

Declarative: add attributes, then call bind() once.

<button data-sound-press data-sound-release>Save</button>
<a href="/docs" data-sound-hover="tick">Docs</a>
<button role="switch" data-sound-toggle>Dark mode</button>
import { bind } from '@bloxwap/sfx';

const unbind = bind(); // the whole document, including elements added later

Imperative: play a sound from your own code.

import { play, preload, setVolume } from '@bloxwap/sfx';

preload(); // optional: render every sound now so the first play is as cheap as the rest
setVolume(0.6);

try {
  await save();
  play('success');
} catch {
  play('error');
}

play('tick', { volume: 0.5, rate: 1.25, pan: -0.3 });

// A tap: release 90 ms after press, scheduled on the audio clock.
play('press');
play('release', { delay: 0.09 });

Attributes

| Attribute | Plays on | Default sound | | -------------------- | -------------------------------- | ------------- | | data-sound-hover | mouse pointerenter | chime | | data-sound-press | primary pointerdown, Enter/Space down | press | | data-sound-release | primary pointerup, Enter/Space up | release | | data-sound-toggle | click (including keyboard) | toggle |

Set the value to a sound name to choose a different sound (data-sound-hover="tick"). Hover sounds play only for a mouse on a fine pointer, at most one every 150 ms. Controls that are disabled, aria-disabled="true", or inert stay silent.

Sounds

| Sound | Character | Good for | | --- | --- | --- | | chime | soft rising two-note bell | default hover | | sparkle | quick four-note twinkle | playful accents | | droplet | single note gliding down | dismiss, collapse | | bloom | warm slow swell | reveal, expand | | whisper | breathy quiet texture | dense lists | | tick | crisp instant tick | navigation hover | | press | dull muted knock | pointer down | | release | bright springy tick | pointer up | | toggle | mechanical click-clack | switches, tabs | | success | warm three-note confirmation | completed actions | | error | soft descending refusal | recoverable errors | | page | papery flick and glass tick | pages, carousels | | loading | brief unresolved rise | work starting | | ready | focus tick and open fifth | content ready | | payout | deep two-stage coin | payouts, rewards | | deposit | wide metallic shimmer | deposits, credits | | pluck | tight falling pluck | pickups, selections | | notification | bright physical bell | messages, activity | | loss | heavy two-stage fall | losses, negative outcomes |

API

import {
  play, preload, bind, unlock,
  setEnabled, isEnabled, setVolume, getVolume, configure,
  stopAll, activeVoices, getOutput, dispose,
  renderTo, renderBuffer,
  sounds, isSound, duration,
  type SoundName, type PlayOptions, type BindOptions, type EngineOptions, type RenderOptions,
} from '@bloxwap/sfx';

| Function | Description | | --- | --- | | play(name = 'chime', options?) | Plays a sound now. options: volume (0–2), rate (0.25–4), pan (-1–1), delay (seconds, 0–10), minInterval (ms, overrides configure()), force (skip the user-gesture check). Never throws. | | preload(names?) | Renders sounds (all of them by default) to buffers. Safe to call before any user gesture. | | bind(root = document, options?) | Wires data-sound-* attributes under root. Returns unbind(). Options: keyboard, hoverInterval. | | unlock() | Creates and resumes audio from inside a gesture. bind() calls it on the first press. | | setEnabled(on) / isEnabled() | Global mute. Sounds already playing finish; sounds waiting on a suspended context are dropped. | | setVolume(0–1) / getVolume() | Master volume, with a short glide to avoid clicks. | | configure({ maxVoices, minInterval, resume }) | Voice cap (default 24), per-sound retrigger guard (default 16 ms), and what play() does while audio is suspended: 'queue' (default) or 'eager'. | | stopAll() / activeVoices() | Stops every playing sound / counts them. | | getOutput() | The last node before the speakers, for an AnalyserNode or a recorder. | | dispose() | Closes the audio context, removes its page listeners, and clears caches. | | renderTo(context, name, options?) | Schedules a sound onto any BaseAudioContext, such as an OfflineAudioContext. options: volume, rate, pan, delay, destination. Returns false instead of throwing. | | renderBuffer(name, { sampleRate }?) | Renders one sound to a new stereo AudioBuffer (3000–768000 Hz). Resolves null if it can't. | | sounds / isSound(value) / duration(name) | The catalog, a type guard, and each sound's length in seconds. |

The raw recipe data is available from @bloxwap/sfx/recipes.

With the default resume: 'queue', sounds requested while the audio context is suspended play once it resumes, and requests older than 250 ms are dropped. 'eager' schedules them at once for the lowest latency, at the cost of stacking up during a long suspension. The engine also resumes the context when the page becomes visible, is shown, or regains focus, as long as it has had a user gesture.

WAV export and native apps

Render the same sounds to WAV files for iOS, Android, or desktop apps. The CLI needs the optional peer dependency node-web-audio-api:

npm install --save-dev @bloxwap/sfx node-web-audio-api
npx sfx-wav --out assets/sfx --sounds tick,toggle,success --combo tap=press@0,[email protected] --trim

| Flag | Default | | | --- | --- | --- | | --out <dir> | ./sfx | Output directory | | --sounds <a,b,c> | all | Sounds to render ("" for only combos) | | --sample-rate <hz> | 48000 | 3000–768000 | | --bit-depth <16\|24\|32> | 16 | 16/24-bit PCM or 32-bit float | | --channels <1\|2> | 2 | Mono is an average of both channels | | --trim | off | Cut the tail below -60 dB, keeping 20 ms | | --combo <name=sound@s,...> | | Mix sounds into one file (repeatable) |

It exits with 0 on success, 1 when rendering or writing fails, and 2 for bad flags.

The encoder is also a separate, dependency-free entry point that works on any AudioBuffer:

import { renderBuffer } from '@bloxwap/sfx';
import { encodeWav, trimSilence } from '@bloxwap/sfx/wav';

// In a browser. In Node, use renderTo() with node-web-audio-api's OfflineAudioContext.
const buffer = await renderBuffer('success', { sampleRate: 44100 });
if (buffer) {
  const wav = encodeWav(trimSilence(buffer), { bitDepth: 24, channels: 1 }); // an ArrayBuffer
  const file = new Blob([wav], { type: 'audio/wav' });
}

encodeWav(buffer, { bitDepth: 16 | 24 | 32, channels: 1 | 2 }) throws a RangeError for an invalid format. trimSilence(buffer, { thresholdDb = -60, padMs = 20 }) cuts the tail relative to the peak. See Native apps and WAV export, which also covers baking sounds into a video soundtrack.

Documentation

Guides, the full API reference, and a live sound board: https://bloxwap.github.io/sfx/

License

MIT