diff --git a/pico-cp/app.py b/pico-cp/app.py index 0650c33..ea01650 100644 --- a/pico-cp/app.py +++ b/pico-cp/app.py @@ -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