diff --git a/web_realart/public/control/control.css b/web_realart/public/control/control.css index 0c209d5..926ac3b 100644 --- a/web_realart/public/control/control.css +++ b/web_realart/public/control/control.css @@ -113,4 +113,7 @@ button.queued { animation: queued-blink 600ms ease-in-out infinite alternate; } .mcell.m5 { background: #c60; border-color: #e80; } .mcell.m6 { background: #82a; border-color: #a4d; } .mcell.playing { box-shadow: 0 0 0 2px rgba(255,255,255,0.28), - inset 0 0 0 1px rgba(255,255,255,0.40); } + inset 0 0 0 1px rgba(255,255,255,0.40), + 0 0 calc(var(--glow, 0) * 12px) calc(var(--glow, 0) * 4px) + rgba(255,255,255, calc(var(--glow, 0) * 0.7)); + filter: brightness(calc(1 + var(--glow, 0) * 0.9)); } diff --git a/web_realart/public/control/control.js b/web_realart/public/control/control.js index eea2c10..7e2b971 100644 --- a/web_realart/public/control/control.js +++ b/web_realart/public/control/control.js @@ -161,6 +161,11 @@ ws.addEventListener("message", (ev) => { } return; } + // /matrix/trig — per-note pulse: glow the active cell of voice vi + if (address === "/matrix/trig") { + triggerGlow(Math.round(Number(args[0])), Number(args[1])); + return; + } }); // --- Matrix (16-voice x 32-bar song arranger) --- @@ -254,6 +259,62 @@ function renderMatrix() { } } +// --- Matrix audio-reactive glow (trigger envelope; math in matrix_glow.js) --- +const voiceLevel = new Array(16).fill(0); +let glowRaf = null; +let glowLastTs = 0; +let glowAppliedBar = -1; + +function clearGlowColumn(bar) { + if (bar < 0) return; + for (let vi = 0; vi < MATRIX_VOICES.length; vi++) { + const el = cellRefs[bar][vi]; + if (el) el.style.removeProperty("--glow"); + } +} + +function glowFrame(ts) { + const decay = window.MatrixGlow ? window.MatrixGlow.nextGlow : (l) => 0; + const dt = glowLastTs ? (ts - glowLastTs) : (1000 / 60); + glowLastTs = ts; + if (matPlayhead !== glowAppliedBar) { + clearGlowColumn(glowAppliedBar); + glowAppliedBar = matPlayhead; + } + let anyActive = false; + for (let vi = 0; vi < 16; vi++) { + const lvl = decay(voiceLevel[vi], dt); + voiceLevel[vi] = lvl; + if (matPlayhead >= 0) { + const el = cellRefs[matPlayhead][vi]; + if (el) { + if (lvl > 0) el.style.setProperty("--glow", lvl.toFixed(3)); + else el.style.removeProperty("--glow"); + } + } + if (lvl > 0) anyActive = true; + } + if (anyActive) { + glowRaf = requestAnimationFrame(glowFrame); + } else { + clearGlowColumn(glowAppliedBar); + glowRaf = null; + glowLastTs = 0; + glowAppliedBar = -1; + } +} + +function triggerGlow(vi, amp) { + if (!Number.isInteger(vi) || vi < 0 || vi >= 16) return; + const a = Math.max(0, Math.min(1, amp)); + if (a <= 0) return; + voiceLevel[vi] = Math.max(voiceLevel[vi], a); + if (glowRaf === null) { + glowLastTs = 0; + glowRaf = requestAnimationFrame(glowFrame); + } +} + // --- Pattern state (defaults mirror launchpad.scd exactly) --- const STORAGE_KEY = "avlive.seq"; diff --git a/web_realart/public/control/index.html b/web_realart/public/control/index.html index dbc61ee..12fcf1b 100644 --- a/web_realart/public/control/index.html +++ b/web_realart/public/control/index.html @@ -163,4 +163,5 @@ +