Skip to content

Commit 56676a4

Browse files
committed
Added the mono tests
1 parent 392fede commit 56676a4

File tree

4 files changed

+328
-4
lines changed

4 files changed

+328
-4
lines changed

test/test_interactive.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,20 +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
309+
# Tests an AudioWorklet with multiple stereo inputs mixing in the processor to a single stereo output (4kB stack)
310310
def test_audio_worklet_stereo_io(self):
311311
os.mkdir('audio_files')
312312
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
313313
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
314314
self.btest('webaudio/audioworklet_in_out_stereo.c', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
315315

316-
# Tests an AudioWorklet with multiple stereo inputs copying in the processor to multiple stereo outputs
317-
def test_audio_worklet_2x_stereo_io(self):
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):
318318
os.mkdir('audio_files')
319319
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
320320
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
321321
self.btest('webaudio/audioworklet_2x_in_out_stereo.c', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
322322

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('webaudio/audioworklet_in_out_mono.c', expected='0', 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('webaudio/audioworklet_2x_in_hard_pan.c', expected='0', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])
336+
323337

324338
class interactive64(interactive):
325339
def setUp(self):
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
// REPORT_RESULT is defined when running in Emscripten test harness.
15+
#ifdef REPORT_RESULT
16+
// Count the mixed frames and return after 375 frames (1 second with the default 128 size)
17+
volatile int audioProcessedCount = 0;
18+
bool playedAndMixed(double time, void* data) {
19+
if (audioProcessedCount >= 375) {
20+
REPORT_RESULT(0);
21+
return false;
22+
}
23+
return true;
24+
}
25+
#endif
26+
27+
// ID to the beat and bass loops
28+
EMSCRIPTEN_WEBAUDIO_T beatID = 0;
29+
EMSCRIPTEN_WEBAUDIO_T bassID = 0;
30+
31+
// Creates a MediaElementAudioSourceNode with the supplied URL (which is
32+
// registered as an internal audio object and the ID returned).
33+
EM_JS(EMSCRIPTEN_WEBAUDIO_T, createTrack, (EMSCRIPTEN_WEBAUDIO_T ctxID, const char* url, bool looping), {
34+
var context = emscriptenGetAudioObject(ctxID);
35+
if (context) {
36+
var audio = document.createElement('audio');
37+
audio.src = UTF8ToString(url);
38+
audio.loop = looping;
39+
var track = context.createMediaElementSource(audio);
40+
return emscriptenRegisterAudioObject(track);
41+
}
42+
return 0;
43+
});
44+
45+
// Toggles the play/pause of a MediaElementAudioSourceNode given its ID
46+
EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcID), {
47+
var source = emscriptenGetAudioObject(srcID);
48+
if (source) {
49+
var audio = source.mediaElement;
50+
if (audio) {
51+
if (audio.paused) {
52+
audio.currentTime = 0;
53+
audio.play();
54+
} else {
55+
audio.pause();
56+
}
57+
}
58+
}
59+
});
60+
61+
// Callback to process and copy the audio tracks
62+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
63+
#ifdef REPORT_RESULT
64+
audioProcessedCount++;
65+
#endif
66+
// Twin mono in, single stereo out
67+
assert(numInputs == 2 && numOutputs == 1);
68+
assert(inputs[0].numberOfChannels == 1 && inputs[1].numberOfChannels == 1);
69+
assert(outputs[0].numberOfChannels == 2);
70+
// All with the same number of samples
71+
assert(inputs[0].samplesPerChannel == inputs[1].samplesPerChannel);
72+
assert(inputs[0].samplesPerChannel == outputs[0].samplesPerChannel);
73+
// Now with all known quantities we can memcpy the data
74+
int samplesPerChannel = inputs[0].samplesPerChannel;
75+
memcpy(outputs[0].data, inputs[0].data, samplesPerChannel * sizeof(float));
76+
memcpy(outputs[0].data + samplesPerChannel, inputs[1].data, samplesPerChannel * sizeof(float));
77+
return true;
78+
}
79+
80+
// Registered click even to (1) enable audio playback and (2) toggle playing the tracks
81+
bool onClick(int type, const EmscriptenMouseEvent* e, void* data) {
82+
EMSCRIPTEN_WEBAUDIO_T ctx = (EMSCRIPTEN_WEBAUDIO_T) (data);
83+
if (emscripten_audio_context_state(ctx) != AUDIO_CONTEXT_STATE_RUNNING) {
84+
printf("Resuming playback\n");
85+
emscripten_resume_audio_context_sync(ctx);
86+
}
87+
printf("Toggling audio playback\n");
88+
toggleTrack(beatID);
89+
toggleTrack(bassID);
90+
return false;
91+
}
92+
93+
// Audio processor created, now register the audio callback
94+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
95+
if (success) {
96+
printf("Audio worklet processor created\n");
97+
printf("Click to toggle audio playback\n");
98+
99+
// Stereo output, two inputs
100+
int outputChannelCounts[2] = { 2 };
101+
EmscriptenAudioWorkletNodeCreateOptions opts = {
102+
.numberOfInputs = 2,
103+
.numberOfOutputs = 1,
104+
.outputChannelCounts = outputChannelCounts
105+
};
106+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
107+
emscripten_audio_node_connect(worklet, context, 0, 0);
108+
109+
// Create the two mono source nodes and connect them to the two inputs
110+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
111+
beatID = createTrack(context, "audio_files/emscripten-beat-mono.mp3", true);
112+
if (beatID) {
113+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
114+
}
115+
bassID = createTrack(context, "audio_files/emscripten-bass-mono.mp3", true);
116+
if (bassID) {
117+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
118+
}
119+
120+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, (void*) (context), false, &onClick);
121+
122+
#ifdef REPORT_RESULT
123+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
124+
#endif
125+
} else {
126+
printf("Audio worklet node creation failed\n");
127+
}
128+
}
129+
130+
// Worklet thread inited, now create the audio processor
131+
void initialised(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
132+
if (success) {
133+
printf("Audio worklet initialised\n");
134+
135+
WebAudioWorkletProcessorCreateOptions opts = {
136+
.name = "mixer",
137+
};
138+
emscripten_create_wasm_audio_worklet_processor_async(context, &opts, &processorCreated, NULL);
139+
} else {
140+
printf("Audio worklet failed to initialise\n");
141+
}
142+
}
143+
144+
int main() {
145+
static char workletStack[AUDIO_STACK_SIZE];
146+
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL);
147+
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, sizeof workletStack, &initialised, NULL);
148+
return 0;
149+
}

test/webaudio/audioworklet_2x_in_out_stereo.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <emscripten/em_js.h>
66
#include <emscripten/webaudio.h>
77

8-
// Tests processing two stereo audio inputs being copied to two stereo outputs.
8+
// Tests two stereo audio inputs being copied to two stereo outputs.
99

1010
// This needs to be big enough for the 2x stereo outputs, 2x inputs and the worker stack
1111
#define AUDIO_STACK_SIZE 6144
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
// REPORT_RESULT is defined when running in Emscripten test harness.
15+
#ifdef REPORT_RESULT
16+
// Count the mixed frames and return after 375 frames (1 second with the default 128 size)
17+
volatile int audioProcessedCount = 0;
18+
bool playedAndMixed(double time, void* data) {
19+
if (audioProcessedCount >= 375) {
20+
REPORT_RESULT(0);
21+
return false;
22+
}
23+
return true;
24+
}
25+
#endif
26+
27+
// ID to the beat and bass loops
28+
EMSCRIPTEN_WEBAUDIO_T beatID = 0;
29+
EMSCRIPTEN_WEBAUDIO_T bassID = 0;
30+
31+
// Creates a MediaElementAudioSourceNode with the supplied URL (which is
32+
// registered as an internal audio object and the ID returned).
33+
EM_JS(EMSCRIPTEN_WEBAUDIO_T, createTrack, (EMSCRIPTEN_WEBAUDIO_T ctxID, const char* url, bool looping), {
34+
var context = emscriptenGetAudioObject(ctxID);
35+
if (context) {
36+
var audio = document.createElement('audio');
37+
audio.src = UTF8ToString(url);
38+
audio.loop = looping;
39+
var track = context.createMediaElementSource(audio);
40+
return emscriptenRegisterAudioObject(track);
41+
}
42+
return 0;
43+
});
44+
45+
// Toggles the play/pause of a MediaElementAudioSourceNode given its ID
46+
EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcID), {
47+
var source = emscriptenGetAudioObject(srcID);
48+
if (source) {
49+
var audio = source.mediaElement;
50+
if (audio) {
51+
if (audio.paused) {
52+
audio.currentTime = 0;
53+
audio.play();
54+
} else {
55+
audio.pause();
56+
}
57+
}
58+
}
59+
});
60+
61+
// Callback to process and mix the audio tracks
62+
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
63+
#ifdef REPORT_RESULT
64+
audioProcessedCount++;
65+
#endif
66+
// Single mono output
67+
assert(numOutputs == 1 && outputs[0].numberOfChannels == 1);
68+
for (int n = 0; n < numInputs; n++) {
69+
// And all inputs are also stereo
70+
assert(inputs[n].numberOfChannels == 1 || inputs[n].numberOfChannels == 0);
71+
// This should always be the case
72+
assert(inputs[n].samplesPerChannel == outputs[0].samplesPerChannel);
73+
}
74+
// We can now do a quick mix since we know the layouts
75+
if (numInputs > 0) {
76+
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
77+
float* outputData = outputs[0].data;
78+
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
79+
for (int n = 1; n < numInputs; n++) {
80+
// It's possible to have an input with no channels
81+
if (inputs[n].numberOfChannels == 1) {
82+
float* inputData = inputs[n].data;
83+
for (int i = totalSamples - 1; i >= 0; i--) {
84+
outputData[i] += inputData[i];
85+
}
86+
}
87+
}
88+
}
89+
return true;
90+
}
91+
92+
// Registered click even to (1) enable audio playback and (2) toggle playing the tracks
93+
bool onClick(int type, const EmscriptenMouseEvent* e, void* data) {
94+
EMSCRIPTEN_WEBAUDIO_T ctx = (EMSCRIPTEN_WEBAUDIO_T) (data);
95+
if (emscripten_audio_context_state(ctx) != AUDIO_CONTEXT_STATE_RUNNING) {
96+
printf("Resuming playback\n");
97+
emscripten_resume_audio_context_sync(ctx);
98+
}
99+
printf("Toggling audio playback\n");
100+
toggleTrack(beatID);
101+
toggleTrack(bassID);
102+
return false;
103+
}
104+
105+
// Audio processor created, now register the audio callback
106+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
107+
if (success) {
108+
printf("Audio worklet processor created\n");
109+
printf("Click to toggle audio playback\n");
110+
111+
// Mono output, two inputs
112+
int outputChannelCounts[1] = { 1 };
113+
EmscriptenAudioWorkletNodeCreateOptions opts = {
114+
.numberOfInputs = 2,
115+
.numberOfOutputs = 1,
116+
.outputChannelCounts = outputChannelCounts
117+
};
118+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
119+
emscripten_audio_node_connect(worklet, context, 0, 0);
120+
121+
// Create the two mono source nodes and connect them to the two inputs
122+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
123+
beatID = createTrack(context, "audio_files/emscripten-beat-mono.mp3", true);
124+
if (beatID) {
125+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
126+
}
127+
bassID = createTrack(context, "audio_files/emscripten-bass-mono.mp3", true);
128+
if (bassID) {
129+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
130+
}
131+
132+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, (void*) (context), false, &onClick);
133+
134+
#ifdef REPORT_RESULT
135+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
136+
#endif
137+
} else {
138+
printf("Audio worklet node creation failed\n");
139+
}
140+
}
141+
142+
// Worklet thread inited, now create the audio processor
143+
void initialised(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
144+
if (success) {
145+
printf("Audio worklet initialised\n");
146+
147+
WebAudioWorkletProcessorCreateOptions opts = {
148+
.name = "mixer",
149+
};
150+
emscripten_create_wasm_audio_worklet_processor_async(context, &opts, &processorCreated, NULL);
151+
} else {
152+
printf("Audio worklet failed to initialise\n");
153+
}
154+
}
155+
156+
int main() {
157+
static char workletStack[AUDIO_STACK_SIZE];
158+
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL);
159+
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, sizeof workletStack, &initialised, NULL);
160+
return 0;
161+
}

0 commit comments

Comments
 (0)