From ffe9bbbf43fa1df1a04b6245ddc675aa9a0a5d1b Mon Sep 17 00:00:00 2001 From: gilpanal Date: Fri, 12 Jun 2026 12:46:08 +0200 Subject: [PATCH] feat: remove never-wired input-gain API, add CHANGELOG, prep 1.2.0 release docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove input-gain attribute and inputGain property (never wired to a GainNode; gain is permanently a host responsibility via the host-gain pattern) from element, types, docs, and agent docs - Add CHANGELOG.md (Keep a Changelog): [Unreleased] removal entry with consumer-impact and semver policy notes; backfill 1.0.0-1.1.0 from tags - Ship CHANGELOG.md in the npm tarball (files[] — npm does not auto-include changelogs) - Rewrite Phase 7 per-release checklist with ordered release flow (push --follow-tags only after npm publish to avoid CDN 404 window); align SESSION_HANDOFF release procedure with it - Drop planned-v2 signal-type from the api.md primary example - Complete CLAUDE.md dev-commands block (typecheck, demo, build variants) - Supersession markers for stale input-gain guidance in CLAUDE_REVIEW.md Co-Authored-By: Claude Fable 5 --- AGENTS.md | 2 +- CHANGELOG.md | 40 ++++++++++++++++++++++++ CLAUDE.md | 11 ++++--- agents/CLAUDE_REVIEW.md | 48 ++++++++++++++--------------- agents/SESSION_HANDOFF.md | 33 +++++++++++++++----- docs/api.md | 7 ++--- docs/examples/host-gain.md | 4 +-- docs/examples/nextjs.md | 1 - docs/examples/react.md | 1 - docs/index.md | 1 - package.json | 1 + src/index.d.ts | 2 -- src/scripts/latency-test-element.js | 3 +- 13 files changed, 102 insertions(+), 52 deletions(-) create mode 100644 CHANGELOG.md diff --git a/AGENTS.md b/AGENTS.md index e9cbc5f..3e69fcb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -213,7 +213,7 @@ Do not re-open these unless the user explicitly asks: - Lifecycle events emitted: `latency-start`, `latency-recording`, `latency-processing`, `latency-result`, `latency-error`, `latency-complete`. All events must set `bubbles: true` and `composed: true`. -- Safari gain: host-controlled via `input-gain` attribute. No internal browser detection. +- Safari gain: host-controlled via the host-gain pattern (gain chain built by the host, processed stream passed as `inputStream`). No internal browser detection, no gain attribute on the component. - Distribution: npm + CDN are both first-class targets. Validate both before publishing. - Measurement inherits the host's audio environment: the component does not create an idealized setup. The host's `AudioContext`, stream constraints, and capture backend diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..accd426 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Removed + +- The `input-gain` attribute and `inputGain` property. They were documented and typed but never wired to a `GainNode` — setting them had no effect in any released version. Input gain is permanently a host responsibility: build a gain chain and pass the processed stream as `inputStream` (see [docs/examples/host-gain.md](docs/examples/host-gain.md)). Consumer impact: TypeScript code referencing `inputGain` will no longer compile, and `'input-gain'` is no longer listed in `LatencyTest.observedAttributes` — runtime behavior is otherwise unchanged. Policy note: this project treats removal of documented-but-never-functional API as a minor release; strict semver would classify any public API removal as major. + +## [1.1.0] - 2026-06-05 + +### Changed + +- Default `recording-mode="mediarecorder"` upgraded to 2-channel capture (`ChannelMergerNode` + `MediaStreamDestinationNode`), removing start-timing bias; emits `latency-error` if the browser downmixes to mono. + +### Added + +- `recording-mode="mediarecorder-1ch"` single-channel fallback mode (direct mic stream, start-timing bias present). + +## [1.0.2] - 2026-06-03 + +### Fixed + +- Web component, build, and error-handling fixes plus documentation accuracy corrections from the Phases 1–3a review; CI workflow added (test, build, docs, pack verification). + +## [1.0.1] - 2026-06-02 + +### Fixed + +- Documentation accuracy and hardening patch; removed a duplicate `inputGain` declaration. + +## [1.0.0] - 2026-06-02 + +### Added + +- Initial release: headless `` Web Component measuring browser round-trip audio latency via MLS and cross-correlation, with `mediarecorder` and `audioworklet` recording modes, ESM/IIFE bundles, TypeScript declarations, VitePress documentation, and live demo. diff --git a/CLAUDE.md b/CLAUDE.md index 9588217..e8b51ad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,9 +27,13 @@ **Dev commands:** ``` npm test # run unit tests (tests/mls.test.js + tests/worker.test.js) +npm run typecheck # tsc --noEmit — validates src/index.d.ts and JS against the types npm run dev # static file server — serves src/ natively +npm run demo # static file server — serves repo root (demo/ needs built dist/) npm run build:component # produces dist/latency-test.esm.js + .iife.js +npm run build:component:dev # unminified build for debugging npm run build:component:legacy # legacy build → dist/latency-test.legacy.{esm,iife}.js (Safari 14 / Chrome 78) +npm run build:component:all # modern + legacy builds (also run by prepublishOnly) npm run docs:dev # VitePress docs dev server (http://localhost:5173) npm run docs:build # build VitePress docs npm run docs:preview # preview built docs locally @@ -186,7 +190,7 @@ demo/ | MLS order (nbits) | 15 | Sequence length = 2^15 − 1 = 32767 samples | | maxLag | 0.600 × sampleRate | 600 ms search window for the correlation peak | | Reliability threshold | 18 dB | `10 × log10(peakPow / meanEnergy)` | -| Safari gain boost | 50× | Was applied automatically when Safari > v16 and echoCancellation is disabled — now host-controlled via `input-gain` attribute; `getCorrectStreamForSafari()` removed in Phase 1 | +| Safari gain boost | 50× | Was applied automatically when Safari > v16 and echoCancellation is disabled — now host-controlled via the host-gain pattern (see `docs/examples/host-gain.md`); `getCorrectStreamForSafari()` removed in Phase 1 | | AudioContext latencyHint | 0 | Requests minimum latency | | Mic constraints | echoCancellation: false, noiseSuppression: false, autoGainControl: false, channelCount: 1 | Essential for accurate measurement | @@ -213,7 +217,7 @@ Results are dispatched as `CustomEvent` from the element. The demo page renders Phases 1–3b are complete. Previous design issues are resolved. Remaining known limitations: -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. +1. **`input-gain` removed** — The attribute and `inputGain` property were removed (they were never wired to a GainNode). Input gain is permanently a host responsibility: build a gain chain and pass the processed stream as `inputStream` — see `docs/examples/host-gain.md`. Do not reintroduce the attribute. 2. **`signal-type` not yet wired** — Only `"mls"` is implemented. The attribute is observed but `signalType` is never read by `LatencyTestController`. Deferred to v2. @@ -244,7 +248,6 @@ Phases 1–3b are complete. The `` Custom Element is implemented w |---|---|---| | `recording-mode` | `"mediarecorder"` \| `"mediarecorder-1ch"` \| `"audioworklet"` | Selects the capture backend. `"mediarecorder"`: dual-channel via `ChannelMergerNode` + `MediaStreamDestinationNode`, no start-timing bias, v1 default (implemented). `"mediarecorder-1ch"`: single-channel, direct mic stream, start-timing bias present; use as fallback when browser downmixes stereo to mono (implemented). `"audioworklet"`: raw Float32 PCM, v2 default (implemented). Each mode measures a different pipeline — see Decision #14 in agents/CLAUDE_REVIEW.md. | | `signal-type` | `"mls"` \| `"chirp"` \| `"golay"` | Selects the test signal. `"mls"` is default. `"chirp"` is a logarithmic sine sweep. `"golay"` uses Golay complementary sequence pairs for high-SNR impulse response measurement. | -| `input-gain` | number \| `0` | Intended to apply a gain multiplier to the input stream before capture. **Not yet wired** — the attribute is observed and the property is settable, but no GainNode is created. Setting it has no effect in the current code. Deferred to v2. | | `debug` | boolean \| `false` | Enables `console.debug('[latency-test]', ...)` logging at key internal checkpoints. Development/debugging only — no effect on measurement output. Do not use during measurements you intend to record — `startPairSpanMs` is an upper-bound diagnostic span, not a pure inter-call gap, and DevTools being open can perturb scheduling generally. Implemented. | **External references used during design:** @@ -259,7 +262,7 @@ Phases 1–3b are complete. The `` Custom Element is implemented w - Chrome/Chromium/Edge: Standard behavior, higher latency variability. First-run latency is often higher than subsequent runs — mitigated by a silent AudioBuffer started at the top of every `prepareAudioToPlayAndRecord()` call (cwilso keepalive technique). - Firefox: Most stable results (std dev often 0), higher absolute latency on Windows -- Safari: Some devices have low microphone levels with `echoCancellation: false`. The `input-gain` attribute is intended to address this but is not yet wired (v2 item). Wired earpods force stereo input (only left channel used). +- Safari: Some devices have low microphone levels with `echoCancellation: false`. Use the host-gain pattern to compensate (see `docs/examples/host-gain.md`). Wired earpods force stereo input (only left channel used). - iOS: Some devices exhibit aliasing above 12 kHz on audio input, degrading MLS quality. `signal-type="chirp"` (planned, bandlimited to 1500–8000 Hz) will address this — not yet implemented. - All browsers: require HTTPS (or localhost) for `getUserMedia` diff --git a/agents/CLAUDE_REVIEW.md b/agents/CLAUDE_REVIEW.md index 2428dcc..632edf7 100644 --- a/agents/CLAUDE_REVIEW.md +++ b/agents/CLAUDE_REVIEW.md @@ -2,6 +2,8 @@ This file tracks open questions and the planned action plan for converting `weblatencytest` into a reusable Web Component with AudioWorklet-based recording. LLMs should read this file alongside CLAUDE.md before starting any work. +> **Editor's note (2026-06-12):** the `input-gain` attribute / `inputGain` property was dropped entirely — it was never wired to a GainNode and will not be added in any future version. All `input-gain` mentions below (decisions, tables, checklists, d.ts samples) are superseded and kept only as historical record. Input gain is permanently a host responsibility via the host-gain pattern (`docs/examples/host-gain.md`). Current API surface: CLAUDE.md and `docs/api.md`. + --- ## Decisions Made @@ -118,6 +120,8 @@ Shadow DOM, open mode, with an empty shadow root attached in the constructor. No **Resolved.** +> **Superseded (2026-06-12):** `input-gain` / `inputGain` was removed entirely and will not return. Do not set `element.inputGain` — use the host-gain pattern (`docs/examples/host-gain.md`). The two paragraphs below are historical. + The `input-gain` attribute is a general-purpose gain multiplier. **The GainNode is not yet wired** — the attribute is observed and the property is set, but no gain is applied in the current code. Wiring the GainNode is deferred to v2. No browser detection lives inside the component. The hardcoded `getCorrectStreamForSafari()` method in `test.js` is **removed during Phase 1**. Its responsibility moves to the host: if the host knows it is running on Safari ≥ 16 with `echoCancellation: false`, it sets `element.inputGain = 50` before calling `start()`. The demo page will include a code example showing this pattern. @@ -240,7 +244,7 @@ Below is the proposed sequence of migration tasks. **No file should be modified - [x] Implement data-return strategy: MessagePort accumulation, single post on stop (plain arrays, no transferables — MLS-length captures are small enough; SharedArrayBuffer deferred — Q1 still open) - [x] Worker.js contract unchanged — both paths use `{ command: 'correlation', data1, data2, maxLag }` - [x] Wire the worklet into the latency test flow alongside the existing MediaRecorder path, selected via `recording-mode` attribute -- [ ] `input-gain` GainNode deferred to v2 +- [x] `input-gain` dropped entirely (2026-06-12) — never wired; gain is permanently a host responsibility via the host-gain pattern (docs/examples/host-gain.md) - [ ] `signal-type="chirp"` bandlimit deferred - [x] Validate measurement stability across multiple runs (Chrome + Firefox — numbers match MediaRecorder path) @@ -368,6 +372,7 @@ Before the first publish, verify the following fields are present in `package.js "files": [ "dist/", "README.md", + "CHANGELOG.md", "LICENSE" ], "sideEffects": true, @@ -385,7 +390,7 @@ Key points: - No CJS exports — Web Components are browser-only APIs. `require('@adasp/latency-test')` would have no meaningful use. The `main` field points to the ESM bundle and `module` mirrors it; `unpkg`/`jsdelivr` fields point to the IIFE for CDN consumers. - `types` / `exports["types"]` points to the TypeScript declaration file — consumers get full IntelliSense with no manual setup. - `unpkg` / `jsdelivr` fields point to the IIFE bundle — so `unpkg.com/@adasp/latency-test` serves the script-tag-compatible version. -- `files` controls what gets included in the published package — only the built output, README, and LICENSE. Everything else (`src/`, `docs/`, `assets/`, config files) is excluded automatically. +- `files` controls what gets included in the published package — only the built output, README, CHANGELOG, and LICENSE. Everything else (`src/`, `docs/`, `assets/`, config files) is excluded automatically. Note: npm does NOT auto-include `CHANGELOG*` — it must be listed explicitly (README and LICENSE are auto-included). - `sideEffects: true` prevents bundler tree-shaking from removing the `customElements.define()` side effect. - `prepublishOnly` ensures the build runs before every publish, preventing a stale `dist/` from being published. - `publishConfig.access: "public"` is **required** for scoped packages (`@scope/name`) — without it npm defaults to private and the publish will either fail or charge for a private package. @@ -394,6 +399,8 @@ Key points: This file ships with the package and gives consumers typed access to the element, its properties, methods, and event payloads with no manual setup: +> **Superseded (2026-06-12):** the sample below is the original Phase 7 draft. The shipped `src/index.d.ts` differs: `inputGain` was removed entirely (never wired), and `signalType` is `'mls'` only in v1. Do not copy from this sample — `src/index.d.ts` is the canonical file. + ```ts export interface LatencyResultDetail { latency: number @@ -475,30 +482,21 @@ CLAUDE*.md CODEX_REVIEW.md ``` -#### Manual publishing (per release) - -```bash -# Log in to npm (only needed once per machine) -npm login - -# Build the component bundle -npm run build:component +#### Release checklist (per release — agreed 2026-06-12, supersedes the original manual-publishing steps) -# Dry run — inspect what will be published without actually publishing -npm publish --dry-run +**Ordering matters:** `.github/workflows/docs.yml` deploys GitHub Pages on every push to `main`, so the CDN pin bump and changelog stamp must not reach `main` before `npm publish` — otherwise the live docs reference a CDN version that 404s until publish. -# Publish (scoped packages require --access public on first publish) -npm publish --access public -``` - -For subsequent releases, bump the version in `package.json` first: - -```bash -npm version patch # 1.0.0 → 1.0.1 (bug fix) -npm version minor # 1.0.0 → 1.1.0 (new feature, backwards compatible) -npm version major # 1.0.0 → 2.0.0 (breaking change — e.g. AudioWorklet default) -npm publish -``` +1. **On the working branch:** add CHANGELOG.md entries under `[Unreleased]`; run verification — `npm run typecheck`, `npm test`, `npm run build:component:all`, `npm run docs:build`, `npm pack --dry-run` (inspect package contents). Commit → PR → merge to `main`. Docs still pin the previous version at this point — harmless. +2. **Locally on updated `main`:** + - Commit "release: prepare X.Y.Z docs" — stamp `[Unreleased]` → `[X.Y.Z]` with date in CHANGELOG.md and bump every CDN version pin (`docs/install.md`, `docs/index.md`, `docs/examples/vanilla-js.md`). + - `npm version patch|minor|major` — creates its own commit (package.json + package-lock.json) and the `vX.Y.Z` tag. There is no `version` lifecycle script in package.json, hence the separate prep commit. + - `npm publish` — `prepublishOnly` runs `build:component:all` automatically. **Do not push yet.** (`npm login` once per machine; `publishConfig.access: public` is already set.) + - `git push --follow-tags` — only after publish succeeds. This triggers the Pages deploy with now-valid pins. +3. **Post-publish verification:** + - `npm view @adasp/latency-test@X.Y.Z version files types exports` + - Confirm the published `dist/index.d.ts` matches `src/index.d.ts` (inspect the tarball from the registry, not the local `dist/`) + - Fetch the jsDelivr/unpkg ESM and IIFE URLs once the CDN propagates + - Create a GitHub Release on the `vX.Y.Z` tag, copying the CHANGELOG entry #### Versioning plan @@ -567,7 +565,7 @@ git push --follow-tags # pushes tag → triggers the publish workflow - Read `CLAUDE.md` first for full architectural context before touching any file. - The cross-correlation algorithm in `worker.js` is correct and validated against published paper results — do not modify it without explicit instruction. - The 18 dB reliability threshold and 600 ms maxLag are research-derived constants — do not change them without asking. -- 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. +- The Safari-specific `getCorrectStreamForSafari()` method is **removed in Phase 1**. Gain compensation is a host responsibility via the host-gain pattern — the host builds a gain chain (`ChannelSplitter` + `GainNode` → `MediaStreamDestination`) and passes the processed stream as `inputStream`; see `docs/examples/host-gain.md`. The component applies no gain and does no browser detection internally. (The former `input-gain` attribute was removed 2026-06-12 — it was never wired; do not reintroduce it.) - `helper.js` no longer exists — do not reference it or attempt to import from it. - **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. - **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. diff --git a/agents/SESSION_HANDOFF.md b/agents/SESSION_HANDOFF.md index a92eec6..2c15b59 100644 --- a/agents/SESSION_HANDOFF.md +++ b/agents/SESSION_HANDOFF.md @@ -1,6 +1,6 @@ # SESSION_HANDOFF.md — Current State Handoff -Last updated: 2026-06-10 +Last updated: 2026-06-12 LLMs reading this file: read `CLAUDE.md`, `AGENTS.md`, and `agents/CLAUDE_REVIEW.md` first. Do not modify files without explicit user approval. @@ -22,6 +22,22 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee --- +## Recently Completed (2026-06-12) + +### input-gain removal + 1.2.0 release preparation (working tree — commit/PR pending at session end) + +**If you are reading this before v1.2.0 is on npm, the release is mid-flight: follow the "Release checklist (per release)" in `agents/CLAUDE_REVIEW.md` Phase 7.** In particular: the CHANGELOG `[Unreleased]` → `[1.2.0]` stamp and the CDN pin bump (`@1.1.0` → `@1.2.0`, 10 occurrences: `docs/install.md` ×8, `docs/index.md` ×1, `docs/examples/vanilla-js.md` ×1) happen in a separate commit on `main` and must NOT be pushed before `npm publish` — `docs.yml` deploys Pages on every push to `main` and the docs would reference a 404ing CDN version. + +- `input-gain` / `inputGain` removed entirely (decision: permanent drop, not deferred — gain is host responsibility via the host-gain pattern). Touched: `src/scripts/latency-test-element.js` (field + `observedAttributes`), `src/index.d.ts`, `docs/api.md`, `docs/index.md`, `docs/examples/{react,nextjs,host-gain}.md`, `CLAUDE.md`, `AGENTS.md`, this file, `agents/CLAUDE_REVIEW.md` (top supersession note + two inline "superseded" markers + closed checkbox). Historical review records (`CODEX_REVIEW.md`, `KNOWN_ISSUES.md`) intentionally untouched. +- Version decision: next release is **1.2.0** (minor, pure removal). Policy: removal of documented-but-never-functional API treated as minor, called out explicitly in the changelog. 1.1.1 rejected (patch promises invisible fixes; semver §7 requires minor even for deprecation). 2.0.0 stays reserved for the audioworklet-default switch. +- `CHANGELOG.md` created (Keep a Changelog): `[Unreleased]` removal entry (names both the property/attribute and the `observedAttributes` change) + backfilled 1.0.0 / 1.0.1 / 1.0.2 / 1.1.0 entries from git tags. Note: a `v1.0.2` tag exists — easy to miss. `"CHANGELOG.md"` added to `package.json` `files[]` — npm does NOT auto-include changelogs in tarballs. +- `agents/CLAUDE_REVIEW.md` Phase 7: per-release checklist rewritten with the Codex-reviewed ordered release flow (prep commit on `main` → `npm version minor` → `npm publish` → `git push --follow-tags` → post-publish verification → GitHub Release copying the changelog entry). +- `docs/api.md` example: `signal-type="mls"` removed (attribute is planned-v2, shouldn't appear in primary example). +- `CLAUDE.md` dev-commands block completed: `typecheck`, `demo`, `build:component:dev`, `build:component:all` were missing. +- Verification all green at session end: `npm run typecheck`, `npm test` (2/2), `npm run build:component:all`, `npm run docs:build`, `npm pack --dry-run` (12 files + CHANGELOG after the `files[]` fix), rebuilt `dist/index.d.ts` contains no `inputGain`. + +--- + ## Recently Completed (2026-06-10) ### MediaRecorder 1ch experiment @@ -97,7 +113,6 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee - [x] **CI Node version divergence** — Intentional and closed. `.nvmrc=22` (local dev), `ci.yml=20` (test/build job), `docs.yml=24` (Pages build/deploy workflow). All satisfy `engines: >=18`. See `agents/KNOWN_ISSUES.md`. ### Deferred to v2 -- `input-gain` GainNode wiring (attribute is observed and typed; currently no-op — use host-gain pattern in the meantime) - `buffer-size` nonzero flush behavior (attribute is wired through; default `0` accumulates and posts once on stop; nonzero values have no effect in v1) - `signal-type="chirp"` and `"golay"` implementation - `recording-mode="audioworklet"` as the v2 default (breaking change — bump major) @@ -114,20 +129,22 @@ See `agents/KNOWN_ISSUES.md` for open findings from code reviews (Codex, DeepSee - Three `recording-mode` values each measure a **different pipeline** — do not flatten them. The differences are research data. - `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`. - `signalType` is `'mls'` only in v1 — the attribute is observed but the controller never reads it. Do not implement chirp/Golay without updating the full algorithm path. -- `input-gain` is observed and typed but intentionally has no effect in v1. Do not wire a GainNode without updating `src/index.d.ts` and the docs. +- `input-gain` was removed entirely (attribute, property, types, docs) — it was never wired. Input gain is permanently a host responsibility via the host-gain pattern. Do not reintroduce it. - `buffer-size` is wired through but nonzero flush behavior is not implemented in v1. Do not add flush logic without updating `src/index.d.ts`, `docs/api.md`, and the agent docs. --- ## npm Release Procedure +**Canonical checklist: "Release checklist (per release)" in `agents/CLAUDE_REVIEW.md` Phase 7 — follow it; ordering matters.** Short form, on updated `main`: + ```bash -# For a new patch/minor/major after code changes are committed: -npm version patch # or minor / major -git push --follow-tags -npm publish +# 1. Prep commit: stamp CHANGELOG [Unreleased] → [X.Y.Z] + bump CDN pins in docs +# 2. npm version patch|minor|major # creates commit + vX.Y.Z tag +# 3. npm publish # prepublishOnly runs build:component:all +# 4. git push --follow-tags # ONLY after publish succeeds ``` -`prepublishOnly` auto-runs `npm run build:component:all` before every publish. +Never push the CDN pin bump to `main` before `npm publish` — `docs.yml` deploys Pages on every push to `main`, and the live docs would reference a CDN version that 404s. Version strategy: v1.x keeps `"mediarecorder"` as default; v2.0.0 switches default to `"audioworklet"` (breaking). diff --git a/docs/api.md b/docs/api.md index 922ef35..455800b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -28,7 +28,6 @@ Attributes can be set in HTML or via JavaScript property assignment. Setting an | `number-of-tests` | `numberOfTests` | `number` | `1` | How many consecutive measurements to run. When > 1, a `latency-complete` event is fired after the last run with aggregate statistics. | implemented | | `recording-mode` | `recordingMode` | `string` | `"mediarecorder"` | Capture backend. `"mediarecorder"` — dual-channel via `ChannelMergerNode` + `MediaStreamDestinationNode`, mic and reference captured in one stereo stream sharing a timeline, no start-timing bias (default, implemented). Emits `latency-error` if the browser downmixes the stereo stream to mono — use `"mediarecorder-1ch"` as fallback in that case. `"mediarecorder-1ch"` — single-channel, mic stream used directly, start-timing bias present; use when `"mediarecorder"` fails due to mono downmix, or to measure the direct-mic capture pipeline (implemented). `"audioworklet"` — raw Float32 PCM, shared AudioContext clock, no codec round-trip, accuracy reference for the component's own minimal capture graph (implemented). Each mode measures the latency of its own pipeline — the differences between modes are intentional and informative. | implemented | | `signal-type` | `signalType` | `string` | `"mls"` | Test signal used for the round-trip measurement. Only `"mls"` is implemented. See signal types table below. | planned (v2) | -| `input-gain` | `inputGain` | `number` | `0` | Gain multiplier applied to the input stream before capture. `0` means no gain applied. Attribute observed but gain node not yet wired. | planned (v2) | | `mls-bits` | `mlsBits` | `number` | `15` | Order of the MLS sequence. Sequence length = 2^n − 1. Valid range: 2–16. Only applies when `signal-type="mls"`. | implemented | | `max-lag-ms` | `maxLagMs` | `number` | `600` | Cross-correlation search window in milliseconds. Determines the maximum measurable round-trip latency. | implemented | | `buffer-size` | `bufferSize` | `number` | `0` | AudioWorklet accumulation buffer in samples. `0` accumulates everything and posts once on stop. Available for future intermediate flush behavior — the value is wired through to the processor. | implemented | @@ -50,10 +49,8 @@ Three factors affect whether the result represents the host app's real latency: + max-lag-ms="600"> ``` @@ -231,4 +228,4 @@ These values are fixed by the research methodology and are not configurable: - `AudioWorklet` — required only for `recording-mode="audioworklet"`; the `"mediarecorder"` and `"mediarecorder-1ch"` modes do not use it. Chrome 80+ is the practical minimum: earlier Chrome versions (66–79) may load the worklet without error but fail to deliver audio to multi-input `AudioWorkletNode` inputs, resulting in a `latency-error` event with an empty-capture message. Use `"mediarecorder"` as the fallback for older browsers. - Stereo `MediaRecorder` output — required for the default `recording-mode="mediarecorder"` (2-channel). If the browser downmixes to mono, the component emits `latency-error`; use `recording-mode="mediarecorder-1ch"` as fallback -Safari may require manual gain compensation on some devices (common with `echoCancellation` disabled on Safari > v16). The `input-gain` attribute is designed for this but is not yet wired to a GainNode — it is a v2 item. In the current version there is no gain adjustment available. +Safari may require manual gain compensation on some devices (common with `echoCancellation` disabled on Safari > v16). Gain is a host responsibility: build a gain chain and pass the processed stream as `inputStream` — see the [host-gain example](./examples/host-gain.md). diff --git a/docs/examples/host-gain.md b/docs/examples/host-gain.md index e1c5140..1fed88b 100644 --- a/docs/examples/host-gain.md +++ b/docs/examples/host-gain.md @@ -5,7 +5,7 @@ Some recording setups require amplifying the microphone signal before the compon ## When to use - Any scenario where the raw mic stream has insufficient level for a reliable cross-correlation result. -- **Safari ≥ 16 with `echoCancellation: false`**: disabling echo cancellation on Safari can produce a very low input level. An empirical gain of 50× has been found effective. The `input-gain` attribute (planned for v2) will allow this to be set directly on the element without the extra stream round-trip described below. +- **Safari ≥ 16 with `echoCancellation: false`**: disabling echo cancellation on Safari can produce a very low input level. An empirical gain of 50× has been found effective. ## Signal chain @@ -74,4 +74,4 @@ gainNode.gain.value = 50 // update before or between test runs ## AudioWorklet mode -The same pattern works with `recording-mode="audioworklet"`. The `dest.stream` is passed as `inputStream` and the component creates a `MediaStreamSource` from it internally. This introduces a second stream round-trip, so the pipeline differs from a direct AudioWorklet path. The `input-gain` attribute (v2) will apply gain inside the component's signal chain without this extra hop. +The same pattern works with `recording-mode="audioworklet"`. The `dest.stream` is passed as `inputStream` and the component creates a `MediaStreamSource` from it internally. This introduces a second stream round-trip, so the pipeline differs from a direct AudioWorklet path — the accepted trade-off of host-controlled gain. diff --git a/docs/examples/nextjs.md b/docs/examples/nextjs.md index b05aacf..2f4bca4 100644 --- a/docs/examples/nextjs.md +++ b/docs/examples/nextjs.md @@ -223,7 +223,6 @@ declare namespace JSX { 'max-lag-ms'?: number 'recording-mode'?: 'mediarecorder' | 'mediarecorder-1ch' | 'audioworklet' 'signal-type'?: 'mls' - 'input-gain'?: number 'buffer-size'?: number } } diff --git a/docs/examples/react.md b/docs/examples/react.md index f200c1d..00d0487 100644 --- a/docs/examples/react.md +++ b/docs/examples/react.md @@ -146,7 +146,6 @@ declare namespace JSX { 'max-lag-ms'?: number 'recording-mode'?: 'mediarecorder' | 'mediarecorder-1ch' | 'audioworklet' 'signal-type'?: 'mls' - 'input-gain'?: number } } } diff --git a/docs/index.md b/docs/index.md index 49576e7..efd4969 100644 --- a/docs/index.md +++ b/docs/index.md @@ -70,7 +70,6 @@ npm install @adasp/latency-test - Before calling `start()`, assign `element.inputStream` (a `MediaStream`) and `element.audioContext` (an `AudioContext`) — the component never creates audio resources itself. If your app already has an `AudioContext`, pass it directly to avoid creating a second one. - Use `recording-mode` to select the capture backend: `"mediarecorder"` (2-channel, v1 default — no start-timing bias), `"mediarecorder-1ch"` (1-channel fallback for browsers that downmix stereo to mono), or `"audioworklet"` (planned v2 default, sample-accurate raw PCM). - `signal-type` is `"mls"` in v1. `"chirp"` and `"golay"` are planned for v2. -- `input-gain` is reserved for a future gain multiplier — it is not yet wired in the current version. ## Try it live diff --git a/package.json b/package.json index 0ad71ea..4b00c4c 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "files": [ "dist/", "README.md", + "CHANGELOG.md", "LICENSE" ], "engines": { diff --git a/src/index.d.ts b/src/index.d.ts index 6a236f3..37f05e2 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -45,8 +45,6 @@ export interface LatencyTestElement extends HTMLElement { recordingMode: 'mediarecorder' | 'mediarecorder-1ch' | 'audioworklet' /** Only `'mls'` is implemented in v1. `'chirp'` and `'golay'` are planned for v2. */ signalType: 'mls' - /** Observed and settable, but has no effect in v1. Use the host-gain pattern instead — see docs/examples/host-gain.md. */ - inputGain: number debug: boolean addEventListener( type: K, diff --git a/src/scripts/latency-test-element.js b/src/scripts/latency-test-element.js index 0cf7b98..5883709 100644 --- a/src/scripts/latency-test-element.js +++ b/src/scripts/latency-test-element.js @@ -15,7 +15,6 @@ class LatencyTest extends (typeof HTMLElement !== 'undefined' ? HTMLElement : cl bufferSize = 0 recordingMode = 'mediarecorder' signalType = 'mls' - inputGain = 0 constructor() { super() @@ -32,7 +31,7 @@ class LatencyTest extends (typeof HTMLElement !== 'undefined' ? HTMLElement : cl } static get observedAttributes() { - return ['mls-bits', 'max-lag-ms', 'input-gain', 'number-of-tests', 'recording-mode', 'signal-type', 'buffer-size'] + return ['mls-bits', 'max-lag-ms', 'number-of-tests', 'recording-mode', 'signal-type', 'buffer-size'] } attributeChangedCallback(name, oldValue, newValue) {