Skip to content

Commit bcad96d

Browse files
authored
[AUDIO_WORKLET] Add multichannel audio tests. NFC (#23394)
These are the audio worklet tests from #22753 extracted to a standalone PR. The tests are: - Multiple stereo inputs mixing in the processor to a single stereo output - Multiple stereo inputs copying in the processor to multiple stereo outputs - Multiple mono inputs mixing in the processor to a single mono output - Multiple mono inputs copying in the processor to L+R stereo outputs The tests use different stack sizes (from 2kB to 6kB depending on the requirement). The audio tracks were composed by Tim Wright especially for Emscripten and released under a CC0 license.
1 parent 6bed395 commit bcad96d

12 files changed

+446
-0
lines changed

test/test_browser.py

+12
Original file line numberDiff line numberDiff line change
@@ -5454,6 +5454,18 @@ def test_audio_worklet_post_function(self, args):
54545454
def test_audio_worklet_modularize(self, args):
54555455
self.btest_exit('webaudio/audioworklet.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-sMODULARIZE=1', '-sEXPORT_NAME=MyModule', '--shell-file', test_file('shell_that_launches_modularize.html')] + args)
54565456

5457+
# Tests multiple inputs, forcing a larger stack (note: passing BROWSER_TEST is
5458+
# specific to this test to allow it to exit rather than play forever).
5459+
@parameterized({
5460+
'': ([],),
5461+
'minimal_with_closure': (['-sMINIMAL_RUNTIME', '--closure=1', '-Oz'],),
5462+
})
5463+
def test_audio_worklet_stereo_io(self, args):
5464+
os.mkdir('audio_files')
5465+
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
5466+
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
5467+
self.btest_exit('webaudio/audioworklet_in_out_stereo.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-DBROWSER_TEST'] + args)
5468+
54575469
def test_error_reporting(self):
54585470
# Test catching/reporting Error objects
54595471
create_file('post.js', 'throw new Error("oops");')

test/test_interactive.py

+28
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,34 @@ def test_audio_worklet_tone_generator(self):
306306
def test_audio_worklet_modularize(self):
307307
self.btest('webaudio/audioworklet.c', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-sMINIMAL_RUNTIME', '-sMODULARIZE'])
308308

309+
# Tests an AudioWorklet with multiple stereo inputs mixing in the processor to a single stereo output (4kB stack)
310+
def test_audio_worklet_stereo_io(self):
311+
os.mkdir('audio_files')
312+
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
313+
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
314+
self.btest_exit('webaudio/audioworklet_in_out_stereo.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
315+
316+
# Tests an AudioWorklet with multiple stereo inputs copying in the processor to multiple stereo outputs (6kB stack)
317+
def test_audio_worklet_2x_stereo_io(self):
318+
os.mkdir('audio_files')
319+
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
320+
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
321+
self.btest_exit('webaudio/audioworklet_2x_in_out_stereo.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
322+
323+
# Tests an AudioWorklet with multiple mono inputs mixing in the processor to a single mono output (2kB stack)
324+
def test_audio_worklet_mono_io(self):
325+
os.mkdir('audio_files')
326+
shutil.copy(test_file('webaudio/audio_files/emscripten-beat-mono.mp3'), 'audio_files/')
327+
shutil.copy(test_file('webaudio/audio_files/emscripten-bass-mono.mp3'), 'audio_files/')
328+
self.btest_exit('webaudio/audioworklet_in_out_mono.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
329+
330+
# Tests an AudioWorklet with multiple mono inputs copying in the processor to L+R stereo outputs (3kB stack)
331+
def test_audio_worklet_2x_hard_pan_io(self):
332+
os.mkdir('audio_files')
333+
shutil.copy(test_file('webaudio/audio_files/emscripten-beat-mono.mp3'), 'audio_files/')
334+
shutil.copy(test_file('webaudio/audio_files/emscripten-bass-mono.mp3'), 'audio_files/')
335+
self.btest_exit('webaudio/audioworklet_2x_in_hard_pan.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
336+
309337

310338
class interactive64(interactive):
311339
def setUp(self):

test/webaudio/audio_files/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Emscripten Beat and Emscripten Bass by [CoLD SToRAGE](https://www.coldstorage.org.uk) (Tim Wright).
2+
3+
Released under the [Creative Commons Zero (CC0)](https://creativecommons.org/publicdomain/zero/1.0/) Public Domain Dedication.
4+
5+
To the extent possible under law, OGP Phonogramatica has waived all copyright and related or neighbouring rights to these works.
Binary file not shown.
258 KB
Binary file not shown.
Binary file not shown.
246 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <assert.h>
2+
#include <string.h>
3+
#include <stdio.h>
4+
5+
#include <emscripten/em_js.h>
6+
#include <emscripten/webaudio.h>
7+
8+
// Tests two mono audio inputs being copied to the left and right channels of a
9+
// single stereo output (with a hard pan).
10+
11+
// This needs to be big enough for the stereo output, 2x mono inputs and the worker stack
12+
#define AUDIO_STACK_SIZE 3072
13+
14+
// Shared file playback and bootstrap
15+
#include "audioworklet_test_shared.inc"
16+
17+
// Callback to process and copy the audio tracks
18+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
19+
audioProcessedCount++;
20+
21+
// Twin mono in, single stereo out
22+
assert(numInputs == 2 && numOutputs == 1);
23+
assert(inputs[0].numberOfChannels == 1 && inputs[1].numberOfChannels == 1);
24+
assert(outputs[0].numberOfChannels == 2);
25+
// All with the same number of samples
26+
assert(inputs[0].samplesPerChannel == inputs[1].samplesPerChannel);
27+
assert(inputs[0].samplesPerChannel == outputs[0].samplesPerChannel);
28+
// Now with all known quantities we can memcpy the data
29+
int samplesPerChannel = inputs[0].samplesPerChannel;
30+
memcpy(outputs[0].data, inputs[0].data, samplesPerChannel * sizeof(float));
31+
memcpy(outputs[0].data + samplesPerChannel, inputs[1].data, samplesPerChannel * sizeof(float));
32+
return true;
33+
}
34+
35+
// Audio processor created, now register the audio callback
36+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
37+
if (!success) {
38+
printf("Audio worklet node creation failed\n");
39+
return;
40+
}
41+
printf("Audio worklet processor created\n");
42+
printf("Click to toggle audio playback\n");
43+
44+
// Stereo output, two inputs
45+
int outputChannelCounts[2] = { 2 };
46+
EmscriptenAudioWorkletNodeCreateOptions opts = {
47+
.numberOfInputs = 2,
48+
.numberOfOutputs = 1,
49+
.outputChannelCounts = outputChannelCounts
50+
};
51+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
52+
emscripten_audio_node_connect(worklet, context, 0, 0);
53+
54+
// Create the two mono source nodes and connect them to the two inputs
55+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
56+
beatID = createTrack(context, "audio_files/emscripten-beat-mono.mp3", true);
57+
if (beatID) {
58+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
59+
}
60+
bassID = createTrack(context, "audio_files/emscripten-bass-mono.mp3", true);
61+
if (bassID) {
62+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
63+
}
64+
65+
// Register a click to start playback
66+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);
67+
68+
// Register the counter that exits the test after one second of mixing
69+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include <assert.h>
2+
#include <string.h>
3+
#include <stdio.h>
4+
5+
#include <emscripten/em_js.h>
6+
#include <emscripten/webaudio.h>
7+
8+
// Tests two stereo audio inputs being copied to two stereo outputs.
9+
10+
// This needs to be big enough for the 2x stereo outputs, 2x inputs and the worker stack
11+
#define AUDIO_STACK_SIZE 6144
12+
13+
// Shared file playback and bootstrap
14+
#include "audioworklet_test_shared.inc"
15+
16+
// Callback to process and copy the audio tracks
17+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
18+
audioProcessedCount++;
19+
20+
// Twin stereo in and out
21+
assert(numInputs == 2 && numOutputs == 2);
22+
assert(inputs[0].numberOfChannels == 2 && inputs[1].numberOfChannels == 2);
23+
assert(outputs[0].numberOfChannels == 2 && outputs[1].numberOfChannels == 2);
24+
// All with the same number of samples
25+
assert(inputs[0].samplesPerChannel == inputs[1].samplesPerChannel);
26+
assert(inputs[0].samplesPerChannel == outputs[0].samplesPerChannel);
27+
assert(outputs[0].samplesPerChannel == outputs[1].samplesPerChannel);
28+
// Now with all known quantities we can memcpy the data
29+
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
30+
memcpy(outputs[0].data, inputs[0].data, totalSamples * sizeof(float));
31+
memcpy(outputs[1].data, inputs[1].data, totalSamples * sizeof(float));
32+
return true;
33+
}
34+
35+
// Audio processor created, now register the audio callback
36+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
37+
if (!success) {
38+
printf("Audio worklet node creation failed\n");
39+
return;
40+
}
41+
printf("Audio worklet processor created\n");
42+
printf("Click to toggle audio playback\n");
43+
44+
// Two stereo outputs, two inputs
45+
int outputChannelCounts[2] = { 2, 2 };
46+
EmscriptenAudioWorkletNodeCreateOptions opts = {
47+
.numberOfInputs = 2,
48+
.numberOfOutputs = 2,
49+
.outputChannelCounts = outputChannelCounts
50+
};
51+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
52+
// Both outputs connected to the context
53+
emscripten_audio_node_connect(worklet, context, 0, 0);
54+
emscripten_audio_node_connect(worklet, context, 1, 0);
55+
56+
// Create the two stereo source nodes and connect them to the two inputs
57+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
58+
beatID = createTrack(context, "audio_files/emscripten-beat.mp3", true);
59+
if (beatID) {
60+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
61+
}
62+
bassID = createTrack(context, "audio_files/emscripten-bass.mp3", true);
63+
if (bassID) {
64+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
65+
}
66+
67+
// Register a click to start playback
68+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);
69+
70+
// Register the counter that exits the test after one second of mixing
71+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
72+
}
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <assert.h>
2+
#include <string.h>
3+
#include <stdio.h>
4+
5+
#include <emscripten/em_js.h>
6+
#include <emscripten/webaudio.h>
7+
8+
// Tests processing two mono audio inputs being mixed to a single mono audio
9+
// output in process() (by adding the inputs together).
10+
11+
// This needs to be big enough for the mono output, 2x inputs and the worker stack
12+
#define AUDIO_STACK_SIZE 2048
13+
14+
// Shared file playback and bootstrap
15+
#include "audioworklet_test_shared.inc"
16+
17+
// Callback to process and mix the audio tracks
18+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
19+
audioProcessedCount++;
20+
21+
// Single mono output
22+
assert(numOutputs == 1 && outputs[0].numberOfChannels == 1);
23+
for (int n = 0; n < numInputs; n++) {
24+
// And all inputs are also stereo
25+
assert(inputs[n].numberOfChannels == 1 || inputs[n].numberOfChannels == 0);
26+
// This should always be the case
27+
assert(inputs[n].samplesPerChannel == outputs[0].samplesPerChannel);
28+
}
29+
// We can now do a quick mix since we know the layouts
30+
if (numInputs > 0) {
31+
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
32+
float* outputData = outputs[0].data;
33+
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
34+
for (int n = 1; n < numInputs; n++) {
35+
// It's possible to have an input with no channels
36+
if (inputs[n].numberOfChannels == 1) {
37+
float* inputData = inputs[n].data;
38+
for (int i = totalSamples - 1; i >= 0; i--) {
39+
outputData[i] += inputData[i];
40+
}
41+
}
42+
}
43+
}
44+
return true;
45+
}
46+
47+
// Audio processor created, now register the audio callback
48+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
49+
if (!success) {
50+
printf("Audio worklet node creation failed\n");
51+
return;
52+
}
53+
printf("Audio worklet processor created\n");
54+
printf("Click to toggle audio playback\n");
55+
56+
// Mono output, two inputs
57+
int outputChannelCounts[1] = { 1 };
58+
EmscriptenAudioWorkletNodeCreateOptions opts = {
59+
.numberOfInputs = 2,
60+
.numberOfOutputs = 1,
61+
.outputChannelCounts = outputChannelCounts
62+
};
63+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
64+
emscripten_audio_node_connect(worklet, context, 0, 0);
65+
66+
// Create the two mono source nodes and connect them to the two inputs
67+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
68+
beatID = createTrack(context, "audio_files/emscripten-beat-mono.mp3", true);
69+
if (beatID) {
70+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
71+
}
72+
bassID = createTrack(context, "audio_files/emscripten-bass-mono.mp3", true);
73+
if (bassID) {
74+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
75+
}
76+
77+
// Register a click to start playback
78+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);
79+
80+
// Register the counter that exits the test after one second of mixing
81+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
82+
}
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <assert.h>
2+
#include <string.h>
3+
#include <stdio.h>
4+
5+
#include <emscripten/em_js.h>
6+
#include <emscripten/webaudio.h>
7+
8+
// Tests processing two stereo audio inputs being mixed to a single stereo audio
9+
// output in process() (by adding the inputs together).
10+
11+
// This needs to be big enough for the stereo output, 2x inputs and the worker stack
12+
#define AUDIO_STACK_SIZE 4096
13+
14+
// Shared file playback and bootstrap
15+
#include "audioworklet_test_shared.inc"
16+
17+
// Callback to process and mix the audio tracks
18+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
19+
audioProcessedCount++;
20+
21+
// Single stereo output
22+
assert(numOutputs == 1 && outputs[0].numberOfChannels == 2);
23+
for (int n = 0; n < numInputs; n++) {
24+
// And all inputs are also stereo
25+
assert(inputs[n].numberOfChannels == 2 || inputs[n].numberOfChannels == 0);
26+
// This should always be the case
27+
assert(inputs[n].samplesPerChannel == outputs[0].samplesPerChannel);
28+
}
29+
// We can now do a quick mix since we know the layouts
30+
if (numInputs > 0) {
31+
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
32+
float* outputData = outputs[0].data;
33+
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
34+
for (int n = 1; n < numInputs; n++) {
35+
// It's possible to have an input with no channels
36+
if (inputs[n].numberOfChannels == 2) {
37+
float* inputData = inputs[n].data;
38+
for (int i = totalSamples - 1; i >= 0; i--) {
39+
outputData[i] += inputData[i];
40+
}
41+
}
42+
}
43+
}
44+
return true;
45+
}
46+
47+
// Audio processor created, now register the audio callback
48+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
49+
if (!success) {
50+
printf("Audio worklet node creation failed\n");
51+
return;
52+
}
53+
printf("Audio worklet processor created\n");
54+
printf("Click to toggle audio playback\n");
55+
56+
// Stereo output, two inputs
57+
int outputChannelCounts[1] = { 2 };
58+
EmscriptenAudioWorkletNodeCreateOptions opts = {
59+
.numberOfInputs = 2,
60+
.numberOfOutputs = 1,
61+
.outputChannelCounts = outputChannelCounts
62+
};
63+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
64+
emscripten_audio_node_connect(worklet, context, 0, 0);
65+
66+
// Create the two stereo source nodes and connect them to the two inputs
67+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
68+
beatID = createTrack(context, "audio_files/emscripten-beat.mp3", true);
69+
if (beatID) {
70+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
71+
}
72+
bassID = createTrack(context, "audio_files/emscripten-bass.mp3", true);
73+
if (bassID) {
74+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
75+
}
76+
77+
// Register a click to start playback
78+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);
79+
80+
// Register the counter that exits the test after one second of mixing
81+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
82+
}

0 commit comments

Comments
 (0)