Without Bridge.run() in loop(), the MCU never reads incoming draw_frame requests from the router. First frame goes through during setup handshake, but subsequent frames fill the router's send buffer and the viz stalls.
Arduino Uno Q — USB Audio Synth
A 2-oscillator subtractive synthesizer running Pure Data on the Arduino Uno Q. Auto-detects its USB role at boot and works in three modes — no config switching needed.
Modes
| Mode | Connection | Audio Out | MIDI In | Use Case |
|---|---|---|---|---|
| Gadget | USB-C → Host computer | UAC1 gadget (virtual sound card) | gmidi gadget (over same cable) | Plug-and-play with DAWs |
| Host | USB hub → MIDI controller + USB audio interface | USB audio interface (your interface) | MIDI controller via hub | Standalone jamming |
| I2S DAC | I2S DAC on GPIO pins (e.g. PCM5102) | Analog line out via DAC | USB MIDI controller (gadget) | Embedded installs |
Auto-Detection
At boot, detect-usb-role.sh probes the USB controller to determine the role:
USB cable plugged into Host? ─→ gadget mode (UAC1 + gmidi)
USB device plugged into Uno? ─→ host mode (drives USB audio interface + MIDI)
Neither? ─→ defaults to gadget (safe fallback)
A udev rule (99-usb-role.rules) re-runs detection on hotplug events and
restarts Pd with the new config — unplug from computer and plug into a hub,
it just works.
Architecture
┌─────────────────────────────────────────────────────────┐
│ GADGET MODE │
│ [Host DAW] ←USB-C→ ┌──────────┐ │
│ │ gmidi │←─ ALSA seq ──→ Pd │
│ │ UAC1 │── ALSA pcm ──→ Pd │
│ └──────────┘ │
├─────────────────────────────────────────────────────────┤
│ HOST MODE │
│ [MIDI Ctrl] ─┐ │
│ [USB Audio] ─┼─USB hub─→ ┌──────────┐ │
│ │ │ USB host │ │
│ └──────────→ │ driver │──→ Pd │
│ └──────────┘ │
├─────────────────────────────────────────────────────────┤
│ I2S DAC MODE │
│ [PCM5102] ←GPIO(I2S)→ ALSA I2S driver ──→ Pd │
└─────────────────────────────────────────────────────────┘
Project Structure
uno-q-audio-synth/
├── pd/
│ └── synth.pd # Pure Data synthesizer patch
├── system/
│ ├── configure-usb-audio.sh # UAC1 + gmidi gadget setup
│ ├── configure-usb-host.sh # Host-mode USB audio discovery
│ ├── detect-usb-role.sh # Boot-time USB role detection
│ ├── detect-i2s-device.sh # I2S device auto-detection
│ ├── on-usb-role-change.sh # udev hotplug handler
│ ├── asound.conf # ALSA config (gadget mode)
│ ├── asound-i2s.conf # ALSA config (I2S DAC mode)
│ ├── 99-usb-role.rules # udev rules for hotplug
│ ├── usb-role-detect.service # Early-boot role detection
│ ├── usb-gadget-audio.service # Gadget-only service
│ ├── pd-synth-auto.service # Unified auto-detect service
│ ├── pd-synth.service # Gadget-only Pd service
│ ├── pd-synth-i2s.service # I2S-only Pd service
│ └── detect-i2s-device.service # I2S probe service
├── scripts/
│ └── start-synth.sh # Manual dev startup (all modes)
├── install.sh # Board-side dependency install
├── justfile # Task runner
└── mise.toml # Tool version management
Quick Start
Deploy to board
export BOARD_HOST=uno-q.local # or IP if mDNS not working
just deploy # copy all files
just enable-auto # enable auto-detect mode
just reboot # apply
Once booted, plug into a computer — it appears as "Uno Q Audio Synth" with audio sink + MIDI port. Or plug a USB hub with MIDI controller + audio interface for standalone play.
Development cycle
just cycle # deploy patch + restart Pd
just log # tail Pd logs
just status # role, ALSA cards, MIDI ports
Manual testing
just test-audio # test gadget audio out
just test-audio-host # test host-mode USB audio interface
just run-remote # run Pd in foreground with auto-detect
I2S DAC Wiring
Add a DAC later without reconfiguring. Common I2S DAC modules:
| DAC | Pins | Voltage |
|---|---|---|
| PCM5102 | BCK, DIN, LCK, 5V, GND | 3.3V–5V |
| MAX98357 (speaker amp) | BCLK, DIN, LRCLK, Vin, GND | 2.7V–5.5V |
| PCM5242 (balanced out) | I2S + I2C config | 3.3V |
Typical PCM5102 wiring to Uno Q GPIO:
PCM5102 → Uno Q GPIO
VIN → 5V
GND → GND
BCK → I2S_BCK (GPIO pin — check board docs)
DIN → I2S_DIN
LCK → I2S_LCK
Activate I2S mode: just enable-i2s && just reboot
Controls (MIDI)
| Control | Mapping |
|---|---|
| MIDI Note On/Off | Play notes |
| Velocity | Volume |
| CC 74 | Filter cutoff |
| GUI sliders (Pd) | Detune, volume, ADSR |
Requirements (on the Uno Q)
- Pure Data (
apt install puredata) - ALSA utilities (
apt install alsa-utils) - Linux kernel with UAC + gmidi gadget support (Qualcomm DragonWing)
Pd Patch: synth.pd
- OSC 1: Saw wave, direct pitch
- OSC 2: Saw wave, detunable (±50 cents)
- Mix: Balanced stereo mix
- Filter: Low-pass (lop~) with adjustable cutoff
- Envelope: env~ (attack, decay, sustain, release)
- Output: Stereo 48kHz 16-bit via dac~
Open in Pd GUI to adjust slider positions and save.