From cfa6f3d4c9d9da98d339b63f86660f126cfa900f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:42:18 +0000 Subject: [PATCH 1/2] Initial plan From ead4bb5d491b4e85a049cc54254961a6712b39c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:21:49 +0000 Subject: [PATCH 2/2] Fix web version audio: enable AudioWorklets with JSPI support - Add MA_ENABLE_AUDIO_WORKLETS define to enable miniaudio's AudioWorklet path for WASM builds - Add -sJSPI, -sAUDIO_WORKLET=1, -sWASM_WORKERS=1 emscripten flags - Remove -sMAIN_MODULE=1 (incompatible with -sWASM_WORKERS=1) - Remove emscripten_builtin_memalign exports (no longer needed without MAIN_MODULE) - Create src/platform/wasm/soluna_audio_wasm.js: override emscripten_request_animation_frame_loop to wrap the RAF callback with WebAssembly.promising() so that emscripten_sleep() called during miniaudio's audio worklet initialization uses JSPI suspension instead of throwing a SuspendError Co-authored-by: cloudwu <729648+cloudwu@users.noreply.github.com> Agent-Logs-Url: https://github.com/cloudwu/soluna/sessions/21adb45c-8735-4dda-8cd7-3ce5749dcbff --- make.lua | 8 +++--- src/platform/wasm/soluna_audio_wasm.js | 37 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 src/platform/wasm/soluna_audio_wasm.js diff --git a/make.lua b/make.lua index 84d97c8..880877c 100644 --- a/make.lua +++ b/make.lua @@ -109,6 +109,7 @@ lm:conf { defines = { "_POSIX_C_SOURCE=200809L", "_GNU_SOURCE", + "MA_ENABLE_AUDIO_WORKLETS", }, }, defines = { @@ -134,9 +135,10 @@ lm:exe "soluna" { deps = deps, emcc = { ldflags = { - "-s MAIN_MODULE=1", - "-Wl,-u,emscripten_builtin_memalign", - "-Wl,--export=emscripten_builtin_memalign", + "-sJSPI", + "-sAUDIO_WORKLET=1", + "-sWASM_WORKERS=1", + "--js-library=src/platform/wasm/soluna_audio_wasm.js", }, }, } diff --git a/src/platform/wasm/soluna_audio_wasm.js b/src/platform/wasm/soluna_audio_wasm.js new file mode 100644 index 0000000..6721ffa --- /dev/null +++ b/src/platform/wasm/soluna_audio_wasm.js @@ -0,0 +1,37 @@ +// Override emscripten_request_animation_frame_loop to support JSPI. +// +// When JSPI is enabled (-sJSPI), audio worklet initialization calls +// emscripten_sleep() which requires the WebAssembly call stack to be +// in a "promising" context (wrapped with WebAssembly.promising). +// +// This override wraps the C frame callback with WebAssembly.promising +// so that emscripten_sleep works correctly during audio init. +mergeInto(LibraryManager.library, { + emscripten_request_animation_frame_loop__deps: ['$getWasmTableEntry'], + emscripten_request_animation_frame_loop__sig: 'vpp', + emscripten_request_animation_frame_loop: function(cb, userData) { + var fn = getWasmTableEntry(cb); + var promisingFn = null; + if (typeof WebAssembly !== 'undefined' && typeof WebAssembly.promising === 'function') { + try { + promisingFn = WebAssembly.promising(fn); + } catch (e) { + promisingFn = null; + } + } + function tick(rAF_time) { + if (promisingFn) { + promisingFn(rAF_time, userData).then(function(keepGoing) { + if (keepGoing) requestAnimationFrame(tick); + }).catch(function(e) { + console.error('Soluna: JSPI frame callback error (WebAssembly.promising):', e instanceof Error ? e.message : String(e), e); + }); + } else { + if (fn(rAF_time, userData)) { + requestAnimationFrame(tick); + } + } + } + requestAnimationFrame(tick); + }, +});