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
8051bool 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
9279bool 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
10492void 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
134125void 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) {
147138int 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