PM_K-1 CircuitPython: fix polymeter (~) timing — true ratio polyrhythm

The firmware ran every lane at a fixed beat/sub, ignoring the ~ flag, so a poly lane
(e.g. cowbell:3~) played quarter-notes instead of fitting its cycle into lane 1's bar —
the duple 'and' coincided with a triplet note. Now match the web engine: a poly lane's
whole cycle spans the master lane's bar (dur = master_bar / steps). Verified: claves:5~
over kick:4 -> both cycles = 2.400s (5-over-4); 3-over-2 lands correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-05-28 23:08:48 -05:00
parent 8254bb042c
commit ba8d57e7ab
2 changed files with 8 additions and 2 deletions

View file

@ -357,10 +357,16 @@ class App:
self.bpm, self.lanes = parse_program(prog) self.bpm, self.lanes = parse_program(prog)
self.master = self.lanes[0] self.master = self.lanes[0]
self._reset_clock(); self.draw_bpm(); self.draw_status(); self.build_grid() self._reset_clock(); self.draw_bpm(); self.draw_status(); self.build_grid()
def _lane_dur(self, L):
beat = 60_000_000_000 / self.bpm
if L['poly']: # ~ polymeter: fit this lane's whole cycle into lane 1's bar
m = self.lanes[0]; master_bar = beat * (m['steps'] // m['sub'])
return int(master_bar / L['steps'])
return int(beat / L['sub']) # straight: a step = one beat / subdivision
def _reset_clock(self): def _reset_clock(self):
now = time.monotonic_ns() now = time.monotonic_ns()
for L in self.lanes: for L in self.lanes:
L['next'] = now; L['step'] = -1; L['dur'] = int(60_000_000_000 / self.bpm / L['sub']) L['next'] = now; L['step'] = -1; L['dur'] = self._lane_dur(L)
# ---------- audio + light ---------- # ---------- audio + light ----------
def click(self, level): def click(self, level):
@ -388,7 +394,7 @@ class App:
v = max(30, min(300, v)) v = max(30, min(300, v))
if v != self.bpm: if v != self.bpm:
self.bpm = v self.bpm = v
for L in self.lanes: L['dur'] = int(60_000_000_000 / self.bpm / L['sub']) for L in self.lanes: L['dur'] = self._lane_dur(L)
self.draw_bpm() self.draw_bpm()
def goto(self, i): def goto(self, i):
was = self.running; self.load(i); self._label("play") was = self.running; self.load(i); self._label("play")