Skip to content

Commit a28e606

Browse files
authored
Merge pull request #23 from idsinge/webcomponent
Add MediaRecorder 1ch experiment and demo panel, doc consistency fixes
2 parents 337cdf9 + ce431f8 commit a28e606

13 files changed

Lines changed: 624 additions & 23 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
**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).
1212

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.
1414

1515
---
1616

@@ -62,6 +62,8 @@ src/
6262
experiments/ — Research-only experiment pages (served by npm run dev, also published to GitHub Pages under /dev/)
6363
mr2ch.html — MediaRecorder 2ch stereo capture feasibility experiment
6464
mr2ch.js — Experiment logic (standalone, no component dependency)
65+
mr1ch.html — MediaRecorder 1ch experiment: direct mic capture, exposes start-timing bias
66+
mr1ch.js — Experiment logic (standalone, no component dependency)
6567
assets/
6668
ERC_logo.png
6769
docs/
@@ -93,7 +95,7 @@ tests/
9395

9496
```
9597
demo/
96-
index.html — Public integration showcase: loads dist/latency-test.legacy.iife.js; grid of 8 demo panels
98+
index.html — Public integration showcase: loads dist/latency-test.legacy.iife.js; grid of 9 demo panels
9799
style.css — Demo-only styles: card grid, panels, result boxes, event log, audio info table
98100
js/
99101
common.js — Shared setup: getUserMedia + AudioContext (created once), card-grid toggle, audio info
@@ -104,6 +106,7 @@ demo/
104106
context-share.js — Panel: Context Share — demonstrates host-managed AudioContext & MediaStream pattern
105107
mode-toggle.js — Panel: Mode Toggle — runs MediaRecorder then AudioWorklet sequentially for A/B comparison
106108
audioworklet.js — Panel: AudioWorklet — recording-mode="audioworklet", multi-run, aggregate stats
109+
mr1ch.js — Panel: MediaRecorder 1ch — recording-mode="mediarecorder-1ch" fallback, multi-run, aggregate stats
107110
lifecycle.js — Panel: Lifecycle — logs all six latency-* events with timestamps
108111
debug.js — Panel: Debug Mode — intercepts console.debug to surface [latency-test] lines on-page
109112
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
208211

209212
## Current Implementation Notes
210213

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:
212215

213216
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.
214217

@@ -224,7 +227,7 @@ The web component refactor (Phases 1–3a) is complete. Previous design issues a
224227

225228
## Web Component Status
226229

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:
228231

229232
- Shadow DOM (open mode, empty — headless-first)
230233
- `start()` / `stop()` public methods
@@ -233,7 +236,6 @@ Phases 1–3a are complete. The `<latency-test>` Custom Element is implemented w
233236
- `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
234237

235238
**Still in progress:**
236-
- Phase 3b: complete ✅
237239
- Phase 4: histogram, browser verification matrix across all three modes
238240

239241
**Planned configurable attributes (beyond `number-of-tests`, `mls-bits`, `max-lag-ms`):**

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A Web Component for measuring browser round-trip audio latency in Web Audio appl
44

55
## What it does
66

7-
<img src="./assets/MLS_Test.png" alt="MLS round-trip latency measurement diagram" width="700"/>
7+
<img src="https://raw.githubusercontent.com/idsinge/latency-test/main/assets/MLS_Test.png" alt="MLS round-trip latency measurement diagram" width="700"/>
88

99
- Measures round-trip browser audio latency using an [MLS (Maximum Length Sequence)](https://en.wikipedia.org/wiki/Maximum_length_sequence) signal and cross-correlation
1010
- Designed for integration into Web Audio and DAW-like web applications
@@ -71,11 +71,12 @@ Multiple consecutive tests with aggregate statistics:
7171

7272
Full integration docs are published via VitePress (see `docs/`):
7373

74+
- **Docs site:** https://idsinge.github.io/latency-test/
7475
- **API reference** — attributes, methods, events, algorithm constants: `docs/api.md`
7576
- **Framework examples** — Vanilla JS, React, Vue, Svelte, Angular, Next.js: `docs/examples/`
7677
- **Installation** — npm, CDN, AudioContext sharing: `docs/install.md`
7778
- **Live demo:** https://idsinge.github.io/latency-test/demo/
78-
- **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)
7980

8081
## Local development
8182

@@ -105,14 +106,14 @@ npm run docs:dev
105106
Other commands:
106107

107108
```bash
108-
npm test # run unit tests (Node 18, no install needed)
109+
npm test # run unit tests (Node 18+, no install needed)
109110
npm run build:component # build the component bundle (dist/)
110111
npm run build:component:legacy # legacy build: lowers private fields + optional chaining for Safari 14 / Chrome 78
111112
npm run docs:build # build VitePress docs
112113
npm run docs:preview # preview built docs locally
113114
```
114115

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.
116117

117118
## Repository scope
118119

@@ -149,7 +150,7 @@ This project originates from research on browser round-trip audio latency presen
149150

150151
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).
151152

152-
<img src="./assets/ERC_logo.png" alt="European Research Council logo" width="250"/>
153+
<img src="https://raw.githubusercontent.com/idsinge/latency-test/main/assets/ERC_logo.png" alt="European Research Council logo" width="250"/>
153154

154155
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).
155156

agents/CLAUDE_REVIEW.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ git push --follow-tags # pushes tag → triggers the publish workflow
569569
- The 18 dB reliability threshold and 600 ms maxLag are research-derived constants — do not change them without asking.
570570
- 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.
571571
- `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.
573573
- **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.
574574
- **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.
575575
- **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.

agents/SESSION_HANDOFF.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# SESSION_HANDOFF.md — Current State Handoff
22

3-
Last updated: 2026-06-05
3+
Last updated: 2026-06-10
44

55
LLMs reading this file: read `CLAUDE.md`, `AGENTS.md`, and `agents/CLAUDE_REVIEW.md` first. Do not modify files without explicit user approval.
66

@@ -11,14 +11,24 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee
1111
## Current Repo State
1212

1313
- 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`.
1515
- `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`.
1717
- `src/dev-test/` contains development test pages served by `npm run dev` (no build needed), also published to GitHub Pages under `/dev/`.
1818
- `src/experiments/` contains research-only experiment pages (not part of the component test suite), also published to GitHub Pages under `/dev/`.
1919
- 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/`.
2020
- 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.
2232

2333
---
2434

@@ -99,7 +109,7 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee
99109

100110
- Do not commit `dist/` — keep it generated.
101111
- 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.
103113
- The component must not close a host-provided `AudioContext` or stop a host-provided `MediaStream`.
104114
- Three `recording-mode` values each measure a **different pipeline** — do not flatten them. The differences are research data.
105115
- `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
118128
npm publish
119129
```
120130

121-
`prepublishOnly` auto-runs `npm run build:component` before every publish.
131+
`prepublishOnly` auto-runs `npm run build:component:all` before every publish.
122132

123133
Version strategy: v1.x keeps `"mediarecorder"` as default; v2.0.0 switches default to `"audioworklet"` (breaking).

agents/SESSION_MODEL_FIX.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ This indicates first-run cold-start on the newly created AudioContext — a sepa
115115

116116
## Firefox macOS — AudioContext Initialization Order
117117

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.
119119
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.
121121

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.
123125

124126
---
125127

0 commit comments

Comments
 (0)