-
-
Save camAtGitHub/1cf6159aec5dc6eff89a44227be0f372 to your computer and use it in GitHub Desktop.
Microphone level visualizer - updated APIs and ghosting peak levels
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Audio Visualizer</title> | |
| </head> | |
| <body> | |
| <svg preserveAspectRatio="none" id="visualizer" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
| <defs> | |
| <mask id="mask"> | |
| <g id="maskGroup"></g> | |
| </mask> | |
| <linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%"> | |
| <stop offset="0%" style="stop-color:#ff0a0a;stop-opacity:1" /> | |
| <stop offset="20%" style="stop-color:#f1ff0a;stop-opacity:1" /> | |
| <stop offset="90%" style="stop-color:#d923b9;stop-opacity:1" /> | |
| <stop offset="100%" style="stop-color:#050d61;stop-opacity:1" /> | |
| </linearGradient> | |
| </defs> | |
| <rect x="0" y="0" width="100%" height="100%" fill="url(#gradient)" mask="url(#mask)"></rect> | |
| <g id="ghostGroup"></g> | |
| </svg> | |
| <div id="permission-container"> | |
| <p id="status-message">Please allow microphone access to start the visualizer.</p> | |
| <button id="allow-microphone">Allow Microphone</button> | |
| </div> | |
| <div id="mic-info" style="display: none;"></div> | |
| <style> | |
| html, body { | |
| width: 100%; | |
| height: 100%; | |
| padding: 0; | |
| margin: 0; | |
| background-color: #222; | |
| font-size: 0; | |
| } | |
| svg { | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| padding: 0; | |
| margin: 0; | |
| position: absolute; | |
| } | |
| #permission-container { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(0, 0, 0, 0.8); | |
| padding: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| color: white; | |
| font-family: sans-serif; | |
| font-size: 16px; | |
| } | |
| #permission-container button { | |
| padding: 10px 20px; | |
| background-color: #48b1f4; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| } | |
| #mic-info { | |
| position: absolute; | |
| top: 60%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(0, 0, 0, 0.8); | |
| padding: 10px; | |
| border-radius: 5px; | |
| color: white; | |
| font-family: sans-serif; | |
| font-size: 14px; | |
| text-align: center; | |
| } | |
| path { | |
| stroke-linecap: square; | |
| stroke: white; | |
| stroke-width: 0.5px; | |
| } | |
| .ghost { | |
| stroke: white; | |
| stroke-width: 0.5px; | |
| opacity: 0; | |
| transition: opacity 2s ease-out; | |
| } | |
| </style> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| var visualizer = document.getElementById('visualizer'); | |
| var mask = visualizer.querySelector('#mask'); | |
| var ghostGroup = visualizer.querySelector('#ghostGroup'); | |
| var permissionContainer = document.getElementById('permission-container'); | |
| var statusMessage = document.getElementById('status-message'); | |
| var allowButton = document.getElementById('allow-microphone'); | |
| var micInfo = document.getElementById('mic-info'); | |
| var ghostTimeouts = new Array(128).fill(null); // Track timeouts for fading | |
| const NOISE_THRESHOLD = 5; // Ignore amplitudes below this value | |
| allowButton.addEventListener('click', function() { | |
| navigator.mediaDevices.getUserMedia({audio: true}) | |
| .then(soundAllowed) | |
| .catch(soundNotAllowed); | |
| }); | |
| async function soundAllowed(stream) { | |
| permissionContainer.style.display = 'none'; | |
| // Get the active microphone | |
| const devices = await navigator.mediaDevices.enumerateDevices(); | |
| const micDevice = devices.find(device => device.kind === 'audioinput' && device.label); | |
| micInfo.textContent = `Using microphone: ${micDevice ? micDevice.label : 'Unknown'}`; | |
| micInfo.style.display = 'block'; | |
| var audioContent = new AudioContext(); | |
| var audioStream = audioContent.createMediaStreamSource(stream); | |
| var analyser = audioContent.createAnalyser(); | |
| audioStream.connect(analyser); | |
| analyser.fftSize = 1024; | |
| var frequencyArray = new Uint8Array(analyser.frequencyBinCount); | |
| var numBins = 128; | |
| visualizer.setAttribute('viewBox', '0 0 ' + numBins + ' 255'); | |
| var paths = []; | |
| var ghostPaths = []; | |
| var ghostLevels = new Array(numBins).fill(0); | |
| for (var i = 0; i < numBins; i++) { | |
| var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); | |
| path.setAttribute('stroke-dasharray', '4,1'); | |
| mask.appendChild(path); | |
| paths.push(path); | |
| var ghostPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); | |
| ghostPath.setAttribute('stroke-dasharray', '4,1'); | |
| ghostPath.classList.add('ghost'); | |
| ghostGroup.appendChild(ghostPath); | |
| ghostPaths.push(ghostPath); | |
| } | |
| function doDraw() { | |
| requestAnimationFrame(doDraw); | |
| analyser.getByteTimeDomainData(frequencyArray); | |
| for (let i = 0; i < numBins; i++) { | |
| var value = frequencyArray[i]; | |
| var adjustedLength = value - 127; | |
| paths[i].setAttribute('d', 'M ' + i + ',127 l 0,' + adjustedLength); | |
| var absAdjusted = Math.abs(adjustedLength); | |
| if (absAdjusted > ghostLevels[i] && absAdjusted > NOISE_THRESHOLD) { | |
| ghostLevels[i] = absAdjusted; | |
| ghostPaths[i].setAttribute('d', 'M ' + i + ',127 l 0,' + adjustedLength); | |
| ghostPaths[i].style.opacity = 0.3; | |
| clearTimeout(ghostTimeouts[i]); // Clear previous timeout | |
| ghostTimeouts[i] = setTimeout(() => { | |
| ghostPaths[i].style.opacity = 0; // Fade out after 2 seconds | |
| ghostLevels[i] = 0; // Reset peak level | |
| }, 2000); | |
| } | |
| } | |
| } | |
| doDraw(); | |
| } | |
| function soundNotAllowed(error) { | |
| statusMessage.textContent = 'Microphone access denied or error: ' + error.message; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment