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

Merged
merged 23 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from 18 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
41 changes: 21 additions & 20 deletions src/audio_worklet.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,56 +65,57 @@ function createWasmAudioWorkletProcessor(audioParams) {
inputsPtr = stackAlloc(stackMemoryNeeded);

// Copy input audio descriptor structs and data to Wasm
k = inputsPtr >> 2;
k = inputsPtr;
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;
k += {{{ C_STRUCTS.AudioSampleFrame.__size__ / 4 }}};
{{{ makeSetValue('k', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'i.length', 'u32') }}};
{{{ makeSetValue('k', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}};
{{{ makeSetValue('k', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}};
k += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}};
// 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 = outputsPtr;
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;
k += {{{ C_STRUCTS.AudioSampleFrame.__size__ / 4 }}};
{{{ makeSetValue('k', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'i.length', 'u32') }}};
{{{ makeSetValue('k', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}};
{{{ makeSetValue('k', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}};
k += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}};
// Reserve space for the output data
dataPtr += bytesPerChannel * i.length;
}

// Copy parameters descriptor structs and data to Wasm
paramsPtr = dataPtr;
k = paramsPtr >> 2;
k = paramsPtr;
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;
k += {{{ C_STRUCTS.AudioParamFrame.__size__ / 4 }}};
{{{ makeSetValue('k', C_STRUCTS.AudioParamFrame.length, 'paramArray.length', 'u32') }}};
{{{ makeSetValue('k', C_STRUCTS.AudioParamFrame.data, 'dataPtr', '*') }}};
k += {{{ C_STRUCTS.AudioParamFrame.__size__ }}};
// Marshal the audio parameters array
HEAPF32.set(paramArray, dataPtr>>2);
dataPtr += paramArray.length*4;
HEAPF32.set(paramArray, {{{ getHeapOffset('dataPtr', 'float') }}});
dataPtr += paramArray.length * {{{ getNativeTypeSize('float') }}};
}

// 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, {{{ to64('inputsPtr') }}}, numOutputs, {{{ to64('outputsPtr') }}}, numParams, {{{ to64('paramsPtr') }}}, {{{ to64('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,7 +169,7 @@ 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': d.callback, args: [d.contextHandle, 1/*EM_TRUE*/, {{{ to64('d.userData') }}}] });
} else if (d['_wsc']) {
getWasmTableEntry(d['_wsc'])(...d.args);
};
Expand Down
39 changes: 21 additions & 18 deletions src/lib/libwebaudio.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ var 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 @@ var 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 @@ var 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;
var audioParams = [],
numAudioParams = HEAPU32[options+1],
audioParamDescriptors = HEAPU32[options+2] >> 2,
i = 0;
var audioParams = [];
var processorName = UTF8ToString({{{ makeGetValue('options', C_STRUCTS.WebAudioWorkletProcessorCreateOptions.name, '*') }}});
var numAudioParams = {{{ makeGetValue('options', C_STRUCTS.WebAudioWorkletProcessorCreateOptions.numAudioParams, 'i32') }}};
var audioParamDescriptors = {{{ makeGetValue('options', C_STRUCTS.WebAudioWorkletProcessorCreateOptions.audioParamDescriptors, '*') }}};
var paramIndex = 0;

while (numAudioParams--) {
audioParams.push({
name: i++,
defaultValue: HEAPF32[audioParamDescriptors++],
minValue: HEAPF32[audioParamDescriptors++],
maxValue: HEAPF32[audioParamDescriptors++],
automationRate: ['a','k'][HEAPU32[audioParamDescriptors++]] + '-rate',
name: paramIndex++,
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 @@ var 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 undefined;
heapIndex = {{{ getHeapOffset('heapIndex', 'i32') }}};
var channelCounts = [];
while (numOutputs--) channelCounts.push(HEAPU32[heapIndex++]);
return channelCounts;
}

var optionsOutputs = options ? {{{ makeGetValue('options', C_STRUCTS.EmscriptenAudioWorkletNodeCreateOptions.numberOfOutputs, 'i32') }}} : 0;
var opts = options ? {
numberOfInputs: HEAP32[options],
numberOfOutputs: HEAP32[options+1],
outputChannelCount: HEAPU32[options+2] ? readChannelCountArray(HEAPU32[options+2]>>2, HEAP32[options+1]) : undefined,
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