Skip to content

[AUDIO_WORKLET] Add support for MEMORY64 with 2GB and 4GB heap #24732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions src/audio_worklet.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ function createWasmAudioWorkletProcessor(audioParams) {
assert(opts.callback)
assert(opts.samplesPerChannel)
#endif
this.callback = getWasmTableEntry(opts.callback);
this.userData = opts.userData;
this.callback = getWasmTableEntry({{{ toIndexType("opts.callback") }}});
this.userData = {{{ toIndexType("opts.userData") }}};
// Then the samples per channel to process, fixed for the lifetime of the
// context that created this processor. Note for when moving to Web Audio
// 1.1: the typed array passed to process() should be the same size as this
Expand Down Expand Up @@ -65,56 +65,66 @@ function createWasmAudioWorkletProcessor(audioParams) {
inputsPtr = stackAlloc(stackMemoryNeeded);

// Copy input audio descriptor structs and data to Wasm
k = inputsPtr >> 2;
k = {{{ getHeapOffset('inputsPtr', 'u32') }}};
dataPtr = inputsPtr + numInputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}};
for (i of inputList) {
// Write the AudioSampleFrame struct instance
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.numberOfChannels / 4 }}}] = i.length;
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.samplesPerChannel / 4 }}}] = this.samplesPerChannel;
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.data / 4 }}}] = dataPtr;
#if MEMORY64
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.data / 4 + 1 }}}] = dataPtr / 0x100000000;
#endif
k += {{{ C_STRUCTS.AudioSampleFrame.__size__ / 4 }}};
// Marshal the input audio sample data for each audio channel of this input
for (j of i) {
HEAPF32.set(j, dataPtr>>2);
HEAPF32.set(j, {{{ getHeapOffset('dataPtr', 'float') }}});
dataPtr += bytesPerChannel;
}
}

// Copy output audio descriptor structs to Wasm
outputsPtr = dataPtr;
k = outputsPtr >> 2;
outputDataPtr = (dataPtr += numOutputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}) >> 2;
k = {{{ getHeapOffset('outputsPtr', 'u32') }}};
outputDataPtr = (dataPtr += numOutputs * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}});
for (i of outputList) {
// Write the AudioSampleFrame struct instance
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.numberOfChannels / 4 }}}] = i.length;
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.samplesPerChannel / 4 }}}] = this.samplesPerChannel;
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.data / 4 }}}] = dataPtr;
#if MEMORY64
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.data / 4 + 1 }}}] = dataPtr / 0x100000000;
#endif
k += {{{ C_STRUCTS.AudioSampleFrame.__size__ / 4 }}};
// Reserve space for the output data
dataPtr += bytesPerChannel * i.length;
}

// Copy parameters descriptor structs and data to Wasm
paramsPtr = dataPtr;
k = paramsPtr >> 2;
k = {{{ getHeapOffset('paramsPtr', 'u32') }}};
dataPtr += numParams * {{{ C_STRUCTS.AudioParamFrame.__size__ }}};

for (i = 0; paramArray = parameters[i++];) {
// Write the AudioParamFrame struct instance
HEAPU32[k + {{{ C_STRUCTS.AudioParamFrame.length / 4 }}}] = paramArray.length;
HEAPU32[k + {{{ C_STRUCTS.AudioParamFrame.data / 4 }}}] = dataPtr;
#if MEMORY64
HEAPU32[k + {{{ C_STRUCTS.AudioSampleFrame.data / 4 + 1 }}}] = dataPtr / 0x100000000;
#endif
k += {{{ C_STRUCTS.AudioParamFrame.__size__ / 4 }}};
// Marshal the audio parameters array
HEAPF32.set(paramArray, dataPtr>>2);
HEAPF32.set(paramArray, {{{ getHeapOffset('dataPtr', 'float') }}});
dataPtr += paramArray.length*4;
}

// Call out to Wasm callback to perform audio processing
if (didProduceAudio = this.callback(numInputs, inputsPtr, numOutputs, outputsPtr, numParams, paramsPtr, this.userData)) {
if (didProduceAudio = this.callback(numInputs, {{{ toIndexType('inputsPtr') }}}, numOutputs, {{{ toIndexType('outputsPtr') }}}, numParams, {{{ toIndexType('paramsPtr') }}}, this.userData)) {
// Read back the produced audio data to all outputs and their channels.
// (A garbage-free function TypedArray.copy(dstTypedArray, dstOffset,
// srcTypedArray, srcOffset, count) would sure be handy.. but web does
// not have one, so manually copy all bytes in)
outputDataPtr = {{{ getHeapOffset('outputDataPtr', 'float') }}};
for (i of outputList) {
for (j of i) {
for (k = 0; k < this.samplesPerChannel; ++k) {
Expand Down Expand Up @@ -168,9 +178,9 @@ class BootstrapMessages extends AudioWorkletProcessor {
//
// '_wsc' is short for 'wasm call', using an identifier that will never
// conflict with user messages
messagePort.postMessage({'_wsc': d.callback, args: [d.contextHandle, 1/*EM_TRUE*/, d.userData] });
messagePort.postMessage({'_wsc': {{{ toIndexType("d.callback") }}}, args: [d.contextHandle, 1/*EM_TRUE*/, {{{ toIndexType("d.userData") }}}] });
} else if (d['_wsc']) {
getWasmTableEntry(d['_wsc'])(...d.args);
getWasmTableEntry({{{ toIndexType("d['_wsc']") }}})(...d.args);
};
}
}
Expand Down
35 changes: 19 additions & 16 deletions src/lib/libwebaudio.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ let LibraryWebAudio = {
#if WEBAUDIO_DEBUG
console.log(`emscripten_resume_audio_context_async() callback: New audio state="${EmAudio[contextHandle].state}", ID=${state}`);
#endif
{{{ makeDynCall('viii', 'callback') }}}(contextHandle, state, userData);
{{{ makeDynCall('viip', 'callback') }}}(contextHandle, state, userData);
}
#if WEBAUDIO_DEBUG
console.log(`emscripten_resume_audio_context_async() resuming...`);
Expand Down Expand Up @@ -202,7 +202,7 @@ let LibraryWebAudio = {
}
});
audioWorklet.bootstrapMessage.port.onmessage = _EmAudioDispatchProcessorCallback;
{{{ makeDynCall('viii', 'callback') }}}(contextHandle, 1/*EM_TRUE*/, userData);
{{{ makeDynCall('viip', 'callback') }}}(contextHandle, 1/*EM_TRUE*/, userData);
}).catch(audioWorkletCreationFailed);
},

Expand All @@ -222,32 +222,33 @@ let LibraryWebAudio = {
assert(EmAudio[contextHandle] instanceof (window.AudioContext || window.webkitAudioContext), `Called emscripten_create_wasm_audio_worklet_processor_async() on a context handle ${contextHandle} that is not an AudioContext, but of type ${typeof EmAudio[contextHandle]}`);
#endif

options >>= 2;
let audioParams = [],
numAudioParams = HEAPU32[options+1],
audioParamDescriptors = HEAPU32[options+2] >> 2,
var audioParams = [],
processorName = UTF8ToString({{{ makeGetValue('options', C_STRUCTS.WebAudioWorkletProcessorCreateOptions.name, '*') }}}),
numAudioParams = {{{ makeGetValue('options', C_STRUCTS.WebAudioWorkletProcessorCreateOptions.numAudioParams, 'i32') }}},
audioParamDescriptors = {{{ makeGetValue('options', C_STRUCTS.WebAudioWorkletProcessorCreateOptions.audioParamDescriptors, '*') }}},
i = 0;

while (numAudioParams--) {
audioParams.push({
name: i++,
defaultValue: HEAPF32[audioParamDescriptors++],
minValue: HEAPF32[audioParamDescriptors++],
maxValue: HEAPF32[audioParamDescriptors++],
automationRate: ['a','k'][HEAPU32[audioParamDescriptors++]] + '-rate',
defaultValue: {{{ makeGetValue('audioParamDescriptors', C_STRUCTS.WebAudioParamDescriptor.defaultValue, 'float') }}},
minValue: {{{ makeGetValue('audioParamDescriptors', C_STRUCTS.WebAudioParamDescriptor.minValue, 'float') }}},
maxValue: {{{ makeGetValue('audioParamDescriptors', C_STRUCTS.WebAudioParamDescriptor.maxValue, 'float') }}},
automationRate: ({{{ makeGetValue('audioParamDescriptors', C_STRUCTS.WebAudioParamDescriptor.automationRate, 'i32') }}} ? 'k' : 'a') + '-rate',
});
audioParamDescriptors += {{{ C_STRUCTS.WebAudioParamDescriptor.__size__ }}};
}

#if WEBAUDIO_DEBUG
console.log(`emscripten_create_wasm_audio_worklet_processor_async() creating a new AudioWorklet processor with name ${UTF8ToString(HEAPU32[options])}`);
console.log(`emscripten_create_wasm_audio_worklet_processor_async() creating a new AudioWorklet processor with name ${processorName}`);
#endif

EmAudio[contextHandle].audioWorklet.bootstrapMessage.port.postMessage({
// Deliberately mangled and short names used here ('_wpn', the 'Worklet
// Processor Name' used as a 'key' to verify the message type so as to
// not get accidentally mixed with user submitted messages, the remainder
// for space saving reasons, abbreviated from their variable names).
'_wpn': UTF8ToString(HEAPU32[options]),
'_wpn': processorName,
audioParams,
contextHandle,
callback,
Expand All @@ -262,18 +263,20 @@ let LibraryWebAudio = {
assert(EmAudio[contextHandle], `Called emscripten_create_wasm_audio_worklet_node() with a nonexisting/already freed Web Audio Context handle ${contextHandle}!`);
assert(EmAudio[contextHandle] instanceof (window.AudioContext || window.webkitAudioContext), `Called emscripten_create_wasm_audio_worklet_node() on a context handle ${contextHandle} that is not an AudioContext, but of type ${typeof EmAudio[contextHandle]}`);
#endif
options >>= 2;

function readChannelCountArray(heapIndex, numOutputs) {
if (!heapIndex) return void 0;
heapIndex = {{{ getHeapOffset('heapIndex', 'i32') }}};
let channelCounts = [];
while (numOutputs--) channelCounts.push(HEAPU32[heapIndex++]);
return channelCounts;
}

let optionsOutputs = options ? {{{ makeGetValue('options', C_STRUCTS.EmscriptenAudioWorkletNodeCreateOptions.numberOfOutputs, 'i32') }}} : 0;
let opts = options ? {
numberOfInputs: HEAP32[options],
numberOfOutputs: HEAP32[options+1],
outputChannelCount: HEAPU32[options+2] ? readChannelCountArray(HEAPU32[options+2]>>2, HEAP32[options+1]) : void 0,
numberOfInputs: {{{ makeGetValue('options', C_STRUCTS.EmscriptenAudioWorkletNodeCreateOptions.numberOfInputs, 'i32') }}},
numberOfOutputs: optionsOutputs,
outputChannelCount: readChannelCountArray({{{ makeGetValue('options', C_STRUCTS.EmscriptenAudioWorkletNodeCreateOptions.outputChannelCounts, 'i32*') }}}, optionsOutputs),
processorOptions: {
callback,
userData,
Expand Down
16 changes: 16 additions & 0 deletions src/struct_info.json
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,17 @@
{
"file": "emscripten/webaudio.h",
"structs": {
"WebAudioParamDescriptor": [
"defaultValue",
"minValue",
"maxValue",
"automationRate"
],
"WebAudioWorkletProcessorCreateOptions": [
"name",
"numAudioParams",
"audioParamDescriptors"
],
"AudioSampleFrame": [
"numberOfChannels",
"samplesPerChannel",
Expand All @@ -1274,6 +1285,11 @@
"AudioParamFrame": [
"length",
"data"
],
"EmscriptenAudioWorkletNodeCreateOptions": [
"numberOfInputs",
"numberOfOutputs",
"outputChannelCounts"
]
}
},
Expand Down
19 changes: 19 additions & 0 deletions src/struct_info_generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,12 @@
"numberOfChannels": 0,
"samplesPerChannel": 4
},
"EmscriptenAudioWorkletNodeCreateOptions": {
"__size__": 12,
"numberOfInputs": 0,
"numberOfOutputs": 4,
"outputChannelCounts": 8
},
"EmscriptenBatteryEvent": {
"__size__": 32,
"charging": 24,
Expand Down Expand Up @@ -1479,6 +1485,19 @@
"module": 4,
"nextInChain": 0
},
"WebAudioParamDescriptor": {
"__size__": 16,
"automationRate": 12,
"defaultValue": 0,
"maxValue": 8,
"minValue": 4
},
"WebAudioWorkletProcessorCreateOptions": {
"__size__": 12,
"audioParamDescriptors": 8,
"name": 0,
"numAudioParams": 4
},
"__cxa_exception": {
"__size__": 24,
"adjustedPtr": 16,
Expand Down
19 changes: 19 additions & 0 deletions src/struct_info_generated_wasm64.json
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,12 @@
"numberOfChannels": 0,
"samplesPerChannel": 4
},
"EmscriptenAudioWorkletNodeCreateOptions": {
"__size__": 16,
"numberOfInputs": 0,
"numberOfOutputs": 4,
"outputChannelCounts": 8
},
"EmscriptenBatteryEvent": {
"__size__": 32,
"charging": 24,
Expand Down Expand Up @@ -1479,6 +1485,19 @@
"module": 8,
"nextInChain": 0
},
"WebAudioParamDescriptor": {
"__size__": 16,
"automationRate": 12,
"defaultValue": 0,
"maxValue": 8,
"minValue": 4
},
"WebAudioWorkletProcessorCreateOptions": {
"__size__": 24,
"audioParamDescriptors": 16,
"name": 0,
"numAudioParams": 8
},
"__cxa_exception": {
"__size__": 48,
"adjustedPtr": 32,
Expand Down
Loading