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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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") }}};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second line doesn't look quite right to me.

userData I assume is a pointer, which means it should be Number and not a bigint (it should be converted to a Number when it first arrived on the JS side).

If it needs to be converted back to a pointer (i64) when this.callback then you should be using to64 at the callback call side, and not here.

toIndexType should only be used for places where you are direclty accessing the table, or the memory bounds.

Can you test with -sMEMORY64=2 as well perhaps to catch these cases where there is difference between to64 and toIndexType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's to convert it back to a pointer. It's constant, so I removed the need to keep doing the conversion.

I can test with -sMEMORY64=2 and put the conversions near the call.

// 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we convert to one-line-per-declaration here? I think it would help make this more readable, and should not effect minified code size.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happily - I just followed the original style.


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