luneraria-control-center/public/assets/js/audio.js
Martin Stickelbroeck 0df07eefd1
Some checks are pending
CI/CD Cloud Deployment / test-and-build (push) Waiting to run
feat: Champions of Luneraria - Portal & LiveOps Schaltzentrale
- 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
2026-10-11 20:07:51 +02:00

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();