Skip to content

Commit

Permalink
[test] Add audio worklet parameter tests (and tidy other interactive …
Browse files Browse the repository at this point in the history
…tests) (#23659)

This is splits out the test changes from #23508 adding an interactive
parameter test:

```
test/runner interactive.test_audio_worklet_params_mixing
```

This test is also added to the browser tests now #23665 has merged,
offering a single test that touches all the various structs and their
offsets. When run as part of the `browser` tests it plays for 1 second,
for interactive without the `TEST_AND_EXIT` macro it will play
continuously.

It also tidies a little the shared code (and related tests) in the
process. Interactive tests had `_2gb` and `_4gb` options added (which
will fail until #23508 lands, but they're not part of the CI). All
features of these tests as interactive now work fully on Chrome, Firefox
and Safari (Safari has issues with looping, but it doesn't affect the
spirit of the test).
  • Loading branch information
cwoffenden authored Feb 21, 2025
1 parent ee520f0 commit f7f6395
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 73 deletions.
12 changes: 8 additions & 4 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5500,17 +5500,21 @@ def test_audio_worklet_post_function(self, args):
def test_audio_worklet_modularize(self, args):
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'), '-DTEST_AND_EXIT'] + args)

# Tests multiple inputs, forcing a larger stack (note: passing BROWSER_TEST is
# specific to this test to allow it to exit rather than play forever).
# Tests an AudioWorklet with multiple stereo inputs mixing in the processor
# via a varying parameter to a single stereo output (touching all of the API
# copying from structs)
@parameterized({
'': ([],),
'minimal_with_closure': (['-sMINIMAL_RUNTIME', '--closure=1', '-Oz'],),
})
def test_audio_worklet_stereo_io(self, args):
@no_wasm64('https://github.com/emscripten-core/emscripten/pull/23508')
@no_2gb('https://github.com/emscripten-core/emscripten/pull/23508')
@requires_sound_hardware
def test_audio_worklet_params_mixing(self, args):
os.mkdir('audio_files')
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
self.btest_exit('webaudio/audioworklet_in_out_stereo.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-DBROWSER_TEST'] + args)
self.btest_exit('webaudio/audioworklet_params_mixing.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS', '-DTEST_AND_EXIT'] + args)

# Tests AudioWorklet with emscripten_futex_wake().
@requires_sound_hardware
Expand Down
24 changes: 23 additions & 1 deletion test/test_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,32 @@ def test_audio_worklet_2x_hard_pan_io(self):
shutil.copy(test_file('webaudio/audio_files/emscripten-bass-mono.mp3'), 'audio_files/')
self.btest_exit('webaudio/audioworklet_2x_in_hard_pan.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])

# Tests an AudioWorklet with multiple stereo inputs mixing in the processor via a parameter to a single stereo output (6kB stack)
def test_audio_worklet_params_mixing(self):
os.mkdir('audio_files')
shutil.copy(test_file('webaudio/audio_files/emscripten-beat.mp3'), 'audio_files/')
shutil.copy(test_file('webaudio/audio_files/emscripten-bass.mp3'), 'audio_files/')
self.btest_exit('webaudio/audioworklet_params_mixing.c', args=['-sAUDIO_WORKLET', '-sWASM_WORKERS'])


class interactive64(interactive):
def setUp(self):
super().setUp()
self.set_setting('MEMORY64')
self.emcc_args.append('-Wno-experimental')
self.require_wasm64()


class interactive64_4gb(interactive):
def setUp(self):
super().setUp()
self.set_setting('MEMORY64')
self.set_setting('INITIAL_MEMORY', '4200mb')
self.set_setting('GLOBAL_BASE', '4gb')
self.require_wasm64()


class interactive_2gb(interactive):
def setUp(self):
super().setUp()
self.set_setting('INITIAL_MEMORY', '2200mb')
self.set_setting('GLOBAL_BASE', '2gb')
45 changes: 31 additions & 14 deletions test/webaudio/audioworklet_2x_in_hard_pan.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <assert.h>
#include <string.h>
#include <stdio.h>

#include <emscripten/em_js.h>
#include <emscripten/webaudio.h>
Expand All @@ -16,30 +15,41 @@

// Callback to process and copy the audio tracks
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
#ifdef TEST_AND_EXIT
audioProcessedCount++;
#endif

// Twin mono in, single stereo out
// Twin mono in (or disabled), single stereo out
assert(numInputs == 2 && numOutputs == 1);
assert(inputs[0].numberOfChannels == 1 && inputs[1].numberOfChannels == 1);
assert(inputs[0].numberOfChannels == 0 || inputs[0].numberOfChannels == 1);
assert(inputs[1].numberOfChannels == 0 || inputs[1].numberOfChannels == 1);
assert(outputs[0].numberOfChannels == 2);
// All with the same number of samples
assert(inputs[0].samplesPerChannel == inputs[1].samplesPerChannel);
assert(inputs[0].samplesPerChannel == outputs[0].samplesPerChannel);
// Now with all known quantities we can memcpy the data
int samplesPerChannel = inputs[0].samplesPerChannel;
memcpy(outputs[0].data, inputs[0].data, samplesPerChannel * sizeof(float));
memcpy(outputs[0].data + samplesPerChannel, inputs[1].data, samplesPerChannel * sizeof(float));
// Now with all known quantities we can memcpy the L&R data (or zero it if the
// channels are disabled)
int bytesPerChannel = outputs[0].samplesPerChannel * sizeof(float);
float* outputData = outputs[0].data;
if (inputs[0].numberOfChannels > 0) {
memcpy(outputData, inputs[0].data, bytesPerChannel);
} else {
memset(outputData, 0, bytesPerChannel);
}
outputData += outputs[0].samplesPerChannel;
if (inputs[1].numberOfChannels > 0) {
memcpy(outputData, inputs[1].data, bytesPerChannel);
} else {
memset(outputData, 0, bytesPerChannel);
}
return true;
}

// Audio processor created, now register the audio callback
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
if (!success) {
printf("Audio worklet node creation failed\n");
return;
}
printf("Audio worklet processor created\n");
printf("Click to toggle audio playback\n");
assert(success && "Audio worklet failed in processorCreated()");
emscripten_out("Audio worklet processor created");
emscripten_out("Click to toggle audio playback");

// Stereo output, two inputs
int outputChannelCounts[2] = { 2 };
Expand All @@ -65,6 +75,13 @@ void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
// Register a click to start playback
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);

// Register the counter that exits the test after one second of mixing
#ifdef TEST_AND_EXIT
// Register the counter that exits the test after one second of playback
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
#endif
}

// This implementation has no custom start-up requirements
EmscriptenStartWebAudioWorkletCallback getStartCallback(void) {
return &initialised;
}
44 changes: 30 additions & 14 deletions test/webaudio/audioworklet_2x_in_out_stereo.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <assert.h>
#include <string.h>
#include <stdio.h>

#include <emscripten/em_js.h>
#include <emscripten/webaudio.h>
Expand All @@ -15,31 +14,41 @@

// Callback to process and copy the audio tracks
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
#ifdef TEST_AND_EXIT
audioProcessedCount++;
#endif

// Twin stereo in and out
assert(numInputs == 2 && numOutputs == 2);
assert(inputs[0].numberOfChannels == 2 && inputs[1].numberOfChannels == 2);
assert(outputs[0].numberOfChannels == 2 && outputs[1].numberOfChannels == 2);
assert(inputs[0].numberOfChannels == 0 || inputs[0].numberOfChannels == 2);
assert(inputs[1].numberOfChannels == 0 || inputs[1].numberOfChannels == 2);
assert(outputs[0].numberOfChannels == 2);
assert(outputs[1].numberOfChannels == 2);
// All with the same number of samples
assert(inputs[0].samplesPerChannel == inputs[1].samplesPerChannel);
assert(inputs[0].samplesPerChannel == outputs[0].samplesPerChannel);
assert(outputs[0].samplesPerChannel == outputs[1].samplesPerChannel);
// Now with all known quantities we can memcpy the data
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
memcpy(outputs[0].data, inputs[0].data, totalSamples * sizeof(float));
memcpy(outputs[1].data, inputs[1].data, totalSamples * sizeof(float));
// Now with all known quantities we can memcpy all the data (or zero it if the
// channels are disabled)
int totalBytes = outputs[0].samplesPerChannel * outputs[0].numberOfChannels * sizeof(float);
if (inputs[0].numberOfChannels > 0) {
memcpy(outputs[0].data, inputs[0].data, totalBytes);
} else {
memset(outputs[0].data, 0, totalBytes);
}
if (inputs[1].numberOfChannels > 0) {
memcpy(outputs[1].data, inputs[1].data, totalBytes);
} else {
memset(outputs[1].data, 0, totalBytes);
}
return true;
}

// Audio processor created, now register the audio callback
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
if (!success) {
printf("Audio worklet node creation failed\n");
return;
}
printf("Audio worklet processor created\n");
printf("Click to toggle audio playback\n");
assert(success && "Audio worklet failed in processorCreated()");
emscripten_out("Audio worklet processor created");
emscripten_out("Click to toggle audio playback");

// Two stereo outputs, two inputs
int outputChannelCounts[2] = { 2, 2 };
Expand Down Expand Up @@ -67,6 +76,13 @@ void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
// Register a click to start playback
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);

// Register the counter that exits the test after one second of mixing
#ifdef TEST_AND_EXIT
// Register the counter that exits the test after one second of playback
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
#endif
}

// This implementation has no custom start-up requirements
EmscriptenStartWebAudioWorkletCallback getStartCallback(void) {
return &initialised;
}
32 changes: 22 additions & 10 deletions test/webaudio/audioworklet_in_out_mono.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <assert.h>
#include <string.h>
#include <stdio.h>

#include <emscripten/em_js.h>
#include <emscripten/webaudio.h>
Expand All @@ -16,7 +15,9 @@

// Callback to process and mix the audio tracks
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
#ifdef TEST_AND_EXIT
audioProcessedCount++;
#endif

// Single mono output
assert(numOutputs == 1 && outputs[0].numberOfChannels == 1);
Expand All @@ -29,11 +30,18 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi
// We can now do a quick mix since we know the layouts
if (numInputs > 0) {
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
// Simple copy of the first input's audio data, checking that we have
// channels (since a muted input has zero channels).
float* outputData = outputs[0].data;
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
if (inputs[0].numberOfChannels > 0) {
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
} else {
// And for muted we need to fill the buffer with zeroes otherwise it repeats the last frame
memset(outputData, 0, totalSamples * sizeof(float));
}
// Now add another inputs
for (int n = 1; n < numInputs; n++) {
// It's possible to have an input with no channels
if (inputs[n].numberOfChannels == 1) {
if (inputs[n].numberOfChannels > 0) {
float* inputData = inputs[n].data;
for (int i = totalSamples - 1; i >= 0; i--) {
outputData[i] += inputData[i];
Expand All @@ -46,12 +54,9 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi

// Audio processor created, now register the audio callback
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
if (!success) {
printf("Audio worklet node creation failed\n");
return;
}
printf("Audio worklet processor created\n");
printf("Click to toggle audio playback\n");
assert(success && "Audio worklet failed in processorCreated()");
emscripten_out("Audio worklet processor created");
emscripten_out("Click to toggle audio playback");

// Mono output, two inputs
int outputChannelCounts[1] = { 1 };
Expand All @@ -77,6 +82,13 @@ void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
// Register a click to start playback
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);

#ifdef TEST_AND_EXIT
// Register the counter that exits the test after one second of mixing
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
#endif
}

// This implementation has no custom start-up requirements
EmscriptenStartWebAudioWorkletCallback getStartCallback(void) {
return &initialised;
}
32 changes: 22 additions & 10 deletions test/webaudio/audioworklet_in_out_stereo.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <assert.h>
#include <string.h>
#include <stdio.h>

#include <emscripten/em_js.h>
#include <emscripten/webaudio.h>
Expand All @@ -16,7 +15,9 @@

// Callback to process and mix the audio tracks
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
#ifdef TEST_AND_EXIT
audioProcessedCount++;
#endif

// Single stereo output
assert(numOutputs == 1 && outputs[0].numberOfChannels == 2);
Expand All @@ -29,11 +30,18 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi
// We can now do a quick mix since we know the layouts
if (numInputs > 0) {
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
// Simple copy of the first input's audio data, checking that we have
// channels (since a muted input has zero channels).
float* outputData = outputs[0].data;
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
if (inputs[0].numberOfChannels > 0) {
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
} else {
// And for muted we need to fill the buffer with zeroes otherwise it repeats the last frame
memset(outputData, 0, totalSamples * sizeof(float));
}
// Now add another inputs
for (int n = 1; n < numInputs; n++) {
// It's possible to have an input with no channels
if (inputs[n].numberOfChannels == 2) {
if (inputs[n].numberOfChannels > 0) {
float* inputData = inputs[n].data;
for (int i = totalSamples - 1; i >= 0; i--) {
outputData[i] += inputData[i];
Expand All @@ -46,12 +54,9 @@ bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, Audi

// Audio processor created, now register the audio callback
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
if (!success) {
printf("Audio worklet node creation failed\n");
return;
}
printf("Audio worklet processor created\n");
printf("Click to toggle audio playback\n");
assert(success && "Audio worklet failed in processorCreated()");
emscripten_out("Audio worklet processor created");
emscripten_out("Click to toggle audio playback");

// Stereo output, two inputs
int outputChannelCounts[1] = { 2 };
Expand All @@ -77,6 +82,13 @@ void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
// Register a click to start playback
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);

#ifdef TEST_AND_EXIT
// Register the counter that exits the test after one second of mixing
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
#endif
}

// This implementation has no custom start-up requirements
EmscriptenStartWebAudioWorkletCallback getStartCallback(void) {
return &initialised;
}
Loading

0 comments on commit f7f6395

Please sign in to comment.