feat: web matrix color editor modal
This commit is contained in:
@@ -163,3 +163,23 @@ button.queued { animation: queued-blink 600ms ease-in-out infinite alternate; }
|
||||
.mix-mute { width: auto; padding: 4px 6px; font-size: 11px; }
|
||||
.mix-mute.on { background: #622; border-color: #c44; color: #f88; font-weight: 700; }
|
||||
.mix-label { font-size: 10px; color: #9af; text-align: center; word-break: break-all; }
|
||||
|
||||
/* --- Color def modal --- */
|
||||
.minst-edit { position: sticky; left: 132px; z-index: 2; width: 22px; font-size: 11px;
|
||||
background: #222; color: #ccc; border: 1px solid #333; flex-shrink: 0; }
|
||||
.cd-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex;
|
||||
align-items: center; justify-content: center; z-index: 50; }
|
||||
.cd-modal[hidden] { display: none; }
|
||||
.cd-panel { background: #161616; border: 1px solid #333; border-radius: 6px;
|
||||
padding: 12px; max-height: 90vh; overflow-y: auto; min-width: 320px; }
|
||||
.cd-head { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
|
||||
.cd-aud.on { background: #2b6; color: #fff; }
|
||||
.cd-row { display: flex; gap: 6px; align-items: center; padding: 4px; border-radius: 3px;
|
||||
border-left: 4px solid; margin-bottom: 3px; }
|
||||
.cd-field { font-size: 9px; color: #999; display: flex; flex-direction: column; }
|
||||
.cd-row.m1 { border-left-color: #c22; }
|
||||
.cd-row.m2 { border-left-color: #1a8; }
|
||||
.cd-row.m3 { border-left-color: #25b; }
|
||||
.cd-row.m4 { border-left-color: #990; }
|
||||
.cd-row.m5 { border-left-color: #c60; }
|
||||
.cd-row.m6 { border-left-color: #82a; }
|
||||
|
||||
@@ -256,6 +256,20 @@ ws.addEventListener("message", (ev) => {
|
||||
saveMatState();
|
||||
return;
|
||||
}
|
||||
if (address === "/matrix/colordefs") {
|
||||
const vi = Math.round(Number(args[0]));
|
||||
if (vi >= 0 && vi < 16) {
|
||||
for (let c = 1; c <= 6; c++) {
|
||||
const b = 1 + (c - 1) * 6;
|
||||
matColorDefs[vi][c] = { stretch:Number(args[b]), octave:Math.round(Number(args[b+1])),
|
||||
amp:Number(args[b+2]), inst:String(args[b+3] ?? "default"),
|
||||
cutoff:Number(args[b+4]), pan:Number(args[b+5]) };
|
||||
}
|
||||
saveMatState();
|
||||
if (cdEditVoice === vi && !document.getElementById("colordef-modal").hidden) openColorDef(vi);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// --- Matrix (16-voice x 32-bar song arranger) ---
|
||||
@@ -286,6 +300,15 @@ const MATRIX_MOD_TARGETS = {
|
||||
bells:["none","amp"], sweep:["none","amp"]
|
||||
};
|
||||
let matMod = Array.from({length:16}, () => ({source:"none", target:"none", depth:0.5}));
|
||||
let matColorDefs = Array.from({ length: 16 }, () =>
|
||||
[null,
|
||||
{stretch:1.0,octave:0,amp:1.0,inst:"default",cutoff:-1,pan:-2},
|
||||
{stretch:0.5,octave:0,amp:1.0,inst:"default",cutoff:-1,pan:-2},
|
||||
{stretch:1.0,octave:1,amp:1.0,inst:"default",cutoff:-1,pan:-2},
|
||||
{stretch:2.0,octave:0,amp:1.0,inst:"default",cutoff:-1,pan:-2},
|
||||
{stretch:1.0,octave:-1,amp:1.05,inst:"default",cutoff:-1,pan:-2},
|
||||
{stretch:0.5,octave:0,amp:1.2,inst:"default",cutoff:-1,pan:-2}]);
|
||||
let cdEditVoice = -1, cdAuditioning = false;
|
||||
const MATRIX_BARS = 32;
|
||||
let matGrid = Array.from({ length: 16 }, () => new Array(32).fill(0));
|
||||
// cellRefs[bar][vi] for O(16) playhead column toggle
|
||||
@@ -296,14 +319,16 @@ let matLoopEnd = 31;
|
||||
|
||||
function saveMatState() {
|
||||
try { localStorage.setItem(MATRIX_STORAGE_KEY,
|
||||
JSON.stringify({ grid: matGrid, inst: matInst, mod: matMod })); } catch (_e) {}
|
||||
JSON.stringify({ grid: matGrid, inst: matInst, mod: matMod, cdef: matColorDefs })); } catch (_e) {}
|
||||
}
|
||||
function loadMatState() {
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem(MATRIX_STORAGE_KEY));
|
||||
if (raw && Array.isArray(raw.grid) && raw.grid.length === 16) { matGrid = raw.grid;
|
||||
if (Array.isArray(raw.inst) && raw.inst.length === 16) matInst = raw.inst;
|
||||
if (Array.isArray(raw.mod) && raw.mod.length === 16) matMod = raw.mod; return; }
|
||||
if (Array.isArray(raw.mod) && raw.mod.length === 16) matMod = raw.mod;
|
||||
if (Array.isArray(raw.cdef) && raw.cdef.length === 16) matColorDefs = raw.cdef;
|
||||
return; }
|
||||
if (Array.isArray(raw) && raw.length === 16) { matGrid = raw; return; } // legacy: bare grid
|
||||
} catch (_e) {}
|
||||
matGrid = Array.from({ length: 16 }, () => new Array(32).fill(0));
|
||||
@@ -363,6 +388,10 @@ function renderMatrix() {
|
||||
send("/matrix/instrument", vi, inst.value);
|
||||
});
|
||||
row.appendChild(inst);
|
||||
const edit = document.createElement("button");
|
||||
edit.className = "minst-edit"; edit.textContent = "..."; edit.dataset.vi = vi;
|
||||
edit.addEventListener("click", () => openColorDef(vi));
|
||||
row.appendChild(edit);
|
||||
for (let bar = 0; bar < MATRIX_BARS; bar++) {
|
||||
const color = matGrid[vi][bar];
|
||||
const cell = document.createElement("button");
|
||||
@@ -410,6 +439,57 @@ function renderMatrix() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Color def modal ---
|
||||
function openColorDef(vi) {
|
||||
cdEditVoice = vi;
|
||||
document.getElementById("cd-title").textContent = MATRIX_VOICES[vi];
|
||||
const rows = document.getElementById("cd-rows");
|
||||
rows.innerHTML = "";
|
||||
const targets = MATRIX_MOD_TARGETS[MATRIX_VOICES[vi]] || ["none","amp"];
|
||||
const hasCut = targets.includes("cutoff"), hasPan = targets.includes("pan");
|
||||
for (let c = 1; c <= 6; c++) {
|
||||
const d = matColorDefs[vi][c];
|
||||
const r = document.createElement("div"); r.className = "cd-row m" + c;
|
||||
const mk = (label, el) => { const w = document.createElement("label");
|
||||
w.className = "cd-field"; w.textContent = label; w.appendChild(el); return w; };
|
||||
const inst = document.createElement("select"); inst.className = "cd-inst";
|
||||
[["default","default"]].concat((MATRIX_INST_CHOICES[MATRIX_VOICES[vi]]||[]).map(s=>[s,s]))
|
||||
.forEach(([v,t]) => { const o=document.createElement("option"); o.value=v; o.textContent=t; inst.appendChild(o); });
|
||||
inst.value = d.inst || "default";
|
||||
inst.addEventListener("change", () => setCD(c, "inst", inst.value));
|
||||
const stretch = document.createElement("select"); stretch.className = "cd-str";
|
||||
[0.5,1.0,2.0].forEach(v => { const o=document.createElement("option"); o.value=v; o.textContent=v+"x"; stretch.appendChild(o); });
|
||||
stretch.value = d.stretch; stretch.addEventListener("change", () => setCD(c, "stretch", +stretch.value));
|
||||
const oct = document.createElement("select"); oct.className = "cd-oct";
|
||||
[-1,0,1].forEach(v => { const o=document.createElement("option"); o.value=v; o.textContent=v; oct.appendChild(o); });
|
||||
oct.value = d.octave; oct.addEventListener("change", () => setCD(c, "octave", +oct.value));
|
||||
const amp = document.createElement("input"); amp.type="range"; amp.min=0; amp.max=1.5; amp.step=0.01;
|
||||
amp.value = d.amp; amp.addEventListener("change", () => setCD(c, "amp", +amp.value));
|
||||
r.appendChild(mk("inst", inst)); r.appendChild(mk("stretch", stretch));
|
||||
r.appendChild(mk("oct", oct)); r.appendChild(mk("amp", amp));
|
||||
if (hasCut) { const cut = document.createElement("input"); cut.type="range"; cut.min=200; cut.max=6000; cut.step=10;
|
||||
cut.value = d.cutoff > 0 ? d.cutoff : 1000; cut.addEventListener("change", () => setCD(c, "cutoff", +cut.value)); r.appendChild(mk("cutoff", cut)); }
|
||||
if (hasPan) { const pan = document.createElement("input"); pan.type="range"; pan.min=-1; pan.max=1; pan.step=0.01;
|
||||
pan.value = d.pan >= -1 ? d.pan : 0; pan.addEventListener("change", () => setCD(c, "pan", +pan.value)); r.appendChild(mk("pan", pan)); }
|
||||
rows.appendChild(r);
|
||||
}
|
||||
document.getElementById("colordef-modal").hidden = false;
|
||||
send("/matrix/colordefs/get", vi);
|
||||
}
|
||||
|
||||
function setCD(color, field, value) {
|
||||
if (cdEditVoice < 0) return;
|
||||
matColorDefs[cdEditVoice][color][field] = value;
|
||||
saveMatState();
|
||||
send("/matrix/colordef", cdEditVoice, color, field, value);
|
||||
}
|
||||
|
||||
function closeColorDef() {
|
||||
if (cdAuditioning && cdEditVoice >= 0) { send("/matrix/audition", cdEditVoice, 0); cdAuditioning = false; }
|
||||
document.getElementById("colordef-modal").hidden = true;
|
||||
cdEditVoice = -1;
|
||||
}
|
||||
|
||||
// --- Matrix timeline: 32-bar loop region + playhead ---
|
||||
function renderTimeline() {
|
||||
const tl = document.getElementById("matrix-timeline");
|
||||
@@ -803,6 +883,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
initTimelinePointer();
|
||||
renderMixer();
|
||||
|
||||
document.getElementById("cd-close").addEventListener("click", closeColorDef);
|
||||
document.getElementById("cd-audition").addEventListener("click", () => {
|
||||
if (cdEditVoice < 0) return;
|
||||
cdAuditioning = !cdAuditioning;
|
||||
send("/matrix/audition", cdEditVoice, cdAuditioning ? 1 : 0);
|
||||
document.getElementById("cd-audition").classList.toggle("on", cdAuditioning);
|
||||
});
|
||||
|
||||
// Name input: rename selected preset and update button labels
|
||||
const melName = document.getElementById("mel-name");
|
||||
if (melName) {
|
||||
|
||||
@@ -177,5 +177,13 @@
|
||||
<div id="matrix-mixer" class="matrix-mixer"></div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="colordef-modal" class="cd-modal" hidden>
|
||||
<div class="cd-panel">
|
||||
<div class="cd-head"><span id="cd-title">voice</span>
|
||||
<button id="cd-audition" class="cd-aud">AUDITION</button>
|
||||
<button id="cd-close" class="cd-close">X</button></div>
|
||||
<div id="cd-rows"></div>
|
||||
</div>
|
||||
</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