8181# from loop() (started by the sequencer that environment_transfer_done() kicks off)
8282# and use random() — so run-to-run they differ in the notes but not the timbre, and
8383# the timing/phase-invariant avg-spectrum compare still matches. `woodpiano` is a
84- # bare DX7 patch that only sounds when we send it MIDI notes. We send the simple
85- # MIDI pattern to every sketch (it also proves the USB-MIDI note path per sketch).
84+ # bare DX7 patch that only sounds when we send it MIDI notes. Only non-generative
85+ # sketches get the simple MIDI pattern (which also proves the USB-MIDI note path);
86+ # generative sketches are recorded as-is so the reference is purely their own output.
8687WORLD_SUITE = [
8788 {"name" : "acid_generator" , "author" : "shorepine" , "generative" : True , "min_sim" : 0.80 },
8889 {"name" : "house_generator" , "author" : "shorepine" , "generative" : True , "min_sim" : 0.80 },
@@ -176,9 +177,20 @@ def send_python(self, code, timeout=ACK_TIMEOUT):
176177 return self .link .send_python (code , timeout = timeout )
177178
178179 def load_world_sketch (self , text , settle = 1.5 ):
179- """amy.reset() → transfer → environment_transfer_done() (restarts the
180- sketch and starts the sequencer, so generative loop()s begin playing).
181- Give the sketch a moment to init before we start recording it."""
180+ """Silence the PREVIOUS sketch first, then amy.reset() → transfer →
181+ environment_transfer_done() (restarts the sketch and starts the
182+ sequencer, so generative loop()s begin playing). The old sketch's
183+ loop() keeps executing during the transfer and can re-arm sequencer
184+ events after our amy.reset(), so its audio bleeds into the next
185+ recording (heard as house_generator under woodpiano) unless the
186+ transport is stopped and the loop() killed before transferring.
187+ Give the new sketch a moment to init before we start recording it."""
188+ try :
189+ self .sysex ("zY0Z" ) # stop sequencer transport
190+ self .send_python ("import amyboard; amyboard.stop_sketch()" ) # kill loop()
191+ self .all_notes_off (0 )
192+ except Exception :
193+ pass
182194 self .link .reset_amy ()
183195 time .sleep (0.3 )
184196 self .link .transfer_file (text )
@@ -446,8 +458,10 @@ def compare(rec, ref):
446458
447459# ── AMYboard World sketch suite ──────────────────────────────────────────────
448460def run_world_suite (args , m ):
449- """Download each WORLD_SUITE sketch, push it onto the board, drive it with the
450- simple MIDI pattern, and record it. Returns [(name, recording|None, min_sim)];
461+ """Download each WORLD_SUITE sketch, push it onto the board, and record it.
462+ Generative sketches drive themselves and are recorded as-is; only
463+ non-generative patch sketches (woodpiano) get the simple MIDI pattern, which
464+ also proves the USB-MIDI note path. Returns [(name, recording|None, min_sim)];
451465 a None recording means fetch/load failed for that sketch (a test failure, but
452466 the run continues so the other sketches + artifacts are still produced)."""
453467 results = []
@@ -465,8 +479,11 @@ def run_world_suite(args, m):
465479 print (f"[world] fetched { len (text )} bytes; transferring + starting sketch" )
466480 try :
467481 m .load_world_sketch (text )
468- rec = record_and_drive (args , args .world_duration ,
469- lambda : m .play_pattern (args .world_duration ))
482+ if spec ["generative" ]:
483+ rec = record_and_drive (args , args .world_duration , lambda : None )
484+ else :
485+ rec = record_and_drive (args , args .world_duration ,
486+ lambda : m .play_pattern (args .world_duration ))
470487 except Exception as e :
471488 print (f"[world] load/record failed for { author } /{ name } : { e } " )
472489 results .append ((name , None , min_sim ))
@@ -589,6 +606,12 @@ def main():
589606 m = Midi (args .midi_port )
590607 results = []
591608 try :
609+ # The board boots into its SAVED sketch (e.g. woodpiano left over from
610+ # a previous run's transfer), which also answers our MIDI notes and
611+ # contaminates the built-in-tones recording. Silence it first so
612+ # hwci_basic only measures the default patch.
613+ m .silence ()
614+ time .sleep (0.5 )
592615 rec = record_and_drive (args , args .duration , lambda : run_test_sequence (m ))
593616 results .append ((args .name , rec , args .min_similarity ))
594617 if args .world :
0 commit comments