dasAudio: strudel in the WASM playground + single-threaded teardown - #3272
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enables dasAudio + strudel playback in the Emscripten/WASM playground (single-threaded Web Audio backend), and adjusts teardown/leak-reporting behavior so browser-loop programs can stop cleanly and be re-run without false-positive leak dumps.
Changes:
- Enable dasAudio for the web build and embed dasAudio/strudel
.dasmodule sources into the WASM package. - Fix single-threaded WASM teardown assumptions (jobque
Wait()behavior + strudel shutdown draining) and add introspection helpers (count_jobque_leaks,sound_set_null_device,audio_is_single_threaded). - Add new playground audio samples plus Playwright/@wasm and interpreter-only device teardown tests.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web/examples/ui/src/main.js | Adds an AudioContext “autoplay unlock” wrapper to resume contexts created during a Run. |
| web/examples/ui/samples/examples/audio_strudel.das | New strudel synth arpeggio sample using init/update/shutdown lifecycle. |
| web/examples/ui/samples/examples/audio_sine.das | New simple sine-beep sample for WASM audio smoke testing. |
| web/examples/ui/samples/data.json | Registers the new audio samples in the playground picker. |
| web/CMakeLists.txt | Enables dasAudio on web builds and embeds dasAudio .das trees into the WASM filesystem. |
| utils/daScript/main.cpp | Adjusts leak reporting for Emscripten browser-loop programs; adds post-loop leak check. |
| tests/strudel_device/test_device_teardown.das | Adds headless/null-backend device teardown regression tests (interpreter-only). |
| tests/.das_test | Gates tests/strudel_device off under --use-aot due to known AOT timing/race issues. |
| src/misc/job_que.cpp | Makes JobStatus::Wait() a no-op on single-threaded Emscripten (no pthreads). |
| src/builtin/module_builtin_jobque.cpp | Exposes count_jobque_leaks() to scripts for before/after leak assertions. |
| site/tests/playground/audio.spec.js | Adds Playwright tests that verify real non-zero Web Audio output and eventual stop. |
| modules/dasAudio/strudel/strudel_player.das | Drains audio command queue during shutdown only when audio is single-threaded. |
| modules/dasAudio/src/dasAudio.h | Declares dasAudio_set_null_device and dasAudio_is_single_threaded API. |
| modules/dasAudio/src/dasAudio.cpp | Implements null-backend forcing and records single-threaded init mode. |
| modules/dasAudio/CMakeLists.txt | Registers missing strudel module .das files for static/embedded builds. |
| include/daScript/simulate/aot_builtin_jobque.h | Adds AOT header declaration for count_jobque_leaks(). |
| doc/source/stdlib/handmade/function-jobque-count_jobque_leaks-0xb4ae32ea24efdbdf.rst | Documents count_jobque_leaks. |
| doc/source/stdlib/handmade/function-audio-sound_set_null_device-0x113af6c1887feebf.rst | Documents sound_set_null_device. |
| doc/source/stdlib/handmade/function-audio-audio_is_single_threaded-0xce7d8afbe2050e04.rst | Documents audio_is_single_threaded. |
| doc/reflections/das2rst.das | Updates reflection grouping so new APIs appear in generated docs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
borisbat
force-pushed
the
bbatkin/wasm-audio
branch
from
June 25, 2026 22:05
ad9c134 to
e6d772b
Compare
Enables dasAudio in the emscripten build so strudel plays in the web playground, single-threaded: miniaudio's ScriptProcessor backend runs its callback on the main thread, so no -pthread / SharedArrayBuffer / cross-origin isolation is needed. strudel_tick main-thread mode is non-blocking and the audio path is one thread, so the jobque-on-wasm problem is sidestepped rather than solved. Single-threaded teardown fix: - JobStatus::Wait() is a no-op on single-threaded wasm (no worker thread can ever signal it; a condition wait would deadlock). - strudel_shutdown drains the audio command queue to run the deferred status-box release ONLY when the audio system came up single-threaded (recorded at init, g_audio_single_threaded). Threaded builds keep the original join path -- a main-thread drain would race the audio thread's command_processor on the non-thread-safe mixer context (heap corruption, caught under AOT). - The wasm CLI's end-of-callMain DumpJobQueLeaks + exit(1) fired while a browser-loop program was still alive (false positives); gated off when a loop is active, with a real post-shutdown leak check in stop_browser_loop. dasAudio/CMakeLists.txt: register strudel_sfx/sfxr/modal/scales (were missing, which breaks any static/embedded build, including wasm). Tests: - tests/strudel_device/test_device_teardown.das drives the real device (miniaudio null backend, headless) through create/tick/shutdown + re-run and asserts no net JobQue leak. Interpreter-only (gated in .das_test): AOT'ing a device+thread test surfaces a pre-existing main-thread drain race in audio_system_finalize; no device test is AOT'd. - site/tests/playground/audio.spec.js (@wasm) runs the strudel sample, taps the Web Audio graph for real output, and confirms it stops. New bindings: count_jobque_leaks, sound_set_null_device, audio_is_single_threaded. Playground: installAudioUnlock (resumes the AudioContext on the Run gesture) plus two audio samples. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XvWu2izV1ebb2LgHwHaWjp
borisbat
force-pushed
the
bbatkin/wasm-audio
branch
from
June 25, 2026 23:33
e6d772b to
f178020
Compare
borisbat
added a commit
that referenced
this pull request
Jul 25, 2026
…ry/ doc archiving (#3563) * release: bump version to 0.6.4 + 0.6.4 changelist Version 0.6.3 -> 0.6.4 across platform.h, CMake, Sphinx conf, the get_das_version doc example, and install/README. CHANGELIST.md gains the 0.6.4 (July 2026) section covering #3272-#3562. version_update.md skill corrected: conf.py carries the full X.Y.Z in both fields (de-facto since 0.6.3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * install: sync SDK CLAUDE.md to 0.6.4, ship dashv + find_dupe skills, scrub personal paths install/CLAUDE.md picks up the 0.6.4-era language content (gen2 default parser, bare named calls, with (module), cast parens, 16/8-bit lattice, distinct types, addr<T?> sugar, delete-on-pointer-containers, options stack, reserved words, the require hyphen rule, STYLE034/035 rows) plus dashv/find_dupe skill rows; install/skills.list ships both skills. Skills scrub: no personal names or machine-local paths in skill content (external_module_debugging examples use a generic package root now). Top-level CLAUDE.md: the named-arguments bullet documents the bare foo(pos, name = value) form (probe-verified), and the stale pre-#3420 reinterpret operand-swallow gotcha is removed (probe-verified: the cast is self-delimiting now). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * history/: archive completed-arc docs + doc_archiving skill New skills/doc_archiving.md defines the archive-vs-stay test and the process: git mv under history/<area>/, update path-qualified references, add a note in the area's living index doc, and record every doc in the Archive log at the bottom of history/README.md (the findability ledger). Swept per the skill: the root rework docs (FIXED_ARRAY_REWORK, ANNOTATION_INFO_REWORK, QUOTE_LOWERING, COVERAGE_GAP), the dasSQLITE API rework corpus + tutorial-mockup design artifacts, the dasSpirv PHASE6-9 working plans, closed dasLLAMA docs (avx_kernel_matrix, epyc9654_measurements, model_expansion_plan), the benchmarks/sql linq_fold plans/audits, example plans (sfx_lab, scalar_packing, sequence, Cadmus), and the daScriptTest migration checklist. ~33 path-qualified references updated across daslib, tests, tutorials, RST, and skills; living contracts (PROVIDER_CONTRACT, the MASTERPLANs, cited dasLLAMA specs, results.md ledgers) stay put and carry archive notes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * tests/linq: clear pre-existing lint in files touched by the archive sweep The archive sweep's comment-path updates put 7 linq_fold test files into CI's changed-files lint set, surfacing 20 pre-existing warnings: unused eid block arguments on create_entities callbacks (now [unused_argument(eid)] per the in-file precedent — the name is decs-bound and cannot be underscore-renamed) and unused require math / require strings lines (dropped). tests/linq suite: 2007/2007 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * release: finish 0.6.4 docs, site, and archive ledger Update the two manual titles and all live site version chips to 0.6.4, correct the version-update instructions to match established full-X.Y.Z practice, and clarify the Sphinx version comment. Expand the archive ledger to one exact old-to-new entry per moved document and repair local links in the archived docs. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Enables dasAudio in the emscripten/WASM build so strudel makes sound in the web playground — single-threaded. miniaudio's emscripten backend uses a ScriptProcessorNode whose audio callback runs on the main thread, and strudel's
strudel_tickmain-thread mode is non-blocking, so the whole stack collapses to one thread: no-pthread, noSharedArrayBuffer, no COOP/COEP. (The "is jobque compatible with wasm" question turned out to be the wrong question — the audio path routes around it.)You can hear it in the playground: pick "Audio: strudel synth arpeggio" → Run.
The single-threaded teardown fix (the meat)
Single-threaded wasm has no separate audio thread, which breaks two teardown assumptions:
JobStatus::Wait()(used byStream/LockBoxjoin) would deadlock waiting for a thread that can't exist → no-op on single-threaded wasm (#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)). Native unchanged.set_status_updatedefers a ref-release to an audio-thread command (notify_and_release). Single-threaded there's no thread to process it, sostrudel_shutdownmust draincommand_processoritself. But draining on the main thread races the audio thread on native (concurrentcommand_processoron the non-thread-safe mixer context → heap corruption, caught under AOT). Fix: record at init whether the audio system came up single-threaded (g_audio_single_threaded) and drain only then; threaded builds keep the original join path untouched.Also fixes a false-positive leak dump: the wasm CLI's end-of-
callMainDumpJobQueLeaks+exit(1)fired the instant a browser-loop program (init/update/shutdown) installed its loop — while its objects were legitimately still alive. Gated off when a loop is active, with a real post-shutdown()leak check instop_browser_loop.dasAudio CMake fix (independent, worth landing on its own)
modules/dasAudio/CMakeLists.txtwas missingstrudel_sfx/strudel_sfxr/strudel_modal/strudel_scalesregistrations → breaks any static/embedded build (the wasm one, and any embedded SDK). Desktop hid it via a real-FS fallback.Tests
tests/strudel_device/test_device_teardown.das— drives the real device (miniaudio null backend, headless/CI) through create/tick/shutdown + re-run, asserts no net JobQue leak. Interpreter-only (gated intests/.das_test): AOT'ing a device+thread test surfaces the pre-existing main-thread drain race inaudio_system_finalize; no device test is AOT'd anywhere.site/tests/playground/audio.spec.js(@wasm) — runs the strudel sample in the playground, taps the Web Audio graph for real non-zero output, confirms it stops.Notes for review
installAudioUnlock(main.js) resumes the AudioContext on the Run-click gesture — standard Web Audio autoplay handling (the playground's run path awaits beforecallMain).count_jobque_leaks,sound_set_null_device,audio_is_single_threaded(documented).Validation
Native interp + AOT (no regression; MCP failures during testing were dashv staleness, gone with a clean build), wasm playground (play/stop/re-run, leak-clean), lint + format + das2rst/sphinx doc gates, JIT smoke (no verifier errors).
🤖 Generated with Claude Code
https://claude.ai/code/session_01XvWu2izV1ebb2LgHwHaWjp