diff --git a/src/App.tsx b/src/App.tsx index 458338b..38936b0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import { getFretboardPositions } from './music/Tunings'; import { INSTRUMENT_DEFINITIONS } from './music/InstrumentConfigs'; +import { FretboardHint } from './components/FretboardHint'; import { Mic, MicOff, SkipForward, HelpCircle } from 'lucide-react'; import './App.css'; import './styles/skip-button.css'; @@ -195,18 +196,12 @@ function App() { )} - {settings.showHint && currentInstrumentDef.showTuning && ( + {settings.showHint && currentInstrumentDef.showTuning && currentTuning && (
{getNoteDetails(targetMidi + currentInstrumentDef.transpose).scientific} (Written)
-
- {hintPositions.map((p, i) => ( - - String {p.stringIndex + 1} / Fret {p.fret} - - ))} -
+
)} diff --git a/src/components/FretboardHint.tsx b/src/components/FretboardHint.tsx new file mode 100644 index 0000000..53a2235 --- /dev/null +++ b/src/components/FretboardHint.tsx @@ -0,0 +1,173 @@ +import type { Tuning, FretPosition } from '../music/Tunings'; + +interface FretboardHintProps { + tuning: Tuning; + positions: FretPosition[]; + maxFrets?: number; // How many frets to display +} + +export function FretboardHint({ tuning, positions, maxFrets = 15 }: FretboardHintProps) { + // Config + const numStrings = tuning.strings.length; + // Visual params + const stringSpacing = 30; // Vertical space between strings + const fretWidth = 50; // Horizontal space for one fret + const nutWidth = 6; // Width of the nut + const paddingX = 20; // Padding left/right + const paddingY = 20; // Padding top/bottom + + const width = paddingX * 2 + nutWidth + (maxFrets * fretWidth); + const height = paddingY * 2 + ((numStrings - 1) * stringSpacing); + + // Markers (standard guitar dots) + // 3, 5, 7, 9, 12, 15, 17, 19, 21 + const markers = [3, 5, 7, 9, 12, 15, 17, 19, 21].filter(m => m <= maxFrets); + + // Helper to get Y coordinate for a string index + // String 0 is lowest pitch. In tab/charts, lowest pitch is usually the BOTTOM line. + // So index 0 -> max Y. + const getStringY = (stringIndex: number) => { + // stringIndex 0 (Low E) -> bottom line + // stringIndex N (High E) -> top line + return height - paddingY - (stringIndex * stringSpacing); + }; + + // Helper to get X coordinate for a fret line + // Fret N line is at x = padding + nut + N * width + const getFretLineX = (fretNum: number) => { + return paddingX + nutWidth + (fretNum * fretWidth); + }; + + // Helper to get Center X for a note at fret N + // If fret 0 (open), place it to left of nut. + // If fret > 0, place it in middle of Fret N-1 and Fret N. + const getNoteX = (fretNum: number) => { + if (fretNum === 0) { + return paddingX + (nutWidth / 2) - 15; // To the left of nut + } + const startX = getFretLineX(fretNum - 1); + const endX = getFretLineX(fretNum); + return (startX + endX) / 2; + }; + + return ( +
+ + {/* Fretboard background */} + + + {/* Nut */} + + + {/* Fret Markers (Dots) on fretboard */} + {markers.map(m => { + const cx = getNoteX(m); + const cy = height / 2; + const isDouble = m % 12 === 0; + + if (isDouble) { + // Draw two dots + return ( + + + + + ); + } + + return ( + + ); + })} + + {/* Frets (Vertical lines) */} + {Array.from({ length: maxFrets + 1 }).map((_, i) => { + if (i === 0) return null; // Nut handles 0 + const x = getFretLineX(i); + return ( + + ); + })} + + {/* Strings (Horizontal lines) */} + {tuning.strings.map((_, i) => { + const y = getStringY(i); + + // i=0 is LOW pitch (thickest). + const visualThickness = 3 - (i * 0.3); // 0 -> 3, 5 -> 1.5 + + return ( + + ); + })} + + {/* Target Note Positions */} + {positions.map((p, idx) => { + // Verify if fret is within range + if (p.fret > maxFrets) return null; + + const cx = getNoteX(p.fret); + const cy = getStringY(p.stringIndex); + + return ( + + + + {p.fret} + + + ); + })} + +
+ ); +}