Compare commits

..

No commits in common. "05ce1d5ce4225e1e1da859a845a7a5d0898fb0f9" and "e80ff9d5644b59a381b2d3654570aae3699f27a0" have entirely different histories.

8 changed files with 3 additions and 724 deletions

View file

@ -42,7 +42,7 @@ def build(name):
out.write_text(src) out.write_text(src)
return out.stat().st_size return out.stat().st_size
for name in ("index.html","editor.html","editor-beta.html","player.html","teacher.html","stage.html","micro.html","showcase.html","kit.html","explorer.html", for name in ("index.html","editor.html","editor-beta.html","player.html","teacher.html","stage.html","micro.html","showcase.html","kit.html",
"embed.html", "embed.html",
"info-editor.html","info-player.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html","info-kit.html","info-explorer.html"): "info-editor.html","info-player.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html","info-kit.html","info-explorer.html"):
print("built %s (%dKB)" % (name, build(name) // 1024)) print("built %s (%dKB)" % (name, build(name) // 1024))

View file

@ -40,7 +40,7 @@ fi
# stamp the version into the built copy only (source stays clean) # stamp the version into the built copy only (source stays clean)
echo "deployed v$BUILD -> $DEST_DIR" echo "deployed v$BUILD -> $DEST_DIR"
for f in index.html editor.html editor-beta.html player.html teacher.html stage.html micro.html showcase.html kit.html explorer.html \ for f in index.html editor.html editor-beta.html player.html teacher.html stage.html micro.html showcase.html kit.html \
embed.html \ embed.html \
info-editor.html info-player.html info-teacher.html info-stage.html info-micro.html info-showcase.html info-kit.html info-explorer.html; do info-editor.html info-player.html info-teacher.html info-stage.html info-micro.html info-showcase.html info-kit.html info-explorer.html; do
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f" sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f"

View file

@ -1,418 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>VARASYS PM_X-1 - Explorer (Pimoroni PIM744 / RP2350)</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
/* ?embed=1 -> strip site chrome + auto-size to the host */
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
document.documentElement.dataset.embed="1";
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
})();
</script>
<!--
PM_X-1 "Explorer" - the off-the-shelf Pimoroni Explorer Kit (PIM744, RP2350) as a
button-driven polymeter metronome. Mirrors the firmware (../pico-explorer/app.py)
visually: 320x240 landscape ST7789V + 6 side buttons (A/B/C left, X/Y/Z right) +
piezo. No touchscreen, no joystick, no RGB - just buttons and an on-screen run dot.
Shares src/engine.js with every other device.
-->
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bd:#2a313c; --device-bd:#33363c; --silk:#231b06; --cyan:#0AB3F7;
--panel-bg:#161b22; --field-bg:#0e1116; --field-bd:#2a313c; }
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bd:#d2dae4; --panel-bg:#ffffff; --field-bg:#f1f4f8; --field-bd:#d2dae4 }
body{ margin:0; min-height:100vh; padding:26px 14px 46px;
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); color:var(--txt);
display:flex; flex-direction:column; align-items:center; gap:16px }
a{ color:var(--link) }
/* Pimoroni-Explorer-style yellow PCB. Wider than tall to fit the 320x240 LCD + side buttons. */
.device{ width:100%; max-width:420px; position:relative; border-radius:14px; padding:12px 12px 14px;
background:
radial-gradient(rgba(0,0,0,.045) .6px, transparent .7px) 0 0/3px 3px,
linear-gradient(180deg, #e6c64b, #c19b25);
border:1px solid #8b6e1f;
box-shadow:0 26px 52px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.18) }
.pcbtop{ display:flex; align-items:center; justify-content:space-between; margin:0 4px 8px }
.dev-logo{ height:16px; filter:invert(15%) sepia(80%) saturate(360%) hue-rotate(355deg) brightness(35%) } /* tint dark on yellow PCB */
:root[data-theme="light"] .dev-logo{ filter:invert(8%) sepia(60%) saturate(0%) brightness(45%) }
.silk{ display:flex; align-items:center; gap:7px; color:var(--silk) }
.silk .model{ font-size:8.5px; text-transform:uppercase; letter-spacing:.16em; opacity:.85 }
.pin{ font-size:7.5px; color:var(--silk); letter-spacing:.12em; text-transform:uppercase; opacity:.7 }
/* main body: side button columns flanking a landscape LCD */
.body{ display:grid; grid-template-columns:auto 1fr auto; gap:9px; align-items:stretch }
.lcol, .rcol{ display:flex; flex-direction:column; justify-content:space-between; gap:6px; padding:6px 0 }
/* the buttons are coloured caps Pimoroni-style: A red, B amber, C teal, X violet, Y yellow, Z blue */
.ebtn{ width:46px; padding:10px 0 6px; border-radius:10px; cursor:pointer;
border:1px solid rgba(0,0,0,.35); color:#0b0e12; font-size:13px; font-weight:900; letter-spacing:.04em; text-transform:uppercase;
box-shadow:0 3px 5px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.4) }
.ebtn small{ display:block; margin-top:2px; font-size:7.5px; font-weight:700; opacity:.7; letter-spacing:.06em }
.ebtn:active{ transform:translateY(2px); box-shadow:0 1px 2px rgba(0,0,0,.3) }
#btnA{ background:radial-gradient(circle at 40% 30%, #ff8a8a, #db3838 70%, #9c1d1d) }
#btnB{ background:radial-gradient(circle at 40% 30%, #ffd084, #ec8a18 70%, #a55b06) }
#btnC{ background:radial-gradient(circle at 40% 30%, #8be3c8, #1faa86 70%, #0a6a52) }
#btnX{ background:radial-gradient(circle at 40% 30%, #c1aaff, #794ee0 70%, #4a23a6) }
#btnY{ background:radial-gradient(circle at 40% 30%, #fff19a, #e6c91a 70%, #9b8505); color:#1a1500 }
#btnZ{ background:radial-gradient(circle at 40% 30%, #98c9ff, #2a83f0 70%, #134a98) }
/* the ST7789V LCD: 320x240 landscape, deep black bezel */
.screen-wrap{ padding:6px; border-radius:8px;
background:linear-gradient(180deg,#05070a,#020406); border:1px solid #04060a;
box-shadow:inset 0 2px 8px rgba(0,0,0,.85), 0 1px 0 rgba(255,255,255,.06) }
#screen{ display:block; width:100%; height:auto; border-radius:4px; background:#06080c; image-rendering:pixelated }
/* PCB footer: piezo + breadboard suggestion + silk */
.pcbbot{ display:flex; align-items:center; justify-content:space-between; margin:10px 4px 0; padding:8px 4px 2px;
border-top:1px dashed rgba(0,0,0,.18); color:var(--silk) }
.piezo{ width:22px; height:22px; border-radius:50%; background:radial-gradient(circle at 50% 40%, #3a3a3a, #0c0c0c); border:2px solid #5b4a14; position:relative }
.piezo::after{ content:""; position:absolute; left:50%; top:50%; width:5px; height:5px; margin:-2.5px 0 0 -2.5px; border-radius:50%; background:#05070a }
.breadboard{ flex:1; height:14px; margin:0 12px; border-radius:3px; background:
repeating-linear-gradient(0deg, rgba(0,0,0,.18) 0 2px, transparent 2px 4px),
linear-gradient(180deg, #f6e8a3, #d8be57);
border:1px solid rgba(0,0,0,.22) }
.pcbbot .silk-tag{ font-size:8px; letter-spacing:.14em; text-transform:uppercase; opacity:.8 }
.hint{ max-width:420px; text-align:center; font-size:11px; color:var(--muted); line-height:1.55 }
[data-embed] .hint{ display:none !important }
</style>
</head>
<body>
/*@BUILD:include:src/header.html@*/
<h1 class="ff-title">PM_X1 Explorer</h1>
<p class="ff-sum">Offtheshelf — the Pimoroni Explorer Kit (RP2350, 2.8″ LCD, 6 buttons, piezo) as a buttondriven sibling to the PM_K1 Kit. Edit grooves on the web with <b>Live sync</b>; the device mirrors play/stop/tempo/track changes both ways.</p>
<div class="device">
<div class="pcbtop">
<div class="silk"><img class="dev-logo" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" /><span class="model">PM_X1 Explorer</span></div>
<span class="pin">RP2350 · USBC</span>
</div>
<div class="body">
<div class="lcol">
<button class="ebtn" id="btnA" type="button">A<small>play</small></button>
<button class="ebtn" id="btnB" type="button">B<small>tap</small></button>
<button class="ebtn" id="btnC" type="button">C<small>list</small></button>
</div>
<div class="screen-wrap"><canvas id="screen" width="320" height="240" aria-label="metronome display"></canvas></div>
<div class="rcol">
<button class="ebtn" id="btnX" type="button">X<small>prev</small></button>
<button class="ebtn" id="btnY" type="button">Y<small>-bpm</small></button>
<button class="ebtn" id="btnZ" type="button">Z<small>next</small></button>
</div>
</div>
<div class="pcbbot">
<div class="piezo" title="Piezo speaker"></div>
<div class="breadboard" title="Mini breadboard (sensors / I/O)"></div>
<span class="silk-tag">Pimoroni Explorer · PIM744</span>
</div>
</div>
<div class="hint">All hardware, no touch: <b>A</b> = play/stop, <b>B</b> = tap, <b>C</b> = switch playlist.
<b>X</b> / <b>Z</b> = prev / next track; <b>Y</b> = tempo 1 (hold for 5). <b>X+Z</b> chord = tempo +1.
Hold buttons to repeat. Keyboard: A / B / C / X / Y / Z, space = play.</div>
/*@BUILD:include:src/progbox.html@*/
<p class="ff-link pageonly"><a href="/info-explorer.html">Wiring, parts &amp; firmware to flash →</a></p>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id);
/* ========================= ENGINE (shared; synth voices only) ================= */
const SAMPLES = {};
/*@BUILD:include:src/engine.js@*/
/*@BUILD:include:src/setlists.js@*/
const state = { bpm:120, volume:0.85, running:false };
let meters = [], muteWindows = [];
function setBpm(v){ state.bpm = Math.max(5, 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++; } }
}
function buildMeters(lanes){
return (lanes||[]).map(c=>{ const p=parseGroups(c.groupsStr);
return {groupsStr:c.groupsStr,groups:p.groups,beatsPerBar:p.beatsPerBar,groupStarts:p.groupStarts,
stepsPerBeat:c.stepsPerBeat||1,sound:c.sound,beatsOn:(c.beatsOn||[]).slice(),poly:!!c.poly,swing:!!c.swing,enabled:c.enabled!==false,gainDb:c.gainDb||0,
tick:0,nextTime:0,vq:[],vqPtr:0,currentStep:-1,currentBar:0}; });
}
function startAudio(){
ensureAudio(); audioCtx.resume(); state.running=true;
const t0=audioCtx.currentTime+0.08;
for(const m of meters){ m.tick=0; m.nextTime=t0; m.vq=[]; m.vqPtr=0; m.currentStep=-1; }
muteWindows=[];
schedulerTimer=setInterval(scheduler,LOOKAHEAD_MS); scheduler();
}
function stopAudio(){ state.running=false; clearInterval(schedulerTimer); schedulerTimer=null; for(const m of meters) m.currentStep=-1; }
function toggle(){ state.running ? stopAudio() : startAudio(); }
/* ========================= TRACKS (seed grooves, with set-list grouping for C) === */
let tracks = SEED_SETLISTS.flatMap(sl => sl.items.map(([n,p]) => ({ name:n, sl: sl.title, ...patchToSetup(p) })));
let trackIdx = 0;
function tracksFromHash(){
const m=(location.hash||"").match(/[#&](p|sl)=([^&]+)/); if(!m) return null;
let payload=m[2]; try{ payload=decodeURIComponent(payload); }catch(e){}
try{
if(m[1]==="sl"){ const sl=codeToSetlist(payload); return sl.items.length ? sl.items.map(it=>({...it, sl: sl.title})) : null; }
const s=patchToSetup(payload); return s.lanes.length ? [{name:"Patch", sl:"Patch", ...s}] : null;
}catch(e){ return null; }
}
function loadTrack(i){
const n=tracks.length; if(!n) return; trackIdx=((i%n)+n)%n;
const t=tracks[trackIdx]; setBpm(t.bpm||120); meters=buildMeters(t.lanes);
const was=state.running; if(was){ clearInterval(schedulerTimer); schedulerTimer=null; state.running=false; }
if(was) startAudio();
}
// C cycles to the FIRST item of the next setlist (matches the firmware's set-list-tab swap)
function nextSetlist(){
const cur = tracks[trackIdx].sl;
for(let i=1; i<=tracks.length; i++){
const j = (trackIdx + i) % tracks.length;
if(tracks[j].sl !== cur){ loadTrack(j); return; }
}
}
/* ========================= SCREEN (canvas mirrors the firmware UI at 320x240) ==== */
const cv=$("screen"), g=cv.getContext("2d"), SW=320, SH=240;
(function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1)); cv.width=SW*dpr; cv.height=SH*dpr; g.scale(dpr,dpr); })();
const COL={ bg:"#06090e", txt:"#c7d0db", mute:"#788494", cyan:"#0AB3F7", amber:"#ff9b2e",
violet:"#967bff", green:"#2fe07a", dim:"#243240", btn:"#1c222c", panel:"#1C222C",
runIdle:"#2fe07a", runGo:"#ff5a5a", runPulse:"#ffec78", grid:"#1a2330" };
const PRIO={2:3,1:2,3:1};
let runPulse=0, beatIdx=-1, segStart=0;
function drawScreen(){
g.fillStyle=COL.bg; g.fillRect(0,0,SW,SH);
// ----- header (y 0..28) -----
g.textBaseline="alphabetic"; g.textAlign="left";
g.fillStyle=COL.cyan; g.font="700 14px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText("VARASYS",10,20);
g.fillStyle=COL.mute; g.font="600 9px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText("v"+(window.APP_VERSION||"0.0.1"),74,20);
// run dot (top-right corner; replaces the Kit's WS2812 RGB LED)
const dotX = SW-14, dotY = 13;
let dotCol = state.running ? COL.runGo : COL.runIdle;
if(runPulse > 0.02){
// 3-channel blend dotCol -> runPulse weighted by runPulse value
const t = runPulse;
const lerp = (a, b) => Math.round(a*(1-t) + b*t);
const rA = parseInt(dotCol.slice(1,3),16), gA = parseInt(dotCol.slice(3,5),16), bA = parseInt(dotCol.slice(5,7),16);
const rB = parseInt(COL.runPulse.slice(1,3),16), gB = parseInt(COL.runPulse.slice(3,5),16), bB = parseInt(COL.runPulse.slice(5,7),16);
dotCol = "#" + lerp(rA,rB).toString(16).padStart(2,"0") + lerp(gA,gB).toString(16).padStart(2,"0") + lerp(bA,bB).toString(16).padStart(2,"0");
}
g.fillStyle = dotCol;
g.beginPath(); g.arc(dotX, dotY, 4, 0, Math.PI*2); g.fill();
// MIDI / USB badges (small markers; mirror the firmware's icon spots)
g.fillStyle = COL.dim;
g.fillRect(dotX-22, dotY-5, 10, 10);
g.fillRect(dotX-38, dotY-5, 10, 10);
// divider
g.fillStyle=COL.panel; g.fillRect(0,28,SW,1);
// ----- setlist tab + CONT (y ~ 32..44) -----
const t = tracks[trackIdx] || {};
const slLabel = (t.sl || "Set list").slice(0,18) + " " + (trackIdx+1) + "/" + tracks.length;
g.fillStyle=COL.mute; g.font="600 11px 'Segoe UI',Roboto,Arial,sans-serif"; g.fillText(slLabel,10,42);
g.textAlign="right"; g.fillStyle=COL.dim; g.fillText("CONT",SW-10,42);
// track name (y 48..60)
g.textAlign="left"; g.fillStyle=COL.txt; g.font="700 14px 'Segoe UI',Roboto,Arial,sans-serif";
g.fillText((t.name||"-").slice(0,22),10,60);
// ----- BPM big (right) + time/bar (left) (y 56..96) -----
g.textAlign="right"; g.fillStyle=COL.txt; g.font="800 38px 'Segoe UI',Roboto,Arial,sans-serif";
g.fillText(String(state.bpm),SW-10,86);
g.textAlign="left"; g.fillStyle=COL.txt; g.font="600 11px 'Segoe UI',Roboto,Arial,sans-serif";
const seg = state.running ? Math.max(0, (audioCtx ? audioCtx.currentTime - audioLatency() : 0) - segStart) : 0;
const fmt = s => { s = Math.floor(s); return Math.floor(s/60) + ":" + (s%60).toString().padStart(2,"0"); };
g.fillText(fmt(seg), 10, 72);
const m0 = meters[0]; const mlen = m0 ? m0.beatsPerBar*(m0.stepsPerBeat||1) : 1;
const bar = state.running && m0 ? Math.max(1, Math.floor((m0.tick-1)/mlen) + 1) : "-";
g.fillStyle=COL.mute; g.fillText("bar " + bar, 10, 88);
// ----- pad grid (y 100..240) -----
const top = 100, gridH = SH - top - 6;
const n = Math.min(meters.length, 6);
if(n === 0) return;
const rowh = Math.min(22, Math.floor(gridH / n));
const px0 = 58, usable = SW - 8 - px0 - 8;
// vertical gridlines at the master lane's beats
const M = meters[0];
const mbeats = Math.max(1, Math.floor(M.beatsPerBar));
g.fillStyle = COL.grid;
for(let bc=0; bc<mbeats; bc++){
const xc = px0 + 6 + Math.floor((bc * usable) / mbeats);
g.fillRect(xc, top, 1, n * rowh);
}
for(let li=0; li<n; li++){
const L = meters[li];
const y = top + li * rowh, cy = y + Math.floor(rowh / 2);
g.fillStyle = COL.mute; g.font="600 10px 'Segoe UI',Roboto,Arial,sans-serif"; g.textAlign="left";
g.fillText((L.sound||"?").slice(0,7), 6, cy + 3);
const steps = (L.beatsOn || []).length || L.beatsPerBar * L.stepsPerBeat;
const stepw = Math.max(1, Math.floor(usable / steps));
const side = Math.max(4, Math.min(12, stepw - 1, rowh - 6));
const rad = Math.max(2, Math.min(Math.floor(side/2), Math.floor(stepw/2) - 1));
const sub = L.stepsPerBeat || 1;
for(let s=0; s<steps; s++){
const cxp = px0 + 6 + Math.floor((s * usable) / steps);
const lvl = (L.beatsOn[s]|0); // 0=mute 1=normal 2=accent 3=ghost
const lit = state.running && (L.currentStep === s);
let col;
if(lvl === 0) col = lit ? "#39414D" : "#10161E";
else if(lvl === 2) col = lit ? COL.amber : "#4A3010";
else if(lvl === 3) col = lit ? COL.violet : "#2A1D4A";
else col = lit ? COL.cyan : "#0A3A52";
g.fillStyle = col;
if(s % sub === 0){
g.fillRect(cxp - Math.floor(side/2), cy - Math.floor(side/2), side, side);
} else {
g.beginPath(); g.arc(cxp, cy, rad, 0, Math.PI*2); g.fill();
}
}
}
}
function audioLatency(){ return audioCtx ? (audioCtx.outputLatency || audioCtx.baseLatency || 0) : 0; }
function frame(){
const now = audioCtx ? audioCtx.currentTime - audioLatency() : 0;
if(audioCtx && state.running){
let fired=[];
for(const m of meters){
while(m.vqPtr<m.vq.length && m.vq[m.vqPtr].time<=now){
const e=m.vq[m.vqPtr]; m.currentStep=e.step; m.currentBar=e.bar;
const lvl=m.beatsOn[e.step]|0; if(lvl>0) fired.push(lvl);
if(m===meters[0] && e.step % m.stepsPerBeat===0) beatIdx = e.step/m.stepsPerBeat;
m.vqPtr++;
}
if(m.vqPtr>512){ m.vq=m.vq.slice(m.vqPtr); m.vqPtr=0; }
}
if(fired.length){ runPulse=1; }
}
runPulse=Math.max(0, runPulse - 0.08);
drawScreen();
requestAnimationFrame(frame);
}
/* ========================= INPUTS (6 buttons + hold-repeat + X+Z chord) =========== */
let taps=[];
function tapTempo(){ const now=performance.now(); taps.push(now); taps=taps.filter(x=>now-x<2400);
if(taps.length>=2){ let s=0; for(let i=1;i<taps.length;i++) s+=taps[i]-taps[i-1];
const bpm=Math.round(60000/(s/(taps.length-1))); if(bpm>=5&&bpm<=300) setBpm(bpm); } }
const held = { X:0, Y:0, Z:0 }; // press start time (ms); 0 = released
const repNext = { X:0, Y:0, Z:0 }; // next auto-repeat deadline
const REPEAT_FIRST = 350, REPEAT_NEXT = 120, FAST_AFTER = 1500; // ms thresholds
let chordXZ = 0; // 0 = not in chord; else press start (ms)
function doX(){ loadTrack(trackIdx - 1); }
function doZ(){ loadTrack(trackIdx + 1); }
function doY(){
const fast = held.Y && (performance.now() - held.Y) > FAST_AFTER;
setBpm(state.bpm + (fast ? -5 : -1));
}
function doChordUp(){
const fast = chordXZ && (performance.now() - chordXZ) > FAST_AFTER;
setBpm(state.bpm + (fast ? 5 : 1));
}
function bindBtn(id, downFn, upFn){
const b = $(id);
b.addEventListener("pointerdown", (e) => { e.preventDefault(); b.setPointerCapture?.(e.pointerId); downFn(); });
b.addEventListener("pointerup", (e) => { e.preventDefault(); upFn(); });
b.addEventListener("pointercancel", () => upFn());
b.addEventListener("pointerleave", () => { if (b.hasPointerCapture?.(0)) upFn(); });
}
bindBtn("btnA", () => toggle(), () => {});
bindBtn("btnB", () => tapTempo(), () => {});
bindBtn("btnC", () => nextSetlist(), () => {});
function pressX(){
const now = performance.now();
if(held.Z && (now - held.Z) < 100){ chordXZ = Math.min(held.Z, now); doChordUp(); }
else { doX(); }
held.X = now; repNext.X = now + REPEAT_FIRST;
}
function pressZ(){
const now = performance.now();
if(held.X && (now - held.X) < 100){ chordXZ = Math.min(held.X, now); doChordUp(); }
else { doZ(); }
held.Z = now; repNext.Z = now + REPEAT_FIRST;
}
function pressY(){
const now = performance.now(); held.Y = now; repNext.Y = now + REPEAT_FIRST; doY();
}
function releaseX(){ held.X = 0; if(!held.Z) chordXZ = 0; }
function releaseZ(){ held.Z = 0; if(!held.X) chordXZ = 0; }
function releaseY(){ held.Y = 0; }
bindBtn("btnX", pressX, releaseX);
bindBtn("btnY", pressY, releaseY);
bindBtn("btnZ", pressZ, releaseZ);
// hold-repeat loop for X / Y / Z
setInterval(() => {
const now = performance.now();
if(held.X && !held.Z && now >= repNext.X){ repNext.X = now + REPEAT_NEXT; doX(); }
if(held.Z && !held.X && now >= repNext.Z){ repNext.Z = now + REPEAT_NEXT; doZ(); }
if(held.X && held.Z && now >= Math.max(repNext.X, repNext.Z)){
repNext.X = repNext.Z = now + REPEAT_NEXT; doChordUp();
}
if(held.Y && now >= repNext.Y){ repNext.Y = now + REPEAT_NEXT; doY(); }
}, 30);
/* ========================= KEYBOARD ============================================ */
addEventListener("keydown", (e) => {
const tag = e.target ? e.target.tagName : ""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
const k = e.key.toLowerCase();
if(e.key === " "){ e.preventDefault(); toggle(); }
else if(k === "a"){ toggle(); }
else if(k === "b"){ tapTempo(); }
else if(k === "c"){ nextSetlist(); }
else if(k === "x"){ pressX(); }
else if(k === "y"){ pressY(); }
else if(k === "z"){ pressZ(); }
});
addEventListener("keyup", (e) => {
const k = e.key.toLowerCase();
if(k === "x") releaseX();
else if(k === "y") releaseY();
else if(k === "z") releaseZ();
});
/* theme toggle + version */
/*@BUILD:include:src/chrome.js@*/
/* ========================= INIT ============================================== */
{ const ht = tracksFromHash(); if(ht) tracks = ht; }
loadTrack(0);
requestAnimationFrame(frame);
// reset segment timer on play; rolls into draw_meters' "X of TOTAL"
const _origStart = startAudio;
startAudio = function(){ segStart = audioCtx ? audioCtx.currentTime + 0.08 : 0; _origStart(); };
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", sl:"Program", ...s}]; trackIdx = 0; loadTrack(0); };
/*@BUILD:include:src/progbox.js@*/
</script>
/*@BUILD:include:src/footer.html@*/
</body>
</html>

View file

@ -1,141 +0,0 @@
#!/usr/bin/env python3
"""PM_K-1 power tree (SKiDL): USB 5V -> +/-18V switcher -> ultra-low-noise +/-15V LDOs,
plus the digital 3V3 rail.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/power_tree.py
Outputs ERC + hardware/kicad/power_tree.net.
WHY THIS SHAPE
A switching converter is noisy; audio op-amps need quiet rails. So the TPS65131 makes
RAW +/-18V (efficiently, from USB 5V), then TPS7A4901/TPS7A3001 ultra-low-noise LDOs
drop that to CLEAN +/-15V for the analog section. 3V3 (digital) comes from a simple LDO.
VERIFIED FROM DATASHEETS (topology + values, not guessed)
* TPS65131 (TI SLVS493E): pinout p.3; Typical Application Fig 8-1 (p.11) gives the
L/D/C topology; Table 8-2 (p.12) the BOM; FB equations p.13 (Vref=1.213V):
Vpos=Vref(1+R1/R2); Vneg=-Vref*R3/R4. Inductors 4.7uH; D1/D2=MBRM120 Schottky.
We target ~+/-18V (LDO headroom): R1=1.4M/R2=100k -> +18.2V ; R3=1.5M/R4=100k -> -18.2V.
No Q1 battery switch (USB powered) -> BSW left open. PSP/PSN tied LOW = forced PWM
(constant-frequency, cleaner in the audio band) -- a deliberate change from the
battery-oriented example.
* TPS7A4901 / TPS7A3001 (8-pin, identical pinout 1=OUT 2=FB 3=NC 4=GND 5=EN 6=NR/SS
7=DNC 8=IN + PowerPAD): Vout=Vfb(1+Rt/Rb). Vfb approx +1.194V / -1.18V -- CONFIRM the
exact Vfb in each elec-char table before finalizing the divider.
* AP2112K-3.3 SOT-23-5 (1=VIN 2=GND 3=EN 4=NC 5=VOUT) -- standard pinout, confirm.
No SPICE here: a switcher is validated against TI's reference design + bench measurement,
not a behavioral op-amp model. Layout (switch nodes, ground return) is critical -- follow
the TI EVM/layout guidance.
"""
import os
from skidl import *
set_default_tool(KICAD9)
P = Pin.types
R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
def Cp(v, fp="Capacitor_SMD:C_0805_2012Metric"):
return Part("Device","C", value=v, footprint=fp)
L = Part("Device","L", dest=TEMPLATE, footprint="Inductor_SMD:L_Wuerth_WE-PD_Typ4_M")
DS = Part("Device","D_Schottky", dest=TEMPLATE, footprint="Diode_SMD:D_SMA")
FB = Part("Device","L", dest=TEMPLATE, footprint="Inductor_SMD:L_0805_2012Metric") # ferrite bead
def mk(name, pins, fp, ref="U"):
return Part(name=name, tool=SKIDL, dest=TEMPLATE, ref_prefix=ref, footprint=fp, pins=pins)
# ---- nets ----
vbus, p5, p18, n18, p15, n15, p3v3, gnd, vref = (Net("VBUS_5V"), Net("+5V"), Net("+18V"),
Net("-18V"), Net("+15V"), Net("-15V"), Net("+3V3"), Net("GND"), Net("VREF"))
for n in (vbus, p5, p18, n18, p15, n15, p3v3): n.drive = POWER
gnd.drive = POWER
sw_boost, sw_inv, fbp, fbn = Net("SW_BOOST"), Net("SW_INV"), Net("FBP"), Net("FBN")
innf = Net("INN_F") # RC-filtered +5V to the inverter input
# ---- parts ----
TPS65131 = mk("TPS65131",
[Pin(num=1,name="INP",func=P.PASSIVE),Pin(num=24,name="INP2",func=P.PASSIVE),
Pin(num=2,name="PGND",func=P.PWRIN),Pin(num=3,name="PGND2",func=P.PWRIN),
Pin(num=4,name="VIN",func=P.PWRIN),Pin(num=5,name="INN",func=P.PASSIVE),Pin(num=6,name="INN2",func=P.PASSIVE),
Pin(num=7,name="BSW",func=P.OUTPUT),Pin(num=8,name="ENP",func=P.INPUT),Pin(num=9,name="PSP",func=P.INPUT),
Pin(num=10,name="ENN",func=P.INPUT),Pin(num=11,name="PSN",func=P.INPUT),
Pin(num=12,name="NC12",func=P.NOCONNECT),Pin(num=20,name="NC20",func=P.NOCONNECT),
Pin(num=13,name="OUTN",func=P.PASSIVE),Pin(num=14,name="OUTN2",func=P.PASSIVE),
Pin(num=15,name="VNEG",func=P.INPUT),Pin(num=16,name="FBN",func=P.INPUT),Pin(num=17,name="VREF",func=P.PWROUT),
Pin(num=18,name="CN",func=P.PASSIVE),Pin(num=19,name="AGND",func=P.PWRIN),Pin(num=21,name="CP",func=P.PASSIVE),
Pin(num=22,name="FBP",func=P.INPUT),Pin(num=23,name="VPOS",func=P.INPUT),
Pin(num=25,name="EP",func=P.PWRIN)],
"Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm")
# LDOs share one pinout (HVSSOP-8 PowerPAD)
def ldo(name):
return mk(name, [Pin(num=1,name="OUT",func=P.PWROUT),Pin(num=2,name="FB",func=P.INPUT),
Pin(num=3,name="NC",func=P.NOCONNECT),Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="EN",func=P.INPUT),
Pin(num=6,name="NR",func=P.PASSIVE),Pin(num=7,name="DNC",func=P.NOCONNECT),Pin(num=8,name="IN",func=P.PWRIN),
Pin(num=9,name="EP",func=P.PWRIN)], "Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm")
AP2112 = mk("AP2112K-3.3", [Pin(num=1,name="VIN",func=P.PWRIN),Pin(num=2,name="GND",func=P.PWRIN),
Pin(num=3,name="EN",func=P.INPUT),Pin(num=4,name="NC",func=P.NOCONNECT),Pin(num=5,name="VOUT",func=P.PWROUT)],
"Package_TO_SOT_SMD:SOT-23-5")
u7 = TPS65131(ref="U7"); u8 = ldo("TPS7A4901")(ref="U8"); u9 = ldo("TPS7A3001")(ref="U9"); u10 = AP2112(ref="U10")
l1, l2 = L(value="4.7uH"), L(value="4.7uH")
d1, d2 = DS(value="MBRM120"), DS(value="MBRM120")
# ---- USB input: ferrite + bulk ----
fb1 = FB(value="600R@100MHz"); vbus += fb1[1]; fb1[2] += p5
cbulk = Cp("10uF","Capacitor_SMD:C_1206_3216Metric"); p5 += cbulk[1]; cbulk[2] += gnd
# ---- TPS65131: control supply + grounds + enables ----
u7["VIN"] += p5; c2 = Cp("4.7uF"); p5 += c2[1]; c2[2] += gnd
u7["PGND"] += gnd; u7["PGND2"] += gnd; u7["AGND"] += gnd; u7["EP"] += gnd
u7["ENP"] += p5; u7["ENN"] += p5 # enabled
u7["PSP"] += gnd; u7["PSN"] += gnd # forced PWM (low audio-band noise)
# BSW, NC left unconnected (no battery switch)
# boost: +5V -> L1 -> INP(switch); C1 at inductor input; D1 INP->+18V; C4 on +18V
c1 = Cp("4.7uF"); p5 += c1[1]; c1[2] += gnd
p5 += l1[1]; l1[2] += sw_boost; u7["INP"] += sw_boost; u7["INP2"] += sw_boost
d1[2] += sw_boost; d1[1] += p18 # Device D_Schottky: pin1=K, pin2=A -> anode at SW, cathode at +18V
c4 = Cp("22uF","Capacitor_SMD:C_1206_3216Metric"); p18 += c4[1]; c4[2] += gnd
u7["VPOS"] += p18
# positive feedback divider (+18.2V): R1 +18->FBP (C9 feed-forward), R2 FBP->gnd
r1 = R(value="1.4M"); r2 = R(value="100k"); c9 = Cp("6.8pF","Capacitor_SMD:C_0603_1608Metric")
p18 += r1[1]; r1[2] += fbp; c9[1] += p18; c9[2] += fbp
r2[1] += fbp; r2[2] += gnd; u7["FBP"] += fbp
# inverter input: +5V -> R7 -> INN ; C3 filter
r7 = R(value="100"); c3 = Cp("100nF"); p5 += r7[1]; r7[2] += innf; c3[1] += innf; c3[2] += gnd
u7["INN"] += innf; u7["INN2"] += innf
# inverter: OUTN -> L2 -> gnd ; D2 -18V->OUTN ; C5 on -18V
u7["OUTN"] += sw_inv; u7["OUTN2"] += sw_inv; l2[1] += sw_inv; l2[2] += gnd
d2[1] += sw_inv; d2[2] += n18 # anode at -18V, cathode at SW_INV
c5 = Cp("22uF","Capacitor_SMD:C_1206_3216Metric"); n18 += c5[1]; c5[2] += gnd
u7["VNEG"] += n18
# negative feedback: R3 -18->FBN (C10 ff), R4 VREF->FBN
r3 = R(value="1.5M"); r4 = R(value="100k"); c10 = Cp("7.5pF","Capacitor_SMD:C_0603_1608Metric")
n18 += r3[1]; r3[2] += fbn; c10[1] += n18; c10[2] += fbn
r4[1] += vref; r4[2] += fbn; u7["FBN"] += fbn
# VREF bypass + compensation
u7["VREF"] += vref; c8 = Cp("220nF"); vref += c8[1]; c8[2] += gnd
c7 = Cp("4.7nF","Capacitor_SMD:C_0603_1608Metric"); u7["CP"] += c7[1]; c7[2] += gnd # boost comp
c6 = Cp("10nF"); u7["CN"] += c6[1]; c6[2] += gnd # inverter comp
# ---- +15V LDO (TPS7A4901): IN<-+18, OUT->+15, divider, NR cap ----
u8["IN"] += p18; ci8 = Cp("1uF"); p18 += ci8[1]; ci8[2] += gnd
u8["OUT"] += p15; co8 = Cp("2.2uF"); p15 += co8[1]; co8[2] += gnd
u8["GND"] += gnd; u8["EP"] += gnd; u8["EN"] += p18
rt8 = R(value="116k"); rb8 = R(value="10k"); p15 += rt8[1]; rt8[2] += u8["FB"]; rb8[1] += u8["FB"]; rb8[2] += gnd
cnr8 = Cp("10nF"); u8["NR"] += cnr8[1]; cnr8[2] += gnd
# ---- -15V LDO (TPS7A3001): IN<--18, OUT->-15, divider, NR cap ----
u9["IN"] += n18; ci9 = Cp("1uF"); n18 += ci9[1]; ci9[2] += gnd
u9["OUT"] += n15; co9 = Cp("2.2uF"); n15 += co9[1]; co9[2] += gnd
u9["GND"] += gnd; u9["EP"] += gnd; u9["EN"] += n18
rt9 = R(value="117k"); rb9 = R(value="10k"); n15 += rt9[1]; rt9[2] += u9["FB"]; rb9[1] += u9["FB"]; rb9[2] += gnd
cnr9 = Cp("10nF"); u9["NR"] += cnr9[1]; cnr9[2] += gnd
# ---- 3V3 LDO (AP2112K): +5V -> +3V3 ----
u10["VIN"] += p5; u10["EN"] += p5; u10["GND"] += gnd; u10["VOUT"] += p3v3
ci10 = Cp("1uF"); p5 += ci10[1]; ci10[2] += gnd
co10 = Cp("1uF"); p3v3 += co10[1]; co10[2] += gnd
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "power_tree.net"))
generate_netlist(file_=out)
print("Power tree netlist ->", out)

View file

@ -1,5 +0,0 @@
ERC WARNING: Only one pin (PASSIVE pin 1/1 of L/L3) attached to net VBUS_5V.
ERC WARNING: Unconnected pin: OUTPUT pin 7/BSW of TPS65131/U7.
ERC INFO: 2 warnings found while running ERC.
ERC INFO: 0 errors found while running ERC.

View file

@ -1,85 +0,0 @@
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
WARNING: fp-lib-table file was not found. Component footprints are not available.
WARNING: fp-lib-table file was not found. Component footprints are not available.
WARNING: fp-lib-table file was not found. Component footprints are not available.
WARNING: fp-lib-table file was not found. Component footprints are not available.
WARNING: Missing tag on TPS65131 instantiated at /work/hardware/eda/circuits/power_tree.py:78.
WARNING: Random tag t1lc_t3f27 generated for TPS65131.
WARNING: Missing tag on TPS7A4901 instantiated at /work/hardware/eda/circuits/power_tree.py:78.
WARNING: Random tag _F9fKhFdb4 generated for TPS7A4901.
WARNING: Missing tag on TPS7A3001 instantiated at /work/hardware/eda/circuits/power_tree.py:78.
WARNING: Random tag bOPQEnHMPE generated for TPS7A3001.
WARNING: Missing tag on AP2112K-3.3 instantiated at /work/hardware/eda/circuits/power_tree.py:78.
WARNING: Random tag 7zaNe3SmR6 generated for AP2112K-3.3.
WARNING: Missing tag on L instantiated at /work/hardware/eda/circuits/power_tree.py:79.
WARNING: Random tag PHETofdCdy generated for L.
WARNING: Missing tag on L instantiated at /work/hardware/eda/circuits/power_tree.py:79.
WARNING: Random tag VLz6BdeRjR generated for L.
WARNING: Missing tag on D_Schottky instantiated at /work/hardware/eda/circuits/power_tree.py:80.
WARNING: Random tag 5l222NbdCl generated for D_Schottky.
WARNING: Missing tag on D_Schottky instantiated at /work/hardware/eda/circuits/power_tree.py:80.
WARNING: Random tag VfUHMWYwrr generated for D_Schottky.
WARNING: Missing tag on L instantiated at /work/hardware/eda/circuits/power_tree.py:83.
WARNING: Random tag 8XkETdYyd1 generated for L.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag kdZZscrEaM generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag BCsDMaf69p generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag Y_Q1QqsLAW generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag Q0WZTfJB2F generated for C.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:99.
WARNING: Random tag 6Keokgw_Mf generated for R.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:99.
WARNING: Random tag eXrrEmgZS2 generated for R.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag xXnkcE28b3 generated for C.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:103.
WARNING: Random tag 71fp6zgOCs generated for R.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag putM5Ti_HW generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag oljnEjjwPl generated for C.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:111.
WARNING: Random tag kGZVZN0ybr generated for R.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:111.
WARNING: Random tag MtS4y5fm0b generated for R.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag BqTyOscObN generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag kxjlLGdHS1 generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag ACrRZDytJN generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag hDO8pzdyZL generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag KeCZzYSyEA generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag rDtkgYITBw generated for C.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:123.
WARNING: Random tag kuDzksgOjV generated for R.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:123.
WARNING: Random tag cCGQfdk067 generated for R.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag dXyIckRkyY generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag 4YxQcPij_v generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag aOlUn2sbkL generated for C.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:130.
WARNING: Random tag boe1kCGhJI generated for R.
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/power_tree.py:130.
WARNING: Random tag kf8HTzBAWP generated for R.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag EMg7Ffu0Ou generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag LpwWDsy7sz generated for C.
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/power_tree.py:38.
WARNING: Random tag ZlDaRGjYdE generated for C.
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
INFO: 79 warnings found while generating netlist.
INFO: 0 errors found while generating netlist.

View file

@ -1,72 +0,0 @@
from collections import defaultdict
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
from skidl.pin import pin_types
SKIDL_lib_version = '0.0.1'
power_tree = SchLib(tool=SKIDL).add_parts(*[
Part(**{ 'name':'TPS65131', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TPS65131'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
Pin(num='1',name='INP',func=pin_types.PASSIVE),
Pin(num='24',name='INP2',func=pin_types.PASSIVE),
Pin(num='2',name='PGND',func=pin_types.PWRIN),
Pin(num='3',name='PGND2',func=pin_types.PWRIN),
Pin(num='4',name='VIN',func=pin_types.PWRIN),
Pin(num='5',name='INN',func=pin_types.PASSIVE),
Pin(num='6',name='INN2',func=pin_types.PASSIVE),
Pin(num='7',name='BSW',func=pin_types.OUTPUT),
Pin(num='8',name='ENP',func=pin_types.INPUT),
Pin(num='9',name='PSP',func=pin_types.INPUT),
Pin(num='10',name='ENN',func=pin_types.INPUT),
Pin(num='11',name='PSN',func=pin_types.INPUT),
Pin(num='12',name='NC12',func=pin_types.NOCONNECT),
Pin(num='20',name='NC20',func=pin_types.NOCONNECT),
Pin(num='13',name='OUTN',func=pin_types.PASSIVE),
Pin(num='14',name='OUTN2',func=pin_types.PASSIVE),
Pin(num='15',name='VNEG',func=pin_types.INPUT),
Pin(num='16',name='FBN',func=pin_types.INPUT),
Pin(num='17',name='VREF',func=pin_types.PWROUT),
Pin(num='18',name='CN',func=pin_types.PASSIVE),
Pin(num='19',name='AGND',func=pin_types.PWRIN),
Pin(num='21',name='CP',func=pin_types.PASSIVE),
Pin(num='22',name='FBP',func=pin_types.INPUT),
Pin(num='23',name='VPOS',func=pin_types.INPUT),
Pin(num='25',name='EP',func=pin_types.PWRIN)] }),
Part(**{ 'name':'TPS7A4901', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TPS7A4901'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
Pin(num='1',name='OUT',func=pin_types.PWROUT),
Pin(num='2',name='FB',func=pin_types.INPUT),
Pin(num='3',name='NC',func=pin_types.NOCONNECT),
Pin(num='4',name='GND',func=pin_types.PWRIN),
Pin(num='5',name='EN',func=pin_types.INPUT),
Pin(num='6',name='NR',func=pin_types.PASSIVE),
Pin(num='7',name='DNC',func=pin_types.NOCONNECT),
Pin(num='8',name='IN',func=pin_types.PWRIN),
Pin(num='9',name='EP',func=pin_types.PWRIN)] }),
Part(**{ 'name':'TPS7A3001', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TPS7A3001'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
Pin(num='1',name='OUT',func=pin_types.PWROUT),
Pin(num='2',name='FB',func=pin_types.INPUT),
Pin(num='3',name='NC',func=pin_types.NOCONNECT),
Pin(num='4',name='GND',func=pin_types.PWRIN),
Pin(num='5',name='EN',func=pin_types.INPUT),
Pin(num='6',name='NR',func=pin_types.PASSIVE),
Pin(num='7',name='DNC',func=pin_types.NOCONNECT),
Pin(num='8',name='IN',func=pin_types.PWRIN),
Pin(num='9',name='EP',func=pin_types.PWRIN)] }),
Part(**{ 'name':'AP2112K-3.3', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'AP2112K-3.3'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_TO_SOT_SMD:SOT-23-5', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
Pin(num='1',name='VIN',func=pin_types.PWRIN),
Pin(num='2',name='GND',func=pin_types.PWRIN),
Pin(num='3',name='EN',func=pin_types.INPUT),
Pin(num='4',name='NC',func=pin_types.NOCONNECT),
Pin(num='5',name='VOUT',func=pin_types.PWROUT)] }),
Part(**{ 'name':'L', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'L'}), 'ref_prefix':'L', 'fplist':[''], 'footprint':'Inductor_SMD:L_Wuerth_WE-PD_Typ4_M', 'keywords':'inductor choke coil reactor magnetic', 'description':'Inductor', 'datasheet':'~', 'pins':[
Pin(num='1',name='1',func=pin_types.PASSIVE,unit=1),
Pin(num='2',name='2',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
Part(**{ 'name':'D_Schottky', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'D_Schottky'}), 'ref_prefix':'D', 'fplist':[''], 'footprint':'Diode_SMD:D_SMA', 'keywords':'diode Schottky', 'description':'Schottky diode', 'datasheet':'~', 'pins':[
Pin(num='1',name='K',func=pin_types.PASSIVE,unit=1),
Pin(num='2',name='A',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_1206_3216Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])

View file

@ -139,7 +139,7 @@ const SAMPLES = {}; let state = { bpm:120, volume:0.85 }, meters = [], muteWindo
const VERSIONS = [ const VERSIONS = [
{ key:"editor", file:"/editor.html", name:"PM_E1 Editor", chip:"app", h:620, sum:"Design grooves: stack meter lanes, perstep accents/ghosts/mutes, swing &amp; polyrhythm, set lists, perlane dB gain." }, { key:"editor", file:"/editor.html", name:"PM_E1 Editor", chip:"app", h:620, sum:"Design grooves: stack meter lanes, perstep accents/ghosts/mutes, swing &amp; polyrhythm, set lists, perlane dB gain." },
{ key:"kit", file:"/kit.html", name:"PM_K1 Kit", chip:"hw", h:560, sum:"Build it today — a Raspberry Pi Pico on the 52Pi touchscreen kit; tap the 3.5″ screen, joystick tempo, RGB beat light, buzzer. MicroPython firmware included." }, { key:"kit", file:"/kit.html", name:"PM_K1 Kit", chip:"hw", h:560, sum:"Build it today — a Raspberry Pi Pico on the 52Pi touchscreen kit; tap the 3.5″ screen, joystick tempo, RGB beat light, buzzer. MicroPython firmware included." },
{ key:"explorer", file:"/explorer.html", name:"PM_X1 Explorer", chip:"hw", h:500, sum:"Offtheshelf — the Pimoroni Explorer (RP2350, 2.8″ LCD, 6 buttons, piezo) as a buttondriven sibling to the Kit. Edit on the web with Live sync; the device mirrors play/stop/tempo/track changes both ways." }, { key:"explorer", file:"/info-explorer.html", name:"PM_X1 Explorer", chip:"hw", h:560, sum:"Offtheshelf — the Pimoroni Explorer (RP2350, 2.8″ LCD, 6 buttons, piezo) as a buttondriven sibling to the Kit. Edit on the web with Live sync; the device mirrors play/stop/tempo/track changes both ways." },
{ key:"teacher", file:"/teacher.html", name:"PM_T1 Teacher", chip:"hw", h:440, sum:"Studio / lesson desk console — colour TFT of every lane, arcade buttons, instrument passthrough." }, { key:"teacher", file:"/teacher.html", name:"PM_T1 Teacher", chip:"hw", h:440, sum:"Studio / lesson desk console — colour TFT of every lane, arcade buttons, instrument passthrough." },
{ key:"stage", file:"/stage.html", name:"PM_S1 Stage", chip:"hw", h:430, sum:"Live foot pedal — two footswitches, expressionpedal tempo, a big floorreadable RGB beat light." }, { key:"stage", file:"/stage.html", name:"PM_S1 Stage", chip:"hw", h:430, sum:"Live foot pedal — two footswitches, expressionpedal tempo, a big floorreadable RGB beat light." },
{ key:"micro", file:"/micro.html", name:"PM_P1 Practice", chip:"hw", h:240, sum:"Inline practice bar — clickable thumbroller, amber 14segment, instrument in/out passthrough." }, { key:"micro", file:"/micro.html", name:"PM_P1 Practice", chip:"hw", h:240, sum:"Inline practice bar — clickable thumbroller, amber 14segment, instrument in/out passthrough." },