diff --git a/src/App.tsx b/src/App.tsx index 7f3f1b2..e26aa9e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -101,7 +101,7 @@ function App() { } }, [pitchData, targetMidi, matchStartTime, generateNewNote]); - const targetDetails = getNoteDetails(targetMidi); + // Hint text construction const hintPositions = useMemo(() => { @@ -135,7 +135,9 @@ function App() { {settings.showHint && (
-
{targetDetails.scientific}
+
+ {getNoteDetails(targetMidi + 12).scientific} (Written) +
{hintPositions.map((p, i) => ( diff --git a/src/components/SheetMusic.tsx b/src/components/SheetMusic.tsx index 6d3e0d2..8dbe26e 100644 --- a/src/components/SheetMusic.tsx +++ b/src/components/SheetMusic.tsx @@ -8,6 +8,7 @@ interface SheetMusicProps { clef?: 'treble' | 'bass'; width?: number; height?: number; + transpose?: number; // Transposition in semitones for visualization (e.g., +12 for guitar) } export const SheetMusic: React.FC = ({ @@ -15,7 +16,8 @@ export const SheetMusic: React.FC = ({ playedMidi, clef = 'treble', width = 300, - height = 200 + height = 200, + transpose = 12 // Default to +1 octave (Guitar Notation) }) => { const containerRef = useRef(null); @@ -35,37 +37,45 @@ export const SheetMusic: React.FC = ({ stave.addClef(clef); stave.setContext(context).draw(); + // Helper to create keys for VexFlow + const getVexFlowKey = (midi: number) => { + const visualMidi = midi + transpose; + const details = getNoteDetails(visualMidi); + return { + keys: [`${details.name.toLowerCase()}/${details.octave}`], + hasAccidental: details.name.includes("#") + }; + }; + // Create Target Note - const targetDetails = getNoteDetails(targetMidi); - const targetKey = `${targetDetails.name.toLowerCase()}/${targetDetails.octave}`; + const targetData = getVexFlowKey(targetMidi); const targetStaveNote = new StaveNote({ - keys: [targetKey], + keys: targetData.keys, duration: "w", clef: clef }); - if (targetDetails.name.includes("#")) { + if (targetData.hasAccidental) { targetStaveNote.addModifier(new Accidental("#")); } if (playedMidi) { - const playedDetails = getNoteDetails(playedMidi); - const playedKey = `${playedDetails.name.toLowerCase()}/${playedDetails.octave}`; + const playedData = getVexFlowKey(playedMidi); const playedStaveNote = new StaveNote({ - keys: [playedKey], + keys: playedData.keys, duration: "h", - clef: clef, + clef: clef }); // Target note as half note to match measure const targetNoteHalf = new StaveNote({ - keys: [targetKey], + keys: targetData.keys, duration: "h", clef: clef }); - if (targetDetails.name.includes("#")) targetNoteHalf.addModifier(new Accidental("#")); + if (targetData.hasAccidental) targetNoteHalf.addModifier(new Accidental("#")); if (playedMidi === targetMidi) { @@ -74,10 +84,11 @@ export const SheetMusic: React.FC = ({ playedStaveNote.setStyle({ fillStyle: "var(--color-error)", strokeStyle: "var(--color-error)" }); } - if (playedDetails.name.includes("#")) { + if (playedData.hasAccidental) { playedStaveNote.addModifier(new Accidental("#")); } + // Use camelCase properties as fixed previously const voiceCombined = new Voice({ numBeats: 4, beatValue: 4 }); voiceCombined.addTickables([targetNoteHalf, playedStaveNote]); @@ -91,7 +102,7 @@ export const SheetMusic: React.FC = ({ voice.draw(context, stave); } - }, [targetMidi, playedMidi, clef, width, height]); + }, [targetMidi, playedMidi, clef, width, height, transpose]); return
; }; diff --git a/src/music/NoteUtils.ts b/src/music/NoteUtils.ts index 1b5cb18..08d3eb9 100644 --- a/src/music/NoteUtils.ts +++ b/src/music/NoteUtils.ts @@ -49,3 +49,8 @@ export function getRandomNote(min: number, max: number, validNotes?: number[]): } return Math.floor(Math.random() * (max - min + 1)) + min; } + +// Helper to convert Written note to Sounding note for guitar +// e.g. Input: "C4" (Written) -> Returns C3 (Sounding) MIDI +// But usually we work with MIDI. +// This is just a note for logic: Guitar Sounding = Written - 12.