Skip to content

Commit 4db36c1

Browse files
authored
[AUDIO_WORKLET] Add new emscripten_audio_node_connect API (#22667)
In all the examples and in the docs, connecting the context and worklet is done with the following type of code: ``` EM_ASM({ emscriptenGetAudioObject($0).connect(emscriptenGetAudioObject($1).destination) }, wasmAudioWorklet, audioContext); ``` Every example or usage needs this to play audio. Granted, more complex graphs will add more node types and `emscriptenGetAudioObject()` will have its uses, but for the general use case a simpler call without JS is preferred: ``` emscripten_audio_worklet_node_connect(audioContext, wasmAudioWorklet); ``` See here for how it's done in the examples: https://github.com/emscripten-core/emscripten/blob/7df48f680e2e901b9bf1f5312d27ca939753548d/test/webaudio/audioworklet.c#L50 I updated the examples and the documentation. @juj Comments or ideas?
1 parent 48a0575 commit 4db36c1

File tree

7 files changed

+34
-20
lines changed

7 files changed

+34
-20
lines changed

site/source/docs/api_reference/wasm_audio_worklets.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ which resumes the audio context when the user clicks on the DOM Canvas element t
126126
"noise-generator", &options, &GenerateNoise, 0);
127127
128128
// Connect it to audio context destination
129-
EM_ASM({emscriptenGetAudioObject($0).connect(emscriptenGetAudioObject($1).destination)},
130-
wasmAudioWorklet, audioContext);
129+
emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0);
131130
132131
// Resume context on mouse click
133132
emscripten_set_click_callback("canvas", (void*)audioContext, 0, OnCanvasClick);

src/library_sigs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ sigs = {
587587
emscripten_atomic_wait_async__sig: 'ipippd',
588588
emscripten_atomics_is_lock_free__sig: 'ii',
589589
emscripten_audio_context_state__sig: 'ii',
590+
emscripten_audio_node_connect__sig: 'viiii',
590591
emscripten_audio_worklet_post_function_sig__sig: 'vippp',
591592
emscripten_audio_worklet_post_function_v__sig: 'vip',
592593
emscripten_audio_worklet_post_function_vd__sig: 'vipd',

src/library_webaudio.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,21 @@ let LibraryWebAudio = {
293293
},
294294
#endif // ~AUDIO_WORKLET
295295

296+
emscripten_audio_node_connect: (source, destination, outputIndex, inputIndex) => {
297+
var srcNode = EmAudio[source];
298+
var dstNode = EmAudio[destination];
299+
#if ASSERTIONS
300+
assert(srcNode, `Called emscripten_audio_node_connect() with an invalid AudioNode handle ${source}`);
301+
assert(srcNode instanceof window.AudioNode, `Called emscripten_audio_node_connect() on handle ${source} that is not an AudiotNode, but of type ${srcNode}`);
302+
assert(dstNode, `Called emscripten_audio_node_connect() with an invalid AudioNode handle ${destination}!`);
303+
assert(dstNode instanceof (window.AudioContext || window.webkitAudioContext) || dstNode instanceof window.AudioNode, `Called emscripten_audio_node_connect() on handle ${destination} that is not an AudioContext or AudioNode, but of type ${dstNode}`);
304+
#endif
305+
#if WEBAUDIO_DEBUG
306+
console.log(`Connecting audio node ID ${source} to audio node ID ${destination} (${srcNode} to ${dstNode})`);
307+
#endif
308+
srcNode.connect(dstNode.destination || dstNode, outputIndex, inputIndex);
309+
},
310+
296311
emscripten_current_thread_is_audio_worklet: () => typeof AudioWorkletGlobalScope !== 'undefined',
297312

298313
emscripten_audio_worklet_post_function_v: (audioContext, funcPtr) => {

system/include/emscripten/webaudio.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ typedef struct EmscriptenAudioWorkletNodeCreateOptions
129129
// userData4: A custom userdata pointer to pass to the callback function. This value will be passed on to the call to the given EmscriptenWorkletNodeProcessCallback callback function.
130130
EMSCRIPTEN_AUDIO_WORKLET_NODE_T emscripten_create_wasm_audio_worklet_node(EMSCRIPTEN_WEBAUDIO_T audioContext, const char *name, const EmscriptenAudioWorkletNodeCreateOptions *options, EmscriptenWorkletNodeProcessCallback processCallback, void *userData4);
131131

132+
// Connects a node's output to a target, e.g., connect the worklet node to the context.
133+
// For outputIndex and inputIndex, see the AudioNode.connect() documentation (setting 0 as the default values)
134+
void emscripten_audio_node_connect(EMSCRIPTEN_WEBAUDIO_T source, EMSCRIPTEN_WEBAUDIO_T destination, int outputIndex, int inputIndex);
135+
132136
// Returns true if the current thread is executing a Wasm AudioWorklet, false otherwise.
133137
// Note that calling this function can be relatively slow as it incurs a Wasm->JS transition,
134138
// so avoid calling it in hot paths.

test/webaudio/audio_worklet_tone_generator.c

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,23 @@ void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool succe
8282
// Instantiate the noise-generator Audio Worklet Processor.
8383
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "tone-generator", &options, &ProcessAudio, 0);
8484

85+
// Connect the audio worklet node to the graph.
86+
emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0);
8587
EM_ASM({
86-
let audioContext = emscriptenGetAudioObject($0);
87-
let audioWorkletNode = emscriptenGetAudioObject($1);
88-
// Connect the audio worklet node to the graph.
89-
audioWorkletNode.connect(audioContext.destination);
90-
9188
// Add a button on the page to toggle playback as a response to user click.
9289
let startButton = document.createElement('button');
9390
startButton.innerHTML = 'Toggle playback';
9491
document.body.appendChild(startButton);
9592

93+
let audioContext = emscriptenGetAudioObject($0);
9694
startButton.onclick = () => {
9795
if (audioContext.state != 'running') {
9896
audioContext.resume();
9997
} else {
10098
audioContext.suspend();
10199
}
102100
};
103-
}, audioContext, wasmAudioWorklet);
101+
}, audioContext);
104102

105103
#ifdef REPORT_RESULT
106104
emscripten_set_timeout_loop(observe_test_end, 10, 0);

test/webaudio/audioworklet.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,13 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs,
4747
return true;
4848
}
4949

50-
EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext, EMSCRIPTEN_AUDIO_WORKLET_NODE_T audioWorkletNode), {
51-
audioContext = emscriptenGetAudioObject(audioContext);
52-
audioWorkletNode = emscriptenGetAudioObject(audioWorkletNode);
53-
// Connect the audio worklet node to the graph.
54-
audioWorkletNode.connect(audioContext.destination);
55-
50+
EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), {
5651
// Add a button on the page to toggle playback as a response to user click.
5752
let startButton = document.createElement('button');
5853
startButton.innerHTML = 'Toggle playback';
5954
document.body.appendChild(startButton);
6055

56+
audioContext = emscriptenGetAudioObject(audioContext);
6157
startButton.onclick = () => {
6258
if (audioContext.state != 'running') {
6359
audioContext.resume();
@@ -98,12 +94,14 @@ void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool succe
9894

9995
// Instantiate the noise-generator Audio Worklet Processor.
10096
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, 0);
97+
// Connect the audio worklet node to the graph.
98+
emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0);
10199

102100
#ifdef REPORT_RESULT
103101
emscripten_set_timeout_loop(main_thread_tls_access, 10, 0);
104102
#endif
105103

106-
InitHtmlUi(audioContext, wasmAudioWorklet);
104+
InitHtmlUi(audioContext);
107105
}
108106

109107
// This callback will fire when the Wasm Module has been shared to the

test/webaudio/audioworklet_emscripten_futex_wake.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ bool ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs,
2626
return false;
2727
}
2828

29-
EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext, EMSCRIPTEN_AUDIO_WORKLET_NODE_T audioWorkletNode), {
30-
audioContext = emscriptenGetAudioObject(audioContext);
31-
audioWorkletNode = emscriptenGetAudioObject(audioWorkletNode);
32-
audioWorkletNode.connect(audioContext.destination);
29+
EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext), {
3330
let startButton = document.createElement('button');
3431
startButton.innerHTML = 'Start playback';
3532
document.body.appendChild(startButton);
3633

34+
audioContext = emscriptenGetAudioObject(audioContext);
3735
startButton.onclick = () => {
3836
audioContext.resume();
3937
};
@@ -54,7 +52,8 @@ void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool succe
5452
int outputChannelCounts[1] = { 1 };
5553
EmscriptenAudioWorkletNodeCreateOptions options = { .numberOfInputs = 0, .numberOfOutputs = 1, .outputChannelCounts = outputChannelCounts };
5654
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, 0);
57-
InitHtmlUi(audioContext, wasmAudioWorklet);
55+
emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0);
56+
InitHtmlUi(audioContext);
5857
}
5958

6059
void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) {

0 commit comments

Comments
 (0)