| <!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> |
|
|