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

aiaudiojs

v0.5.0

Published

Thin Web Audio shell over Howler.js — dispose() idempotency, AbortSignal cancellation, first-class equal-power crossfade(), and ai*js conventions. Howler stays as a peerDependency.

Readme

aiaudiojs

npm version CI License AI Generated 繁體中文

A thin Web Audio shell over Howler.js. Wraps Howl with dispose() idempotency, AbortSignal cancellation, first-class equal-power crossfade(), and the rest of the ai*js conventions. Howler stays as a peerDependency and is reachable via sound.nativeHowl when you need its full surface.

Part of the ai*js micro-runtime ecosystem — see also aifsmjs (FSM), aiecsjs (ECS), aibridgejs (cross-context RPC), aipooljs (object pool), aiquadtreejs (spatial), and aieventjs (event emitter).

Status: 0.4.0. Full implementation live. createAudio / load / play / pause / stop / fade / crossfade / dispose are all wired. Crossfade defaults to linear (backward-compat); opt in to equal-power via { curve: 'equal-power' }. 0.4.0 is a dependency-hygiene + stability-freeze release — no runtime API change from 0.3.0, and howler stays a required peer dependency (see "Why aiaudiojs").


Why aiaudiojs

Web Audio on browsers is mature but the cliff is steep, and most of the cliff is iOS Safari. Howler.js has spent 13 years polishing the unlock flow, HTML5 fallback, sprite support, codec detection, and a global AudioContext lifecycle. Throwing that away to write our own is a bad trade. Three reasons to ship a shell instead:

  • Howler.js is MIT, 9.7 KB gzip, and already mature. Even in its half-dormant 2024-onward state, the API is stable and the iOS unlock pipeline works for the happy path. Rewriting it from scratch would mean rediscovering the same edge cases.
  • The iOS Safari edge cases are WebKit-bound. iOS 17.4+ regressions on HTML5 streaming, iOS 18 VoiceOver / Audio Ducking quirks, the "5-second relock" — these are WebKit bugs, not Howler bugs. A clean-room reimplementation would catch the same bugs in the same situations.
  • ai*js conventions are what's missing, not the audio runtime. dispose() idempotency, AbortSignal end-to-end, first-class crossfade() scheduled on the AudioContext timeline, named errors, typed events — these are ~2 KB of shell code, not 10 KB of runtime.

So aiaudiojs is the ai*js-shaped audio handle:

  • Howler.js is a required peer dependency. Users install both. The shell never bundles Howler.
  • dispose() is idempotent everywhere. Top-level audio.disposeAll() and per-sound sound.dispose() are both safe to call any number of times; subsequent operations throw AudioDisposedError.
  • AbortSignal end-to-end. audio.load(url, signal) aborts in-flight network; sound.play({ signal }) stops the instance when the signal aborts; audio.crossfade(a, b, { duration, signal }) resolves early on abort. On the default linear curve the underlying Howl.fade() ramps cannot be cancelled mid-flight (they continue silently; the promise just stops blocking you); the equal-power curve schedules on the AudioContext timeline and DOES cancel the in-flight ramp on abort (cancelScheduledValues then setValueAtTime).
  • First-class crossfade(). Defaults to a linear-curve fade via Howl.fade(). Opt in to equal-power — a perceptually-flat sin/cos ramp on the AudioContext timeline via GainNode.gain.setValueCurveAtTime — with { curve: 'equal-power' }, shipped in 0.3.0.
  • Escape hatch via sound.nativeHowl. When you need Howler's sprite API, custom HTML5 element, or any advanced feature the shell deliberately doesn't expose.
  • iOS unlock retry on visibilitychange. Best-effort fix for the "context suspends after background" pattern. Doesn't pretend to solve every WebKit bug.

What this is not: not a 3D spatial framework (Howler's spatial plugin exists; use it directly if you need it), not a synth / MIDI engine, not a sprite generator (use the audiosprite CLI), not an audio worklet host. The shell is deliberately narrow.


Quick Start

pnpm add aiaudiojs howler
import { createAudio } from "aiaudiojs";

const audio = createAudio({
  autoUnlock: true,        // bind to first user gesture; default true
  resumeOnVisibility: true, // best-effort iOS Safari recover
});

// 1. Load. Returns a Promise<Sound>; signal aborts mid-load.
const zap = await audio.load("zap.mp3");
const bgm1 = await audio.load("level1.mp3");
const bgm2 = await audio.load("level2.mp3");

// 2. Play. Returns Howler's sound id so you can target this instance later.
const zapId = zap.play({ volume: 0.8 });
bgm1.play({ loop: true });

// 3. Crossfade between two loaded sounds. Default curve is linear via
//    Howl.fade() on both ramps; pass `curve: 'equal-power'` for a
//    perceptually-flat sin/cos schedule on the AudioContext timeline.
await audio.crossfade(bgm1, bgm2, { duration: 2 });
await audio.crossfade(bgm1, bgm2, { duration: 2, curve: "equal-power" });

// 4. Escape hatch — reach into Howler for the advanced surface.
const howl = zap.nativeHowl;
howl.fade(1, 0, 500, zapId); // direct Howler call

// 5. Tear down.
audio.disposeAll(); // every sound this Audio created

Capabilities / Limitations

| Will do (v1) | Won't do | | --------------------------------------------------------- | ----------------------------------------------------- | | createAudio({ autoUnlock, volume, resumeOnVisibility }) | Multi-AudioContext orchestration (Safari caps at 4) | | iOS unlock on first user gesture | Solve WebKit bugs we don't own (#1744 etc.) | | load(url, signal) with abort support | Worker-side audio decode (no OfflineAudioContext) | | play / pause / stop / fade per Sound | Audio worklets / DSP graph composition | | crossfade() (linear default + equal-power opt-in) | MIDI / synth / oscillator-driven sound | | dispose() idempotent; post-dispose throws | 3D spatial (use Howler's spatial plugin directly) | | sound.nativeHowl escape hatch (readonly property) | Sprite generator CLI (use audiosprite) | | Howler as peerDependency | Bundling Howler (keep it in user's deps graph) |


API sketch

import type { Howl } from "howler";

interface AudioOptions {
  autoUnlock?: boolean;        // default true
  volume?: number;             // master, default 1
  resumeOnVisibility?: boolean; // default true
}

interface PlayOptions {
  volume?: number;
  rate?: number;
  loop?: boolean;
  signal?: AbortSignal;
}

type CrossfadeCurve = "linear" | "equal-power";

interface CrossfadeOptions {
  duration: number;            // seconds
  signal?: AbortSignal;
  curve?: CrossfadeCurve;      // default "linear" (backward-compat)
}

interface Sound {
  play(opts?: PlayOptions): number;
  pause(id?: number): void;
  stop(id?: number): void;
  fade(from: number, to: number, ms: number, id?: number): Promise<void>;
  dispose(): void;
  readonly nativeHowl: Howl;
  readonly disposed: boolean;
}

interface Audio {
  unlock(): Promise<void>;
  load(url: string, signal?: AbortSignal): Promise<Sound>;
  crossfade(from: Sound, to: Sound, opts: CrossfadeOptions): Promise<void>;
  volume: number;
  dispose(): void;
  disposeAll(): void;
  readonly disposed: boolean;
}

class AudioError extends Error {}
class AudioDisposedError extends Error {}

function createAudio(opts?: AudioOptions): Audio;

Full JSDoc lives in src/index.ts.


Roadmap

| Version | Adds | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | | 0.0.1 | Scaffold landed — frozen API surface as a throw stub; full config + CI walk clean. | | 0.1.0 | First npm release. createAudio / load / play / pause / stop / fade / crossfade / dispose implemented. Shell ≤ 2 KB. | | 0.2.0 | Skipped — version number reserved; no release. Aligns with the v0.3 cross-package limitation cycle (all sibling packages ship 0.3.x simultaneously). | | 0.3.0 | Equal-power crossfade via { curve: 'equal-power' } — sin/cos curves scheduled directly on each sound's Web Audio _node.gain (setValueCurveAtTime); 64-sample curves; STABILITY.md. Stays within the 2 KB gzip shell budget. | | 0.4.0 | Dependency-reduction cycle. howler stays a required peer dependency — there is no Howler-free core to extract (see "Why aiaudiojs"); devDependencies aligned to the ai*js family, lockfile deduped, pnpm audit clean. Stability freeze: the 0.3.x surface is frozen on the 1.0 track. No runtime API change. | | 0.5+ | TBD — driven by v0.5 shmup integration feedback (stage→boss equal-power BGM crossfade + heavy SFX). Spatial audio (PannerNode / HRTF) is experimental, targeted at v0.7. |


License

MIT. Howler.js is also MIT.