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.
Four coordinated components. Each is independent enough to commit on its own.
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.
Best: -3.2c [Apply]. The cents value is colour-coded with the same thresholds the existing threshold-display uses (≤1c green, ≤5c plain, ≤25c orange, otherwise red).tune_history[best_idx]'s width/height into tuning_width_/height_, then fires tuning_state.apply_clicked = true to flow through the existing apply-and-advance pipeline.target_note; cleared by Reset and by the start of a fresh fine-tune.A hard upper bound on how many iterations a single fine-tune session may consume before forcibly stopping.
int fine_max_iters_ = 40000; on App.tune_history_ (the convergence-check block near app.cpp:1620), add: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.
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:
fine_auto_ is true and tune_history_ is non-empty (i.e. we already recorded fine_strike_rel_x_/y_ from the first strike), Phase 1 fires modal_strike() at the recorded relative position immediately and advances to Phase 2 without waiting for input. The first strike of a fresh session still requires user input — the auto-strike position is captured from it.|best_cents| ≤ threshold): apply best, advance target_note, re-arm fine-tune in the same mode (Fine-W / Fine-Size / Fine-WH). If target_note == 8, Auto stops; user clicks Save.fine_wh_round_ > 6): same Apply pipeline as the convergence path, but saved_notes_[target_note].suboptimal = true is set first.Behaviour while off: identical to current code. Auto mode is purely additive.
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:
| State | Colour |
|---|---|
| Saved & converged | Green (0.2, 0.6, 0.2) (current) |
| Saved & suboptimal | Orange (0.9, 0.5, 0.1) (new) |
| Currently selected target (unsaved) | Yellow/orange (0.9, 0.7, 0.1) (current) |
| Default | Plain (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.
| Variable | Type | Owner | Default | Reset by |
|---|---|---|---|---|
fine_max_iters_ | int | App (private) | 40000 | n/a (config-only) |
fine_auto_ | bool | App (private) | false | Reset button |
saved_notes_[i].suboptimal | bool | App::SavedNote | false | Reset; save-with-converged-result |
Adaptive wrapping (Phase B of #1) is already in place; no new wrap rules required.
!tune_history_.empty().fine_auto_ stays on. Re-clicking a Fine-* button resumes auto behaviour for that note.target_note simply changes. On next convergence/cap event, Apply writes to the current target_note. Predictable.[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.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).suboptimal flag.Four commits, each independently reviewable:
[Apply] button in toolbar.fine_max_iters_, enforce in convergence-check sites).saved_notes_[i].suboptimal field + orange button colour + tooltip + Export/Import round-trip. (Done before C so C has somewhere to store its result.)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.