Some checks are pending
CI/CD Cloud Deployment / test-and-build (push) Waiting to run
- Triple-A Werbeseite mit Gameplay-Videos, 3-Lane Radar und Einheiten-Showcase - LiveOps-Schaltzentrale mit CCU-Tracking, Download-Statistiken und Match-Balance - Reale Engine-Benchmarks (C# vs Godot, U2-U5.5 FPS, GPU-Skinning Entlastung) - Zero-Dependency Node.js Backend mit REST-APIs und Server-Sent Events (SSE) - Cloud-Ready mit Dockerfile, docker-compose.yml und GitHub Actions CI/CD
152 lines
4 KiB
JavaScript
152 lines
4 KiB
JavaScript
/**
|
|
* CHAMPIONS OF LUNERARIA - Ambient Audio Engine
|
|
* Pure Web Audio API Synthesizer (No external sound files, 100% offline & local)
|
|
* Provides an atmospheric fantasy soundscape with gentle bass drone & crystalline pads.
|
|
*/
|
|
|
|
class LuneAudioEngine {
|
|
constructor() {
|
|
this.ctx = null;
|
|
this.isPlaying = false;
|
|
this.masterGain = null;
|
|
this.nodes = [];
|
|
this.intervalId = null;
|
|
}
|
|
|
|
init() {
|
|
if (this.ctx) return;
|
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
if (!AudioContextClass) return;
|
|
this.ctx = new AudioContextClass();
|
|
|
|
this.masterGain = this.ctx.createGain();
|
|
this.masterGain.gain.setValueAtTime(0, this.ctx.currentTime);
|
|
this.masterGain.connect(this.ctx.destination);
|
|
}
|
|
|
|
toggle() {
|
|
this.init();
|
|
if (!this.ctx) return false;
|
|
|
|
if (this.ctx.state === 'suspended') {
|
|
this.ctx.resume();
|
|
}
|
|
|
|
if (this.isPlaying) {
|
|
this.stop();
|
|
return false;
|
|
} else {
|
|
this.start();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
start() {
|
|
if (this.isPlaying) return;
|
|
this.isPlaying = true;
|
|
const now = this.ctx.currentTime;
|
|
|
|
// Master fade in
|
|
this.masterGain.gain.cancelScheduledValues(now);
|
|
this.masterGain.gain.linearRampToValueAtTime(0.18, now + 3);
|
|
|
|
// 1. Warm Sub Drone (A1: 55Hz)
|
|
const droneOsc = this.ctx.createOscillator();
|
|
droneOsc.type = 'sawtooth';
|
|
droneOsc.frequency.setValueAtTime(55, now);
|
|
|
|
const droneFilter = this.ctx.createBiquadFilter();
|
|
droneFilter.type = 'lowpass';
|
|
droneFilter.frequency.setValueAtTime(120, now);
|
|
|
|
const droneGain = this.ctx.createGain();
|
|
droneGain.gain.setValueAtTime(0.6, now);
|
|
|
|
droneOsc.connect(droneFilter);
|
|
droneFilter.connect(droneGain);
|
|
droneGain.connect(this.masterGain);
|
|
droneOsc.start();
|
|
this.nodes.push(droneOsc);
|
|
|
|
// 2. Harmonic Ethereal Pad (A2 / E3 chords)
|
|
const padFrequencies = [110, 164.81, 220, 277.18];
|
|
padFrequencies.forEach((freq, i) => {
|
|
const osc = this.ctx.createOscillator();
|
|
osc.type = 'sine';
|
|
osc.frequency.setValueAtTime(freq, now);
|
|
|
|
// Subtle detune for shimmer
|
|
osc.detune.setValueAtTime((i - 1.5) * 4, now);
|
|
|
|
const pFilter = this.ctx.createBiquadFilter();
|
|
pFilter.type = 'bandpass';
|
|
pFilter.frequency.setValueAtTime(freq * 1.5, now);
|
|
pFilter.Q.setValueAtTime(2.0, now);
|
|
|
|
const pGain = this.ctx.createGain();
|
|
pGain.gain.setValueAtTime(0.25 / padFrequencies.length, now);
|
|
|
|
osc.connect(pFilter);
|
|
pFilter.connect(pGain);
|
|
pGain.connect(this.masterGain);
|
|
osc.start();
|
|
this.nodes.push(osc);
|
|
});
|
|
|
|
// 3. Periodic Shimmer Bells (Crystalline Moon Chimes)
|
|
this.startShimmerBells();
|
|
}
|
|
|
|
startShimmerBells() {
|
|
const bellNotes = [440, 554.37, 659.25, 830.61, 880, 1108.73];
|
|
this.intervalId = setInterval(() => {
|
|
if (!this.isPlaying || !this.ctx) return;
|
|
const note = bellNotes[Math.floor(Math.random() * bellNotes.length)];
|
|
this.playChime(note);
|
|
}, 2800);
|
|
}
|
|
|
|
playChime(freq) {
|
|
if (!this.ctx) return;
|
|
const now = this.ctx.currentTime;
|
|
const osc = this.ctx.createOscillator();
|
|
const gain = this.ctx.createGain();
|
|
|
|
osc.type = 'triangle';
|
|
osc.frequency.setValueAtTime(freq, now);
|
|
|
|
gain.gain.setValueAtTime(0.001, now);
|
|
gain.gain.exponentialRampToValueAtTime(0.04, now + 0.08);
|
|
gain.gain.exponentialRampToValueAtTime(0.0001, now + 2.5);
|
|
|
|
osc.connect(gain);
|
|
gain.connect(this.masterGain);
|
|
|
|
osc.start(now);
|
|
osc.stop(now + 2.6);
|
|
}
|
|
|
|
stop() {
|
|
if (!this.isPlaying) return;
|
|
this.isPlaying = false;
|
|
const now = this.ctx.currentTime;
|
|
|
|
// Fade out
|
|
this.masterGain.gain.cancelScheduledValues(now);
|
|
this.masterGain.gain.linearRampToValueAtTime(0.0001, now + 1.5);
|
|
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
}
|
|
|
|
setTimeout(() => {
|
|
this.nodes.forEach(n => {
|
|
try { n.stop(); n.disconnect(); } catch (e) {}
|
|
});
|
|
this.nodes = [];
|
|
}, 1600);
|
|
}
|
|
}
|
|
|
|
window.luneAudio = new LuneAudioEngine();
|