diff --git a/micro.html b/micro.html index 547db23..f8c0324 100644 --- a/micro.html +++ b/micro.html @@ -127,6 +127,9 @@ /*@BUILD:include:src/header.html@*/ +

PM‑µ Micro

+

Inline practice bar — patch your instrument through it (in one end, amp/headphones out the other) with the click mixed in. One clickable thumb‑roller, an amber 14‑segment display.

+
@@ -168,8 +171,9 @@
-
Roll = tempo · press = start / stop · hold & roll = switch track. - Instrument in one end, amp/headphones out the other — the click is mixed into your signal in the analog domain.
+
Roll = tempo · press = start / stop · hold & roll = switch track.
+ +/*@BUILD:include:src/progbox.html@*/ -
+ + +

Embed this widget elsewhere with one <div> + a script — + see the embed docs.

+ + /*@BUILD:include:src/footer.html@*/ diff --git a/player.html b/player.html index dcd3fa4..64d58a5 100644 --- a/player.html +++ b/player.html @@ -211,6 +211,9 @@ /*@BUILD:include:src/header.html@*/ +

PM‑1 Initial

+

The idealized concept render — a clean screen‑first player with a colour beat display, set‑list navigation, theming and a fullscreen landscape view.

+ @@ -487,8 +490,12 @@ if(location.hash && /(p|sl)=/.test(location.hash)) loadConfig(location.hash, tru if(!setlist){ setlist=BUILTIN[0]; idx=0; loadSetup(setlist.items[0]); } renderAll(); requestAnimationFrame(draw); +/*@BUILD:include:src/progbox.js@*/ -
+ + + /*@BUILD:include:src/footer.html@*/ diff --git a/showcase.html b/showcase.html index 71f5e13..41572b2 100644 --- a/showcase.html +++ b/showcase.html @@ -59,6 +59,9 @@ /*@BUILD:include:src/header.html@*/
+

PM‑S Showcase

+

A display‑piece metronome — the pendulum is an RGB light bar that combines every lane's subdivisions & accents; a printed tempo scale with a sliding weight sets the tempo.

+
@@ -70,7 +73,12 @@ moving RGB light. Drag the weight up/down (or scroll) to set tempo — the scale is printed on the bar, just like a wind‑up metronome. (No power switch: the real one starts when you lift it from its holder.)
-
+ /*@BUILD:include:src/progbox.html@*/ + + + + +

Embed this widget elsewhere with one <div> + a script — + see the embed docs.

+
/*@BUILD:include:src/footer.html@*/ @@ -125,7 +137,7 @@ const SAMPLES = {}; const state = { bpm:120, volume:0.85, running:false }; let meters = [], muteWindows = []; -function setBpm(v){ state.bpm = Math.max(30, Math.min(300, Math.round(v))); } +function setBpm(v){ state.bpm = Math.max(30, Math.min(300, Math.round(v))); if(window.progRefresh) progRefresh(); } function scheduler(){ const ahead = audioCtx.currentTime + SCHEDULE_AHEAD; for(const m of meters){ while(m.nextTime < ahead){ scheduleMeterTick(m, m.nextTime); m.nextTime += laneStepDur(m, m.tick); m.tick++; } } @@ -286,6 +298,9 @@ addEventListener("keydown",(e)=>{ { const ht=tracksFromHash(); if(ht) tracks=ht; } loadTrack(0); syncBtns(); requestAnimationFrame(draw); +window.currentProgramString = function(){ var t=tracks[trackIdx]||{}; return setupToPatch({bpm:state.bpm, volume:state.volume, lanes:t.lanes||[]}); }; +window.loadProgramString = function(plain){ var s=patchToSetup(plain); tracks=[{name:"Program", ...s}]; trackIdx=0; loadTrack(0); syncBtns(); }; +/*@BUILD:include:src/progbox.js@*/ /*@BUILD:include:src/chrome.js@*/ diff --git a/src/base.css b/src/base.css index cde1fff..284460c 100644 --- a/src/base.css +++ b/src/base.css @@ -73,3 +73,30 @@ details.spec .spec-body { padding:2px 16px 16px; } .about p { margin:0 0 10px; max-width:64ch; } /* page-only chrome (description, dimensioned views, loading panel) — hidden when embedded */ [data-embed] .pageonly { display:none !important; } + +/* ---- per-form-factor page: visible title + summary (plain view) ---- */ +.ff-title { font-size:20px; margin:6px 0 2px; text-align:center; color:var(--txt,#c7d0db); } +.ff-sum { max-width:60ch; margin:0 auto; text-align:center; color:var(--muted,#7f8b9a); font-size:13.5px; line-height:1.55; } +[data-embed] .ff-title, [data-embed] .ff-sum { display:none !important; } + +/* ---- per-device program I/O box (plain view) ---- */ +.progbox { width:100%; max-width:620px; margin:14px auto 0; display:flex; align-items:center; gap:9px; flex-wrap:wrap; + padding:9px 12px; border:1px solid var(--panel-bd,#2a313c); border-radius:11px; background:var(--panel-bg,#161b22); } +.progbox > label { flex:0 0 auto; font-size:10px; text-transform:uppercase; letter-spacing:.09em; color:var(--muted,#7f8b9a); } +.progbox input { flex:1; min-width:160px; background:var(--field-bg,#0e1116); color:var(--txt,#c7d0db); + border:1px solid var(--field-bd,#2a313c); border-radius:8px; padding:8px 10px; font-family:"Courier New",monospace; font-size:12.5px; } +.progbox input.err { border-color:#c0392b; } +.progbox button { flex:0 0 auto; background:var(--field-bg,#0e1116); color:var(--txt,#c7d0db); border:1px solid var(--field-bd,#2a313c); + border-radius:8px; padding:8px 12px; font-size:13px; cursor:pointer; } +.progbox button.primary { background:linear-gradient(180deg,#34c6ff,var(--cyan)); color:#04121b; border-color:transparent; font-weight:600; } +.progbox button:hover { border-color:var(--cyan); } +.progbox-msg { flex:1 1 100%; font-size:11.5px; color:var(--muted,#7f8b9a); min-height:1em; } +.progbox-msg.ok { color:#5fd08a; } .progbox-msg.bad { color:#ff8a7a; } +[data-embed] .progbox { display:none !important; } + +/* ---- "Show info" toggle + technical section ---- */ +.info-toggle { display:flex; align-items:center; justify-content:center; gap:8px; margin:18px auto 0; max-width:620px; + font-size:13px; color:var(--muted,#7f8b9a); cursor:pointer; } +.info-toggle input { width:15px; height:15px; cursor:pointer; } +[data-embed] .info-toggle { display:none !important; } +#techinfo[hidden] { display:none; } diff --git a/src/progbox.html b/src/progbox.html new file mode 100644 index 0000000..b5a2132 --- /dev/null +++ b/src/progbox.html @@ -0,0 +1,11 @@ + +
+ + + + + +
diff --git a/src/progbox.js b/src/progbox.js new file mode 100644 index 0000000..84c8bbf --- /dev/null +++ b/src/progbox.js @@ -0,0 +1,60 @@ +/* Per-device program I/O + "Show info" toggle — assembled into each form-factor page. + Uses the engine codec (patchToSetup / setupToPatch / codeToSetlist) for decode + lint. + Host page provides: window.currentProgramString() and window.loadProgramString(plain). + Exposes window.progRefresh() — call it after the device's program changes. When the + page is embedded it posts {type:'varasys-prog'} to the parent instead of touching the box. */ +(function () { + var box = document.getElementById("dProg"); + var msg = document.getElementById("dProgMsg"); + var embedded = document.documentElement.dataset.embed === "1"; + function setMsg(t, ok) { if (!msg) return; msg.textContent = t || ""; msg.classList.toggle("ok", !!ok && !!t); msg.classList.toggle("bad", !ok && !!t); } + function lint(text) { + text = (text || "").trim(); if (!text) return { ok: false, msg: "empty" }; + var m = text.match(/[#?&](p|sl)=([^&\s]+)/), kind = null, payload = text; + if (m) { kind = m[1]; try { payload = decodeURIComponent(m[2]); } catch (e) { payload = m[2]; } } + var b64 = /^[A-Za-z0-9_-]{12,}$/.test(payload) && !/[;:]/.test(payload); + try { + if (kind === "sl" || (kind !== "p" && b64)) { + var sl = codeToSetlist(payload); if (!sl.items || !sl.items.length) throw new Error("set-list code has no items"); + return { ok: true, plain: setupToPatch(sl.items[0]), msg: "decoded set list “" + sl.title + "” — item 1" }; + } + var s = patchToSetup(payload); if (!s.lanes.length) throw new Error("no lanes — try e.g. kick:4"); + return { ok: true, plain: setupToPatch(s), msg: s.lanes.length + " lane" + (s.lanes.length > 1 ? "s" : "") + " · " + s.bpm + " BPM" }; + } catch (e) { return { ok: false, msg: "✗ " + e.message }; } + } + var editing = false; + // report the current program: to the parent when embedded, else into the box. + // No-op on pages without a program hook (e.g. panel-based Teacher/Player). + window.progRefresh = function () { + if (!window.currentProgramString) return; + var p = ""; try { p = window.currentProgramString(); } catch (e) {} + if (embedded) { try { parent.postMessage({ type: "varasys-prog", patch: p }, "*"); } catch (e) {} return; } + if (box && !editing) { box.value = p; setMsg("", true); } + }; + if (box) { + box.addEventListener("focus", function () { editing = true; }); + box.addEventListener("blur", function () { editing = false; }); + box.addEventListener("input", function () { box.classList.remove("err"); }); + box.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); doLoad(); } }); + var loadBtn = document.getElementById("dProgLoad"); if (loadBtn) loadBtn.addEventListener("click", doLoad); + var copyBtn = document.getElementById("dProgCopy"); if (copyBtn) copyBtn.addEventListener("click", function () { + try { navigator.clipboard.writeText(box.value); copyBtn.textContent = "Copied!"; setTimeout(function () { copyBtn.textContent = "Copy"; }, 1200); } catch (e) { box.select(); } + }); + } + function doLoad() { + var r = lint(box.value); + if (!r.ok) { box.classList.add("err"); setMsg(r.msg, false); return; } + box.classList.remove("err"); box.value = r.plain; setMsg("✓ " + r.msg, true); + try { if (window.loadProgramString) window.loadProgramString(r.plain); } catch (e) { setMsg("✗ " + e.message, false); } + } + // "Show info" toggle — reveals the technical section; ?info=1 opens it checked + var tog = document.getElementById("infoToggle"), tech = document.getElementById("techinfo"); + if (tog) { + var extra = document.querySelectorAll(".tech"); // e.g. dimensioned views outside #techinfo + var apply = function (on) { if (tech) tech.hidden = !on; for (var i = 0; i < extra.length; i++) extra[i].hidden = !on; }; + var open = /[?&]info=1/.test(location.search); + tog.checked = open; apply(open); + tog.addEventListener("change", function () { apply(tog.checked); }); + } + window.progRefresh(); +})(); diff --git a/stage.html b/stage.html index 08d4c81..5a1ad82 100644 --- a/stage.html +++ b/stage.html @@ -111,6 +111,9 @@ /*@BUILD:include:src/header.html@*/ +

PM‑1 Stage

+

Foot‑pedal stompbox for the stage — hands‑free with two footswitches and an expression pedal, a big floor‑readable RGB beat light, instrument pass‑through with the click mixed in.

+
@@ -154,6 +157,8 @@
Stomp Tap to set tempo (hold = start/stop) · Next to change item (hold = previous) · an expression pedal sweeps tempo. Instrument passes through with the click mixed in (analog).
+/*@BUILD:include:src/progbox.html@*/ + -
+ + +

Embed this widget elsewhere with one <div> + a script — + see the embed docs.

+
+ /*@BUILD:include:src/footer.html@*/ diff --git a/teacher.html b/teacher.html index e1d75d4..e9a05a9 100644 --- a/teacher.html +++ b/teacher.html @@ -204,11 +204,14 @@ /*@BUILD:include:src/header.html@*/ +

PM‑1 Teacher

+

Full‑feature studio / lesson desk console — a colour TFT showing every lane, arcade buttons and a thumb‑roller, with your instrument running through and the click mixed in.

+
- -
+ +