onboard audio: JMISC HPH wiring, UCM fix, update scripts for git deploy
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Executable
+132
@@ -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
|
||||
Executable
+41
@@ -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)"
|
||||
@@ -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
|
||||
Executable
+94
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Executable
+26
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user