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

libsidplayfp-wasm

v1.1.1

Published

Independent WebAssembly distribution of libsidplayfp, the Commodore 64 SID player, with TypeScript bindings.

Readme

libsidplayfp WASM

npm Build Codecov License: GPL v2 or later Runtime

Play Commodore 64 SID music in the browser, Node.js, or Bun.

This is libsidplayfp compiled to WebAssembly, with TypeScript types and a SidAudioEngine wrapper. It runs the tune's own 6510 code inside a cycle-based emulation of the C64 and its SID chips, so what you hear is the program playing, not a file being read.

Two SID emulations come with it: reSIDfp, which models the 6581 and 8580 chips as faithfully as it can while staying realtime, and SIDLite, which approximates them and renders about an order of magnitude faster.

Every build is compared sample-for-sample against a native build of the same source before it is published.

npm install libsidplayfp-wasm

🎵 Play a SID

import { SidAudioEngine } from "libsidplayfp-wasm";

const engine = new SidAudioEngine();
await engine.loadSidBuffer(new Uint8Array(sidFileBytes));

const pcm = await engine.renderSeconds(60); // interleaved 16-bit stereo PCM
engine.dispose();

renderSeconds returns a plain Int16Array you own. To stream instead of rendering ahead, pull fixed-size blocks:

const block = await engine.renderFrames(4096); // 4096 frames, interleaved

The low-level render(cycles) argument counts event-scheduler steps, not a strict elapsed-cycle or audio-duration budget. SIDLite grows its sample storage before clocking the chip and keeps mixer pointers current, including when video DMA makes a pull span more than the original 20 ms allocation. SIDLite's approximation can still differ slightly between pull sizes; a PCM difference alone does not diagnose a buffer error. The capacity regression uses AddressSanitizer and the real raster-sensitive input from issue #14.

Subtunes, seeking, and metadata:

const info = engine.getTuneInfo();       // songs, chips, clock, format, infoStrings…
await engine.selectSong(2);              // 0-based; clamped to the tune's range
await engine.seekSeconds(30);            // repositions playback, not just the counter
engine.getTimeMs();                      // libsidplayfp's own playback clock
engine.getTuneMd5();                     // key into HVSC Songlengths.md5

Call dispose() when finished. It deletes the underlying C++ object, which is not garbage collected, and drops the reference to the WebAssembly heap so it becomes collectable.

〰️ Shaping the sound

A SID sounds different depending on the machine it is played back on. All of libsidplayfp's emulation settings are available:

await engine.setEmulationConfig({
  c64Model: "NTSC",      // PAL | NTSC | OLD_NTSC | DREAN | PAL_M
  forceC64Model: true,   // ignore what the tune's header claims
  sidModel: "MOS8580",   // MOS6581 | MOS8580 - audibly very different chips
  forceSidModel: true,
  digiBoost: true,       // improves 8580 digi playback
});

Per-voice control and register read-back, for players and visualisers:

engine.mute(0, 2, true);            // silence voice 3 of the first chip
engine.setFilterEnabled(0, false);  // bypass its analogue filter
engine.getSidStatus(0);             // Uint8Array(32) of live register values
engine.getInstalledSids();          // 1, 2 or 3 for multi-SID tunes

The reSIDfp engine additionally models the analogue filter closely enough that you can tune it toward a particular physical chip:

if (engine.supportsFilterConfig()) {
  engine.setFilterConfig({
    filter6581Curve: 0.5,         // 0.0 (dark) .. 1.0 (bright)
    filter6581Range: 0.5,
    old6581Caps: true,            // the leakier original capacitors
    combinedWaveforms: "AVERAGE", // AVERAGE | WEAK | STRONG
    dacLeakage: 1.0,              // DAC leakage, 0.0 (none) .. 1.0 (standard)
    offset6581: 1.0,              // 6581 digi loudness, 0.0 (average) .. 1.0 (loud)
    dcbRes: 0.0,                  // DC blocker high-pass, 0.0 (~1.6 Hz) .. 1.0 (~16 Hz)
  });
}

The last three values shown are the defaults.

filter6581Range and old6581Caps are process-global inside reSIDfp: they reach a shared model through static methods, so they affect every player in the same WebAssembly instance. The rest are per chip.

⚙️ Engines

Pick per instance.

| Engine | Select with | Use it for | | ------- | ----------- | ---------- | | SIDLite | "sidlite" | The default. Fast, clean playback and bulk corpus work. | | reSIDfp | "residfp" | Cycle-exact 6581/8580 emulation. The only one of the two that supports filter tuning. |

const accurate = new SidAudioEngine({ engine: "residfp" });

LIBSIDPLAYFP_WASM_ENGINE sets a process-wide default; an explicit engine option always wins.

On slow hardware the choice decides whether playback survives. Filling a 20 ms audio buffer, in Chromium with a 64 MB heap and the CPU throttled:

| CPU throttle | SIDLite | reSIDfp | | --- | --- | --- | | 1× (desktop) | 0.4 ms | 1.3 ms | | 4× (mid-tier phone) | 1.8 ms | 7.5 ms | | 10× | 4.7 ms | 13.7 ms | | 20× | 9.1 ms | 31.8 ms - too slow to keep up |

Worst call over 300, against a 20 ms budget. reSIDfp costs about 5× SIDLite and scales linearly with CPU speed, so on a low-end device it stops being realtime while SIDLite still has half its budget spare. Render offline and cache, or use SIDLite, if you cannot control the hardware.

Neither engine accumulates memory: 2000 seconds of streamed audio - 100,000 buffers - retains nothing measurable and does not slow down.

🔧 Lower-level access

The default export gives you libsidplayfp's SidPlayerContext directly. It is the same object SidAudioEngine drives, minus the buffer management.

import loadLibsidplayfp from "libsidplayfp-wasm";

const module = await loadLibsidplayfp({ engine: "residfp" });
const player = new module.SidPlayerContext();
try {
  player.configure(48_000, true);
  player.loadSidBuffer(new Uint8Array(sidFileBytes));
  // render() returns a view into WebAssembly memory: the next render()
  // overwrites it and a heap growth detaches it. Copy before doing anything else.
  const pcm = player.render(100_000).slice();
} finally {
  player.delete(); // WebAssembly objects are not garbage collected
}

SidAudioEngine copies for you; the transient-buffer contract applies only here.

Every method libsidplayfp exposes is reachable, and a check in CI reports it if a future upstream release adds one this binding has not caught up with.

⏱️ How long is this tune?

SID files carry no duration - a tune plays until you stop it. HVSC publishes Songlengths.md5, keyed by the same MD5 the engine computes.

import { SonglengthDatabase } from "libsidplayfp-wasm";

const songlengths = SonglengthDatabase.parse(
  await (await fetch("/Songlengths.md5")).text(),
);

const md5 = await player.getTuneMd5();
const seconds = songlengths.lengthSeconds(md5, player.getTuneInfo().currentSong);

Absent tunes and out-of-range songs return null, and a malformed line costs you that line rather than the database. Parsing matches libsidplayfp's own SidDatabase, down to .5 meaning 500 ms.

🌐 Browsers and bundlers

The loader finds its .wasm beside its JavaScript. If your bundler relocates static assets, say where they went:

const module = await loadLibsidplayfp({
  locateFile: (asset) => `/wasm/${asset}`,
});

Copy both dist/libsidplayfp.* and dist/sidlite/libsidplayfp.* if your users can choose an engine. In Node-like runtimes LIBSIDPLAYFP_WASM_PATH overrides the binary path.

The binaries use WebAssembly exception handling, so they need a runtime that supports it. Every browser Playwright ships is verified on each commit, and the packed package is installed and made to play a SID under Node 20, 22 and 24 on every commit. Bun works too - the test suite runs on it.

💾 C64 ROMs

Tunes that run as real C64 programs - RSID, and anything driven by interrupts or BASIC - need the KERNAL, BASIC, and CHARGEN ROMs. Without them libsidplayfp initialises the tune but never advances it, so it renders as silence or a single held frame.

Those ROMs are copyrighted and are not distributed here. Supply legally obtained images (KERNAL 8 KiB, BASIC 8 KiB, CHARGEN 4 KiB) and confirm they took effect:

await engine.setSystemROMs(kernal, basic, chargen);
engine.getRomStatus(); // { requested: true, active: true, … }

🔢 Which libsidplayfp am I getting?

The package version and the libsidplayfp version inside it are related but not always identical, so the build states what it contains:

import { LIBSIDPLAYFP_VERSION, LIBRESIDFP_VERSION } from "libsidplayfp-wasm";

This package versions itself, on its own 1.x line: an upstream engine bump takes a minor, a fix of our own takes a patch. So 1.2.0 tells you the binding changed, not which libsidplayfp is inside it - the constants above do that.

It will switch to mirroring upstream once this distribution has a track record, and a release that only advances libsidplayfp will then carry upstream's exact version - v3.0.2 publishing as 3.0.2.

Because semver has no version between 3.0.2 and 3.0.3, a fix to this package takes the next free patch and keeps the same upstream pin, and a mirror steps over any number already used:

| Version | Contains libsidplayfp | | | ------- | --------------------- | --- | | 3.0.2 | v3.0.2 | exact mirror | | 3.0.3 | v3.0.2 | a fix here; upstream unchanged | | 3.0.4 | v3.0.3 | mirror, one ahead because 3.0.3 was used | | 3.1.0 | v3.1.0 | exact again - drift closes at every upstream minor |

The exported constants are always exact, whichever scheme is in force.

✅ How releases are verified

An emulator can sound plausible while being subtly wrong, so correctness here is settled by comparing the WebAssembly build against a native build of the same source at the same pinned commits, not by listening.

Every commit

  • Full unit suite at 100% line coverage.
  • Native differential parity: both engines compared sample-for-sample against a native build of libsidplayfp at the same pinned commit. SIDLite matches bit for bit. reSIDfp must stay below −60 dBFS of error on every tune, and hold correlation ≥ 0.9999 on every tune loud enough for correlation to be meaningful. Measured runs reach correlation ≥ 0.99998 with errors between −77 and −95 dBFS, below the SID's own noise floor.
  • Browsers: Chromium and Firefox on the desktop, Chromium as a Pixel 5, WebKit as an iPhone 13 - including playback from module workers.
  • Determinism: identical audio on repeat and at any chunk size.
  • The packed tarball is installed into a clean project and made to play a SID under Node 20, 22 and 24.

Every release

A release does not repeat the checks above - it refuses to start unless that commit's own run of them was green. What it adds is everything about the artifact rather than the source:

  • Engines rebuilt from immutable upstream commits; the build aborts if a tag no longer resolves to the pinned commit, and each binary is checked to contain the engine it claims and not the other.
  • The exact tarball that will be published is installed into a clean project and made to play a SID before it is published.
  • After publishing, that exact version is reinstalled from npm and made to play a SID. The tag and release are created only if that succeeds.

Weekly, and whenever the engine's own bytes change

  • 1,678 tunes selected from HVSC #85's 61,157 - every multi-SID, every BASIC-driven RSID, every file with 32+ subtunes, plus 400 each sampled from the RSID and zero-play-address populations - rendered through both engines and compared against native builds. This also runs on any pull request that touches the upstream pins, the toolchain or the bindings.
  • A soak renders two hours of emulated playback per engine and requires the WebAssembly heap to stay flat.

Every release ships an SBOM, SHA-256 checksums, and build provenance.

⚖️ Licence and attribution

GPL-2.0-or-later, like libsidplayfp. The binaries also contain MIT components (hashlib, musl, the Emscripten runtime) and LLVM runtime libraries under Apache-2.0 with LLVM Exceptions.

  • LICENSE - the GPL-2.0 text.
  • THIRD-PARTY-NOTICES.md - every component compiled in, with its licence and copyright.
  • MODIFICATIONS.md - what this project changes in libsidplayfp and libresidfp. None of it alters the emulation.
  • dist/complete-source.tar.gz, inside this package - the complete corresponding source for the binaries, also attached to every release.

This is an independent redistribution, not an official libsidplayfp product, and not endorsed by or affiliated with its authors. Report problems here rather than upstream, unless a native sidplayfp reproduces them.

No C64 ROMs, SID tunes, or music corpora are distributed.

Contributing? See AGENTS.md.