// WebAudio synth of the AIM "buddy in / buddy out" door sounds. The real AIM // samples are copyrighted, so these are originals built to evoke them: a wooden // door CREAK (swept band-pass noise with stick-slip amplitude wobble) plus a // low thunk/latch. Open = creak rising + soft settle; close = creak falling // into a heavier slam. All generated on the fly — no audio files. let ctx = null; let master = null; let noiseBuf = null; let enabled = true; export function setEnabled(on) { enabled = on; } export function isEnabled() { return enabled; } function ensure() { if (ctx) return ctx; const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return null; ctx = new AC(); master = ctx.createGain(); master.gain.value = 0.9; master.connect(ctx.destination); // 1s of white noise to feed the creak filter. const len = ctx.sampleRate; noiseBuf = ctx.createBuffer(1, len, ctx.sampleRate); const d = noiseBuf.getChannelData(0); for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1; return ctx; } // Must be called from a user gesture (the handle submit) to satisfy autoplay. export function unlock() { ensure(); if (ctx && ctx.state === 'suspended') ctx.resume(); } // A creak: band-pass-filtered noise whose center frequency sweeps f0->f1, with a // sawtooth LFO chewing the amplitude to give the dry stick-slip "rrrr" texture. function creak(t0, dur, f0, f1, peak, lfoHz) { const src = ctx.createBufferSource(); src.buffer = noiseBuf; src.loop = true; const bp = ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.Q.value = 7; bp.frequency.setValueAtTime(f0, t0); bp.frequency.exponentialRampToValueAtTime(f1, t0 + dur); const env = ctx.createGain(); env.gain.setValueAtTime(0.0001, t0); env.gain.exponentialRampToValueAtTime(peak, t0 + 0.035); env.gain.exponentialRampToValueAtTime(0.0001, t0 + dur); // stick-slip tremolo: LFO (-depth..+depth) summed onto a base gain. const trem = ctx.createGain(); trem.gain.value = 0.55; const lfo = ctx.createOscillator(); lfo.type = 'sawtooth'; lfo.frequency.setValueAtTime(lfoHz, t0); lfo.frequency.linearRampToValueAtTime(lfoHz * 0.6, t0 + dur); const lfoDepth = ctx.createGain(); lfoDepth.gain.value = 0.5; lfo.connect(lfoDepth).connect(trem.gain); src.connect(bp).connect(env).connect(trem).connect(master); src.start(t0); src.stop(t0 + dur + 0.05); lfo.start(t0); lfo.stop(t0 + dur + 0.05); } // A wooden thunk/latch: short pitched triangle blip that drops in pitch. function thunk(t0, freq, dur, peak) { const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.setValueAtTime(freq * 1.5, t0); o.frequency.exponentialRampToValueAtTime(freq, t0 + dur); const g = ctx.createGain(); g.gain.setValueAtTime(0.0001, t0); g.gain.exponentialRampToValueAtTime(peak, t0 + 0.008); g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur); o.connect(g).connect(master); o.start(t0); o.stop(t0 + dur + 0.02); } // Door swings OPEN: a latch tick, a creak that rises, a soft settle. export function doorOpen() { if (!enabled || !ensure()) return; const t = ctx.currentTime; thunk(t, 520, 0.04, 0.18); // latch lifts creak(t + 0.02, 0.34, 300, 760, 0.5, 28); // hinge creak, rising thunk(t + 0.34, 130, 0.13, 0.3); // door settles open } // Door clicks SHUT: a faster falling creak into a heavier low slam + latch. export function doorClose() { if (!enabled || !ensure()) return; const t = ctx.currentTime; creak(t, 0.24, 720, 300, 0.45, 40); // hinge creak, falling thunk(t + 0.22, 95, 0.17, 0.55); // the slam thunk(t + 0.33, 780, 0.04, 0.2); // latch clicks }