Tone-finder UX Proposed

Best-so-far indicator, iteration cap, Auto mode, suboptimal marking.
Date: 2026-04-25 Scope: modal_viewer/{app,ui} Item: #6 Plan: 4 commits

Goal

Make the existing binary-search auto-tuner usable as a hands-off batch worker rather than a click-per-iteration tool. Specifically: surface the best-so-far result so the user can accept "good enough" without waiting for the threshold to be met, cap runaway searches, and add an Auto mode that re-fires strikes and advances notes without manual intervention.

This spec covers UX and control-flow improvements only. The deeper algorithmic rewrite (parameter-history-aware exploration, "add a degree of freedom" suggestion, anti-circling detection) is item #10, scheduled separately.

Design

Four coordinated components. Each is independent enough to commit on its own.

A. Best-so-far indicator

A compact toolbar widget showing the closest tune_history entry to the target frequency. Visible whenever tune_history is non-empty, regardless of whether fine-tuning is currently active.

B. Iteration cap

A hard upper bound on how many iterations a single fine-tune session may consume before forcibly stopping.

if ((int)tune_history_.size() >= fine_max_iters_) {
    fine_mode_ = 0;
    fine_phase_ = 0;
    // best-so-far survives in tune_history_; toolbar A surfaces it
}

The cap counts total iterations across all internal jitter-restarts within a single Fine-* invocation. The existing jitter-retry behaviour is unchanged — the cap simply guarantees the session ends.

C. Auto mode

A sticky toggle (fine_auto_, persists across fine-tune sessions, reset on Reset) that automates two manual steps: striking between iterations and advancing notes.

Toggle UI: new [Auto] button in Row 3, immediately to the right of [Fine-WH]. When on, the button uses a distinct highlight colour (matches the existing "selected toggle" styling — bright accent).

Behaviour while on:

Behaviour while off: identical to current code. Auto mode is purely additive.

D. Suboptimal note marking

Visual indication that a saved note didn't meet the user's threshold.

Struct change. App::saved_notes_[i] gains:

bool suboptimal = false;

Default false. Set true only by Auto mode when accepting a non-converged result. Reset whenever a note is overwritten with a converged result.

UI change. Note-button colour rules in ui_manager.cpp:381–384:

StateColour
Saved & convergedGreen (0.2, 0.6, 0.2) (current)
Saved & suboptimalOrange (0.9, 0.5, 0.1) (new)
Currently selected target (unsaved)Yellow/orange (0.9, 0.7, 0.1) (current)
DefaultPlain (current)

Tooltip on suboptimal buttons reads: "Best result was N cents off — re-tune for tighter accuracy."

Persistence. The suboptimal flag is saved/restored in the Export/Import file format. Existing files (without the field) load with suboptimal = false.

New state summary

VariableTypeOwnerDefaultReset by
fine_max_iters_intApp (private)40000n/a (config-only)
fine_auto_boolApp (private)falseReset button
saved_notes_[i].suboptimalboolApp::SavedNotefalseReset; save-with-converged-result

Toolbar layout (Row 3, Create mode)

[Fine-W][Fine-Size][Fine-WH][Auto] | [Threshold slider][3c]
                                                                          | [Best: -3.2c (Apply)]
                                                                          | [Chart]

Adaptive wrapping (Phase B of #1) is already in place; no new wrap rules required.

Failure modes & edge cases

Out of scope

Acceptance criteria

  1. Clicking [Apply] on the best-so-far indicator produces the same end state as clicking the existing Apply button when the best iteration happened to be current.
  2. With fine_max_iters_ = 40000, a non-converging fine-tune session stops after exactly 40,000 history entries (modulo the +1 for the entry that triggered the check).
  3. With Auto mode on, a successful tuning of all 8 notes proceeds end-to-end without user clicks beyond the initial Fine-* + first strike.
  4. Suboptimal-marked notes show as orange in the toolbar; converged notes show as green.
  5. Export/Import round-trips the suboptimal flag.
  6. With Auto mode off, behaviour is byte-identical to before this change for the manual flow.

Implementation plan

Four commits, each independently reviewable:

  1. A: Best-so-far indicator + [Apply] button in toolbar.
  2. B: Iteration cap (fine_max_iters_, enforce in convergence-check sites).
  3. D: saved_notes_[i].suboptimal field + orange button colour + tooltip + Export/Import round-trip. (Done before C so C has somewhere to store its result.)
  4. C: fine_auto_ toggle, auto-strike, auto-advance on convergence, auto-advance + suboptimal mark on cap/exhaust.

Total estimate: ~150–250 LOC across the four commits. No new files; all changes within existing app.cpp, app.h, ui_manager.cpp, ui_manager.h.