133 lines
5.1 KiB
Bash
Executable File
133 lines
5.1 KiB
Bash
Executable File
#!/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
|