1 of 43

AudioWorklet

:: What, Why and How

Hongchan Choi | Google Chrome

2 of 43

“What is AudioWorklet?”

3 of 43

“What is Worklet?”

"...API for running scripts in stages of the rendering pipeline

independent of the main javascript execution environment..."

4 of 43

“What is Worklet?”

"...API for running scripts in stages of the rendering pipeline

independent of the main javascript execution environment..."

5 of 43

Main Execution Context

Rendering Pipeline

worklet.addModule('script.js')

script.js

"Main Global Scope"

"Main Thread"

"Worklet Global Scope"

"Rendering Thread"

module

loading

6 of 43

Worklet

PaintWorklet

AudioWorklet

AnimationWorklet

Web Audio API

(WebAudio render thread)

CSS Paint API�(Main thread)

CSS Animation Worklet API

(Compositor thread)

7 of 43

“What is AudioWorklet?”

"...exposing the low-level audio processing capability�to web developers within Web Audio API's ecosystem."

8 of 43

"What ScriptProcessor?”

9 of 43

ScriptProcessorNode : PROBLEMS

Underspecified.

Double buffering.

Access to DOM.

Runs on main thread.

Data race: glitch, drop-out, stuttering

10 of 43

“Well, is it that bad?”

11 of 43

BiquadFilter

Node

Gain�Node

Destination

Node

BufferSource

Node

Main Thread�(Global Scope)

12 of 43

BiquadFilter

Node

Gain�Node

Destination

Node

BufferSource

Node

Main Thread

WebAudio Render Thread

BiquadFilter

Internal

Gain

Internal

Destination

Internal

BufferSource

Internal

13 of 43

ScriptProcessor

Node

Destination

Node

BufferSource

Node

Main Thread

WebAudio Render Thread

ScriptProcessor

Internal

Destination

Internal

BufferSource

Internal

processor

onaudioprocess

(script code)

async event handling

14 of 43

“Well, how is AWN better?”

15 of 43

AudioWorklet�Node

Destination

Node

BufferSource

Node

Main Thread

WebAudio Render Thread

AudioWorklet�Internal

Destination

Internal

BufferSource

Internal

AudioWorklet�Processor

(script code)

JS

Engine

(e.g. V8)

16 of 43

AudioWorklet : BENEFITS

Well-defined.

JS code as a first class citizen.

Native support on AudioParam.

Runs on WebAudio render thread.

Extensibility.

and so on...

17 of 43

“Okay, how do I use it?”

18 of 43

AudioWorklet

AudioWorkletGlobalScope

AudioWorkletNode

AudioWorkletProcessor

19 of 43

audioWorklet.addModule('bypass.js').then(() => {

let context = new AudioContext();

let oscillatorNode =

new OscillatorNode(context);

let bypassNode =

new AudioWorkletNode(context, 'bypass');

oscillatorNode

.connect(bypassNode)

.connect(context.destination);

});

class Bypass extends AudioWorkletProcessor {

constructor() {

super();

}

process(inputs, outputs) {

/** process audio **/

}

}

registerProcessor('bypass', Bypass);

"bypass.js"

Main Global Scope

AudioWorkletGlobalScope

20 of 43

process(inputs, outputs) {

let input = inputs[0];

let output = outputs[0];

for (let channel = 0; channel < input.length; ++channel) {

output[channel].set(input[channel]);

}

return true;

}

Bypass.process()

AudioWorkletGlobalScope

sequence<sequence<Float32Array>>

21 of 43

AudioWorklet�Processor

Input #0�4 Channels

Input #1�3 Channels

Output #0�2 Channels

Output #1�3 Channels

inputs[0][2]

outputs[1][0]

inputs

outputs

Input #2�1 Channel

22 of 43

AudioParam

AudioWorklet

23 of 43

audioWorklet.addModule('bypass.js').then(() => {

let context = new AudioContext();

let bypassNode =

new AudioWorkletNode(context, 'bypass');

let gainParam =

bypassNode.parameters.get('gain');

gainParam.value = 1.0;

gainParam.linearRampToValue(0.0, 5.0);

});

class Bypass extends AudioWorkletProcessor {

static get parameterDescriptors() {

return [{

name: 'gain',

defaultValue: 0.0

}];

}

constructor() {...}

process(inputs, outputs, parameters) {...}

}

registerProcessor('bypass', Bypass);

"bypass.js"

Main Global Scope

AudioWorkletGlobalScope

24 of 43

AudioWorklet�Processor

"gain" parameter�values

inputs

outputs

parameters

AudioParam�Processing

(e.g. automation)

25 of 43

process(inputs, outputs, parameters) {

let input = inputs[0];

let output = outputs[0];

let gainValues = parameters['gain'];

for (let channel = 0; channel < input.length; ++channel) {

let inputChannel = input[channel];

let outputChannel = output[channel];

for (let i = 0; i < inputChannel.length; ++i)

outputChannel[i] = inputChannel[i] * gainValues[i];

}

return true;

}

Bypass.process()

AudioWorkletGlobalScope

26 of 43

MessagePort

AudioWorklet

27 of 43

AudioWorklet�Node

AudioWorklet

Processor

.port

.port

<examples>

myCompressor.type = 'hardknee';

mySource.startNote(time);

<examples>

this.sendFFTResult();

this.onended();

28 of 43

audioWorklet.addModule('dummy.js').then(() => {

let context = new AudioContext();

let dummyNode =

new AudioWorkletNode(context, 'dummy');

dummyNode.port.onmessage = (event) => {

console.log(event.data);

dummyNode.port.postMessage('backatya!');

};

});

class Dummy extends AudioWorkletProcessor {

constructor() {

super();

this.port.postMessage('ready!');

this.port.onmessage = (event) => {

console.log(event.data);

};

}

process(inputs, outputs, parameters) {...}

}

registerProcessor('dummy', Dummy);

"dummy.js"

Main Global Scope

AudioWorkletGlobalScope

29 of 43

(real) CurrentTime

AudioWorklet

30 of 43

audioWorklet.addModule('dummy.js').then(() => {

let context = new AudioContext();

let dummyNode =

new AudioWorkletNode(context, 'dummy');

});

class Dummy extends AudioWorkletProcessor {

constructor() {

super();

this.createdAt_ = currentTime;

}

process(inputs, outputs, parameters) {

if (currentTime - this.createdAt_ > 5.0) {

/* do something here */

}

}

}

registerProcessor('dummy', Dummy);

"dummy.js"

Main Global Scope

AudioWorkletGlobalScope

31 of 43

“Sure, can I use it now?”

32 of 43

Available

in Chrome 66

33 of 43

Origin Trial

in Chrome 64, 65

34 of 43

1. Sign Up for Origin Trial.

For Chrome 64 and 65

35 of 43

$CHROME_CANARY_PATH --enable-blink-features=AudioWorklet

2. Use Command Line Option.

(recommended)

For Chrome 64 and 65

36 of 43

3. Use "Flags" setting.

Enable "Experimental Web Platform Features" and

re-launch the browser.

(Not recommended)

For Chrome 64 and 65

37 of 43

38 of 43

“Where do we go from here?”

39 of 43

Possibilities

WASM

Shared array buffer (disabled due to spectre/meltdown)

AudioWorklet module distribution

Dynamic code generation

and so on...

40 of 43

Questions?

@hochsays | github.com/hoch

41 of 43

Backup Material

For more WebAudio fun!

42 of 43

processor

processor

onaudioprocess

(script code)

1

2

1

2

onaudioprocess

(script code)

write

read

write

read

dispatch

event

dispatch

event

ScriptProcessorNode : Data Race

43 of 43

processor

processor

onaudioprocess�(script code)

1

2

write

read

dispatch

event

1

2

read

still writing?

ScriptProcessorNode : Data Race

!