Skip to content

Commit 48361fd

Browse files
committed
Initial mixer using audio params
1 parent bd5d653 commit 48361fd

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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* __unused params, void* __unused 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+
// Interestingly, params won't have a length > 1 unless the value changes, but
30+
// we do know two params are incoming
31+
assert(numParams = 2);
32+
// We can now do a quick mix since we know the layouts
33+
if (numInputs > 0) {
34+
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
35+
float* outputData = outputs[0].data;
36+
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
37+
for (int n = 1; n < numInputs; n++) {
38+
// It's possible to have an input with no channels
39+
if (inputs[n].numberOfChannels == 2) {
40+
float* inputData = inputs[n].data;
41+
for (int i = totalSamples - 1; i >= 0; i--) {
42+
outputData[i] += inputData[i] * params[0].data[(params[0].length > 1) ? i : 0]; // world's worst mixer...
43+
}
44+
}
45+
}
46+
}
47+
return true;
48+
}
49+
50+
// Grabs the known worklet parameter and fades in or out (depending on whether
51+
// it's already fading up or down, we reverse the fade direction).
52+
EM_JS(void, doFade, (EMSCRIPTEN_AUDIO_WORKLET_NODE_T workletID), {
53+
var worklet = emscriptenGetAudioObject(workletID);
54+
if (worklet) {
55+
// Emscripten's API creates these from a C array, indexing them instead of a
56+
// name, so technically 0 is "0" but we might as well use numerical indices.
57+
var param = worklet.parameters.get(0);
58+
if (param) {
59+
param.setTargetAtTime((param.value > 0.5) ? 0 : 1, 0 /* same as context.currentTime */, 0.5);
60+
}
61+
}
62+
});
63+
64+
// Registered keypress event to call the JS doFade()
65+
bool onPress(int __unused type, const EmscriptenKeyboardEvent* e, void* data) {
66+
if (!e->repeat && data) {
67+
doFade(VOIDP_2_WA(data));
68+
}
69+
return false;
70+
}
71+
72+
// Audio processor created, now register the audio callback
73+
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* __unused data) {
74+
if (!success) {
75+
printf("Audio worklet node creation failed\n");
76+
return;
77+
}
78+
printf("Audio worklet processor created\n");
79+
printf("Click to toggle audio playback\n");
80+
printf("Keypress to fade the baseline in or out\n");
81+
82+
// Stereo output, two inputs
83+
int outputChannelCounts[1] = { 2 };
84+
EmscriptenAudioWorkletNodeCreateOptions opts = {
85+
.numberOfInputs = 2,
86+
.numberOfOutputs = 1,
87+
.outputChannelCounts = outputChannelCounts
88+
};
89+
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
90+
emscripten_audio_node_connect(worklet, context, 0, 0);
91+
92+
// Create the two stereo source nodes and connect them to the two inputs
93+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
94+
beatID = createTrack(context, "audio_files/emscripten-beat.mp3", true);
95+
if (beatID) {
96+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
97+
}
98+
bassID = createTrack(context, "audio_files/emscripten-bass.mp3", true);
99+
if (bassID) {
100+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
101+
}
102+
103+
// Register a click to start playback
104+
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(context), false, &onClick);
105+
// And a keypress to do affect the fader
106+
emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, WA_2_VOIDP(worklet), false, &onPress);
107+
108+
// Register the counter that exits the test after one second of mixing
109+
emscripten_set_timeout_loop(&playedAndMixed, 16, NULL);
110+
}
111+
112+
// Worklet thread inited, now create the audio processor
113+
void initialisedWithParams(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* __unused data) {
114+
if (!success) {
115+
printf("Audio worklet failed to initialise\n");
116+
return;
117+
}
118+
printf("Audio worklet initialised\n");
119+
120+
// Custom audio params we'll use as a fader
121+
WebAudioParamDescriptor faderParam[] = {
122+
{
123+
// This a-rate (one entry per sample) is used to set the mix level
124+
.defaultValue = 1.0f,
125+
.minValue = 0.0f,
126+
.maxValue = 1.0f,
127+
.automationRate = WEBAUDIO_PARAM_A_RATE
128+
}, {
129+
// This k-rate (one entry per frame) is used just to test
130+
.defaultValue = 0.0f,
131+
.minValue = -100.0f,
132+
.maxValue = 100.0f,
133+
.automationRate = WEBAUDIO_PARAM_K_RATE
134+
}
135+
};
136+
WebAudioWorkletProcessorCreateOptions opts = {
137+
.name = "mixer",
138+
.numAudioParams = 2,
139+
.audioParamDescriptors = faderParam
140+
};
141+
emscripten_create_wasm_audio_worklet_processor_async(context, &opts, &processorCreated, NULL);
142+
}
143+
144+
// This implementation has no custom start-up requirements
145+
EmscriptenStartWebAudioWorkletCallback getStartCallback() {
146+
return &initialisedWithParams;
147+
}

test/webaudio/audioworklet_test_shared.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcID), {
6262
}
6363
});
6464

65-
// Registered click even to (1) enable audio playback and (2) toggle playing the tracks
65+
// Registered click event to (1) enable audio playback and (2) toggle playing the tracks
6666
bool onClick(int __unused type, const EmscriptenMouseEvent* __unused e, void* data) {
6767
EMSCRIPTEN_WEBAUDIO_T ctx = VOIDP_2_WA(data);
6868
if (emscripten_audio_context_state(ctx) != AUDIO_CONTEXT_STATE_RUNNING) {

0 commit comments

Comments
 (0)