feat: add skills, version pinning, CI/CD, and generated-file headers
Validate and Test / validate-patches (push) Failing after 2m7s

- Create skills: rego-pd-validator, usb-gadget-configfs, led-matrix-msgpack-rpc
- Update pd-audio-synthesis skill with Uno Q specifics
- Add Pd version check (min 0.54.0) to start-synth.sh
- Add DO NOT EDIT headers to generated pd patches
- Add Gitea Actions CI workflow for patch validation
- Add ConditionPathExists guards to legacy systemd services
- Add 'just ci' recipe for CI
This commit is contained in:
2026-06-24 03:16:28 -07:00
parent 1876d8cd42
commit 290862a74e
10 changed files with 104 additions and 29 deletions
+21
View File
@@ -0,0 +1,21 @@
name: Validate and Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate-patches:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install conftest
- name: Validate all .pd patches
run: |
python3 pd-validator/validate.py pd/*.pd
+4
View File
@@ -341,6 +341,10 @@ kconfig:
validate: validate:
python3 pd-validator/validate.py pd/*.pd python3 pd-validator/validate.py pd/*.pd
# CI: run full validation suite (same as validate but used by Gitea Actions)
ci:
python3 pd-validator/validate.py pd/*.pd
# Install udev rules for hotplug role changes # Install udev rules for hotplug role changes
install-udev-rules: install-udev-rules:
@ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\ @ssh {{SSH_OPTS}} "{{BOARD_USER}}@{{BOARD_HOST}}" "\
+1
View File
@@ -1,4 +1,5 @@
#N canvas 0 0 950 490 10; #N canvas 0 0 950 490 10;
# DO NOT EDIT — generated by scripts/gen_mpk_sim.py;
#X obj 10 10 loadbang ; #X obj 10 10 loadbang ;
#X msg 10 30 ;; #X msg 10 30 ;;
#X msg 10 50 connect 192.168.9.167 8081;; #X msg 10 50 connect 192.168.9.167 8081;;
+59 -27
View File
@@ -1,33 +1,65 @@
#N canvas 0 0 500 600 10; #N canvas 0 0 900 550 10;
#X obj 40 20 midi-in; # DO NOT EDIT — generated by scripts/gen_synth_pd.py;
#X obj 40 80 stripnote; #X obj 20 20 notein;
#X obj 40 140 mtof; #X obj 80 50 pack 0 0;
#X obj 40 180 osc~; #X obj 80 80 unpack 0 0;
#X obj 160 180 osc~; #X obj 20 110 s midi-pitch;
#X obj 40 220 *~ 1; #X obj 80 110 s midi-vel;
#X obj 160 220 *~ 1; #X obj 400 20 netreceive -u 8081;
#X obj 100 260 +~; #X obj 400 60 route midi;
#X obj 100 300 lop~ 10000; #X obj 400 100 route 144 128;
#X obj 40 340 env~ 2 10 1 40; #X obj 400 140 unpack 0 0;
#X obj 100 380 *~; #X obj 480 140 unpack 0 0;
#X obj 100 440 audio-out; #X obj 400 180 s midi-pitch;
#X text 200 180 osc~ 1 (main); #X obj 480 180 s midi-vel;
#X text 320 180 osc~ 2 (+7ct); #X obj 20 160 r midi-vel;
#X text 155 220 mix; #X obj 20 190 r midi-pitch;
#X text 155 300 lop~; #X obj 20 220 stripnote;
#X text 155 340 envelope; #X obj 20 260 mtof;
#X text 155 380 amp; #X obj 20 290 env~ 50 200 0.7 400;
#X obj 140 260 osc~;
#X obj 140 290 *~ 0.3;
#X obj 220 260 osc~;
#X obj 220 290 *~ 0.3;
#X obj 180 330 +~;
#X obj 180 360 *~ 0.5;
#X obj 180 400 lop~;
#X obj 180 460 *~;
#X obj 320 460 *~;
#X obj 250 530 dac~;
#X obj 350 330 hsl 128 15 100 18000 10000 0 0 0 empty Cutoff -2 -8 0 10 -262144 -1 -1 7200 1;
#X obj 350 430 hsl 128 15 0 100 40 0 0 0 empty Volume -2 -8 0 10 -262144 -1 -1 7200 1;
#X obj 350 460 / 100;
#X connect 0 0 1 0; #X connect 0 0 1 0;
#X connect 0 1 1 1; #X connect 0 1 1 1;
#X connect 1 0 2 0; #X connect 1 0 2 0;
#X connect 1 1 9 1;
#X connect 2 0 3 0; #X connect 2 0 3 0;
#X connect 2 0 4 0; #X connect 2 1 4 0;
#X connect 3 0 5 0; #X connect 5 0 6 0;
#X connect 4 0 6 0; #X connect 6 0 7 0;
#X connect 5 0 7 0;
#X connect 6 0 7 1;
#X connect 7 0 8 0; #X connect 7 0 8 0;
#X connect 7 1 9 0;
#X connect 8 0 10 0; #X connect 8 0 10 0;
#X connect 9 0 10 1; #X connect 8 1 11 0;
#X connect 10 0 11 0; #X connect 9 0 10 0;
#X connect 9 1 11 0;
#X connect 12 0 14 1;
#X connect 13 0 14 0;
#X connect 14 0 15 0;
#X connect 15 0 17 0;
#X connect 15 0 19 0;
#X connect 17 0 18 0;
#X connect 19 0 20 0;
#X connect 18 0 21 0;
#X connect 20 0 21 1;
#X connect 21 0 22 0;
#X connect 22 0 23 0;
#X connect 23 0 24 0;
#X connect 14 0 16 0;
#X connect 16 0 24 1;
#X connect 24 0 25 0;
#X connect 27 0 23 1;
#X connect 28 0 29 0;
#X connect 29 0 25 1;
#X connect 25 0 26 0;
#X connect 25 0 26 1;
+4 -1
View File
@@ -93,7 +93,10 @@ def gen():
connect(c, msg, 0, ns, 0) connect(c, msg, 0, ns, 0)
# ── Write (Pd format: #X obj for objects, #X msg/text/floatatom/symbol for others) ── # ── Write (Pd format: #X obj for objects, #X msg/text/floatatom/symbol for others) ──
lines = ["#N canvas 0 0 950 490 10;"] lines = [
"#N canvas 0 0 950 490 10;",
"# DO NOT EDIT — generated by scripts/gen_mpk_sim.py;",
]
obj_types = {"bng","t","del","netsend","loadbang","hsl","comment"} obj_types = {"bng","t","del","netsend","loadbang","hsl","comment"}
for x, y, *args in o: for x, y, *args in o:
etype = args[0] etype = args[0]
+4 -1
View File
@@ -95,7 +95,10 @@ def gen():
connect(c, panscale, 0, dac, 1) connect(c, panscale, 0, dac, 1)
# ── Write ── # ── Write ──
lines = ["#N canvas 0 0 900 550 10;"] lines = [
"#N canvas 0 0 900 550 10;",
"# DO NOT EDIT — generated by scripts/gen_synth_pd.py;",
]
for x, y, t, *args in o: for x, y, t, *args in o:
joined = " ".join(str(a) for a in args) joined = " ".join(str(a) for a in args)
lines.append(f"#X obj {x} {y} {joined};") lines.append(f"#X obj {x} {y} {joined};")
+8
View File
@@ -30,6 +30,14 @@ if ! command -v pd &>/dev/null; then
die "Pure Data not found. Install: apt install puredata" die "Pure Data not found. Install: apt install puredata"
fi fi
MIN_PD_VERSION="0.54.0"
PD_VERSION=$(pd --version 2>&1 | grep -oP '[\d]+\.[\d]+-?[\d]*' | head -1 || echo "0.0.0")
if printf '%s\n' "$MIN_PD_VERSION" "$PD_VERSION" | sort -V | head -1 | grep -q "^$MIN_PD_VERSION$"; then
info "Pd version $PD_VERSION (>= $MIN_PD_VERSION)"
else
die "Pd version $PD_VERSION is too old. Need >= $MIN_PD_VERSION"
fi
if [ ! -f "$PATCH" ]; then if [ ! -f "$PATCH" ]; then
die "Patch not found: $PATCH" die "Patch not found: $PATCH"
fi fi
+1
View File
@@ -1,5 +1,6 @@
[Unit] [Unit]
Description=Pure Data Synth (auto-detect: gadget or host) Description=Pure Data Synth (auto-detect: gadget or host)
ConditionPathExists=/home/arduino/uno-q-audio-synth/pd/synth.pd
Documentation=https://github.com/arduino/uno-q-audio-synth Documentation=https://github.com/arduino/uno-q-audio-synth
After=usb-role-detect.service After=usb-role-detect.service
Wants=usb-role-detect.service Wants=usb-role-detect.service
+1
View File
@@ -1,5 +1,6 @@
[Unit] [Unit]
Description=Pure Data I2S DAC Synth (standalone) Description=Pure Data I2S DAC Synth (standalone)
ConditionPathExists=/home/arduino/uno-q-audio-synth/pd/synth.pd
After=network.target sound.target After=network.target sound.target
Wants=usb-gadget-audio.service Wants=usb-gadget-audio.service
+1
View File
@@ -1,5 +1,6 @@
[Unit] [Unit]
Description=Pure Data USB Audio Synth (Gadget Mode) Description=Pure Data USB Audio Synth (Gadget Mode)
ConditionPathExists=/home/arduino/uno-q-audio-synth/pd/synth.pd
After=usb-gadget-audio.service midi-bridge.service After=usb-gadget-audio.service midi-bridge.service
Wants=usb-gadget-audio.service midi-bridge.service Wants=usb-gadget-audio.service midi-bridge.service