# Arduino Uno Q — USB Audio Synth # Run with: just # Override board host: just BOARD_HOST=192.168.1.100 deploy set dotenv-load := true BOARD_HOST := env_var_or_default('BOARD_HOST', '192.168.9.167') BOARD_USER := env_var_or_default('BOARD_USER', 'arduino') BOARD_PASS := env_var_or_default('BOARD_PASS', 'arduino') SSH_KEY := env_var_or_default('SSH_KEY', '/home/david/.ssh/unoq_deploy_key') PD_DIR := "/home/" + BOARD_USER + "/pd" SYSTEM_DIR := "/usr/local" SSH_OPTS := "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i " + SSH_KEY default: @just --list # ───────────────────────────────────────────────────────── # FACTORY SETUP — first-time board configuration # ───────────────────────────────────────────────────────── # Full factory setup: connect to board via USB (ADB), set password, # connect WiFi, enable SSH, install Pd, deploy project factory-setup: #!/usr/bin/env bash set -e echo "[SETUP] Arduino Uno Q — Factory to Synth" echo "" # Check ADB if ! command -v adb &>/dev/null; then echo "[ERROR] adb not found. Install: sudo apt install adb" exit 1 fi DEVICE=$(adb devices | grep -v "List" | grep "device$" | head -1 | awk '{print $1}') if [ -z "$DEVICE" ]; then echo "[ERROR] No ADB device found. Connect Uno Q via USB." exit 1 fi echo "[OK] Found ADB device: $DEVICE" # Reset expired password echo "[STEP] Setting board user password..." adb -s "$DEVICE" shell 'printf "{{BOARD_PASS}}\n{{BOARD_PASS}}\n" | passwd' 2>/dev/null # Connect WiFi read -p "WiFi SSID: " WIFI_SSID read -rsp "WiFi password: " WIFI_PASS echo adb -s "$DEVICE" shell "echo '{{BOARD_PASS}}' | sudo -S nmcli device wifi connect \"$WIFI_SSID\" password \"$WIFI_PASS\"" # Get IP IP=$(adb -s "$DEVICE" shell "ip addr show wlan0 2>/dev/null | grep 'inet ' | awk '{print \$2}' | cut -d/ -f1") if [ -z "$IP" ]; then echo "[ERROR] Could not get IP address" exit 1 fi echo "[OK] Board IP: $IP" # Generate SSH host keys + enable SSH adb -s "$DEVICE" shell 'echo "{{BOARD_PASS}}" | sudo -S ssh-keygen -A 2>/dev/null' adb -s "$DEVICE" shell 'echo "{{BOARD_PASS}}" | sudo -S systemctl enable --now ssh 2>/dev/null' # Install SSH key KEY=$(cat "{{SSH_KEY}}.pub" 2>/dev/null || echo "") if [ -n "$KEY" ]; then adb -s "$DEVICE" shell "mkdir -p ~/.ssh && echo '$KEY' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh" echo "[OK] SSH key installed" else echo "[WARN] No SSH key at {{SSH_KEY}}.pub — generate with: ssh-keygen -t ed25519 -f {{SSH_KEY}}" fi # Install packages echo "[STEP] Installing puredata (this may take a while)..." ssh {{SSH_OPTS}} "{{BOARD_USER}}@$IP" 'echo "{{BOARD_PASS}}" | sudo -S apt-get install -y -qq puredata alsa-utils 2>&1 | tail -3' echo "" echo "[DONE] Board is ready at $IP" echo " Run: just BOARD_HOST=$IP setup" # ───────────────────────────────────────────────────────── # DEPLOY (git-based: commit+push locally, just deploy on board) # ───────────────────────────────────────────────────────── REPO_DIR := "/home/" + BOARD_USER + "/uno-q-audio-synth" REPO_URL := "https://git.notsosm.art/david/uno-q-audio-synth.git" # First-time install: clone repo + install services + fix UCM install: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ rm -rf {{REPO_DIR}} 2>/dev/null; \ git clone {{REPO_URL}} {{REPO_DIR}} && \ echo '[OK] Repo cloned' && \ chmod +x {{REPO_DIR}}/scripts/*.py {{REPO_DIR}}/scripts/*.sh && \ bash {{REPO_DIR}}/install.sh && \ echo '[OK] Services installed'" @echo "[OK] Run: just deploy" # Deploy + reload services after git pull deploy: @git push origin HEAD 2>&1 @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ cd {{REPO_DIR}} && git pull && \ chmod +x scripts/*.py scripts/*.sh 2>/dev/null; \ echo '{{BOARD_PASS}}' | sudo -S systemctl daemon-reload && \ echo '{{BOARD_PASS}}' | sudo -S systemctl stop midi-bridge pd-synth-onboard led-matrix-viz && \ echo '{{BOARD_PASS}}' | sudo -S systemctl restart prometheus-exporter && \ echo '{{BOARD_PASS}}' | sudo -S systemctl start synth-diagnostic && \ echo '{{BOARD_PASS}}' | sudo -S systemctl restart midi-bridge pd-synth-onboard led-matrix-viz && \ echo '[OK] Deployed'" # Start synth (onboard audio) synth-onboard: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "sudo systemctl start pd-synth-onboard && echo '[OK] Synth started'" # Stop synth synth-stop: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "sudo systemctl stop pd-synth-onboard && echo '[OK] Stopped'" # Restart all services synth-restart: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ sudo systemctl restart midi-bridge pd-synth-onboard led-matrix-viz && \ echo '[OK] All services restarted'" # Show service status status: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ echo '=== Pd ==='; pgrep -a pd | grep -v watchdog | grep -v kswapd || echo '(not running)'; \ echo '=== Services ==='; \ for svc in midi-bridge pd-synth-onboard led-matrix-viz; do \ echo -n \" \$svc: \"; systemctl is-active \$svc 2>/dev/null || echo 'inactive'; \ done; \ echo '=== Audio ==='; cat /proc/asound/cards; \ echo '=== Git ==='; cd {{REPO_DIR}} && git log --oneline -1" # Enable midi-bridge service enable-midi-bridge: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "sudo systemctl enable midi-bridge && echo '[OK] midi-bridge enabled'" # Reboot board reboot: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "echo '{{BOARD_PASS}}' | sudo -S reboot" # Fix UCM bug: removes nonexistent "RX HPH Mode" control # that prevents PipeWire from enabling the Headphones UCM device. # Must run once after OS updates or first install. fix-ucm: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ if grep -q 'RX HPH Mode' /usr/share/alsa/ucm2/codecs/qcom-lpass/rx-macro/HeadphoneEnableSeq.conf 2>/dev/null; then \ echo '{{BOARD_PASS}}' | sudo -S sed -i '/RX HPH Mode/d' /usr/share/alsa/ucm2/codecs/qcom-lpass/rx-macro/HeadphoneEnableSeq.conf && \ sudo systemctl restart wireplumber && \ echo '[OK] UCM HeadphoneEnableSeq patched'; \ else \ echo '[OK] UCM already patched'; \ fi" # Test onboard headphone audio (hw:0,0 — ArduinoImolaHPH) test-audio-headphones: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ systemctl --user stop pipewire.socket pipewire.service wireplumber 2>/dev/null; \ sleep 0.5; \ speaker-test -t sine -f 440 -l 2 -D hw:0,0 2>&1 | tail -3; \ systemctl --user start pipewire.socket pipewire.service wireplumber 2>/dev/null" # Test audio (gadget mode) test-audio: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "speaker-test -D hw:UAC1Gadget,0 -c 2 -t sine -f 440 -l 1" # Test audio (host mode — first USB audio interface) test-audio-host: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ card=\$(aplay -l 2>/dev/null | grep -i usb | grep -vi gadget | head -1 | awk '{print \$2}' | tr -d ':'); \ if [ -n \"\$card\" ]; then \ speaker-test -D hw:\$card,0 -c 2 -t sine -f 440 -l 1; \ else \ echo '[ERROR] No USB audio interface found'; \ fi" # LED matrix end-to-end diagnostic (router → MCU → UDP → viz) diag-led: @scp {{SSH_OPTS}} scripts/diag-led.sh "{{BOARD_USER}}@{{BOARD_HOST}}:/tmp/diag-led.sh" @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "chmod +x /tmp/diag-led.sh && bash /tmp/diag-led.sh" # Deploy diagnostic script to board deploy-diag: @scp {{SSH_OPTS}} scripts/diag.sh "{{BOARD_USER}}@{{BOARD_HOST}}:{{SYSTEM_DIR}}/bin/diag.sh" # Full deploy + restart synth cycle: deploy synth-stop synth-onboard # Tail synth logs (last 50 lines) log: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "tail -50 /tmp/synth.log" # Full setup: install repo on board + fix UCM + start synth setup: install fix-ucm synth-onboard @echo "[SETUP] Done. Synth is running on onboard audio." # ───────────────────────────────────────────────────────── # LOCAL TESTING (on your computer) # ───────────────────────────────────────────────────────── # Open local plugdata controller + monitor for Uno Q synth play-local: #!/usr/bin/env bash set -e echo "[PLAY] Opening plugdata controller for Uno Q synth..." echo "" if ! command -v plugdata &>/dev/null; then echo "[ERROR] plugdata not found" exit 1 fi if ! aconnect -l 2>/dev/null | grep -q "Uno Q Audio Synth"; then echo "[ERROR] Uno Q not found in ALSA sequencer — is it connected?" exit 1 fi MIDI_PORT=$(aconnect -l 2>/dev/null | grep "Uno Q Audio Synth" | head -1 | awk '{print $2}' | tr -d ':') echo " MIDI port: $MIDI_PORT (Uno Q Audio Synth)" echo " Audio in: hw:Synth,0 (UAC1 gadget — synth return)" echo " Patch: pd/controller.pd" echo "" echo " IN PLUGDATA, configure:" echo " Settings → Audio → Device Type: ALSA" echo " Settings → Audio → Input: hw:Synth,0" echo " Settings → Audio → Output: Default" echo " Settings → MIDI Input: all — MIDI Output: Uno Q" echo " Enable DSP (Ctrl+D)" echo "" plugdata pd/controller.pd # Start interactive play session: on-screen keyboard + audio monitor play: #!/usr/bin/env bash set -e echo "[PLAY] Starting Uno Q synth test environment..." echo "" # Install vmpk if missing if ! command -v vmpk &>/dev/null; then echo "[STEP] Installing vmpk..." sudo apt-get install -y -qq vmpk fi # Verify Uno Q is connected via ALSA (should be visible through PipeWire) if ! aconnect -l 2>/dev/null | grep -q "Uno Q Audio Synth"; then echo "[ERROR] Uno Q not found in ALSA sequencer" exit 1 fi if ! pw-cli list-objects 2>/dev/null | grep -q "Uno Q Audio Synth"; then echo "[ERROR] Uno Q not found via PipeWire" [ -z "$(lsusb -d 2341:006a 2>/dev/null)" ] && echo " Device not showing in lsusb either!" exit 1 fi # Start vmpk in background echo "[STEP] Starting on-screen keyboard..." vmpk & VMPK_PID=$! sleep 1 # Route vmpk MIDI -> Uno Q via ALSA sequencer VMPK_PORT=$(aconnect -l 2>/dev/null | grep -B1 "Virtual MIDI Piano" | head -1 | awk '{print $2}' | tr -d ':') UNOQ_PORT=$(aconnect -l 2>/dev/null | grep "Uno Q Audio Synth" | head -1 | awk '{print $2}' | tr -d ':') if [ -n "$VMPK_PORT" ] && [ -n "$UNOQ_PORT" ]; then aconnect "$VMPK_PORT" "$UNOQ_PORT" 2>/dev/null && echo " MIDI: vmpk -> Uno Q Synth" else echo " [WARN] Auto-connect failed. Use: aconnect \$VMPK_PORT \$UNOQ_PORT" fi # Route Uno Q audio -> host speakers via PipeWire UNOQ_CAPTURE=$(pw-cli list-objects 2>/dev/null | grep -B1 "alsa_input.*Uno_Q" | grep "node.name" | head -1 | awk -F'"' '{print $2}') HOST_OUTPUT="alsa_output.pci-0000_00_1f.3.analog-stereo" if [ -n "$UNOQ_CAPTURE" ]; then pw-link "$UNOQ_CAPTURE:capture_MONO" "$HOST_OUTPUT:playback_FL" 2>/dev/null && \ pw-link "$UNOQ_CAPTURE:capture_MONO" "$HOST_OUTPUT:playback_FR" 2>/dev/null && \ echo " Audio: Uno Q Synth -> host speakers" else echo " [WARN] Could not find Uno Q audio capture node" fi echo "" echo "==============================" echo " Synth is LIVE!" echo " Click keys in the vmpk window" echo " Close vmpk or Ctrl+C to stop" echo "==============================" echo "" wait $VMPK_PID 2>/dev/null echo "[STOP] Cleaning up..." pw-disconnect "$UNOQ_CAPTURE:capture_MONO" "$HOST_OUTPUT:playback_FL" 2>/dev/null || true pw-disconnect "$UNOQ_CAPTURE:capture_MONO" "$HOST_OUTPUT:playback_FR" 2>/dev/null || true kill $VMPK_PID 2>/dev/null || true aconnect -x 2>/dev/null || true echo "[OK] Done" # Stop test session stop-play: @pw-disconnect "$(pw-cli list-objects 2>/dev/null | grep -B1 "alsa_input.*Uno_Q" | grep "node.name" | head -1 | awk -F'"' '{print $2}'):capture_MONO" "alsa_output.pci-0000_00_1f.3.analog-stereo:playback_FL" 2>/dev/null; \ pw-disconnect "$(pw-cli list-objects 2>/dev/null | grep -B1 "alsa_input.*Uno_Q" | grep "node.name" | head -1 | awk -F'"' '{print $2}'):capture_MONO" "alsa_output.pci-0000_00_1f.3.analog-stereo:playback_FR" 2>/dev/null; \ killall vmpk 2>/dev/null; aconnect -x 2>/dev/null; echo "[OK] Stopped" # Show current MIDI routing midi-route: @echo "=== ALSA MIDI Connections ===" && aconnect -l && echo "" && echo "=== Active connections ===" && aconnect -lio 2>/dev/null | grep -A1 "Connected From\|Connected To" || echo "(none)" # ───────────────────────────────────────────────────────── # DIAGNOSTICS # ───────────────────────────────────────────────────────── # Full diagnostic dump of the board diag: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ echo '=== Git ==='; cd {{REPO_DIR}} && git log --oneline -3; \ echo '=== Pd ==='; pgrep -a pd 2>/dev/null || echo '(not running)'; \ echo '=== Audio ==='; cat /proc/asound/cards; \ echo '=== PipeWire ==='; wpctl status 2>&1 | head -10; \ echo '=== USB ==='; lsusb 2>/dev/null" # Show kernel module info for custom modules modinfo: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ for m in usb_f_uac1 usb_f_midi; do \ echo \"=== \$m ===\"; \ modinfo \$m 2>/dev/null | grep -E 'filename|description|version|parm' | awk '{print \" \" \$0}'; \ echo ''; \ done" # Check kernel config for USB gadget features kconfig: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ zgrep -E 'CONFIG_USB_(GADGET|CONFIGFS|F_UAC1|F_MIDI|DWC3|F_ACC|F_SERIAL)' /proc/config.gz 2>/dev/null | awk -F= '{printf \" %-45s %s\\n\", \$1, \$2}'" # Validate all .pd files using Conftest/OPA Rego policies validate: python3 pd-validator/validate.py pd/*.pd # Install udev rules for hotplug role changes install-udev-rules: @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ sudo cp /etc/udev/rules.d/99-usb-role.rules /etc/udev/rules.d/99-usb-role.rules.bak 2>/dev/null; \ sudo cp /tmp/99-usb-role.rules /etc/udev/rules.d/ && \ sudo udevadm control --reload-rules && \ echo '[OK] udev rules installed'"