feat: web clip quantize and queued states
CI build oscope-of / build-check (push) Has been cancelled

Add /launch/quant selector (0.5/1/4/8 beats, default 1 mes)
to the LIVE tab. Update /armed/ handler to read 4-state int
(0=off 1=playing 2=queued-launch 3=queued-stop), apply .armed
and .queued classes accordingly, sync clipState map. Pad tap
now toggles based on last-known state. CSS blink animation for
.queued (600 ms ease-in-out infinite).
This commit is contained in:
L'électron rare
2026-06-28 14:46:52 +02:00
parent 582d955522
commit b071c31b86
3 changed files with 44 additions and 8 deletions
+13
View File
@@ -62,3 +62,16 @@ button.sel { background: #246; border-color: #4af; color: #8cf; }
border: 1px solid #333; border-radius: 4px; overflow: hidden; }
.vu-bar { height: 100%; width: 0%; background: linear-gradient(to right, #2b6, #9d4, #fa0);
border-radius: 4px; transition: width 0.08s linear; }
/* --- Quantize selector --- */
.quant-bar { display: flex; align-items: center; gap: 6px; margin: 8px 0; flex-wrap: wrap; }
.quant-label { font-size: 13px; color: #888; flex-shrink: 0; }
.quant-btn { width: auto; padding: 8px 14px; font-size: 14px; flex-shrink: 0; }
.quant-btn.active { background: #246; color: #8cf; border-color: #4af; font-weight: 700; }
/* --- Queued state blink --- */
@keyframes queued-blink {
from { opacity: 1; }
to { opacity: 0.35; }
}
button.queued { animation: queued-blink 600ms ease-in-out infinite alternate; }
+24 -8
View File
@@ -6,11 +6,16 @@ function send(address, ...args) {
if (ws.readyState === 1) ws.send(JSON.stringify({ address, args }));
}
const armed = new Set();
const clipState = new Map(); // name -> last-known state int: 0 off, 1 playing, 2 queued-launch, 3 queued-stop
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);
// Use last-known state for toggle direction; fall back to armed Set
const st = clipState.has(name) ? clipState.get(name) : (armed.has(name) ? 1 : 0);
// launch (1) when off or queued-stop; stop (0) when playing or queued-launch
const launch = (st === 0 || st === 3);
// optimistic feedback — authoritative update comes from /armed feedback
el.classList.add("queued");
if (launch) el.classList.add("armed");
send("/launch", name, launch ? 1 : 0);
}
// --- Feedback: engine -> browser ---
@@ -23,13 +28,16 @@ ws.addEventListener("message", (ev) => {
if (!msg || typeof msg.address !== "string") return;
const { address, args } = msg;
// /armed/<name> — engine is source of truth for arm state
// /armed/<name> — engine is source of truth; args[0] is a state int 0-3
// 0 = off, 1 = playing (solid), 2 = queued-launch (blink), 3 = queued-stop (blink)
if (address.startsWith("/armed/")) {
const name = address.slice(7); // strip "/armed/"
const isArmed = args[0] === 1;
if (isArmed) armed.add(name); else armed.delete(name);
const st = Number(args[0]);
clipState.set(name, st);
if (st >= 1) armed.add(name); else armed.delete(name);
document.querySelectorAll(`[data-pad="${name}"]`).forEach((btn) => {
btn.classList.toggle("armed", isArmed);
btn.classList.toggle("armed", st >= 1);
btn.classList.toggle("queued", st === 2 || st === 3);
});
return;
}
@@ -310,6 +318,14 @@ document.addEventListener("DOMContentLoaded", () => {
const filt = document.getElementById("filter");
filt && filt.addEventListener("input", () => send("/control/fx/filter", +filt.value));
// Quantize selector
document.querySelectorAll(".quant-btn").forEach(btn =>
btn.addEventListener("click", () => {
document.querySelectorAll(".quant-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
send("/launch/quant", +btn.dataset.quant);
}));
// Clear
document.getElementById("clear").addEventListener("click", () => {
armed.clear();
+7
View File
@@ -36,6 +36,13 @@
</div>
<button id="clear" class="wide">Tout couper</button>
<label>Tempo <input id="tempo" type="range" min="60" max="180" value="126"></label>
<div class="quant-bar">
<span class="quant-label">Quant</span>
<button class="quant-btn" data-quant="0.5">½</button>
<button class="quant-btn" data-quant="1">1</button>
<button class="quant-btn active" data-quant="4">1 mes</button>
<button class="quant-btn" data-quant="8">2 mes</button>
</div>
</section>
</div>