diff --git a/robs_metronome.html b/robs_metronome.html
index 5e1c50c..66cf287 100644
--- a/robs_metronome.html
+++ b/robs_metronome.html
@@ -263,6 +263,55 @@
margin-top: 10px;
min-height: 1em;
}
+
+ .tuner-grid {
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ gap: 8px;
+ }
+ .string-btn {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2px;
+ background: var(--panel);
+ border: 1px solid var(--rule);
+ border-radius: 6px;
+ padding: 12px 4px;
+ cursor: pointer;
+ font-family: "Work Sans";
+ color: var(--ink);
+ transition:
+ background 0.12s ease,
+ border-color 0.12s ease;
+ }
+ .string-btn .note {
+ font-family: "Fraunces", serif;
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: var(--walnut);
+ }
+ .string-btn .hz {
+ font-size: 0.68rem;
+ color: var(--ink-soft);
+ font-variant-numeric: tabular-nums;
+ }
+ .string-btn:hover {
+ border-color: var(--brass);
+ }
+ .string-btn.active {
+ background: var(--brass-light);
+ border-color: var(--brass);
+ }
+ .string-btn.active .note {
+ color: var(--walnut);
+ }
+ .tuner-hint {
+ text-align: center;
+ font-size: 0.8rem;
+ color: var(--ink-soft);
+ margin-top: 12px;
+ }
@@ -304,6 +353,14 @@
+ Tuner
+
+
+ Tap a string to play its tone. Tap again to stop.
+
+
+
+
Your tunes
Loading…
@@ -330,10 +387,8 @@
accept="application/json"
style="display: none"
/>
-
-
-
+
@@ -455,6 +510,79 @@
flashPad(false);
});
+ // ---------- Guitar tuner ----------
+ const GUITAR_STRINGS = [
+ { note: "E", octave: 2, freq: 82.41 },
+ { note: "A", octave: 2, freq: 110.0 },
+ { note: "D", octave: 3, freq: 146.83 },
+ { note: "G", octave: 3, freq: 196.0 },
+ { note: "B", octave: 3, freq: 246.94 },
+ { note: "E", octave: 4, freq: 329.63 },
+ ];
+
+ const tunerGrid = document.getElementById("tunerGrid");
+ let tuneOsc = null;
+ let tuneGain = null;
+ let activeStringBtn = null;
+
+ function stopTunerTone() {
+ if (tuneOsc) {
+ const g = tuneGain;
+ const now = audioCtx.currentTime;
+ g.gain.cancelScheduledValues(now);
+ g.gain.setValueAtTime(g.gain.value, now);
+ g.gain.exponentialRampToValueAtTime(0.0001, now + 0.06);
+ tuneOsc.stop(now + 0.08);
+ }
+ if (activeStringBtn) activeStringBtn.classList.remove("active");
+ tuneOsc = null;
+ tuneGain = null;
+ activeStringBtn = null;
+ }
+
+ function playTunerTone(freq, btn) {
+ ensureAudio();
+ if (audioCtx.state === "suspended") audioCtx.resume();
+
+ // Toggle off if this string is already sounding
+ if (activeStringBtn === btn) {
+ stopTunerTone();
+ return;
+ }
+
+ stopTunerTone();
+
+ const osc = audioCtx.createOscillator();
+ const gain = audioCtx.createGain();
+ osc.type = "sine";
+ osc.frequency.value = freq;
+ const now = audioCtx.currentTime;
+ gain.gain.setValueAtTime(0.0001, now);
+ gain.gain.exponentialRampToValueAtTime(0.28, now + 0.03);
+ osc.connect(gain);
+ gain.connect(audioCtx.destination);
+ osc.start();
+
+ tuneOsc = osc;
+ tuneGain = gain;
+ activeStringBtn = btn;
+ btn.classList.add("active");
+ }
+
+ GUITAR_STRINGS.forEach((s) => {
+ const btn = document.createElement("button");
+ btn.className = "string-btn";
+ btn.innerHTML =
+ '' +
+ s.note +
+ s.octave +
+ '' +
+ s.freq.toFixed(2) +
+ " Hz";
+ btn.addEventListener("click", () => playTunerTone(s.freq, btn));
+ tunerGrid.appendChild(btn);
+ });
+
// ---------- Tune storage (browser-local cache + JSON export/import) ----------
const tuneList = document.getElementById("tuneList");
const emptyMsg = document.getElementById("emptyMsg");