Skip to content

[test] Add audio worklet parameter tests (and tidy other interactive tests) #23659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5495,17 +5495,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')] + 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)

def test_error_reporting(self):
# Test catching/reporting Error objects
Expand Down
24 changes: 23 additions & 1 deletion test/test_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be a function that returns a function? Why not just declare a function called startCallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was because most tests return and use this default function in the shared code:

void initialised(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {

Whereas one needed a custom implementation, so this was the cleanest way to keep most of the code shared.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Cleanest way and still keeping it C)

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