feat(tuner): mic-verify — promote working tuning assumed→verified via a per-string check (working-tuning PR 9b) (#670)

* feat(tuner): mic-verify — promote the working tuning assumed→verified via a per-string check (working-tuning PR 9b)

Adds the choreographed per-string mic verification the design reserved for
'verified' provenance (audio-engine's honesty rule — nothing else may claim it):
the player plays each string, and once every one reads in tune (±6 cents) and
holds stable for 8 frames, the tuner stamps the working tuning
provenance:'verified' + verifiedStrings via workingTuning.set.

- screen.js: a pure verify state machine (verifyStart/verifyFeed/verifyCancel/
  verifyState, exposed on the tuner API) + the set-verified writer; cancels on close.
- ui.js: updateUI feeds each processed frame (matched string + cents) into the
  session; a "Verify tuning" button + per-string progress + status, shown for a
  selected (non-free) tuning.

Pairs with the 9a lifecycle: a 'verified' decays back to 'assumed' on the next
song load, so mic-verify is a per-session confidence boost, never a sticky claim.

Tests: tests/js/tuner_auto_open.test.js +4 (all-strings->verified, out-of-tune
never completes, streak resets on drift, API exposed / only it claims verified) —
33/33. The state machine is headless-verified with synthetic frames; the real
per-string mic detection + the button flow need an on-device pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF

* fix(tuner): mic-verify writes the confirmed tuning + no clobber + stricter streak (PR #670 review)

Review fixes for mic-verify (working-tuning PR 9b):

- 'verified' could attach to STALE offsets: _publishVerified stamped provenance
  without writing offsets, so the slot's pre-tuning offsets got marked verified.
  verifyStart(targets, offsets) now captures the confirmed tuning's offsets, and
  _publishVerified writes offsets + stringCount + instrument + referencePitch +
  verifiedStrings ATOMICALLY with provenance:'verified' into the selected slot
  (and refuses to stamp verified with no concrete offsets).
- The assumed publish-on-clear immediately clobbered a just-earned 'verified':
  disable() now skips it when a mic-verify wrote verified this session
  (_verifiedPublished).
- The per-string streak could accumulate across silence / wrong-string frames.
  verifyFeed now requires CONSECUTIVE in-tune frames: the one confirmed string
  advances, every other unfinished string resets each frame.
- A mid-verify tuning change (song switch) left stale captured offsets; verify is
  now cancelled in _syncCurrentTuning when the song tuning changes.

Tests: verify writes the confirmed offsets (not stale); source-guard for the
no-clobber path. 47 tuner + 77 tuner/capability tests green. Codex-reviewed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
ChrisBeWithYou
2026-07-01 11:33:52 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent 115c96a3f0
commit 2910a3c8cb
3 changed files with 205 additions and 2 deletions
+53
View File
@@ -697,3 +697,56 @@ test('a transient /api/settings failure is not cached — the next coverage read
const r2 = await api.coverageReport(DROP_D); // retry: settings now readable → a real report
assert.equal(r2.retune.length, 1, 'the retry actually computes coverage (Drop-D low string vs standard)');
});
// ── PR 9b: mic-verify (assumed -> verified via a per-string check) ──────────
const VERIFY_TARGETS = [82.41, 110, 146.83, 196, 246.94, 329.63]; // guitar-6 standard freqs
test('mic-verify: all strings in-tune-and-stable promotes to verified (with the confirmed offsets)', () => {
const sandbox = createTunerSandbox();
const sets = [];
sandbox.window.feedBack.workingTuning = { get: () => ({ offsets: [0, 0, 0, 0, 0, 0] }), set: (n, o) => sets.push({ n, o }) };
const api = sandbox.window._tunerAutoOpen;
assert.ok(api.verifyStart(VERIFY_TARGETS, [-2, 0, 0, 0, 0, 0])); // verifying a Drop-D tuning
for (let i = 0; i < 8; i++) api.verifyFeed(82.41, 2); // one string alone isn't enough
assert.equal(api.verifyState().complete, false);
for (const f of VERIFY_TARGETS) { for (let i = 0; i < 8; i++) api.verifyFeed(f, 2); }
assert.equal(api.verifyState().complete, true);
assert.equal(sets.length, 1);
assert.equal(sets[0].o.provenance, 'verified');
// The stamp carries the CONFIRMED offsets, not whatever stale offsets the slot held.
assert.deepEqual(sets[0].n.offsets, [-2, 0, 0, 0, 0, 0]);
assert.equal(sets[0].n.verifiedStrings.length, 6);
});
test('mic-verify: an out-of-tune string never completes; cancel clears', () => {
const sandbox = createTunerSandbox();
sandbox.window.feedBack.workingTuning = { get: () => ({}), set() {} };
const api = sandbox.window._tunerAutoOpen;
api.verifyStart([82.41, 110]);
for (let i = 0; i < 30; i++) api.verifyFeed(82.41, 25); // 25c off -> never passes
assert.equal(api.verifyState().complete, false);
assert.deepEqual(api.verifyState().done, [false, false]);
api.verifyCancel();
assert.equal(api.verifyState(), null);
});
test('mic-verify: the in-tune streak resets if a frame drifts out', () => {
const sandbox = createTunerSandbox();
sandbox.window.feedBack.workingTuning = { get: () => ({}), set() {} };
const api = sandbox.window._tunerAutoOpen;
api.verifyStart([100]);
for (let i = 0; i < 5; i++) api.verifyFeed(100, 2); // 5 in tune (need 8)
api.verifyFeed(100, 30); // drift out -> streak resets
for (let i = 0; i < 7; i++) api.verifyFeed(100, 2); // 7 more (not yet 8 in a row)
assert.equal(api.verifyState().done[0], false);
api.verifyFeed(100, 2); // 8th consecutive -> done
assert.equal(api.verifyState().complete, true);
});
test('the tuner exposes the mic-verify API and only it claims verified (screen.js)', () => {
const src = fs.readFileSync(TUNER_SCREEN_JS, 'utf8');
assert.match(src, /verifyStart:/);
assert.match(src, /verifyFeed:/);
assert.match(src, /provenance: 'verified'/); // the only place that stamps verified
// A just-earned 'verified' is not clobbered by the assumed publish-on-clear.
assert.match(src, /wasAutoOpened && !_verifiedPublished/);
});