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

@soundtouchjs/audio-worklet

v1.0.10

Published

AudioWorklet implementation of the SoundTouchJS library

Downloads

10,771

Readme

@soundtouchjs/audio-worklet

An AudioWorklet implementation of the SoundTouchJS audio processing library. Provides real-time pitch shifting, tempo adjustment, and rate transposition on the audio rendering thread — replacing the deprecated ScriptProcessorNode approach.

Installation

npm install @soundtouchjs/audio-worklet

This package depends on @soundtouchjs/core, which will be installed automatically.

Usage

1. Register the processor

The package ships a pre-bundled processor file at @soundtouchjs/audio-worklet/processor. You need to serve this file and register it with the AudioContext before creating a node.

import { SoundTouchNode } from '@soundtouchjs/audio-worklet';

const audioCtx = new AudioContext();

// Register the worklet processor (do this once)
await SoundTouchNode.register(audioCtx, '/soundtouch-processor.js');

How you serve the processor file depends on your setup:

  • Vite: Copy or serve node_modules/@soundtouchjs/audio-worklet/dist/soundtouch-processor.js from your public/ directory
  • Webpack: Use new URL('@soundtouchjs/audio-worklet/processor', import.meta.url) with asset modules
  • Static hosting: Copy the file to your static assets directory

2. Create a node and connect it

SoundTouchNode works with any Web Audio source node. The recommended approach for tempo control is to drive playback speed via the source's playbackRate and set the matching value on stNode.playbackRate — the processor automatically compensates pitch so you never need to calculate the ratio yourself.

With AudioBufferSourceNode

const stNode = new SoundTouchNode(audioCtx);
stNode.connect(audioCtx.destination);

const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.playbackRate.value = tempo; // tempo via playback rate
source.connect(stNode);

stNode.playbackRate.value = tempo; // tell processor the source rate
stNode.pitch.value = pitch; // desired pitch (auto-compensated)
source.start();

With an HTML audio element

const audioEl = document.querySelector('audio')!;
const stNode = new SoundTouchNode(audioCtx);
stNode.connect(audioCtx.destination);

const source = audioCtx.createMediaElementSource(audioEl);
source.connect(stNode);

audioEl.preservesPitch = false; // let SoundTouch handle pitch, not the browser
audioEl.playbackRate = tempo; // tempo via element playback rate
stNode.playbackRate.value = tempo; // tell processor the source rate
stNode.pitch.value = pitch; // desired pitch (auto-compensated)

Why playbackRate for tempo? SoundTouch's internal time-stretcher operates on small 128-sample blocks in the AudioWorklet. At higher tempos, it can't produce enough output samples per block, causing audible gaps. Using the source's playbackRate feeds samples faster, keeping the processing pipe balanced. SoundTouch then only needs to correct pitch, which it handles cleanly.

When using an <audio> element, set preservesPitch = false so the browser doesn't apply its own pitch correction on top of SoundTouch's.

3. Control parameters

All parameters are exposed as AudioParam objects, supporting both direct value setting and automation.

// Direct value
stNode.pitch.value = 1.2;
stNode.tempo.value = 0.8;
stNode.rate.value = 1.0;
stNode.pitchSemitones.value = -3;

// Automation
stNode.pitch.linearRampToValueAtTime(2.0, audioCtx.currentTime + 5);

| Parameter | Default | Range | Description | | ---------------- | ------- | --------- | -------------------------------------------------- | | pitch | 1.0 | 0.1 – 8.0 | Pitch multiplier (1.0 = original) | | tempo | 1.0 | 0.1 – 8.0 | Tempo multiplier (1.0 = original) | | rate | 1.0 | 0.1 – 8.0 | Playback rate (affects both pitch and tempo) | | pitchSemitones | 0 | -24 – 24 | Pitch shift in semitones (combined with pitch) | | playbackRate | 1.0 | 0.1 – 8.0 | Source playback rate (for auto pitch compensation) |

These ranges are intentionally broader than the typical musical sweet spot, but still bounded for real-time stability. Values outside this window tend to produce more audible artifacts, less predictable output, and higher risk of buffer starvation or unnatural sounding results, especially in the AudioWorklet's small render blocks. For most material, settings closer to 1.0 will sound cleaner.

Full example — AudioBuffer

import { SoundTouchNode } from '@soundtouchjs/audio-worklet';

const audioCtx = new AudioContext();
const gainNode = audioCtx.createGain();
gainNode.connect(audioCtx.destination);

await SoundTouchNode.register(audioCtx, '/soundtouch-processor.js');
const stNode = new SoundTouchNode(audioCtx);
stNode.connect(gainNode);

const response = await fetch('/audio.mp3');
const buffer = await response.arrayBuffer();
const audioBuffer = await audioCtx.decodeAudioData(buffer);

const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.playbackRate.value = 1.2; // 1.2x tempo
source.connect(stNode);

stNode.playbackRate.value = 1.2; // tell processor the source rate
stNode.pitch.value = 0.9; // desired pitch (auto-compensated)
stNode.pitchSemitones.value = -2;
gainNode.gain.value = 0.8;

source.start();

Full example — Audio element

import { SoundTouchNode } from '@soundtouchjs/audio-worklet';

const audioEl = document.querySelector('audio')!;
const audioCtx = new AudioContext();
const gainNode = audioCtx.createGain();
gainNode.connect(audioCtx.destination);

await SoundTouchNode.register(audioCtx, '/soundtouch-processor.js');
const stNode = new SoundTouchNode(audioCtx);
stNode.connect(gainNode);

const source = audioCtx.createMediaElementSource(audioEl);
source.connect(stNode);

audioEl.preservesPitch = false;
audioEl.playbackRate = 1.2; // 1.2x tempo
stNode.playbackRate.value = 1.2; // tell processor the source rate
stNode.pitch.value = 0.9; // desired pitch (auto-compensated)
stNode.pitchSemitones.value = -2;
gainNode.gain.value = 0.8;

Key switching and pitch control

Changing the musical key of playback is handled by the pitchSemitones parameter. Each integer step corresponds to one semitone (half-step) on the chromatic scale. For example:

  • stNode.pitchSemitones.value = 2 shifts the key up a whole step
  • stNode.pitchSemitones.value = -3 shifts down a minor third

The processor combines this with the pitch multiplier:

effectivePitch = pitch * 2^(pitchSemitones / 12)

This lets you combine continuous pitch control (pitch) with discrete key changes (pitchSemitones).

For most musical applications, set pitchSemitones to the desired interval and leave pitch at 1.0 unless you want fine-tuning within a semitone.

Package exports

| Export | Description | | --------------------------------------- | --------------------------------------------------------------------------- | | @soundtouchjs/audio-worklet | Main-thread API: SoundTouchNode class, types | | @soundtouchjs/audio-worklet/processor | Pre-bundled processor script (self-contained, @soundtouchjs/core inlined) |

Architecture

  • Processor thread: SoundTouchProcessor extends AudioWorkletProcessor, runs on the audio rendering thread. It interleaves stereo input, feeds it through the SoundTouch processing pipe, and deinterleaves the output. The @soundtouchjs/core library is bundled directly into the processor file so there are no import dependencies at runtime.
  • Main thread: SoundTouchNode extends AudioWorkletNode, providing typed AudioParam accessors for pitch, tempo, rate, semitone shift, and playback rate. A static register() method handles audioWorklet.addModule(). When playbackRate is set, the processor automatically divides the desired pitch by the playback rate, so developers never need to manually compensate for rate-induced pitch shift.

What's new in v0.4

  • Complete rewrite in TypeScript (strict mode, full type exports)
  • ESM only, targeting ES2024
  • AudioParam-based parameter control (supports Web Audio automation)
  • Pre-bundled processor file with @soundtouchjs/core inlined (~23 KB)
  • NaN protection on audio output
  • Stereo processing (mono input is duplicated to both channels)

License

LGPL-2.1 — see LICENSE for details.