Metronome sync toggle in widget, more Giuliani figures, chord pool list

- MetronomeWidget: the Phrase Mode switch now lives IN the widget as a
  Free/Sync toggle. Free = independent metronome (own clock, starts when
  toggled, with or without clicks). Sync = silent while idle, starts with
  the exercise (count-in included), pendulum mirrors the run's beats.
- Fixed double phraseBeatBus.emit (leftover from an earlier edit) that
  flipped the pendulum twice per beat — animation appeared frozen.
- Click decision unified: one sound checkbox governs both the widget and
  (in sync mode) the run's click scheduler. 'Click 1 after count-in' is
  musically the last count-in click (suppressed duplicate) — now with a
  working pendulum the offset is visible rather than confusing.
- Giuliani figures extended: p-i-m-a-m-i, p-a-p-i-m-a, a-m-i-p-i-m,
  m-i-a-m-i-m (six-note rolling/bass-skip/tremolo-adjacent figures).
- Arpeggio chord pool: togglable degree list (empty = all 7); functional
  and random progressions honor the pool; first bar draws a random chord
  from the pool.
- Tests 174/174.
This commit is contained in:
OpenClaw
2026-09-14 20:13:42 +00:00
parent ffb94cc93f
commit 6253194632
6 changed files with 113 additions and 42 deletions

View File

@@ -215,11 +215,6 @@ export function usePhraseTrainer(
if (e.kind === 'count-in-beat' || e.kind === 'beat') {
phraseBeatBus.emit(e.beat ?? 0, e.at);
}
// Publish every beat to the shared bus so the metronome widget's
// pendulum stays in sync with the run (click or no click).
if (e.kind === 'count-in-beat' || e.kind === 'beat') {
phraseBeatBus.emit(e.kind === 'count-in-beat' ? (e.beat ?? 0) : ((e.beat ?? 0) + countInBeatsRef.current), e.at);
}
clickIdxRef.current++;
}
// Fire state boundaries only when the clock reaches them.