diff options
author | elioat <elioat@tilde.institute> | 2024-09-25 22:35:30 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-09-25 22:35:30 -0400 |
commit | 1d73f5df208dffe157aff4f6261518e852d873fb (patch) | |
tree | 695083aa155029c097be6e66b06dbc258b54da3f /html/tuner | |
parent | e010163faaca10c20d5301af99f1505bb361d5a9 (diff) | |
download | tour-1d73f5df208dffe157aff4f6261518e852d873fb.tar.gz |
*
Diffstat (limited to 'html/tuner')
-rw-r--r-- | html/tuner/index.html | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/html/tuner/index.html b/html/tuner/index.html new file mode 100644 index 0000000..4fe848d --- /dev/null +++ b/html/tuner/index.html @@ -0,0 +1,193 @@ +<!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 analyser; + 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); + + analyser = audioContext.createAnalyser(); + analyser.fftSize = 2048; + bufferLength = analyser.frequencyBinCount; + dataArray = new Uint8Array(bufferLength); + + source.connect(analyser); + 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); + + analyser.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(analyser.frequencyBinCount); + analyser.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> |