Files
bootle/app/static/js/audio.js
T

163 lines
5.5 KiB
JavaScript
Executable File

// Web Audio API Synthesizer for magical escape sounds
class SoundFX {
constructor() {
this.ctx = null;
this.muted = localStorage.getItem('escape_sound_muted') === 'true';
}
init() {
if (!this.ctx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
this.ctx = new AudioContext();
}
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
toggleMute() {
this.muted = !this.muted;
localStorage.setItem('escape_sound_muted', this.muted);
return !this.muted;
}
isMuted() {
return this.muted;
}
// Whoosh of flying broomstick
playWhoosh() {
if (this.muted) return;
this.init();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const filter = this.ctx.createBiquadFilter();
osc.type = 'sawtooth';
filter.type = 'lowpass';
filter.frequency.setValueAtTime(300, this.ctx.currentTime);
filter.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.15);
filter.frequency.exponentialRampToValueAtTime(200, this.ctx.currentTime + 0.35);
gain.gain.setValueAtTime(0.15, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.35);
osc.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.36);
}
// Key caught / fluttering chime
playKeyChime() {
if (this.muted) return;
this.init();
const freqs = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6
freqs.forEach((freq, idx) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.value = freq;
const startTime = this.ctx.currentTime + idx * 0.05;
gain.gain.setValueAtTime(0.1, startTime);
gain.gain.exponentialRampToValueAtTime(0.001, startTime + 0.4);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(startTime);
osc.stop(startTime + 0.41);
});
}
// Clink of glass bottle
playBottleClink() {
if (this.muted) return;
this.init();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(1200, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.12);
gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.16);
}
// Success magical harmony
playMagicSuccess() {
if (this.muted) return;
this.init();
const chords = [440, 554.37, 659.25, 880, 1108.73]; // A Major triumph
chords.forEach((freq, i) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.06);
gain.gain.setValueAtTime(0.12, this.ctx.currentTime + i * 0.06);
gain.gain.exponentialRampToValueAtTime(0.0001, this.ctx.currentTime + i * 0.06 + 1.2);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(this.ctx.currentTime + i * 0.06);
osc.stop(this.ctx.currentTime + i * 0.06 + 1.25);
});
}
// Poison fizzle
playPoisonSizzle() {
if (this.muted) return;
this.init();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(180, this.ctx.currentTime);
osc.frequency.linearRampToValueAtTime(90, this.ctx.currentTime + 0.5);
gain.gain.setValueAtTime(0.25, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.5);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.51);
}
// Stone slide
playStoneSlide() {
if (this.muted) return;
this.init();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(80, this.ctx.currentTime);
gain.gain.setValueAtTime(0.08, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.21);
}
}
window.soundFX = new SoundFX();
function updateAudioButtonUI() {
const btn = document.getElementById('audioToggleBtn');
if (!btn) return;
const isMuted = window.soundFX.isMuted();
btn.innerHTML = isMuted ? '🔇 <span class="audio-lbl">Ton: Aus</span>' : '🔊 <span class="audio-lbl">Ton: An</span>';
}
function toggleSound() {
window.soundFX.toggleMute();
updateAudioButtonUI();
}
document.addEventListener('DOMContentLoaded', () => {
updateAudioButtonUI();
document.body.addEventListener('click', () => {
window.soundFX.init();
}, { once: true });
});