Vanilla JS WS sender + pad armed-state toggle + OSC wiring. 10 pattern pads, tempo/filter ranges, concert/harmony/fx sections.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
* { box-sizing: border-box; } body { margin: 0; background: #111; color: #eee;
|
||||
font: 16px/1.3 -apple-system, system-ui, sans-serif; padding: 12px; }
|
||||
header { display: flex; align-items: center; gap: 10px; }
|
||||
h1 { font-size: 18px; margin: 4px 0; } h2 { font-size: 14px; color: #9af; margin: 16px 0 8px; text-transform: uppercase; letter-spacing: .06em; }
|
||||
#status { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
|
||||
#status.on { background: #4c8; } #status.off { background: #c44; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; }
|
||||
button { background: #222; color: #eee; border: 1px solid #333; border-radius: 10px;
|
||||
padding: 18px 10px; font-size: 16px; cursor: pointer; }
|
||||
button:active { background: #2a2a2a; } button.armed { background: #2b6; color: #061; border-color: #4d9; font-weight: 700; }
|
||||
button.wide { width: 100%; margin: 8px 0; background: #422; border-color: #633; }
|
||||
label { display: block; margin: 10px 0; } input[type=range] { width: 100%; height: 36px; }
|
||||
@@ -0,0 +1,31 @@
|
||||
const ws = new WebSocket(`ws://${location.host}/ws`);
|
||||
const dot = () => document.getElementById("status");
|
||||
ws.addEventListener("open", () => dot().className = "on");
|
||||
ws.addEventListener("close", () => dot().className = "off");
|
||||
function send(address, ...args) {
|
||||
if (ws.readyState === 1) ws.send(JSON.stringify({ address, args }));
|
||||
}
|
||||
const armed = new Set();
|
||||
function togglePad(el, name) {
|
||||
const on = !armed.has(name);
|
||||
on ? armed.add(name) : armed.delete(name);
|
||||
el.classList.toggle("armed", on);
|
||||
send("/launch", name, on ? 1 : 0);
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll("[data-pad]").forEach((el) =>
|
||||
el.addEventListener("click", () => togglePad(el, el.dataset.pad)));
|
||||
document.querySelectorAll("[data-osc]").forEach((el) =>
|
||||
el.addEventListener("click", () => {
|
||||
const a = el.dataset.arg;
|
||||
send(el.dataset.osc, ...(a === undefined ? [] : [isNaN(+a) ? a : +a]));
|
||||
}));
|
||||
const tempo = document.getElementById("tempo");
|
||||
tempo && tempo.addEventListener("input", () => send("/launch/tempo", +tempo.value));
|
||||
const filt = document.getElementById("filter");
|
||||
filt && filt.addEventListener("input", () => send("/control/fx/filter", +filt.value));
|
||||
document.getElementById("clear").addEventListener("click", () => {
|
||||
armed.clear(); document.querySelectorAll("[data-pad]").forEach((e) => e.classList.remove("armed"));
|
||||
send("/launch/clear");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
<!doctype html><html lang="fr"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>AV-Live — Control</title><link rel="stylesheet" href="control.css"></head>
|
||||
<body>
|
||||
<header><h1>AV-Live Control</h1><span id="status" class="off"></span></header>
|
||||
<section><h2>Patterns</h2>
|
||||
<div class="grid">
|
||||
<button data-pad="kick">Kick</button><button data-pad="hats">Hats</button>
|
||||
<button data-pad="clap">Clap</button><button data-pad="perc">Perc</button>
|
||||
<button data-pad="sub">Sub</button><button data-pad="acid">Acid</button>
|
||||
<button data-pad="arp">Arp</button><button data-pad="lead">Lead</button>
|
||||
<button data-pad="stab">Stab</button><button data-pad="pad">Pad</button>
|
||||
</div>
|
||||
<button id="clear" class="wide">Tout couper</button>
|
||||
<label>Tempo <input id="tempo" type="range" min="60" max="180" value="126"></label>
|
||||
</section>
|
||||
<section><h2>Concert</h2>
|
||||
<div class="grid">
|
||||
<button data-osc="/control/concertNext" data-arg="1">Morceau suivant</button>
|
||||
<button data-osc="/control/doScene" data-arg="concert">Scène concert</button>
|
||||
<button data-osc="/control/doScene" data-arg="play">Body-play</button>
|
||||
</div>
|
||||
</section>
|
||||
<section><h2>Harmonie</h2>
|
||||
<div class="grid">
|
||||
<button data-osc="/control/harmony/root" data-arg="-2">Root −</button>
|
||||
<button data-osc="/control/harmony/root" data-arg="2">Root +</button>
|
||||
<button data-osc="/control/harmony/scale" data-arg="minor">Minor</button>
|
||||
<button data-osc="/control/harmony/scale" data-arg="dorian">Dorian</button>
|
||||
<button data-osc="/control/harmony/scale" data-arg="phrygian">Phrygian</button>
|
||||
<button data-osc="/control/harmony/scale" data-arg="penta">Penta</button>
|
||||
<button data-osc="/control/harmony/octave" data-arg="0">Oct 0</button>
|
||||
<button data-osc="/control/harmony/octave" data-arg="1">Oct +1</button>
|
||||
<button data-osc="/control/harmony/phrase" data-arg="0">Phr 0</button>
|
||||
<button data-osc="/control/harmony/phrase" data-arg="1">Phr 1</button>
|
||||
<button data-osc="/control/harmony/phrase" data-arg="2">Phr 2</button>
|
||||
<button data-osc="/control/harmony/phrase" data-arg="3">Phr 3</button>
|
||||
</div>
|
||||
</section>
|
||||
<section><h2>FX</h2>
|
||||
<label>Filtre <input id="filter" type="range" min="0" max="1" step="0.01" value="1"></label>
|
||||
<div class="grid">
|
||||
<button data-osc="/control/fx/stutter">Stutter</button>
|
||||
<button data-osc="/control/fx/crash">Crash</button>
|
||||
<button data-osc="/control/fx/kick">Kick</button>
|
||||
<button data-osc="/control/fx/swell">Swell</button>
|
||||
<button data-osc="/control/fx/breakdown" data-arg="1">Breakdown ↓</button>
|
||||
<button data-osc="/control/fx/breakdown" data-arg="0">Breakdown ↑</button>
|
||||
</div>
|
||||
</section>
|
||||
<script src="control.js"></script></body></html>
|
||||
Reference in New Issue
Block a user