broadfield-dev/stack-overflow-game
0
1/**2 * Generate simple sound effects using Web Audio API3 */4export default class SoundGenerator {5 /**6 * Play a simple beep sound7 * @param {number} frequency - Frequency in Hz8 * @param {number} duration - Duration in seconds9 * @param {number} volume - Volume (0-1)10 */11 static getAudioContext() {12 if (!this.audioContext) {13 this.audioContext = new (window.AudioContext || window.webkitAudioContext)();14 }15 return this.audioContext;16 }17 18 static playBeep(frequency, duration, volume = 0.3) {19 try {20 const audioContext = this.getAudioContext();21 22 // Create oscillator23 const oscillator = audioContext.createOscillator();24 const gainNode = audioContext.createGain();25 26 oscillator.connect(gainNode);27 gainNode.connect(audioContext.destination);28 29 oscillator.frequency.value = frequency;30 oscillator.type = 'square'; // Retro square wave sound31 32 // Envelope33 gainNode.gain.setValueAtTime(volume, audioContext.currentTime);34 gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration);35 36 oscillator.start(audioContext.currentTime);37 oscillator.stop(audioContext.currentTime + duration);38 } catch (e) {39 console.warn('Audio not available:', e);40 }41 }42 43 static playMove() {44 this.playBeep(200, 0.05, 0.2);45 }46 47 static playRotate() {48 this.playBeep(300, 0.08, 0.25);49 }50 51 static playDrop() {52 this.playBeep(150, 0.15, 0.3);53 }54 55 static playLineClear(lineCount = 1) {56 // Noisy 8-bit explosion57 this.playNoisyExplosion();58 59 // Add bonus sounds for multiple lines60 if (lineCount === 2) {61 setTimeout(() => this.playBeep(600, 0.15, 0.25), 100);62 setTimeout(() => this.playBeep(800, 0.15, 0.25), 200);63 } else if (lineCount === 3) {64 setTimeout(() => this.playBeep(700, 0.12, 0.25), 100);65 setTimeout(() => this.playBeep(900, 0.12, 0.25), 180);66 setTimeout(() => this.playBeep(1100, 0.15, 0.25), 260);67 } else if (lineCount >= 4) {68 // Tetris! Extra exciting69 setTimeout(() => this.playBeep(800, 0.1, 0.3), 100);70 setTimeout(() => this.playBeep(1000, 0.1, 0.3), 180);71 setTimeout(() => this.playBeep(1200, 0.1, 0.3), 260);72 setTimeout(() => this.playBeep(1400, 0.2, 0.3), 340);73 }74 }75 76 static playNoisyExplosion() {77 try {78 const audioContext = this.getAudioContext();79 80 // Create white noise81 const bufferSize = audioContext.sampleRate * 0.5;82 const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);83 const noiseData = noiseBuffer.getChannelData(0);84 85 for (let i = 0; i < bufferSize; i++) {86 noiseData[i] = Math.random() * 2 - 1;87 }88 89 const noise = audioContext.createBufferSource();90 noise.buffer = noiseBuffer;91 92 // Filter the noise93 const filter = audioContext.createBiquadFilter();94 filter.type = 'lowpass';95 filter.frequency.setValueAtTime(2000, audioContext.currentTime);96 filter.frequency.exponentialRampToValueAtTime(80, audioContext.currentTime + 0.5);97 98 const gainNode = audioContext.createGain();99 gainNode.gain.setValueAtTime(0.7, audioContext.currentTime);100 gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);101 102 noise.connect(filter);103 filter.connect(gainNode);104 gainNode.connect(audioContext.destination);105 106 noise.start(audioContext.currentTime);107 noise.stop(audioContext.currentTime + 0.5);108 } catch (e) {109 console.warn('Audio not available:', e);110 }111 }112 113 static playSoftDrop() {114 this.playBeep(120, 0.04, 0.15);115 }116 117 static playTetris() {118 // Exciting sound for 4-line clear119 this.playBeep(500, 0.1, 0.35);120 setTimeout(() => this.playBeep(600, 0.1, 0.35), 60);121 setTimeout(() => this.playBeep(700, 0.1, 0.35), 120);122 setTimeout(() => this.playBeep(800, 0.2, 0.35), 180);123 }124 125 static playLevelUp() {126 // Ascending tone127 this.playBeep(400, 0.1, 0.3);128 setTimeout(() => this.playBeep(500, 0.1, 0.3), 80);129 setTimeout(() => this.playBeep(600, 0.1, 0.3), 160);130 setTimeout(() => this.playBeep(700, 0.2, 0.3), 240);131 }132 133 static playGameOver() {134 // Descending tone135 this.playBeep(400, 0.15, 0.3);136 setTimeout(() => this.playBeep(300, 0.15, 0.3), 120);137 setTimeout(() => this.playBeep(200, 0.15, 0.3), 240);138 setTimeout(() => this.playBeep(100, 0.3, 0.3), 360);139 }140 141 static playWoosh() {142 try {143 const audioContext = this.getAudioContext();144 145 // Create white noise for woosh effect146 const bufferSize = audioContext.sampleRate * 0.4;147 const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);148 const noiseData = noiseBuffer.getChannelData(0);149 150 for (let i = 0; i < bufferSize; i++) {151 noiseData[i] = Math.random() * 2 - 1;152 }153 154 const noise = audioContext.createBufferSource();155 noise.buffer = noiseBuffer;156 157 // High-pass filter for airy woosh sound158 const filter = audioContext.createBiquadFilter();159 filter.type = 'highpass';160 filter.frequency.setValueAtTime(800, audioContext.currentTime);161 filter.frequency.exponentialRampToValueAtTime(200, audioContext.currentTime + 0.4);162 163 const gainNode = audioContext.createGain();164 gainNode.gain.setValueAtTime(0.4, audioContext.currentTime);165 gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4);166 167 noise.connect(filter);168 filter.connect(gainNode);169 gainNode.connect(audioContext.destination);170 171 noise.start(audioContext.currentTime);172 noise.stop(audioContext.currentTime + 0.4);173 } catch (e) {174 console.warn('Audio not available:', e);175 }176 }177}178 179 180 