From f3e6a2e692290f95d47b1ed113c6ea6167a2bc4c Mon Sep 17 00:00:00 2001 From: josh Date: Tue, 28 Apr 2026 20:20:43 -0400 Subject: [PATCH] Fix background music: bright lo-fi pad instead of horror ambient 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 --- apps/web/src/audio/musicEngine.ts | 70 ++++++++++++++++++------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/apps/web/src/audio/musicEngine.ts b/apps/web/src/audio/musicEngine.ts index 508f376..4d35938 100644 --- a/apps/web/src/audio/musicEngine.ts +++ b/apps/web/src/audio/musicEngine.ts @@ -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;