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
tulip: make external_midi_sync work on the web builds via the AMY wire protocol (#1073)
tulip.external_midi_sync() was a C binding compiled out on __EMSCRIPTEN__
(micropython doesn't link AMY on web), so sketches using it raised
AttributeError on Tulip Web and AMYboard Web simulate mode -- even though
both apps already forward incoming WebMIDI bytes (including 0xF8 clock)
into the AMY worklet. It's now a Python function in tulip.py that sends
the new AMY wire command (external_midi_sync / zC, shorepine/amy#788),
one code path on all platforms, same pattern as sequencer.start/stop.
- amy submodule -> a8ab375 (PR commit; latest amy main + the zC command)
- amy_api.generated.js / patches.generated.js regenerated from the new pin
(also picks up the previously missing sequencer_run kwarg)
- refdocs/amy refreshed via sync_amy_docs.py
Tested: tulip/web build, amyboardweb stage build, macos build, AMYBOARD
idf build; in the web simulator tulip.external_midi_sync(True/False)
sends zC1Z/zC0Z to the AMY module; amy-message (native) accepts zC1/zC0.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|`im`|`grab_midi_notes`|`grab_midi_notes`| 0/1 | Use `amy.send(synth=CHANNEL, grab_midi_notes=0)` to prevent the default direct forwarding of MIDI note-on/offs to synth CHANNEL. |
220
-
|`iM`|`note_source`| 0/1 | Mark the note-ons for this synth as MIDI-derived, meaning they won't get forwarded to MIDI out (or clear that marking). |
220
+
|`iM`|`note_source`|`note_source`|0/1 | Mark the note-ons for this synth as MIDI-derived, meaning they won't get forwarded to MIDI out (or clear that marking). |
221
221
|`in`|`oscs_per_voice`|`oscs_per_voice`| >0 | Reserve this many oscs for each voice. Needed when initializing a synth (or voice) withouth an initial patch. Setting `oscs_per_voice` on an existing synth resets all oscs to their default state. |
222
222
|`io`|**TODO**|`midi_note_cmd`| M,L,N,X,O,CMD | MIDI Note on/off command for this synth. M=MIDI note number, or -1 for all notes. Other args map the velocity, as for `ic`. `%n` is substituted with the note number. |
223
223
|`ip`|`pedal`|`pedal`| int | Non-zero means pedal is down (i.e., sustain). Must be used with `synth`. |
Copy file name to clipboardExpand all lines: tulip/server/refdocs/amy/midi.md
+31-1Lines changed: 31 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,7 +49,37 @@ AMY supports MIDI realtime transport for the internal sequencer:
49
49
-`0xFA` (Start): starts sequencer playback from tick 0 in external clock mode.
50
50
-`0xFC` (Stop): stops sequencer playback.
51
51
52
+
External realtime sync is **off by default** — AMY ignores incoming clock/start/stop and keeps its own tempo. Enable it with the C API `amy_external_midi_sync(1)`, or over the wire with `amy.send(external_midi_sync=1)` (wire command `zC1`). Set it back to 0 to return to the internal clock.
53
+
52
54
MIDI Timing Clock is 24 PPQ. AMY's sequencer runs at 48 PPQ, so AMY advances two sequencer ticks per one MIDI clock pulse.
53
55
54
-
If you stop the sequencer with MIDI (using `0xFC`) the only way to start the sequencer again is with MIDI `0xFA`. We don't (yet) have AMY controls to stop / start the sequencer.
56
+
You can also start and stop the sequencer transport directly, independent of MIDI clock sync, with `amy.send(sequencer_run=1)` / `amy.send(sequencer_run=0)` (wire command `zY1` / `zY0`).
57
+
58
+
## Sending MIDI out
59
+
60
+
As well as responding to incoming MIDI, AMY can _send_ MIDI out. Set any oscillator's `wave` to `amy.AMY_MIDI` and it stops producing audio — instead, every note you play on that osc is emitted as a MIDI message out whatever MIDI interface you've configured (`amy_config.midi`, e.g. UART or USB gadget).
61
+
62
+
```python
63
+
amy.send(osc=0, wave=amy.AMY_MIDI) # osc 0 is now a MIDI sender, not an audio oscillator
amy.send(osc=0, note=60, vel=0) # -> MIDI note off (sent as a note on with velocity 0)
66
+
```
67
+
68
+
- Messages always go out on **MIDI channel 1** (status byte `0x90`).
69
+
- The event's `note` (`midi_note`) and `vel` become the MIDI note number and velocity. Velocity is scaled from AMY's `0.0`–`1.0` range to `0`–`127`. A note-off is sent as a note-on with velocity 0.
70
+
- Notes that AMY received over MIDI *in* are **not** echoed back out, so you won't create a feedback loop. Only notes you generate inside AMY — directly, or from the sequencer — are forwarded.
71
+
72
+
### Sequencing MIDI out
73
+
74
+
Because an `AMY_MIDI` osc emits MIDI in response to ordinary note events, you can drive it from [AMY's sequencer](synth.md) to get tightly-timed MIDI output — a step sequencer or drum pattern clocked by AMY that plays an external synth. Schedule note-ons and note-offs on the osc just like you would schedule audio notes:
75
+
76
+
```python
77
+
amy.send(osc=0, wave=amy.AMY_MIDI) # set up the MIDI sender once
78
+
79
+
# Send a MIDI note on channel 1 every quarter note (48 ticks), held for an eighth note.
80
+
amy.send(osc=0, note=60, vel=1, sequence="0,48,1") # note on at tick 0 of each 48-tick period
81
+
amy.send(osc=0, note=60, vel=0, sequence="24,48,2") # note off at tick 24 of each 48-tick period
82
+
```
83
+
84
+
AMY keeps sending those MIDI messages out the port at the configured tempo until you remove them (by their `tag`) or reset the sequencer. See [the sequencer docs](synth.md) for `tick` / `period` / `tag` details.
0 commit comments