#!/bin/bash # configure-usb-audio.sh # Configure the Arduino Uno Q as a composite USB gadget: # UAC1 (USB Audio Class) + gmidi (USB MIDI) # # The host sees one device with both an audio sink and a MIDI port, # allowing MIDI input over the same USB connection as audio output. # # Based on Linux USB gadget configfs setup. # The Uno Q's USB peripheral controller is exposed via configfs. set -euo pipefail GADGET_DIR="/sys/kernel/config/usb_gadget" GADGET_NAME="g1" UDC_DIR="/sys/class/udc" # Gadget config VID="0x2341" # Arduino VID PID="0x006A" # Custom PID (Uno Q audio + MIDI) SERIAL="$(cat /sys/firmware/devicetree/base/serial-number 2>/dev/null | tr -d '\0' || cat /proc/cpuinfo 2>/dev/null | grep Serial | head -1 | awk '{print $3}' || echo 'UnoQ')" MANUFACTURER="Arduino" PRODUCT="Uno Q Audio Synth" # USB audio params CHANNELS=2 # stereo out SAMPLE_RATE=48000 SAMPLE_SIZE=2 # bytes (2 = 16-bit; kernel UAC1 gadget expects bytes, not bits) # Find UDC (USB Device Controller) find_udc() { for controller in "$UDC_DIR"/*; do if [ -d "$controller" ]; then basename "$controller" return 0 fi done echo "ERROR: No UDC found" >&2 exit 1 } create_gadget() { local udc="$1" # Remove existing gadget if present if [ -d "$GADGET_DIR/$GADGET_NAME" ]; then echo "" > "$GADGET_DIR/$GADGET_NAME/UDC" 2>/dev/null || true for _ in $(seq 1 50); do if [ ! -f "$GADGET_DIR/$GADGET_NAME/UDC" ] || [ -z "$(cat "$GADGET_DIR/$GADGET_NAME/UDC" 2>/dev/null)" ]; then break fi sleep 0.1 done for config in "$GADGET_DIR/$GADGET_NAME/configs"/*/; do config=$(basename "$config") for func in "$GADGET_DIR/$GADGET_NAME/configs/$config/"*.*/; do func=$(basename "$func") [ -L "$GADGET_DIR/$GADGET_NAME/configs/$config/$func" ] && rm -f "$GADGET_DIR/$GADGET_NAME/configs/$config/$func" done rmdir "$GADGET_DIR/$GADGET_NAME/configs/$config" 2>/dev/null || true done for func in "$GADGET_DIR/$GADGET_NAME/functions"/*/; do func=$(basename "$func") rmdir "$GADGET_DIR/$GADGET_NAME/functions/$func" 2>/dev/null || true done rmdir "$GADGET_DIR/$GADGET_NAME" 2>/dev/null || true fi mkdir -p "$GADGET_DIR/$GADGET_NAME" echo "$VID" > "$GADGET_DIR/$GADGET_NAME/idVendor" echo "$PID" > "$GADGET_DIR/$GADGET_NAME/idProduct" echo "0x0100" > "$GADGET_DIR/$GADGET_NAME/bcdDevice" echo "0x0200" > "$GADGET_DIR/$GADGET_NAME/bcdUSB" mkdir -p "$GADGET_DIR/$GADGET_NAME/strings/0x409" echo "$SERIAL" > "$GADGET_DIR/$GADGET_NAME/strings/0x409/serialnumber" echo "$MANUFACTURER" > "$GADGET_DIR/$GADGET_NAME/strings/0x409/manufacturer" echo "$PRODUCT" > "$GADGET_DIR/$GADGET_NAME/strings/0x409/product" # Create UAC1 function (audio output, wide host compatibility) mkdir -p "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0" echo "$CHANNELS" > "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0/p_chmask" echo "$SAMPLE_RATE" > "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0/p_srate" echo "$SAMPLE_SIZE" > "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0/p_ssize" echo "$CHANNELS" > "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0/c_chmask" echo "$SAMPLE_RATE" > "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0/c_srate" echo "$SAMPLE_SIZE" > "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0/c_ssize" # Create MIDI function (MIDI input from host) mkdir -p "$GADGET_DIR/$GADGET_NAME/functions/midi.gs0" echo 1 > "$GADGET_DIR/$GADGET_NAME/functions/midi.gs0/in_ports" echo 1 > "$GADGET_DIR/$GADGET_NAME/functions/midi.gs0/out_ports" # Create configuration mkdir -p "$GADGET_DIR/$GADGET_NAME/configs/c.1" mkdir -p "$GADGET_DIR/$GADGET_NAME/configs/c.1/strings/0x409" echo "Audio + MIDI" > "$GADGET_DIR/$GADGET_NAME/configs/c.1/strings/0x409/configuration" echo 250 > "$GADGET_DIR/$GADGET_NAME/configs/c.1/MaxPower" # Link both functions to the same config (composite device) ln -s "$GADGET_DIR/$GADGET_NAME/functions/uac1.gs0" "$GADGET_DIR/$GADGET_NAME/configs/c.1/" ln -s "$GADGET_DIR/$GADGET_NAME/functions/midi.gs0" "$GADGET_DIR/$GADGET_NAME/configs/c.1/" # Bind to UDC echo "$udc" > "$GADGET_DIR/$GADGET_NAME/UDC" echo "USB gadget '$GADGET_NAME' bound to $udc (UAC1 audio + gmidi)" } case "${1:-start}" in start) udc=$(find_udc) create_gadget "$udc" ;; stop) if [ -d "$GADGET_DIR/$GADGET_NAME" ]; then udc=$(cat "$GADGET_DIR/$GADGET_NAME/UDC" 2>/dev/null || true) [ -n "$udc" ] && echo "" > "$GADGET_DIR/$GADGET_NAME/UDC" 2>/dev/null || true fi echo "USB gadget '$GADGET_NAME' stopped" ;; status) if [ -d "$GADGET_DIR/$GADGET_NAME/UDC" ]; then echo "Gadget: $(cat "$GADGET_DIR/$GADGET_NAME/UDC" 2>/dev/null || echo 'not bound')" else echo "Gadget: not configured" fi ;; *) echo "Usage: $0 {start|stop|status}" exit 1 ;; esac