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

bungee-pitch-shift

v1.0.8

Published

High-quality, low-latency pitch shifting and time stretching for Web Audio API using WebAssembly

Readme

Bungee Pitch Shift

High-quality, low-latency pitch shifting and time stretching for Web Audio API using WebAssembly.

Built on the Bungee phase vocoder library, this package provides both a framework-agnostic class-based API and optional React hooks for easy integration.

Features

  • High Quality: Phase vocoder algorithm for natural-sounding pitch shifts
  • Low Latency: ~40ms latency using AudioWorklet and WebAssembly
  • Independent Control: Separate pitch shifting (-12 to +12 semitones) and time stretching (0.5x to 2x speed)
  • Wet/Dry Mix: Blend processed and original signals
  • Web Worker Support: Offline processing, file loading, and waveform generation in background threads
  • Framework Agnostic: Works with vanilla JavaScript or any framework
  • React Support: Optional React hooks included
  • TypeScript: Full TypeScript support with type definitions

Installation

npm install bungee-pitch-shift

Setup

After installation, copy the bundled processor to your public directory:

# Copy the bundled processor (includes embedded WASM)
cp node_modules/bungee-pitch-shift/dist/bungee-processor-bundled.js public/

That's it! The bundled processor is completely self-contained:

  • ✅ Includes the AudioWorklet processor
  • ✅ Includes the Emscripten WASM glue code
  • ✅ Includes the embedded WASM binary
  • ✅ No additional files needed

Note: The path assumes your public directory is at public/. Adjust as needed for your project structure (e.g., public/, static/, dist/).

Build Tool Integration

For automated copying during builds, add a postinstall script to your package.json:

{
  "scripts": {
    "postinstall": "cp node_modules/bungee-pitch-shift/dist/bungee-processor-bundled.js public/"
  }
}

Using a CDN

Load the bundled processor from a CDN or remote server:

const pitchShift = await BungeePitchShift.create(audioContext, {
  workletPath: 'https://cdn.example.com/bungee-processor-bundled.js'
});

Benefits:

  • No need to copy files to your public directory
  • Better caching and performance via CDN
  • Simplified deployment
  • Share across multiple projects

CORS Note: When loading from a different domain, ensure the server sends appropriate CORS headers.

Next.js / Vite / Other Build Tools

Copy the file to your static assets directory:

Next.js:

cp node_modules/bungee-pitch-shift/dist/bungee-processor-bundled.js public/

Vite:

cp node_modules/bungee-pitch-shift/dist/bungee-processor-bundled.js public/

Create React App:

cp node_modules/bungee-pitch-shift/dist/bungee-processor-bundled.js public/

Quick Start

Vanilla JavaScript / TypeScript

import { BungeePitchShift } from 'bungee-pitch-shift';

// Create audio context
const audioContext = new AudioContext();

// Initialize pitch shifter (uses bundled processor by default)
const pitchShift = await BungeePitchShift.create(audioContext);

// Or specify custom path:
// const pitchShift = await BungeePitchShift.create(audioContext, {
//   workletPath: '/bungee-processor-bundled.js'
// });

// Connect to audio graph
sourceNode.connect(pitchShift.node);
pitchShift.connect(audioContext.destination);

// Control parameters
pitchShift.setPitch(5);    // +5 semitones (perfect fourth up)
pitchShift.setSpeed(1.2);  // 20% faster
pitchShift.setMix(0.8);    // 80% wet, 20% dry

// Cleanup when done
pitchShift.dispose();

React

import { usePitchShift } from 'bungee-pitch-shift/react';
import { useRef, useEffect } from 'react';

function AudioPlayer() {
  const audioContext = useRef(new AudioContext()).current;
  const {
    pitchShift,
    initialized,
    updatePitch,
    updateSpeed,
    updateMix,
    info
  } = usePitchShift(audioContext);

  useEffect(() => {
    if (!pitchShift || !initialized) return;

    // Connect to audio graph
    sourceNode.connect(pitchShift.node);
    pitchShift.connect(audioContext.destination);

    return () => {
      pitchShift.disconnect();
    };
  }, [pitchShift, initialized]);

  return (
    <div>
      <h3>Bungee v{info.version}</h3>

      <label>
        Pitch:
        <input
          type="range"
          min="-12"
          max="12"
          step="1"
          onChange={(e) => updatePitch(Number(e.target.value))}
        />
      </label>

      <label>
        Speed:
        <input
          type="range"
          min="0.5"
          max="2"
          step="0.1"
          onChange={(e) => updateSpeed(Number(e.target.value))}
        />
      </label>

      <label>
        Mix:
        <input
          type="range"
          min="0"
          max="1"
          step="0.01"
          onChange={(e) => updateMix(Number(e.target.value))}
        />
      </label>
    </div>
  );
}

Web Worker Utilities

The bungee-pitch-shift/worker module provides Web Worker-based utilities for CPU-intensive audio operations. These tools keep your main thread responsive during heavy processing tasks.

Features

  • Offline Processing: Process entire audio files with pitch shifting
  • Audio File Loading: Load and decode audio files off the main thread
  • Waveform Generation: Generate visualization data without blocking UI
  • Fully Typed: Complete TypeScript support
  • Transferable Objects: Efficient zero-copy data transfer

Setup

Copy the bundled worker file to your public directory:

cp node_modules/bungee-pitch-shift/dist/worker/audio-processor.worker.bundle.js public/

Important: Use the .bundle.js file (not .worker.js). The bundle file is self-contained and has all dependencies included.

OfflineProcessor - Batch Audio Processing

Process entire audio files with pitch shifting in a worker thread:

import { OfflineProcessor } from 'bungee-pitch-shift/worker';

const processor = new OfflineProcessor({
  workerPath: '/audio-processor.worker.bundle.js',
  workletPath: '/bungee-processor-bundled.js'
});

// Process audio buffer
const audioContext = new AudioContext();
const processed = await processor.processWithContext(
  audioBuffer,
  audioContext,
  {
    pitch: 5,      // +5 semitones
    speed: 1.2,    // 20% faster
    mix: 1.0,      // 100% wet
    onProgress: (progress) => {
      console.log(`Progress: ${Math.round(progress * 100)}%`);
    }
  }
);

// Cleanup
processor.dispose();

Key Benefits:

  • Non-blocking: UI remains responsive during processing
  • Progress tracking: Get real-time updates on processing status
  • Faster than real-time: No playback constraints

AudioFileLoader - Load Audio Files in Workers

Load and decode audio files without blocking the main thread:

import { AudioFileLoader } from 'bungee-pitch-shift/worker';

const loader = new AudioFileLoader({
  workerPath: '/audio-processor.worker.bundle.js'
});

// Load from URL
const audioContext = new AudioContext();
const audioBuffer = await loader.loadWithContext(
  'https://example.com/audio.mp3',
  audioContext,
  {
    onProgress: (progress) => {
      console.log(`Loading: ${Math.round(progress * 100)}%`);
    }
  }
);

// Or decode raw data
const response = await fetch('audio.mp3');
const arrayBuffer = await response.arrayBuffer();
const decoded = await loader.decodeWithContext(arrayBuffer, audioContext);

// Cleanup
loader.dispose();

Use Cases:

  • Large file loading without UI freezing
  • Batch loading multiple files
  • Pre-loading audio for web apps

WaveformGenerator - Visualization Data

Generate waveform data for audio visualizations:

import { WaveformGenerator } from 'bungee-pitch-shift/worker';

const generator = new WaveformGenerator({
  workerPath: '/audio-processor.worker.bundle.js'
});

// Generate waveform with 1000 samples
const waveform = await generator.generate(audioBuffer, {
  samples: 1000,
  channel: 0,      // Left channel (or -1 for average)
  useRMS: false    // Use peak values
});

// Use waveform.data for rendering
for (let i = 0; i < waveform.data.length; i++) {
  const amplitude = waveform.data[i];
  // Draw visualization bar
}

// Generate for multiple channels
const multiChannel = await generator.generateMultiChannel(audioBuffer, {
  samples: 1000
});

// Quick preview (low resolution)
const preview = await generator.generatePreview(audioBuffer, 100);

// High resolution
const hires = await generator.generateHighRes(audioBuffer);

// Cleanup
generator.dispose();

Waveform Options:

  • samples: Number of samples in output (default: 1000)
  • channel: Which channel to use (0 = left, 1 = right, -1 = average all)
  • useRMS: Use RMS values instead of peaks (default: false)

Worker Utilities

The worker module also exports useful utility functions:

import {
  serializeAudioBuffer,
  deserializeAudioBuffer,
  getTransferables,
  downsampleAudioBuffer,
  validateAudioBuffer,
  formatDuration,
  formatBytes
} from 'bungee-pitch-shift/worker';

// Serialize for efficient transfer
const serialized = serializeAudioBuffer(audioBuffer);
const transferables = getTransferables(serialized);
worker.postMessage({ audioData: serialized }, transferables);

// Downsample for visualization
const waveformData = downsampleAudioBuffer(audioBuffer, 1000);

// Format helpers
console.log(formatDuration(audioBuffer.duration)); // "2:30"
console.log(formatBytes(arrayBuffer.byteLength));  // "1.5 MB"

Worker Configuration

All worker classes accept configuration options:

interface WorkerConfig {
  workerPath?: string;      // Path to worker script
  workletPath?: string;     // Path to bundled AudioWorklet processor
  timeout?: number;         // Operation timeout (ms)

  // Deprecated - no longer needed with bundled processor:
  wasmPath?: string;        // @deprecated
  wasmBinaryPath?: string;  // @deprecated
}

Best Practices

  1. Reuse Worker Instances: Create once, use multiple times
  2. Dispose When Done: Always call .dispose() to clean up
  3. Progress Callbacks: Use for better UX on long operations
  4. Error Handling: Wrap worker calls in try-catch blocks
const processor = new OfflineProcessor();

try {
  const result = await processor.processWithContext(
    audioBuffer,
    audioContext,
    { pitch: 5, speed: 1.0 }
  );
  // Use result...
} catch (error) {
  console.error('Processing failed:', error);
} finally {
  processor.dispose();
}

API Reference

BungeePitchShift Class

Static Methods

create(audioContext, config?)

Create and initialize a new pitch shifter instance.

// Simplest usage (uses defaults)
const pitchShift = await BungeePitchShift.create(audioContext);

// With configuration
const pitchShift = await BungeePitchShift.create(audioContext, {
  workletPath: '/bungee-processor-bundled.js', // Path or URL to bundled processor
  initialPitch: 0,                              // Initial pitch in semitones
  initialSpeed: 1.0,                            // Initial speed multiplier
  initialMix: 1.0                               // Initial wet/dry mix
});

Note: wasmPath and wasmBinaryPath options are deprecated. The bundled processor has the WASM binary embedded, so these are no longer needed.

Instance Properties

  • node: AudioWorkletNode - The AudioWorkletNode for connecting to audio graph
  • initialized: boolean - Whether the processor is initialized
  • params: PitchShiftParams - Current parameters (read-only)
  • info: BungeeInfo - Information about the Bungee processor

Instance Methods

setPitch(semitones: number)

Set pitch shift in semitones.

pitchShift.setPitch(7);   // Perfect fifth up
pitchShift.setPitch(-12); // One octave down
setSpeed(speed: number)

Set playback speed multiplier.

pitchShift.setSpeed(1.5);  // 50% faster
pitchShift.setSpeed(0.75); // 25% slower
setMix(mix: number)

Set wet/dry mix (0 = fully dry, 1 = fully wet).

pitchShift.setMix(1.0); // 100% processed
pitchShift.setMix(0.5); // 50/50 blend
pitchShift.setMix(0.0); // 100% dry (bypass)
reset()

Reset internal processor state. Call this when seeking in audio playback.

pitchShift.reset();
connect(destination: AudioNode)

Connect to another audio node.

pitchShift.connect(audioContext.destination);
disconnect(destination?: AudioNode)

Disconnect from audio nodes.

pitchShift.disconnect();
addEventListener(listener: EventListener)

Add an event listener for processor events.

pitchShift.addEventListener((event) => {
  if (event.type === 'initialized') {
    console.log('Version:', event.info.version);
  }
});
removeEventListener(listener: EventListener)

Remove an event listener.

dispose()

Clean up and release resources. Always call this when done.

pitchShift.dispose();

React Hook: usePitchShift

const {
  pitchShift,      // BungeePitchShift instance
  initialized,     // boolean
  params,          // Current parameters
  info,            // Processor info
  updatePitch,     // (semitones: number) => void
  updateSpeed,     // (speed: number) => void
  updateMix,       // (mix: number) => void
  setEnabled,      // (enabled: boolean) => void
  reset            // () => void
} = usePitchShift(audioContext, config);

TypeScript Types

interface BungeePitchShiftConfig {
  /** Path to bundled processor (default: '/bungee-processor-bundled.js') */
  workletPath?: string;

  /** @deprecated - No longer needed, WASM is embedded in bundled processor */
  wasmPath?: string;

  /** @deprecated - No longer needed, WASM is embedded in bundled processor */
  wasmBinaryPath?: string;

  /** Initial pitch shift in semitones (default: 0) */
  initialPitch?: number;

  /** Initial playback speed (default: 1.0) */
  initialSpeed?: number;

  /** Initial wet/dry mix (default: 1.0) */
  initialMix?: number;
}

interface PitchShiftParams {
  pitchSemitones: number;
  speed: number;
  mix: number;
}

interface BungeeInfo {
  version?: string;
  edition?: string;
  latency?: number;
}

Examples

Audio File Player with Pitch Shift

import { BungeePitchShift } from 'bungee-pitch-shift';

const audioContext = new AudioContext();
const pitchShift = await BungeePitchShift.create(audioContext);

// Load audio file
const response = await fetch('audio.mp3');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

// Create source
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;

// Connect: source → pitch shift → destination
source.connect(pitchShift.node);
pitchShift.connect(audioContext.destination);

// Play with pitch shift
pitchShift.setPitch(3);
source.start();

Real-time Microphone Processing

import { BungeePitchShift } from 'bungee-pitch-shift';

const audioContext = new AudioContext();
const pitchShift = await BungeePitchShift.create(audioContext);

// Get microphone input
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const source = audioContext.createMediaStreamSource(stream);

// Connect: mic → pitch shift → destination
source.connect(pitchShift.node);
pitchShift.connect(audioContext.destination);

// Apply pitch shift
pitchShift.setPitch(-5);

Integration with Existing Effects Chain

const reverb = audioContext.createConvolver();
const delay = audioContext.createDelay();
const gain = audioContext.createGain();

// Complex chain: source → pitch → reverb → delay → gain → destination
source.connect(pitchShift.node);
pitchShift.connect(reverb);
reverb.connect(delay);
delay.connect(gain);
gain.connect(audioContext.destination);

Browser Compatibility

Requires:

  • Web Audio API with AudioWorklet support
  • WebAssembly support

Supported browsers:

  • Chrome/Edge 66+
  • Firefox 76+
  • Safari 14.1+

Performance

  • Latency: ~40ms (depending on buffer size and sample rate)
  • CPU Usage: Efficient WASM implementation
  • Automatic Bypass: When pitch=0 and speed=1.0, processing is bypassed for maximum performance

License

MIT

Credits

Built on Bungee by Signalsmith Audio.