Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Vibe Voice - Real-Time Audio Visualizer</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #6c5ce7; | |
| --primary-dark: #5649c0; | |
| --secondary: #a29bfe; | |
| --accent: #fd79a8; | |
| --dark: #2d3436; | |
| --light: #f5f6fa; | |
| --success: #00b894; | |
| --warning: #fdcb6e; | |
| --danger: #d63031; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| color: var(--dark); | |
| line-height: 1.6; | |
| } | |
| header { | |
| width: 100%; | |
| padding: 1.5rem 2rem; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(10px); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| text-decoration: none; | |
| color: var(--primary); | |
| font-weight: 700; | |
| font-size: 1.5rem; | |
| } | |
| .logo i { | |
| font-size: 1.8rem; | |
| } | |
| .anycoder-link { | |
| color: var(--primary); | |
| text-decoration: none; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| } | |
| .anycoder-link:hover { | |
| color: var(--primary-dark); | |
| transform: translateY(-2px); | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 1200px; | |
| padding: 2rem; | |
| margin: 2rem 0; | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 1.5rem; | |
| color: var(--primary); | |
| font-size: 2.5rem; | |
| background: linear-gradient(90deg, var(--primary), var(--accent)); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| background-color: transparent; | |
| } | |
| .subheading { | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| color: var(--dark); | |
| font-size: 1.1rem; | |
| max-width: 700px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| flex-wrap: wrap; | |
| } | |
| button { | |
| padding: 0.8rem 1.5rem; | |
| border: none; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .btn-primary { | |
| background: var(--primary); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| background: var(--primary-dark); | |
| transform: translateY(-2px); | |
| } | |
| .btn-secondary { | |
| background: var(--secondary); | |
| color: var(--dark); | |
| } | |
| .btn-secondary:hover { | |
| background: #8a83e6; | |
| transform: translateY(-2px); | |
| } | |
| .btn-danger { | |
| background: var(--danger); | |
| color: white; | |
| } | |
| .btn-danger:hover { | |
| background: #b72727; | |
| transform: translateY(-2px); | |
| } | |
| .visualizer-container { | |
| background: rgba(255, 255, 255, 0.9); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| margin-bottom: 2rem; | |
| backdrop-filter: blur(10px); | |
| } | |
| .visualizer { | |
| width: 100%; | |
| height: 300px; | |
| display: flex; | |
| align-items: flex-end; | |
| justify-content: center; | |
| gap: 2px; | |
| padding: 1rem; | |
| background: rgba(255, 255, 255, 0.5); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| .bar { | |
| width: 4px; | |
| background: linear-gradient(to top, var(--primary), var(--accent)); | |
| border-radius: 2px; | |
| transition: height 0.1s ease; | |
| } | |
| .status { | |
| text-align: center; | |
| margin: 1rem 0; | |
| font-weight: 600; | |
| color: var(--dark); | |
| } | |
| .status.active { | |
| color: var(--success); | |
| } | |
| .stats { | |
| display: flex; | |
| justify-content: space-around; | |
| margin: 2rem 0; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .stat-card { | |
| background: white; | |
| padding: 1.5rem; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| text-align: center; | |
| flex: 1; | |
| min-width: 200px; | |
| } | |
| .stat-value { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| color: var(--primary); | |
| margin-bottom: 0.5rem; | |
| } | |
| .stat-label { | |
| color: var(--dark); | |
| font-size: 0.9rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .features { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| margin: 2rem 0; | |
| } | |
| .feature-card { | |
| background: white; | |
| padding: 1.5rem; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| transition: all 0.3s ease; | |
| } | |
| .feature-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); | |
| } | |
| .feature-icon { | |
| font-size: 2.5rem; | |
| color: var(--primary); | |
| margin-bottom: 1rem; | |
| } | |
| .feature-title { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| margin-bottom: 0.5rem; | |
| color: var(--dark); | |
| } | |
| .feature-description { | |
| color: #636e72; | |
| font-size: 0.95rem; | |
| } | |
| footer { | |
| width: 100%; | |
| padding: 1.5rem; | |
| background: rgba(255, 255, 255, 0.9); | |
| text-align: center; | |
| color: var(--dark); | |
| margin-top: auto; | |
| } | |
| .social-links { | |
| display: flex; | |
| justify-content: center; | |
| gap: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .social-links a { | |
| color: var(--dark); | |
| font-size: 1.5rem; | |
| transition: all 0.3s ease; | |
| } | |
| .social-links a:hover { | |
| color: var(--primary); | |
| transform: translateY(-3px); | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 1rem; | |
| } | |
| h1 { | |
| font-size: 2rem; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| .visualizer { | |
| height: 200px; | |
| } | |
| .stats { | |
| flex-direction: column; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| padding: 1rem; | |
| } | |
| .visualizer { | |
| height: 150px; | |
| } | |
| .bar { | |
| width: 2px; | |
| } | |
| } | |
| /* Loading animation */ | |
| .loading { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid rgba(255,255,255,.3); | |
| border-radius: 50%; | |
| border-top-color: white; | |
| animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Pulse animation for active state */ | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .status.active { | |
| animation: pulse 1.5s ease-in-out infinite; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <a href="#" class="logo"> | |
| <i class="fas fa-microphone-alt"></i> | |
| <span>Vibe Voice</span> | |
| </a> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank"> | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <div class="container"> | |
| <h1>Real-Time Audio Visualizer</h1> | |
| <p class="subheading"> | |
| Experience your voice like never before with our advanced real-time audio visualization. | |
| See the frequency spectrum of your voice and analyze its characteristics. | |
| </p> | |
| <div class="controls"> | |
| <button id="startBtn" class="btn-primary"> | |
| <i class="fas fa-play"></i> Start Visualization | |
| </button> | |
| <button id="stopBtn" class="btn-danger" disabled> | |
| <i class="fas fa-stop"></i> Stop Visualization | |
| </button> | |
| <button id="clearBtn" class="btn-secondary"> | |
| <i class="fas fa-eraser"></i> Clear Visualization | |
| </button> | |
| </div> | |
| <div class="status" id="status"> | |
| Ready to visualize your voice | |
| </div> | |
| <div class="visualizer-container"> | |
| <div class="visualizer" id="visualizer"> | |
| <!-- Bars will be added by JavaScript --> | |
| </div> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="frequency">0</div> | |
| <div class="stat-label">Dominant Frequency (Hz)</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="volume">0</div> | |
| <div class="stat-label">Volume Level</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="pitch">0</div> | |
| <div class="stat-label">Pitch Detection</div> | |
| </div> | |
| </div> | |
| <div class="features"> | |
| <div class="feature-card"> | |
| <div class="feature-icon"> | |
| <i class="fas fa-wave-square"></i> | |
| </div> | |
| <h3 class="feature-title">Real-Time Analysis</h3> | |
| <p class="feature-description"> | |
| Get instant feedback on your voice with our real-time audio processing engine. | |
| See frequency patterns as you speak or sing. | |
| </p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon"> | |
| <i class="fas fa-chart-line"></i> | |
| </div> | |
| <h3 class="feature-title">Frequency Visualization</h3> | |
| <p class="feature-description"> | |
| Visualize the frequency spectrum of your voice with our interactive bar graph. | |
| Understand the composition of your voice. | |
| </p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon"> | |
| <i class="fas fa-cog"></i> | |
| </div> | |
| <h3 class="feature-title">Advanced Processing</h3> | |
| <p class="feature-description"> | |
| Our system uses Web Audio API for precise audio analysis. | |
| Detect pitch, volume, and frequency characteristics. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <footer> | |
| <div class="social-links"> | |
| <a href="#" title="Twitter"><i class="fab fa-twitter"></i></a> | |
| <a href="#" title="Facebook"><i class="fab fa-facebook"></i></a> | |
| <a href="#" title="GitHub"><i class="fab fa-github"></i></a> | |
| <a href="#" title="Instagram"><i class="fab fa-instagram"></i></a> | |
| </div> | |
| <p>© 2023 Vibe Voice. All rights reserved.</p> | |
| </footer> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM elements | |
| const startBtn = document.getElementById('startBtn'); | |
| const stopBtn = document.getElementById('stopBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const status = document.getElementById('status'); | |
| const visualizer = document.getElementById('visualizer'); | |
| const frequencyDisplay = document.getElementById('frequency'); | |
| const volumeDisplay = document.getElementById('volume'); | |
| const pitchDisplay = document.getElementById('pitch'); | |
| // Audio context and analyzer | |
| let audioContext; | |
| let analyser; | |
| let dataArray; | |
| let source; | |
| let stream; | |
| let animationId; | |
| let isVisualizing = false; | |
| // Number of bars for visualization | |
| const barCount = 128; | |
| // Create visualizer bars | |
| function createVisualizer() { | |
| visualizer.innerHTML = ''; | |
| for (let i = 0; i < barCount; i++) { | |
| const bar = document.createElement('div'); | |
| bar.className = 'bar'; | |
| bar.style.height = '2px'; | |
| visualizer.appendChild(bar); | |
| } | |
| } | |
| // Initialize audio context | |
| function initAudio() { | |
| try { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 256; | |
| const bufferLength = analyser.frequencyBinCount; | |
| dataArray = new Uint8Array(bufferLength); | |
| createVisualizer(); | |
| status.textContent = "Ready to start visualization"; | |
| } catch (err) { | |
| console.error("Audio context error:", err); | |
| status.textContent = "Audio context not supported in your browser"; | |
| startBtn.disabled = true; | |
| } | |
| } | |
| // Start visualization | |
| async function startVisualization() { | |
| if (isVisualizing) return; | |
| try { | |
| status.textContent = "Accessing microphone..."; | |
| stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| source = audioContext.createMediaStreamSource(stream); | |
| source.connect(analyser); | |
| isVisualizing = true; | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| status.textContent = "Visualizing your voice"; | |
| status.classList.add('active'); | |
| draw(); | |
| } catch (err) { | |
| console.error("Microphone access error:", err); | |
| status.textContent = "Could not access microphone"; | |
| status.classList.remove('active'); | |
| startBtn.disabled = false; | |
| } | |
| } | |
| // Stop visualization | |
| function stopVisualization() { | |
| if (!isVisualizing) return; | |
| if (stream) { | |
| stream.getTracks().forEach(track => track.stop()); | |
| } | |
| if (source) { | |
| source.disconnect(); | |
| } | |
| if (animationId) { | |
| cancelAnimationFrame(animationId); | |
| } | |
| isVisualizing = false; | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| status.textContent = "Visualization stopped"; | |
| status.classList.remove('active'); | |
| // Reset displays | |
| frequencyDisplay.textContent = '0'; | |
| volumeDisplay.textContent = '0'; | |
| pitchDisplay.textContent = '0'; | |
| } | |
| // Clear visualization | |
| function clearVisualization() { | |
| const bars = document.querySelectorAll('.bar'); | |
| bars.forEach(bar => { | |
| bar.style.height = '2px'; | |
| }); | |
| // Reset displays | |
| frequencyDisplay.textContent = '0'; | |
| volumeDisplay.textContent = '0'; | |
| pitchDisplay.textContent = '0'; | |
| } | |
| // Draw visualization | |
| function draw() { | |
| analyser.getByteFrequencyData(dataArray); | |
| const bars = document.querySelectorAll('.bar'); | |
| let totalVolume = 0; | |
| let maxFrequency = 0; | |
| let maxIndex = 0; | |
| for (let i = 0; i < barCount; i++) { | |
| const barHeight = dataArray[i] / 2; | |
| bars[i].style.height = barHeight + 'px'; | |
| // Calculate total volume | |
| totalVolume += dataArray[i]; | |
| // Find dominant frequency | |
| if (dataArray[i] > maxFrequency) { | |
| maxFrequency = dataArray[i]; | |
| maxIndex = i; | |
| } | |
| } | |
| // Calculate average volume (0-100) | |
| const averageVolume = Math.round((totalVolume / barCount) * 100 / 255); | |
| volumeDisplay.textContent = averageVolume; | |
| // Calculate dominant frequency (approximate) | |
| const nyquist = audioContext.sampleRate / 2; | |
| const dominantFreq = Math.round((maxIndex * nyquist) / analyser.fftSize); | |
| frequencyDisplay.textContent = dominantFreq; | |
| // Simple pitch detection (note: this is a simplified approximation) | |
| const pitch = dominantFreq > 0 ? Math.round(12 * (Math.log(dominantFreq / 440) / Math.log(2)) + 69) : 0; | |
| pitchDisplay.textContent = pitch > 0 ? pitch : '0'; | |
| animationId = requestAnimationFrame(draw); | |
| } | |
| // Event listeners | |
| startBtn.addEventListener('click', startVisualization); | |
| stopBtn.addEventListener('click', stopVisualization); | |
| clearBtn.addEventListener('click', clearVisualization); | |
| // Initialize on page load | |
| initAudio(); | |
| // Clean up on page unload | |
| window.addEventListener('beforeunload', function() { | |
| if (isVisualizing) { | |
| stopVisualization(); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |