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 🙏

© 2024 – Pkg Stats / Ryan Hefner

simple-audio-worklet

v1.0.1

Published

Write an audio worklet with frame-based processing and a variety of input formats.

Downloads

3

Readme

Simple Audio Worklet

AudioWorklets handle audio in blocks of 128 frames at a time, provide parameters that can be arrays of either one or 128 values, and make setup that depends on parameters difficult.

This package abstracts the complexity and focuses on the actual processing, one frame at a time. It works with generator functions, classes, and pure functions, and produces efficient code that does not even allocate any memory after initialization.

If you are not already familiar with using Audio Worklets, see this article.

Usage

Whether a generator function, class, or pure function, use this package like this:

let processor = fromX(yourImplementation, options);

where options looks like the following:

{
  // If true, stops running the worklet if there are no input nodes connected.
  processOnly?: boolean,
  // An array of descriptors describing the parameters this worklet
  // takes. These correspond to the parameters sent to the implementation.
  parameterDescriptors?: AudioParamDescriptor[],
  // Handles incoming messages from the paired AudioWorkletNode.
  onmessage?: (this: MessagePort, ev: MessageEvent) => any,
  // If a non-empty string, calls registerProcessor(...) on the generated class.
  registerAs?: string
}

The implementation will be called each frame with args as follows:

{
  // An array containing the sample value for each input channel.
  // Empty if not connected.
  input: number[],
  // An object of the parameters declared in parameterDescriptors
  // and their corresponding values.
  readonly parameters: {
    [param: string]: number
  },
  // A port usable for communication with the paired AudioWorkletNode.
  readonly port: MessagePort,
  // An "environment" containing useful values. These values can
  // change but usually won't.
  readonly env: {
    outputChannelCount: number,
    sampleRate: number
  }
}

and expected to return either a number or a Float32Array.

A returned Float32Array will map one-to-one with the output frame channels, if it is long enough. If not, the first value of the array will be treated as a returned number.

A returned number will be treated as a mono frame, and will be copied to all output channels.

Generator function

Generator functions work as straightforward representations of asynchronous processing, which makes them well suited to audio processing applications. Most cases will involve an infinite loop with all processing done inside, and optional setup done beforehand.

For many cases, a generator function is the most readable and writable way to express a DSP algorithm.

import { fromGenerator } from 'simple-audio-worklet';

function* addNoise({parameters, input, port}) {
  // setup would go here
  for (;;) {
    let {gain} = parameters; // or just use parameters.gain
    for (let channel = 0; channel < input.length; ++channel) {
      let noise = (Math.random() * 2 - 1) * gain;
      input[channel] += noise; // Safe and faster to modify input in place
    }
    yield input;
  }
}
let processor = fromGenerator(addNoise, {
  processOnly: true,
  parameterDescriptors: [{
    name: 'gain',
    minValue: 0,
    maxValue: 1,
    defaultValue: 0.5
  }]
});

Note: Because of the nature of generators, there is a one-frame delay on the output, which may not be suitable for some cases. The very first frame is zero-filled.

Note: The identity of args, args.parameters, args.input, and args.port is guaranteed not to change. Therefore, it is okay to use

yield x;

whereas otherwise one would have to use

({parameters, input} = yield x);

However, the values for the individual parameters must be accessed on the parameters object or updated every yield, or else the values will become outdated. For example,

function* process({parameters, input, port}) {
  let {param} = parameters; // BAD, will go stale
  for (;;) {
    let {param} = parameters; // OK
    ... parameters.param ...; // OK
    // compute something as result
    yield result;
  }
}

Class

The class version looks similar to the standards-compliant processor, but with frame-based processing as opposed to block-based processing. It must define a next() method instead of a process() method, and the parameters differ. It may be more familiar to object oriented programmers than the generator function, has no frame delay, and can use state.

import { fromClass } from 'simple-audio-worklet';

class AddNoise {
  constructor({parameters, input, port}) {
    // Initialize some instance variables,
    // same arguments as on first call to next()
  }

  next({parameters, input, port}) {
    for (let channel = 1; channel < input.length; ++channel) {
      let noise = (Math.random() * 2 - 1) * parameters.gain;
      input[channel] += noise;
    }
    return input;
  }
}

let processor = fromClass(AddNoise, {
  processOnly: true,
  parameterDescriptors: [{
    name: 'gain',
    minValue: 0,
    maxValue: 1,
    defaultValue: 0.5
  }]
});

Pure function

The pure function implementation is best when no state is necessary. Every output frame should be based solely on the frame input and the parameters, not on previous frames.

import { fromPure } from 'simple-audio-worklet';

function addNoise({parameters, input, port}) {
  for (let channel = 1; channel < input.length; ++channel) {
    let noise = (Math.random() * 2 - 1) * parameters.gain;
    input[channel] += noise;
  }
  return input;
}

let processor = fromPure(addNoise, {
  processOnly: true,
  parameterDescriptors: [{
    name: 'gain',
    minValue: 0,
    maxValue: 1,
    defaultValue: 0.5
  }]
});

It is important to keep this function pure in the sense that it does not store information by capturing variables and writing to them; alternatively, we can say the function must be memoryless. This limitation is in place because multiple instances of the processor would be capturing the same variables, leading to unexpected results. Capturing a constant value is fine.

Installation

$ npm install simple-audio-worklet

Browser Module

In Chromium and potentially other browsers, worklets can now use standard ES6 module loading. This means this package can be included into browser code with something like

<script
   src="simple-audio-worklet.js"
   type="module">
</script>

where simple-audio-worklet.js has been copied from /node_modules/simple-audio-worklet/lib/index.js and then running

let context = new AudioContext();
await context.audioWorklet.addModule("my-audio-worklet.js");

with my-audio-worklet.js containing

import { fromGenerator, fromClass, fromPure } from 'simple-audio-worklet';

Webpack

Packing files in worklet global scopes with packages like worklet-loader can work but is still relatively immature at time of writing.