Status: implemented, largely as designed below. Differences from the original proposal:
- Segmented control uses
data-audio="on"/"off"(not a separate toggle component) and lives inpublic/index.html's#side-panel, between Sketch and Libraries - matching the anchor toggle's existing pattern. - The injected controller is
buildAudioController(enabled, volume)inpublic/script.js(a function, not a static template string likecaptureController, since the initial gain needs to be baked in per run). The postMessage type isaudio-setwith{ enabled, volume }in one message rather than two separate types. - Uses
Reflect.construct(RealAC, arguments)to forward constructor arguments to the realAudioContext, then shadowsdestinationas an own property on that instance (not the prototype) - this avoids ever needing to redefine a possibly non-configurable prototype accessor. - The open questions below (autoplay policy, recording integration, volume
curve, WebKitGTK coverage) are unresolved - this was a v1, not a final
design. In particular, if
p5.soundis ever bundled, note that it may capture its ownAudioContextreference when it first loads (before the controller has a chance to patchwindow.AudioContext, since the library<script>tag is in<head>and this controller runs later, in<body>)- unverified until p5.sound is actually used with this.
Sketches can already produce audio (raw Web Audio API, or p5.sound if the user
brings it in via libraries.json or the new "Import JS Library" button), but
there is no app-level control over it: no way to mute all sketch audio, and no
master volume. For full-screen/kiosk-style deployment (the project's stated
goal) an operator needs a single, reliable ON/OFF and a master level that
works regardless of what audio API a given sketch happens to use.
A new panel-section titled Sound, placed after Sketch and before
Libraries (it's a sketch-output setting like Sketch/Capture, not a library
concern):
Sound
[ Off | On ] <- segmented toggle, same visual pattern as the
Sketch section's anchor toggle (Center/Top Left)
Master [====================o] 0.00-1.00, step 0.01
"Sketch audio is muted by default. Turn it on to hear output; browser
autoplay policy may require a click inside the sketch before sound starts."
- Defaults to Off - a silent-by-default app is the safer default for unattended/full-screen installs, and matches "let the user turn audio on" in the request.
- The Master slider is enabled/disabled visually in step with the toggle, but its value is retained even when muted (matches the existing Sketch background-colour control, which keeps its value across anchor changes).
The app doesn't control which audio API a sketch uses - p5.sound, raw
AudioContext, or something an imported library brings in. Hooking every
possible library individually doesn't scale. Instead, patch the audio
destination, once, generically:
- Inject a small controller (same pattern as
captureControllerinscript.js, prepended into the sketch iframe'ssrcdocbefore the sketch code) that wrapswindow.AudioContext(andwebkitAudioContextfor older WebKitGTK):(Sketch/proof-of-concept form - see Open questions below for the two-message vs one-message API choice.)(function () { var RealAC = window.AudioContext || window.webkitAudioContext; if (!RealAC) return; // no Web Audio in this webview - Sound section no-ops var masterGain = null; function patched() { var ctx = new RealAC(...arguments); masterGain = ctx.createGain(); masterGain.gain.value = 0; // starts muted; parent pushes real state below masterGain.connect(ctx.destination); // Redefine `destination` so anything the sketch connects to it // (oscillator.connect(ctx.destination), p5.sound's master bus, etc.) // actually lands on our gain node instead of the real output. Object.defineProperty(ctx, 'destination', { get: function () { return masterGain; } }); return ctx; } patched.prototype = RealAC.prototype; window.AudioContext = window.webkitAudioContext = patched; window.addEventListener('message', function (event) { var data = event.data || {}; if (data.type === 'audio-set-enabled' || data.type === 'audio-set-volume') { if (masterGain) { masterGain.gain.value = data.enabled === false ? 0 : (data.volume != null ? data.volume : masterGain.gain.value); } } }); })();
script.jskeepsaudioEnabled/audioMasterVolumestate exactly likesketchBg/sketchAnchor: baked into the initial gain value whenrunSketch()builds thesrcdoc, and pushed live viasketchFrame.contentWindow.postMessage(...)when the toggle or slider changes while a sketch is running (same live-update patternapplySketchBg()already uses).
This means no changes to libraries.json or the capture pipeline are
needed, and it works whether the sketch uses p5.sound, plain Web Audio, or an
imported library - because they all eventually reach AudioContext.destination.
No new capability crosses the sandbox boundary: the postMessage payload is a
boolean and a 0-1 float, and the iframe stays sandbox="allow-scripts" with
no allow-same-origin, exactly as today. The parent cannot read or capture
sketch audio (no allow-same-origin means no cross-frame audio graph
introspection either) - this proposal is output-side muting/leveling only.
- Autoplay policy. Browsers (Chromium/WebKit) require a user gesture
before audio plays. The Run button click happens in the parent document;
it does not automatically count as a gesture inside the freshly-created
sandboxed iframe. First cut: document the limitation (a click inside the
sketch canvas unlocks audio, same as any web page). If that's not
acceptable for kiosk use, a follow-up could auto-resume the
AudioContextfrom theset-anchor/set-bgstyle postMessage handshake that already fires right after Run, though this may still be blocked by policy without a genuine gesture. - Recording integration.
captureController's WebM recording is video-only today (canvas frames viadrawImage). A future extension could merge amasterGaintap (createMediaStreamDestination()) into the recorded stream so captured video has audio - out of scope for this proposal, but the master-gain design leaves the door open since it's a single, well-known node to tap. - Volume curve. Linear 0-1 gain (as requested) is simplest and matches
the ask. Human loudness perception is closer to logarithmic; if the linear
slider feels front-loaded in testing, a follow-up could map the slider
through a perceptual curve before writing
gain.value. - WebKitGTK coverage. Like
MediaRecorder(already a known limitation inAGENTS.md), Web Audio support varies by WebKitGTK version. The controller should no-op cleanly (as sketched above) rather than throw whenAudioContextisn't available.