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[][] = [
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user