re4stonepuzzle / src /audio.js
Scribbler310
Initial commit: Hexagon Stone Pedestal Puzzle RE4
11ecc10
Raw
History Blame Contribute Delete
3.8 kB
class SoundEffects {
constructor() {
this.ctx = null;
}
init() {
if (!this.ctx) {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
}
if (this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
playClick() {
try {
this.init();
const now = this.ctx.currentTime;
// Stone grinding sub-sound (low frequency friction noise)
const osc1 = this.ctx.createOscillator();
const gain1 = this.ctx.createGain();
osc1.type = 'sawtooth';
osc1.frequency.setValueAtTime(80, now);
osc1.frequency.exponentialRampToValueAtTime(30, now + 0.35);
// Filter to make it sound muffled and stony
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(250, now);
filter.frequency.exponentialRampToValueAtTime(60, now + 0.35);
gain1.gain.setValueAtTime(0.3, now);
gain1.gain.exponentialRampToValueAtTime(0.01, now + 0.35);
// High metal click
const osc2 = this.ctx.createOscillator();
const gain2 = this.ctx.createGain();
osc2.type = 'triangle';
osc2.frequency.setValueAtTime(1200, now);
osc2.frequency.exponentialRampToValueAtTime(600, now + 0.08);
gain2.gain.setValueAtTime(0.15, now);
gain2.gain.exponentialRampToValueAtTime(0.01, now + 0.08);
// Connections
osc1.connect(filter);
filter.connect(gain1);
gain1.connect(this.ctx.destination);
osc2.connect(gain2);
gain2.connect(this.ctx.destination);
osc1.start(now);
osc1.stop(now + 0.4);
osc2.start(now);
osc2.stop(now + 0.1);
} catch (e) {
console.warn("Audio context failed to play", e);
}
}
playReset() {
try {
this.init();
const now = this.ctx.currentTime;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(440, now);
osc.frequency.linearRampToValueAtTime(220, now + 0.3);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.3);
} catch (e) {
console.warn("Audio context failed to play", e);
}
}
playVictory() {
try {
this.init();
const now = this.ctx.currentTime;
const notes = [261.63, 329.63, 392.00, 523.25, 659.25, 783.99, 1046.50]; // C major arpeggio
notes.forEach((freq, idx) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const delay = idx * 0.12;
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now + delay);
gain.gain.setValueAtTime(0, now + delay);
gain.gain.linearRampToValueAtTime(0.15, now + delay + 0.05);
gain.gain.exponentialRampToValueAtTime(0.001, now + delay + 1.2);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now + delay);
osc.stop(now + delay + 1.3);
});
// Add a warm background pad/hum
const pad = this.ctx.createOscillator();
const padGain = this.ctx.createGain();
pad.type = 'triangle';
pad.frequency.setValueAtTime(130.81, now); // Low C
padGain.gain.setValueAtTime(0, now);
padGain.gain.linearRampToValueAtTime(0.2, now + 0.5);
padGain.gain.exponentialRampToValueAtTime(0.001, now + 2.0);
pad.connect(padGain);
padGain.connect(this.ctx.destination);
pad.start(now);
pad.stop(now + 2.0);
} catch (e) {
console.warn("Audio context failed to play", e);
}
}
}
export const sounds = new SoundEffects();