Files
doom-clone/js/audio.js
T
aharrison-fullerandClaude Opus 4.6 c55d7a7475 Add browser-based Doom clone with raycasting engine
Built a complete FPS game playable in the browser using vanilla JS and
HTML5 Canvas. Features a raycasting renderer with 8 textured wall types,
3 weapons (pistol, shotgun, plasma gun), 3 enemy types with AI
(imp, demon, baron), 3 levels of increasing difficulty, HUD with
minimap, pickups, doors, explosive barrels, and procedural audio.
Includes GitHub Actions workflow for GitHub Pages deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:27:43 +00:00

254 lines
7.1 KiB
JavaScript

// Procedural audio using Web Audio API
const Audio = {
ctx: null,
enabled: true,
init() {
try {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
} catch (e) {
this.enabled = false;
}
},
resume() {
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
},
// Pistol shot - short, sharp
playPistol() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const noise = this.createNoise(0.08);
osc.type = 'square';
osc.frequency.setValueAtTime(150, now);
osc.frequency.exponentialRampToValueAtTime(50, now + 0.08);
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.1);
osc.connect(gain);
gain.connect(ctx.destination);
noise.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.1);
},
// Shotgun blast - loud, wide
playShotgun() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const noise = this.createNoise(0.2);
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, now);
osc.frequency.exponentialRampToValueAtTime(30, now + 0.15);
gain.gain.setValueAtTime(0.4, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
osc.connect(gain);
gain.connect(ctx.destination);
noise.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.2);
},
// Plasma shot - sci-fi energy
playPlasma() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const osc2 = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(800, now);
osc.frequency.exponentialRampToValueAtTime(200, now + 0.15);
osc2.type = 'sine';
osc2.frequency.setValueAtTime(1200, now);
osc2.frequency.exponentialRampToValueAtTime(300, now + 0.12);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
osc.connect(gain);
osc2.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.15);
osc2.start(now);
osc2.stop(now + 0.15);
},
// Enemy hurt
playEnemyHurt() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, now);
osc.frequency.exponentialRampToValueAtTime(80, now + 0.15);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.15);
},
// Enemy death
playEnemyDeath() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(300, now);
osc.frequency.exponentialRampToValueAtTime(30, now + 0.4);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.4);
},
// Player hurt
playPlayerHurt() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(120, now);
osc.frequency.exponentialRampToValueAtTime(60, now + 0.2);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.2);
},
// Pickup sound
playPickup() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(400, now);
osc.frequency.setValueAtTime(600, now + 0.05);
osc.frequency.setValueAtTime(800, now + 0.1);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.15);
},
// Door open
playDoorOpen() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(80, now);
osc.frequency.linearRampToValueAtTime(120, now + 0.3);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.3);
},
// Explosion
playExplosion() {
if (!this.enabled || !this.ctx) return;
const ctx = this.ctx;
const now = ctx.currentTime;
const noise = this.createNoise(0.4);
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(60, now);
osc.frequency.exponentialRampToValueAtTime(20, now + 0.4);
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
osc.connect(gain);
gain.connect(ctx.destination);
noise.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.4);
},
createNoise(duration) {
const ctx = this.ctx;
const bufferSize = ctx.sampleRate * duration;
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) * Math.max(0, 1 - i / bufferSize);
}
const source = ctx.createBufferSource();
source.buffer = buffer;
const gain = ctx.createGain();
gain.gain.value = 0.15;
source.connect(gain);
source.start();
return gain;
}
};