Fix background music: bright lo-fi pad instead of horror ambient
CI / build-and-push (push) Successful in 46s

Moved chords up an octave (C4-E5 range), switched to triangle waves,
faster LFO rates, all major voicings, and higher filter cutoff. The
previous version with sub-bass sine drones and ultra-slow modulation
was genuinely terrifying.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 20:20:43 -04:00
parent d609934b73
commit f3e6a2e692
+41 -29
View File
@@ -1,16 +1,20 @@
// Bright, lo-fi ambient pad in C major — pleasant background for a tech management game.
// Higher register, all major/bright voicings, gentle triangle waves, moderate LFO rates.
const CHORDS: number[][] = [ const CHORDS: number[][] = [
[130.81, 164.81, 196.00, 246.94], // Cmaj7: C3, E3, G3, B3 [261.63, 329.63, 392.00, 493.88], // Cmaj7: C4, E4, G4, B4
[110.00, 130.81, 164.81, 207.65], // Am7: A2, C3, E3, Ab3 [349.23, 440.00, 523.25, 659.25], // Fmaj7: F4, A4, C5, E5
[87.31, 110.00, 130.81, 164.81], // Fmaj7: F2, A2, C3, E3 [293.66, 369.99, 440.00, 523.25], // Dm7: D4, F#4, A4, C5
[98.00, 123.47, 146.83, 174.61], // G7: G2, B2, D3, F3 [196.00, 246.94, 293.66, 392.00], // Gadd9: G3, B3, D4, G4
]; ];
const CHORD_DURATION = 32; const CHORD_DURATION = 24;
export class MusicEngine { export class MusicEngine {
private ctx: AudioContext; private ctx: AudioContext;
private dest: AudioNode; private dest: AudioNode;
private oscs: OscillatorNode[] = []; private oscs: OscillatorNode[] = [];
private lfos: OscillatorNode[] = [];
private gains: GainNode[] = []; private gains: GainNode[] = [];
private shimmerOsc: OscillatorNode | null = null; private shimmerOsc: OscillatorNode | null = null;
private shimmerGain: GainNode | null = null; private shimmerGain: GainNode | null = null;
@@ -31,22 +35,23 @@ export class MusicEngine {
if (this.running) return; if (this.running) return;
this.running = true; this.running = true;
// Warm lowpass keeps it soft
this.filter = this.ctx.createBiquadFilter(); this.filter = this.ctx.createBiquadFilter();
this.filter.type = 'lowpass'; this.filter.type = 'lowpass';
this.filter.frequency.value = 1400; this.filter.frequency.value = 2200;
this.filter.Q.value = 0.5; this.filter.Q.value = 0.3;
this.filter.connect(this.dest); this.filter.connect(this.dest);
// Filter LFO // Gentle filter movement
this.filterLfo = this.ctx.createOscillator(); this.filterLfo = this.ctx.createOscillator();
this.filterLfoGain = this.ctx.createGain(); this.filterLfoGain = this.ctx.createGain();
this.filterLfo.type = 'sine'; this.filterLfo.type = 'sine';
this.filterLfo.frequency.value = 0.01; this.filterLfo.frequency.value = 0.04;
this.filterLfoGain.gain.value = 600; this.filterLfoGain.gain.value = 400;
this.filterLfo.connect(this.filterLfoGain).connect(this.filter.frequency); this.filterLfo.connect(this.filterLfoGain).connect(this.filter.frequency);
this.filterLfo.start(); this.filterLfo.start();
// Base chord oscillators with breathing LFOs // Chord pad — triangle waves for warmth without the drone menace
const chord = CHORDS[0]; const chord = CHORDS[0];
for (let i = 0; i < chord.length; i++) { for (let i = 0; i < chord.length; i++) {
const osc = this.ctx.createOscillator(); const osc = this.ctx.createOscillator();
@@ -54,43 +59,45 @@ export class MusicEngine {
const lfo = this.ctx.createOscillator(); const lfo = this.ctx.createOscillator();
const lfoGain = this.ctx.createGain(); const lfoGain = this.ctx.createGain();
osc.type = 'sine'; osc.type = 'triangle';
osc.frequency.value = chord[i]; osc.frequency.value = chord[i];
gain.gain.value = 0.04; gain.gain.value = 0.03;
// Moderate breathing — fast enough to feel alive, not ominous
lfo.type = 'sine'; lfo.type = 'sine';
lfo.frequency.value = 0.05 + i * 0.03; lfo.frequency.value = 0.12 + i * 0.04;
lfoGain.gain.value = 0.03; lfoGain.gain.value = 0.015;
lfo.connect(lfoGain).connect(gain.gain); lfo.connect(lfoGain).connect(gain.gain);
osc.connect(gain).connect(this.filter!); osc.connect(gain).connect(this.filter!);
osc.start(); osc.start();
lfo.start(); lfo.start();
this.oscs.push(osc, lfo); this.oscs.push(osc);
this.lfos.push(lfo);
this.gains.push(gain); this.gains.push(gain);
} }
// Shimmer layer // High sparkle layer
this.shimmerOsc = this.ctx.createOscillator(); this.shimmerOsc = this.ctx.createOscillator();
this.shimmerGain = this.ctx.createGain(); this.shimmerGain = this.ctx.createGain();
this.shimmerLfo = this.ctx.createOscillator(); this.shimmerLfo = this.ctx.createOscillator();
const shimmerLfoGain = this.ctx.createGain(); const shimmerLfoGain = this.ctx.createGain();
this.shimmerOsc.type = 'triangle'; this.shimmerOsc.type = 'sine';
this.shimmerOsc.frequency.value = 523.25; // C5 this.shimmerOsc.frequency.value = 1046.50; // C6
this.shimmerGain.gain.value = 0.02; this.shimmerGain.gain.value = 0.008;
this.shimmerLfo.type = 'sine'; this.shimmerLfo.type = 'sine';
this.shimmerLfo.frequency.value = 0.02; this.shimmerLfo.frequency.value = 0.06;
shimmerLfoGain.gain.value = 0.018; shimmerLfoGain.gain.value = 0.007;
this.shimmerLfo.connect(shimmerLfoGain).connect(this.shimmerGain.gain); this.shimmerLfo.connect(shimmerLfoGain).connect(this.shimmerGain.gain);
this.shimmerOsc.connect(this.shimmerGain).connect(this.filter!); this.shimmerOsc.connect(this.shimmerGain).connect(this.filter!);
this.shimmerOsc.start(); this.shimmerOsc.start();
this.shimmerLfo.start(); this.shimmerLfo.start();
this.oscs.push(this.shimmerLfo); this.lfos.push(this.shimmerLfo);
this.chordIndex = 0; this.chordIndex = 0;
this.timer = setInterval(() => this.nextChord(), CHORD_DURATION * 1000); this.timer = setInterval(() => this.nextChord(), CHORD_DURATION * 1000);
@@ -101,20 +108,18 @@ export class MusicEngine {
const chord = CHORDS[this.chordIndex]; const chord = CHORDS[this.chordIndex];
const now = this.ctx.currentTime; const now = this.ctx.currentTime;
// Only the first 4 oscs are chord tones (every other is an LFO)
for (let i = 0; i < chord.length; i++) { for (let i = 0; i < chord.length; i++) {
const osc = this.oscs[i * 2]; // chord oscs are at even indices const osc = this.oscs[i];
osc.frequency.cancelScheduledValues(now); osc.frequency.cancelScheduledValues(now);
osc.frequency.setValueAtTime(osc.frequency.value, now); osc.frequency.setValueAtTime(osc.frequency.value, now);
osc.frequency.linearRampToValueAtTime(chord[i], now + 2); osc.frequency.linearRampToValueAtTime(chord[i], now + 3);
} }
// Shift shimmer to match root at higher octave
if (this.shimmerOsc) { if (this.shimmerOsc) {
const shimmerFreq = chord[0] * 4; const shimmerFreq = chord[2] * 2; // Fifth of chord, up an octave
this.shimmerOsc.frequency.cancelScheduledValues(now); this.shimmerOsc.frequency.cancelScheduledValues(now);
this.shimmerOsc.frequency.setValueAtTime(this.shimmerOsc.frequency.value, now); this.shimmerOsc.frequency.setValueAtTime(this.shimmerOsc.frequency.value, now);
this.shimmerOsc.frequency.linearRampToValueAtTime(shimmerFreq, now + 2); this.shimmerOsc.frequency.linearRampToValueAtTime(shimmerFreq, now + 3);
} }
} }
@@ -130,10 +135,17 @@ export class MusicEngine {
for (const osc of this.oscs) { for (const osc of this.oscs) {
try { osc.stop(); } catch { /* already stopped */ } try { osc.stop(); } catch { /* already stopped */ }
} }
for (const lfo of this.lfos) {
try { lfo.stop(); } catch { /* already stopped */ }
}
if (this.shimmerOsc) { if (this.shimmerOsc) {
try { this.shimmerOsc.stop(); } catch { /* already stopped */ } try { this.shimmerOsc.stop(); } catch { /* already stopped */ }
} }
if (this.filterLfo) {
try { this.filterLfo.stop(); } catch { /* already stopped */ }
}
this.oscs = []; this.oscs = [];
this.lfos = [];
this.gains = []; this.gains = [];
this.shimmerOsc = null; this.shimmerOsc = null;
this.shimmerGain = null; this.shimmerGain = null;