PM_G-1: scrolling boot splash (model name) + transient BPM flash on tempo nudge
The splash doubles as a liveness/pixel-map check (if 'PM-G1 GRID' reads correctly, the firmware booted and the LED mapping is right). The BPM flash makes X/Y tempo nudges visible from any view (previously invisible in Grid/Pendulum). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4275187008
commit
35726b57ac
1 changed files with 40 additions and 3 deletions
|
|
@ -272,6 +272,11 @@ DIGITS = {
|
|||
'4': (5, 5, 7, 1, 1), '5': (7, 4, 7, 1, 7), '6': (7, 4, 7, 5, 7), '7': (7, 1, 2, 2, 2),
|
||||
'8': (7, 5, 7, 5, 7), '9': (7, 5, 7, 1, 7),
|
||||
}
|
||||
# 3x5 uppercase letters + marks for the boot splash (scrolls the model name "PM-G1 GRID")
|
||||
LETTERS = {
|
||||
'P': (7, 5, 7, 4, 4), 'M': (5, 7, 7, 5, 5), 'G': (7, 4, 5, 5, 7), 'R': (7, 5, 7, 6, 5),
|
||||
'I': (7, 2, 2, 2, 7), 'D': (6, 5, 5, 5, 6), '-': (0, 0, 7, 0, 0), ' ': (0, 0, 0, 0, 0),
|
||||
}
|
||||
|
||||
# ============================== APP ==============================
|
||||
class App:
|
||||
|
|
@ -301,8 +306,9 @@ class App:
|
|||
self.rep = None; self.end = None # per-track playback flow: rep=cycles, end=stop|next|+/-N goto
|
||||
self.continue_on = False; self._advance = False
|
||||
self._next_pending = None; self._seam_t = 0
|
||||
self.view = 0 # 0 = Grid, 1 = Pendulum, 2 = BPM (button B cycles)
|
||||
self.view = 0 # 0 = Grid, 1 = Pendulum, 2 = BPM (button A-hold cycles)
|
||||
self._beatflash = 0; self._beatflash_off = 0
|
||||
self._bpm_flash = 0 # while set, render() briefly shows the BPM view (so X/Y nudges are visible in any view)
|
||||
self._beat_ns = 60_000_000_000 // self.bpm
|
||||
self._note_buf = bytearray([0x90, 0, 0])
|
||||
self._clock_byte = bytes([0xF8]); self._start_byte = bytes([0xFA]); self._stop_byte = bytes([0xFC])
|
||||
|
|
@ -531,6 +537,7 @@ class App:
|
|||
if v != self.bpm:
|
||||
self.bpm = v; self._beat_ns = 60_000_000_000 // v
|
||||
self._rebuild_dur_all(); self.dirty = True
|
||||
self._bpm_flash = time.monotonic() + 0.7 # flash the tempo so the nudge is visible even in Grid/Pendulum
|
||||
self._sync_broadcast("bpm=%d" % v)
|
||||
def goto(self, i):
|
||||
was = self.running
|
||||
|
|
@ -708,10 +715,35 @@ class App:
|
|||
if lvl == 1: return max(8, BRIGHTNESS // 4) # normal
|
||||
if lvl == 3: return max(3, BRIGHTNESS // 16) # ghost
|
||||
return 0
|
||||
def _splash(self, text):
|
||||
# Scroll text across the matrix once at boot, right-to-left, as a 3x5 font on rows 1..5.
|
||||
# Doubles as a liveness + pixel-map check: if "PM-G1 GRID" reads correctly, the firmware
|
||||
# booted and the LED mapping is right.
|
||||
cols = []
|
||||
for ch in text:
|
||||
g = DIGITS.get(ch) or LETTERS.get(ch.upper()) or LETTERS.get(' ')
|
||||
for cx in range(3):
|
||||
c = 0
|
||||
for ry in range(5):
|
||||
if g[ry] & (1 << (2 - cx)): c |= (1 << ry)
|
||||
cols.append(c)
|
||||
cols.append(0) # 1px gap between glyphs
|
||||
n = len(cols); m = self.mtx; off = -16
|
||||
while off < n:
|
||||
m.clear()
|
||||
for x in range(17):
|
||||
ci = x + off
|
||||
if 0 <= ci < n:
|
||||
c = cols[ci]
|
||||
for ry in range(5):
|
||||
if c & (1 << ry): m.set(x, ry + 1, BRIGHTNESS)
|
||||
m.show(); time.sleep(0.05); off += 1
|
||||
def render(self):
|
||||
self.mtx.clear()
|
||||
if self.view == 2: self._render_bpm()
|
||||
elif self.view == 1: self._render_pendulum()
|
||||
v = self.view
|
||||
if v != 2 and self._bpm_flash and time.monotonic() < self._bpm_flash: v = 2 # transient tempo readout
|
||||
if v == 2: self._render_bpm()
|
||||
elif v == 1: self._render_pendulum()
|
||||
else: self._render_grid()
|
||||
self.mtx.show()
|
||||
def _render_grid(self):
|
||||
|
|
@ -927,6 +959,8 @@ class App:
|
|||
boot = time.monotonic()
|
||||
try: os.stat("/trial"); committed = False
|
||||
except OSError: committed = True
|
||||
try: self._splash("PM-G1 GRID") # boot banner (scrolls once); wrapped so a splash bug never blocks the app
|
||||
except Exception: pass
|
||||
next_frame = 0.0
|
||||
while True:
|
||||
try:
|
||||
|
|
@ -940,6 +974,9 @@ class App:
|
|||
self._sync_broadcast_full()
|
||||
if self.running and tnow >= next_frame: # keep pendulum/playhead moving even with no input
|
||||
self.dirty = True; next_frame = tnow + 0.04
|
||||
if self._bpm_flash: # keep rendering through the tempo flash, then one frame to revert
|
||||
if tnow >= self._bpm_flash: self._bpm_flash = 0
|
||||
self.dirty = True
|
||||
if self.dirty:
|
||||
self.dirty = False; self.render()
|
||||
time.sleep(0.0005)
|
||||
|
|
|
|||
Loading…
Reference in a new issue