Spaces:
Running
Running
| <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; | |
| // Enumerate devices on page load | |
| 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'); | |
| // Refresh device list now that we have permission | |
| 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'); | |
| // Test 1: AnalyserNode | |
| 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); | |
| // Test 2: ScriptProcessorNode | |
| 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!'); | |
| } | |
| } | |
| // Update meter | |
| 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> | |