···11+# USB MIDI Gadget for ac-native → Ableton direct
22+33+> Status: **plan / unscoped**. Lets the ac-native ThinkPad present itself
44+> to the MacBook (running Ableton) as a USB MIDI controller, bypassing the
55+> session-server WebSocket relay entirely for the lowest possible
66+> ThinkPad→Ableton latency.
77+88+## Motivation
99+1010+Current path: `notepat.mjs (ThinkPad) → UDP → session-server.aesthetic.computer:10010 → WS fanout → notepat-remote.amxd (Mac) → Operator`. That's a round trip over public internet. Observed latency is fine for async use but **40-100ms** is unavoidable even on good wifi.
1111+1212+Direct USB MIDI gets us:
1313+1414+- **~1-3ms** ThinkPad key → MacBook MIDI-in (USB controller polling + kernel hop)
1515+- No internet dependency, no session-server required
1616+- No WS subscriptions, no handle/machineId negotiation
1717+- Ableton sees it as a native MIDI controller — appears in MIDI input list, can be mapped, recorded, etc.
1818+1919+The session-server relay can stay as a **networked** backup / for multi-ThinkPad scenarios (one ThinkPad playing into multiple remote DAWs), but the USB path becomes the default for a local setup.
2020+2121+## Architecture
2222+2323+```
2424+┌───────────────────────────┐ USB-C ┌──────────────────────┐
2525+│ ThinkPad (ac-native OS) │ ═════════▶ │ MacBook (Ableton) │
2626+│ Linux g_midi gadget │ │ CoreMIDI auto-mount │
2727+│ /dev/snd/midiCxDy │ │ "Linux USB MIDI" │
2828+│ ↑ notepat.mjs writes │ │ → MIDI input list │
2929+│ raw MIDI bytes │ │ → Ableton track │
3030+└───────────────────────────┘ └──────────────────────┘
3131+```
3232+3333+`notepat.mjs` on ac-native gains a new output sink alongside the existing UDP→session-server path:
3434+3535+```
3636+keypress → playSoundKey → [ UDP relay | USB MIDI | USB audio synth | ... ]
3737+```
3838+3939+Configurable at runtime via `/mnt/config.json` (new key `usbMidiGadget: true`). Both paths can be on simultaneously — it's fire-and-forget MIDI data, the ingest endpoints are independent.
4040+4141+## Hardware prerequisites
4242+4343+### ThinkPad 11e Yoga Gen 6
4444+4545+The USB-C port must support **device/OTG mode** in its controller. Most 8th-gen Intel and newer support this through the Thunderbolt PHY, but it needs to be enabled in:
4646+4747+1. **BIOS/UEFI**: look for "USB-C device mode", "Thunderbolt configuration", or similar. On Lenovo ThinkPads this is usually under "Config → USB" or "Thunderbolt(TM) 3".
4848+2. **Kernel**: `CONFIG_USB_GADGET=y`, `CONFIG_USB_GADGETFS=y`, `CONFIG_USB_CONFIGFS=y`, `CONFIG_USB_CONFIGFS_F_MIDI=y`. Verify with `zcat /proc/config.gz | grep GADGET` on the running kernel.
4949+3. **UDC driver**: the "USB Device Controller" needs a loaded driver. On Intel platforms this is often `dwc3` or `xhci` with gadget support. Check `ls /sys/class/udc/` — there should be at least one entry when device mode is active.
5050+5151+**If the ThinkPad's USB-C is host-only**, the fallback is a USB device such as a **Raspberry Pi Zero W / 2 W** or **BeagleBone** tethered to the ThinkPad via network/serial, acting as the gadget. Out of scope for this plan.
5252+5353+### MacBook
5454+5555+No setup required. macOS auto-detects USB MIDI devices on plug-in and exposes them through CoreMIDI. They show up in:
5656+5757+- Audio MIDI Setup → Window → Show MIDI Studio
5858+- Ableton → Preferences → Link Tempo MIDI → MIDI Ports → check "Track" for the gadget
5959+6060+### Cable
6161+6262+USB-C to USB-C (or USB-C to USB-A with correct gadget direction). Thunderbolt cables work but are overkill — a standard USB 2.0 data cable is plenty for MIDI (31.25 kbps).
6363+6464+## Linux gadget setup (ac-native)
6565+6666+### Option A — legacy `g_midi` driver (simplest)
6767+6868+```bash
6969+modprobe g_midi \
7070+ iProduct="AC Notepat" \
7171+ iManufacturer="Aesthetic Computer" \
7272+ id="AC_NOTEPAT"
7373+```
7474+7575+Creates `/dev/snd/midiC<N>D0` and shows up on the Mac as "AC Notepat".
7676+7777+### Option B — configfs composite gadget (more control)
7878+7979+Scripted at boot time so it survives reboots and can be composed with
8080+other gadget functions later (e.g. serial console).
8181+8282+```bash
8383+#!/bin/sh
8484+# /usr/local/bin/ac-usb-gadget-start
8585+8686+set -e
8787+GADGET=/sys/kernel/config/usb_gadget/ac_notepat
8888+mkdir -p "$GADGET"
8989+9090+echo 0x1d6b > "$GADGET/idVendor" # Linux Foundation
9191+echo 0x0104 > "$GADGET/idProduct" # Multifunction Composite Gadget
9292+echo 0x0100 > "$GADGET/bcdDevice"
9393+echo 0x0200 > "$GADGET/bcdUSB"
9494+9595+mkdir -p "$GADGET/strings/0x409"
9696+echo "ACNP-$(cat /etc/machine-id | cut -c1-8)" > "$GADGET/strings/0x409/serialnumber"
9797+echo "Aesthetic Computer" > "$GADGET/strings/0x409/manufacturer"
9898+echo "AC Notepat" > "$GADGET/strings/0x409/product"
9999+100100+mkdir -p "$GADGET/configs/c.1/strings/0x409"
101101+echo "MIDI config" > "$GADGET/configs/c.1/strings/0x409/configuration"
102102+echo 250 > "$GADGET/configs/c.1/MaxPower"
103103+104104+mkdir -p "$GADGET/functions/midi.usb0"
105105+echo 1 > "$GADGET/functions/midi.usb0/in_ports"
106106+echo 1 > "$GADGET/functions/midi.usb0/out_ports"
107107+echo 64 > "$GADGET/functions/midi.usb0/buflen" # small buffer = low latency
108108+echo 32 > "$GADGET/functions/midi.usb0/qlen"
109109+110110+ln -s "$GADGET/functions/midi.usb0" "$GADGET/configs/c.1/"
111111+112112+# Bind to the first available UDC. ls /sys/class/udc shows the devices.
113113+UDC=$(ls /sys/class/udc | head -n1)
114114+echo "$UDC" > "$GADGET/UDC"
115115+```
116116+117117+Run at boot via an ac-native init hook. A matching teardown script writes empty to `UDC` and `rmdir`s the tree.
118118+119119+### Verifying on the Mac
120120+121121+With the cable plugged in:
122122+123123+```bash
124124+system_profiler SPUSBDataType | grep -A 4 "AC Notepat"
125125+# Should list it as a USB device.
126126+127127+# In Audio MIDI Setup app: "AC Notepat" appears with a MIDI icon.
128128+```
129129+130130+In Ableton → Preferences → Link Tempo MIDI → MIDI Ports, the new input appears. Enable "Track" to make it a note source, or "Remote" to map its controls.
131131+132132+## ac-native integration
133133+134134+### New code in `fedac/native/src/`
135135+136136+`usb-midi.c` (new file):
137137+138138+```c
139139+int usb_midi_open(const char *device); // opens /dev/snd/midiC0D0
140140+void usb_midi_close(int fd);
141141+int usb_midi_send_note_on(int fd, int channel, int pitch, int velocity);
142142+int usb_midi_send_note_off(int fd, int channel, int pitch);
143143+```
144144+145145+Uses raw ALSA MIDI bytes:
146146+147147+- note-on: `0x90 | channel, pitch, velocity` (3 bytes)
148148+- note-off: `0x80 | channel, pitch, 0`
149149+150150+`write(fd, buf, 3)` on the MIDI char device. Non-blocking mode preferred so a stalled receiver doesn't wedge the audio thread.
151151+152152+### JS bindings (`fedac/native/src/js-bindings.c`)
153153+154154+Mirror the existing `system.udp.sendMidi` helpers:
155155+156156+```js
157157+system.usbGadget.open() // → bool
158158+system.usbGadget.sendMidi(event, note, vel, ch)
159159+system.usbGadget.close()
160160+system.usbGadget.status // { connected, device, bytesSent }
161161+```
162162+163163+### notepat.mjs wiring
164164+165165+At the existing `sendUdpMidiEvent(…)` call site (around `fedac/native/pieces/notepat.mjs:1847-1850`), also emit USB MIDI when enabled:
166166+167167+```js
168168+const usbMidiGadgetEnabled =
169169+ cfg.usbMidiGadget === true || cfg.usbMidiGadget === "true";
170170+171171+function sendMidiEvent(system, event, midiNote, velocity, channel = 0) {
172172+ // Existing: UDP relay
173173+ if (udpMidiBroadcast && system?.udp?.connected) {
174174+ system.udp.sendMidi(event, midiNote, velocity, channel, "notepat");
175175+ }
176176+ // NEW: USB gadget direct to Mac
177177+ if (usbMidiGadgetEnabled && system?.usbGadget?.status?.connected) {
178178+ system.usbGadget.sendMidi(event, midiNote, velocity, channel);
179179+ }
180180+ // Existing telemetry counters
181181+ udpMidiSentCount += 1;
182182+ …
183183+}
184184+```
185185+186186+### Prompt command
187187+188188+Add `usb midi on/off/status` to `prompt.mjs`, parallel to the existing `midi relay on/off` command. Persists the flag to `/mnt/config.json`.
189189+190190+## Expected latency budget
191191+192192+| Stage | Cost |
193193+|---|---|
194194+| ThinkPad key press → notepat handler | <1 ms |
195195+| JS → C `sendMidi()` binding | <0.1 ms |
196196+| `write()` → kernel USB gadget | <0.2 ms |
197197+| USB bus traversal (2× full-speed frame ≈ 1 ms polling interval) | ~1-2 ms |
198198+| MacBook USB host → CoreMIDI callback | <0.5 ms |
199199+| Ableton MIDI-in → track → instrument | <1 ms |
200200+| **Audio buffer output** (Live at 64 samples / 48 kHz) | ~1.3 ms |
201201+| **Total keypress → audible** | **~5-7 ms** |
202202+203203+Versus current session-server path (~40-100 ms), that's **~10-15× faster**.
204204+205205+## Out of scope / follow-ups
206206+207207+- **Bi-directional MIDI**: this plan is one-way ThinkPad→Mac. Receiving MIDI from Ableton back to ac-native (e.g., for playback-synced visuals) needs a `read()` loop on the same gadget endpoint. Easy to add.
208208+- **MIDI clock sync**: ac-native can emit `0xF8` every 24 PPQ to sync Ableton's transport.
209209+- **Multi-channel**: currently all notes fire on channel 0. Exposing channel selection in notepat's UI is trivial once the gadget is up.
210210+- **Power**: the ThinkPad draws from its own battery; USB in device mode doesn't supply power to the Mac. Bus power is one-directional.
211211+- **SysEx / MPE**: raw `write()` handles arbitrary MIDI bytes — both just work.
212212+213213+## Test plan
214214+215215+1. On the ThinkPad: `dmesg | grep gadget` after `g_midi` modprobe — expect "using random self ethernet address" + MIDI gadget init lines.
216216+2. `aconnect -l` lists the gadget port.
217217+3. `amidi -l` shows the device with its hw:N,M address.
218218+4. `amidi -p hw:N,M -S '90 3C 7F'` plays a C4 note-on — verify by ear through Ableton after enabling the MIDI input.
219219+5. Wire into `notepat.mjs`, rebuild ac-native, flash to ThinkPad.
220220+6. Plug into Mac. Enable in Live's MIDI preferences.
221221+7. Press keys in ac-native notepat — notes should fire instantly on the Mac.
222222+8. A/B against the session-server relay path (`midi relay on`) to confirm the latency improvement.
223223+224224+## Why not this?
225225+226226+- If the ThinkPad's USB-C can't do device mode (BIOS or silicon limitation), fall back to a USB-serial adapter or a Pi Zero bridge.
227227+- If you're primarily using ac-native remotely (not physically next to the Mac), keep the session-server path — USB obviously requires a cable.
228228+- The M4L `notepat-remote.amxd` device is still useful either way: on USB MIDI the AC-native-sourced notes land on *any* MIDI track directly, no device needed; on network relay the device is required to subscribe + route.