Fix double metronome click in single-note modes

useGameLogic's own scheduler and the footer widget's scheduler both
played audible clicks while rhythm.active — assist->learn-notes switch
made the second one mount. Now the widget is the single click source in
non-zen modes; useGameLogic's scheduler is a silent tick source for
auto-advance (full sound only in zen mode, where the widget isn't
rendered). Also: widget honors Timer mode (60/seconds) like the trainer,
and useMetronome skips audio nodes at volume 0.
This commit is contained in:
2026-09-20 00:27:59 +02:00
parent 56ace9efac
commit 71271387ab
3 changed files with 13 additions and 3 deletions

View File

@@ -65,8 +65,12 @@ export const MetronomeWidget: React.FC<MetronomeWidgetProps> = ({
// Free/plain: the widget's own scheduler runs whenever armed.
const running = isSync ? runActive : armed;
// Timer mode = one beat per N seconds: effective BPM 60/seconds so
// clicks and pendulum match the trainer's auto-advance cadence.
const effectiveBpm = rhythm.mode === 'bpm' ? rhythm.bpm : 60 / Math.max(1, rhythm.seconds);
const { restart } = useMetronome({
bpm: rhythm.bpm,
bpm: effectiveBpm,
volume: rhythm.sound ? rhythm.volume : 0,
playing: running && !isSync,
onTick: handleTick,
@@ -82,7 +86,7 @@ export const MetronomeWidget: React.FC<MetronomeWidgetProps> = ({
});
}, [isSync, runActive]);
const beatMs = 60000 / Math.max(1, rhythm.bpm);
const beatMs = 60000 / Math.max(1, effectiveBpm);
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>

View File

@@ -85,7 +85,11 @@ export const useGameLogic = (
const { restart: restartMetronome } = useMetronome({
bpm: effectiveBpm,
volume: settings.rhythm.sound ? settings.rhythm.volume : 0,
// Audible clicks come from the shared footer widget (Controls) in
// non-zen single-note modes — this scheduler is only the tick source
// for auto-advance. Sounding it here too doubles every beat. In zen
// mode the widget isn't rendered, so this scheduler IS the click.
volume: settings.zenMode && settings.rhythm.sound ? settings.rhythm.volume : 0,
playing: settings.rhythm.active,
onTick: handleTick
});

View File

@@ -34,6 +34,8 @@ export function useMetronome({ bpm, volume, playing, onTick }: MetronomeOptions)
const playClick = useCallback((time: number) => {
if (!audioContext.current) return;
onTickRef.current(currentBeat.current);
if (volume <= 0) return; // silent tick source — no audio nodes needed
const osc = audioContext.current.createOscillator();
const gainNode = audioContext.current.createGain();