mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-11-03 22:03:54 +01:00
Footer tools stacked in all modes; fix silent phrase-mode metronome
Layout: tuner + metronome now stack vertically everywhere (was phrase-only). Phrase metronome bug: clicks were gated on rhythm.active, but phrase mode has no on/off switch (the sync checkbox replaces it) and the default is active:false — so a synced run stayed silent with no in-mode remedy. Now: phrase mode is always armed (sync checkbox = free vs synced, sound checkbox = clicks), and phrase clickSound reads rhythm.sound only. Single-note modes keep the rhythm.active on/off semantics.
This commit is contained in:
@@ -68,8 +68,8 @@ export const Controls: React.FC<ControlsProps> = ({ currentPitch }) => {
|
|||||||
)}
|
)}
|
||||||
{shouldShowFilters && <TargetNoteControls />}
|
{shouldShowFilters && <TargetNoteControls />}
|
||||||
|
|
||||||
{/* Bottom Section: Tools Grid (2 Columns now) */}
|
{/* Bottom Section: Tools Grid (stacked in all modes) */}
|
||||||
<div className="tools-grid" style={inPhraseMode ? { gridTemplateColumns: '1fr' } : undefined}>
|
<div className="tools-grid" style={{ gridTemplateColumns: '1fr' }}>
|
||||||
{/* Tool 1: Tuner */}
|
{/* Tool 1: Tuner */}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', border: '1px solid rgba(128,128,128,0.2)', padding: '12px', borderRadius: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', border: '1px solid rgba(128,128,128,0.2)', padding: '12px', borderRadius: '8px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
|
|||||||
@@ -55,10 +55,15 @@ export const MetronomeWidget: React.FC<MetronomeWidgetProps> = ({
|
|||||||
|
|
||||||
const isSync = syncMode === 'option' && !!rhythm.syncToExercise;
|
const isSync = syncMode === 'option' && !!rhythm.syncToExercise;
|
||||||
const runActive = gate === true;
|
const runActive = gate === true;
|
||||||
|
// Phrase mode (syncMode 'option') has no on/off switch — the sync
|
||||||
|
// checkbox replaces it (unchecked = free metronome, checked = synced to
|
||||||
|
// the exercise), so the metronome is always armed there. Gating on
|
||||||
|
// rhythm.active would leave phrase users with no way to enable clicks.
|
||||||
|
const armed = syncMode === 'option' ? true : rhythm.active;
|
||||||
// Sync: pendulum/click only while the run is active (the run's scheduler
|
// Sync: pendulum/click only while the run is active (the run's scheduler
|
||||||
// is the click source; the widget's own scheduler stays off).
|
// is the click source; the widget's own scheduler stays off).
|
||||||
// Free/plain: the widget's own scheduler runs whenever armed.
|
// Free/plain: the widget's own scheduler runs whenever armed.
|
||||||
const running = isSync ? runActive : rhythm.active;
|
const running = isSync ? runActive : armed;
|
||||||
|
|
||||||
const { restart } = useMetronome({
|
const { restart } = useMetronome({
|
||||||
bpm: rhythm.bpm,
|
bpm: rhythm.bpm,
|
||||||
@@ -78,7 +83,6 @@ export const MetronomeWidget: React.FC<MetronomeWidgetProps> = ({
|
|||||||
}, [isSync, runActive]);
|
}, [isSync, runActive]);
|
||||||
|
|
||||||
const beatMs = 60000 / Math.max(1, rhythm.bpm);
|
const beatMs = 60000 / Math.max(1, rhythm.bpm);
|
||||||
const armed = rhythm.active;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
|
|||||||
@@ -85,13 +85,15 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
const pace: PhraseSettings['pace'] = settings.rhythm.syncToExercise ? 'tempo' : 'step';
|
const pace: PhraseSettings['pace'] = settings.rhythm.syncToExercise ? 'tempo' : 'step';
|
||||||
phrase.pace = pace;
|
phrase.pace = pace;
|
||||||
// Click decision — ONE source: the metronome widget's sound checkbox.
|
// Click decision — ONE source: the metronome widget's sound checkbox.
|
||||||
// - Sync (tempo): run clicks when armed && sound (count-in incl.);
|
// Phrase mode has no metronome on/off switch (the sync checkbox
|
||||||
// widget silent while idle.
|
// replaces it, see MetronomeWidget), so rhythm.active must NOT gate
|
||||||
// - Free metronome: widget clicks freely; the run stays silent to
|
// clicks here — with the default active:false a synced run would
|
||||||
// avoid doubling (sync off in tempo = free widget during runs).
|
// stay silent with no way to fix it from this mode.
|
||||||
phrase.clickSound = pace === 'tempo'
|
// - Sync (tempo): run clicks when sound is on (count-in included);
|
||||||
? settings.rhythm.active && settings.rhythm.sound
|
// the widget stays silent while idle.
|
||||||
: settings.rhythm.active && settings.rhythm.sound && !settings.rhythm.syncToExercise;
|
// - Free metronome (step): the widget clicks freely; the run stays
|
||||||
|
// silent to avoid doubling.
|
||||||
|
phrase.clickSound = settings.rhythm.sound;
|
||||||
const setPhrase = useCallback((updates: Partial<PhraseSettings>) => {
|
const setPhrase = useCallback((updates: Partial<PhraseSettings>) => {
|
||||||
updateSettings(s => ({ ...s, phrase: { ...getPhraseSettings(s), ...updates } }));
|
updateSettings(s => ({ ...s, phrase: { ...getPhraseSettings(s), ...updates } }));
|
||||||
}, [updateSettings]);
|
}, [updateSettings]);
|
||||||
|
|||||||
Reference in New Issue
Block a user