Compare commits
2 commits
649501b51c
...
00cb245331
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00cb245331 | ||
|
|
fcf58d9c1f |
2 changed files with 43 additions and 23 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.0.15
|
||||
0.0.16
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@
|
|||
• a 128×64 MONOCHROME OLED (SSD1306/SH1106 class): rendered as a true 1-bit
|
||||
framebuffer (drawn, then thresholded to crisp on/off pixels, scaled with
|
||||
image-rendering:pixelated) so the cramped real layout is honest;
|
||||
• a fixed 16-pixel WS2812 ("NeoPixel") RGB beat bar (PIO-driven) — lights the
|
||||
first beatsPerBar slots, cyan downbeats / amber group-starts / dim others;
|
||||
• a 4×16 WS2812 ("NeoPixel") RGB matrix (PIO-driven): the bottom row is the
|
||||
beat (cyan downbeats / amber group-starts), and the three rows above stack
|
||||
the current beat's subdivisions as they pass (driven by the finest lane);
|
||||
• an EC11 rotary encoder (turn it: wheel or drag) for tempo, tactile buttons,
|
||||
a MAX98357A-style speaker, USB-C and a PWR LED in a matte 3D-printed case.
|
||||
Compare with the idealized /player.html. One file, no deps; shares src/engine.js.
|
||||
|
|
@ -86,13 +87,15 @@
|
|||
background:#000; border-radius:2px }
|
||||
.oled-cap{ text-align:center; font-size:10px; color:var(--muted); margin-top:6px; letter-spacing:.02em }
|
||||
|
||||
/* ---- WS2812 RGB beat bar (fixed 16 px) ---- */
|
||||
.ledbar{ display:flex; gap:5px; justify-content:center; align-items:center; margin:16px auto 4px; width:max-content;
|
||||
background:linear-gradient(180deg,#10221c,var(--pcb)); border:1px solid #07140f; border-radius:5px; padding:7px 9px;
|
||||
/* ---- 4×16 WS2812 RGB matrix: bottom row = beat, 3 rows above = subdivisions ---- */
|
||||
.ledgrid{ display:flex; flex-direction:column; gap:5px; width:max-content; margin:16px auto 4px;
|
||||
background:linear-gradient(180deg,#10221c,var(--pcb)); border:1px solid #07140f; border-radius:5px; padding:8px 9px;
|
||||
box-shadow:inset 0 1px 2px rgba(0,0,0,.6) }
|
||||
.npx{ width:16px; height:16px; border-radius:3px; background:#0c0e10; border:1px solid #05measure;
|
||||
border:1px solid #060708; position:relative; transition:background .05s, box-shadow .05s }
|
||||
.npx::after{ content:""; position:absolute; inset:4px; border-radius:1px; background:rgba(255,255,255,.06) } /* the 5050 die */
|
||||
.ledrow{ display:flex; gap:5px }
|
||||
.ledrow.beatrow{ margin-top:4px; padding-top:6px; border-top:1px solid rgba(255,255,255,.07) }
|
||||
.npx{ width:15px; height:15px; border-radius:3px; background:#0c0e10; border:1px solid #060708;
|
||||
position:relative; transition:background .05s, box-shadow .05s }
|
||||
.npx::after{ content:""; position:absolute; inset:4px; border-radius:1px; background:rgba(255,255,255,.05) } /* the 5050 die */
|
||||
.ledbar-cap{ text-align:center; font-size:10px; color:var(--muted); margin:2px 0 0; letter-spacing:.02em }
|
||||
|
||||
/* ---- controls: encoder + tactile buttons ---- */
|
||||
|
|
@ -165,8 +168,8 @@
|
|||
</div>
|
||||
<div class="oled-cap">0.96″ 128×64 mono OLED (SSD1306)</div>
|
||||
|
||||
<div class="ledbar" id="leds"></div>
|
||||
<div class="ledbar-cap">16‑px WS2812 RGB beat bar</div>
|
||||
<div class="ledgrid" id="leds"></div>
|
||||
<div class="ledbar-cap">4×16 WS2812 — beat (bottom) + 3 subdivision rows</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="keys">
|
||||
|
|
@ -320,19 +323,36 @@ function drawOLED(){
|
|||
oledThreshold();
|
||||
}
|
||||
|
||||
/* ========================= RENDER: 16-px WS2812 beat bar ===================== */
|
||||
const LED_COUNT=16, CYAN="#0AB3F7", AMBER="#ffd166";
|
||||
function buildBar(){ const box=$("leds"); box.innerHTML=""; for(let i=0;i<LED_COUNT;i++){ const d=document.createElement("div"); d.className="npx"; box.appendChild(d); } }
|
||||
/* ========== RENDER: 4×16 WS2812 matrix — beat row + 3 subdivision rows ========= */
|
||||
const LED_COLS=16, LED_ROWS=4, CYAN="#0AB3F7", AMBER="#ffd166", SUB="#2fe0a0";
|
||||
let gridLeds=[];
|
||||
function buildBar(){
|
||||
const box=$("leds"); box.innerHTML=""; gridLeds=[[],[],[],[]];
|
||||
for(let r=LED_ROWS-1;r>=0;r--){ // top row built first → the beat row (r=0) sits at the bottom
|
||||
const row=document.createElement("div"); row.className="ledrow"+(r===0?" beatrow":"");
|
||||
for(let c=0;c<LED_COLS;c++){ const d=document.createElement("div"); d.className="npx"; row.appendChild(d); gridLeds[r][c]=d; }
|
||||
box.appendChild(row);
|
||||
}
|
||||
}
|
||||
function setPx(led,bg,sh){ led.style.background=bg; led.style.boxShadow=sh||"none"; }
|
||||
function renderBar(){
|
||||
const m=meters[0], bpb=m?m.beatsPerBar:0;
|
||||
const cur=(state.running&&m)? Math.floor(m.currentStep/m.stepsPerBeat) : -1;
|
||||
const els=$("leds").children;
|
||||
for(let i=0;i<els.length;i++){
|
||||
const led=els[i];
|
||||
if(i>=bpb){ led.style.background="#0c0e10"; led.style.boxShadow="none"; continue; } // unused pixel on the fixed strip
|
||||
const grp=m.groupStarts.has(i);
|
||||
if(i===cur){ const c=grp?AMBER:CYAN; led.style.background=c; led.style.boxShadow="0 0 10px "+c+", inset 0 0 4px #fff"; }
|
||||
else { led.style.background = grp?"rgba(255,209,102,.30)":"rgba(10,179,247,.20)"; led.style.boxShadow="none"; }
|
||||
const ref=meters[0], bpb=ref?ref.beatsPerBar:0;
|
||||
// beats come from lane 1; subdivisions from the finest lane sharing that beat grid
|
||||
let disp=ref; if(ref){ for(const m of meters){ if(!m.poly && m.beatsPerBar===ref.beatsPerBar && m.stepsPerBeat>disp.stepsPerBeat) disp=m; } }
|
||||
const spb=disp?disp.stepsPerBeat:1;
|
||||
let curBeat=-1, curSub=-1;
|
||||
if(state.running&&disp&&disp.currentStep>=0){ curBeat=Math.floor(disp.currentStep/spb); curSub=disp.currentStep%spb; }
|
||||
for(let r=0;r<LED_ROWS;r++) for(let c=0;c<LED_COLS;c++){
|
||||
const led=gridLeds[r][c];
|
||||
if(c>=bpb){ setPx(led,"#0c0e10"); continue; } // unused column on the fixed matrix
|
||||
const grp=ref.groupStarts.has(c);
|
||||
if(r===0){ // BEAT row (bottom)
|
||||
if(c===curBeat){ const col=grp?AMBER:CYAN; setPx(led,col,"0 0 9px "+col+", inset 0 0 3px #fff"); }
|
||||
else setPx(led, grp?"rgba(255,209,102,.26)":"rgba(10,179,247,.15)");
|
||||
} else if(r<spb && c===curBeat){ // SUBDIVISION rows — climb in the current beat's column
|
||||
if(curSub>=r) setPx(led,SUB,"0 0 7px "+SUB); // reached this subdivision
|
||||
else setPx(led,"rgba(47,224,160,.10)"); // ahead: faint slot showing the ladder this beat will climb
|
||||
} else setPx(led,"#0c0e10"); // off (no such subdivision, or not the current beat)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue