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

pxtone

v4.1.3

Published

Play Pxtone Collage "pxtone" files in the browser

Downloads

147

Readme

Demo

PxtoneJS v4 Demo

Install

npm

npm install pxtone

JSR (Deno)

deno add jsr:@petamoriken/pxtone

Direct download

Bundled scripts are available on GitHub Releases:

  • Pxtone.js — IIFE bundle (exposes a Pxtone namespace as a global, e.g. Pxtone.Pxtone, Pxtone.PxtoneError)
  • Pxtone.mjs — ES Modules bundle

Usage

Playing a .ptcop / .pttune file

Pxtone holds a native WebAssembly resource. While it will be automatically released eventually, it is recommended to call close() when the instance is no longer needed to ensure the resource is released promptly. If Symbol.dispose is available in your environment, you can also use the using declaration (Explicit Resource Management).

stream() returns a ReadableStream<AudioData> (format "f32-planar"). To play back with the Web Audio API using AudioBufferSourceNode, schedule each chunk ahead of time:

const response = await fetch("song.ptcop");
const fileBytes = await response.arrayBuffer();

const BUFFER_AHEAD = 0.5; // seconds

const ctx = new AudioContext();
const pxtone = new Pxtone({ sampleRate: ctx.sampleRate });
pxtone.read(fileBytes);

const stream = pxtone.stream({ loop: true });
const reader = stream.getReader();
let nextStartTime = ctx.currentTime + 0.1;

async function scheduleMore() {
  while (nextStartTime < ctx.currentTime + BUFFER_AHEAD) {
    const { done, value: audioData } = await reader.read();
    if (done) return;

    const buffer = new AudioBuffer({
      numberOfChannels: audioData.numberOfChannels,
      length: audioData.numberOfFrames,
      sampleRate: audioData.sampleRate,
    });
    for (let ch = 0; ch < audioData.numberOfChannels; ch++) {
      audioData.copyTo(buffer.getChannelData(ch), { planeIndex: ch });
    }
    audioData.close();

    if (nextStartTime < ctx.currentTime) nextStartTime = ctx.currentTime + 0.05;
    const source = new AudioBufferSourceNode(ctx, { buffer });
    source.connect(ctx.destination);
    source.start(nextStartTime);
    nextStartTime += buffer.duration;
  }
}

await scheduleMore();
setInterval(scheduleMore, 100);

// When stopping playback:
// pxtone.close();

Each AudioData chunk can also be forwarded to an AudioWorkletNode, MediaStreamTrackGenerator, or other consumers:

const response = await fetch("song.ptcop");
const fileBytes = await response.arrayBuffer();

const pxtone = new Pxtone();
pxtone.read(fileBytes);

console.log(pxtone.name);
console.log(pxtone.duration); // total length in seconds

const stream = pxtone.stream({ loop: true });
const reader = stream.getReader();

while (true) {
  const { done, value: audioData } = await reader.read();
  if (done) break;
  // pass audioData to an AudioWorklet, MediaStreamTrackGenerator, etc.
}
pxtone.close();

Decoding a .ptnoise file

decodeNoiseData() returns an AudioData with format "f32-planar". To play it back with the Web Audio API, copy each channel plane into an AudioBuffer:

const response = await fetch("drum.ptnoise");
const fileBytes = await response.arrayBuffer();

const ctx = new AudioContext();
const pxtone = new Pxtone({ sampleRate: ctx.sampleRate });
const audioData = await pxtone.decodeNoiseData(fileBytes);
pxtone.close();

const buffer = new AudioBuffer({
  numberOfChannels: audioData.numberOfChannels,
  length: audioData.numberOfFrames,
  sampleRate: audioData.sampleRate,
});
for (let ch = 0; ch < audioData.numberOfChannels; ch++) {
  audioData.copyTo(buffer.getChannelData(ch), { planeIndex: ch });
}
audioData.close();

const source = new AudioBufferSourceNode(ctx, { buffer });
source.connect(ctx.destination);
source.start();

API

new Pxtone(options?: PxtoneOptions)

Creates an instance backed by a WebAssembly service.

export interface PxtoneOptions {
  /**
   * Number of output channels. Must be `1` (mono) or `2` (stereo).
   * @default 2
   */
  numberOfChannels?: 1 | 2;
  /**
   * Output sample rate in Hz.
   * @default 44100
   */
  sampleRate?: number;
}

Properties

Audio output

| Property | Type | Description | | ------------------ | -------- | ------------------------ | | numberOfChannels | 1 \| 2 | Output channel count | | sampleRate | number | Output sample rate in Hz |

Metadata (available after read())

| Property | Type | Description | | --------- | ---------------- | -------------------------------- | | name | string \| null | Song title (Shift-JIS decoded) | | comment | string \| null | Song comment (Shift-JIS decoded) |

Master (available after read())

| Property | Type | Description | | ------------------ | ---------------- | ------------------------- | | ticksPerBeat | number \| null | Ticks per beat | | beatsPerMeasure | number \| null | Beats per measure | | beatTempo | number \| null | Tempo in BPM | | numberOfMeasures | number \| null | Total number of measures | | numberOfTicks | number \| null | Total length in ticks | | duration | number \| null | Total duration in seconds |

Loop (available after read())

| Property | Type | Description | | ------------------ | ---------------- | ------------------------------- | | loopStartMeasure | number \| null | Loop start position in measures | | loopEndMeasure | number \| null | Loop end position in measures | | loopStart | number \| null | Loop start position in seconds | | loopEnd | number \| null | Loop end position in seconds |

Playback

| Property | Type | Description | | ------------- | -------- | ------------------------------------ | | currentTick | number | Current playback position in ticks | | currentTime | number | Current playback position in seconds |

Song data

| Property | Type | Description | | -------- | ------------------------ | --------------------- | | units | readonly PxtoneUnit[] | Instrument tracks | | events | readonly PxtoneEvent[] | Automation event list |

Methods

static Pxtone.validate(buffer: ArrayBuffer | Uint8Array): boolean

Checks whether buffer is a valid .ptcop or .pttune file without loading it. Returns true if valid, false otherwise.

static Pxtone.validateNoiseData(buffer: ArrayBuffer | Uint8Array): boolean

Checks whether buffer is a valid .ptnoise file without loading it. Returns true if valid, false otherwise.

read(buffer: ArrayBuffer | Uint8Array): void

Loads a .ptcop or .pttune file and prepares it for playback. Throws a PxtoneError if the file is invalid or a stream is currently active.

stream(options?: StreamOptions): ReadableStream<AudioData>

Returns a ReadableStream that yields PCM chunks as AudioData objects (format "f32-planar"). Each chunk has at most numberOfFrames frames; the final chunk may be shorter. Only one stream may be active at a time. Throws a PxtoneError if no data has been loaded or a stream is already active.

export interface StreamOptions {
  /**
   * Playback start position in seconds.
   * @default 0
   */
  startTime?: number;
  /**
   * Units whose `played` flag is false are silenced.
   * @default false
   */
  unitMute?: boolean;
  /**
   * Loop playback from the song's repeat point.
   * @default false
   */
  loop?: boolean;
  /**
   * Maximum number of frames per channel per chunk. The final chunk may be shorter.
   * @default 1024
   */
  numberOfFrames?: number;
  /**
   * Backpressure threshold for the underlying `ReadableStream`.
   * @default 1
   */
  highWaterMark?: number;
  /** AbortSignal to cancel the stream early. */
  signal?: AbortSignal;
}

close(): void

Releases the underlying Wasm resource. Safe to call multiple times; subsequent calls are no-ops. If Symbol.dispose is available in the runtime environment, it is aliased to close() so the using declaration also works.

clear(): void

Resets the instance to its initial idle state, discarding all loaded song data.

decodeNoiseData(buffer: ArrayBuffer | Uint8Array): Promise<AudioData>

Decodes a .ptnoise file and returns an AudioData with format "f32-planar". Throws a PxtoneError if the data is invalid.

PxtoneError

Thrown by Pxtone methods on operation failures. Extends Error with an optional code property for programmatic error handling.

| code | Static constant | Description | | ------------------------------- | ---------------------------------------------- | ----------------------------------------------- | | "DISPOSED" | PxtoneError.CODE_DISPOSED | The instance has already been disposed. | | "STREAMING_ACTIVE" | PxtoneError.CODE_STREAMING_ACTIVE | Operation not allowed while a stream is active. | | "NOT_READY" | PxtoneError.CODE_NOT_READY | read() has not been called yet. | | "READ_FAILED" | PxtoneError.CODE_READ_FAILED | Failed to load the pxtone data. | | "TONES_READY_FAILED" | PxtoneError.CODE_TONES_READY_FAILED | Failed to initialize audio tones. | | "PLAYBACK_PREPARATION_FAILED" | PxtoneError.CODE_PLAYBACK_PREPARATION_FAILED | Failed to prepare audio playback. | | "RENDER_NOISE_FAILED" | PxtoneError.CODE_RENDER_NOISE_FAILED | Failed to render noise data. |

try {
  pxtone.read(buffer);
} catch (e) {
  if (e instanceof PxtoneError && e.code === PxtoneError.CODE_READ_FAILED) {
    console.error("Invalid pxtone file");
  }
}

PxtoneUnit

| Property | Type | Description | | -------- | --------- | -------------------------------------- | | name | string | Display name | | played | boolean | Whether the unit is active (not muted) |

togglePlayed(force?: boolean): void

Toggles the played flag for this unit. If force is provided, the flag is set explicitly rather than toggled.

PxtoneEvent

| Property | Type | Description | | -------- | ----------------- | ----------------------------------------------- | | tick | number | Tick position | | unit | PxtoneUnit | Target unit in the loaded song | | kind | PxtoneEventKind | Event type (see PxtoneEvent.KIND_* constants) | | value | number | Event payload |

WebAssembly

src/pxtone.wasm is built from petamoriken/pxtone-rs, a Rust port of the Pxtone Collage library.

License

MIT

See NOTICE.md for third-party license notices.