Revert "Add looping chiptune BGM — cheerful overworld track and tense battle track, phrase-based seamless loop"

This reverts commit 48a35d381b.
This commit is contained in:
Justin Oros
2026-05-14 11:33:16 -07:00
parent 48a35d381b
commit 457b1c3280
+66 -130
View File
@@ -55,9 +55,6 @@ function clearSave() { try { localStorage.removeItem(SAVE_KEY); } catch(_){} }
/* ─── CHIPTUNE SOUND ENGINE ──────────────────────────────── */ /* ─── CHIPTUNE SOUND ENGINE ──────────────────────────────── */
const SFX = (() => { const SFX = (() => {
let ctx = null; let ctx = null;
let bgmNodes = []; /* currently playing BGM oscillators/gains */
let bgmTrack = null; /* 'overworld' | 'battle' | null */
let bgmTimer = null;
function getCtx() { function getCtx() {
if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)(); if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)();
@@ -65,11 +62,12 @@ const SFX = (() => {
return ctx; return ctx;
} }
/* Core: play a tone with envelope */
function tone(freq, type, vol, attack, sustain, release, when) { function tone(freq, type, vol, attack, sustain, release, when) {
const c = getCtx(); const c = getCtx();
const t = when ?? c.currentTime; const t = when ?? c.currentTime;
const osc = c.createOscillator(); const osc = c.createOscillator();
const gain = c.createGain(); const gain= c.createGain();
osc.connect(gain); osc.connect(gain);
gain.connect(c.destination); gain.connect(c.destination);
osc.type = type; osc.type = type;
@@ -79,158 +77,98 @@ const SFX = (() => {
gain.gain.setValueAtTime(vol, t + attack + sustain); gain.gain.setValueAtTime(vol, t + attack + sustain);
gain.gain.exponentialRampToValueAtTime(0.0001, t + attack + sustain + release); gain.gain.exponentialRampToValueAtTime(0.0001, t + attack + sustain + release);
osc.start(t); osc.start(t);
osc.stop(t + attack + sustain + release + 0.02); osc.stop(t + attack + sustain + release + 0.01);
return { osc, gain };
} }
function seq(notes, type='square', vol=0.18, startAt) { /* Sequence of notes: [{f, d}...] */
function seq(notes, type='square', vol=0.18) {
const c = getCtx(); const c = getCtx();
let t = startAt ?? (c.currentTime + 0.02); let t = c.currentTime + 0.02;
notes.forEach(n => { notes.forEach(n => {
if (n.f) tone(n.f, type, vol, 0.01, n.d * 0.55, n.d * 0.35, t); if (n.f) tone(n.f, type, vol, 0.01, n.d * 0.6, n.d * 0.4, t);
t += n.d; t += n.d;
}); });
return t; /* returns time when sequence ends */
} }
/* ── BGM TRACKS ───────────────────────────────────────────
Each track is an array of "phrases" (arrays of notes).
The engine plays them in sequence, then loops.
Two channels: melody (square) + bass (triangle).
─────────────────────────────────────────────────────────── */
/* Pallet Town-inspired overworld — cheerful, major key */
const OVERWORLD = {
melody: [
/* phrase A */
[{f:392,d:.15},{f:440,d:.15},{f:494,d:.15},{f:523,d:.2},
{f:494,d:.12},{f:440,d:.12},{f:392,d:.22},
{f:330,d:.15},{f:370,d:.15},{f:392,d:.3}],
/* phrase B */
[{f:523,d:.15},{f:494,d:.1},{f:440,d:.1},{f:494,d:.15},{f:523,d:.2},
{f:587,d:.15},{f:659,d:.15},{f:587,d:.12},{f:523,d:.28}],
/* phrase C — bridge */
[{f:659,d:.12},{f:587,d:.12},{f:523,d:.12},{f:494,d:.12},{f:440,d:.2},
{f:392,d:.12},{f:440,d:.12},{f:494,d:.12},{f:523,d:.24}],
/* phrase A repeat */
[{f:392,d:.15},{f:440,d:.15},{f:494,d:.15},{f:523,d:.2},
{f:494,d:.12},{f:440,d:.12},{f:392,d:.22},
{f:330,d:.15},{f:392,d:.15},{f:523,d:.35}],
],
bass: [
[{f:131,d:.3},{f:0,d:.1},{f:147,d:.3},{f:0,d:.1},{f:131,d:.3},{f:0,d:.1},{f:110,d:.3},{f:0,d:.1}],
[{f:131,d:.3},{f:0,d:.1},{f:147,d:.3},{f:0,d:.1},{f:165,d:.3},{f:0,d:.1},{f:147,d:.3},{f:0,d:.1}],
[{f:165,d:.3},{f:0,d:.1},{f:147,d:.3},{f:0,d:.1},{f:131,d:.3},{f:0,d:.1},{f:110,d:.4}],
[{f:131,d:.3},{f:0,d:.1},{f:147,d:.3},{f:0,d:.1},{f:131,d:.3},{f:0,d:.1},{f:98,d:.5}],
],
};
/* Battle theme — tense, minor key, faster */
const BATTLE = {
melody: [
/* phrase A — urgent opening */
[{f:494,d:.1},{f:523,d:.1},{f:494,d:.1},{f:0,d:.05},
{f:440,d:.1},{f:466,d:.1},{f:440,d:.1},{f:0,d:.05},
{f:392,d:.1},{f:415,d:.1},{f:392,d:.15},{f:0,d:.08},
{f:370,d:.1},{f:392,d:.22}],
/* phrase B */
[{f:523,d:.1},{f:587,d:.1},{f:523,d:.1},{f:494,d:.1},{f:0,d:.05},
{f:466,d:.1},{f:494,d:.1},{f:466,d:.1},{f:440,d:.15},
{f:0,d:.08},{f:392,d:.1},{f:440,d:.25}],
/* phrase C — climax */
[{f:659,d:.1},{f:587,d:.08},{f:523,d:.08},{f:494,d:.08},{f:466,d:.1},
{f:440,d:.08},{f:415,d:.08},{f:392,d:.08},{f:370,d:.1},
{f:349,d:.08},{f:330,d:.25}],
/* phrase D — resolve back */
[{f:392,d:.1},{f:440,d:.1},{f:494,d:.1},{f:523,d:.1},{f:494,d:.1},
{f:440,d:.1},{f:415,d:.1},{f:392,d:.28}],
],
bass: [
[{f:147,d:.2},{f:0,d:.05},{f:147,d:.2},{f:0,d:.05},{f:139,d:.2},{f:0,d:.05},{f:131,d:.25}],
[{f:147,d:.2},{f:0,d:.05},{f:155,d:.2},{f:0,d:.05},{f:147,d:.2},{f:0,d:.05},{f:131,d:.25}],
[{f:165,d:.2},{f:0,d:.05},{f:155,d:.2},{f:0,d:.05},{f:147,d:.2},{f:0,d:.05},{f:131,d:.3}],
[{f:131,d:.2},{f:0,d:.05},{f:139,d:.2},{f:0,d:.05},{f:147,d:.2},{f:0,d:.05},{f:131,d:.3}],
],
};
function stopBGM() {
if (bgmTimer) { clearTimeout(bgmTimer); bgmTimer = null; }
bgmTrack = null;
}
function playBGMTrack(track, name, phraseIdx) {
if (bgmTrack !== name) return; /* stopped or switched */
const c = getCtx();
const now = c.currentTime;
const melody = track.melody[phraseIdx % track.melody.length];
const bass = track.bass[phraseIdx % track.bass.length];
const melVol = name === 'battle' ? 0.13 : 0.11;
const basVol = name === 'battle' ? 0.07 : 0.06;
let end = now + 0.02;
/* play melody and bass simultaneously, get the longer duration */
let mEnd = now + 0.02;
melody.forEach(n => { if(n.f) tone(n.f,'square',melVol,0.01,n.d*.55,n.d*.3,mEnd); mEnd+=n.d; });
let bEnd = now + 0.02;
bass.forEach(n => { if(n.f) tone(n.f,'triangle',basVol,0.01,n.d*.7,n.d*.2,bEnd); bEnd+=n.d; });
end = Math.max(mEnd, bEnd);
const delay = Math.max(0, (end - c.currentTime) * 1000 - 30);
bgmTimer = setTimeout(() => {
playBGMTrack(track, name, phraseIdx + 1);
}, delay);
}
function startBGM(name) {
if (bgmTrack === name) return;
stopBGM();
bgmTrack = name;
const track = name === 'battle' ? BATTLE : OVERWORLD;
playBGMTrack(track, name, 0);
}
/* ── SFX ──────────────────────────────────────────────── */
return { return {
stopBGM, /* Boot jingle — classic rising fanfare */
overworldBGM() { startBGM('overworld'); },
battleBGM() { startBGM('battle'); },
boot() { boot() {
seq([{f:262,d:.1},{f:330,d:.1},{f:392,d:.1},{f:523,d:.25}],'square',0.2); seq([
{f:262,d:.1},{f:330,d:.1},{f:392,d:.1},{f:523,d:.25}
], 'square', 0.2);
}, },
/* Overworld bgm loop — simple cheerful melody */
overworldStart() {
seq([
{f:392,d:.12},{f:440,d:.12},{f:494,d:.12},{f:523,d:.18},
{f:494,d:.1}, {f:440,d:.1}, {f:392,d:.18},
{f:330,d:.12},{f:392,d:.12},{f:440,d:.12},{f:392,d:.24},
], 'square', 0.12);
},
/* Footstep click — tiny blip each walk tick */
step() {
tone(180, 'square', 0.04, 0.005, 0.02, 0.03);
},
/* NPC encounter — ascending two-note ding */
encounter() { encounter() {
stopBGM(); seq([{f:523,d:.1},{f:659,d:.18}], 'square', 0.22);
seq([{f:523,d:.1},{f:659,d:.18}],'square',0.22);
}, },
/* Typewriter blip per character */
type() { type() {
tone(880+Math.random()*200,'square',0.03,0.005,0.01,0.02); tone(880 + Math.random()*200, 'square', 0.03, 0.005, 0.01, 0.02);
}, },
/* Select / menu move */
select() { select() {
tone(440,'square',0.12,0.005,0.03,0.04); tone(440, 'square', 0.12, 0.005, 0.03, 0.04);
}, },
/* Correct answer — happy ascending chord */
correct() { correct() {
seq([{f:523,d:.08},{f:659,d:.08},{f:784,d:.08},{f:1047,d:.2}],'square',0.18); seq([
{f:523,d:.08},{f:659,d:.08},{f:784,d:.08},{f:1047,d:.2}
], 'square', 0.18);
}, },
/* Streak bonus — extra flourish */
streak() { streak() {
seq([{f:523,d:.06},{f:659,d:.06},{f:784,d:.06},{f:1047,d:.06},{f:1319,d:.2}],'square',0.18); seq([
{f:523,d:.06},{f:659,d:.06},{f:784,d:.06},
{f:1047,d:.06},{f:1319,d:.2}
], 'square', 0.18);
}, },
/* Wrong answer — descending buzz */
wrong() { wrong() {
seq([{f:330,d:.1},{f:277,d:.1},{f:233,d:.18}],'sawtooth',0.15); seq([
{f:330,d:.1},{f:277,d:.1},{f:233,d:.18}
], 'sawtooth', 0.15);
}, },
/* Badge / level up fanfare */
levelUp() { levelUp() {
stopBGM(); seq([
seq([{f:523,d:.1},{f:659,d:.1},{f:784,d:.1},{f:659,d:.1},{f:784,d:.1},{f:1047,d:.3}],'square',0.2); {f:523,d:.1},{f:659,d:.1},{f:784,d:.1},{f:659,d:.1},
{f:784,d:.1},{f:1047,d:.3}
], 'square', 0.2);
}, },
/* Champion / completion — full fanfare */
complete() { complete() {
stopBGM(); seq([
seq([{f:523,d:.1},{f:659,d:.1},{f:784,d:.1},{f:1047,d:.1}, {f:523,d:.1},{f:659,d:.1},{f:784,d:.1},{f:1047,d:.1},
{f:784,d:.08},{f:880,d:.08},{f:1047,d:.08},{f:1319,d:.4}],'square',0.2); {f:784,d:.08},{f:880,d:.08},{f:1047,d:.08},{f:1319,d:.4}
], 'square', 0.2);
}, },
/* Menu confirm (name entry OK) */
confirm() { confirm() {
seq([{f:523,d:.08},{f:784,d:.15}],'square',0.18); seq([{f:523,d:.08},{f:784,d:.15}], 'square', 0.18);
}, },
addChar(ch) { /* kept for compat */ },
}; };
})(); })();
@@ -802,7 +740,7 @@ class Game {
this._placeMapSprites(); this._placeMapSprites();
this._updateDirArrow(false); this._updateDirArrow(false);
SFX.overworldBGM(); SFX.overworldStart();
saveGame(this.state); saveGame(this.state);
this.flashSaveDot(); this.flashSaveDot();
@@ -836,7 +774,6 @@ class Game {
this.show('battle'); this.show('battle');
this._showController(true); this._showController(true);
SFX.battleBGM();
this.state.answering=false; this.state.answering=false;
this.state.cursor=0; this.state.cursor=0;
@@ -901,7 +838,6 @@ class Game {
/* ── RESULT ───────────────────────────────────────────── */ /* ── RESULT ───────────────────────────────────────────── */
showResult(correct,q) { showResult(correct,q) {
this.show('result'); this.show('result');
SFX.stopBGM();
this._showController(false); this._showController(false);
document.getElementById('result-icon').textContent = correct?'✓':'✗'; document.getElementById('result-icon').textContent = correct?'✓':'✗';