You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Teach loop(step) in AMY_AGENTS, the generator prompt, and online docs
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>
Copy file name to clipboardExpand all lines: docs/amyboard/online.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -206,7 +206,7 @@ Click any knob **label** (the text above or below the knob, e.g. "freq") to open
206
206
207
207
## Write and run Python sketches
208
208
209
-
When AMYboard starts up, either on web or hardware, it sets up whatever patches are set to each channel (using data stored in a sketch) and then runs a "sketch" set up in `sketch.py`. This is a Python program that runs setup code (in the top level of the code file) and then calls `loop()` periodically -- every 32nd note of the sequencer.
209
+
When AMYboard starts up, either on web or hardware, it sets up whatever patches are set to each channel (using data stored in a sketch) and then runs a "sketch" set up in `sketch.py`. This is a Python program that runs setup code (in the top level of the code file) and then calls `loop(step)` periodically -- every 32nd note of the sequencer, starting on a bar downbeat. `step` is the 32nd-note index on the sequencer's bar-locked grid (`step % 32 == 0` is a downbeat), so patterns built from it stay in sync with `sequencer.AMYSequence` patterns. (A zero-argument `loop()` also works.)
Copy file name to clipboardExpand all lines: tulip/server/amyboardworld_db_api.py
+11-14Lines changed: 11 additions & 14 deletions
Original file line number
Diff line number
Diff line change
@@ -1264,7 +1264,7 @@ def retag_hardware(
1264
1264
- Respond with ONLY the contents of sketch.py as plain MicroPython source.
1265
1265
- No Markdown, no code fences, no explanation before or after the code.
1266
1266
- Begin with comment lines, including one line of the form: # DESCRIPTION: <short summary>
1267
-
- You MUST define a top-level loop() function. It may just `pass`. loop() is called repeatedly (about every 32nd note) while the sketch runs; use it for sequencing, timing, and reading inputs.
1267
+
- You MUST define a top-level loop(step) function. It may just `pass`. loop(step) is called once per 32nd note while the sketch runs, starting on a bar downbeat; use it for sequencing, timing, and reading inputs. step is the global 32nd-note index on the sequencer's bar-locked grid: step % 32 == 0 is always a downbeat, 8 steps = one beat, and it is the same grid AMYSequence events fire on, so derive all rhythm from step (never keep your own call counter and never do BPM-to-milliseconds math).
1268
1268
- Top-level code runs once at boot (set up synths, effects, callbacks there).
1269
1269
- Only use the APIs documented below. Do NOT invent functions, modules, or parameters.
1270
1270
- Keep sketches self-contained and runnable in the web simulator: no network, no filesystem, no SD card, no long blocking loops, and never call time.sleep() inside loop().
@@ -1307,9 +1307,9 @@ def retag_hardware(
1307
1307
1308
1308
OTHER MODULES
1309
1309
- import midi: midi.add_callback(fn) registers fn(msg) for incoming MIDI. msg is a 3-byte sequence [status, data1, data2]; note-on is (status & 0xF0)==0x90 with vel>0, note-off is 0x80 (or 0x90 with vel==0).
1310
-
- import tulip: tulip.amy_ticks_ms() returns a millisecond clock. Use it in loop() to schedule events instead of sleeping.
1310
+
- import tulip: tulip.amy_ticks_ms() returns a millisecond clock. Only for non-musical timing (UI debounce etc.) -- musical timing should always come from loop(step).
1311
1311
- from music import Chord, Key: Chord("C:maj").annotations is a list of semitone offsets; Key("A:min") for scales. Root note names are 'C','C#','D','D#','E','F','F#','G','G#','A','A#','B'.
1312
-
- import sequencer: sequencer.tempo(bpm) sets the sketch tempo.
1312
+
- import sequencer: sequencer.tempo(bpm) sets the sketch tempo (the loop(step) grid follows it).
1313
1313
- Standard library available: random, math.
1314
1314
1315
1315
CONVENTIONS
@@ -1323,7 +1323,7 @@ def retag_hardware(
1323
1323
import amy
1324
1324
amy.send(synth=1, patch=256, num_voices=6)
1325
1325
1326
-
def loop():
1326
+
def loop(step):
1327
1327
pass
1328
1328
1329
1329
# AMYboard Sketch
@@ -1358,19 +1358,18 @@ def midi_cb(m):
1358
1358
1359
1359
midi.add_callback(midi_cb)
1360
1360
1361
-
def loop():
1361
+
def loop(step):
1362
1362
pass
1363
1363
1364
1364
# AMYboard Sketch
1365
1365
# DESCRIPTION: Hold MIDI keys; plays them in order as 8th-note arpeggios.
1366
-
import amy, midi, tulip
1366
+
import amy, midi, sequencer
1367
1367
1368
1368
amy.send(synth=1, grab_midi_notes=0)
1369
-
STEP_MS = 250 # 8th note at 120 BPM
1369
+
sequencer.tempo(120)
1370
1370
held = set()
1371
1371
arp_idx = 0
1372
1372
last_played = None
1373
-
last_step_ms = 0
1374
1373
1375
1374
def midi_cb(m):
1376
1375
if not m or len(m) < 3:
@@ -1385,12 +1384,10 @@ def midi_cb(m):
1385
1384
1386
1385
midi.add_callback(midi_cb)
1387
1386
1388
-
def loop():
1389
-
global arp_idx, last_played, last_step_ms
1390
-
now = tulip.amy_ticks_ms()
1391
-
if now - last_step_ms < STEP_MS:
1387
+
def loop(step):
1388
+
global arp_idx, last_played
1389
+
if step % 4: # an 8th note is 4 steps on the 32nd-note grid
0 commit comments