PM_K-1 0.0.18: split post-seam redraw -- fast bits now, heavy build_grid deferred
0.0.17 fixed the off-by-one so A plays its full final bar, but B's first beats were still being eaten -- by build_grid + draw_log blocking the main loop for ~hundreds of ms right at the seam (a full grid teardown + ~64-128 vectorio rectangle allocations + draw_log text bitmaps + an SPI refresh). tick() doesn't run during that, so B's first master steps fire only when the catch-up while-loop runs at the next tick, collapsing into one click. Split the post-seam refresh: - Fast pass: draw_meters / draw_bpm / draw_status / draw_train. Cheap (each just swaps one TileGrid). Runs immediately so the new title + bpm + status are correct on screen. - Heavy pass: build_grid + draw_log. Deferred by 0.6s after the seam, gated by self._heavy_redraw_at. B's intro plays clean; the grid catches up half a beat or so into B with a single brief jitter (rather than B's first 1-2 beats being lost). Plus gc.collect() at the start of build_grid AND draw_log. Both allocate enough small objects that a fragmented heap will MemoryError partway through; collecting first gives them a clean run. load() / switch_setlist() clear _heavy_redraw_at so a user-initiated navigation doesn't trigger a second deferred rebuild on top of the immediate one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da71604c0d
commit
93c1fb62e9
1 changed files with 12 additions and 6 deletions
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import board, busio, digitalio, analogio, pwmio, displayio, vectorio, time, json, gc, os, supervisor
|
||||
supervisor.runtime.autoreload = False # we write our own files (log + pushed programs); never self-restart
|
||||
APP_VERSION = "0.0.17" # firmware version (the A/B updater pushes/compares this)
|
||||
APP_VERSION = "0.0.18" # firmware version (the A/B updater pushes/compares this)
|
||||
try:
|
||||
import rtc # set from the editor's clock SysEx so the log has real timestamps
|
||||
except ImportError:
|
||||
|
|
@ -452,6 +452,7 @@ class App:
|
|||
self._dirty = False; self._overlay = None; self._ovbtns = [] # on-device editing: unsaved edits + modal
|
||||
self.continue_on = False; self._advance = False; self._grid = {} # auto-advance + pad hit-test geometry
|
||||
self._next_pending = None; self._seam_t = 0; self._need_redraw = False # gapless seam between tracks
|
||||
self._heavy_redraw_at = 0 # deferred build_grid + draw_log deadline (so B's intro isn't blocked by SPI/alloc)
|
||||
self._displayed_bpm = -1; self._clock_next = 0 # lazy BPM redraw + MIDI Clock Out tick scheduler
|
||||
self._clock_in_last_t = 0; self._clock_in_avg = 0; self._slaved = False # MIDI Clock In: smoothed tracker + slave flag
|
||||
self.sl = 0; self.rebuild_setlists() # built-in playlists (baked) + user playlists (programs.json)
|
||||
|
|
@ -550,7 +551,7 @@ class App:
|
|||
self.bpm, self.lanes, self.bars, self.ramp, self.trainer = parse_program(prog)
|
||||
self.master = self.lanes[0]; self._ramp_base = self.bpm; self._lastbar = -1; self._muted = False
|
||||
self._dirty = False; self._overlay = None # fresh load -> no unsaved edits
|
||||
self._next_pending = None; self._need_redraw = False # discard any prepared seam (user navigated away)
|
||||
self._next_pending = None; self._need_redraw = False; self._heavy_redraw_at = 0 # discard any prepared seam (user navigated away)
|
||||
while len(self.g_overlay): self.g_overlay.pop() # dismiss any open modal
|
||||
self._reset_clock(); self.draw_bpm(); self.draw_status(); self.draw_train()
|
||||
self.build_grid(); self.draw_log()
|
||||
|
|
@ -1097,7 +1098,8 @@ class App:
|
|||
while len(self.g_overlay): self.g_overlay.pop()
|
||||
seam = self._seam_t
|
||||
for L in self.lanes: L['next'] = seam; L['step'] = -1 # NEXT tick fires step 0 of the new track at seam_t
|
||||
self._need_redraw = True # visuals (grid + draws) catch up on the next refresh
|
||||
self._need_redraw = True # cheap header bits: meters/bpm/status/train -> next refresh
|
||||
self._heavy_redraw_at = time.monotonic() + 0.6 # heavy: build_grid + draw_log deferred ~0.6s so B's intro plays unblocked
|
||||
self._seg_start = time.monotonic() # reset the on-screen timer
|
||||
self.led_rest()
|
||||
|
||||
|
|
@ -1208,6 +1210,7 @@ class App:
|
|||
def build_grid(self):
|
||||
while len(self.g_grid): self.g_grid.pop()
|
||||
self.lane_pads = []; self.lane_lit = []
|
||||
gc.collect() # 64-128 vectorio allocs in this fn want a defragmented heap
|
||||
n = min(len(self.lanes), MAXLANES)
|
||||
top = GRID_TOP; rowh = min(40, ((LOG_TOP - 10) - top) // max(1, n))
|
||||
px0 = 60; usable = WIDTH - 8 - px0 - 12; gridh = n * rowh
|
||||
|
|
@ -1282,6 +1285,7 @@ class App:
|
|||
g = self.g_log
|
||||
while len(g): g.pop()
|
||||
self.log_rows = []
|
||||
gc.collect() # several text bitmaps allocated below want a clean heap
|
||||
hdr, w, h = make_text("PRACTICE LOG - THIS TRACK", FONT_S, C_MUTE, C_BG); hdr.x = 10; hdr.y = LOG_TOP; g.append(hdr)
|
||||
rows = [(i, e) for i, e in enumerate(self.log) if e.get("name") == self.name] # current track only
|
||||
if not rows:
|
||||
|
|
@ -1409,10 +1413,12 @@ class App:
|
|||
except OSError: committed = True
|
||||
while True:
|
||||
self.tick(); self.poll()
|
||||
if self._need_redraw: # post-seam: visuals catch up AFTER the audio swap
|
||||
if self._need_redraw: # post-seam fast pass: cheap header/status bits, runs immediately
|
||||
self._need_redraw = False
|
||||
self.draw_bpm(); self.draw_status(); self.draw_train()
|
||||
self.build_grid(); self.draw_log(); self.draw_meters()
|
||||
self.draw_bpm(); self.draw_status(); self.draw_train(); self.draw_meters()
|
||||
if self._heavy_redraw_at and time.monotonic() >= self._heavy_redraw_at:
|
||||
self._heavy_redraw_at = 0 # post-seam slow pass: build_grid + draw_log are ~hundreds of ms,
|
||||
self.build_grid(); self.draw_log() # deferred so B's first few beats fire on time
|
||||
tnow = time.monotonic()
|
||||
if tnow >= self._uiNext: # ~4x/s: tick the stopwatch + bar counter
|
||||
self._uiNext = tnow + 0.25; self.draw_meters(); self.draw_bpm() # bpm follows the continuous ramp
|
||||
|
|
|
|||
Loading…
Reference in a new issue