// Audio engine: builds a Web Audio graph from either a loaded file or a live // input device, runs an FFT analyser, and exposes smoothed bass/mid/treble/ // level values plus a simple beat detector. class AudioEngine { constructor() { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); this.analyser = this.ctx.createAnalyser(); this.analyser.fftSize = 2048; this.analyser.smoothingTimeConstant = 0.78; this.freq = new Uint8Array(this.analyser.frequencyBinCount); // Output gain so a loaded file is audible; live input stays muted to // avoid feedback (it's already playing through the system). this.outGain = this.ctx.createGain(); this.outGain.gain.value = 1.0; this.outGain.connect(this.ctx.destination); // Tap for recording: every source also feeds this destination so the // recorder captures file playback and live input alike. this.recordDest = this.ctx.createMediaStreamDestination(); // Silent keep-alive so the audio track always produces frames (otherwise, // with no source connected, the recorder stalls with empty data). try { const keep = this.ctx.createConstantSource(); keep.offset.value = 0; keep.connect(this.recordDest); keep.start(); } catch (e) { /* ConstantSource unsupported: live/file audio still flows */ } this.sourceNode = null; // current MediaElement / MediaStream source this.sourceGain = null; // per-source gain (for crossfades) this.mediaEl = null; //