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

pcm-ringbuf-player

v0.1.0

Published

Plays PCM Audio in the browser leveraging a ring buffer for efficient transport of data

Readme

pcm-ringbuf-player

A TypeScript library for playing PCM audio in the browser with support for multiple formats (Int16, Int24, Int32, Float32). Uses a ring buffer for efficient, low-latency transport of data between the main thread and the audio thread.

Features

  • Multiple PCM formats: Int16Array, Int32Array, Float32Array
  • 24-bit PCM support with conversion utilities
  • TypeScript generics for type-safe audio data handling
  • Configurable buffer size for different latency/stability requirements
  • Low-latency playback using AudioWorklet and SharedArrayBuffer
  • Volume control with optional ramping
  • Type-aware conversion automatically normalizes to Float32 for Web Audio API
  • Official Vite and Webpack plugins for seamless integration

Installation

npm install pcm-ringbuf-player

Build Tool Integration

Since pcm-ringbuf-player uses AudioWorklet and SharedArrayBuffer, it requires:

  1. The audio.worklet.js file to be copied to your output directory
  2. Proper HTTP headers for SharedArrayBuffer support

The library provides official plugins for Vite and Webpack to automate both of these requirements.

Vite Plugin

npm install pcm-ringbuf-player

vite.config.ts:

import { defineConfig } from 'vite'
import { pcmPlayerPlugin } from 'pcm-ringbuf-player/vite'

export default defineConfig({
  plugins: [
    pcmPlayerPlugin(), // Automatically copies worklet and sets headers
  ],
})

Options:

pcmPlayerPlugin({
  setHeaders: true  // Set to false to disable automatic CORS headers (default: true)
})

What the plugin does:

  • ✅ Copies audio.worklet.js to your dist/ folder during build
  • ✅ Serves the worklet file at /audio.worklet.js in dev mode
  • ✅ Automatically sets required SharedArrayBuffer headers:
    • Cross-Origin-Opener-Policy: same-origin
    • Cross-Origin-Embedder-Policy: require-corp

Webpack Plugin

npm install pcm-ringbuf-player

webpack.config.js:

const { PcmPlayerWebpackPlugin } = require('pcm-ringbuf-player/webpack')

module.exports = {
  plugins: [
    new PcmPlayerWebpackPlugin(), // Automatically copies worklet and sets headers
  ],
}

Options:

new PcmPlayerWebpackPlugin({
  outputDir: 'dist',     // Output directory (default: 'dist')
  setHeaders: true       // Auto-configure webpack-dev-server headers (default: true)
})

What the plugin does:

  • ✅ Copies audio.worklet.js to your output directory after build
  • ✅ Automatically configures webpack-dev-server headers for SharedArrayBuffer
  • ✅ Works with both build and development modes

Manual header configuration (if setHeaders: false):

const { PcmPlayerWebpackPlugin } = require('pcm-ringbuf-player/webpack')

module.exports = {
  plugins: [
    new PcmPlayerWebpackPlugin({ setHeaders: false }),
  ],
  devServer: {
    ...PcmPlayerWebpackPlugin.getDevServerConfig(), // Manual config
  },
}

Without a Build Tool Plugin

If you're not using Vite or Webpack, you need to:

  1. Copy the worklet file manually from node_modules/pcm-ringbuf-player/dist/audio.worklet.js to your public/static directory

  2. Set the required headers on your development server:

    Cross-Origin-Opener-Policy: same-origin
    Cross-Origin-Embedder-Policy: require-corp
  3. Load the worklet with the correct path in your code:

    const player = new PcmPlayer(48000, 2)
    await player.start() // Loads from /audio.worklet.js by default

Quick Start

Basic Usage (Int16)

import { PcmPlayer } from 'pcm-ringbuf-player'

// Create a player with 48kHz sample rate and stereo (2 channels)
const player = new PcmPlayer(48000, 2)

// Start the audio worklet
await player.start()

// Feed PCM audio data (Int16Array)
const pcmData = new Int16Array([/* your audio samples */])
player.feed(pcmData)

// Control volume (0.0 to 1.0)
player.volume(0.5)

// Stop playback and cleanup
await player.stop()

Using Different PCM Formats

import { PcmPlayer } from 'pcm-ringbuf-player'

// Int32 PCM (32-bit signed integer)
const int32Player = new PcmPlayer<Int32Array>(48000, 2, Int32Array)
await int32Player.start()
const int32Data = new Int32Array([/* samples in range -2147483648 to 2147483647 */])
int32Player.feed(int32Data)

// Float32 PCM (normalized -1.0 to 1.0)
const float32Player = new PcmPlayer<Float32Array>(48000, 2, Float32Array)
await float32Player.start()
const float32Data = new Float32Array([/* samples in range -1.0 to 1.0 */])
float32Player.feed(float32Data)

Custom Buffer Size

// Default buffer: 1000 blocks (~2.7 seconds at 48kHz stereo)
const defaultPlayer = new PcmPlayer<Int16Array>(48000, 2, Int16Array)

// Larger buffer for unstable networks or background tabs (5 seconds)
const largeBufferPlayer = new PcmPlayer<Int16Array>(48000, 2, Int16Array, 2000)

// Smaller buffer to reduce memory usage (1.3 seconds)
const smallBufferPlayer = new PcmPlayer<Int16Array>(48000, 2, Int16Array, 500)

Example

See the example folder for a complete React + Vite + TypeScript application demonstrating:

  • Tone Player: Programmatic PCM tone generation with selectable formats (Int16/Int32/Float32)
  • WAV File Player: Upload and play WAV files (supports 8-bit, 16-bit, 24-bit, 32-bit PCM and 32-bit Float)
  • Real-time audio playback with play/stop/restart controls
  • Proper setup with required SharedArrayBuffer headers

To run the example:

cd example
npm install
npm run dev

The example automatically detects WAV file formats including:

  • PCM (8-bit, 16-bit, 24-bit, 32-bit integer)
  • IEEE Float (32-bit)
  • WAVE_FORMAT_EXTENSIBLE (common in 24-bit and 32-bit files)

API

new PcmPlayer<T>(sampleRate, channels, pcmType?, maxBlocks?)

Creates a new PCM player instance.

Parameters:

  • sampleRate: number - Sample rate in Hz (e.g., 48000, 44100)
  • channels: number - Number of audio channels (1 for mono, 2 for stereo)
  • pcmType?: PcmArrayConstructor<T> - TypedArray constructor (Int16Array, Int32Array, or Float32Array). Default: Int16Array
  • maxBlocks?: number - Buffer size in blocks (128 samples per block). Default: 1000 (~2.7 seconds)

Returns: PcmPlayer<T> instance

Examples:

// Default: Int16Array with 1000 blocks
const player1 = new PcmPlayer(48000, 2)

// Explicit Int32Array with default buffer
const player2 = new PcmPlayer<Int32Array>(48000, 2, Int32Array)

// Float32Array with custom buffer size
const player3 = new PcmPlayer<Float32Array>(48000, 2, Float32Array, 2000)

async start(): Promise<void>

Initializes the AudioWorklet and starts the player. Must be called before feeding data.

feed(source: T): void

Feeds PCM audio data to the player. The type T matches the type specified in the constructor.

Parameters:

  • source: T - PCM audio data (Int16Array, Int32Array, or Float32Array)

Notes:

  • For stereo, data should be interleaved: [L, R, L, R, ...]
  • Data is automatically converted to Float32 for Web Audio API:
    • Int16: divided by 32768
    • Int32: divided by 2147483648
    • Float32: passed through (already normalized -1.0 to 1.0)

volume(volume: number, duration?: number): void

Sets the playback volume.

Parameters:

  • volume: number - Volume level (0.0 to 1.0)
  • duration?: number - Optional ramp duration in seconds. Default: 0

async stop(): Promise<void>

Stops playback and cleans up resources. Closes the AudioContext and disconnects all nodes.

getRawBuffer(): SharedArrayBuffer

Returns the underlying SharedArrayBuffer used for the ring buffer. Useful for advanced use cases or debugging.

Returns: SharedArrayBuffer

Type Exports

The library exports the following types for TypeScript users:

import type { PcmArrayType, PcmArrayConstructor } from 'pcm-ringbuf-player'

// PcmArrayType = Int16Array | Int32Array | Float32Array
// PcmArrayConstructor<T> - Conditional type for array constructors

24-bit PCM Support

Since JavaScript doesn't have a native Int24Array type, 24-bit PCM data must be converted to Int32Array. The library provides utility functions for this:

pcm24ToInt32(data, littleEndian?): Int32Array

Converts 24-bit PCM data (stored as Uint8Array) to Int32Array.

Parameters:

  • data: Uint8Array - Raw 24-bit PCM data (3 bytes per sample)
  • littleEndian?: boolean - Byte order (default: true)

Returns: Int32Array with converted samples

Example:

import { pcm24ToInt32 } from 'pcm-ringbuf-player'

// 24-bit PCM data from file or network
const pcm24Data = new Uint8Array([/* 3 bytes per sample */])

// Convert to Int32Array for playback
const int32Data = pcm24ToInt32(pcm24Data, true) // little-endian

// Use with PcmPlayer
const player = new PcmPlayer<Int32Array>(48000, 2, Int32Array)
await player.start()
player.feed(int32Data)

pcm24BufferToInt32(buffer, offset?, length?, littleEndian?): Int32Array

Converts 24-bit PCM data from ArrayBuffer to Int32Array.

Parameters:

  • buffer: ArrayBuffer - Buffer containing 24-bit PCM data
  • offset?: number - Byte offset (default: 0)
  • length?: number - Length in bytes (default: entire buffer from offset)
  • littleEndian?: boolean - Byte order (default: true)

Returns: Int32Array with converted samples

Example:

import { pcm24BufferToInt32 } from 'pcm-ringbuf-player'

// From WAV file or other source
const arrayBuffer = await file.arrayBuffer()

// Convert starting at byte 44 (typical WAV data offset), 1000 bytes
const int32Data = pcm24BufferToInt32(arrayBuffer, 44, 1000)

getPcm24Range(): { min, max, bits }

Returns the valid range for 24-bit PCM values.

Returns: { min: -8388608, max: 8388607, bits: 24 }

Buffer Size Guidelines

The maxBlocks parameter controls the ring buffer size. Each block is 128 samples (RENDER_QUANTUM_FRAMES).

Formula: Buffer duration (seconds) ≈ (maxBlocks * 128) / (sampleRate * channels)

| maxBlocks | 48kHz Stereo | 48kHz Mono | 44.1kHz Stereo | Use Case | |-----------|--------------|------------|----------------|----------| | 250 | ~0.67s | ~1.3s | ~0.72s | Low latency, stable networks | | 500 | ~1.3s | ~2.7s | ~1.45s | Balanced, memory constrained | | 1000 (default) | ~2.7s | ~5.3s | ~2.9s | Recommended for most cases | | 2000 | ~5.3s | ~10.7s | ~5.8s | Unstable networks, background tabs |

Recommendations:

  • Increase for: network streams, background tab performance, slower devices
  • Decrease for: real-time applications, memory-constrained environments
  • Monitor console for "UNDERFLOW" messages to tune appropriately

Supported PCM Formats

| Format | TypedArray | Range | Bytes/Sample | Common Usage | |--------|-----------|-------|--------------|--------------| | 16-bit PCM | Int16Array | -32768 to 32767 | 2 | CD quality, most common | | 24-bit PCM | Int32Array* | -8388608 to 8388607 | 3 | Professional audio, studio recordings | | 32-bit PCM | Int32Array | -2147483648 to 2147483647 | 4 | High precision integer | | 32-bit Float | Float32Array | -1.0 to 1.0 | 4 | Professional audio, DAWs |

Note: *24-bit PCM is automatically converted to Int32Array (JavaScript has no Int24Array type). The conversion preserves full 24-bit precision.

Contributing

Contributions are welcome! Please open a PR with:

  • Clear description of changes
  • Tests for new features
  • Updated documentation

To Do

  • [x] Tests
  • [x] Support multiple TypedArrays (Int16, Int32, Float32)
  • [x] Documentation
  • [x] Configurable buffer size
  • [x] Support for 24-bit PCM
  • [ ] Real-time resampling
  • [ ] Support for other formats (8-bit signed, 64-bit)

Performance Tips

  1. Feed in chunks: Don't feed the entire audio file at once. Feed in small chunks (50-100ms) to avoid blocking the main thread.

    const chunkSize = sampleRate * channels * 0.05 // 50ms chunks
    for (let i = 0; i < pcmData.length; i += chunkSize) {
      player.feed(pcmData.slice(i, i + chunkSize))
      await new Promise(resolve => setTimeout(resolve, 40)) // Feed faster than playback
    }
  2. Pre-buffer audio: Feed several chunks before playback starts to build up a buffer.

    await player.start()
    // Feed 1 second of audio before considering playback "started"
    for (let i = 0; i < 20; i++) {
      player.feed(chunk)
    }
  3. Monitor underflows: Check the browser console for "UNDERFLOW" messages. If you see these frequently, increase maxBlocks.

  4. Choose appropriate format:

    • Use Int16Array for most cases (2 bytes/sample, good quality)
    • Use 24-bit PCM → Int32Array for professional audio (3 bytes/sample source, 4 bytes/sample converted)
    • Use Int32Array for high-precision requirements (4 bytes/sample)
    • Use Float32Array when audio is already normalized (4 bytes/sample, common in DSP)

Compatibility

Browser Support

This library uses SharedArrayBuffer and AudioWorklet, which require:

  • Chrome/Edge 68+
  • Firefox 79+
  • Safari 14.1+

Required Headers

To enable SharedArrayBuffer, the server must set these HTTP headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Using build plugins: The official Vite and Webpack plugins automatically configure these headers for you in both development and production.

Manual configuration: If not using the plugins, you'll need to configure your server to send these headers. See the Build Tool Integration section for manual setup instructions.

For more information see the SharedArrayBuffer MDN docs.

License

MIT