Buckets:
| // This file is the main bootstrap script for Wasm Audio Worklets loaded in an | |
| // Emscripten application. Build with -sAUDIO_WORKLET linker flag to enable | |
| // targeting Audio Worklets. | |
| // AudioWorkletGlobalScope does not have a onmessage/postMessage() functionality | |
| // at the global scope, which means that after creating an | |
| // AudioWorkletGlobalScope and loading this script into it, we cannot | |
| // postMessage() information into it like one would do with Web Workers. | |
| // Instead, we must create an AudioWorkletProcessor class, then instantiate a | |
| // Web Audio graph node from it on the main thread. Using its message port and | |
| // the node constructor's "processorOptions" field, we can share the necessary | |
| // bootstrap information from the main thread to the AudioWorkletGlobalScope. | |
| #if MINIMAL_RUNTIME | |
| var instantiatePromise; | |
| #endif | |
| if (ENVIRONMENT_IS_AUDIO_WORKLET) { | |
| #if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS | |
| function createWasmAudioWorkletProcessor(audioParams) { | |
| #else | |
| function createWasmAudioWorkletProcessor() { | |
| #endif | |
| class WasmAudioWorkletProcessor extends AudioWorkletProcessor { | |
| constructor(args) { | |
| super(); | |
| // Capture the Wasm function callback to invoke. | |
| let opts = args.processorOptions; | |
| #if ASSERTIONS | |
| assert(opts.callback) | |
| assert(opts.samplesPerChannel) | |
| #endif | |
| this.callback = {{{ makeDynCall('iipipipp', 'opts.callback') }}}; | |
| this.userData = opts.userData; | |
| // Then the samples per channel to process, fixed for the lifetime of the | |
| // context that created this processor. Even though this 'render quantum | |
| // size' is fixed at 128 samples in the 1.0 spec, it will be variable in | |
| // the 1.1 spec. It's passed in now, just to prove it's settable, but will | |
| // eventually be a property of the AudioWorkletGlobalScope (globalThis). | |
| this.samplesPerChannel = opts.samplesPerChannel; | |
| this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; | |
| // Prepare the output views; see createOutputViews(). The 'STACK_ALIGN' | |
| // deduction stops the STACK_OVERFLOW_CHECK failing (since the stack will | |
| // be full if we allocate all the available space) leaving room for a | |
| // single AudioSampleFrame as a minimum. There's an arbitrary maximum of | |
| // 64 frames, for the case where a multi-MB stack is passed. | |
| this.outputViews = new Array(Math.min(((wwParams.stackSize - {{{ STACK_ALIGN }}}) / this.bytesPerChannel) | 0, /*sensible limit*/ 64)); | |
| #if ASSERTIONS | |
| assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); | |
| #endif | |
| this.createOutputViews(); | |
| #if ASSERTIONS | |
| // Explicitly verify this later in process(). Note to self, stackSave is a | |
| // bit of a misnomer as it simply gets the stack address. | |
| this.ctorOldStackPtr = stackSave(); | |
| #endif | |
| } | |
| /** | |
| * Create up-front as many typed views for marshalling the output data as | |
| * may be required, allocated at the *top* of the worklet's stack (and whose | |
| * addresses are fixed). | |
| */ | |
| createOutputViews() { | |
| // These are still alloc'd to take advantage of the overflow checks, etc. | |
| var oldStackPtr = stackSave(); | |
| var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.outputViews.length * this.bytesPerChannel)', 'float') }}}; | |
| #if WEBAUDIO_DEBUG | |
| console.log(`AudioWorklet creating ${this.outputViews.length} buffer one-time views (for a stack size of ${wwParams.stackSize} at address ${ptrToString(viewDataIdx * 4)})`); | |
| #endif | |
| // Inserted in reverse so the lowest indices are closest to the stack top | |
| for (var n = this.outputViews.length - 1; n >= 0; n--) { | |
| this.outputViews[n] = HEAPF32.subarray(viewDataIdx, viewDataIdx += this.samplesPerChannel); | |
| } | |
| stackRestore(oldStackPtr); | |
| } | |
| #if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS | |
| static get parameterDescriptors() { | |
| return audioParams; | |
| } | |
| #endif | |
| /** | |
| * Marshals all inputs and parameters to the Wasm memory on the thread's | |
| * stack, then performs the wasm audio worklet call, and finally marshals | |
| * audio output data back. | |
| * | |
| * @param {Object} parameters | |
| */ | |
| #if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS | |
| process(inputList, outputList, parameters) { | |
| #else | |
| /** @suppress {checkTypes} */ | |
| process(inputList, outputList) { | |
| #endif | |
| #if ALLOW_MEMORY_GROWTH | |
| // Recreate the output views if the heap has changed | |
| // TODO: add support for GROWABLE_ARRAYBUFFERS | |
| if (HEAPF32.buffer != this.outputViews[0].buffer) { | |
| this.createOutputViews(); | |
| } | |
| #endif | |
| var numInputs = inputList.length; | |
| var numOutputs = outputList.length; | |
| var entry; // reused list entry or index | |
| var subentry; // reused channel or other array in each list entry or index | |
| // Calculate the required stack and output buffer views (stack is further | |
| // split into aligned structs and the raw float data). | |
| var stackMemoryStruct = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; | |
| var stackMemoryData = 0; | |
| for (entry of inputList) { | |
| stackMemoryData += entry.length; | |
| } | |
| stackMemoryData *= this.bytesPerChannel; | |
| // Collect the total number of output channels (mapped to array views) | |
| var outputViewsNeeded = 0; | |
| for (entry of outputList) { | |
| outputViewsNeeded += entry.length; | |
| } | |
| stackMemoryData += outputViewsNeeded * this.bytesPerChannel; | |
| var numParams = 0; | |
| #if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS | |
| for (entry in parameters) { | |
| ++numParams; | |
| stackMemoryStruct += {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; | |
| stackMemoryData += parameters[entry].byteLength; | |
| } | |
| #endif | |
| var oldStackPtr = stackSave(); | |
| #if ASSERTIONS | |
| assert(oldStackPtr == this.ctorOldStackPtr, 'AudioWorklet stack address has unexpectedly moved'); | |
| assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`); | |
| #endif | |
| // Allocate the necessary stack space. All pointer variables are in bytes; | |
| // 'structPtr' starts at the first struct entry (all run sequentially) | |
| // and is the working start to each record; 'dataPtr' is the same for the | |
| // audio/params data, starting after *all* the structs. | |
| // 'structPtr' begins 16-byte aligned, allocated from the internal | |
| // _emscripten_stack_alloc(), as are the output views, and so to ensure | |
| // the views fall on the correct addresses (and we finish at stacktop) we | |
| // request additional bytes, taking this alignment into account, then | |
| // offset `dataPtr` by the difference. | |
| var stackMemoryAligned = (stackMemoryStruct + stackMemoryData + 15) & ~15; | |
| var structPtr = stackAlloc(stackMemoryAligned); | |
| var dataPtr = structPtr + (stackMemoryAligned - stackMemoryData); | |
| #if ASSERTIONS | |
| // TODO: look at why stackAlloc isn't tripping the assertions | |
| assert(stackMemoryAligned <= wwParams.stackSize, `Not enough stack allocated to the AudioWorklet (need ${stackMemoryAligned}, got ${wwParams.stackSize})`); | |
| #endif | |
| // Copy input audio descriptor structs and data to Wasm (recall, structs | |
| // first, audio data after). 'inputsPtr' is the start of the C callback's | |
| // input AudioSampleFrame. | |
| var /*const*/ inputsPtr = structPtr; | |
| for (entry of inputList) { | |
| // Write the AudioSampleFrame struct instance | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}}; | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}}; | |
| structPtr += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; | |
| // Marshal the input audio sample data for each audio channel of this input | |
| for (subentry of entry) { | |
| HEAPF32.set(subentry, {{{ getHeapOffset('dataPtr', 'float') }}}); | |
| dataPtr += this.bytesPerChannel; | |
| } | |
| } | |
| #if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS | |
| // Copy parameters descriptor structs and data to Wasm. 'paramsPtr' is the | |
| // start of the C callback's input AudioParamFrame. | |
| var /*const*/ paramsPtr = structPtr; | |
| for (entry = 0; subentry = parameters[entry++];) { | |
| // Write the AudioParamFrame struct instance | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioParamFrame.length, 'subentry.length', 'u32') }}}; | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioParamFrame.data, 'dataPtr', '*') }}}; | |
| structPtr += {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; | |
| // Marshal the audio parameters array | |
| HEAPF32.set(subentry, {{{ getHeapOffset('dataPtr', 'float') }}}); | |
| dataPtr += subentry.length * {{{ getNativeTypeSize('float') }}}; | |
| } | |
| #else | |
| var paramsPtr = 0; | |
| #endif | |
| // Copy output audio descriptor structs to Wasm. 'outputsPtr' is the start | |
| // of the C callback's output AudioSampleFrame. 'dataPtr' will now be | |
| // aligned with the output views, ending at stacktop (which is why this | |
| // needs to be last). | |
| var /*const*/ outputsPtr = structPtr; | |
| for (entry of outputList) { | |
| // Write the AudioSampleFrame struct instance | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}}; | |
| {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}}; | |
| structPtr += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; | |
| // Advance the output pointer to the next output (matching the pre-allocated views) | |
| dataPtr += this.bytesPerChannel * entry.length; | |
| } | |
| #if ASSERTIONS | |
| // If all the maths worked out, we arrived at the original stack address | |
| console.assert(dataPtr == oldStackPtr, `AudioWorklet stack mismatch (audio data finishes at ${dataPtr} instead of ${oldStackPtr})`); | |
| // Sanity checks. If these trip the most likely cause, beyond unforeseen | |
| // stack shenanigans, is that the 'render quantum size' changed after | |
| // construction (which shouldn't be possible). | |
| if (numOutputs) { | |
| // First that the output view addresses match the stack positions | |
| dataPtr -= this.bytesPerChannel; | |
| for (entry = 0; entry < outputViewsNeeded; entry++) { | |
| console.assert(dataPtr == this.outputViews[entry].byteOffset, 'AudioWorklet internal error in addresses of the output array views'); | |
| dataPtr -= this.bytesPerChannel; | |
| } | |
| // And that the views' size match the passed in output buffers | |
| for (entry of outputList) { | |
| for (subentry of entry) { | |
| assert(subentry.byteLength == this.bytesPerChannel, `AudioWorklet unexpected output buffer size (expected ${this.bytesPerChannel} got ${subentry.byteLength})`); | |
| } | |
| } | |
| } | |
| #endif | |
| // Call out to Wasm callback to perform audio processing | |
| var didProduceAudio = this.callback(numInputs, inputsPtr, numOutputs, outputsPtr, numParams, paramsPtr, this.userData); | |
| if (didProduceAudio) { | |
| // Read back the produced audio data to all outputs and their channels. | |
| // The preallocated 'outputViews' already have the correct offsets and | |
| // sizes into the stack (recall from createOutputViews() that they run | |
| // backwards). | |
| for (entry of outputList) { | |
| for (subentry of entry) { | |
| subentry.set(this.outputViews[--outputViewsNeeded]); | |
| } | |
| } | |
| } | |
| stackRestore(oldStackPtr); | |
| // Return 'true' to tell the browser to continue running this processor. | |
| // (Returning 1 or any other truthy value won't work in Chrome) | |
| return !!didProduceAudio; | |
| } | |
| } | |
| return WasmAudioWorkletProcessor; | |
| } | |
| #if MIN_FIREFOX_VERSION < 138 || MIN_CHROME_VERSION != TARGET_NOT_SUPPORTED || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED | |
| // If this browser does not support the up-to-date AudioWorklet standard | |
| // that has a MessagePort over to the AudioWorklet, then polyfill that by | |
| // a hacky AudioWorkletProcessor that provides the MessagePort. | |
| // Firefox added support in https://hg-edge.mozilla.org/integration/autoland/rev/ab38a1796126f2b3fc06475ffc5a625059af59c1 | |
| // Chrome ticket: https://crbug.com/446920095 | |
| // Safari ticket: https://webkit.org/b/299386 | |
| /** | |
| * @suppress {duplicate, checkTypes} | |
| */ | |
| var port = globalThis.port || {}; | |
| // Specify a worklet processor that will be used to receive messages to this | |
| // AudioWorkletGlobalScope. We never connect this initial AudioWorkletProcessor | |
| // to the audio graph to do any audio processing. | |
| class BootstrapMessages extends AudioWorkletProcessor { | |
| constructor(arg) { | |
| super(); | |
| startWasmWorker(arg.processorOptions) | |
| // Listen to messages from the main thread. These messages will ask this | |
| // scope to create the real AudioWorkletProcessors that call out to Wasm to | |
| // do audio processing. | |
| if (!(port instanceof MessagePort)) { | |
| this.port.onmessage = port.onmessage; | |
| /** @suppress {checkTypes} */ | |
| port = this.port; | |
| } | |
| } | |
| // No-op, not doing audio processing in this processor. It is just for | |
| // receiving bootstrap messages. However browsers require it to still be | |
| // present. It should never be called because we never add a node to the graph | |
| // with this processor, although it does look like Chrome does still call this | |
| // function. | |
| process() { | |
| // keep this function a no-op. Chrome redundantly wants to call this even | |
| // though this processor is never added to the graph. | |
| } | |
| }; | |
| // Register the dummy processor that will just receive messages. | |
| registerProcessor('em-bootstrap', BootstrapMessages); | |
| #endif | |
| port.onmessage = async (msg) => { | |
| #if MINIMAL_RUNTIME | |
| // Wait for the module instantiation before processing messages. | |
| await instantiatePromise; | |
| #endif | |
| let d = msg.data; | |
| if (d['_boot']) { | |
| startWasmWorker(d); | |
| #if WEBAUDIO_DEBUG | |
| console.log('AudioWorklet global scope looks like this:'); | |
| console.dir(globalThis); | |
| #endif | |
| } else if (d['_wpn']) { | |
| // '_wpn' is short for 'Worklet Processor Node', using an identifier | |
| // that will never conflict with user messages | |
| // Register a real AudioWorkletProcessor that will actually do audio processing. | |
| #if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS | |
| registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor(d.audioParams)); | |
| #else | |
| registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor()); | |
| #endif | |
| #if WEBAUDIO_DEBUG | |
| console.log(`Registered a new WasmAudioWorkletProcessor "${d['_wpn']}" with AudioParams: ${d.audioParams}`); | |
| #endif | |
| // Post a Wasm Call message back telling that we have now registered the | |
| // AudioWorkletProcessor, and should trigger the user onSuccess callback | |
| // of the emscripten_create_wasm_audio_worklet_processor_async() call. | |
| // | |
| // '_wsc' is short for 'wasm call', using an identifier that will never | |
| // conflict with user messages. | |
| // | |
| // Note: we convert the pointer arg manually here since the call site | |
| // ($_EmAudioDispatchProcessorCallback) is used with various signatures | |
| // and we do not know the types in advance. | |
| port.postMessage({'_wsc': d.callback, args: [d.contextHandle, 1/*EM_TRUE*/, {{{ to64('d.userData') }}}] }); | |
| } else if (d['_wsc']) { | |
| getWasmTableEntry(d['_wsc'])(...d.args); | |
| }; | |
| } | |
| } // ENVIRONMENT_IS_AUDIO_WORKLET | |
Xet Storage Details
- Size:
- 15.7 kB
- Xet hash:
- abe6d74a8c24e3f1713604a60508f576a0d9002d2c3ac5268f9265d7645c04d9
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.