Document JamesDSP audio ownership audit

This commit is contained in:
Jonathan Sykes
2026-10-09 19:42:11 +08:00
parent 933e920701
commit d6a3570ff9

30
plans/jamesdsp-audit.md Normal file
View File

@@ -0,0 +1,30 @@
# JamesDSP integration audit
Date: 2026-10-09
Base: `933e920` (`main`, Phase 0–5, offline completion, UI and transport integration)
## Audio ownership found in the current runtime
| Concern | Current owner / finding | Integration consequence |
|---|---|---|
| Realtime context | `EQ` in `frontend/app.js` is the only runtime constructor (`new AC()` around line 5532). | Preserve this exact lazy creation point. JamesDSP may not instantiate another context. |
| Media element sources | `EQ.attach()` is the only runtime `createMediaElementSource()` call (around line 5559); it records sources by element in `st.sources`. | Extract this into one AudioGraph owner with a retained `WeakMap`/`Map`. Never retry creation for a previously attached media element. |
| Playback elements | `Player.master`, `Player.secondary`, and `Player.soundEl` change between `els.video` and `els.audio` for dual/audio-only playback and handoffs. `EQ.apply()` attaches both video and audio elements. | Attach each element once; route both through the same selected insert. Do not tie graph lifetime to a track or to `Player.master`. |
| Continuity and transitions | `audio-continuity.js` owns play/resume/alignment. `Transition` changes the active master and uses a bridge element; the bridge is not currently connected to EQ. | Keep continuity and bridge timing unchanged. A JamesDSP route applies to the same player elements as the Standard route; do not source the bridge or alter transition policy. |
| iOS live processing | `EQ.available()` is false on iOS unless `data.settings.liveAudioIOS === true` (around line 5516). `EqRender` prepares saved audio with a 32 kHz `OfflineAudioContext`. | Gate live JamesDSP on the same setting and supported realtime graph. V1 does not process EqRender output; a DSP graph must never be layered over an already-rendered Standard EQ copy. |
| Level / limiter | `Level` measures at 22.05 kHz offline and applies dB gain through `EQ.setLevel()`. The realtime Standard chain ends in a compressor limiter. | Preserve the Level measurement and dB value. In JamesDSP mode map it into the engine gain with a documented combined-gain clamp; Standard remains untouched. |
| EqRender | `EqRender` renders cached media through an offline graph at 32 kHz and saves `eq-renders` OPFS copies. It is independent from the single realtime `AudioContext`. | Leave this renderer and its bytes unchanged. Until offline worklet rendering is proven, JamesDSP does not claim to process saved/rendered copies. |
| VocalReducer / widener | Loaded lazily by `feature:vocal-reducer` and `feature:eq-core`, then connected in `EQ.build()`. | In JamesDSP mode disconnect the Standard insert. Do not create either lazy Standard effect solely to initialize JamesDSP. |
| Cold path | `audio-continuity.js`, `eq-parameters.js`, and `app.js` are core assets. JamesDSP has no existing runtime assets. | Keep all new vendor/worklet/wasm/controller files exclusively in lazy `feature:jamesdsp`; the small graph owner may be core only if unavoidable, and its cost must be checked against integration cold-start medians. |
## Seam and compatibility decisions
Phase 2 will add a small `frontend/audio-graph.js` core seam because it gives the one-context/one-source invariant an independently testable owner. It will create the context only when the existing EQ asks for it, retain each element source for the page lifetime, and expose controlled Standard and optional JamesDSP route operations. It will not change playback, transition, or volume code. The new core request is a measured risk: compare cold FCP/bytes to `plans/integration-report.md` and inline the seam into an existing core module if it causes a regression.
The Standard graph's node order, filter constants, gain automation, lazy-load triggers, and destination routing are a frozen compatibility contract. Phase 2 will add a graph trace test comparing the old route with the extracted route, plus deterministic sample-response tests for the Standard chain. JamesDSP activation is opt-in and serialized: restore Standard before destroying a failed node, never create a replacement until the previous 128 MiB node is destroyed, and reject the engine when the existing context rate is not 44.1 or 48 kHz. The existing context is never closed/recreated to request 48 kHz.
The standalone port verifies 44.1/48 kHz, a fixed 128 MiB heap, immediate exact bypass, and configuration that can pause worklet rendering. It has no Safari/iPhone verification. V1 will report JamesDSP live processing unavailable on iOS unless `liveAudioIOS` is on; EqRender/offline rendered playback remains Standard. Text effects (Liveprog, DDC, arbitrary text EQ) will be neither shown nor accepted by the v1 settings/import surface. IR state is memory-only because JSON presets do not contain its PCM.
## Licensing and release boundaries
The standalone source is pinned by commit in the vendor `VERSION` file and its supplied notices are retained. Its upstream `Main/LICENSE` contains GPL-2.0 text while the port documents GPL-3.0-or-later additions; this discrepancy is recorded and no license file will be silently rewritten. A redistribution decision remains an owner release matter. The user instruction prohibits pushing or deploying, so Phase 7 will prepare beta listening notes only and will not publish a beta build.