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[][] = [
[130.81, 164.81, 196.00, 246.94], // Cmaj7: C3, E3, G3, B3
[110.00, 130.81, 164.81, 207.65], // Am7: A2, C3, E3, Ab3
[87.31, 110.00, 130.81, 164.81], // Fmaj7: F2, A2, C3, E3
[98.00, 123.47, 146.83, 174.61], // G7: G2, B2, D3, F3
[261.63, 329.63, 392.00, 493.88], // Cmaj7: C4, E4, G4, B4
[349.23, 440.00, 523.25, 659.25], // Fmaj7: F4, A4, C5, E5
[293.66, 369.99, 440.00, 523.25], // Dm7: D4, F#4, A4, C5
[196.00, 246.94, 293.66, 392.00], // Gadd9: G3, B3, D4, G4
];
const CHORD_DURATION = 32;
const CHORD_DURATION = 24;
export class MusicEngine {
private ctx: AudioContext;
private dest: AudioNode;
private oscs: OscillatorNode[] = [];
private lfos: OscillatorNode[] = [];
private gains: GainNode[] = [];
private shimmerOsc: OscillatorNode | null = null;
private shimmerGain: GainNode | null = null;
@@ -31,22 +35,23 @@ export class MusicEngine {
if (this.running) return;
this.running = true;
// Warm lowpass keeps it soft
this.filter = this.ctx.createBiquadFilter();
this.filter.type = 'lowpass';
this.filter.frequency.value = 1400;
this.filter.Q.value = 0.5;
this.filter.frequency.value = 2200;
this.filter.Q.value = 0.3;
this.filter.connect(this.dest);
// Filter LFO
// Gentle filter movement
this.filterLfo = this.ctx.createOscillator();
this.filterLfoGain = this.ctx.createGain();
this.filterLfo.type = 'sine';
this.filterLfo.frequency.value = 0.01;
this.filterLfoGain.gain.value = 600;
this.filterLfo.frequency.value = 0.04;
this.filterLfoGain.gain.value = 400;
this.filterLfo.connect(this.filterLfoGain).connect(this.filter.frequency);
this.filterLfo.start();
// Base chord oscillators with breathing LFOs
// Chord pad — triangle waves for warmth without the drone menace
const chord = CHORDS[0];
for (let i = 0; i < chord.length; i++) {
const osc = this.ctx.createOscillator();
@@ -54,43 +59,45 @@ export class MusicEngine {
const lfo = this.ctx.createOscillator();
const lfoGain = this.ctx.createGain();
osc.type = 'sine';
osc.type = 'triangle';
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.frequency.value = 0.05 + i * 0.03;
lfoGain.gain.value = 0.03;
lfo.frequency.value = 0.12 + i * 0.04;
lfoGain.gain.value = 0.015;
lfo.connect(lfoGain).connect(gain.gain);
osc.connect(gain).connect(this.filter!);
osc.start();
lfo.start();
this.oscs.push(osc, lfo);
this.oscs.push(osc);
this.lfos.push(lfo);
this.gains.push(gain);
}
// Shimmer layer
// High sparkle layer
this.shimmerOsc = this.ctx.createOscillator();
this.shimmerGain = this.ctx.createGain();
this.shimmerLfo = this.ctx.createOscillator();
const shimmerLfoGain = this.ctx.createGain();
this.shimmerOsc.type = 'triangle';
this.shimmerOsc.frequency.value = 523.25; // C5
this.shimmerGain.gain.value = 0.02;
this.shimmerOsc.type = 'sine';
this.shimmerOsc.frequency.value = 1046.50; // C6
this.shimmerGain.gain.value = 0.008;
this.shimmerLfo.type = 'sine';
this.shimmerLfo.frequency.value = 0.02;
shimmerLfoGain.gain.value = 0.018;
this.shimmerLfo.frequency.value = 0.06;
shimmerLfoGain.gain.value = 0.007;
this.shimmerLfo.connect(shimmerLfoGain).connect(this.shimmerGain.gain);
this.shimmerOsc.connect(this.shimmerGain).connect(this.filter!);
this.shimmerOsc.start();
this.shimmerLfo.start();
this.oscs.push(this.shimmerLfo);
this.lfos.push(this.shimmerLfo);
this.chordIndex = 0;
this.timer = setInterval(() => this.nextChord(), CHORD_DURATION * 1000);
@@ -101,20 +108,18 @@ export class MusicEngine {
const chord = CHORDS[this.chordIndex];
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++) {
const osc = this.oscs[i * 2]; // chord oscs are at even indices
const osc = this.oscs[i];
osc.frequency.cancelScheduledValues(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) {
const shimmerFreq = chord[0] * 4;
const shimmerFreq = chord[2] * 2; // Fifth of chord, up an octave
this.shimmerOsc.frequency.cancelScheduledValues(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) {
try { osc.stop(); } catch { /* already stopped */ }
}
for (const lfo of this.lfos) {
try { lfo.stop(); } catch { /* already stopped */ }
}
if (this.shimmerOsc) {
try { this.shimmerOsc.stop(); } catch { /* already stopped */ }
}
if (this.filterLfo) {
try { this.filterLfo.stop(); } catch { /* already stopped */ }
}
this.oscs = [];
this.lfos = [];
this.gains = [];
this.shimmerOsc = null;
this.shimmerGain = null;