feat(keys_highway_3d): audio-reactive ambience + score FX overlay (K4) (#709)

- BG_STYLES port (off/particles/lights/geometric; lights use the
  pitch-class palette) mounted behind the scene; _bgGetAnalyser/
  _bgReadBands (stems-first, one-shot #audio fallback, permanent-failure
  latch); Ambience intensity + Audio-reactive settings; remounts on
  style/intensity change
- Score-FX overlay canvas (drum_highway_3d pattern): +1 pops at the
  scored key, ring pulse every 10-combo, milestone bursts at 25/50/100,
  red wash on 3+ streak break (wrong notes AND swept misses); cleared
  when idle, removed in teardown
- Tests: style id validation + FX defaults (30 total)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Byron Gamatos
2026-07-02 09:06:12 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent d409d55615
commit 3e2703d8e1
6 changed files with 607 additions and 0 deletions
+19
View File
@@ -2196,6 +2196,24 @@
}
const audio = document.getElementById('audio');
if (!audio) return null;
// Shared tap: createMediaElementSource is one-shot per element, so
// the FIRST visualizer to tap #audio publishes it at
// window.__feedBackAudioTap and every later one (this plugin, the
// drum/keys 3D highways) adopts it instead of throwing
// InvalidStateError when visualizers are switched or mixed in
// splitscreen.
const sharedTap = window.__feedBackAudioTap;
if (sharedTap && sharedTap.analyser && sharedTap.mediaEl === audio) {
_bgAudio = {
ctx: sharedTap.ctx,
analyser: sharedTap.analyser,
freq: new Uint8Array(Math.max(BG_FREQ_BINS, sharedTap.analyser.frequencyBinCount)),
source: 'core',
};
_bgAudioCore = _bgAudio;
_bgRecordAudioBridge('audio-mix.analyser', 'shared #audio analyser tap', 'handled', '', 'core');
return _bgAudio;
}
// Hoist ctx out of the try so we can close() it if a later step
// throws (e.g. createMediaElementSource on an element that
// already has a source node). Otherwise the AudioContext leaks.
@@ -2210,6 +2228,7 @@
source.connect(analyser);
analyser.connect(ctx.destination);
_bgAudio = { ctx, analyser, freq: new Uint8Array(Math.max(BG_FREQ_BINS, analyser.frequencyBinCount)), source: 'core' };
try { window.__feedBackAudioTap = { ctx, analyser, mediaEl: audio }; } catch (_) {}
_bgRecordAudioBridge('audio-mix.analyser', 'HTMLAudioElement analyser tap', 'handled', '', 'core');
// Remember the core analyser so a later stems-then-back-to-core
// transition can re-use it instead of re-tapping #audio (which