diff --git a/index.html b/index.html index 5d267ef..57e6355 100644 --- a/index.html +++ b/index.html @@ -510,41 +510,128 @@ const OPEN_NOTES = [ {label:"G string",note:"G3"},{label:"B string",note:"B3"},{label:"High e string",note:"E4"}, ]; -function OpenStringPractice({ detected, onTarget }) { +function OpenStringPractice({ detected, onTarget, onComplete }) { const [idx, setIdx] = useState(0); - const [done, setDone] = useState({}); + const [heard, setHeard] = useState(false); + const [doneSet, setDoneSet] = useState({}); + const [finished, setFinished] = useState(false); + const [kudos] = useState(()=>{ + const msgs = ["Great job!", "You nailed it!", "Awesome work!", "Sounding good!", "Perfect!", "Nice playing!"]; + return msgs[Math.floor(Math.random()*msgs.length)]; + }); - useEffect(()=>{ onTarget(idx < OPEN_NOTES.length ? OPEN_NOTES[idx].note : null); },[idx]); + const current = OPEN_NOTES[idx]; useEffect(()=>{ - if (!detected || idx >= OPEN_NOTES.length) return; - if (detected === OPEN_NOTES[idx].note){ - setDone(d=>({...d,[idx]:true})); - setTimeout(()=>setIdx(i=>i+1), 700); + if (!finished) onTarget(current ? current.note : null); + },[idx, finished]); + + useEffect(()=>{ + if (!detected || heard || finished || !current) return; + if (detected === current.note) setHeard(true); + },[detected, heard, finished, current]); + + const handleNext = () => { + setDoneSet(d=>({...d,[idx]:true})); + setHeard(false); + if (idx + 1 >= OPEN_NOTES.length) { + setFinished(true); + onTarget(null); + if (onComplete) onComplete(); + } else { + setIdx(i=>i+1); } - },[detected, idx]); + }; + + const NUM_FRETS=5, NS=6, W=320, H=200; + const TOP=44, BOT=H-24, LEFT=44, RIGHT=W-12; + const strGap=(BOT-TOP)/(NS-1); + const fretGap=(RIGHT-LEFT)/NUM_FRETS; + const STRING_LABELS=["e","B","G","D","A","E"]; return ( -
- {idx < OPEN_NOTES.length ? ( +
+ {!finished ? ( <> -
Pluck this string:
-
{OPEN_NOTES[idx].label}
-
Target: {OPEN_NOTES[idx].note}
+
+
+
Pluck this string:
+
{current.label}
+ {!heard &&
Target: {current.note}
} + {heard &&
Heard it!
} +
+ + + {Array.from({length:NUM_FRETS+1},(_,f)=>( + + ))} + {Array.from({length:NS},(_,s)=>{ + const isTarget = s === current.note && false; + const strIdx = NS-1-s; + const targetStrIdx = OPEN_NOTES.indexOf(current); + const isHighlighted = (NS-1-s) === idx; + return ( + + {isHighlighted && ( + + )} + + + {STRING_LABELS[s]} + + {doneSet[NS-1-s] && ( + ✓ + )} + + ); + })} + {Array.from({length:NUM_FRETS},(_,f)=>( + {f+1} + ))} + +
+ + {heard && ( + + )} + +
+ {OPEN_NOTES.map((s,i)=>( +
+ {doneSet[i]?"✓":s.note} +
+ ))} +
) : ( -
All 6 open strings done!
- )} -
- {OPEN_NOTES.map((s,i)=>( -
- {s.note} +
+
🎸
+
{kudos}
+
You just played all 6 open strings. Click Next to continue.
+
+ {OPEN_NOTES.map((s,i)=>( +
✓
+ ))}
- ))} -
+
+ )}
); } @@ -784,11 +871,17 @@ function LessonBody({ id, detected, onTarget, listening, onStartMic }) { Read left to right. 0 = open string. 2 = press 2nd fret. Numbers stacked vertically = chord.
); + const [stringsComplete, setStringsComplete] = useState(false); if (id === 2) return (
-

Start with open strings. Enable the mic and the app confirms each note as you pluck.

- - Press just behind the fret wire, not on top of it. +

Pluck each open string one at a time. The app listens and lights it up green when it hears you - then tap Next to move on.

+ setStringsComplete(true)}/> + {!stringsComplete && Open strings means no fingers on the frets - just pluck the string.} + {stringsComplete && ( +
+ setStringsComplete(false)} variant="secondary">Start over +
+ )}
); if (id === 3) return (