feat: web clip quantize and queued states
CI build oscope-of / build-check (push) Has been cancelled
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:
@@ -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; }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user