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.
npm install --save audioworklet
Or
yarn add audioworklet
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
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);
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;
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.
git submodule update --init --recursive
cd vendor/libsoundio
Most regular installs will support prebuilds that are built with each release, this is required if you want to develop with.
apt-get install -y libasound2-dev libpulse-dev
)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