feat: render audio-reactive glow on matrix grid
This commit is contained in:
@@ -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)); }
|
||||
|
||||
@@ -161,6 +161,11 @@ ws.addEventListener("message", (ev) => {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// /matrix/trig <vi> <amp> — 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";
|
||||
|
||||
|
||||
@@ -163,4 +163,5 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script type="module">import * as G from "./matrix_glow.js"; window.MatrixGlow = G;</script>
|
||||
<script src="control.js"></script></body></html>
|
||||
|
||||
Reference in New Issue
Block a user