Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Multi-Device Audio Analysis System</title> | |
| <style> | |
| :root { | |
| --primary: #2196F3; | |
| --secondary: #4CAF50; | |
| --bg-dark: #1a1a1a; | |
| --bg-light: #2a2a2a; | |
| --text: #ffffff; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Segoe UI', system-ui, sans-serif; | |
| } | |
| body { | |
| background: var(--bg-dark); | |
| color: var(--text); | |
| min-height: 100vh; | |
| } | |
| .app-container { | |
| display: grid; | |
| grid-template-columns: 300px 1fr; | |
| gap: 20px; | |
| padding: 20px; | |
| height: 100vh; | |
| } | |
| .sidebar { | |
| background: var(--bg-light); | |
| border-radius: 12px; | |
| padding: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| .main-content { | |
| display: grid; | |
| grid-template-rows: auto 1fr; | |
| gap: 20px; | |
| } | |
| .visualization-container { | |
| display: grid; | |
| grid-template-columns: 2fr 1fr; | |
| gap: 20px; | |
| } | |
| .canvas-container { | |
| background: var(--bg-light); | |
| border-radius: 12px; | |
| padding: 20px; | |
| position: relative; | |
| } | |
| canvas { | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.2); | |
| border-radius: 8px; | |
| } | |
| .control-panel { | |
| background: var(--bg-light); | |
| border-radius: 12px; | |
| padding: 20px; | |
| } | |
| .btn { | |
| background: var(--primary); | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: opacity 0.2s; | |
| width: 100%; | |
| margin-bottom: 10px; | |
| } | |
| .btn:hover { | |
| opacity: 0.9; | |
| } | |
| .btn.secondary { | |
| background: var(--secondary); | |
| } | |
| .input-group { | |
| margin-bottom: 15px; | |
| } | |
| .input-group label { | |
| display: block; | |
| margin-bottom: 5px; | |
| color: #888; | |
| } | |
| input[type="number"], | |
| select { | |
| width: 100%; | |
| padding: 8px; | |
| background: rgba(255,255,255,0.1); | |
| border: 1px solid rgba(255,255,255,0.2); | |
| border-radius: 4px; | |
| color: white; | |
| } | |
| .device-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .device-item { | |
| background: rgba(255,255,255,0.1); | |
| padding: 10px; | |
| border-radius: 4px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .status-indicator { | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: var(--secondary); | |
| } | |
| #frequencyDisplay { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| background: rgba(0,0,0,0.7); | |
| padding: 5px 10px; | |
| border-radius: 4px; | |
| } | |
| .preset-list { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); | |
| gap: 10px; | |
| } | |
| .preset-item { | |
| background: rgba(255,255,255,0.1); | |
| padding: 10px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| } | |
| .preset-item:hover { | |
| background: rgba(255,255,255,0.2); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <aside class="sidebar"> | |
| <div class="control-panel"> | |
| <h3>Device Settings</h3> | |
| <div class="input-group"> | |
| <label>Device Role</label> | |
| <select id="deviceRole"> | |
| <option value="primary">Primary</option> | |
| <option value="left">Left Channel</option> | |
| <option value="right">Right Channel</option> | |
| </select> | |
| </div> | |
| <button class="btn" id="startCapture">Start Capture</button> | |
| <button class="btn secondary" id="calibrate">Calibrate</button> | |
| </div> | |
| <div class="device-list"> | |
| <h3>Connected Devices</h3> | |
| <div class="device-item"> | |
| <span>This Device</span> | |
| <div class="status-indicator"></div> | |
| </div> | |
| </div> | |
| </aside> | |
| <main class="main-content"> | |
| <div class="control-panel"> | |
| <h2>Binaural Beat Generator</h2> | |
| <div class="input-group"> | |
| <label>Base Frequency (Hz)</label> | |
| <input type="number" id="baseFreq" value="432" min="20" max="1000"> | |
| </div> | |
| <div class="input-group"> | |
| <label>Beat Frequency (Hz)</label> | |
| <input type="number" id="beatFreq" value="7" min="1" max="40"> | |
| </div> | |
| <button class="btn" id="generateBeat">Generate Beat</button> | |
| </div> | |
| <div class="visualization-container"> | |
| <div class="canvas-container"> | |
| <canvas id="spectrumAnalyzer"></canvas> | |
| <div id="frequencyDisplay">0 Hz</div> | |
| </div> | |
| <div class="canvas-container"> | |
| <canvas id="roomAnalysis"></canvas> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <script> | |
| class AudioSystem { | |
| constructor() { | |
| this.audioContext = null; | |
| this.analyser = null; | |
| this.gainNode = null; | |
| this.oscillators = {}; | |
| this.isCapturing = false; | |
| this.isGenerating = false; | |
| this.initialize(); | |
| this.setupEventListeners(); | |
| } | |
| async initialize() { | |
| try { | |
| this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| this.analyser = this.audioContext.createAnalyser(); | |
| this.gainNode = this.audioContext.createGain(); | |
| this.analyser.fftSize = 2048; | |
| this.gainNode.connect(this.audioContext.destination); | |
| this.analyser.connect(this.gainNode); | |
| await this.setupSpectrumVisualizer(); | |
| } catch (error) { | |
| console.error('Audio initialization failed:', error); | |
| } | |
| } | |
| async startCapture() { | |
| if (this.isCapturing) return; | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| const source = this.audioContext.createMediaStreamSource(stream); | |
| source.connect(this.analyser); | |
| this.isCapturing = true; | |
| } catch (error) { | |
| console.error('Capture failed:', error); | |
| } | |
| } | |
| generateBinauralBeat(baseFreq, beatFreq) { | |
| if (this.isGenerating) this.stopBinauralBeat(); | |
| const leftOsc = this.audioContext.createOscillator(); | |
| const rightOsc = this.audioContext.createOscillator(); | |
| leftOsc.frequency.value = baseFreq; | |
| rightOsc.frequency.value = baseFreq + beatFreq; | |
| const merger = this.audioContext.createChannelMerger(2); | |
| leftOsc.connect(merger, 0, 0); | |
| rightOsc.connect(merger, 0, 1); | |
| merger.connect(this.gainNode); | |
| leftOsc.start(); | |
| rightOsc.start(); | |
| this.oscillators = { left: leftOsc, right: rightOsc }; | |
| this.isGenerating = true; | |
| } | |
| stopBinauralBeat() { | |
| if (!this.isGenerating) return; | |
| Object.values(this.oscillators).forEach(osc => osc.stop()); | |
| this.oscillators = {}; | |
| this.isGenerating = false; | |
| } | |
| async setupSpectrumVisualizer() { | |
| const canvas = document.getElementById('spectrumAnalyzer'); | |
| const ctx = canvas.getContext('2d'); | |
| const bufferLength = this.analyser.frequencyBinCount; | |
| const dataArray = new Uint8Array(bufferLength); | |
| const draw = () => { | |
| requestAnimationFrame(draw); | |
| this.analyser.getByteFrequencyData(dataArray); | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| const barWidth = canvas.width / bufferLength; | |
| let x = 0; | |
| for(let i = 0; i < bufferLength; i++) { | |
| const barHeight = (dataArray[i] / 255) * canvas.height; | |
| const hue = i / bufferLength * 360; | |
| ctx.fillStyle = `hsl(${hue}, 100%, 50%)`; | |
| ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); | |
| x += barWidth + 1; | |
| } | |
| }; | |
| draw(); | |
| } | |
| setupEventListeners() { | |
| document.getElementById('startCapture').onclick = () => this.startCapture(); | |
| document.getElementById('generateBeat').onclick = () => { | |
| const baseFreq = parseFloat(document.getElementById('baseFreq').value); | |
| const beatFreq = parseFloat(document.getElementById('beatFreq').value); | |
| this.generateBinauralBeat(baseFreq, beatFreq); | |
| }; | |
| } | |
| } | |
| // Initialize the system | |
| const audioSystem = new AudioSystem(); | |
| </script> | |
| </body> | |
| </html><script async data-explicit-opt-in="true" data-cookie-opt-in="true" src="https://vercel.live/_next-live/feedback/feedback.js"></script> |