Skip to content

Instantly share code, notes, and snippets.

@ggorlen
Created April 7, 2025 19:39
Show Gist options
  • Save ggorlen/fb77ee7856ad5f9e79cd3cfa568b6e39 to your computer and use it in GitHub Desktop.
Save ggorlen/fb77ee7856ad5f9e79cd3cfa568b6e39 to your computer and use it in GitHub Desktop.
Guitar tuner
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="dark light">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 16 16'><text x='0' y='14'>🎸</text></svg>" />
<title>Guitar Tuner</title>
<style>
body {
font-family: sans-serif;
padding: 2rem;
}
h1 {
margin-bottom: 1rem;
}
.note-buttons {
margin-top: 1rem;
}
button {
margin: 0.3rem;
padding: 0.5rem 1rem;
font-size: 1rem;
}
button.active {
background-color: #4caf50;
color: white;
}
select {
font-size: 1rem;
padding: 0.4rem;
}
</style>
</head>
<body>
<!-- https://chatgpt.com/c/67f40eff-996c-8012-981f-6c5082a85dd2 -->
<h1>Guitar Tuner</h1>
<label for="tuning-select">Tuning:</label>
<select id="tuning-select" onchange="loadTuning()">
<option value="standard">Standard E</option>
<option value="dropD">Drop D</option>
<option value="halfStepDown">Half Step Down</option>
<option value="fullStepDown">Full Step Down</option>
<option value="openD">Open D</option>
</select>
<div class="note-buttons" id="note-buttons"></div>
<script>
let audioContext;
let currentOscillator = null;
let currentGain = null;
let currentButton = null;
const noteMap = {
C: 0, 'C#': 1, Db: 1, D: 2, 'D#': 3, Eb: 3, E: 4, F: 5,
'F#': 6, Gb: 6, G: 7, 'G#': 8, Ab: 8, A: 9, 'A#': 10, Bb: 10, B: 11
};
function noteToFrequency(note) {
const match = note.match(/^([A-Ga-g#b]+)(-?\d+)$/);
if (!match) throw new Error(`Invalid note: ${note}`);
let [, pitch, octave] = match;
pitch = pitch.toUpperCase();
octave = parseInt(octave, 10);
const semitoneOffset = noteMap[pitch];
if (semitoneOffset === undefined) throw new Error(`Invalid pitch: ${pitch}`);
const noteNumber = (octave + 1) * 12 + semitoneOffset;
return 440 * Math.pow(2, (noteNumber - 69) / 12);
}
const tunings = {
standard: ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'],
dropD: ['D2', 'A2', 'D3', 'G3', 'B3', 'E4'],
halfStepDown: ['Eb2', 'Ab2', 'Db3', 'Gb3', 'Bb3', 'Eb4'],
fullStepDown: ['D2', 'G2', 'C3', 'F3', 'A3', 'D4'],
openD: ['D2', 'A2', 'D3', 'F#3', 'A3', 'D4']
};
function stopCurrentTone() {
if (currentOscillator) {
currentOscillator.stop();
currentOscillator.disconnect();
currentGain.disconnect();
currentOscillator = null;
currentGain = null;
}
if (currentButton) {
currentButton.classList.remove('active');
currentButton = null;
}
}
function toggleTone(button, frequency) {
if (button === currentButton) {
stopCurrentTone();
return;
}
stopCurrentTone();
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
currentOscillator = oscillator;
currentGain = gainNode;
currentButton = button;
button.classList.add('active');
}
function loadTuning() {
stopCurrentTone();
const tuningName = document.getElementById('tuning-select').value;
const tuningNotes = tunings[tuningName];
const container = document.getElementById('note-buttons');
container.innerHTML = '';
tuningNotes.forEach(note => {
const btn = document.createElement('button');
btn.textContent = note;
btn.onclick = () => {
const freq = noteToFrequency(note);
toggleTone(btn, freq);
};
container.appendChild(btn);
});
}
loadTuning();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment