| #include <emscripten/webaudio.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <assert.h> |
| |
| /* Steps to use Wasm-based AudioWorklets: |
| 1. Create a Web Audio AudioContext either via manual JS code and calling emscriptenRegisterAudioObject() from JS, or by calling emscripten_create_audio_context() (shown in this sample) |
| 2. Initialize a Wasm AudioWorklet scope on the audio context by calling emscripten_start_wasm_audio_worklet_thread_async(). This shares the Wasm Module, Memory, etc. to the AudioWorklet scope, |
| and establishes the stack space for the Audio Worklet. |
| This needs to be called exactly once during page's lifetime. There is no mechanism in Web Audio to shut down/uninitialize the scope. |
| 3. Create one or more of Audio Worklet Processors with the desired name and AudioParam configuration. |
| 4. Instantiate Web Audio audio graph nodes from the above created worklet processors, specifying the desired input-output configurations and Wasm-side function callbacks to call for each node. |
| 5. Add the graph nodes to the Web Audio graph, and the audio callbacks should begin to fire. |
| */ |
| |
| #ifdef REPORT_RESULT // This is defined when running in Emscripten test harness. You can strip these out in your own project. |
| _Thread_local int testTlsVariable = 1; |
| int lastTlsVariableValueInAudioThread = 1; |
| #endif |
| |
| // This function will be called for every fixed 128 samples of audio to be processed. |
| EM_BOOL ProcessAudio(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) |
| { |
| #ifdef REPORT_RESULT |
| assert(testTlsVariable == lastTlsVariableValueInAudioThread); |
| ++testTlsVariable; |
| lastTlsVariableValueInAudioThread = testTlsVariable; |
| assert(emscripten_current_thread_is_audio_worklet()); |
| #endif |
| |
| // Produce noise in all output channels. |
| for(int i = 0; i < numOutputs; ++i) |
| for(int j = 0; j < 128*outputs[i].numberOfChannels; ++j) |
| outputs[i].data[j] = (rand() / (float)RAND_MAX * 2.0f - 1.0f) * 0.3f; |
| |
| // We generated audio and want to keep this processor going. Return EM_FALSE here to shut down. |
| return EM_TRUE; |
| } |
| |
| EM_JS(void, InitHtmlUi, (EMSCRIPTEN_WEBAUDIO_T audioContext, EMSCRIPTEN_AUDIO_WORKLET_NODE_T audioWorkletNode), { |
| audioContext = emscriptenGetAudioObject(audioContext); |
| audioWorkletNode = emscriptenGetAudioObject(audioWorkletNode); |
| |
| // Add a button on the page to toggle playback as a response to user click. |
| let startButton = document.createElement('button'); |
| startButton.innerHTML = 'Toggle playback'; |
| document.body.appendChild(startButton); |
| |
| startButton.onclick = () => { |
| if (audioContext.state != 'running') { |
| audioContext.resume(); |
| // Connect the audio worklet node to the graph. |
| audioWorkletNode.connect(audioContext.destination); |
| } else { |
| audioContext.suspend(); |
| } |
| }; |
| }); |
| |
| #ifdef REPORT_RESULT |
| EM_BOOL main_thread_tls_access(double time, void *userData) |
| { |
| // Try to mess the TLS variable on the main thread, with the expectation that it should not change |
| // the TLS value on the AudioWorklet thread. |
| testTlsVariable = (int)time; |
| if (lastTlsVariableValueInAudioThread >= 100) |
| { |
| REPORT_RESULT(0); |
| return EM_FALSE; |
| } |
| return EM_TRUE; |
| } |
| #endif |
| |
| // This callback will fire after the Audio Worklet Processor has finished being added to the Worklet global scope. |
| void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void *userData) |
| { |
| if (!success) return; |
| |
| // Specify the input and output node configurations for the Wasm Audio Worklet. A simple setup with single mono output channel here, and no inputs. |
| int outputChannelCounts[1] = { 1 }; |
| |
| EmscriptenAudioWorkletNodeCreateOptions options = { |
| .numberOfInputs = 0, |
| .numberOfOutputs = 1, |
| .outputChannelCounts = outputChannelCounts |
| }; |
| |
| // Instantiate the noise-generator Audio Worklet Processor. |
| EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "noise-generator", &options, &ProcessAudio, 0); |
| |
| #ifdef REPORT_RESULT |
| emscripten_set_timeout_loop(main_thread_tls_access, 10, 0); |
| #endif |
| |
| InitHtmlUi(audioContext, wasmAudioWorklet); |
| } |
| |
| // This callback will fire when the Wasm Module has been shared to the AudioWorklet global scope, and is now ready to begin adding Audio Worklet Processors. |
| void WebAudioWorkletThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void *userData) |
| { |
| if (!success) return; |
| |
| WebAudioWorkletProcessorCreateOptions opts = { |
| .name = "noise-generator", |
| }; |
| emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, AudioWorkletProcessorCreated, 0); |
| } |
| |
| // Define a global stack space for the AudioWorkletGlobalScope. Note that all AudioWorkletProcessors and/or AudioWorkletNodes on the given Audio Context all share the same AudioWorkerGlobalScope, |
| // i.e. they all run on the same one audio thread (multiple nodes/processors do not each get their own thread). Hence one stack is enough. |
| uint8_t wasmAudioWorkletStack[4096]; |
| |
| int main() |
| { |
| srand(time(NULL)); |
| |
| assert(!emscripten_current_thread_is_audio_worklet()); |
| |
| // Create an audio context |
| EMSCRIPTEN_WEBAUDIO_T context = emscripten_create_audio_context(0 /* use default constructor options */); |
| |
| // and kick off Audio Worklet scope initialization, which shares the Wasm Module and Memory to the AudioWorklet scope and initializes its stack. |
| emscripten_start_wasm_audio_worklet_thread_async(context, wasmAudioWorkletStack, sizeof(wasmAudioWorkletStack), WebAudioWorkletThreadInitialized, 0); |
| } |