From dd24e1832ea951622ee6fda604a8c62244325790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Tue, 21 Apr 2026 10:16:24 +0200 Subject: [PATCH] wip: wasm: use miniaudio instead of sdl in emscripten --- src/ossia/audio/audio_engine.cpp | 23 +++++++++++++++++++++ src/ossia/audio/miniaudio_protocol.hpp | 28 +++++++++++++++++++++++--- src/ossia_features.cmake | 7 +++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/ossia/audio/audio_engine.cpp b/src/ossia/audio/audio_engine.cpp index df5022b801e..cc67e022527 100644 --- a/src/ossia/audio/audio_engine.cpp +++ b/src/ossia/audio/audio_engine.cpp @@ -2,6 +2,7 @@ #include #include #include +//#include #include #include #include @@ -56,6 +57,13 @@ void audio_engine::sync() req++; request.store(req); +#if defined(__EMSCRIPTEN__) + // On WASM, the audio runs in a worklet thread. We cannot spin-wait + // on the main thread as it blocks the browser event loop entirely. + // The tick will pick up the new request on its next callback. + return; +#endif + // The engine has started running, we wait for a couple iterations // to leave some time for the ticks to be updated int k = 0; @@ -120,7 +128,22 @@ ossia::audio_engine* make_audio_engine( bs = 1024; inputs = 0; outputs = 2; +#if OSSIA_ENABLE_MINIAUDIO + { + static auto ctx = std::make_shared(); + static bool ctx_init = false; + if(!ctx_init) + { + auto cfg = ma_context_config_init(); + ma_context_init(nullptr, 0, &cfg, &ctx->context); + ctx_init = true; + } + ma_device_id in_id{}, out_id{}; + return new ossia::miniaudio_engine{ctx, name, in_id, out_id, inputs, outputs, rate, bs}; + } +#else return new ossia::sdl_protocol{rate, bs}; +#endif #endif if(0) diff --git a/src/ossia/audio/miniaudio_protocol.hpp b/src/ossia/audio/miniaudio_protocol.hpp index 83e492493ac..dcb19e12e83 100644 --- a/src/ossia/audio/miniaudio_protocol.hpp +++ b/src/ossia/audio/miniaudio_protocol.hpp @@ -8,8 +8,13 @@ #define MA_NO_RUNTIME_LINKING 1 #endif #define MA_ENABLE_ONLY_SPECIFIC_BACKENDS 1 +#if defined(__EMSCRIPTEN__) +#define MA_ENABLE_WEBAUDIO 1 +#define MA_ENABLE_AUDIO_WORKLETS 1 +#else #define MA_ENABLE_COREAUDIO 1 #define MA_ENABLE_ALSA 1 +#endif #define MA_NO_WAV 1 #define MA_NO_FLAC 1 #define MA_NO_MP3 1 @@ -22,7 +27,7 @@ #include #include -#include +//#include #include @@ -144,6 +149,7 @@ class miniaudio_engine final : public audio_engine static void callback(ma_device* pDevice, void* output, const void* input, ma_uint32 nframes) { +#if !defined(__EMSCRIPTEN__) [[maybe_unused]] static const thread_local auto _ = [] { @@ -151,11 +157,10 @@ class miniaudio_engine final : public audio_engine ossia::set_thread_pinned(thread_type::Audio, 0); return 0; }(); +#endif auto& self = *static_cast(pDevice->pUserData); self.tick_start(); - if(!self.m_start) - self.m_start = std::chrono::steady_clock::now(); if(self.stop_processing) { @@ -167,7 +172,9 @@ class miniaudio_engine final : public audio_engine auto ins_data = self.ins_data.data(); for(int i = 0; i < self.effective_inputs; i++) ins[i] = ins_data + i * nframes; +#if !defined(__EMSCRIPTEN__) kfr::deinterleave(ins, (float*)input, self.effective_inputs, nframes); +#endif auto outs = self.outs.data(); auto outs_data = self.outs_data.data(); @@ -175,11 +182,20 @@ class miniaudio_engine final : public audio_engine for(int i = 0; i < self.effective_outputs; i++) outs[i] = outs_data + i * nframes; +#if defined(__EMSCRIPTEN__) + // On WASM audio worklets, std::chrono::steady_clock is unavailable. + // Use the sample count to derive time instead. + self.m_frames_elapsed += nframes; + double nsecs = (double)self.m_frames_elapsed / self.effective_sample_rate; +#else + if(!self.m_start) + self.m_start = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); auto nsecs = std::chrono::duration_cast(now - *self.m_start) .count() / 1e9; +#endif ossia::audio_tick_state ts{(float* const*)ins, outs, self.effective_inputs, self.effective_outputs, nframes, nsecs}; @@ -187,13 +203,19 @@ class miniaudio_engine final : public audio_engine self.tick_end(); +#if !defined(__EMSCRIPTEN__) kfr::interleave( (float*)output, (const float**)outs, self.effective_outputs, nframes); +#endif } std::shared_ptr m_ctx; ma_device m_stream; +#if defined(__EMSCRIPTEN__) + uint64_t m_frames_elapsed{}; +#else std::optional m_start; +#endif boost::container::vector ins_data; boost::container::vector ins; diff --git a/src/ossia_features.cmake b/src/ossia_features.cmake index a7c3e7dde30..9cc8b58456d 100644 --- a/src/ossia_features.cmake +++ b/src/ossia_features.cmake @@ -277,6 +277,13 @@ if(OSSIA_DATAFLOW) target_link_libraries(ossia PRIVATE $) endif() + # MiniAudio / Web Audio support + if(CMAKE_SYSTEM_NAME MATCHES Emscripten) + target_include_directories(ossia PRIVATE $) + target_compile_definitions(ossia PRIVATE MA_ENABLE_AUDIO_WORKLETS MA_ENABLE_WEBAUDIO) + target_link_options(ossia PUBLIC -sAUDIO_WORKLET=1 -sWASM_WORKERS=1) + endif() + if(OSSIA_ENABLE_LIBSAMPLERATE) target_link_libraries(ossia PRIVATE $) endif()