186 lines
6.0 KiB
JavaScript
186 lines
6.0 KiB
JavaScript
// Sound effects system using Web Audio API - no external files needed
|
|
const SoundManager = {
|
|
_ctx: null,
|
|
_enabled: true,
|
|
_volume: 0.3,
|
|
|
|
init() {
|
|
try {
|
|
this._ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
} catch (e) {
|
|
console.warn('Web Audio API not supported');
|
|
this._enabled = false;
|
|
}
|
|
const saved = localStorage.getItem('sound_enabled');
|
|
if (saved !== null) this._enabled = saved === 'true';
|
|
},
|
|
|
|
setEnabled(on) {
|
|
this._enabled = on;
|
|
localStorage.setItem('sound_enabled', on);
|
|
},
|
|
|
|
toggle() {
|
|
this.setEnabled(!this._enabled);
|
|
return this._enabled;
|
|
},
|
|
|
|
_play(freq, duration, type = 'sine', volume = 1) {
|
|
if (!this._enabled || !this._ctx) return;
|
|
if (this._ctx.state === 'suspended') this._ctx.resume();
|
|
|
|
const osc = this._ctx.createOscillator();
|
|
const gain = this._ctx.createGain();
|
|
osc.type = type;
|
|
osc.frequency.setValueAtTime(freq, this._ctx.currentTime);
|
|
gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime);
|
|
gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration);
|
|
osc.connect(gain);
|
|
gain.connect(this._ctx.destination);
|
|
osc.start();
|
|
osc.stop(this._ctx.currentTime + duration);
|
|
},
|
|
|
|
_noise(duration, volume = 1) {
|
|
if (!this._enabled || !this._ctx) return;
|
|
if (this._ctx.state === 'suspended') this._ctx.resume();
|
|
|
|
const bufferSize = this._ctx.sampleRate * duration;
|
|
const buffer = this._ctx.createBuffer(1, bufferSize, this._ctx.sampleRate);
|
|
const data = buffer.getChannelData(0);
|
|
for (let i = 0; i < bufferSize; i++) {
|
|
data[i] = Math.random() * 2 - 1;
|
|
}
|
|
const source = this._ctx.createBufferSource();
|
|
source.buffer = buffer;
|
|
const gain = this._ctx.createGain();
|
|
gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime);
|
|
gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration);
|
|
source.connect(gain);
|
|
gain.connect(this._ctx.destination);
|
|
source.start();
|
|
},
|
|
|
|
caseOpen() {
|
|
// Descending sweep with click
|
|
this._play(800, 0.1, 'square', 0.3);
|
|
setTimeout(() => this._play(400, 0.15, 'sawtooth', 0.2), 80);
|
|
setTimeout(() => this._play(200, 0.2, 'sine', 0.15), 180);
|
|
this._noise(0.05, 0.4);
|
|
},
|
|
|
|
caseRare() {
|
|
// Ascending bright chime for rare items
|
|
this._play(523, 0.15, 'sine', 0.3);
|
|
setTimeout(() => this._play(659, 0.15, 'sine', 0.3), 100);
|
|
setTimeout(() => this._play(784, 0.15, 'sine', 0.3), 200);
|
|
setTimeout(() => this._play(1047, 0.3, 'sine', 0.4), 300);
|
|
},
|
|
|
|
caseCovert() {
|
|
// Epic sound for covert/knife
|
|
this._play(392, 0.2, 'sawtooth', 0.3);
|
|
setTimeout(() => this._play(523, 0.2, 'sawtooth', 0.3), 150);
|
|
setTimeout(() => this._play(659, 0.2, 'sawtooth', 0.3), 300);
|
|
setTimeout(() => this._play(784, 0.4, 'sine', 0.5), 450);
|
|
setTimeout(() => this._play(1047, 0.6, 'sine', 0.6), 600);
|
|
},
|
|
|
|
slotSpin() {
|
|
// Ratcheting sound for slot reels
|
|
for (let i = 0; i < 8; i++) {
|
|
setTimeout(() => {
|
|
this._play(300 + Math.random() * 400, 0.05, 'square', 0.15);
|
|
}, i * 80);
|
|
}
|
|
},
|
|
|
|
slotMatch() {
|
|
// Win jingle
|
|
this._play(523, 0.1, 'sine', 0.3);
|
|
setTimeout(() => this._play(659, 0.1, 'sine', 0.3), 100);
|
|
setTimeout(() => this._play(784, 0.1, 'sine', 0.3), 200);
|
|
setTimeout(() => this._play(1047, 0.4, 'sine', 0.5), 300);
|
|
},
|
|
|
|
contractSubmit() {
|
|
// Mechanical sound
|
|
this._play(150, 0.3, 'sawtooth', 0.2);
|
|
setTimeout(() => this._play(200, 0.2, 'square', 0.2), 200);
|
|
setTimeout(() => this._play(250, 0.1, 'square', 0.15), 350);
|
|
},
|
|
|
|
contractResult() {
|
|
// Rising success
|
|
this._play(400, 0.15, 'sine', 0.3);
|
|
setTimeout(() => this._play(600, 0.15, 'sine', 0.3), 120);
|
|
setTimeout(() => this._play(800, 0.3, 'sine', 0.4), 240);
|
|
},
|
|
|
|
wheelSpin() {
|
|
// Ticking as wheel rotates
|
|
this._play(200, 0.03, 'square', 0.1);
|
|
},
|
|
|
|
wheelWin() {
|
|
// Celebration
|
|
this._play(523, 0.15, 'sine', 0.3);
|
|
setTimeout(() => this._play(659, 0.15, 'sine', 0.3), 120);
|
|
setTimeout(() => this._play(784, 0.2, 'sine', 0.35), 240);
|
|
setTimeout(() => this._play(1047, 0.5, 'sine', 0.5), 360);
|
|
},
|
|
|
|
wheelLose() {
|
|
// Sad trombone
|
|
this._play(400, 0.2, 'sine', 0.2);
|
|
setTimeout(() => this._play(350, 0.2, 'sine', 0.2), 150);
|
|
setTimeout(() => this._play(300, 0.3, 'sine', 0.15), 300);
|
|
},
|
|
|
|
achievement() {
|
|
// Achievement unlock
|
|
this._play(659, 0.1, 'sine', 0.3);
|
|
setTimeout(() => this._play(523, 0.1, 'sine', 0.3), 100);
|
|
setTimeout(() => this._play(784, 0.1, 'sine', 0.3), 200);
|
|
setTimeout(() => this._play(659, 0.1, 'sine', 0.3), 300);
|
|
setTimeout(() => this._play(1047, 0.4, 'sine', 0.5), 400);
|
|
},
|
|
|
|
crashBet() {
|
|
this._play(300, 0.1, 'square', 0.15);
|
|
},
|
|
|
|
crashTick() {
|
|
this._play(500 + Math.random() * 500, 0.03, 'sine', 0.08);
|
|
},
|
|
|
|
crashCrashed() {
|
|
// Explosion
|
|
this._noise(0.3, 0.6);
|
|
this._play(80, 0.5, 'sawtooth', 0.4);
|
|
setTimeout(() => this._play(50, 0.8, 'sine', 0.3), 100);
|
|
},
|
|
|
|
crashCashout() {
|
|
// Cashout success
|
|
this._play(600, 0.1, 'sine', 0.3);
|
|
setTimeout(() => this._play(800, 0.1, 'sine', 0.3), 80);
|
|
setTimeout(() => this._play(1000, 0.2, 'sine', 0.4), 160);
|
|
},
|
|
|
|
click() {
|
|
this._play(800, 0.03, 'sine', 0.1);
|
|
},
|
|
|
|
error() {
|
|
this._play(200, 0.15, 'square', 0.2);
|
|
},
|
|
|
|
balanceUpdate() {
|
|
this._play(1000, 0.05, 'sine', 0.1);
|
|
}
|
|
};
|
|
|
|
// Auto-init
|
|
document.addEventListener('DOMContentLoaded', () => SoundManager.init());
|