This commit is contained in:
2025-12-21 12:32:03 +01:00
parent f510de47d1
commit 295d0cf0de
3 changed files with 33 additions and 15 deletions

View File

@@ -101,7 +101,7 @@ function App() {
} }
}, [pitchData, targetMidi, matchStartTime, generateNewNote]); }, [pitchData, targetMidi, matchStartTime, generateNewNote]);
const targetDetails = getNoteDetails(targetMidi);
// Hint text construction // Hint text construction
const hintPositions = useMemo(() => { const hintPositions = useMemo(() => {
@@ -135,7 +135,9 @@ function App() {
{settings.showHint && ( {settings.showHint && (
<div className="hint-card"> <div className="hint-card">
<div className="hint-note">{targetDetails.scientific}</div> <div className="hint-note">
{getNoteDetails(targetMidi + 12).scientific} (Written)
</div>
<div className="hint-positions"> <div className="hint-positions">
{hintPositions.map((p, i) => ( {hintPositions.map((p, i) => (
<span key={i} className="hint-tag"> <span key={i} className="hint-tag">

View File

@@ -8,6 +8,7 @@ interface SheetMusicProps {
clef?: 'treble' | 'bass'; clef?: 'treble' | 'bass';
width?: number; width?: number;
height?: number; height?: number;
transpose?: number; // Transposition in semitones for visualization (e.g., +12 for guitar)
} }
export const SheetMusic: React.FC<SheetMusicProps> = ({ export const SheetMusic: React.FC<SheetMusicProps> = ({
@@ -15,7 +16,8 @@ export const SheetMusic: React.FC<SheetMusicProps> = ({
playedMidi, playedMidi,
clef = 'treble', clef = 'treble',
width = 300, width = 300,
height = 200 height = 200,
transpose = 12 // Default to +1 octave (Guitar Notation)
}) => { }) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -35,37 +37,45 @@ export const SheetMusic: React.FC<SheetMusicProps> = ({
stave.addClef(clef); stave.addClef(clef);
stave.setContext(context).draw(); 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 // Create Target Note
const targetDetails = getNoteDetails(targetMidi); const targetData = getVexFlowKey(targetMidi);
const targetKey = `${targetDetails.name.toLowerCase()}/${targetDetails.octave}`;
const targetStaveNote = new StaveNote({ const targetStaveNote = new StaveNote({
keys: [targetKey], keys: targetData.keys,
duration: "w", duration: "w",
clef: clef clef: clef
}); });
if (targetDetails.name.includes("#")) { if (targetData.hasAccidental) {
targetStaveNote.addModifier(new Accidental("#")); targetStaveNote.addModifier(new Accidental("#"));
} }
if (playedMidi) { if (playedMidi) {
const playedDetails = getNoteDetails(playedMidi); const playedData = getVexFlowKey(playedMidi);
const playedKey = `${playedDetails.name.toLowerCase()}/${playedDetails.octave}`;
const playedStaveNote = new StaveNote({ const playedStaveNote = new StaveNote({
keys: [playedKey], keys: playedData.keys,
duration: "h", duration: "h",
clef: clef, clef: clef
}); });
// Target note as half note to match measure // Target note as half note to match measure
const targetNoteHalf = new StaveNote({ const targetNoteHalf = new StaveNote({
keys: [targetKey], keys: targetData.keys,
duration: "h", duration: "h",
clef: clef clef: clef
}); });
if (targetDetails.name.includes("#")) targetNoteHalf.addModifier(new Accidental("#")); if (targetData.hasAccidental) targetNoteHalf.addModifier(new Accidental("#"));
if (playedMidi === targetMidi) { if (playedMidi === targetMidi) {
@@ -74,10 +84,11 @@ export const SheetMusic: React.FC<SheetMusicProps> = ({
playedStaveNote.setStyle({ fillStyle: "var(--color-error)", strokeStyle: "var(--color-error)" }); playedStaveNote.setStyle({ fillStyle: "var(--color-error)", strokeStyle: "var(--color-error)" });
} }
if (playedDetails.name.includes("#")) { if (playedData.hasAccidental) {
playedStaveNote.addModifier(new Accidental("#")); playedStaveNote.addModifier(new Accidental("#"));
} }
// Use camelCase properties as fixed previously
const voiceCombined = new Voice({ numBeats: 4, beatValue: 4 }); const voiceCombined = new Voice({ numBeats: 4, beatValue: 4 });
voiceCombined.addTickables([targetNoteHalf, playedStaveNote]); voiceCombined.addTickables([targetNoteHalf, playedStaveNote]);
@@ -91,7 +102,7 @@ export const SheetMusic: React.FC<SheetMusicProps> = ({
voice.draw(context, stave); voice.draw(context, stave);
} }
}, [targetMidi, playedMidi, clef, width, height]); }, [targetMidi, playedMidi, clef, width, height, transpose]);
return <div ref={containerRef} className="sheet-music-container" />; return <div ref={containerRef} className="sheet-music-container" />;
}; };

View File

@@ -49,3 +49,8 @@ export function getRandomNote(min: number, max: number, validNotes?: number[]):
} }
return Math.floor(Math.random() * (max - min + 1)) + min; 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.