fokemal / client /src /lib /game-audio.ts
automindy's picture
Upload 62 files
7fbafd3 verified
Raw
History Blame Contribute Delete
5.47 kB
class GameAudio {
private ctx: AudioContext | null = null;
private ambientOscillators: { osc: OscillatorNode; gain: GainNode }[] = [];
private isAmbientPlaying = false;
private initCtx() {
if (!this.ctx) {
const AudioCtx = window.AudioContext || (window as any).webkitAudioContext;
if (AudioCtx) {
this.ctx = new AudioCtx();
}
}
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
return this.ctx;
}
playPop() {
const ctx = this.initCtx();
if (!ctx) return;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
const now = ctx.currentTime;
osc.type = 'sine';
// Quick pitch decay from 800Hz to 150Hz
osc.frequency.setValueAtTime(800, now);
osc.frequency.exponentialRampToValueAtTime(150, now + 0.08);
// Fast volume envelope
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
osc.start(now);
osc.stop(now + 0.08);
}
playExplosion() {
const ctx = this.initCtx();
if (!ctx) return;
const now = ctx.currentTime;
// Create a noise buffer (white noise)
const bufferSize = ctx.sampleRate * 0.3; // 0.3 seconds
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = ctx.createBufferSource();
noise.buffer = buffer;
// Filter to make it sound like a rumble/thud explosion
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(1000, now);
filter.frequency.exponentialRampToValueAtTime(10, now + 0.3);
const gain = ctx.createGain();
gain.gain.setValueAtTime(0.5, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
noise.connect(filter);
filter.connect(gain);
gain.connect(ctx.destination);
noise.start(now);
noise.stop(now + 0.3);
// Add a low sine sub-bass drop for punch
const sub = ctx.createOscillator();
const subGain = ctx.createGain();
sub.type = 'sine';
sub.frequency.setValueAtTime(120, now);
sub.frequency.linearRampToValueAtTime(40, now + 0.2);
subGain.gain.setValueAtTime(0.6, now);
subGain.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
sub.connect(subGain);
subGain.connect(ctx.destination);
sub.start(now);
sub.stop(now + 0.25);
}
playLaser() {
const ctx = this.initCtx();
if (!ctx) return;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
const now = ctx.currentTime;
osc.type = 'sawtooth';
// Sweep frequency up rapidly
osc.frequency.setValueAtTime(200, now);
osc.frequency.exponentialRampToValueAtTime(1500, now + 0.12);
// Fast volume envelope
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
osc.start(now);
osc.stop(now + 0.12);
}
playChime() {
const ctx = this.initCtx();
if (!ctx) return;
const now = ctx.currentTime;
const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6 (Arpeggio)
notes.forEach((freq, index) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const delay = index * 0.06;
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, now + delay);
gain.gain.setValueAtTime(0, now + delay);
gain.gain.linearRampToValueAtTime(0.25, now + delay + 0.02);
gain.gain.exponentialRampToValueAtTime(0.001, now + delay + 0.5);
osc.start(now + delay);
osc.stop(now + delay + 0.6);
});
}
startAmbient() {
const ctx = this.initCtx();
if (!ctx || this.isAmbientPlaying) return;
this.isAmbientPlaying = true;
const now = ctx.currentTime;
// A beautiful major pentatonic warm chord: C3, G3, C4, E4
const freqs = [130.81, 196.00, 261.63, 329.63];
this.ambientOscillators = freqs.map((freq) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = freq;
// Soft volume level
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(0.025, now + 2.0); // Slow fade-in
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
return { osc, gain };
});
}
stopAmbient() {
if (!this.isAmbientPlaying) return;
this.isAmbientPlaying = false;
if (!this.ctx) return;
const now = this.ctx.currentTime;
this.ambientOscillators.forEach(({ osc, gain }) => {
try {
gain.gain.setValueAtTime(gain.gain.value, now);
gain.gain.linearRampToValueAtTime(0, now + 1.5); // Slow fade-out
setTimeout(() => {
try {
osc.stop();
} catch {}
}, 1600);
} catch {}
});
this.ambientOscillators = [];
}
toggleAmbient() {
if (this.isAmbientPlaying) {
this.stopAmbient();
return false;
} else {
this.startAmbient();
return true;
}
}
isPlayingAmbient() {
return this.isAmbientPlaying;
}
}
export const gameAudio = new GameAudio();