Options
All
  • Public
  • Public/Protected
  • All
Menu

audioworklet

Node Audioworklet

Node Audioworklet is a NodeJS lib exposing a set of function to output audio on an audio card. It's close to the AudioWorklet interface of WebAudio. It uses libsoundio to support a wide variety of OS and configuration: Linux (native ALSA, JACK, PulseAudio), Macintosh OS X (CoreAudio and JACK), and Windows (WASAPI). It only handle raw PCM frames that should be set into a Buffer for every frame. You can set the buffer size to precisely time the sound you are emitting.

AudioWorklet also can use another file started in a NodeJS Worker Thread as the process function to isolate the thread and better manage the memory to prevent stop-the-world Garbage Collection from stopping the thread and cause audio artifacts. You need Node v10 or higher to use this feature.

Installation

npm install --save audioworklet

Or

yarn add audioworklet

Example

List devices

const { Soundio } = require('audioworklet');

const soundio = new Soundio();

const logDevice = (device) => {
  console.log('---------------');
  for (let prop in device) {
    console.log(`${prop}:`, device[prop]);
  }
}

soundio.getDevices().outputDevices.forEach(logDevice);
soundio.getDevices().inputDevices.forEach(logDevice);

console.log('-------')

console.log('default output:', soundio.getDefaultOutputDevice().name);
console.log('default input:', soundio.getDefaultInputDevice().name);
console.log('API:', soundio.getApi());

Will output:

---------------
name: Built-in Audio Analog Stereo
id: alsa_output.pci-0000_00_1f.3.analog-stereo
formats: [
   2,  3, 4, 15, 16,
  11, 12, 7,  8
]
sampleRates: [ { min: 8000, max: 5644800 } ]
channelLayouts: [
  { name: 'Mono', channelCount: 1 },
  { name: 'Stereo', channelCount: 2 },
  { name: '2.1', channelCount: 3 },
  { name: '3.0', channelCount: 3 },
  { name: '3.0 (back)', channelCount: 3 },
  { name: '3.1', channelCount: 4 },
  { name: '4.0', channelCount: 4 },
  { name: 'Quad', channelCount: 4 },
  { name: 'Quad (side)', channelCount: 4 },
  { name: '4.1', channelCount: 5 },
  { name: '5.0 (back)', channelCount: 5 },
  { name: '5.0 (side)', channelCount: 5 },
  { name: '5.1', channelCount: 6 },
  { name: '5.1 (back)', channelCount: 6 },
  { name: '6.0 (side)', channelCount: 6 },
  { name: '6.0 (front)', channelCount: 6 },
  { name: 'Hexagonal', channelCount: 6 },
  { name: '6.1', channelCount: 7 },
  { name: '6.1 (back)', channelCount: 7 },
  { name: '6.1 (front)', channelCount: 7 },
  { name: '7.0', channelCount: 7 },
  { name: '7.0 (front)', channelCount: 7 },
  { name: '7.1', channelCount: 8 },
  { name: '7.1 (wide)', channelCount: 8 },
  { name: '7.1 (wide) (back)', channelCount: 8 },
  { name: 'Octagonal', channelCount: 8 }
]
isInput: false
isOutput: true
---------------
name: Webcam C930e Analog Stereo
id: alsa_input.usb-046d_Logitech_Webcam_C930e_1658212E-02.analog-stereo
formats: [
   2,  3, 4, 15, 16,
  11, 12, 7,  8
]
sampleRates: [ { min: 8000, max: 5644800 } ]
channelLayouts: [
  { name: 'Mono', channelCount: 1 },
  { name: 'Stereo', channelCount: 2 },
  { name: '2.1', channelCount: 3 },
  { name: '3.0', channelCount: 3 },
  { name: '3.0 (back)', channelCount: 3 },
  { name: '3.1', channelCount: 4 },
  { name: '4.0', channelCount: 4 },
  { name: 'Quad', channelCount: 4 },
  { name: 'Quad (side)', channelCount: 4 },
  { name: '4.1', channelCount: 5 },
  { name: '5.0 (back)', channelCount: 5 },
  { name: '5.0 (side)', channelCount: 5 },
  { name: '5.1', channelCount: 6 },
  { name: '5.1 (back)', channelCount: 6 },
  { name: '6.0 (side)', channelCount: 6 },
  { name: '6.0 (front)', channelCount: 6 },
  { name: 'Hexagonal', channelCount: 6 },
  { name: '6.1', channelCount: 7 },
  { name: '6.1 (back)', channelCount: 7 },
  { name: '6.1 (front)', channelCount: 7 },
  { name: '7.0', channelCount: 7 },
  { name: '7.0 (front)', channelCount: 7 },
  { name: '7.1', channelCount: 8 },
  { name: '7.1 (wide)', channelCount: 8 },
  { name: '7.1 (wide) (back)', channelCount: 8 },
  { name: 'Octagonal', channelCount: 8 }
]
isInput: true
isOutput: false
-------
default output: Built-in Audio Analog Stereo
default input: Webcam C930e Analog Stereo
API: PulseAudio

Play white noise

const { Soundio } = require('audioworklet');
const soundio = new Soundio();

let streamStatus = true;

const processFrame = (outputChannels) => {
  for (let sample = 0; sample < outputChannels[0].length; sample++) {
    outputChannels[0][sample] = Math.random();
    outputChannels[1][sample] = Math.random();
  }

  return streamStatus;
}

const device = soundio.getDefaultOutputDevice();
console.log('Opening stream');
const outputStream = device.openOutputStream({
  format: Soundio.SoundIoFormatFloat32LE,
  sampleRate: 48000,
  name: "test test",
  process: processFrame,
});

console.log('Starting stream');
outputStream.start();

setTimeout(() => {
  console.log('Stopping stream');
  // streamStatus = false;
  outputStream.close();
}, 2000);
setTimeout(() => {
  process.exit(0);
}, 3000);

Use another file as AudioWorklet

const path = require('path');
const { Soundio } = require('audioworklet');
const soundio = new Soundio();

const device = soundio.getDefaultOutputDevice();
const outputStream = device.openOutputStream();

outputStream.attachProcessFunctionFromWorker(path.resolve(__dirname, './workers/whitenoise.js'));
outputStream.start();

setTimeout(() => {
  console.log('exiting');
  process.exit(0);
}, 1000);

And in ./workers/whitenoise.js:

const {AudioWorkletProcessor} = require('../../');

class WhiteNoiseProcessor extends AudioWorkletProcessor {
  constructor() {
    super();
  }

  process(outputChannels) {
    outputChannels.forEach((channel) => {
      for (let sample = 0; sample < channel.length; sample++) {
        channel[sample] = Math.random();
      }
    })

    return true;
  }
}

module.exports = WhiteNoiseProcessor;

Communicate with an AudioWorklet

const path = require('path');
const { Soundio } = require('audioworklet');
const soundio = new Soundio();

const device = soundio.getDefaultOutputDevice();
const outputStream = device.openOutputStream();

const worklet = outputStream.attachProcessFunctionFromWorker(path.resolve(__dirname, './workers/messages.js'));
outputStream.start();

setTimeout(() => {
  console.log('Muting worklet');
  worklet.postMessage({
    mute: true,
  });
}, 1000);

setTimeout(() => {
  console.log('exiting');
  process.exit(0);
}, 2000);

And in workers/messages.js:

const {AudioWorkletProcessor} = require('audioworklet');

class WhiteNoiseProcessorWithMessage extends AudioWorkletProcessor {
  constructor() {
    super();
    this.mute = false;

    this.port.onmessage = this.handleMessage.bind(this);
  }

  handleMessage(message) {
    console.log('Receiving mute status', message.data.mute);
    this.mute = message.data.mute;
  }

  process(outputChannels) {
    if (this.mute) {
      return true;
    }
    outputChannels.forEach((channel) => {
      for (let sample = 0; sample < channel.length; sample++) {
        channel[sample] = Math.random();
      }
    })

    return true;
  }
}

module.exports = WhiteNoiseProcessorWithMessage;

The this.port property is a MessagePort and also handle passing a second argument transferList to prevent a data copy of ArrayBuffers. Look at the documentation for more information.

Development

Download libsoundio

git submodule update --init --recursive
cd vendor/libsoundio

Requirements for source build

Most regular installs will support prebuilds that are built with each release, this is required if you want to develop with.

  • Node version that support N-API 4 and up (N-API Node Version Matrix)
  • CMake
  • A proper C/C++ compiler toolchain of the given platform
    • Windows:
    • Unix/Posix:
      • Clang or GCC
      • Ninja or Make (Ninja will be picked if both present)
      • Alsa and/or Pulseaudio libs (on ubuntu, it can be installed with apt-get install -y libasound2-dev libpulse-dev)

Legal

This project is licensed under the MIT license.

It is based on the Audify project from Almogh52 also released under MIT license.

Generated using TypeDoc