AMYboard: phase-lock sketch loop() to the sequencer downbeat#1123
Conversation
|
Added a commit bumping the |
Sketch loop() callbacks fire on the AMY sequencer's absolute tick grid (every 6 ticks = a 32nd note), the same clock AMYSequence events use -- but a sketch's own step counter starts at 0 whenever the sketch loads, which is some arbitrary 32nd note inside the bar. Any pattern built on that counter plays at a constant phase offset from AMY-sequenced patterns (e.g. dx7_groove_128_bpm's bass/piano/arp vs. its drums). Two changes in _start_sketch_loop: - The first loop() call is held until the next bar boundary (tick % 192 == 0 at 48 PPQ, 4/4), so existing sketches' counters start on the downbeat, in phase with AMYSequence bars. - If the sketch declares loop(step), it gets the global 32nd-note index on the bar-locked grid (step % 32 == 0 is always a downbeat). Deriving position from step instead of a local counter also can't drift if a callback is ever dropped (mp_sched_schedule queue full). Plain loop() still works via a one-time TypeError fallback. The default sketch template (amyboard.py and both spss.js copies) now uses loop(step) and documents the grid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2b0fa13 to
ca987a6
Compare
All sketch guidance now documents the loop(step) convention: step is the global 32nd-note index on the sequencer's bar-locked grid (step % 32 == 0 is always a downbeat, the same grid AMYSequence events fire on), and the first call lands on a downbeat. Every example converts from a hand-rolled call counter (or, in the generator's arpeggio example, wall-clock BPM-to-milliseconds math) to the step grid. The zero-argument loop() is documented as legacy and still works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
python3 tulip/server/sync_amy_docs.py against amy@3a7c176; only the source marker changed (doc contents are identical to the previous pin). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Two more commits, per Brian:
Also: the amy 1.2.43 pin in this PR includes shorepine/amy#854 (merged as 1.2.42), so merging this brings the render-loop sequencer, the downbeat sync, and the guidance in one go. Closed the #1120 test pin as obsolete. |
🔌 AMYboard PR previewEditor + flasher: https://amyboard-pr-1123.vercel.app/editor/ This preview bundles this PR's firmware — its flasher only flashes this build (not the release). Rebuilt on every push; removed when the PR closes. The hardware CI has been kicked off and should return within a few minutes, stand by! |
🌷 Tulip Web PR previewTulip Web: https://tulip-pr-1123.vercel.app/run/ Flash a Tulip to this build: on-device run Rebuilt on every push; removed when the PR closes. |
🎛️ HW CI (physical bench)AMYboard (USB-MIDI + AMY Tulip (TULIP4_R11; serial-REPL audio + WiFi screenshot): ✅ PASS — flashed this PR’s firmware; all checks matched the references. ⬇️ Artifacts: recordings · screenshot · serial logs · run logs Self-hosted bench. Audio spectral-compared to |
Fixes the "loop() sounds out of phase with AMYSequence" problem (e.g.
dx7_groove_128_bpmon AMYboard World: the AMY-sequenced drums are locked to the absolute tick grid, but the sketch's localstepcounter starts at 0 whenever the sketch loads — some arbitrary 32nd note inside the bar — so the bass/piano/arp play at a constant phase offset from the drums).loop()callbacks already fire on the AMY sequencer's absolute tick grid (tick % 6 == 0at 48 PPQ), andtulip_amy_sequencer_hookalready hands the tick to the callback —_guarded_loopwas just discarding it. Two changes in_start_sketch_loop:loop()call is held until the next bar boundary (tick % 192 == 0, 4/4 at 48 PPQ). Existing sketches' internal counters now start on the downbeat, in phase withAMYSequencebars, with no sketch changes. (Costs up to one bar of startup delay — ~1.9s at 128 bpm — which for music sketches means "starts on the one".)loop(step)signature: sketches can declaredef loop(step):and get the global 32nd-note index on the bar-locked grid (step % 32 == 0is always a downbeat). Deriving bar position fromstepinstead of a local counter also can't drift if a callback is ever dropped (mp_sched_schedulequeue full under heavy UI). Detection is a one-timeTypeErrorfallback, so plainloop()sketches are unchanged.The default sketch template (canonical copy in
amyboard.py, both fallback copies inspss.js) now usesloop(step)and documents the grid. Verified the three template copies are byte-identical and simulated the dispatch logic for both signatures (step-aware first call lands onstep % 32 == 0; plain first call lands ontick % 192 == 0).Note: the server-side sketch-generator prompt (railway) still teaches the old
def loop():convention — worth updating it toloop(step)so generated sketches are phase-locked by construction.🤖 Generated with Claude Code