feat(integrations): add retro doom web engine and agy proxy nginx config
This commit is contained in:
1 parent
b8db5a123d
commit
50eadb4b5e
15 files changed
+6397
No files matched your search
Executable
+728
@@ -0,0 +1,728 @@
|
||||
/**
|
||||
* DOOM 3D - Retro Sound Synthesizer & Music Sequencer (Web Audio API)
|
||||
* Generates authentic 90s DOOM-inspired sound effects and the iconic E1M1 metal riff.
|
||||
*/
|
||||
|
||||
class SoundSystem {
|
||||
constructor() {
|
||||
this.ctx = null;
|
||||
this.masterGain = null;
|
||||
this.sfxGain = null;
|
||||
this.musicGain = null;
|
||||
this.initialized = false;
|
||||
|
||||
this.sfxVolume = 0.8;
|
||||
this.musicVolume = 0.5;
|
||||
this.masterVolume = 1.0;
|
||||
|
||||
this.musicPlaying = false;
|
||||
this.musicStep = 0;
|
||||
this.musicTimer = null;
|
||||
this.musicBpm = 135;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.initialized) return;
|
||||
try {
|
||||
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
this.ctx = new AudioContext();
|
||||
|
||||
this.masterGain = this.ctx.createGain();
|
||||
this.masterGain.gain.value = this.masterVolume;
|
||||
this.masterGain.connect(this.ctx.destination);
|
||||
|
||||
this.sfxGain = this.ctx.createGain();
|
||||
this.sfxGain.gain.value = this.sfxVolume;
|
||||
this.sfxGain.connect(this.masterGain);
|
||||
|
||||
this.musicGain = this.ctx.createGain();
|
||||
this.musicGain.gain.value = this.musicVolume;
|
||||
this.musicGain.connect(this.masterGain);
|
||||
|
||||
this.initialized = true;
|
||||
this.startMusic();
|
||||
} catch (e) {
|
||||
console.warn('Web Audio API not supported or blocked:', e);
|
||||
}
|
||||
}
|
||||
|
||||
resume() {
|
||||
if (this.ctx && this.ctx.state === 'suspended') {
|
||||
this.ctx.resume();
|
||||
}
|
||||
if (!this.initialized) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
setMasterVolume(val) {
|
||||
this.masterVolume = Math.max(0, Math.min(1, val));
|
||||
if (this.masterGain) this.masterGain.gain.setValueAtTime(this.masterVolume, this.ctx.currentTime);
|
||||
}
|
||||
|
||||
setSfxVolume(val) {
|
||||
this.sfxVolume = Math.max(0, Math.min(1, val));
|
||||
if (this.sfxGain) this.sfxGain.gain.setValueAtTime(this.sfxVolume, this.ctx.currentTime);
|
||||
}
|
||||
|
||||
setMusicVolume(val) {
|
||||
this.musicVolume = Math.max(0, Math.min(1, val));
|
||||
if (this.musicGain) this.musicGain.gain.setValueAtTime(this.musicVolume, this.ctx.currentTime);
|
||||
}
|
||||
|
||||
// Helper: Create White Noise buffer
|
||||
createNoiseBuffer(duration = 1) {
|
||||
if (!this.ctx) return null;
|
||||
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;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Distortion curve for heavy metal tone
|
||||
makeDistortionCurve(amount = 50) {
|
||||
const k = typeof amount === 'number' ? amount : 50;
|
||||
const n_samples = 44100;
|
||||
const curve = new Float32Array(n_samples);
|
||||
const deg = Math.PI / 180;
|
||||
for (let i = 0; i < n_samples; ++i) {
|
||||
const x = (i * 2) / n_samples - 1;
|
||||
curve[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x));
|
||||
}
|
||||
return curve;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// WEAPON SOUND EFFECTS
|
||||
// ----------------------------------------------------
|
||||
|
||||
playFist() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
// Swoosh
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.setValueAtTime(120, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(40, t + 0.15);
|
||||
gain.gain.setValueAtTime(0.6, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.15);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.16);
|
||||
|
||||
// Impact punch
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.12);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.setValueAtTime(800, t);
|
||||
filter.frequency.exponentialRampToValueAtTime(100, t + 0.12);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(0.8, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.12);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playPistol() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
// Punch osc
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(320, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(45, t + 0.18);
|
||||
oGain.gain.setValueAtTime(0.7, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.18);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.19);
|
||||
|
||||
// Noise crack
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.2);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(1800, t);
|
||||
filter.frequency.exponentialRampToValueAtTime(300, t + 0.2);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(0.9, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.2);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playShotgun() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
// Low boom
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.setValueAtTime(200, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(30, t + 0.35);
|
||||
oGain.gain.setValueAtTime(1.0, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.35);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.36);
|
||||
|
||||
// Heavy blast noise
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.35);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.setValueAtTime(2800, t);
|
||||
filter.frequency.exponentialRampToValueAtTime(400, t + 0.35);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(1.2, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.35);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
|
||||
// Pump action slide (clack-clack) after 0.35s
|
||||
setTimeout(() => {
|
||||
if (!this.ctx) return;
|
||||
const t2 = this.ctx.currentTime;
|
||||
const p1 = this.ctx.createOscillator();
|
||||
const pGain = this.ctx.createGain();
|
||||
p1.type = 'square';
|
||||
p1.frequency.setValueAtTime(400, t2);
|
||||
p1.frequency.setValueAtTime(650, t2 + 0.06);
|
||||
pGain.gain.setValueAtTime(0.3, t2);
|
||||
pGain.gain.exponentialRampToValueAtTime(0.01, t2 + 0.15);
|
||||
p1.connect(pGain);
|
||||
pGain.connect(this.sfxGain);
|
||||
p1.start(t2);
|
||||
p1.stop(t2 + 0.16);
|
||||
}, 320);
|
||||
}
|
||||
|
||||
playChaingun() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(260 + Math.random() * 40, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(50, t + 0.12);
|
||||
oGain.gain.setValueAtTime(0.65, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.12);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.13);
|
||||
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.12);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(2200, t);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(0.7, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.12);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playRocketLaunch() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
// Swoosh tone
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(100, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(450, t + 0.25);
|
||||
osc.frequency.exponentialRampToValueAtTime(120, t + 0.5);
|
||||
oGain.gain.setValueAtTime(0.7, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.5);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.51);
|
||||
|
||||
// Rocket booster hiss
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.4);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(800, t);
|
||||
filter.frequency.linearRampToValueAtTime(1600, t + 0.4);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(0.8, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.4);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playExplosion(volumeMultiplier = 1.0) {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
// Sub bass boom
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.setValueAtTime(160, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(25, t + 0.7);
|
||||
oGain.gain.setValueAtTime(1.2 * volumeMultiplier, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.7);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.71);
|
||||
|
||||
// Crumbling explosion noise
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.8);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.setValueAtTime(1400, t);
|
||||
filter.frequency.exponentialRampToValueAtTime(120, t + 0.8);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(1.4 * volumeMultiplier, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.8);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playPlasma() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(950, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(180, t + 0.16);
|
||||
oGain.gain.setValueAtTime(0.7, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.16);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.17);
|
||||
|
||||
const osc2 = this.ctx.createOscillator();
|
||||
const oGain2 = this.ctx.createGain();
|
||||
osc2.type = 'sine';
|
||||
osc2.frequency.setValueAtTime(1400, t);
|
||||
osc2.frequency.exponentialRampToValueAtTime(300, t + 0.14);
|
||||
oGain2.gain.setValueAtTime(0.5, t);
|
||||
oGain2.gain.exponentialRampToValueAtTime(0.01, t + 0.14);
|
||||
osc2.connect(oGain2);
|
||||
oGain2.connect(this.sfxGain);
|
||||
osc2.start(t);
|
||||
osc2.stop(t + 0.15);
|
||||
}
|
||||
|
||||
playBFGCharge() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(100, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(1200, t + 0.8);
|
||||
gain.gain.setValueAtTime(0.1, t);
|
||||
gain.gain.linearRampToValueAtTime(0.9, t + 0.8);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.82);
|
||||
}
|
||||
|
||||
playBFGFire() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
// Big apocalyptic energy discharge
|
||||
const osc = this.ctx.createOscillator();
|
||||
const oGain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(800, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(60, t + 0.9);
|
||||
oGain.gain.setValueAtTime(1.3, t);
|
||||
oGain.gain.exponentialRampToValueAtTime(0.01, t + 0.9);
|
||||
osc.connect(oGain);
|
||||
oGain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.92);
|
||||
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(1.0);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(3000, t);
|
||||
filter.frequency.exponentialRampToValueAtTime(200, t + 1.0);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(1.5, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 1.0);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// MONSTER & WORLD SOUND EFFECTS
|
||||
// ----------------------------------------------------
|
||||
|
||||
playEnemyAlert(type = 'zombie') {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
|
||||
if (type === 'pinky' || type === 'caco') {
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(140, t);
|
||||
osc.frequency.linearRampToValueAtTime(220, t + 0.15);
|
||||
osc.frequency.exponentialRampToValueAtTime(60, t + 0.4);
|
||||
gain.gain.setValueAtTime(0.7, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.4);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.42);
|
||||
} else {
|
||||
// Zombie / Imp grunt
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(220, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(90, t + 0.28);
|
||||
gain.gain.setValueAtTime(0.6, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.28);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
playEnemyPain() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(180, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(70, t + 0.18);
|
||||
gain.gain.setValueAtTime(0.6, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.18);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.19);
|
||||
}
|
||||
|
||||
playEnemyDeath() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(130, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(30, t + 0.45);
|
||||
gain.gain.setValueAtTime(0.8, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.45);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.46);
|
||||
|
||||
// Gurgle
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.35);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(600, t);
|
||||
const nGain = this.ctx.createGain();
|
||||
nGain.gain.setValueAtTime(0.7, t);
|
||||
nGain.gain.exponentialRampToValueAtTime(0.01, t + 0.35);
|
||||
noise.connect(filter);
|
||||
filter.connect(nGain);
|
||||
nGain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playFireball() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.3);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(1200, t);
|
||||
filter.frequency.exponentialRampToValueAtTime(300, t + 0.3);
|
||||
const gain = this.ctx.createGain();
|
||||
gain.gain.setValueAtTime(0.8, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.3);
|
||||
noise.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
playItemPickup(type = 'ammo') {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sine';
|
||||
|
||||
if (type === 'health' || type === 'armor') {
|
||||
osc.frequency.setValueAtTime(440, t);
|
||||
osc.frequency.setValueAtTime(554.37, t + 0.06);
|
||||
osc.frequency.setValueAtTime(659.25, t + 0.12);
|
||||
gain.gain.setValueAtTime(0.5, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.25);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.26);
|
||||
} else if (type === 'weapon' || type === 'key') {
|
||||
// Fanfare chime
|
||||
osc.frequency.setValueAtTime(330, t);
|
||||
osc.frequency.setValueAtTime(440, t + 0.08);
|
||||
osc.frequency.setValueAtTime(660, t + 0.16);
|
||||
osc.frequency.setValueAtTime(880, t + 0.24);
|
||||
gain.gain.setValueAtTime(0.7, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.45);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.46);
|
||||
} else {
|
||||
// Ammo / click
|
||||
osc.frequency.setValueAtTime(600, t);
|
||||
osc.frequency.setValueAtTime(900, t + 0.05);
|
||||
gain.gain.setValueAtTime(0.4, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.12);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.13);
|
||||
}
|
||||
}
|
||||
|
||||
playDoor(open = true) {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
const startFreq = open ? 90 : 150;
|
||||
const endFreq = open ? 160 : 70;
|
||||
osc.frequency.setValueAtTime(startFreq, t);
|
||||
osc.frequency.linearRampToValueAtTime(endFreq, t + 0.6);
|
||||
gain.gain.setValueAtTime(0.4, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.6);
|
||||
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.setValueAtTime(450, t);
|
||||
|
||||
osc.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.62);
|
||||
}
|
||||
|
||||
playPlayerPain() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.setValueAtTime(200, t);
|
||||
osc.frequency.exponentialRampToValueAtTime(80, t + 0.22);
|
||||
gain.gain.setValueAtTime(0.9, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.22);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.23);
|
||||
}
|
||||
|
||||
playSlimeHurt() {
|
||||
if (!this.ctx) return;
|
||||
const t = this.ctx.currentTime;
|
||||
const noise = this.ctx.createBufferSource();
|
||||
noise.buffer = this.createNoiseBuffer(0.2);
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(3200, t);
|
||||
filter.Q.setValueAtTime(3, t);
|
||||
const gain = this.ctx.createGain();
|
||||
gain.gain.setValueAtTime(0.5, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.2);
|
||||
noise.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.sfxGain);
|
||||
noise.start(t);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// E1M1 RETRO SYNTHESIZER SOUNDTRACK
|
||||
// ----------------------------------------------------
|
||||
|
||||
startMusic() {
|
||||
if (this.musicPlaying) return;
|
||||
this.musicPlaying = true;
|
||||
this.musicStep = 0;
|
||||
|
||||
// E1M1 Notes in Hz (E Minor riff)
|
||||
const E1 = 41.20;
|
||||
const E2 = 82.41;
|
||||
const G2 = 98.00;
|
||||
const A2 = 110.00;
|
||||
const Bb2 = 116.54;
|
||||
const B2 = 123.47;
|
||||
const D3 = 146.83;
|
||||
const E3 = 164.81;
|
||||
|
||||
// 32-step bass riff pattern for classic E1M1
|
||||
// E E E3 E E D3 E E | C3 E E Bb2 E E B2 C3
|
||||
const riffNotes = [
|
||||
E2, E2, E3, E2, E2, D3, E2, E2,
|
||||
E3, E2, E2, D3, E2, E2, Bb2, B2,
|
||||
E2, E2, E3, E2, E2, D3, E2, E2,
|
||||
E3, E2, E2, G2, E2, A2, Bb2, B2
|
||||
];
|
||||
|
||||
const stepDuration = (60 / this.musicBpm) / 4; // 16th notes
|
||||
|
||||
const scheduleStep = () => {
|
||||
if (!this.musicPlaying || !this.ctx) return;
|
||||
const now = this.ctx.currentTime;
|
||||
|
||||
const note = riffNotes[this.musicStep % riffNotes.length];
|
||||
const isHeavyBeat = (this.musicStep % 4 === 0);
|
||||
const isSnareBeat = (this.musicStep % 8 === 4);
|
||||
|
||||
// Synth Distortion Bass/Guitar
|
||||
const osc = this.ctx.createOscillator();
|
||||
const dist = this.ctx.createWaveShaper();
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
const gain = this.ctx.createGain();
|
||||
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(note, now);
|
||||
dist.curve = this.makeDistortionCurve(30);
|
||||
dist.oversample = '4x';
|
||||
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.setValueAtTime(1400, now);
|
||||
filter.frequency.exponentialRampToValueAtTime(350, now + stepDuration * 0.9);
|
||||
|
||||
gain.gain.setValueAtTime(0.35, now);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, now + stepDuration * 0.95);
|
||||
|
||||
osc.connect(dist);
|
||||
dist.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.musicGain);
|
||||
|
||||
osc.start(now);
|
||||
osc.stop(now + stepDuration);
|
||||
|
||||
// Kick Drum on quarter notes
|
||||
if (isHeavyBeat) {
|
||||
const kick = this.ctx.createOscillator();
|
||||
const kGain = this.ctx.createGain();
|
||||
kick.type = 'sine';
|
||||
kick.frequency.setValueAtTime(140, now);
|
||||
kick.frequency.exponentialRampToValueAtTime(35, now + 0.12);
|
||||
kGain.gain.setValueAtTime(0.7, now);
|
||||
kGain.gain.exponentialRampToValueAtTime(0.01, now + 0.12);
|
||||
kick.connect(kGain);
|
||||
kGain.connect(this.musicGain);
|
||||
kick.start(now);
|
||||
kick.stop(now + 0.13);
|
||||
}
|
||||
|
||||
// Snare Drum on 2 and 4
|
||||
if (isSnareBeat) {
|
||||
const snare = this.ctx.createBufferSource();
|
||||
snare.buffer = this.createNoiseBuffer(0.15);
|
||||
const sFilter = this.ctx.createBiquadFilter();
|
||||
sFilter.type = 'highpass';
|
||||
sFilter.frequency.setValueAtTime(1000, now);
|
||||
const sGain = this.ctx.createGain();
|
||||
sGain.gain.setValueAtTime(0.45, now);
|
||||
sGain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
|
||||
snare.connect(sFilter);
|
||||
sFilter.connect(sGain);
|
||||
sGain.connect(this.musicGain);
|
||||
snare.start(now);
|
||||
}
|
||||
|
||||
// Closed Hi-hat every 8th note
|
||||
if (this.musicStep % 2 === 0) {
|
||||
const hat = this.ctx.createBufferSource();
|
||||
hat.buffer = this.createNoiseBuffer(0.04);
|
||||
const hFilter = this.ctx.createBiquadFilter();
|
||||
hFilter.type = 'highpass';
|
||||
hFilter.frequency.setValueAtTime(7000, now);
|
||||
const hGain = this.ctx.createGain();
|
||||
hGain.gain.setValueAtTime(0.15, now);
|
||||
hGain.gain.exponentialRampToValueAtTime(0.01, now + 0.04);
|
||||
hat.connect(hFilter);
|
||||
hFilter.connect(hGain);
|
||||
hGain.connect(this.musicGain);
|
||||
hat.start(now);
|
||||
}
|
||||
|
||||
this.musicStep++;
|
||||
this.musicTimer = setTimeout(scheduleStep, stepDuration * 1000);
|
||||
};
|
||||
|
||||
scheduleStep();
|
||||
}
|
||||
|
||||
stopMusic() {
|
||||
this.musicPlaying = false;
|
||||
if (this.musicTimer) {
|
||||
clearTimeout(this.musicTimer);
|
||||
this.musicTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
toggleMusic() {
|
||||
if (this.musicPlaying) {
|
||||
this.stopMusic();
|
||||
return false;
|
||||
} else {
|
||||
this.startMusic();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.SoundSystem = SoundSystem;
|
||||
Reference in new issue
Block a user