diff --git a/src/App.css b/src/App.css
index 83c91eb..72a4f7f 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1172,3 +1172,37 @@ button {
margin: 0;
text-align: center;
}
+
+/* ---- Transport step group (preview stepping) ---------------------------- */
+.transport-step-group {
+ display: flex;
+ gap: 6px;
+}
+
+.step-button {
+ padding: 0.4rem 0.55rem;
+}
+
+/* ---- Display toggles (virtual instrument / hint show-hide) -------------- */
+/* Separate row of pure show/hide switches, styled like the tuner and
+ metronome switches — visually distinct from transport actions. */
+.display-toggles {
+ display: flex;
+ justify-content: center;
+ gap: var(--spacing-lg);
+ margin-top: 10px;
+}
+
+.display-toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.display-toggle-label {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 12px;
+ color: var(--color-text-muted);
+}
diff --git a/src/App.tsx b/src/App.tsx
index 57e10eb..7d2b771 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -165,6 +165,12 @@ function App() {
case 'n':
phraseRef.current?.next();
break;
+ case 'arrowleft':
+ phraseRef.current?.previewStep(-1);
+ break;
+ case 'arrowright':
+ phraseRef.current?.previewStep(1);
+ break;
case 's':
phraseRef.current?.skip();
break;
@@ -430,6 +436,7 @@ function App() {
<>
Start / Pause / ResumeSpace
Preview phrase (Play / Stop)P
+ While previewing: step note forward / back→ / ←
Retry phraseR
New melody / next barsN
Skip note (at your pace)S
diff --git a/src/components/PhraseTrainer.tsx b/src/components/PhraseTrainer.tsx
index f1b30b4..5d2eee1 100644
--- a/src/components/PhraseTrainer.tsx
+++ b/src/components/PhraseTrainer.tsx
@@ -35,11 +35,12 @@ import { TUNINGS, getFretboardPositions } from '../music/Tunings';
import { INSTRUMENT_DEFINITIONS, resolveClefTranspose } from '../music/InstrumentConfigs';
const AUTO_CONTINUE_DELAY_MS = 1500;
-import { Play, Pause, RotateCcw, SkipForward, Volume2, Square, ChevronDown, Music2, Upload, HelpCircle, Guitar } from 'lucide-react';
+import { Play, Pause, RotateCcw, SkipForward, SkipBack, Volume2, Square, ChevronDown, Music2, Upload, HelpCircle, Guitar } from 'lucide-react';
export interface PhraseHandle {
pauseToggle: () => void;
previewToggle: () => void;
+ previewStep: (dir: 1 | -1) => void;
retry: () => void;
skip: () => void;
next: () => void;
@@ -328,6 +329,7 @@ export const PhraseTrainer = forwardRef(
useImperativeHandle(ref, () => ({
pauseToggle: () => trainer?.pauseToggle(),
previewToggle: () => trainer?.previewToggle(),
+ previewStep: (dir) => trainer?.previewStep(dir),
retry: () => trainer?.retry(),
skip: () => trainer?.skip(),
pause: () => trainer?.pause(),
@@ -337,8 +339,11 @@ export const PhraseTrainer = forwardRef(
// ---- Hints --------------------------------------------------------------
const activeScore = trainer.activeScore;
const events = scoreEvents(activeScore);
- const currentMidi = trainer.currentIdx >= 0 && events[trainer.currentIdx]?.pitch
- ? events[trainer.currentIdx]!.pitch!.midi
+ // While previewing, the hint instruments follow the preview cursor so
+ // the user can find notes on the fretboard while listening.
+ const displayIdx = trainer.phase === 'preview' ? trainer.previewIdx : trainer.currentIdx;
+ const currentMidi = displayIdx >= 0 && events[displayIdx]?.pitch
+ ? events[displayIdx]!.pitch!.midi
: null;
const hintPositions = useMemo(() => {
@@ -714,7 +719,7 @@ export const PhraseTrainer = forwardRef(
{scoreOk ? (
(
{!settings.zenMode && (
<>
+ {trainer.phase === 'preview' && (
+
+
+
+
+ )}