# JamesDSP in ytplayer — integration plan (2026-10-09) ## What exists - **Standalone PWA/port**: `~/development/personal/jamesdsp-web` (GPL-3 port of JamesDSPManager's portable core to WASM). `build.sh` -> `web/dsp/jamesdsp.{js,wasm}` (1.2 MB wasm), ES modules `web/jamesdsp-node.js`, `params.js`, `worklet.js`. Docs: `INTEGRATION.md`, `PLAN.md`, `VERIFICATION.md` (Chromium-only verified: 44.1/48 kHz, 15-band EQ, compander, bass, convolver, stereo, reverb, crossfeed, tube, liveprog/DDC/arbitrary EQ; fixed 128 MiB WASM heap per node; no real-phone/Safari testing). - **Prototype in ytplayer**: branch `codex/jamesdsp` (3 commits on old base 93d3b05): vendor/jamesdsp/{controller,jamesdsp-node,params,worklet}.js + wasm, `JamesDSP` IIFE in app.js, Settings group (enable, 5 presets, 3 EQ sliders, bass, crossfeed, limiter), sw.js precache, beta compose stack (`docker-compose.beta.yml`, beta.worship.hesed.sbs, already a deploy target "codex/jamesdsp"), `tests/jamesdsp.spec.js`. ## Problems the prototype does not solve (why it can't just be merged) 1. **Second AudioContext + second MediaElementSource**: app.js already owns `EQ` (app.js ~5500: 10-band biquad EQ, Level/limiter, VocalReducer, widener) which calls `createMediaElementSource` on `els.video`/`els.audio`. An element can have only ONE source, ever. The two systems fight; whichever attaches first wins and the other throws/silences. 2. **iOS background playback**: Safari suspends Web Audio when the screen locks; the existing EQ is disabled on iOS unless `liveAudioIOS` is on, and iOS saved songs get EQ rendered into a copy (`EqRender`, `eqAudioUrl`). The prototype ignores this -> likely silence on lock screen. 3. **Dual elements / audio-continuity** (`audio-continuity.js`, master/secondary, `Player.soundEl`) and crossfade/preload swaps. 4. **Assets**: now manifest-driven, lazy `assets.json` groups with `contract`, N-1 retention, offline completion job, per-file hashed URLs. The wasm/worklet must be a lazy group (`feature:jamesdsp`), in the manifest, cached for offline, never in first paint/install-critical set, and fetched only after opt-in (or idle-prefetch only on Wi-Fi). The worklet module URL must be a hashed immutable URL resolvable offline; wasm bytes are fetched on the main thread and passed via processorOptions (already designed so). 5. **CORS/proxy**: streaming `