GameSoundSynth / index.html
MySafeCode's picture
Upload folder using huggingface_hub
fac0845 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game Sound Synthesizer - 20 Effects</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Orbitron', monospace;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
overflow-x: hidden;
}
.sound-button {
background: linear-gradient(145deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.sound-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.sound-button.playing::before {
width: 300px;
height: 300px;
}
.sound-button:hover {
transform: translateY(-5px) scale(1.05);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
border-color: rgba(255, 255, 255, 0.4);
}
.sound-button:active {
transform: translateY(-2px) scale(1.02);
}
.visualizer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100px;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
display: flex;
align-items: flex-end;
justify-content: space-around;
padding: 10px;
z-index: 10;
}
.bar {
width: 3px;
background: linear-gradient(to top, #00ff88, #00ffff);
transition: height 0.1s ease;
border-radius: 2px;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.pulse {
animation: pulse 0.5s ease-in-out;
}
.glass-morphism {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.neon-text {
text-shadow: 0 0 10px rgba(255, 255, 255, 0.8),
0 0 20px rgba(255, 255, 255, 0.6),
0 0 30px rgba(255, 255, 255, 0.4);
}
.category-badge {
background: linear-gradient(135deg, #667eea, #764ba2);
padding: 2px 8px;
border-radius: 12px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.float-animation {
animation: float 3s ease-in-out infinite;
}
</style>
</head>
<body>
<!-- Header -->
<header class="glass-morphism sticky top-0 z-50 p-4 mb-8">
<div class="container mx-auto flex justify-between items-center">
<div class="flex items-center space-x-4">
<div class="float-animation">
<i class="fas fa-gamepad text-3xl text-white"></i>
</div>
<div>
<h1 class="text-2xl font-bold text-white neon-text">Game Sound Synthesizer</h1>
<p class="text-xs text-gray-200">20 Procedurally Generated Sound Effects</p>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" class="text-xs text-blue-300 hover:text-blue-200 transition-colors">Built with anycoder</a>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="flex items-center space-x-2">
<i class="fas fa-volume-up text-white"></i>
<input type="range" id="volumeControl" min="0" max="100" value="50"
class="w-32 h-2 bg-white/30 rounded-lg appearance-none cursor-pointer">
<span id="volumeValue" class="text-white text-sm">50%</span>
</div>
<button id="randomPlay" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-all">
<i class="fas fa-random mr-2"></i>Random Play
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 pb-32">
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4" id="soundGrid">
<!-- Sound buttons will be generated here -->
</div>
</main>
<!-- Visualizer -->
<div class="visualizer" id="visualizer">
<!-- Bars will be generated here -->
</div>
<script>
// Audio Context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
let masterGainNode = audioContext.createGain();
masterGainNode.connect(audioContext.destination);
masterGainNode.gain.value = 0.5;
// Sound definitions
const sounds = [
{ id: 1, name: 'Jump', icon: 'fa-arrow-up', category: 'action' },
{ id: 2, name: 'Explosion', icon: 'fa-burst', category: 'action' },
{ id: 3, name: 'Power Up', icon: 'fa-bolt', category: 'power' },
{ id: 4, name: 'Coin', icon: 'fa-coins', category: 'collect' },
{ id: 5, name: 'Laser', icon: 'fa-location-arrow', category: 'weapon' },
{ id: 6, name: 'Hit', icon: 'fa-hammer', category: 'impact' },
{ id: 7, name: 'Footstep', icon: 'fa-shoe-prints', category: 'movement' },
{ id: 8, name: 'Magic', icon: 'fa-hat-wizard', category: 'magic' },
{ id: 9, name: 'Alert', icon: 'fa-bell', category: 'ui' },
{ id: 10, name: 'Success', icon: 'fa-check-circle', category: 'ui' },
{ id: 11, name: 'Door Open', icon: 'fa-door-open', category: 'environment' },
{ id: 12, name: 'Heal', icon: 'fa-heart', category: 'power' },
{ id: 13, name: 'Shield', icon: 'fa-shield', category: 'power' },
{ id: 14, name: 'Teleport', icon: 'fa-portal-enter', category: 'magic' },
{ id: 15, name: 'Sword Swing', icon: 'fa-sword', category: 'weapon' },
{ id: 16, name: 'Water Splash', icon: 'fa-droplet', category: 'environment' },
{ id: 17, name: 'Fire', icon: 'fa-fire', category: 'element' },
{ id: 18, name: 'Thunder', icon: 'fa-cloud-bolt', category: 'element' },
{ id: 19, name: 'Level Up', icon: 'fa-trophy', category: 'ui' },
{ id: 20, name: 'Game Over', icon: 'fa-skull', category: 'ui' }
];
// Sound synthesis functions
const soundSynthesizers = {
1: () => { // Jump
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.frequency.setValueAtTime(400, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, audioContext.currentTime + 0.1);
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.2);
},
2: () => { // Explosion
const bufferSize = audioContext.sampleRate * 0.5;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
}
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
noise.buffer = buffer;
filter.type = 'lowpass';
filter.frequency.value = 1000;
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.5, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
noise.start();
},
3: () => { // Power Up
const osc1 = audioContext.createOscillator();
const osc2 = audioContext.createOscillator();
const gain = audioContext.createGain();
osc1.connect(gain);
osc2.connect(gain);
gain.connect(masterGainNode);
osc1.type = 'sine';
osc2.type = 'sine';
osc1.frequency.setValueAtTime(523.25, audioContext.currentTime);
osc2.frequency.setValueAtTime(659.25, audioContext.currentTime);
osc1.frequency.exponentialRampToValueAtTime(1046.5, audioContext.currentTime + 0.3);
osc2.frequency.exponentialRampToValueAtTime(1318.5, audioContext.currentTime + 0.3);
gain.gain.setValueAtTime(0, audioContext.currentTime);
gain.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.05);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
osc1.start(audioContext.currentTime);
osc2.start(audioContext.currentTime);
osc1.stop(audioContext.currentTime + 0.5);
osc2.stop(audioContext.currentTime + 0.5);
},
4: () => { // Coin
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.type = 'square';
osc.frequency.setValueAtTime(988, audioContext.currentTime);
osc.frequency.setValueAtTime(1319, audioContext.currentTime + 0.1);
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.15);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.15);
},
5: () => { // Laser
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
const filter = audioContext.createBiquadFilter();
osc.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(800, audioContext.currentTime);
filter.type = 'highpass';
filter.frequency.setValueAtTime(1000, audioContext.currentTime);
filter.Q.value = 10;
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.2);
},
6: () => { // Hit
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.type = 'triangle';
osc.frequency.setValueAtTime(150, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, audioContext.currentTime + 0.1);
gain.gain.setValueAtTime(0.4, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.1);
},
7: () => { // Footstep
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
const bufferSize = audioContext.sampleRate * 0.1;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 3);
}
noise.buffer = buffer;
filter.type = 'highpass';
filter.frequency.value = 2000;
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.2, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
noise.start();
},
8: () => { // Magic
const osc1 = audioContext.createOscillator();
const osc2 = audioContext.createOscillator();
const gain = audioContext.createGain();
const lfo = audioContext.createOscillator();
const lfoGain = audioContext.createGain();
lfo.frequency.value = 5;
lfoGain.gain.value = 100;
lfo.connect(lfoGain);
lfoGain.connect(osc1.frequency);
lfoGain.connect(osc2.frequency);
osc1.connect(gain);
osc2.connect(gain);
gain.connect(masterGainNode);
osc1.type = 'sine';
osc2.type = 'sine';
osc1.frequency.value = 440;
osc2.frequency.value = 554.37;
gain.gain.setValueAtTime(0, audioContext.currentTime);
gain.gain.linearRampToValueAtTime(0.2, audioContext.currentTime + 0.1);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 1);
osc1.start(audioContext.currentTime);
osc2.start(audioContext.currentTime);
lfo.start(audioContext.currentTime);
osc1.stop(audioContext.currentTime + 1);
osc2.stop(audioContext.currentTime + 1);
lfo.stop(audioContext.currentTime + 1);
},
9: () => { // Alert
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.type = 'square';
osc.frequency.setValueAtTime(880, audioContext.currentTime);
gain.gain.setValueAtTime(0.2, audioContext.currentTime);
gain.gain.setValueAtTime(0, audioContext.currentTime + 0.1);
gain.gain.setValueAtTime(0.2, audioContext.currentTime + 0.2);
gain.gain.setValueAtTime(0, audioContext.currentTime + 0.3);
gain.gain.setValueAtTime(0.2, audioContext.currentTime + 0.4);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.5);
},
10: () => { // Success
const notes = [523.25, 659.25, 783.99];
notes.forEach((freq, i) => {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.frequency.value = freq;
osc.type = 'sine';
gain.gain.setValueAtTime(0, audioContext.currentTime + i * 0.1);
gain.gain.linearRampToValueAtTime(0.2, audioContext.currentTime + i * 0.1 + 0.05);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + i * 0.1 + 0.3);
osc.start(audioContext.currentTime + i * 0.1);
osc.stop(audioContext.currentTime + i * 0.1 + 0.3);
});
},
11: () => { // Door Open
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.type = 'sine';
osc.frequency.setValueAtTime(200, audioContext.currentTime);
osc.frequency.linearRampToValueAtTime(400, audioContext.currentTime + 0.5);
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.5);
},
12: () => { // Heal
const osc1 = audioContext.createOscillator();
const osc2 = audioContext.createOscillator();
const gain = audioContext.createGain();
osc1.connect(gain);
osc2.connect(gain);
gain.connect(masterGainNode);
osc1.type = 'sine';
osc2.type = 'sine';
osc1.frequency.setValueAtTime(440, audioContext.currentTime);
osc2.frequency.setValueAtTime(554.37, audioContext.currentTime);
osc1.frequency.exponentialRampToValueAtTime(880, audioContext.currentTime + 0.5);
osc2.frequency.exponentialRampToValueAtTime(1108.73, audioContext.currentTime + 0.5);
gain.gain.setValueAtTime(0, audioContext.currentTime);
gain.gain.linearRampToValueAtTime(0.2, audioContext.currentTime + 0.1);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.8);
osc1.start(audioContext.currentTime);
osc2.start(audioContext.currentTime);
osc1.stop(audioContext.currentTime + 0.8);
osc2.stop(audioContext.currentTime + 0.8);
},
13: () => { // Shield
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
const filter = audioContext.createBiquadFilter();
osc.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
osc.type = 'sawtooth';
osc.frequency.value = 150;
filter.type = 'bandpass';
filter.frequency.value = 500;
filter.Q.value = 5;
gain.gain.setValueAtTime(0.2, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.3);
},
14: () => { // Teleport
const bufferSize = audioContext.sampleRate * 0.3;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.1));
}
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
noise.buffer = buffer;
filter.type = 'highpass';
filter.frequency.setValueAtTime(100, audioContext.currentTime);
filter.frequency.exponentialRampToValueAtTime(5000, audioContext.currentTime + 0.3);
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
noise.start();
},
15: () => { // Sword Swing
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
const bufferSize = audioContext.sampleRate * 0.2;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
}
noise.buffer = buffer;
filter.type = 'highpass';
filter.frequency.value = 3000;
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
noise.start();
},
16: () => { // Water Splash
const bufferSize = audioContext.sampleRate * 0.4;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 1.5);
}
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
noise.buffer = buffer;
filter.type = 'bandpass';
filter.frequency.value = 1000;
filter.Q.value = 2;
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4);
noise.start();
},
17: () => { // Fire
const bufferSize = audioContext.sampleRate * 0.5;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
}
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
noise.buffer = buffer;
filter.type = 'lowpass';
filter.frequency.value = 2000;
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.2, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
noise.start();
},
18: () => { // Thunder
const bufferSize = audioContext.sampleRate * 1;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.05));
}
const noise = audioContext.createBufferSource();
const filter = audioContext.createBiquadFilter();
const gain = audioContext.createGain();
noise.buffer = buffer;
filter.type = 'lowpass';
filter.frequency.value = 200;
noise.connect(filter);
filter.connect(gain);
gain.connect(masterGainNode);
gain.gain.setValueAtTime(0.5, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 1);
noise.start();
},
19: () => { // Level Up
const notes = [261.63, 329.63, 392, 523.25, 659.25];
notes.forEach((freq, i) => {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.frequency.value = freq;
osc.type = 'square';
gain.gain.setValueAtTime(0, audioContext.currentTime + i * 0.1);
gain.gain.linearRampToValueAtTime(0.15, audioContext.currentTime + i * 0.1 + 0.05);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + i * 0.1 + 0.2);
osc.start(audioContext.currentTime + i * 0.1);
osc.stop(audioContext.currentTime + i * 0.1 + 0.2);
});
},
20: () => { // Game Over
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(masterGainNode);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(440, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(110, audioContext.currentTime + 1);
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 1);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 1);
}
};
// Create visualizer bars
function createVisualizer() {
const visualizer = document.getElementById('visualizer');
for (let i = 0; i < 50; i++) {
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.height = '5px';
visualizer.appendChild(bar);
}
}
// Animate visualizer
function animateVisualizer() {
const bars = document.querySelectorAll('.bar');
bars.forEach(bar => {
const height = Math.random() * 80 + 10;
bar.style.height = height + 'px';
});
}
// Play sound function
function playSound(soundId) {
if (soundSynthesizers[soundId]) {
soundSynthesizers[soundId]();
animateVisualizer();
// Add visual feedback
const button = document.querySelector(`[data-sound-id="${soundId}"]`);
if (button) {
button.classList.add('playing', 'pulse');
setTimeout(() => {
button.classList.remove('playing', 'pulse');
}, 500);
}
}
}
// Create sound buttons
function createSoundButtons() {
const grid = document.getElementById('soundGrid');
sounds.forEach(sound => {
const button = document.createElement('button');
button.className = 'sound-button p-6 rounded-xl text-white flex flex-col items-center justify-center space-y-2 min-h-[120px]';
button.dataset.soundId = sound.id;
button.innerHTML = `
<i class="fas ${sound.icon} text-3xl"></i>
<span class="text-sm font-semibold">${sound.name}</span>
<span class="category-badge">${sound.category}</span>
`;
button.addEventListener('click', () => playSound(sound.id));
grid.appendChild(button);
});
}
// Volume control
document.getElementById('volumeControl').addEventListener('input', (e) => {
const volume = e.target.value / 100;
masterGainNode.gain.value = volume;
document.getElementById('volumeValue').textContent = e.target.value + '%';
});
// Random play
document.getElementById('randomPlay').addEventListener('click', () => {
const randomSound = sounds[Math.floor(Math.random() * sounds.length)];
playSound(randomSound.id);
});
// Initialize
createVisualizer();
createSoundButtons();
// Animate visualizer periodically
setInterval(() => {
if (Math.random() > 0.7) {
animateVisualizer();
}
}, 500);
</script>
</body>
</html>