diff --git a/index.html b/index.html
index ee61933..b871010 100644
--- a/index.html
+++ b/index.html
@@ -846,8 +846,249 @@ function MicBar({ note, hz, feedback, listening, onToggle }) {
);
}
+
+const OPEN_STRINGS = [
+ {note:"E2", name:"Low E", freq:82.41, string:6},
+ {note:"A2", name:"A", freq:110.00, string:5},
+ {note:"D3", name:"D", freq:146.83, string:4},
+ {note:"G3", name:"G", freq:196.00, string:3},
+ {note:"B3", name:"B", freq:246.94, string:2},
+ {note:"E4", name:"High e", freq:329.63, string:1},
+];
+
+function HamburgerMenu({ onTuner, onBasics, onSongSearch, onClose }) {
+ return (
+ <>
+
+
+
+
+ GuitarTeacher
+
+
+
+
+ {[
+ {icon:"🎵", label:"Guitar Tuner", sub:"Tune before you play", action:onTuner},
+ {icon:"📖", label:"Guitar Basics", sub:"8 lessons for beginners", action:onBasics},
+ {icon:"🔍", label:"Song Search", sub:"70+ songs with tab", action:onSongSearch},
+ ].map(({icon,label,sub,action})=>(
+
+ ))}
+
+
+ GuitarTeacher - Learn at your own pace
+
+
+ >
+ );
+}
+
+
+function GuitarTuner({ onBack, onMenu }) {
+ const [listening, setListening] = useState(false);
+ const [detectedNote, setDetectedNote] = useState("");
+ const [detectedFreq, setDetectedFreq] = useState(0);
+ const [cents, setCents] = useState(0);
+ const [closestString, setClosestString] = useState(null);
+ const ctxRef = useRef(null);
+ const streamRef = useRef(null);
+ const rafRef = useRef(null);
+
+ const stopMic = useCallback(()=>{
+ if(streamRef.current) streamRef.current.getTracks().forEach(t=>t.stop());
+ if(rafRef.current) cancelAnimationFrame(rafRef.current);
+ if(ctxRef.current) ctxRef.current.close();
+ ctxRef.current=null; streamRef.current=null;
+ setListening(false); setDetectedNote(""); setDetectedFreq(0); setCents(0); setClosestString(null);
+ },[]);
+
+ const startMic = useCallback(async()=>{
+ if(listening){ stopMic(); return; }
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+ const ctx = new (window.AudioContext||window.webkitAudioContext)();
+ const analyser = ctx.createAnalyser();
+ analyser.fftSize = 4096;
+ ctx.createMediaStreamSource(stream).connect(analyser);
+ ctxRef.current=ctx; streamRef.current=stream;
+ setListening(true);
+ const buf = new Float32Array(analyser.fftSize);
+ const loop = ()=>{
+ rafRef.current = requestAnimationFrame(loop);
+ analyser.getFloatTimeDomainData(buf);
+ const freq = autoCorrelate(buf, ctx.sampleRate);
+ if(freq > 60 && freq < 1200){
+ const midi = Math.round(12*Math.log2(freq/440)+69);
+ const name = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"][midi%12];
+ const oct = Math.floor(midi/12)-1;
+ const exactHz = 440*Math.pow(2,(midi-69)/12);
+ const c = Math.round(1200*Math.log2(freq/exactHz));
+ setDetectedNote(name+oct);
+ setDetectedFreq(Math.round(freq*10)/10);
+ setCents(c);
+ const closest = OPEN_STRINGS.reduce((best,s)=>{
+ const diff = Math.abs(freq - s.freq);
+ return diff < Math.abs(freq - best.freq) ? s : best;
+ }, OPEN_STRINGS[0]);
+ setClosestString(closest);
+ }
+ };
+ loop();
+ } catch(e){ alert("Mic access denied. Please allow microphone access and try again."); }
+ },[listening, stopMic]);
+
+ useEffect(()=>()=>stopMic(),[]);
+
+ const inTune = Math.abs(cents) <= 5;
+ const slightlyOff = Math.abs(cents) <= 15;
+ const needleAngle = Math.max(-45, Math.min(45, cents * 0.9));
+ const needleColor = inTune ? C.sage : slightlyOff ? C.amber : C.coral;
+ const tuneMsg = inTune ? "In tune!" : cents < 0 ? "Tune up" : "Tune down";
+ const tuneMsgColor = inTune ? C.sage : slightlyOff ? C.amber : C.coral;
+
+ return (
+
+
+
+
+ GuitarTeacher
+ / Tuner
+
+