Files
NeonPlaformer/js/sound.js

213 lines
9.6 KiB
JavaScript

/* ═══════════════════════════════════════════════════════════════
AUDIO SYSTEM - Web Audio API Sound Effects
═══════════════════════════════════════════════════════════════ */
const AudioSystem = {
ctx: null,
masterVolume: 0.3,
sounds: {},
muted: false,
init() {
try {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.createSounds();
} catch (e) {
console.log('Audio not supported');
}
},
createSounds() {
// Jump sound - rising pitch
this.sounds.jump = () => {
if (!this.ctx || this.muted) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(200, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(this.masterVolume * 0.5, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
osc.start();
osc.stop(this.ctx.currentTime + 0.15);
};
// Double jump sound - higher pitch
this.sounds.doubleJump = () => {
if (!this.ctx || this.muted) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(400, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(this.masterVolume * 0.4, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.12);
osc.start();
osc.stop(this.ctx.currentTime + 0.12);
};
// Coin collect - bright chime
this.sounds.coin = () => {
if (!this.ctx || this.muted) return;
const frequencies = [880, 1100, 1320];
frequencies.forEach((freq, i) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.05);
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(this.masterVolume * 0.3, this.ctx.currentTime + i * 0.05);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2 + i * 0.05);
osc.start(this.ctx.currentTime + i * 0.05);
osc.stop(this.ctx.currentTime + 0.25 + i * 0.05);
});
};
// Damage/hurt - low thud
this.sounds.hurt = () => {
if (!this.ctx || this.muted) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, this.ctx.currentTime + 0.2);
gain.gain.setValueAtTime(this.masterVolume * 0.4, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2);
osc.start();
osc.stop(this.ctx.currentTime + 0.2);
};
// Power-up collected - magical chime
this.sounds.powerUp = () => {
if (!this.ctx || this.muted) return;
const frequencies = [523, 659, 784, 1047];
frequencies.forEach((freq, i) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.08);
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(this.masterVolume * 0.25, this.ctx.currentTime + i * 0.08);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3 + i * 0.08);
osc.start(this.ctx.currentTime + i * 0.08);
osc.stop(this.ctx.currentTime + 0.4 + i * 0.08);
});
};
// Checkpoint - celebratory sound
this.sounds.checkpoint = () => {
if (!this.ctx || this.muted) return;
const frequencies = [392, 523, 659, 784];
frequencies.forEach((freq, i) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.1);
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(this.masterVolume * 0.3, this.ctx.currentTime + i * 0.1);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3 + i * 0.1);
osc.start(this.ctx.currentTime + i * 0.1);
osc.stop(this.ctx.currentTime + 0.5 + i * 0.1);
});
};
// Level complete - victory fanfare
this.sounds.levelComplete = () => {
if (!this.ctx || this.muted) return;
const notes = [523, 587, 659, 698, 784, 880, 988, 1047];
notes.forEach((freq, i) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'square';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.12);
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(this.masterVolume * 0.15, this.ctx.currentTime + i * 0.12 + 0.02);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4 + i * 0.12);
osc.start(this.ctx.currentTime + i * 0.12);
osc.stop(this.ctx.currentTime + 0.5 + i * 0.12);
});
};
// Game over - sad descending
this.sounds.gameOver = () => {
if (!this.ctx || this.muted) return;
const frequencies = [440, 415, 392, 370, 349, 330, 311, 294];
frequencies.forEach((freq, i) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i * 0.15);
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(this.masterVolume * 0.2, this.ctx.currentTime + i * 0.15);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3 + i * 0.15);
osc.start(this.ctx.currentTime + i * 0.15);
osc.stop(this.ctx.currentTime + 0.4 + i * 0.15);
});
};
// Step/land sound
this.sounds.land = () => {
if (!this.ctx || this.muted) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(80, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(40, this.ctx.currentTime + 0.05);
gain.gain.setValueAtTime(this.masterVolume * 0.2, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.05);
osc.start();
osc.stop(this.ctx.currentTime + 0.05);
};
},
play(soundName) {
if (this.sounds[soundName]) {
// Resume audio context if suspended (browser autoplay policy)
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
this.sounds[soundName]();
}
},
toggleMute() {
this.muted = !this.muted;
return this.muted;
},
setVolume(vol) {
this.masterVolume = Math.max(0, Math.min(1, vol));
}
};
// Auto-initialize on first user interaction
document.addEventListener('click', () => {
if (!AudioSystem.ctx) {
AudioSystem.init();
}
}, { once: true });
document.addEventListener('keydown', () => {
if (!AudioSystem.ctx) {
AudioSystem.init();
}
}, { once: true });