Files
raspberry-pi-mixer/scripts/lv2lint-check.sh
T
shawn 9cd8292acc
Lint & Validate / lint (push) Has been cancelled
Phase 1-4: Audio stack, mixer engine, MIDI, and network API
P2-R1: ALSA + JACK2 low-latency config (scripts, quirks, tuning)
P2-R2: Carla integration (build scripts, 8ch rack config, NAM LV2 support)
P2-R3: Plugin manager, categories, blacklist, NAM model support
P3-R1: Mixer DSP engine (channel strip, routing matrix, bus mgr, automation)
P4-R1: MIDI engine (learn mode, clock sync, HID discovery, mapping store)
P4-R2: Network API (OSC server, FastAPI REST, WebSocket, auth, rate limiter)
P5-R1: Touchscreen UI evaluation + main entry point
docs: Audio stack, Carla integration, MIDI support, UI evaluation
tests: Full test suite (292 passing)
2026-05-19 20:39:17 -04:00

225 lines
7.6 KiB
Bash

#!/bin/bash
# lv2lint-check.sh — Validate LV2 plugins for compatibility with Carla/RPi4B
# Uses lv2lint to check plugin spec compliance, plus runtime checks.
#
# Usage:
# chmod +x lv2lint-check.sh
# ./lv2lint-check.sh # Check all installed LV2 plugins
# ./lv2lint-check.sh /path/to/plugin.lv2 # Check a specific plugin
# ./lv2lint-check.sh --nam-only # Only check NAM plugin
# ./lv2lint-check.sh --list # List all installed LV2 plugins
#
# lv2lint checks (from https://git.open-music-kontrollers.ch/lv2/lv2lint):
# - URI validity, manifest parsing
# - Port definitions (in/out, control/audio/CV)
# - TTL consistency
# - Required features
# - Instantiation test
# - Parameter ranges
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[lv2lint]${NC} $*"; }
warn() { echo -e "${YELLOW}[lv2lint]${NC} $*"; }
fail() { echo -e "${RED}[lv2lint] FAIL:${NC} $*"; }
# ── Install lv2lint if not present ────────────────────
install_lv2lint() {
if ! command -v lv2lint >/dev/null 2>&1; then
log "lv2lint not found. Building from source..."
sudo apt update -qq
sudo apt install -y build-essential cmake git \
liblilv-dev libserd-dev libsord-dev libsratom-dev \
lv2-dev libexempi-dev libsndfile1-dev
LV2LINT_DIR="/tmp/lv2lint-build"
git clone --depth 1 https://git.open-music-kontrollers.ch/lv2/lv2lint "$LV2LINT_DIR" 2>/dev/null || \
git clone --depth 1 https://github.com/brummer10/lv2lint.git "$LV2LINT_DIR"
cd "$LV2LINT_DIR"
meson setup build 2>/dev/null || \
(mkdir -p build && cd build && cmake .. && make -j"$(nproc)")
if [ -d "build" ]; then
cd build
if [ -f "meson.build" ] || [ -f "../meson.build" ]; then
meson compile 2>/dev/null && sudo meson install 2>/dev/null || true
elif [ -f "Makefile" ]; then
make -j"$(nproc)" && sudo make install 2>/dev/null || true
fi
fi
if command -v lv2lint >/dev/null 2>&1; then
log "lv2lint installed successfully."
else
warn "Could not install lv2lint. Skipping spec checks."
fi
fi
}
# ── List installed LV2 plugins ────────────────────────
list_plugins() {
log "Installed LV2 plugins:"
echo ""
for dir in /usr/lib/lv2 /usr/local/lib/lv2 /usr/lib/aarch64-linux-gnu/lv2 ~/.lv2; do
if [ -d "$dir" ]; then
find "$dir" -maxdepth 2 -name "*.ttl" | while read -r ttl; do
uri=$(grep -oP 'a\s+lv2:Plugin' "$(dirname "$ttl")"/*.ttl 2>/dev/null | head -1 || true)
if [ -n "$uri" ]; then
echo " $(basename "$(dirname "$ttl")")"
fi
done | sort -u
fi
done
}
# ── Run lv2lint checks on a plugin ─────────────────────
check_plugin() {
local plugin_uri="$1"
local plugin_path="$2"
local issues=0
log "Checking: $(basename "$plugin_path")"
echo " URI: $plugin_uri"
# Manifest check
if [ ! -f "$plugin_path/manifest.ttl" ]; then
fail " Missing manifest.ttl"
issues=$((issues + 1))
fi
# Binary check
local so_file=$(find "$plugin_path" -name "*.so" 2>/dev/null | head -1)
if [ -z "$so_file" ]; then
warn " No .so file found (might be an extension-only bundle)"
else
echo " Binary: $so_file"
file "$so_file" 2>/dev/null | sed 's/^/ /'
# ARM-specific: check architecture
if file "$so_file" 2>/dev/null | grep -q "x86"; then
fail " WRONG ARCH: x86 binary on ARM. Rebuild!"
issues=$((issues + 1))
fi
# Check shared library deps
echo " Shared libs needed:"
ldd "$so_file" 2>/dev/null | grep "not found" | while read -r missing; do
fail " MISSING: $missing"
issues=$((issues + 1))
done || true
fi
# Run lv2lint if available
if command -v lv2lint >/dev/null 2>&1; then
echo " lv2lint output:"
local lint_output
lint_output=$(lv2lint -M pack -q -s skip "$plugin_uri" 2>&1) || true
if [ -n "$lint_output" ]; then
echo "$lint_output" | sed 's/^/ /'
# Count warnings/errors
local lint_issues=$(echo "$lint_output" | grep -ciE "(FAIL|ERROR|WARN)" || true)
issues=$((issues + lint_issues))
else
echo " ✓ No issues found"
fi
fi
# Check for Carla-specific features
if grep -qi "carla" "$plugin_path"/*.ttl 2>/dev/null; then
echo " ✓ Contains Carla-specific extensions"
fi
echo ""
return $issues
}
# ── Check NAM specifically ─────────────────────────────
check_nam() {
local nam_dir=""
for dir in /usr/lib/lv2/neural_amp_modeler.lv2 \
/usr/local/lib/lv2/neural_amp_modeler.lv2 \
~/.lv2/neural_amp_modeler.lv2 \
/tmp/nam-lv2-build/build/neural_amp_modeler.lv2; do
if [ -d "$dir" ]; then
nam_dir="$dir"
break
fi
done
if [ -z "$nam_dir" ]; then
warn "NAM LV2 plugin not found. Build it first: ./scripts/nam-build.sh --install"
return 1
fi
check_plugin "http://github.com/mikeoliphant/neural-amp-modeler-lv2" "$nam_dir"
# NAM-specific: check if atom:Path is supported (critical for model loading)
if grep -q "atom:Path" "$nam_dir"/*.ttl 2>/dev/null; then
log "✓ NAM supports atom:Path (model selection in Carla works)"
else
warn "NAM does not declare atom:Path support. Model loading may not work in Carla."
fi
# Performance note for RPi4B
echo ""
warn "NAM on RPi4B — performance note:"
echo " Standard models may cause xruns at 128f buffer."
echo " Use 'nano' or 'feather' models from https://www.tone3000.com/"
echo " Increase JACK buffer to 256 samples if needed."
}
# ── Check all installed plugins ────────────────────────
check_all() {
local total_issues=0
for dir in /usr/lib/lv2 /usr/local/lib/lv2 /usr/lib/aarch64-linux-gnu/lv2 ~/.lv2; do
if [ ! -d "$dir" ]; then continue; fi
for bundle in "$dir"/*/; do
if [ -f "$bundle/manifest.ttl" ]; then
# Extract URI from manifest
local uri=""
uri=$(grep -oP 'a\s+lv2:Plugin' "$bundle"/*.ttl 2>/dev/null | head -1 | sed 's/:.*//' || true)
if [ -z "$uri" ]; then
# Just use bundle name
uri="urn:lv2:$(basename "$bundle")"
fi
check_plugin "$uri" "${bundle%/}" || total_issues=$((total_issues + $?))
fi
done
done
echo ""
log "Total issues: $total_issues"
}
# ── Main ───────────────────────────────────────────────
case "${1:-}" in
--list|-l)
list_plugins
exit 0
;;
--nam-only|-n)
install_lv2lint
check_nam
exit 0
;;
"")
install_lv2lint
check_all
check_nam
;;
*)
if [ -d "$1" ]; then
install_lv2lint
check_plugin "urn:lv2:$(basename "$1")" "$1"
else
echo "Usage: $0 [--list|--nam-only|<plugin-path>]"
exit 1
fi
;;
esac