| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>3D Audio Spectrum Analyzer</title> |
| | <style> |
| | * { |
| | margin: 0; |
| | padding: 0; |
| | box-sizing: border-box; |
| | font-family: Arial, sans-serif; |
| | } |
| | |
| | body { |
| | background: #1a1a1a; |
| | color: #fff; |
| | } |
| | |
| | .container { |
| | max-width: 1200px; |
| | margin: 0 auto; |
| | padding: 20px; |
| | } |
| | |
| | .header { |
| | text-align: center; |
| | padding: 20px 0; |
| | } |
| | |
| | .controls { |
| | display: grid; |
| | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| | gap: 20px; |
| | margin: 20px 0; |
| | } |
| | |
| | .control-panel { |
| | background: #2a2a2a; |
| | padding: 20px; |
| | border-radius: 8px; |
| | } |
| | |
| | .visualization { |
| | display: grid; |
| | grid-template-columns: 2fr 1fr; |
| | gap: 20px; |
| | margin: 20px 0; |
| | } |
| | |
| | canvas { |
| | width: 100%; |
| | height: 400px; |
| | background: #2a2a2a; |
| | border-radius: 8px; |
| | } |
| | |
| | button { |
| | background: #4CAF50; |
| | color: white; |
| | padding: 10px 20px; |
| | border: none; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | margin: 5px; |
| | } |
| | |
| | button:hover { |
| | background: #45a049; |
| | } |
| | |
| | input[type="number"] { |
| | width: 100%; |
| | padding: 8px; |
| | margin: 5px 0; |
| | border-radius: 4px; |
| | border: 1px solid #444; |
| | background: #333; |
| | color: white; |
| | } |
| | |
| | .device-status { |
| | background: #2a2a2a; |
| | padding: 20px; |
| | border-radius: 8px; |
| | margin-top: 20px; |
| | } |
| | |
| | .preset-container { |
| | display: grid; |
| | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| | gap: 10px; |
| | margin: 20px 0; |
| | } |
| | |
| | .preset { |
| | background: #333; |
| | padding: 10px; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | } |
| | |
| | .preset:hover { |
| | background: #444; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="container"> |
| | <div class="header"> |
| | <h1>3D Audio Spectrum Analyzer</h1> |
| | </div> |
| |
|
| | <div class="controls"> |
| | <div class="control-panel"> |
| | <h3>Device Configuration</h3> |
| | <button id="startAnalysis">Start Analysis</button> |
| | <button id="stopAnalysis">Stop Analysis</button> |
| | <button id="calibrate">Calibrate Devices</button> |
| | <div> |
| | <label>Device Role:</label> |
| | <select id="deviceRole"> |
| | <option value="left">Left Channel</option> |
| | <option value="right">Right Channel</option> |
| | <option value="center">Center Channel</option> |
| | </select> |
| | </div> |
| | </div> |
| |
|
| | <div class="control-panel"> |
| | <h3>Binaural Beat Generator</h3> |
| | <div> |
| | <label>Base Frequency (Hz):</label> |
| | <input type="number" id="baseFreq" value="432" min="20" max="1000"> |
| | </div> |
| | <div> |
| | <label>Beat Frequency (Hz):</label> |
| | <input type="number" id="beatFreq" value="7" min="1" max="40"> |
| | </div> |
| | <button id="startBinaural">Generate Binaural Beat</button> |
| | <button id="stopBinaural">Stop</button> |
| | </div> |
| | </div> |
| |
|
| | <div class="visualization"> |
| | <canvas id="spectrumCanvas"></canvas> |
| | <canvas id="3dRoomCanvas"></canvas> |
| | </div> |
| |
|
| | <div class="preset-container"> |
| | <div class="preset"> |
| | <h4>Alpha Wave</h4> |
| | <p>8-12 Hz</p> |
| | </div> |
| | <div class="preset"> |
| | <h4>Theta Wave</h4> |
| | <p>4-7 Hz</p> |
| | </div> |
| | <div class="preset"> |
| | <h4>Delta Wave</h4> |
| | <p>0.5-4 Hz</p> |
| | </div> |
| | </div> |
| |
|
| | <div class="device-status"> |
| | <h3>Connected Devices</h3> |
| | <div id="deviceList"></div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | class AudioAnalyzer { |
| | constructor() { |
| | this.audioContext = null; |
| | this.analyser = null; |
| | this.oscillator = null; |
| | this.isPlaying = false; |
| | } |
| | |
| | async initialize() { |
| | try { |
| | this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| | this.analyser = this.audioContext.createAnalyser(); |
| | const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
| | const source = this.audioContext.createMediaStreamSource(stream); |
| | source.connect(this.analyser); |
| | this.setupVisualization(); |
| | } catch (error) { |
| | console.error('Error initializing audio:', error); |
| | } |
| | } |
| | |
| | generateBinauralBeat(baseFreq, beatFreq) { |
| | if (this.isPlaying) this.stopBinauralBeat(); |
| | |
| | const leftOsc = this.audioContext.createOscillator(); |
| | const rightOsc = this.audioContext.createOscillator(); |
| | const leftGain = this.audioContext.createGain(); |
| | const rightGain = this.audioContext.createGain(); |
| | const merger = this.audioContext.createChannelMerger(2); |
| | |
| | leftOsc.frequency.value = baseFreq; |
| | rightOsc.frequency.value = baseFreq + beatFreq; |
| | |
| | leftOsc.connect(leftGain); |
| | rightOsc.connect(rightGain); |
| | leftGain.connect(merger, 0, 0); |
| | rightGain.connect(merger, 0, 1); |
| | merger.connect(this.audioContext.destination); |
| | |
| | leftOsc.start(); |
| | rightOsc.start(); |
| | this.oscillator = { left: leftOsc, right: rightOsc }; |
| | this.isPlaying = true; |
| | } |
| | |
| | stopBinauralBeat() { |
| | if (this.oscillator) { |
| | this.oscillator.left.stop(); |
| | this.oscillator.right.stop(); |
| | this.isPlaying = false; |
| | } |
| | } |
| | |
| | setupVisualization() { |
| | const canvas = document.getElementById('spectrumCanvas'); |
| | const ctx = canvas.getContext('2d'); |
| | const width = canvas.width; |
| | const height = canvas.height; |
| | |
| | const draw = () => { |
| | requestAnimationFrame(draw); |
| | const dataArray = new Uint8Array(this.analyser.frequencyBinCount); |
| | this.analyser.getByteFrequencyData(dataArray); |
| | |
| | ctx.fillStyle = '#2a2a2a'; |
| | ctx.fillRect(0, 0, width, height); |
| | |
| | const barWidth = width / dataArray.length; |
| | let x = 0; |
| | |
| | dataArray.forEach(value => { |
| | const barHeight = value * height / 255; |
| | ctx.fillStyle = `hsl(${value}, 100%, 50%)`; |
| | ctx.fillRect(x, height - barHeight, barWidth, barHeight); |
| | x += barWidth; |
| | }); |
| | }; |
| | |
| | draw(); |
| | } |
| | } |
| | |
| | |
| | const analyzer = new AudioAnalyzer(); |
| | |
| | document.getElementById('startAnalysis').addEventListener('click', () => analyzer.initialize()); |
| | document.getElementById('startBinaural').addEventListener('click', () => { |
| | const baseFreq = parseFloat(document.getElementById('baseFreq').value); |
| | const beatFreq = parseFloat(document.getElementById('beatFreq').value); |
| | analyzer.generateBinauralBeat(baseFreq, beatFreq); |
| | }); |
| | document.getElementById('stopBinaural').addEventListener('click', () => analyzer.stopBinauralBeat()); |
| | |
| | |
| | const room3d = document.getElementById('3dRoomCanvas'); |
| | const ctx3d = room3d.getContext('2d'); |
| | |
| | function draw3dRoom() { |
| | |
| | |
| | ctx3d.fillStyle = '#2a2a2a'; |
| | ctx3d.fillRect(0, 0, room3d.width, room3d.height); |
| | ctx3d.strokeStyle = '#4CAF50'; |
| | ctx3d.beginPath(); |
| | ctx3d.moveTo(50, 50); |
| | ctx3d.lineTo(room3d.width - 50, 50); |
| | ctx3d.lineTo(room3d.width - 50, room3d.height - 50); |
| | ctx3d.lineTo(50, room3d.height - 50); |
| | ctx3d.closePath(); |
| | ctx3d.stroke(); |
| | } |
| | |
| | draw3dRoom(); |
| | </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> |