|
<!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> |