Skip to content

Commit f6a78ae

Browse files
committed
Tidied mixer
1 parent fc3476a commit f6a78ae

File tree

1 file changed

+52
-62
lines changed

1 file changed

+52
-62
lines changed
Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <assert.h>
2+
#include <string.h>
13
#include <stdio.h>
24

35
#include <emscripten/em_js.h>
@@ -12,13 +14,13 @@ void playedAndMixed(void* data) {
1214
#endif
1315

1416
// ID to the beat and bass loops
15-
EMSCRIPTEN_WEBAUDIO_T beatHnd = 0;
16-
EMSCRIPTEN_WEBAUDIO_T bassHnd = 0;
17+
EMSCRIPTEN_WEBAUDIO_T beatID = 0;
18+
EMSCRIPTEN_WEBAUDIO_T bassID = 0;
1719

1820
// Creates a MediaElementAudioSourceNode with the supplied URL (which is
1921
// registered as an internal audio object and the ID returned).
20-
EM_JS(EMSCRIPTEN_WEBAUDIO_T, createTrack, (EMSCRIPTEN_WEBAUDIO_T ctxHnd, const char* url, bool looping), {
21-
var context = emscriptenGetAudioObject(ctxHnd);
22+
EM_JS(EMSCRIPTEN_WEBAUDIO_T, createTrack, (EMSCRIPTEN_WEBAUDIO_T ctxID, const char* url, bool looping), {
23+
var context = emscriptenGetAudioObject(ctxID);
2224
if (context) {
2325
var audio = document.createElement('audio');
2426
audio.src = UTF8ToString(url);
@@ -30,8 +32,8 @@ EM_JS(EMSCRIPTEN_WEBAUDIO_T, createTrack, (EMSCRIPTEN_WEBAUDIO_T ctxHnd, const c
3032
});
3133

3234
// Toggles the play/pause of a MediaElementAudioSourceNode given its ID
33-
EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcHnd), {
34-
var source = emscriptenGetAudioObject(srcHnd);
35+
EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcID), {
36+
var source = emscriptenGetAudioObject(srcID);
3537
if (source) {
3638
var audio = source.mediaElement;
3739
if (audio) {
@@ -45,66 +47,54 @@ EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcHnd), {
4547
}
4648
});
4749

48-
// Adds a button to play and stop an audio file
49-
EM_JS(bool, addAudio, (EMSCRIPTEN_WEBAUDIO_T ctxHnd, EMSCRIPTEN_AUDIO_WORKLET_NODE_T nodeHnd, int index, const char* url, const char* label), {
50-
var context = emscriptenGetAudioObject(ctxHnd);
51-
if (context) {
52-
var audio = document.createElement('audio');
53-
audio.src = UTF8ToString(url);
54-
audio.loop = true;
55-
var track = context.createMediaElementSource(audio);
56-
57-
var worklet = emscriptenGetAudioObject(nodeHnd);
58-
track.connect(worklet ? worklet : context.destination, 0, index);
59-
60-
var button = document.createElement('button');
61-
button.innerHTML = UTF8ToString(label);
62-
button.onclick = () => {
63-
if (context.state == 'suspended') {
64-
context.resume();
65-
}
66-
if (audio.paused) {
67-
audio.currentTime = 0;
68-
audio.play();
69-
} else {
70-
audio.pause();
71-
}
72-
73-
};
74-
document.body.appendChild(button);
75-
return true;
76-
}
77-
return false;
78-
});
79-
50+
// Callback to process and mix the audio tracks
8051
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
81-
for (int o = 0; o < numOutputs; o++) {
82-
for (int n = outputs[o].samplesPerChannel * outputs[o].numberOfChannels - 1; n >= 0; n--) {
83-
outputs[o].data[n] = 0.0f;
84-
for (int i = 0; i < numInputs; i++) {
85-
outputs[o].data[n] += inputs[i].data[n] * 0.75f;
52+
// Single stereo output
53+
assert(numOutputs == 1 && outputs[0].numberOfChannels == 2);
54+
for (int n = 0; n < numInputs; n++) {
55+
// And all inputs are also stereo
56+
assert(inputs[n].numberOfChannels == 2 || inputs[n].numberOfChannels == 0);
57+
// This should always be the case
58+
assert(inputs[n].samplesPerChannel == outputs[0].samplesPerChannel);
59+
}
60+
// We can now do a quick mix since we know the layouts
61+
if (numInputs > 0) {
62+
int totalSamples = outputs[0].samplesPerChannel * outputs[0].numberOfChannels;
63+
float* outputData = outputs[0].data;
64+
memcpy(outputData, inputs[0].data, totalSamples * sizeof(float));
65+
for (int n = 1; n < numInputs; n++) {
66+
// It's possible to have an input with no channels
67+
if (inputs[n].numberOfChannels == 2) {
68+
float* inputData = inputs[n].data;
69+
for (int i = totalSamples - 1; i >= 0; i--) {
70+
outputData[i] += inputData[i];
71+
}
8672
}
8773
}
8874
}
8975
return true;
9076
}
9177

78+
// Registered click even to (1) enable audio playback and (2) toggle playing the tracks
9279
bool onClick(int type, const EmscriptenMouseEvent* e, void* data) {
93-
printf("Click event!\n");
94-
EMSCRIPTEN_WEBAUDIO_T ctx = (EMSCRIPTEN_WEBAUDIO_T) (data);
95-
if (emscripten_audio_context_state(ctx) != AUDIO_CONTEXT_STATE_RUNNING) {
96-
emscripten_resume_audio_context_sync(ctx);
97-
98-
toggleTrack(beatHnd);
99-
toggleTrack(bassHnd);
100-
}
101-
return false;
80+
EMSCRIPTEN_WEBAUDIO_T ctx = (EMSCRIPTEN_WEBAUDIO_T) (data);
81+
if (emscripten_audio_context_state(ctx) != AUDIO_CONTEXT_STATE_RUNNING) {
82+
printf("Resuming playback\n");
83+
emscripten_resume_audio_context_sync(ctx);
84+
}
85+
printf("Toggling audio playback\n");
86+
toggleTrack(beatID);
87+
toggleTrack(bassID);
88+
return false;
10289
}
10390

91+
// Audio processor created, now register the audio callback
10492
void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
10593
if (success) {
10694
printf("Audio worklet processor created\n");
95+
printf("Click the toggle audio playback\n");
10796

97+
// Stereo output, two inputs
10898
int outputChannelCounts[1] = { 2 };
10999
EmscriptenAudioWorkletNodeCreateOptions opts = {
110100
.numberOfInputs = 2,
@@ -114,23 +104,24 @@ void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
114104
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(context, "mixer", &opts, &process, NULL);
115105
emscripten_audio_node_connect(worklet, context, 0, 0);
116106

117-
beatHnd = createTrack(context, "audio_files/emscripten-beat.mp3", true);
118-
if (beatHnd) {
119-
emscripten_audio_node_connect(beatHnd, worklet, 0, 0);
107+
// Create the two stereo source nodes and connect them to the two inputs
108+
// Note: we can connect the sources to the same input and it'll get mixed for us, but that's not the point
109+
beatID = createTrack(context, "audio_files/emscripten-beat.mp3", true);
110+
if (beatID) {
111+
emscripten_audio_node_connect(beatID, worklet, 0, 0);
120112
}
121-
EMSCRIPTEN_WEBAUDIO_T bassHnd = createTrack(context, "audio_files/emscripten-bass.mp3", true);
122-
if (bassHnd) {
123-
emscripten_audio_node_connect(bassHnd, worklet, 0, 1);
113+
bassID = createTrack(context, "audio_files/emscripten-bass.mp3", true);
114+
if (bassID) {
115+
emscripten_audio_node_connect(bassID, worklet, 0, 1);
124116
}
125117

126118
emscripten_set_click_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, (void*) (context), false, &onClick);
127-
//addAudio(context, worklet, 0, "audio_files/emscripten-beat.mp3", "Toggle Beat");
128-
//addAudio(context, worklet, 1, "audio_files/emscripten-bass.mp3", "Toggle Bass");
129119
} else {
130120
printf("Audio worklet node creation failed\n");
131121
}
132122
}
133123

124+
// Worklet thread inited, now create the audio processor
134125
void initialised(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
135126
if (success) {
136127
printf("Audio worklet initialised\n");
@@ -147,7 +138,6 @@ void initialised(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
147138
int main() {
148139
static char workletStack[AUDIO_STACK_SIZE];
149140
EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(NULL);
150-
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, sizeof(workletStack), initialised, NULL);
151-
141+
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, sizeof workletStack, &initialised, NULL);
152142
return 0;
153143
}

0 commit comments

Comments
 (0)