1
+ #include <assert.h>
2
+ #include <string.h>
1
3
#include <stdio.h>
2
4
3
5
#include <emscripten/em_js.h>
@@ -12,13 +14,13 @@ void playedAndMixed(void* data) {
12
14
#endif
13
15
14
16
// 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 ;
17
19
18
20
// Creates a MediaElementAudioSourceNode with the supplied URL (which is
19
21
// 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 );
22
24
if (context ) {
23
25
var audio = document .createElement ('audio' );
24
26
audio .src = UTF8ToString (url );
@@ -30,8 +32,8 @@ EM_JS(EMSCRIPTEN_WEBAUDIO_T, createTrack, (EMSCRIPTEN_WEBAUDIO_T ctxHnd, const c
30
32
});
31
33
32
34
// 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 );
35
37
if (source ) {
36
38
var audio = source .mediaElement ;
37
39
if (audio ) {
@@ -45,66 +47,54 @@ EM_JS(void, toggleTrack, (EMSCRIPTEN_WEBAUDIO_T srcHnd), {
45
47
}
46
48
});
47
49
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
80
51
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
+ }
86
72
}
87
73
}
88
74
}
89
75
return true;
90
76
}
91
77
78
+ // Registered click even to (1) enable audio playback and (2) toggle playing the tracks
92
79
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;
102
89
}
103
90
91
+ // Audio processor created, now register the audio callback
104
92
void processorCreated (EMSCRIPTEN_WEBAUDIO_T context , bool success , void * data ) {
105
93
if (success ) {
106
94
printf ("Audio worklet processor created\n" );
95
+ printf ("Click the toggle audio playback\n" );
107
96
97
+ // Stereo output, two inputs
108
98
int outputChannelCounts [1 ] = { 2 };
109
99
EmscriptenAudioWorkletNodeCreateOptions opts = {
110
100
.numberOfInputs = 2 ,
@@ -114,23 +104,24 @@ void processorCreated(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
114
104
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node (context , "mixer" , & opts , & process , NULL );
115
105
emscripten_audio_node_connect (worklet , context , 0 , 0 );
116
106
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 );
120
112
}
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 );
124
116
}
125
117
126
118
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");
129
119
} else {
130
120
printf ("Audio worklet node creation failed\n" );
131
121
}
132
122
}
133
123
124
+ // Worklet thread inited, now create the audio processor
134
125
void initialised (EMSCRIPTEN_WEBAUDIO_T context , bool success , void * data ) {
135
126
if (success ) {
136
127
printf ("Audio worklet initialised\n" );
@@ -147,7 +138,6 @@ void initialised(EMSCRIPTEN_WEBAUDIO_T context, bool success, void* data) {
147
138
int main () {
148
139
static char workletStack [AUDIO_STACK_SIZE ];
149
140
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 );
152
142
return 0 ;
153
143
}
0 commit comments