You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@
10
10
11
11
**weblatencytest** is a proof-of-concept web application that measures browser round-trip audio latency using an MLS (Maximum Length Sequence) signal and cross-correlation. It is a research tool associated with a WAC 2025 paper (see README.md for citation).
12
12
13
-
**Future goal (in progress):**Convert this app into a reusable Web Component that can be embedded in other Web Audio projects. v1 ships with `MediaRecorder` as the default recording backend. v2 will switch the default to `AudioWorklet` for sample-accurate raw PCM capture.
13
+
**Status:**The `<latency-test>`Web Component is live and published as `@adasp/latency-test` (v1.x). v1 ships with `MediaRecorder` (2-channel) as the default recording backend. v2 will switch the default to `AudioWorklet` for sample-accurate raw PCM capture.
14
14
15
15
---
16
16
@@ -62,6 +62,8 @@ src/
62
62
experiments/ — Research-only experiment pages (served by npm run dev, also published to GitHub Pages under /dev/)
host-gain.js — Panel: Host Gain — ChannelSplitter + GainNode chain for low-level mics (e.g. Safari)
@@ -208,7 +211,7 @@ Results are dispatched as `CustomEvent` from the element. The demo page renders
208
211
209
212
## Current Implementation Notes
210
213
211
-
The web component refactor (Phases 1–3a) is complete. Previous design issues are resolved. Remaining known limitations:
214
+
Phases 1–3b are complete. Previous design issues are resolved. Remaining known limitations:
212
215
213
216
1.**`input-gain` not yet wired** — The attribute is observed and the property is settable, but no `GainNode` is created. Setting `input-gain` has no effect in the current code. Use the host-gain pattern instead (see `docs/examples/host-gain.md`). Deferred to v2.
214
217
@@ -224,7 +227,7 @@ The web component refactor (Phases 1–3a) is complete. Previous design issues a
224
227
225
228
## Web Component Status
226
229
227
-
Phases 1–3a are complete. The `<latency-test>` Custom Element is implemented with:
230
+
Phases 1–3b are complete. The `<latency-test>` Custom Element is implemented with:
228
231
229
232
- Shadow DOM (open mode, empty — headless-first)
230
233
-`start()` / `stop()` public methods
@@ -233,7 +236,6 @@ Phases 1–3a are complete. The `<latency-test>` Custom Element is implemented w
233
236
-`worker.js` cross-correlates two buffers: in the audioworklet path these are `{ mic, ref }` Float32 PCM; in the mediarecorder (2ch) path these are `ch0` (mic) and `ch1` (reference) from the decoded stereo recording; in the mediarecorder-1ch path these are the decoded mono recording vs the pre-generated MLS AudioBuffer
234
237
235
238
**Still in progress:**
236
-
- Phase 3b: complete ✅
237
239
- Phase 4: histogram, browser verification matrix across all three modes
- Measures round-trip browser audio latency using an [MLS (Maximum Length Sequence)](https://en.wikipedia.org/wiki/Maximum_length_sequence) signal and cross-correlation
10
10
- Designed for integration into Web Audio and DAW-like web applications
@@ -71,11 +71,12 @@ Multiple consecutive tests with aggregate statistics:
71
71
72
72
Full integration docs are published via VitePress (see `docs/`):
-**Dev & research pages:**https://idsinge.github.io/latency-test/dev/ (test pages and experiments, including the MediaRecorder 2ch stereo capture feasibility experiment)
79
+
-**Dev & research pages:**https://idsinge.github.io/latency-test/dev/ (test pages and experiments, including the MediaRecorder 2ch and 1ch experiments)
79
80
80
81
## Local development
81
82
@@ -105,14 +106,14 @@ npm run docs:dev
105
106
Other commands:
106
107
107
108
```bash
108
-
npm test# run unit tests (Node 18, no install needed)
109
+
npm test# run unit tests (Node 18+, no install needed)
109
110
npm run build:component # build the component bundle (dist/)
110
111
npm run build:component:legacy # legacy build: lowers private fields + optional chaining for Safari 14 / Chrome 78
111
112
npm run docs:build # build VitePress docs
112
113
npm run docs:preview # preview built docs locally
113
114
```
114
115
115
-
**Requirement:** Node.js v18 or above (project pins v18.12.1 via `.nvmrc`).
116
+
**Requirement:** Node.js v18 or above (minimum tested). Development is pinned to Node 22 via `.nvmrc`; the docs CI deploy uses Node 24.
116
117
117
118
## Repository scope
118
119
@@ -149,7 +150,7 @@ This project originates from research on browser round-trip audio latency presen
149
150
150
151
This work is developed as part of the project *Hybrid and Interpretable Deep Neural Audio Machines*, funded by the **European Research Council (ERC)** under the European Union's Horizon Europe research and innovation programme (grant agreement No. 101052978).
151
152
152
-
<imgsrc="./assets/ERC_logo.png"alt="European Research Council logo"width="250"/>
153
+
<imgsrc="https://raw.githubusercontent.com/idsinge/latency-test/main/assets/ERC_logo.png"alt="European Research Council logo"width="250"/>
153
154
154
155
We also thank [Louis Bahrman](https://github.com/Louis-Bahrman) for his collaboration on this project, including his contributions to the [Python/Google Colab notebook for MLS-based latency estimation](https://gist.github.com/gilpanal/f6a64a8fe797190bba22123dfea29611).
Copy file name to clipboardExpand all lines: agents/CLAUDE_REVIEW.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -569,7 +569,7 @@ git push --follow-tags # pushes tag → triggers the publish workflow
569
569
- The 18 dB reliability threshold and 600 ms maxLag are research-derived constants — do not change them without asking.
570
570
- The Safari-specific `getCorrectStreamForSafari()` method is **removed in Phase 1**. Gain compensation is now a general `input-gain` property set by the host — the component applies whatever value it receives and does no browser detection internally.
571
571
- `helper.js`no longer exists — do not reference it or attempt to import from it.
572
-
- **Docs homepage expectation management:** `docs/index.md` must always carry a visible draft/work-in-progress signal near the top (currently at line 30). The homepage shows install and usage code snippets that read like a published package — without an explicit notice, readers will assume the package already exists. Do not remove or soften this notice until the package is actually published on npm.
572
+
- **Docs homepage expectation management (resolved):** while the package was unpublished, `docs/index.md` was required to carry a visible draft/work-in-progress notice so readers would not assume the package existed. `@adasp/latency-test` is now live on npm and the notice has been removed — this requirement no longer applies.
573
573
- **Phase 3 dual-channel capture is not optional:** A WAC 2025 peer reviewer identified that the current single-channel MediaRecorder approach has an uncontrolled timing offset between `mediaRecorder.start()` and `noiseSource.start()`. The AudioWorklet processor must use `numberOfInputs: 2` — mic on input 0, reference signal loopback on input 1 — and cross-correlate the two captures. The `naomiaro/recording-calibration` reference implements this correctly. Do not implement Phase 3 as a direct MediaRecorder-to-AudioWorklet port without adopting this two-channel architecture.
574
574
- **Three `recording-mode` values — each measures a different pipeline:** `"mediarecorder"` (default, dual-channel via `ChannelMergerNode` + `MediaStreamDestinationNode`, removes start-timing bias, channel-relative stable but not sample-accurate, measures a **different pipeline** due to extra Web Audio nodes — `createMediaStreamSource` is unavoidable; overhead direction is browser-dependent; emits `latency-error` if browser downmixes to mono); `"mediarecorder-1ch"` (single-channel, direct mic stream, has unknown start-timing *bias* — not just jitter — but is the closest to the production DAW recording path; use as fallback when `"mediarecorder"` fails); `"audioworklet"` (raw PCM, shared AudioContext clock, accuracy reference). Do not flatten these into a single implementation — the mode differences are research data.
575
575
- **Timing bias vs. jitter distinction:** the single-channel `"mediarecorder"` path has a *systematic timing bias* (the unknown JS start offset between `noiseSource.start()` and `mediaRecorder.start()` shifts the measured lag on every run). `maxLag` makes the correlation peak searchable but does not cancel this offset. Always use the word "bias" not "jitter" when describing this effect in docs or code comments.
Copy file name to clipboardExpand all lines: agents/SESSION_HANDOFF.md
+16-6Lines changed: 16 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# SESSION_HANDOFF.md — Current State Handoff
2
2
3
-
Last updated: 2026-06-05
3
+
Last updated: 2026-06-10
4
4
5
5
LLMs reading this file: read `CLAUDE.md`, `AGENTS.md`, and `agents/CLAUDE_REVIEW.md` first. Do not modify files without explicit user approval.
6
6
@@ -11,14 +11,24 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee
11
11
## Current Repo State
12
12
13
13
- Working branch: `webcomponent`. All PRs merged — branch is ahead of `main` with active development.
14
-
- Phases 1–7 complete. **v1.0.2** is live on npm as `@adasp/latency-test`.
14
+
- Phases 1–7 complete. **v1.1.0** is live on npm as `@adasp/latency-test`.
15
15
-`dist/` remains gitignored — generated with `npm run build:component`.
16
-
-`demo/` validates the built IIFE bundle via `../dist/latency-test.iife.js`. Run with `npm run build:component && npm run demo`.
16
+
-`demo/` validates the built IIFE bundle via `../dist/latency-test.legacy.iife.js`. Run with `npm run build:component:legacy && npm run demo`.
17
17
-`src/dev-test/` contains development test pages served by `npm run dev` (no build needed), also published to GitHub Pages under `/dev/`.
18
18
-`src/experiments/` contains research-only experiment pages (not part of the component test suite), also published to GitHub Pages under `/dev/`.
19
19
- GitHub Pages deploys the VitePress docs site from `docs/.vitepress/dist/` via `.github/workflows/docs.yml`. `src/` is also copied to `dist/dev/` — dev and experiment pages accessible at `https://idsinge.github.io/latency-test/dev/`.
20
20
- VitePress base is `/latency-test/` → site at `https://idsinge.github.io/latency-test/`.
21
-
-`.nvmrc` pins Node 22. `docs.yml` CI uses Node 24. CDN URLs in `docs/install.md` are pinned to `@1.0.2`.
21
+
-`.nvmrc` pins Node 22. `docs.yml` CI uses Node 24. CDN URLs in `docs/install.md` are pinned to `@1.1.0`.
22
+
23
+
---
24
+
25
+
## Recently Completed (2026-06-10)
26
+
27
+
### MediaRecorder 1ch experiment
28
+
-`src/experiments/mr1ch.html` + `mr1ch.js` added — standalone 1ch MediaRecorder experiment for comparison against `mr2ch`. Records mic directly from `inputStream` via `MediaRecorder`; plays MLS reference to `ac.destination`; cross-correlates decoded mic recording (`getChannelData(0)`) against pre-generated `noiseBuffer.getChannelData(0)`. Intentionally exposes the start-timing bias between `MediaRecorder.start()` and `noiseSource.start()` — the bias that the 2ch experiment eliminates. Includes the Codex-recommended safety improvement: `MediaRecorder.stop()` called in the `catch` path if `noiseSource.start()` throws after recording has started.
29
+
-`src/index.html` — link added under Experiments section.
30
+
-`CLAUDE.md` — file map updated; "Future goal" wording replaced with current status; phase references corrected to 1–3b throughout.
31
+
-`README.md` — dev & research pages description updated to mention both 2ch and 1ch experiments.
22
32
23
33
---
24
34
@@ -99,7 +109,7 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee
99
109
100
110
- Do not commit `dist/` — keep it generated.
101
111
- Do not move the interactive demo into `docs/public/` — `demo/` is an integration fixture, not VitePress source.
102
-
-Always call `getUserMedia` before `new AudioContext()` in demos and host examples — required for correct Firefox macOS sample rate selection.
112
+
-Create `new AudioContext()` before `getUserMedia()` in demos and host examples — ensures the AudioContext starts in running state in Firefox, making `outputLatency` available. The demos follow this order. See `agents/SESSION_MODEL_FIX.md` for historical context on why the order was previously reversed.
103
113
- The component must not close a host-provided `AudioContext` or stop a host-provided `MediaStream`.
104
114
- Three `recording-mode` values each measure a **different pipeline** — do not flatten them. The differences are research data.
105
115
-`recording-mode` should match the host app's real capture pipeline. AudioWorklet mode measures a minimal direct graph and is a lower-bound estimate for hosts with more complex AudioWorklet graphs. See Decision #15 in `agents/CLAUDE_REVIEW.md`.
@@ -118,6 +128,6 @@ git push --follow-tags
118
128
npm publish
119
129
```
120
130
121
-
`prepublishOnly` auto-runs `npm run build:component` before every publish.
131
+
`prepublishOnly` auto-runs `npm run build:component:all` before every publish.
122
132
123
133
Version strategy: v1.x keeps `"mediarecorder"` as default; v2.0.0 switches default to `"audioworklet"` (breaking).
Copy file name to clipboardExpand all lines: agents/SESSION_MODEL_FIX.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -115,11 +115,13 @@ This indicates first-run cold-start on the newly created AudioContext — a sepa
115
115
116
116
## Firefox macOS — AudioContext Initialization Order
117
117
118
-
**Confirmed on Firefox macOS:** creating `AudioContext`*before*`getUserMedia` produces a different sample rate than creating it *after*. The old component demo called `#setupAudioContext()`first, then `#acquireMic()`, which caused the AudioContext to initialise at a different rate (likely 48000 Hz) than CoreAudio's mic path (typically 44100 Hz). The MLS buffer was then created at the wrong rate, giving audibly different spectral content.
118
+
> **Historical — superseded 2026-06-10.** Current guidance: create `new AudioContext()`before `getUserMedia()`. See the superseded note at the end of this section.
119
119
120
-
The fix in `index.js` follows the same order as the original `main` branch: `getUserMedia` first, `new AudioContext()` second. This incidentally resolved the sound difference on Firefox macOS as well as the stream lifetime instability.
120
+
**Confirmed on Firefox macOS (historical):** creating `AudioContext`*before*`getUserMedia` produced a different sample rate than creating it *after*. The old component demo called `#setupAudioContext()` first, then `#acquireMic()`, which caused the AudioContext to initialise at a different rate (likely 48000 Hz) than CoreAudio's mic path (typically 44100 Hz). The MLS buffer was then created at the wrong rate, giving audibly different spectral content.
121
121
122
-
**Do not swap this order.** Always call `getUserMedia` before creating the `AudioContext` in any demo or host app on this project.
122
+
The fix at the time in `index.js` followed the same order as the original `main` branch: `getUserMedia` first, `new AudioContext()` second. This incidentally resolved the sound difference on Firefox macOS as well as the stream lifetime instability.
123
+
124
+
**Superseded (2026-06-10):** The demos and experiments now create `new AudioContext()` before `getUserMedia()`. This ensures the AudioContext starts in running state in Firefox, making `outputLatency` available — which outweighs the sample rate concern that motivated the original order. The sample rate mismatch described above is no longer treated as a blocking issue.
0 commit comments