onboard audio: JMISC HPH wiring, UCM fix, update scripts for git deploy

This commit is contained in:
2026-06-23 21:25:51 -07:00
parent a135dc4452
commit d239d58c44
22 changed files with 227 additions and 77 deletions
+68
View File
@@ -0,0 +1,68 @@
# ALSA configuration for I2S DAC (e.g. PCM5102, PCM5242, MAX98357)
# Installed alongside asound.conf — activate via:
# sudo mv /etc/asound.conf /etc/asound.conf.bak
# sudo cp /etc/asound-i2s.conf /etc/asound.conf
#
# The I2S interface on the Qualcomm DragonWing typically appears as:
# hw:0,0 (Primary MI2S — default audio path)
# hw:MI2S,0 (Named MI2S device)
#
# Find available devices: aplay -l
# Default PCM — try common I2S device names
pcm.!default {
type plug
slave {
pcm "hw:I2S,0"
format S16_LE
channels 2
rate 48000
}
hint.description "I2S DAC (48kHz stereo)"
}
# Fallback: try hw:0,0 if named device not found
pcm.i2s-default {
type plug
slave {
pcm "hw:0,0"
format S16_LE
channels 2
rate 48000
}
}
# Software volume control (I2S DACs typically have no hardware mixer)
pcm.i2s-softvol {
type softvol
slave.pcm "hw:I2S,0"
control {
name "I2S Playback Volume"
card I2S
}
min_dB -51.0
max_dB 0.0
resolution 256
}
# Direct hardware access (bypass plug/softvol for low latency)
pcm.i2s-direct {
type plug
slave {
pcm "hw:I2S,0"
format S16_LE
channels 2
rate 48000
}
}
# Loopback test (no I2S hardware connected)
pcm.loopback {
type plug
slave {
pcm "hw:Loopback,0,0"
format S16_LE
channels 2
rate 48000
}
}
+44
View File
@@ -0,0 +1,44 @@
# ALSA configuration for USB gadget audio
# The UAC1 gadget appears as a ALSA card (typically card 1 or 2).
# This config routes Pd audio output to the USB gadget.
# Default PCM device for USB gadget audio
pcm.!default {
type plug
slave {
pcm "hw:UAC1Gadget,0"
format S16_LE
channels 2
rate 48000
}
}
# Control device
ctl.!default {
type hw
card UAC1Gadget
}
# Software volume control via ALSA (hardware may not have mixer)
pcm.softvol {
type softvol
slave.pcm "hw:UAC1Gadget,0"
control {
name "Master Playback Volume"
card UAC1Gadget
}
min_dB -51.0
max_dB 0.0
resolution 256
}
# Null sink for testing when no USB gadget is connected
pcm.loopback {
type plug
slave {
pcm "hw:Loopback,0,0"
format S16_LE
channels 2
rate 48000
}
}
+132
View File
@@ -0,0 +1,132 @@
#!/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
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# configure-usb-host.sh — Wait for USB audio interface in host mode.
# Finds the first USB audio capture/playback device and writes device
# info to /run/usb-audio-dev for Pd to use.
#
# When the Uno Q acts as USB host (MIDI controller + USB audio interface
# connected via hub), the USB audio interface appears as a ALSA card
# driven by snd-usb-audio.
set -euo pipefail
DEV_FILE="/run/usb-audio-dev"
POLL_SECONDS=10
die() { echo "[USB-HOST] $*" >&2; exit 1; }
info() { echo "[USB-HOST] $*"; }
info "Waiting for USB audio interface..."
for ((i = 0; i < POLL_SECONDS; i++)); do
# Look for USB audio cards (not UAC1Gadget, not internal)
cards=$(aplay -l 2>/dev/null | grep -i "usb" | grep -vi "gadget" || true)
if [ -n "$cards" ]; then
# Pick the first USB audio card
card=$(echo "$cards" | head -1 | awk '{print $2}' | tr -d ':')
info "Found USB audio card: $card"
echo "hw:$card,0" > "$DEV_FILE"
cat "$DEV_FILE"
exit 0
fi
# Also check if any MIDI devices appeared
if command -v aseqdump &>/dev/null; then
midi_ports=$(aconnect -i -o -l 2>/dev/null | grep -i "usb" | head -1 || true)
if [ -n "$midi_ports" ]; then
info "Found USB MIDI device: $midi_ports"
fi
fi
sleep 1
done
die "No USB audio interface found after ${POLL_SECONDS}s (connect one and restart)"
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# detect-i2s-device.sh — Probe for available I2S audio devices
# and create /run/i2s-audio-dev containing the best candidate.
# Called by detect-i2s-device.service before pd-synth-i2s starts.
set -euo pipefail
DEVICE_FILE="/run/i2s-audio-dev"
write_dev() { echo "$1" > "$DEVICE_FILE"; echo "[DETECT] I2S device: $1"; exit 0; }
# Try named I2S devices first (most reliable)
for dev in "hw:I2S,0" "hw:MI2S,0" "hw:i2s,0" "hw:mi2s,0"; do
if aplay -l 2>/dev/null | grep -qi "${dev%,*}"; then
write_dev "$dev"
fi
done
# Try probing ALSA device list for anything that looks like I2S
while read -r card; do
id=$(echo "$card" | awk '{print $2}' | tr -d ':')
name=$(echo "$card" | cut -d: -f2- | xargs)
if echo "$name" | grep -qi -E "(i2s|mi2s|quat|tert|dac|audio)"; then
write_dev "hw:$id,0"
fi
done < <(cat /proc/asound/cards 2>/dev/null || true)
# Fallback to hw:0,0 (first audio device, may be HDMI or analog)
if aplay -l 2>/dev/null | grep -q "^card 0"; then
write_dev "hw:0,0"
fi
# Nothing found
echo "[DETECT] No I2S audio device found" >&2
echo "" > "$DEVICE_FILE"
exit 1
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
# detect-usb-role.sh — Detect whether USB-C should be gadget or host.
# Writes result to /run/usb-role for consumption by pd-synth-auto.service.
#
# Detection methods (tried in order):
# 1. extcon — USB cable detection (most reliable on Qualcomm)
# 2. dwc3 debug — USB controller mode
# 3. UDC state — if a UDC is bound as configured, we're in gadget mode
# 4. lsusb — if USB devices appear (after host controller init), we're host
#
# Exit code: 0 if role detected, 1 if unknown
set -euo pipefail
ROLE_FILE="/run/usb-role"
unset ROLE
die() { echo "[USB-ROLE] $*" >&2; exit 1; }
# --- Method 1: extcon ---
check_extcon() {
for path in /sys/class/extcon/*/state; do
[ -f "$path" ] || continue
local state
state=$(cat "$path" 2>/dev/null || true)
if echo "$state" | grep -q "USB=1"; then
echo "gadget"
return 0
fi
done
return 1
}
# --- Method 2: dwc3 debugfs ---
check_dwc3() {
local mode_file
for mode_file in /sys/kernel/debug/usb/dwc3*/mode; do
[ -f "$mode_file" ] || continue
local mode
mode=$(cat "$mode_file" 2>/dev/null || true)
case "$mode" in
*peripheral*) echo "gadget"; return 0 ;;
*host*) echo "host"; return 0 ;;
esac
done
return 1
}
# --- Method 3: UDC state ---
check_udc() {
for path in /sys/class/udc/*/state; do
[ -f "$path" ] || continue
local state
state=$(cat "$path" 2>/dev/null || true)
case "$state" in
configured) echo "gadget"; return 0 ;;
esac
done
return 1
}
# --- Method 4: USB host devices ---
check_lsusb() {
if command -v lsusb &>/dev/null; then
local count
count=$(lsusb 2>/dev/null | grep -v "Linux Foundation" | wc -l)
# If we see non-root-hub devices, likely we're the host
if [ "$count" -gt 1 ]; then
echo "host"
return 0
fi
fi
return 1
}
# Try methods in order
ROLE=$(check_extcon || check_dwc3 || check_udc || echo "")
# If still unknown, check lsusb after brief delay (host controller needs time)
if [ -z "$ROLE" ]; then
sleep 2
ROLE=$(check_lsusb || echo "")
fi
# Write result
if [ -n "$ROLE" ]; then
echo "$ROLE" > "$ROLE_FILE"
echo "[USB-ROLE] Detected: $ROLE"
exit 0
else
echo "[USB-ROLE] Unknown — defaulting to gadget"
echo "gadget" > "$ROLE_FILE"
exit 0
fi
+169
View File
@@ -0,0 +1,169 @@
#!/bin/bash
# diag-led.sh — LED matrix end-to-end diagnostic
# Run on the board: just diag-led
# Tests: router → MCU serial → draw_frame → OK
# UDP 8082 → viz process → rchar increase
set -euo pipefail
PASS=0; FAIL=0
pass() { echo "$*"; PASS=$((PASS+1)); }
fail() { echo "$*"; FAIL=$((FAIL+1)); }
echo "=== LED Matrix Diagnostic ==="
echo ""
# --- Test 1: Router socket ---
echo "1. Router socket"
if [ -S /var/run/arduino-router.sock ]; then
pass "socket /var/run/arduino-router.sock exists"
else
fail "router socket missing"
fi
# --- Test 2: arduino-router service ---
echo "2. arduino-router service"
if systemctl is-active arduino-router &>/dev/null; then
pass "arduino-router is running"
else
fail "arduino-router not running"
fi
# --- Test 3: RPC connectivity ---
echo "3. Router RPC connectivity"
RPC_RESPONSE=$(python3 -c "
import socket, msgpack
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.settimeout(3)
s.connect('/var/run/arduino-router.sock')
p = msgpack.Packer()
s.sendall(p.pack([0, 99, 'get_status', ['']]))
s.settimeout(1)
resp = s.recv(256)
s.close()
print('OK:' + repr(resp[:60]))
except Exception as e:
print('FAIL:' + str(e))
" 2>&1)
if echo "$RPC_RESPONSE" | grep -q "^OK:"; then
pass "get_status responded"
else
fail "get_status: $RPC_RESPONSE"
fi
# --- Test 4: draw_frame ---
echo "4. draw_frame RPC"
FRAME_RESPONSE=$(python3 -c "
import socket, msgpack, struct
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.settimeout(3)
s.connect('/var/run/arduino-router.sock')
p = msgpack.Packer()
# Alternating columns test pattern
bits = 0
for row in range(8):
for col in range(0, 13, 2):
bits |= 1 << (row * 13 + col)
f0 = bits & 0xFFFFFFFF
f1 = (bits >> 32) & 0xFFFFFFFF
f2 = (bits >> 64) & 0xFFFFFFFF
f3 = (bits >> 96) & 0xFFFFFFFF
s.sendall(p.pack([0, 100, 'draw_frame', [str(f0), str(f1), str(f2), str(f3)]]))
s.settimeout(1)
resp = s.recv(128)
s.close()
if b'OK' in resp:
print('OK')
else:
print('FAIL:' + repr(resp[:50]))
except Exception as e:
print('FAIL:' + str(e))
" 2>&1)
if echo "$FRAME_RESPONSE" | grep -q "^OK"; then
pass "draw_frame returned OK"
else
fail "draw_frame: $FRAME_RESPONSE"
fi
# --- Test 5: led-matrix-viz service ---
echo "5. led-matrix-viz service"
if systemctl is-active led-matrix-viz &>/dev/null; then
pass "led-matrix-viz is running"
else
fail "led-matrix-viz not running"
fi
# --- Test 6: UDP receive (send note, check rchar) ---
echo "6. UDP → viz → router (end-to-end)"
VIZ_PID=$(pgrep -f 'python3.*led-matrix-viz' 2>/dev/null | head -1)
if [ -z "$VIZ_PID" ]; then
fail "viz process not found"
else
R1=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep rchar | awk '{print $2}')
W1=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep wchar | awk '{print $2}')
python3 -c "
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for n in [60, 64, 67, 72, 76, 79, 84]:
s.sendto(f'midi 144 {n} 100;'.encode(), ('127.0.0.1', 8082))
s.close()
" 2>/dev/null
sleep 2
R2=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep rchar | awk '{print $2}')
W2=$(sudo cat /proc/$VIZ_PID/io 2>/dev/null | grep wchar | awk '{print $2}')
RDIFF=$((R2 - R1))
WDIFF=$((W2 - W1))
if [ "$RDIFF" -gt 0 ]; then
pass "viz received +${RDIFF}r bytes, sent +${WDIFF}w bytes to router"
else
fail "viz rchar unchanged — not receiving UDP (r=$RDIFF, w=$WDIFF)"
fi
fi
# --- Test 7: f_midi gadget ---
echo "7. f_midi gadget status"
if [ -f /proc/asound/card2/midi0 ]; then
RX=$(grep "Rx bytes" /proc/asound/card2/midi0 | awk '{print $4}')
pass "f_midi present (Rx=${RX} bytes)"
else
fail "f_midi gadget not found"
fi
# --- Test 8: midi-bridge forwarding ---
echo "8. midi-bridge service"
if systemctl is-active midi-bridge &>/dev/null; then
MB_PID=$(pgrep -f 'python3.*midi-bridge' | head -1)
pass "midi-bridge running (PID=$MB_PID)"
else
fail "midi-bridge not running"
fi
# --- Clear matrix, summary ---
python3 -c "
import socket, msgpack
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.settimeout(3)
s.connect('/var/run/arduino-router.sock')
p = msgpack.Packer()
s.sendall(p.pack([0, 200, 'clear', []]))
s.recv(128)
s.close()
" 2>/dev/null
echo ""
echo "=============================="
if [ "$FAIL" -eq 0 ]; then
echo " ALL CHECKS PASSED ($PASS/$((PASS+FAIL)))"
echo " LED matrix chain is healthy."
else
echo " $PASS passed, $FAIL failed"
fi
echo "=============================="
exit $FAIL
-1
View File
@@ -26,7 +26,6 @@ def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", VIZ_PORT))
sock.setblocking(False)
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
router.settimeout(2)
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# on-usb-role-change.sh — Called by udev on USB role/connect events.
# Re-detects role, stops/restarts Pd with appropriate config.
set -euo pipefail
ROLE_FILE="/run/usb-role"
LOG_TAG="[USB-HOTPLUG]"
# Debounce: skip if we just ran (within 3s)
LOCK="/tmp/usb-role-change.lock"
if [ -f "$LOCK" ]; then
age=$(($(date +%s) - $(stat -c %Y "$LOCK" 2>/dev/null || echo 0)))
[ "$age" -lt 3 ] && exit 0
fi
touch "$LOCK"
logger -t "$LOG_TAG" "USB role change event — re-detecting..."
/usr/local/bin/detect-usb-role.sh
# Restart Pd if it's running
if systemctl is-active pd-synth-auto &>/dev/null; then
logger -t "$LOG_TAG" "Restarting Pd with new role"
systemctl restart pd-synth-auto
fi
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
set -euo pipefail
# Reset USB controller and re-bind gadget
UDC="4e00000.usb"
GADGET="/sys/kernel/config/usb_gadget/g1"
# Find the driver binding
DRIVER=""
for d in /sys/bus/platform/drivers/*/; do
dname=$(basename "$d")
if [ -L "$d/$UDC" ] 2>/dev/null; then
DRIVER="$dname"
break
fi
done
if [ -z "$DRIVER" ]; then
# Try looking at the UDC device itself
UDC_PATH=$(readlink -f /sys/class/udc/$UDC/device 2>/dev/null || echo "")
if [ -n "$UDC_PATH" ]; then
DRIVER=$(basename "$(dirname "$UDC_PATH")" 2>/dev/null || echo "")
fi
fi
echo "Driver: ${DRIVER:-unknown}"
# Unbind
if [ -n "$DRIVER" ]; then
echo "$UDC" > "/sys/bus/platform/drivers/$DRIVER/unbind" 2>/dev/null && echo "Unbound" || echo "Unbind failed"
sleep 2
echo "$UDC" > "/sys/bus/platform/drivers/$DRIVER/bind" 2>/dev/null && echo "Rebound" || echo "Rebind failed"
sleep 1
fi
# Bind gadget
echo "$UDC" > "$GADGET/UDC" 2>&1 && echo "Gadget bound" || echo "Gadget bind failed"
cat /sys/class/udc/$UDC/state
+11 -61
View File
@@ -1,17 +1,23 @@
#!/bin/bash
set -euo pipefail
PD_DIR="/home/arduino/pd"
REPO_DIR="/home/arduino/uno-q-audio-synth"
PD_DIR="$REPO_DIR/pd"
PATCH="$PD_DIR/synth.pd"
MODE="usb"
VERBOSE=false
# Fallback to legacy path if REPO_DIR not cloned
[ -d "$PD_DIR" ] || PD_DIR="/home/arduino/pd"
PATCH="$PD_DIR/synth.pd"
die() { echo "[ERROR] $*" >&2; exit 1; }
info() { echo "[INFO] $*"; }
while [ $# -gt 0 ]; do
case "$1" in
-a|--auto) MODE="auto"; shift ;;
-o|--onboard) MODE="onboard"; shift ;;
-i|--i2s) MODE="i2s"; shift ;;
-l|--loopback) MODE="loopback"; shift ;;
-v|--verbose) VERBOSE=true; shift ;;
@@ -30,53 +36,7 @@ fi
# --- Auto-detect mode ---
if [ "$MODE" = "auto" ]; then
if [ -f /run/usb-role ]; then
ROLE=$(cat /run/usb-role)
else
ROLE=$(/usr/local/bin/detect-usb-role.sh 2>/dev/null && cat /run/usb-role || echo "gadget")
fi
case "$ROLE" in
gadget)
MODE="usb"
if ! aplay -l 2>/dev/null | grep -q "UAC1Gadget"; then
info "Configuring USB gadget..."
/usr/local/bin/configure-usb-audio.sh start 2>/dev/null || true
sleep 1
fi
;;
host)
if [ -f /run/usb-audio-dev ] && [ -s /run/usb-audio-dev ]; then
ALSA_DEV=$(cat /run/usb-audio-dev)
else
info "Detecting USB audio interface..."
/usr/local/bin/configure-usb-host.sh 2>/dev/null || {
die "No USB audio interface found. Plug one in via hub."
}
ALSA_DEV=$(cat /run/usb-audio-dev)
fi
info "Mode: USB host (MIDI controller + USB audio interface)"
# MIDI handled by midi-bridge.service → UDP → Pd netreceive
AUDIO_OPTS=(-audiooutdev 4 -audioindev 4)
PD_ARGS=(
-nogui -alsa
-send "pd dsp 1"
"${AUDIO_OPTS[@]}"
-audiobuf 20 -blocksize 64 -r 48000
-channels 2 -inchannels 0 -outchannels 2
-path "$PD_DIR" "$PATCH"
)
if [ "$VERBOSE" = true ]; then pd "${PD_ARGS[@]}"
else pd "${PD_ARGS[@]}" > /dev/null 2>&1 &
PID=$!; info "Pd started (PID: $PID)"; info " Stop: kill $PID"
fi
exit 0
;;
*)
info "Auto-detect: unknown role '$ROLE', falling back to gadget"
MODE="usb"
;;
esac
MODE="onboard"
fi
# --- Configure per-mode options ---
@@ -93,19 +53,9 @@ if [ "$MODE" = "usb" ]; then
fi
fi
if [ "$MODE" = "i2s" ]; then
if [ -f /run/i2s-audio-dev ] && [ -s /run/i2s-audio-dev ]; then
ALSA_DEV=$(cat /run/i2s-audio-dev)
else
for dev in "hw:I2S,0" "hw:MI2S,0" "hw:0,0"; do
if aplay -l 2>/dev/null | grep -qi "${dev%,*}"; then
ALSA_DEV="$dev"
break
fi
done
fi
info "Mode: I2S DAC (standalone analog audio)"
AUDIO_OPTS=(-audioadddev "$ALSA_DEV")
if [ "$MODE" = "onboard" ] || [ "$MODE" = "i2s" ]; then
info "Mode: Onboard audio (PMIC headphone / line out on JMISC)"
AUDIO_OPTS=(-audiooutdev 0 -inchannels 0 -outchannels 1)
fi
if [ "$MODE" = "loopback" ]; then