<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tuner</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background-color: beige;
}
canvas {
border: none;
width: 100%;
max-width: 100%;
height: auto;
}
#note {
margin-top: 10px;
font-size: 18px;
font-weight: bold;
text-align: center;
}
#buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 10px;
}
button {
margin: 5px;
padding: 10px;
font-size: 16px;
flex: 1 1 25%;
max-width: 100px;
}
@media (max-width: 600px) {
button {
flex: 1 1 33.33%;
}
}
</style>
</head>
<body>
<canvas id="waveformCanvas"></canvas>
<div id="note">Note: N/A</div>
<div id="buttons"></div>
<script>
const canvas = document.getElementById('waveformCanvas');
const canvasCtx = canvas.getContext('2d');
const noteDisplay = document.getElementById('note');
let audioContext;
let analyzer;
let dataArray;
let bufferLength;
// Note frequencies in Hz for A4 = 440Hz
const notesFrequencies = {
'C': 261.63,
'C#': 277.18,
'D': 293.66,
'D#': 311.13,
'E': 329.63,
'F': 349.23,
'F#': 369.99,
'G': 392.00,
'G#': 415.30,
'A': 440.00,
'A#': 466.16,
'B': 493.88
};
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = audioContext.createMediaStreamSource(stream);
analyzer = audioContext.createAnalyser();
analyzer.fftSize = 2048;
bufferLength = analyzer.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
source.connect(analyzer);
drawWaveform();
setInterval(detectNote, 500);
}).catch(err => {
console.error('Error accessing the microphone:', err);
});
function resizeCanvas() {
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight / 4;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function drawWaveform() {
requestAnimationFrame(drawWaveform);
analyzer.getByteTimeDomainData(dataArray);
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
canvasCtx.beginPath();
const sliceWidth = canvas.width / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * canvas.height / 2;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(canvas.width, canvas.height / 2);
canvasCtx.stroke();
}
// Detect the dominant frequency and map it to a musical note
function detectNote() {
const freqArray = new Float32Array(analyzer.frequencyBinCount);
analyzer.getFloatFrequencyData(freqArray);
let maxAmp = -Infinity;
let maxIndex = 0;
for (let i = 0; i < freqArray.length; i++) {
if (freqArray[i] > maxAmp) {
maxAmp = freqArray[i];
maxIndex = i;
}
}
// Nyquist frequency is half the sample rate of a signal
const nyquist = audioContext.sampleRate / 2;
const frequency = maxIndex * nyquist / freqArray.length;
const note = getNoteFromFrequency(frequency);
noteDisplay.textContent = `Note: ${note}`;
}
// Convert frequency to musical note
function getNoteFromFrequency(frequency) {
const notes = [
'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
];
const A4 = 440;
const semitoneRatio = Math.pow(2, 1 / 12);
const noteIndex = Math.round(12 * Math.log2(frequency / A4));
const note = notes[(noteIndex % 12 + 12) % 12];
return frequency ? note : 'N/A';
}
const buttonsContainer = document.getElementById('buttons');
Object.keys(notesFrequencies).forEach(note => {
const button = document.createElement('button');
button.textContent = note;
button.onclick = () => playNote(notesFrequencies[note]);
buttonsContainer.appendChild(button);
});
function playNote(frequency) {
const osc = audioContext.createOscillator();
osc.type = 'sine'; // Valid values include 'sine', 'square', 'triangle', 'sawtooth'
osc.frequency.setValueAtTime(frequency, audioContext.currentTime); // Frequency in Hz
osc.connect(audioContext.destination);
osc.start();
osc.stop(audioContext.currentTime + 1); // Play the note for 1 second
}
</script>
</body>
</html>