feat: web color-centric pattern editor

This commit is contained in:
clement
2026-06-29 00:50:38 +02:00
parent 4cb6ce14c0
commit 84054714d3
3 changed files with 199 additions and 191 deletions
+7 -2
View File
@@ -137,7 +137,7 @@ button.queued { animation: queued-blink 600ms ease-in-out infinite alternate; }
/* --- Matrix timeline header --- */
.matrix-timeline { display: flex; gap: 1px; margin: 2px 0; touch-action: none; }
.tl-spacer { width: 54px; flex-shrink: 0; }
.tl-spacer { width: 154px; flex-shrink: 0; }
.tl-bar { width: 22px; min-width: 22px; height: 20px; flex-shrink: 0;
background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 2px;
cursor: pointer; font-size: 9px; color: #555; text-align: center;
@@ -158,8 +158,13 @@ button.queued { animation: queued-blink 600ms ease-in-out infinite alternate; }
.mix-label { font-size: 10px; color: #9af; text-align: center; word-break: break-all; }
/* --- Color def modal --- */
.minst-edit { position: sticky; left: 54px; z-index: 2; width: 22px; font-size: 11px;
.minst { position:sticky; left:54px; z-index:2; width:78px; font-size:10px; background:#1a1a1a; color:#ccc; border:1px solid #333; flex-shrink:0; }
.minst-head { width:78px; flex-shrink:0; }
.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-colorsel { display:flex; gap:4px; margin:6px 0; }
.cd-colorsel button.on { outline:2px solid #fff; }
.cd-pattern { display:flex; flex-direction:column; gap:6px; }
.cd-tabs { display:flex; gap:4px; margin:6px 0; }
.cd-tab { width: auto; padding: 6px 10px; font-size: 12px; }
.cd-tab.on { background:#2b6; color:#fff; }
+190 -177
View File
@@ -217,10 +217,8 @@ ws.addEventListener("message", (ev) => {
const vi = Math.round(Number(args[0])); const v = String(args[1] ?? "default");
if (vi >= 0 && vi < 16) {
matInst[vi] = v; saveMatState();
if (cdEditVoice === vi && !document.getElementById("colordef-modal").hidden) {
const sel = document.getElementById("cd-inst-sel");
if (sel) sel.value = v;
}
const rowSel = document.querySelector(`.minst[data-vi="${vi}"]`);
if (rowSel) rowSel.value = v;
}
return;
}
@@ -228,38 +226,8 @@ ws.addEventListener("message", (ev) => {
if (address === "/matrix/instruments") {
for (let vi = 0; vi < 16 && vi < args.length; vi++) {
const v = String(args[vi] ?? "default"); matInst[vi] = v;
}
if (cdEditVoice >= 0 && !document.getElementById("colordef-modal").hidden) {
const sel = document.getElementById("cd-inst-sel");
if (sel) sel.value = matInst[cdEditVoice];
}
saveMatState();
return;
}
if (address === "/matrix/mod") {
const vi = Math.round(Number(args[0]));
if (vi >= 0 && vi < 16) {
matMod[vi] = { source:String(args[1]??"none"), target:String(args[2]??"none"), depth:Number(args[3])||0 };
if (cdEditVoice === vi && !document.getElementById("colordef-modal").hidden) {
const r = document.querySelector(".cd-mod-src");
if (r) { r.value = matMod[vi].source;
document.querySelector(".cd-mod-tgt").value = matMod[vi].target;
document.querySelector(".cd-mod-dep").value = matMod[vi].depth; }
}
saveMatState();
}
return;
}
if (address === "/matrix/mods") { // 16 triples flattened = 48 values
for (let vi = 0; vi < 16; vi++) {
const b = vi*3;
matMod[vi] = { source:String(args[b]??"none"), target:String(args[b+1]??"none"), depth:Number(args[b+2])||0 };
}
if (cdEditVoice >= 0 && !document.getElementById("colordef-modal").hidden) {
const r = document.querySelector(".cd-mod-src");
if (r) { r.value=matMod[cdEditVoice].source;
document.querySelector(".cd-mod-tgt").value=matMod[cdEditVoice].target;
document.querySelector(".cd-mod-dep").value=matMod[cdEditVoice].depth; }
const rowSel = document.querySelector(`.minst[data-vi="${vi}"]`);
if (rowSel) rowSel.value = v;
}
saveMatState();
return;
@@ -269,35 +237,49 @@ ws.addEventListener("message", (ev) => {
if (vi >= 0 && vi < 16) {
for (let c = 1; c <= 6; c++) {
const b = 1 + (c - 1) * 6;
const ex = matColorDefs[vi][c] || {};
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]) };
cutoff:Number(args[b+4]), pan:Number(args[b+5]),
mod:ex.mod ?? null, pose:ex.pose || [], steps:ex.steps || new Array(16).fill(null) };
}
saveMatState();
if (cdEditVoice === vi && !document.getElementById("colordef-modal").hidden) renderColorDefRows(vi);
if (cdEditVoice === vi && !document.getElementById("colordef-modal").hidden) {
renderColorSel(vi); renderPattern(vi);
}
}
return;
}
if (address === "/matrix/steps") {
const vi = Math.round(Number(args[0])), c = Math.round(Number(args[1]));
if (vi>=0 && vi<16 && c>=1 && c<=6) {
if (vi>=0 && vi<16 && c>=1 && c<=6 && matColorDefs[vi][c]) {
if (!matColorDefs[vi][c].steps) matColorDefs[vi][c].steps = new Array(16).fill(null);
for (let s=0; s<16; s++) {
const deg = Number(args[2 + s*2]), vel = Number(args[3 + s*2]);
matSteps[vi][c][s] = (deg === -99) ? null : {degree:deg, vel:vel};
matColorDefs[vi][c].steps[s] = (deg === -99) ? null : {degree:deg, vel:vel};
}
saveMatState();
if (cdEditVoice===vi && !document.getElementById("colordef-modal").hidden) renderSeq(vi);
if (cdEditVoice===vi && cdColor===c && !document.getElementById("colordef-modal").hidden) renderPattern(vi);
}
return;
}
if (address === "/matrix/poses") {
const vi = Math.round(Number(args[0])), n = Math.round(Number(args[1]));
if (vi>=0 && vi<16) {
matPoses[vi] = [];
for (let k=0; k<n; k++) { const b=2+k*3;
matPoses[vi].push({poseId:String(args[b]), action:String(args[b+1]), color:Math.round(Number(args[b+2]))}); }
if (address === "/matrix/colormod") {
const vi=Math.round(Number(args[0])), c=Math.round(Number(args[1]));
if (vi>=0&&vi<16&&c>=1&&c<=6) {
const src=String(args[2]??"none");
matColorDefs[vi][c].mod = (src==="none") ? null : {source:src, target:String(args[3]??"none"), depth:Number(args[4])||0};
saveMatState();
if (cdEditVoice===vi && !document.getElementById("colordef-modal").hidden) renderPoses(vi);
if (cdEditVoice===vi && cdColor===c && !document.getElementById("colordef-modal").hidden) renderPattern(vi);
}
return;
}
if (address === "/matrix/colorpose") {
const vi=Math.round(Number(args[0])), c=Math.round(Number(args[1])), n=Math.round(Number(args[2]));
if (vi>=0&&vi<16&&c>=1&&c<=6) {
matColorDefs[vi][c].pose = [];
for (let k=0;k<n;k++){ const b=3+k*2; matColorDefs[vi][c].pose.push({poseId:String(args[b]),action:String(args[b+1])}); }
saveMatState();
if (cdEditVoice===vi && cdColor===c && !document.getElementById("colordef-modal").hidden) renderPattern(vi);
}
return;
}
@@ -331,18 +313,17 @@ const MATRIX_MOD_TARGETS = {
ride:["none","amp","pan","rev"], rim:["none","amp","pan","rev"], tom:["none","amp","pan","rev"], reese:["none","amp","cutoff","pan","rev"],
bells:["none","amp","pan","rev"], sweep:["none","amp","pan","rev"]
};
let matMod = Array.from({length:16}, () => ({source:"none", target:"none", depth:0.5}));
const mkColor = (stretch, octave, amp, inst, cutoff, pan) =>
({stretch, octave, amp, inst, cutoff, pan, mod:null, pose:[], steps:new Array(16).fill(null)});
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 matSteps = Array.from({length:16}, () => Array.from({length:7}, () => new Array(16).fill(null)));
let matPoses = Array.from({length:16}, () => []);
let cdEditVoice = -1, cdAuditioning = false;
mkColor(1.0,0,1.0,"default",-1,-2),
mkColor(0.5,0,1.0,"default",-1,-2),
mkColor(1.0,1,1.0,"default",-1,-2),
mkColor(2.0,0,1.0,"default",-1,-2),
mkColor(1.0,-1,1.05,"default",-1,-2),
mkColor(0.5,0,1.2,"default",-1,-2)]);
let cdEditVoice = -1, cdColor = 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
@@ -353,17 +334,25 @@ let matLoopEnd = 31;
function saveMatState() {
try { localStorage.setItem(MATRIX_STORAGE_KEY,
JSON.stringify({ grid: matGrid, inst: matInst, mod: matMod, cdef: matColorDefs, steps: matSteps, pose: matPoses })); } catch (_e) {}
JSON.stringify({ grid: matGrid, inst: matInst, 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;
if (Array.isArray(raw.cdef) && raw.cdef.length === 16) matColorDefs = raw.cdef;
if (Array.isArray(raw.steps) && raw.steps.length === 16) matSteps = raw.steps;
if (Array.isArray(raw.pose) && raw.pose.length === 16) matPoses = raw.pose;
if (Array.isArray(raw.cdef) && raw.cdef.length === 16) {
matColorDefs = raw.cdef;
for (let vi = 0; vi < 16; vi++) {
for (let c = 1; c <= 6; c++) {
if (matColorDefs[vi][c]) {
if (!matColorDefs[vi][c].steps) matColorDefs[vi][c].steps = new Array(16).fill(null);
if (!matColorDefs[vi][c].pose) matColorDefs[vi][c].pose = [];
if (matColorDefs[vi][c].mod === undefined) matColorDefs[vi][c].mod = null;
}
}
}
}
return; }
if (Array.isArray(raw) && raw.length === 16) { matGrid = raw; return; } // legacy: bare grid
} catch (_e) {}
@@ -387,6 +376,9 @@ function renderMatrix() {
const corner = document.createElement("span");
corner.className = "mvoice-label";
headerRow.appendChild(corner);
const ih = document.createElement("span");
ih.className = "minst-head";
headerRow.appendChild(ih);
const editHead = document.createElement("span");
editHead.className = "minst-edit-head";
headerRow.appendChild(editHead);
@@ -406,6 +398,13 @@ function renderMatrix() {
label.className = "mvoice-label";
label.textContent = MATRIX_VOICES[vi];
row.appendChild(label);
const vinst = document.createElement("select");
vinst.className = "minst"; vinst.dataset.vi = vi;
[["default","default"]].concat((MATRIX_INST_CHOICES[MATRIX_VOICES[vi]]||[]).map(s=>[s,s]))
.forEach(([val,txt])=>{const o=document.createElement("option");o.value=val;o.textContent=txt;vinst.appendChild(o);});
vinst.value = matInst[vi] || "default";
vinst.addEventListener("change", ()=>{ matInst[vi]=vinst.value; saveMatState(); send("/matrix/instrument", vi, vinst.value); });
row.appendChild(vinst);
const edit = document.createElement("button");
edit.className = "minst-edit"; edit.textContent = "..."; edit.dataset.vi = vi;
edit.addEventListener("click", () => openColorDef(vi));
@@ -442,128 +441,149 @@ function renderMatrix() {
}
// --- Color def modal ---
function renderColorDefRows(vi) {
document.getElementById("cd-title").textContent = MATRIX_VOICES[vi];
const rows = document.getElementById("cd-pane-colors");
rows.innerHTML = "";
function renderColorSel(vi) {
const sel = document.getElementById("cd-colorsel");
if (!sel) return;
sel.innerHTML = "";
const COLORS = ["#c22","#1a8","#25b","#990","#c60","#82a"];
for (let c = 1; c <= 6; c++) {
const btn = document.createElement("button");
btn.textContent = String(c);
btn.style.background = COLORS[c-1];
btn.style.borderColor = COLORS[c-1];
btn.classList.toggle("on", c === cdColor);
btn.addEventListener("click", () => { cdColor = c; renderColorSel(vi); renderPattern(vi); });
sel.appendChild(btn);
}
}
function renderPattern(vi) {
const p = document.getElementById("cd-pattern");
if (!p) return;
p.innerHTML = "";
const c = cdColor;
const d = matColorDefs[vi][c];
if (!d) return;
if (!d.steps) d.steps = new Array(16).fill(null);
if (!d.pose) d.pose = [];
if (d.mod === undefined) d.mod = null;
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);
}
}
const mk = (label, el) => { const w = document.createElement("label");
w.className = "cd-field"; w.textContent = label; w.appendChild(el); return w; };
function renderSeq(vi) {
const p = document.getElementById("cd-pane-seq"); p.innerHTML = "";
for (let c = 1; c <= 6; c++) {
const row = document.createElement("div"); row.className = "seq-row m" + c;
for (let s = 0; s < 16; s++) {
const cell = document.createElement("button"); cell.className = "seq-cell";
const st = matSteps[vi][c][s];
cell.textContent = st ? st.degree : "";
if (st) cell.classList.add("on");
cell.addEventListener("click", () => {
if (matSteps[vi][c][s]) { matSteps[vi][c][s] = null; send("/matrix/step", vi, c, s, -99, 0); }
else { matSteps[vi][c][s] = {degree:0, vel:0.8}; send("/matrix/step", vi, c, s, 0, 0.8); }
saveMatState(); renderSeq(vi);
});
cell.addEventListener("wheel", (e) => {
e.preventDefault(); const st2 = matSteps[vi][c][s]; if (!st2) return;
if (e.shiftKey) {
st2.vel = Math.max(0, Math.min(1, st2.vel + (e.deltaY < 0 ? 0.1 : -0.1)));
} else {
st2.degree = Math.max(-48, Math.min(48, st2.degree + (e.deltaY < 0 ? 1 : -1)));
}
send("/matrix/step", vi, c, s, st2.degree, st2.vel); saveMatState(); renderSeq(vi);
});
row.appendChild(cell);
}
p.appendChild(row);
}
}
function renderPoses(vi) {
if (!Array.isArray(matPoses[vi])) matPoses[vi] = [];
const p = document.getElementById("cd-pane-poses"); p.innerHTML = "";
const add = document.createElement("div");
const id = document.createElement("input"); id.placeholder = "poseId"; id.className = "pose-id";
const act = document.createElement("select");
["trigger","gate"].forEach(a=>{const o=document.createElement("option");o.value=a;o.textContent=a;act.appendChild(o);});
const col = document.createElement("select");
[1,2,3,4,5,6].forEach(c=>{const o=document.createElement("option");o.value=c;o.textContent="color "+c;col.appendChild(o);});
const go = document.createElement("button"); go.textContent = "+";
go.addEventListener("click", ()=>{
if (!id.value) return;
matPoses[vi] = matPoses[vi].filter(b=>b.poseId!==id.value).concat([{poseId:id.value, action:act.value, color:+col.value}]);
saveMatState(); send("/matrix/posebind", vi, id.value, act.value, +col.value); renderPoses(vi);
});
[id,act,col,go].forEach(e=>add.appendChild(e)); p.appendChild(add);
matPoses[vi].forEach(b=>{ const line=document.createElement("div");
line.textContent = `${b.poseId} -> ${b.action} color ${b.color}`; p.appendChild(line); });
}
function renderInstEdit(vi) {
// Instrument pane
const ip = document.getElementById("cd-pane-inst"); ip.innerHTML = "";
const sel = document.createElement("select"); sel.id = "cd-inst-sel";
// Instrument + variation row
const varRow = document.createElement("div"); varRow.className = "cd-row m" + c;
const inst = document.createElement("select");
[["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;sel.appendChild(o);});
sel.value = matInst[vi] || "default";
sel.addEventListener("change", ()=>{ matInst[vi]=sel.value; saveMatState(); send("/matrix/instrument", vi, sel.value); });
ip.appendChild(sel);
// Colors pane: reuse renderColorDefRows but target #cd-pane-colors
renderColorDefRows(vi);
// Sequence pane
renderSeq(vi);
// Poses pane
renderPoses(vi);
// Assign pane
const ap = document.getElementById("cd-pane-assign"); ap.innerHTML = "";
.forEach(([val,txt])=>{ const o=document.createElement("option"); o.value=val; o.textContent=txt; inst.appendChild(o); });
inst.value = d.inst || "default";
inst.addEventListener("change", () => setCD(c, "inst", inst.value));
const stretch = document.createElement("select");
[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");
[-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));
varRow.appendChild(mk("inst", inst)); varRow.appendChild(mk("stretch", stretch));
varRow.appendChild(mk("oct", oct)); varRow.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)); varRow.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)); varRow.appendChild(mk("pan", pan)); }
p.appendChild(varRow);
// 16-step row
const seqRow = document.createElement("div"); seqRow.className = "seq-row m" + c;
for (let s = 0; s < 16; s++) {
const cell = document.createElement("button"); cell.className = "seq-cell";
const st = d.steps[s];
cell.textContent = st ? st.degree : "";
if (st) cell.classList.add("on");
cell.addEventListener("click", () => {
if (d.steps[s]) { d.steps[s] = null; send("/matrix/step", vi, c, s, -99, 0); }
else { d.steps[s] = {degree:0, vel:0.8}; send("/matrix/step", vi, c, s, 0, 0.8); }
saveMatState(); renderPattern(vi);
});
cell.addEventListener("wheel", (e) => {
e.preventDefault(); const st2 = d.steps[s]; if (!st2) return;
if (e.shiftKey) {
st2.vel = Math.max(0, Math.min(1, st2.vel + (e.deltaY < 0 ? 0.1 : -0.1)));
} else {
st2.degree = Math.max(-48, Math.min(48, st2.degree + (e.deltaY < 0 ? 1 : -1)));
}
send("/matrix/step", vi, c, s, st2.degree, st2.vel); saveMatState(); renderPattern(vi);
});
seqRow.appendChild(cell);
}
p.appendChild(seqRow);
// Effect (mod) row
const modDiv = document.createElement("div"); modDiv.className = "cd-row m" + c;
const src = document.createElement("select");
MATRIX_MOD_SOURCES.forEach(s=>{const o=document.createElement("option");o.value=s;o.textContent=s;src.appendChild(o);});
MATRIX_MOD_SOURCES.forEach(s=>{ const o=document.createElement("option"); o.value=s; o.textContent=s; src.appendChild(o); });
const tgt = document.createElement("select");
(MATRIX_MOD_TARGETS[MATRIX_VOICES[vi]]||["none","amp"]).forEach(t=>{const o=document.createElement("option");o.value=t;o.textContent=t;tgt.appendChild(o);});
targets.forEach(t=>{ const o=document.createElement("option"); o.value=t; o.textContent=t; tgt.appendChild(o); });
const dep = document.createElement("input"); dep.type="range"; dep.min=0; dep.max=1; dep.step=0.01;
src.value=matMod[vi].source; tgt.value=matMod[vi].target; dep.value=matMod[vi].depth;
src.className="cd-mod-src"; tgt.className="cd-mod-tgt"; dep.className="cd-mod-dep";
const sm=()=>{matMod[vi]={source:src.value,target:tgt.value,depth:+dep.value};saveMatState();send("/matrix/mod",vi,src.value,tgt.value,+dep.value);};
src.addEventListener("change",sm); tgt.addEventListener("change",sm); dep.addEventListener("change",sm);
[src,tgt,dep].forEach(e=>ap.appendChild(e));
const mod = d.mod || {source:"none", target:"none", depth:0.5};
src.value = mod.source; tgt.value = mod.target; dep.value = mod.depth;
const sendMod = () => {
matColorDefs[vi][c].mod = {source:src.value, target:tgt.value, depth:+dep.value};
saveMatState(); send("/matrix/colormod", vi, c, src.value, tgt.value, +dep.value);
};
src.addEventListener("change", sendMod); tgt.addEventListener("change", sendMod); dep.addEventListener("change", sendMod);
modDiv.appendChild(mk("src", src)); modDiv.appendChild(mk("tgt", tgt)); modDiv.appendChild(mk("depth", dep));
p.appendChild(modDiv);
// Pose section
const poseDiv = document.createElement("div");
const addDiv = document.createElement("div");
const poseId = document.createElement("input"); poseId.placeholder = "poseId"; poseId.className = "pose-id";
const poseAct = document.createElement("select");
["trigger","gate"].forEach(a=>{ const o=document.createElement("option"); o.value=a; o.textContent=a; poseAct.appendChild(o); });
const poseGo = document.createElement("button"); poseGo.textContent = "+";
poseGo.addEventListener("click", () => {
if (!poseId.value) return;
const poses = (d.pose || []).filter(b => b.poseId !== poseId.value);
poses.push({poseId: poseId.value, action: poseAct.value});
matColorDefs[vi][c].pose = poses;
saveMatState(); send("/matrix/colorpose", vi, c, poses.length, ...poses.flatMap(b => [b.poseId, b.action]));
renderPattern(vi);
});
[poseId, poseAct, poseGo].forEach(e => addDiv.appendChild(e));
poseDiv.appendChild(addDiv);
(d.pose || []).forEach(b => {
const line = document.createElement("div");
line.textContent = `${b.poseId} -> ${b.action}`;
const del = document.createElement("button"); del.textContent = "x";
del.style.cssText = "width:auto;padding:2px 6px;font-size:10px;";
del.addEventListener("click", () => {
matColorDefs[vi][c].pose = (d.pose || []).filter(pp => pp.poseId !== b.poseId);
const poses = matColorDefs[vi][c].pose;
saveMatState(); send("/matrix/colorpose", vi, c, poses.length, ...poses.flatMap(pp => [pp.poseId, pp.action]));
renderPattern(vi);
});
line.appendChild(del); poseDiv.appendChild(line);
});
p.appendChild(poseDiv);
}
function openColorDef(vi) {
cdEditVoice = vi;
cdColor = 1;
document.getElementById("cd-title").textContent = MATRIX_VOICES[vi];
document.getElementById("cd-audition").classList.toggle("on", cdAuditioning);
document.querySelectorAll(".cd-tab").forEach(b => b.classList.toggle("on", b.dataset.tab === "inst"));
document.querySelectorAll(".cd-pane").forEach(p => { p.hidden = p.id !== "cd-pane-inst"; });
renderInstEdit(vi);
renderColorSel(vi);
renderPattern(vi);
document.getElementById("colordef-modal").hidden = false;
send("/matrix/colordefs/get", vi);
send("/matrix/steps/get", vi);
send("/matrix/poses/get", vi);
send("/matrix/colordefs/get/ext", vi);
}
function setCD(color, field, value) {
@@ -974,13 +994,6 @@ document.addEventListener("DOMContentLoaded", () => {
renderMixer();
document.getElementById("cd-close").addEventListener("click", closeColorDef);
document.querySelectorAll(".cd-tab").forEach(btn =>
btn.addEventListener("click", () => {
document.querySelectorAll(".cd-tab").forEach(b => b.classList.remove("on"));
btn.classList.add("on");
const tab = btn.dataset.tab;
document.querySelectorAll(".cd-pane").forEach(p => { p.hidden = p.id !== `cd-pane-${tab}`; });
}));
document.getElementById("cd-audition").addEventListener("click", () => {
if (cdEditVoice < 0) return;
cdAuditioning = !cdAuditioning;
+2 -12
View File
@@ -182,18 +182,8 @@
<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 class="cd-tabs">
<button class="cd-tab on" data-tab="inst">Instrument</button>
<button class="cd-tab" data-tab="colors">Colors</button>
<button class="cd-tab" data-tab="seq">Sequence</button>
<button class="cd-tab" data-tab="assign">Assign</button>
<button class="cd-tab" data-tab="poses">Poses</button>
</div>
<div id="cd-pane-inst" class="cd-pane"></div>
<div id="cd-pane-colors" class="cd-pane" hidden></div>
<div id="cd-pane-seq" class="cd-pane" hidden></div>
<div id="cd-pane-assign" class="cd-pane" hidden></div>
<div id="cd-pane-poses" class="cd-pane" hidden></div>
<div id="cd-colorsel" class="cd-colorsel"></div>
<div id="cd-pattern" class="cd-pattern"></div>
</div>
</div>
<script type="module">import * as G from "./matrix_glow.js"; window.MatrixGlow = G;</script>