77 lines
2.8 KiB
JavaScript
77 lines
2.8 KiB
JavaScript
'use strict';
|
|
|
|
let audioCtx = null;
|
|
|
|
function getAudio() {
|
|
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
return audioCtx;
|
|
}
|
|
|
|
function beep(freq, freq2, dur, type = 'sine', vol = 0.3) {
|
|
try {
|
|
const ac = getAudio();
|
|
const o = ac.createOscillator();
|
|
const g = ac.createGain();
|
|
o.type = type;
|
|
o.connect(g); g.connect(ac.destination);
|
|
o.frequency.setValueAtTime(freq, ac.currentTime);
|
|
if (freq2 && freq2 !== freq) o.frequency.exponentialRampToValueAtTime(freq2, ac.currentTime + dur);
|
|
g.gain.setValueAtTime(vol, ac.currentTime);
|
|
g.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + dur);
|
|
o.start(ac.currentTime);
|
|
o.stop(ac.currentTime + dur + 0.01);
|
|
} catch(e) {}
|
|
}
|
|
|
|
const SFX = {
|
|
jump: () => beep(280, 560, 0.18, 'sine', 0.25),
|
|
attack: () => beep(140, 70, 0.12, 'sawtooth', 0.30),
|
|
stomp: () => { beep(200, 80, 0.1, 'square', 0.35); beep(400, 400, 0.08, 'sine', 0.2); },
|
|
hurt: () => beep(180, 90, 0.28, 'square', 0.40),
|
|
die: () => beep(380, 50, 0.80, 'square', 0.40),
|
|
collect: () => beep(550, 880, 0.10, 'sine', 0.20),
|
|
heart: () => { beep(660, 660, 0.06, 'sine', 0.2); setTimeout(() => beep(880, 880, 0.12, 'sine', 0.2), 70); },
|
|
killEnemy: () => beep(260, 100, 0.25, 'sawtooth', 0.28),
|
|
combo: (n) => beep(440 + n * 40, 550 + n * 40, 0.08, 'sine', 0.18),
|
|
levelUp: () => {
|
|
[523, 659, 784, 1047].forEach((f, i) =>
|
|
setTimeout(() => beep(f, f, i === 3 ? 0.25 : 0.08, 'sine', 0.28), i * 100)
|
|
);
|
|
},
|
|
};
|
|
|
|
// ─── Background Music ─────────────────────────────────────────────────────────
|
|
let _musicTimer = null;
|
|
let _musicStep = 0;
|
|
|
|
const MUSIC = {
|
|
menu: { bpm: 112, notes: [392,0,523,0,659,523,392,330, 392,0,523,659,784,659,523,0] },
|
|
grass: { bpm: 132, notes: [523,0,659,0,784,659,523,0, 440,0,523,0,659,523,440,0] },
|
|
cave: { bpm: 96, notes: [294,0,330,0,262,0,294,0, 220,0,247,0,262,0,220,0] },
|
|
ice: { bpm: 108, notes: [440,0,494,523,587,0,523,494, 440,0,392,0,440,494,523,0] },
|
|
};
|
|
|
|
function startMusic(theme) {
|
|
stopMusic();
|
|
try { getAudio(); } catch(e) { return; }
|
|
const pat = MUSIC[theme] || MUSIC.menu;
|
|
const beatMs = (60000 / pat.bpm) / 2; // eighth-note duration
|
|
_musicStep = 0;
|
|
|
|
const tick = () => {
|
|
const freq = pat.notes[_musicStep % pat.notes.length];
|
|
if (freq > 0) {
|
|
beep(freq / 2, freq / 2, beatMs / 1000 * 0.75, 'square', 0.04);
|
|
if (_musicStep % 2 === 0) beep(freq, freq, beatMs / 1000 * 0.55, 'triangle', 0.05);
|
|
}
|
|
_musicStep++;
|
|
_musicTimer = setTimeout(tick, beatMs);
|
|
};
|
|
_musicTimer = setTimeout(tick, 0);
|
|
}
|
|
|
|
function stopMusic() {
|
|
if (_musicTimer) { clearTimeout(_musicTimer); _musicTimer = null; }
|
|
_musicStep = 0;
|
|
}
|