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

@libraz/libsonare

v1.7.2

Published

Audio analysis, mastering, mixing, and MIDI synthesis in WebAssembly

Readme

libsonare

CI npm npm downloads types License Docs PyPI

Turn audio into data and back — entirely in the browser. Analyze songs (BPM, key, chords, loudness), master and mix to broadcast loudness, and render MIDI through built-in instruments, all client-side via WebAssembly — the same C++ engine that runs natively, with zero dependencies and no Python or model weights. 88 named mastering DSP processors implemented against published references (ITU-R BS.1770-4 true-peak limiting, Linkwitz-Riley crossovers, Vicanek matched-Z biquads, ADAA-antialiased saturation); analysis defaults match librosa where the two overlap.

Try it in the browser

Everything runs client-side — no server, nothing uploaded.

  • 🎧 Live demos — analyze a song (BPM / key / chords), master to a target loudness, mix, and render MIDI through the built-in instruments, all in the page.
  • 🎛️ sonare studio — a full browser DAW (multi-track sequencing, piano roll, mixer, mastering, WAV / MP3 / MIDI / MusicXML export) built entirely on this WASM engine. It shows how far one Apache-2.0 engine reaches, from analysis to a playable, exportable arrangement.
  • 📖 Documentation & getting started

Installation

npm install @libraz/libsonare

For BPM/key/chord detection, feature extraction, and metering without the mastering, mixing, or realtime-engine APIs, import the smaller analysis entry:

import { detectBpm, init } from '@libraz/libsonare/analysis';

With emsdk 5.0.2, the analysis binary is 0.91 MiB raw / 368 KiB gzip; the full entry is 3.88 MiB raw / 1.31 MiB gzip. The analysis entry deliberately has no masterAudio, mixStereo, Project, Mixer, or RealtimeEngine export.

Quick Start

init() loads the WASM module once; every API is available afterwards. Top-level one-shot functions accept a request object (recommended) or positional arguments.

Audio input: start with Audio.fromMemoryWithBrowserFallback(bytes). It decodes WAV/MP3 in WASM, then uses the browser's decodeAudioData for other browser-supported formats. Pass a decoded mono Float32Array only when one is already available.

Platform constraints: the WebAssembly build is single-threaded (analysis runs to completion on the calling thread — there is no non-blocking variant) and has no host filesystem access. Drive long-running calls from a Web Worker to keep the UI responsive.

import { Audio, init } from '@libraz/libsonare';

await init();

const bytes = new Uint8Array(await file.arrayBuffer());
const audio = await Audio.fromMemoryWithBrowserFallback(bytes);
const { bpm, key } = audio.analyze();
console.log(`BPM: ${bpm}  Key: ${key.name}`);

Render a MIDI arrangement through a built-in instrument with the headless Project. The embind handle is not garbage-collected — call delete() when done.

import { init, Project } from '@libraz/libsonare';

await init();

const project = new Project();
try {
  const { clipId } = project.addMidiClip(0, 4);
  project.setMidiEvents(clipId, [
    Project.midiNoteOn(0, 0, 0, 60, 100), // ppq, group, channel, note, velocity
    Project.midiNoteOff(1, 0, 0, 60),
  ]);
  const audio = project.bounceWithSynthInstrument('saw-lead', { numChannels: 2 });
} finally {
  project.delete();
}

Using already-decoded audio

Use Float32Array directly when another API already decoded the audio:

const audio = Audio.fromBuffer(decoded.getChannelData(0), decoded.sampleRate);
const { bpm, key } = audio.analyze();

Offline Worker for longer audio

For audio longer than roughly 30 seconds, use OfflineWorkerClient to keep analysis or preset mastering off the UI thread. The published ./worker subpath is resolved automatically. It intentionally exposes only one-shot value APIs (analyze, BPM/key/chord detection, and masterAudio): native handles such as Project, Mixer, and realtime engines stay in their owning JavaScript realm.

import { OfflineWorkerClient } from '@libraz/libsonare';

const offline = new OfflineWorkerClient();
const task = offline.analyze(
  { samples, sampleRate },
  {
    onProgress: ({ progress, stage }) => updateProgress(progress, stage),
    // copy: true, // retain `samples`; the default transfers and detaches it
  },
);

cancelButton.onclick = () => task.cancel();
try {
  const result = await task;
  console.log(result.bpm, result.key.name);
} finally {
  offline.dispose();
}

By default the input Float32Array is transferred, so its buffer is detached on the calling thread. Pass { copy: true } when it must remain usable. Prompt cancellation of a running synchronous WASM call uses SharedArrayBuffer; serve the page with cross-origin isolation (COOP/COEP) when a cancel button must take effect immediately. workerUrl lets a host point the client at a separately hosted copy of @libraz/libsonare/worker.

Loading the .wasm file

Bundlers that don't auto-resolve the .wasm asset need its URL. Pass a locateFile resolver to init():

import wasmUrl from '@libraz/libsonare/wasm?url'; // Vite; adapt per bundler

await init({ locateFile: (path) => (path.endsWith('.wasm') ? wasmUrl : path) });

From a CDN, import { init } from 'https://esm.sh/@libraz/libsonare' resolves the .wasm automatically. See the getting-started guide for per-bundler setup and the AudioWorklet bridge.

Realtime voice changer preset schemas

The published package includes the JSON Schema documents for third-party voice changer presets. Resolve them through the package exports rather than copying a schema from the repository:

@libraz/libsonare/schemas/realtime-voice-changer-preset.schema.json
@libraz/libsonare/schemas/realtime-voice-changer-preset-pack.schema.json

Validate data against the schema before saving it, then pass the JSON text to validateRealtimeVoiceChangerPresetJson() before applying it. The runtime check is authoritative and also rejects malformed JSON such as duplicate keys.

Bounded-memory OPFS clip streaming

For long raw float32 clips stored in OPFS, attachOpfsClipStream supplies only the current playback window to WASM. It primes the first page, then fetches page misses on the main thread and evicts pages outside the configured read-ahead / retain-behind window. The AudioWorklet path uses the same helper: the worklet posts a bounded batch of misses, and it outputs silence until a page arrives.

import { attachOpfsClipStream } from '@libraz/libsonare';
import { SonareEngine } from '@libraz/libsonare/worklet';

const engine = await SonareEngine.create(audioContext);
const stream = await attachOpfsClipStream(engine, {
  path: 'takes/lead.f32',
  clipId: 42,
  numChannels: 2,
  numSamples: 48_000 * 600,
  pageFrames: 16_384,
});

// `clipId` must equal the explicit id supplied here.
engine.addClip(trackId, stream.provider, 0, { id: 42 });

// Close the returned binding after removing the clip (or when the host closes).
stream.binding.close();

The bounded-memory guarantee applies only to an OPFS/page-provider source. Passing a Float32Array[] to addClip keeps that full array in the JavaScript heap, so it is appropriate for short clips but does not make long clips bounded.

Cue bus on a second AudioWorklet output

Per-track PFL/AFL monitoring normally folds the cue into the program output. Pass cueOutput and the node gains a second output carrying the cue alone, so it can be routed to headphones or a separate device while the program mix stays untouched.

import { SonareEngine } from '@libraz/libsonare/worklet';

const engine = await SonareEngine.create(audioContext, { cueOutput: true });
engine.setTrackMonitorMode(trackId, 'pfl');

engine.node.connect(audioContext.destination, 0); // program
engine.node.connect(cueDestination, 1); // cue

Without cueOutput the node keeps a single output and the folded mix, sample for sample. Off the worklet, the same split is available on the zero-copy path as prepareMonitorChannels / getMonitorChannelBuffer / processPreparedWithMonitor, and as processWithMonitor for a copy-in call.

Mastering preview inside the worklet

StreamingMasteringChain is exported from @libraz/libsonare/worklet, so a live preview can run in the render realm instead of round-tripping audio to the main thread. Build and prepare() it from a message handler — prepare() allocates and must not run inside process(). An enabled loudness stage needs the offline-measured loudnessStaticGainDb, since whole-signal integrated LUFS cannot be measured block by block. The chain is a host-side stage: it is outside the engine's own delay compensation, so aligning it against other engine outputs is the caller's job.

Capabilities

Every area below has runnable examples and the full API in the documentation.

  • Analysis — BPM, key (+ candidates), chords, downbeats, sections, melody, tuning; pitch (YIN / pYIN), timbre, and the full spectral feature set (STFT, mel, MFCC, chroma, CQT/VQT, spectral contrast); metering (true-peak, LUFS, correlation, vectorscope, waveform peaks). → API
  • Mastering — 88 named DSP processors, the configurable masteringChain, 25 named presets via masterAudio, and reference-matching. → Mastering processors
  • Mixing — offline mixStereo and the block-based Mixer with scene presets. → Mixing
  • Editing DSP — time-stretch, pitch-shift, HPSS (+ residual), phase vocoder, normalize, trim, remix. → Editing DSP
  • Room acoustics — blind RT60 / EDT, impulse-response clarity metrics, RIR synthesis, room estimation and morphing. → Room acoustics
  • Realtime & streaming — RealtimeEngine (transport / MIDI / render, bounded-memory clip streaming), StreamingMasteringChain / StreamingEqualizer / StreamingRetune, RealtimeVoiceChanger, and the AudioWorklet bridge. → Realtime & streaming
  • Instruments & synthesis — built-in oscillator synth, patch-driven NativeSynth (15 synthesis engines, incl. physically-modeled piano / strings / winds — being tuned over time), and a GS-compatible SoundFont (SF2) player. → API
  • Headless DAW — Project arrangement model: audio / MIDI tracks and clips, undo/redo, clip warp, SMF / MIDI 2.0 Clip File I/O, deterministic JSON, offline bounce. → API
  • Conversions — Hz / mel / MIDI / note, frames / time, resample.

Native failures throw a SonareError carrying a numeric code (an ErrorCode value) and its codeName; narrow with the isSonareError type guard.

Documentation

Full API reference, guides, and browser-local demos live at libsonare.libraz.net (getting started · browser / WASM API · demos).

Also available

pip install libsonare  # Python bindings with CLI

The native Node.js N-API binding (reads files from disk) lives at bindings/node.

License

Apache License 2.0