Fix background music: bright lo-fi pad instead of horror ambient
CI / build-and-push (push) Successful in 46s
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user