| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Microphone Test</title> |
| | <style> |
| | body { |
| | font-family: monospace; |
| | padding: 20px; |
| | max-width: 800px; |
| | margin: 0 auto; |
| | } |
| | button { |
| | padding: 10px 20px; |
| | font-size: 16px; |
| | margin: 10px 0; |
| | } |
| | #log { |
| | background: #000; |
| | color: #0f0; |
| | padding: 10px; |
| | font-size: 12px; |
| | height: 400px; |
| | overflow-y: scroll; |
| | margin-top: 20px; |
| | } |
| | #meter { |
| | width: 100%; |
| | height: 40px; |
| | background: #222; |
| | margin-top: 10px; |
| | position: relative; |
| | } |
| | #meter-bar { |
| | height: 100%; |
| | background: linear-gradient(to right, green, yellow, red); |
| | width: 0%; |
| | transition: width 0.05s; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <h1>Microphone Test</h1> |
| | <p>This tests if your microphone is working with Web Audio API</p> |
| |
|
| | <label for="deviceSelect">Select Microphone:</label> |
| | <select id="deviceSelect" style="width: 100%; padding: 8px; margin: 10px 0; background: #222; color: #0f0; border: 1px solid #0f0; font-family: monospace;"> |
| | <option value="">Default Microphone</option> |
| | </select> |
| |
|
| | <button id="start">Start Microphone Test</button> |
| | <button id="stop" disabled>Stop Test</button> |
| |
|
| | <div id="meter"> |
| | <div id="meter-bar"></div> |
| | </div> |
| |
|
| | <div id="log"></div> |
| |
|
| | <script> |
| | const logEl = document.getElementById('log'); |
| | const meterBar = document.getElementById('meter-bar'); |
| | const startBtn = document.getElementById('start'); |
| | const stopBtn = document.getElementById('stop'); |
| | const deviceSelect = document.getElementById('deviceSelect'); |
| | |
| | let audioContext = null; |
| | let source = null; |
| | let analyser = null; |
| | let processor = null; |
| | let animationId = null; |
| | |
| | |
| | async function loadDevices() { |
| | try { |
| | const devices = await navigator.mediaDevices.enumerateDevices(); |
| | const audioInputs = devices.filter(d => d.kind === 'audioinput'); |
| | |
| | deviceSelect.innerHTML = '<option value="">Default Microphone</option>'; |
| | audioInputs.forEach(device => { |
| | const option = document.createElement('option'); |
| | option.value = device.deviceId; |
| | option.textContent = device.label || `Microphone ${device.deviceId.slice(0, 8)}...`; |
| | deviceSelect.appendChild(option); |
| | }); |
| | |
| | console.log('Available devices:', audioInputs); |
| | } catch (error) { |
| | console.error('Failed to enumerate devices:', error); |
| | } |
| | } |
| | |
| | loadDevices(); |
| | |
| | function log(msg) { |
| | const line = document.createElement('div'); |
| | line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; |
| | logEl.appendChild(line); |
| | logEl.scrollTop = logEl.scrollHeight; |
| | console.log(msg); |
| | } |
| | |
| | async function startTest() { |
| | try { |
| | const selectedDeviceId = deviceSelect.value; |
| | log(`Requesting microphone access... ${selectedDeviceId ? `(Device: ${deviceSelect.options[deviceSelect.selectedIndex].text})` : '(Default)'}`); |
| | |
| | const audioConstraints = { |
| | channelCount: 1, |
| | echoCancellation: false, |
| | noiseSuppression: false, |
| | autoGainControl: false, |
| | }; |
| | |
| | if (selectedDeviceId) { |
| | audioConstraints.deviceId = { exact: selectedDeviceId }; |
| | } |
| | |
| | const stream = await navigator.mediaDevices.getUserMedia({ |
| | audio: audioConstraints |
| | }); |
| | |
| | log('✓ Microphone access granted'); |
| | |
| | |
| | await loadDevices(); |
| | |
| | const tracks = stream.getAudioTracks(); |
| | log(`Stream has ${tracks.length} audio tracks`); |
| | if (tracks.length > 0) { |
| | const track = tracks[0]; |
| | const settings = track.getSettings(); |
| | log(`Track: ${track.label}`); |
| | log(`Settings: ${JSON.stringify(settings, null, 2)}`); |
| | log(`Enabled: ${track.enabled}, Muted: ${track.muted}, State: ${track.readyState}`); |
| | } |
| | |
| | audioContext = new AudioContext(); |
| | log(`AudioContext created: ${audioContext.sampleRate}Hz, state: ${audioContext.state}`); |
| | |
| | if (audioContext.state === 'suspended') { |
| | await audioContext.resume(); |
| | log(`AudioContext resumed to: ${audioContext.state}`); |
| | } |
| | |
| | source = audioContext.createMediaStreamSource(stream); |
| | log('MediaStreamSource created'); |
| | |
| | |
| | analyser = audioContext.createAnalyser(); |
| | analyser.fftSize = 2048; |
| | source.connect(analyser); |
| | log('AnalyserNode connected'); |
| | |
| | const dataArray = new Uint8Array(analyser.frequencyBinCount); |
| | |
| | function checkAnalyser() { |
| | analyser.getByteTimeDomainData(dataArray); |
| | let sum = 0; |
| | let max = 0; |
| | for (let i = 0; i < dataArray.length; i++) { |
| | const val = Math.abs(dataArray[i] - 128); |
| | sum += val; |
| | max = Math.max(max, val); |
| | } |
| | const avg = sum / dataArray.length; |
| | const percent = (max / 128) * 100; |
| | meterBar.style.width = percent + '%'; |
| | |
| | log(`Analyser - Avg: ${avg.toFixed(2)}, Max: ${max}, Samples: ${dataArray.length}`); |
| | |
| | if (avg < 0.1) { |
| | log('⚠️ WARNING: Audio level is 0 or very quiet!'); |
| | } |
| | } |
| | |
| | setTimeout(checkAnalyser, 500); |
| | setTimeout(checkAnalyser, 1000); |
| | setTimeout(checkAnalyser, 2000); |
| | |
| | |
| | processor = audioContext.createScriptProcessor(4096, 1, 1); |
| | source.connect(processor); |
| | processor.connect(audioContext.destination); |
| | |
| | let chunkCount = 0; |
| | processor.onaudioprocess = (event) => { |
| | const inputData = event.inputBuffer.getChannelData(0); |
| | const max = Math.max(...Array.from(inputData).map(Math.abs)); |
| | const avg = Array.from(inputData).reduce((sum, val) => sum + Math.abs(val), 0) / inputData.length; |
| | |
| | chunkCount++; |
| | if (chunkCount % 10 === 0) { |
| | log(`ScriptProcessor - Chunk ${chunkCount}, Avg: ${avg.toFixed(6)}, Max: ${max.toFixed(6)}, Length: ${inputData.length}`); |
| | |
| | if (max < 0.0001) { |
| | log('⚠️ WARNING: ScriptProcessor getting all zeros!'); |
| | } |
| | } |
| | |
| | |
| | const percent = (max * 100); |
| | meterBar.style.width = Math.min(100, percent) + '%'; |
| | }; |
| | |
| | log('ScriptProcessorNode connected and listening...'); |
| | log('✓ Test running - speak into your microphone!'); |
| | |
| | startBtn.disabled = true; |
| | stopBtn.disabled = false; |
| | deviceSelect.disabled = true; |
| | |
| | } catch (error) { |
| | log(`❌ ERROR: ${error.message}`); |
| | console.error(error); |
| | } |
| | } |
| | |
| | function stopTest() { |
| | if (processor) processor.disconnect(); |
| | if (analyser) analyser.disconnect(); |
| | if (source) source.disconnect(); |
| | if (audioContext) audioContext.close(); |
| | if (animationId) cancelAnimationFrame(animationId); |
| | |
| | log('Test stopped'); |
| | startBtn.disabled = false; |
| | stopBtn.disabled = true; |
| | deviceSelect.disabled = false; |
| | meterBar.style.width = '0%'; |
| | } |
| | |
| | startBtn.addEventListener('click', startTest); |
| | stopBtn.addEventListener('click', stopTest); |
| | </script> |
| | </body> |
| | </html> |
| |
|