As-built: drop back view; inch dims on front+top (same width); TFT shows all lanes

- Removed the PCB back view (BOM stays in the right column).
- Front and top views now share a left dimension gutter, so they're the same
  width and aligned, and carry inch dimensions: front 4.7 × 5.5 in (120 × 140 mm),
  top-edge thickness ≈ 1.8 in (45 mm), width 4.7 in below.
- Reworked the TFT to use the empty space: tempo with the item name beside it,
  bar/beat at the bottom, and — filling the middle — ALL meter lanes drawn as
  rows of step pads (subdivisions, accent/normal/ghost/mute, amber beat ticks,
  per-lane playhead). Replaces the single beat-dots row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-05-26 08:55:52 -05:00
parent 9b5dfd63c5
commit e8c613e3e4

View file

@ -10,8 +10,8 @@
parts you'd actually solder for an RP2040 build — parts you'd actually solder for an RP2040 build —
• a 2.0″ 320×240 colour IPS TFT (ST7789, e.g. Pimoroni Pico Display 2.0): • a 2.0″ 320×240 colour IPS TFT (ST7789, e.g. Pimoroni Pico Display 2.0):
the upgrade from the cramped 128×64 mono OLED — full colour, smooth type, the upgrade from the cramped 128×64 mono OLED — full colour, smooth type,
hi-DPI. It also draws the beat indicator itself — a row of beat dots with hi-DPI. It also draws all lane patterns (each lane's steps — accent /
the current beat's subdivisions below — so there's no separate LED bar; normal / ghost / mute — with the playhead), so there's no separate LED bar;
• controls: a big PLAY up top (a plain arcade button — it does NOT change • controls: a big PLAY up top (a plain arcade button — it does NOT change
while playing; the screen shows transport state; an illuminated/RGB arcade while playing; the screen shows transport state; an illuminated/RGB arcade
button could reflect it), then a row of PREV (far left), a recessed button could reflect it), then a row of PREV (far left), a recessed
@ -24,8 +24,8 @@
amp + speaker. Powered from a standard 9 V DC pedal jack (2.1 mm centre- amp + speaker. Powered from a standard 9 V DC pedal jack (2.1 mm centre-
negative, pedalboard-friendly) or USB-C (also carries config), in a brushed negative, pedalboard-friendly) or USB-C (also carries config), in a brushed
aluminium / stainless enclosure. aluminium / stainless enclosure.
Beside the device: a top-edge view, a back view (PCB + components) and a bill of Beside the device: a top-edge view and a bill of materials. The front and top
materials. Overall ≈ 4.7 × 5.5 × 1.8 in (120 × 140 × 45 mm). views carry inch dimensions (≈ 4.7 × 5.5 × 1.8 in / 120 × 140 × 45 mm).
Compare with the idealized /player.html. One file, no deps; shares src/engine.js. Compare with the idealized /player.html. One file, no deps; shares src/engine.js.
--> -->
<script> <script>
@ -97,7 +97,7 @@
/* side-by-side: player on the left, BOM on the right (stacks when narrow) */ /* side-by-side: player on the left, BOM on the right (stacks when narrow) */
.cols{ display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:center; gap:18px; width:100% } .cols{ display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:center; gap:18px; width:100% }
.col-left{ display:flex; flex-direction:column; align-items:center; gap:14px; width:380px; max-width:100% } .col-left{ display:flex; flex-direction:column; align-items:center; gap:14px; width:380px; max-width:100% }
.bom-panel{ width:100%; align-self:flex-start } .bom-panel{ width:380px; max-width:100%; align-self:flex-start }
/* ---- top-edge view: all connectors on the top + total thickness ---- */ /* ---- top-edge view: all connectors on the top + total thickness ---- */
.topview{ width:380px; max-width:100%; display:flex; flex-direction:column; gap:5px } .topview{ width:380px; max-width:100%; display:flex; flex-direction:column; gap:5px }
@ -118,27 +118,15 @@
.tv-dim{ display:flex; flex-direction:column; align-items:center; justify-content:center; font-size:9px; color:var(--muted); letter-spacing:.03em; line-height:1.05; white-space:nowrap } .tv-dim{ display:flex; flex-direction:column; align-items:center; justify-content:center; font-size:9px; color:var(--muted); letter-spacing:.03em; line-height:1.05; white-space:nowrap }
.tv-dim .ar{ font-size:40px; line-height:.55; opacity:.5 } .tv-dim .ar{ font-size:40px; line-height:.55; opacity:.5 }
/* ---- right column: back view (PCB) above the BOM ---- */ /* ---- dimensioned views: top edge + front share a left gutter, so they're the
.col-right{ display:flex; flex-direction:column; gap:14px; width:380px; max-width:100% } same width and aligned; inch dims (thickness/height on the left, width below) ---- */
.backview{ width:100%; display:flex; flex-direction:column; gap:5px } .dim-row{ display:flex; align-items:stretch; gap:6px; width:100% }
.bv-cap{ text-align:center; font-size:10px; color:var(--muted); letter-spacing:.02em } .dim-y{ flex:0 0 13px; writing-mode:vertical-rl; transform:rotate(180deg);
.bv-frame{ display:flex; align-items:stretch; gap:6px } display:flex; align-items:center; justify-content:center; text-align:center;
.bv-dimy{ writing-mode:vertical-rl; transform:rotate(180deg); text-align:center; font-size:9px; color:var(--muted); font-size:8.5px; color:var(--muted); letter-spacing:.04em; white-space:nowrap; border-right:1px solid var(--panel-bd) }
letter-spacing:.05em; padding-right:3px; border-right:1px solid var(--panel-bd); white-space:nowrap } .dim-row > .device, .dim-row > .tv-edge{ flex:1 1 0; min-width:0; max-width:none; width:auto }
.bv-dimx{ text-align:center; font-size:9px; color:var(--muted); letter-spacing:.05em; border-top:1px solid var(--panel-bd); padding-top:3px; margin:1px 0 0 20px } .dim-x{ text-align:center; font-size:8.5px; color:var(--muted); letter-spacing:.04em;
.bv-board{ position:relative; flex:1; aspect-ratio:47/55; border-radius:6px; overflow:hidden; border-top:1px solid var(--panel-bd); padding-top:3px; margin:3px 0 0 19px }
background:
repeating-linear-gradient(0deg, rgba(255,255,255,.022) 0 1px, transparent 1px 10px),
repeating-linear-gradient(90deg, rgba(255,255,255,.022) 0 1px, transparent 1px 10px),
linear-gradient(160deg, #134a3d, #0b3026);
border:1px solid #06231b; box-shadow:inset 0 0 16px rgba(0,0,0,.55), 0 12px 24px rgba(0,0,0,.4) }
.bv-mod{ position:absolute; display:flex; align-items:center; justify-content:center; text-align:center;
font-size:7px; font-weight:600; color:#d3ece2; letter-spacing:.02em; line-height:1.18; padding:2px;
background:rgba(7,26,20,.72); border:1px solid #2f6f5c; border-radius:3px }
.bv-rnd{ position:absolute; aspect-ratio:1; border-radius:50%; display:flex; align-items:center; justify-content:center;
font-size:6.5px; font-weight:700; color:#0a1e18; letter-spacing:.02em;
background:radial-gradient(circle at 38% 32%, #dbe3df, #8e9b95); border:1px solid #5f6b65; box-shadow:0 1px 2px rgba(0,0,0,.4) }
.bv-hole{ position:absolute; width:7px; height:7px; border-radius:50%; background:#05140f; border:1px solid #3a7e6a; box-shadow:inset 0 0 2px #000 }
/* ---- controls: encoder above (under the screen), arcade buttons spread below ---- /* ---- controls: encoder above (under the screen), arcade buttons spread below ----
wheel never hides the readout · PREV far-left / NEXT far-right · big central PLAY */ wheel never hides the readout · PREV far-left / NEXT far-right · big central PLAY */
@ -223,7 +211,8 @@
<!-- ===================== TOP EDGE (connectors) ===================== --> <!-- ===================== TOP EDGE (connectors) ===================== -->
<div class="topview"> <div class="topview">
<div class="tv-cap">Top edge — all connectors (cables exit upward; pedalboard-friendly)</div> <div class="tv-cap">Top edge — all connectors (cables exit upward; pedalboard-friendly)</div>
<div class="tv-row"> <div class="dim-row">
<div class="dim-y">↕ 1.8 in (45 mm)</div>
<div class="tv-edge"> <div class="tv-edge">
<div class="tv-jack" title="External trigger in — footswitch to start/stop or tap tempo"><i></i><b>Trig In</b></div> <div class="tv-jack" title="External trigger in — footswitch to start/stop or tap tempo"><i></i><b>Trig In</b></div>
<div class="tv-jack" title="Instrument in — 1/4&quot; pass-through; the click is mixed into your signal"><i></i><b>Inst In</b></div> <div class="tv-jack" title="Instrument in — 1/4&quot; pass-through; the click is mixed into your signal"><i></i><b>Inst In</b></div>
@ -231,12 +220,13 @@
<div class="tv-jack dc" title="9 V DC in — standard 2.1 mm centre-negative pedal power"><i></i><b>9V DC</b></div> <div class="tv-jack dc" title="9 V DC in — standard 2.1 mm centre-negative pedal power"><i></i><b>9V DC</b></div>
<div class="tv-jack usb" title="USB-C — power &amp; set-list transfer"><i></i><b>USB-C</b></div> <div class="tv-jack usb" title="USB-C — power &amp; set-list transfer"><i></i><b>USB-C</b></div>
</div> </div>
<div class="tv-dim"><span class="ar"></span>≈ 1.8 in<br>(45 mm)</div>
</div> </div>
<div class="ledbar-cap">Trig in · 1/4″ inst passthrough (click injected) · shared 1/4″ balancedTRS out · 9 V DC / USBC power</div> <div class="ledbar-cap">Trig in · 1/4″ inst passthrough (click injected) · shared 1/4″ balancedTRS out · 9 V DC / USBC power</div>
</div> </div>
<!-- ===================== THE DEVICE ===================== --> <!-- ===================== THE DEVICE (front view) ===================== -->
<div class="dim-row">
<div class="dim-y">↕ 5.5 in (140 mm)</div>
<div class="device"> <div class="device">
<div class="brandrow"> <div class="brandrow">
@ -249,7 +239,7 @@
<canvas id="tft" width="320" height="240" aria-label="2 inch 320 by 240 colour IPS display"></canvas> <canvas id="tft" width="320" height="240" aria-label="2 inch 320 by 240 colour IPS display"></canvas>
</div> </div>
</div> </div>
<div class="tft-cap">2.0″ 320×240 IPS TFT (ST7789) — beat &amp; subdivisions onscreen</div> <div class="tft-cap">2.0″ 320×240 IPS TFT (ST7789) — tempo, name &amp; all lane patterns</div>
<div class="controls"> <div class="controls">
<div class="key"><button class="abtn play" id="bPlay" title="play / stop (Space)"></button><small>Play / Stop</small></div> <div class="key"><button class="abtn play" id="bPlay" title="play / stop (Space)"></button><small>Play / Stop</small></div>
@ -264,7 +254,9 @@
</div> </div>
<div class="grille"></div> <div class="grille"></div>
</div> </div><!-- /device -->
</div><!-- /dim-row -->
<div class="dim-x">↔ 4.7 in (120 mm) wide</div>
<!-- ===================== LOAD CONFIG ===================== --> <!-- ===================== LOAD CONFIG ===================== -->
<div class="panel"> <div class="panel">
@ -283,34 +275,6 @@
</div> </div>
</div><!-- /col-left --> </div><!-- /col-left -->
<div class="col-right">
<!-- ===================== BACK VIEW (PCB + components) ===================== -->
<div class="backview">
<div class="bv-cap">Back view — PCB &amp; components (mounting holes at the corners)</div>
<div class="bv-frame">
<div class="bv-dimy">↕ 5.5 in (140 mm)</div>
<div class="bv-board">
<span class="bv-hole" style="left:3%;top:2%"></span>
<span class="bv-hole" style="right:3%;top:2%"></span>
<span class="bv-hole" style="left:3%;bottom:2%"></span>
<span class="bv-hole" style="right:3%;bottom:2%"></span>
<div class="bv-mod" style="left:8%;top:2%;width:84%;height:6%">Trig · Inst · Out TRS · 9V DC · USB-C — top-edge jacks</div>
<div class="bv-mod" style="left:13%;top:11%;width:74%;height:24%">ST7789 2.0″ 320×240 TFT</div>
<div class="bv-mod" style="left:12%;top:38%;width:37%;height:9%">9 V→5 V buck</div>
<div class="bv-mod" style="left:53%;top:38%;width:35%;height:9%">RP2040</div>
<div class="bv-rnd" style="left:41%;top:50%;width:18%">PLAY</div>
<div class="bv-rnd" style="left:7%;top:65%;width:13%">PREV</div>
<div class="bv-rnd" style="left:35%;top:67%;width:9%">ENC</div>
<div class="bv-rnd" style="left:50%;top:65%;width:13%">TAP</div>
<div class="bv-rnd" style="right:7%;top:65%;width:13%">NEXT</div>
<div class="bv-mod" style="left:9%;top:80%;width:60%;height:13%">Analog audio — PCM5102 · NE5532 · DRV134 · PAM8302</div>
<div class="bv-rnd" style="right:8%;bottom:3%;width:19%">SPKR</div>
</div>
</div>
<div class="bv-dimx">↔ 4.7 in (120 mm)</div>
</div>
<!-- ===================== BILL OF MATERIALS ===================== --> <!-- ===================== BILL OF MATERIALS ===================== -->
<div class="panel bom-panel"> <div class="panel bom-panel">
<h2>Bill of materials</h2> <h2>Bill of materials</h2>
@ -346,7 +310,6 @@
buffer of the 1/4″ instrument input, then fed to the balanced line driver (1/4″ TRS out) and the monitor amp — buffer of the 1/4″ instrument input, then fed to the balanced line driver (1/4″ TRS out) and the monitor amp —
so your instrument is never redigitised (no added latency).</p> so your instrument is never redigitised (no added latency).</p>
</div> </div>
</div><!-- /col-right -->
</div><!-- /cols --> </div><!-- /cols -->
<script> <script>
@ -436,57 +399,63 @@ const TFT_W=320, TFT_H=240;
const tft=$("tft"), tc=tft.getContext("2d"); const tft=$("tft"), tc=tft.getContext("2d");
(function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1)); (function(){ const dpr=Math.max(1,Math.min(3,window.devicePixelRatio||1));
tft.width=TFT_W*dpr; tft.height=TFT_H*dpr; tc.scale(dpr,dpr); })(); // hi-DPI backing → crisp type tft.width=TFT_W*dpr; tft.height=TFT_H*dpr; tc.scale(dpr,dpr); })(); // hi-DPI backing → crisp type
function drawTFT(){ const SND_ABBR={kick:"KICK",snare:"SNR",rim:"RIM",clap:"CLAP",hatClosed:"HAT",hatOpen:"OHAT",ride:"RIDE",crash:"CRSH",
const ref=meters[0], bpb=ref?ref.beatsPerBar:0; tomLow:"TOM↓",tomMid:"TOM",tomHigh:"TOM↑",tambourine:"TAMB",cowbell:"CBL",woodblock:"WOOD",claves:"CLAV",jamblock:"JAM",beep:"BEEP"};
// beats from lane 1; subdivisions from the finest lane sharing that beat grid function soundLabel(s){ if(SND_ABBR[s]) return SND_ABBR[s];
let disp=ref; if(ref){ for(const x of meters){ if(!x.poly && x.beatsPerBar===ref.beatsPerBar && x.stepsPerBeat>disp.stepsPerBeat) disp=x; } } const m=String(s).match(/^([a-zA-Z]+)(\d+)?$/);
const spb=disp?disp.stepsPerBeat:1; return m ? (m[1].slice(0,3).toUpperCase()+(m[2]||"")) : String(s).slice(0,5).toUpperCase(); }
let curBeat=-1, curSub=-1;
if(state.running&&disp&&disp.currentStep>=0){ curBeat=Math.floor(disp.currentStep/spb); curSub=disp.currentStep%spb; }
function drawTFT(){
const ref=meters[0];
const g=tc.createLinearGradient(0,0,0,TFT_H); g.addColorStop(0,"#0b0f18"); g.addColorStop(1,"#05070c"); const g=tc.createLinearGradient(0,0,0,TFT_H); g.addColorStop(0,"#0b0f18"); g.addColorStop(1,"#05070c");
tc.fillStyle=g; tc.fillRect(0,0,TFT_W,TFT_H); tc.fillStyle=g; tc.fillRect(0,0,TFT_W,TFT_H);
// header // header
tc.textBaseline="middle"; tc.textAlign="left"; tc.textBaseline="middle"; tc.textAlign="left";
tc.font='600 13px "Segoe UI",system-ui,sans-serif'; tc.fillStyle="#5b7a93"; tc.font='600 12px "Segoe UI",system-ui,sans-serif'; tc.fillStyle="#5b7a93";
tc.fillText("♪ "+(setlist?((idx+1)+"/"+setlist.items.length):"/"), 14, 17); tc.fillText("♪ "+(setlist?((idx+1)+"/"+setlist.items.length):"/"), 12, 13);
tc.textAlign="right"; tc.fillStyle = state.running ? "#2fe07a" : "#6b7787"; tc.textAlign="right"; tc.fillStyle = state.running ? "#2fe07a" : "#6b7787";
tc.fillText(state.running?"▶ PLAY":"■ STOP", TFT_W-14, 17); tc.fillText(state.running?"▶ PLAY":"■ STOP", TFT_W-12, 13);
tc.fillStyle="#13283c"; tc.fillRect(14,30,TFT_W-28,1); tc.fillStyle="#13283c"; tc.fillRect(12,25,TFT_W-24,1);
// tempo — the hero number // tempo (left) + name (right) — uses the wide upper area
tc.textBaseline="alphabetic"; tc.textAlign="left"; tc.textBaseline="alphabetic"; tc.textAlign="left";
tc.fillStyle="#1fb6f0"; tc.font='800 78px "Segoe UI",system-ui,sans-serif'; tc.fillStyle="#1fb6f0"; tc.font='800 50px "Segoe UI",system-ui,sans-serif';
const bpm=String(state.bpm); tc.fillText(bpm, 16, 110); const bpm=String(state.bpm); tc.fillText(bpm, 12, 74);
const bx=16+tc.measureText(bpm).width+9; const bx=12+tc.measureText(bpm).width+8;
tc.fillStyle="#5b7a93"; tc.font='700 16px "Segoe UI",system-ui,sans-serif'; tc.fillText("BPM", bx, 74); tc.fillStyle="#5b7a93"; tc.font='700 13px "Segoe UI",system-ui,sans-serif'; tc.fillText("BPM", bx, 48);
tc.font='600 17px "Segoe UI",system-ui,sans-serif'; tc.fillText("♩ "+(ref?(ref.groupsStr.replace(/[^0-9+]/g,"")||ref.beatsPerBar):""), bx, 98); tc.font='600 13px "Segoe UI",system-ui,sans-serif'; tc.fillText("♩ "+(ref?(ref.groupsStr.replace(/[^0-9+]/g,"")||ref.beatsPerBar):""), bx, 67);
// item name — centred, ellipsised // item name, right-aligned, ellipsised to the space right of the tempo labels
tc.textAlign="center"; tc.fillStyle="#e9eff7"; tc.font='600 22px "Segoe UI",system-ui,sans-serif'; tc.textAlign="right"; tc.fillStyle="#e9eff7"; tc.font='600 16px "Segoe UI",system-ui,sans-serif';
let nm=setlist?(setlist.items[idx].name||"—"):"—"; let nm=setlist?(setlist.items[idx].name||"—"):"—"; const nmMax=(TFT_W-12)-(bx+42);
if(tc.measureText(nm).width>TFT_W-28){ while(nm.length>1 && tc.measureText(nm+"…").width>TFT_W-28) nm=nm.slice(0,-1); nm+="…"; } if(tc.measureText(nm).width>nmMax){ while(nm.length>1 && tc.measureText(nm+"…").width>nmMax) nm=nm.slice(0,-1); nm+="…"; }
tc.fillText(nm, TFT_W/2, 138); tc.fillText(nm, TFT_W-12, 58);
// beat dots + the current beat's subdivisions — the on-screen replacement for the LED matrix tc.fillStyle="#13283c"; tc.fillRect(12,84,TFT_W-24,1);
if(bpb>0){ // ---- all meter lanes: each a row of step pads (subdivisions · accent/normal/ghost/mute · playhead) ----
const gap=Math.min(30,(TFT_W-44)/bpb), x0=TFT_W/2 - gap*(bpb-1)/2, by=166; const lanes=meters||[], gx0=48, gx1=TFT_W-12, gw=gx1-gx0, top=90, bot=204;
for(let i=0;i<bpb;i++){ const rowH = lanes.length ? Math.min(20,(bot-top)/lanes.length) : 0;
const x=x0+i*gap, grp=ref.groupStarts.has(i), on=(i===curBeat), col=grp?"#ffd166":"#1fb6f0"; tc.textBaseline="middle";
tc.beginPath(); tc.arc(x,by, on?7:4, 0, Math.PI*2); lanes.forEach((m,li)=>{
if(on){ tc.fillStyle=col; tc.shadowColor=col; tc.shadowBlur=11; tc.fill(); tc.shadowBlur=0; } const cy=top+li*rowH+rowH/2, en=m.enabled;
else { tc.fillStyle=grp?"rgba(255,209,102,.34)":"rgba(31,182,240,.26)"; tc.fill(); } tc.textAlign="left"; tc.font='700 8px "Segoe UI",system-ui,sans-serif'; tc.fillStyle=en?"#9fb4c4":"#4a5560";
} tc.fillText(soundLabel(m.sound), 6, cy+0.5);
if(spb>1){ const nsteps=Math.max(1,m.beatsPerBar*m.stepsPerBeat), cw=gw/nsteps, ch=Math.min(rowH-4,13), cyTop=cy-ch/2;
const sgap=8, sx0=TFT_W/2 - sgap*(spb-1)/2, sy=188; for(let s=0;s<nsteps;s++){
for(let s=0;s<spb;s++){ tc.beginPath(); tc.arc(sx0+s*sgap, sy, 2.4, 0, Math.PI*2); const cx=gx0+s*cw, lvl=m.beatsOn[s]|0, beatStart=(s%m.stepsPerBeat)===0, cur=state.running&&m.currentStep===s;
tc.fillStyle=(curBeat>=0&&s<=curSub)?"#2fe0a0":"rgba(47,224,160,.16)"; tc.fill(); } const x=cx+0.6, w=Math.max(1,cw-1.4);
} let fill = !en ? (lvl?"rgba(120,140,160,.18)":null)
: lvl===2?"#1fb6f0" : lvl===1?"rgba(31,182,240,.62)" : lvl===3?"rgba(31,182,240,.30)" : null;
if(fill){ tc.fillStyle=fill; tc.fillRect(x,cyTop,w,ch); }
else { tc.strokeStyle="rgba(120,140,160,.22)"; tc.lineWidth=1; tc.strokeRect(x+0.5,cyTop+0.5,w-1,ch-1); } // mute = outline
if(beatStart){ tc.fillStyle="rgba(255,209,102,.55)"; tc.fillRect(cx,cyTop-1,1,ch+2); } // beat tick
if(cur){ tc.strokeStyle="#fff"; tc.lineWidth=1.4; tc.strokeRect(x-0.6,cyTop-0.6,w+1.2,ch+1.2); } // playhead
} }
});
// bottom strip // bottom strip
tc.fillStyle="#13283c"; tc.fillRect(14,206,TFT_W-28,1); tc.fillStyle="#13283c"; tc.fillRect(12,208,TFT_W-24,1);
tc.textBaseline="alphabetic"; tc.textAlign="left"; tc.fillStyle="#7f8b9a"; tc.font='600 14px "Segoe UI",system-ui,sans-serif'; tc.textBaseline="alphabetic"; tc.textAlign="left"; tc.fillStyle="#7f8b9a"; tc.font='600 13px "Segoe UI",system-ui,sans-serif';
tc.fillText(state.running&&ref ? ("BAR "+(ref.currentBar+1)) : "READY", 14, 226); tc.fillText(state.running&&ref ? ("BAR "+(ref.currentBar+1)+" · BEAT "+(Math.floor(ref.currentStep/ref.stepsPerBeat)+1||"")) : "READY", 12, 226);
if(segBars>0){ const rem=Math.max(0,segBars-(ref?ref.currentBar:0)); if(segBars>0){ const rem=Math.max(0,segBars-(ref?ref.currentBar:0));
tc.textAlign="right"; tc.fillStyle="#ffd166"; tc.font='700 15px "Segoe UI",system-ui,sans-serif'; tc.textAlign="right"; tc.fillStyle="#ffd166"; tc.font='700 14px "Segoe UI",system-ui,sans-serif';
tc.fillText((state.running?rem:segBars)+" BARS", TFT_W-14, 226); } tc.fillText((state.running?rem:segBars)+" BARS", TFT_W-12, 226); }
} }
function renderAll(){ drawTFT(); /* PLAY button is static hardware — transport state is shown on the screen */ function renderAll(){ drawTFT(); /* PLAY button is static hardware — transport state is shown on the screen */