fix(tuner): mic-verify stamps the tuning it actually checked + on-device test plan (#684)

Working-tuning follow-ups:

- Mic-verify used _state.currentSongOffsets for the 'verified' stamp, but for a
  MANUALLY-selected tuning (tuner opened off a song) that's a stale/different
  song's tuning — so verify could mark the WRONG tuning verified. It now derives
  the verified offsets from the tuning actually being checked (its target freqs;
  the player's reference pitch cancels in the ratio), so 'verified' always
  attaches to the tuning the player confirmed. Explicit offsets still win.

- Adds docs/working-tuning-on-device-tests.md: the checklist for the parts that
  can't be covered headlessly — the auto-open/gate flow, both-directions prompts,
  mic-verify detection, and the tuner-mic-vs-note_detect ASIO/exclusive-mode
  contention flagged in the design charrette.

Test: mic-verify with no explicit offsets / no song context derives the correct
offsets (Drop-D). 55 tuner tests green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Byron Gamatos
2026-07-01 11:45:26 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 2910a3c8cb
commit 96a84996a2
3 changed files with 92 additions and 2 deletions
+24 -2
View File
@@ -827,11 +827,33 @@
// Start a verify session for `targets` (freqs; defaults to the selected tuning).
// Captures the tuning's OFFSETS now (explicit arg, else the current song's) so that
// when it completes we stamp 'verified' onto the exact tuning that was confirmed.
// Per-string semitone offsets (from standard) of the tuning we're verifying, derived
// from its target freqs. This is authoritative for BOTH the song ('_current') tuning
// and a manually-selected tuning — unlike _state.currentSongOffsets, which for a manual
// tuning is a stale/different song's tuning that must NOT be stamped 'verified'. The
// player's reference pitch scales both the targets and the standard, so it cancels.
function _tuningOffsetsFromFreqs(freqs) {
const u = window._tunerUtils;
if (!u || typeof u.offsetsToFreqs !== 'function' || typeof u.freqToMidi !== 'function') return null;
const isBass = /^bass/.test(_state.selectedInstrument || ''); // the selected instrument, not the song's
const refScale = (Number(_state.referencePitch) || 440) / 440;
const std = u.offsetsToFreqs(new Array(freqs.length).fill(0), isBass);
if (!Array.isArray(std) || std.length !== freqs.length) return null;
const out = [];
for (let i = 0; i < freqs.length; i++) {
const f = Number(freqs[i]);
const s = Number(std[i]) * refScale;
if (!(f > 0) || !(s > 0)) return null;
out.push(Math.round(u.freqToMidi(f) - u.freqToMidi(s)) || 0); // normalize -0 → 0
}
return out;
}
function verifyStart(targets, offsets) {
const freqs = (Array.isArray(targets) && targets.length) ? targets : _state.selectedTuning;
if (!Array.isArray(freqs) || !freqs.length) return null;
const offs = (Array.isArray(offsets) && offsets.length) ? offsets.slice()
: (Array.isArray(_state.currentSongOffsets) ? _state.currentSongOffsets.slice() : null);
// Explicit offsets win (callers/tests that already have them); otherwise derive
// them from the tuning actually being verified.
const offs = (Array.isArray(offsets) && offsets.length) ? offsets.slice() : _tuningOffsetsFromFreqs(freqs);
_verify = { targets: freqs.map((f) => ({ freq: f, streak: 0, done: false })), complete: false, offsets: offs };
_verifiedPublished = false;
return verifyState();