Phase 1-4: Audio stack, mixer engine, MIDI, and network API
Lint & Validate / lint (push) Has been cancelled
Lint & Validate / lint (push) Has been cancelled
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)
This commit is contained in:
@@ -0,0 +1,763 @@
|
||||
# Carla Integration — Plugin Host for RPi4B Audio Mixer
|
||||
|
||||
**Date:** 2026-05-19
|
||||
**Task:** P2-R2
|
||||
**Parent:** P2-R1 (ALSA + JACK2 Low-Latency Config)
|
||||
**Target Platform:** Raspberry Pi 4B, RPi OS Lite 64-bit (Bookworm), PREEMPT_RT 6.12.y
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architecture Overview](#1-architecture-overview)
|
||||
2. [Build Carla from Source](#2-build-carla-from-source)
|
||||
3. [Build Neural Amp Modeler (NAM) LV2](#3-build-neural-amp-modeler-nam-lv2)
|
||||
4. [Carla Configuration](#4-carla-configuration)
|
||||
5. [Default Mixer Rack & Patchbay](#5-default-mixer-rack--patchbay)
|
||||
6. [JACK Auto-Connection](#6-jack-auto-connection)
|
||||
7. [Plugin Compatibility Testing](#7-plugin-compatibility-testing)
|
||||
8. [Preset & Program Management](#8-preset--program-management)
|
||||
9. [Performance Tuning for RPi4B](#9-performance-tuning-for-rpi4b)
|
||||
10. [Operational Procedures](#10-operational-procedures)
|
||||
11. [Troubleshooting](#11-troubleshooting)
|
||||
12. [File Inventory](#12-file-inventory)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Overview
|
||||
|
||||
Carla sits as the plugin host layer between JACK and the audio applications:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ User Space │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ Carla Plugin Host (carla) │ │
|
||||
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Rack Mode — 8-channel mixer │ │ │
|
||||
│ │ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────────┐ │ │ │
|
||||
│ │ │ │Ch1 │ │Ch2 │ │Ch3 │ │AUX 1/2 │ │ │ │
|
||||
│ │ │ │Gate→ │ │Gate→ │ │NAM→ │ │Reverb │ │ │ │
|
||||
│ │ │ │EQ→ │ │EQ→ │ │IR │ │Delay │ │ │ │
|
||||
│ │ │ │Comp │ │Comp │ │ │ │ │ │ │ │
|
||||
│ │ │ └──┬───┘ └──┬───┘ └──┬───┘ └────┬─────┘ │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ └────────┼────────┼───────────┘ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ ┌────▼────────▼────┐ │ │ │
|
||||
│ │ │ │ Master Limiter │ │ │ │
|
||||
│ │ │ └────────┬────────┘ │ │ │
|
||||
│ │ └──────────────────┼──────────────────────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌──────────────────▼──────────────────────────┐ │ │
|
||||
│ │ │ Patchbay — dynamic signal routing │ │ │
|
||||
│ │ │ (system capture → plugin inputs) │ │ │
|
||||
│ │ │ (plugin outputs → system playback) │ │ │
|
||||
│ │ └──────────────────┬──────────────────────────┘ │ │
|
||||
│ └─────────────────────┼──────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────▼──────────────────────────────┐ │
|
||||
│ │ JACK2 (jackd) — SCHED_FIFO prio 70-80 │ │
|
||||
│ │ ALSA backend (hw:USB), 48kHz, 128f/3p, 8ms │ │
|
||||
│ └─────────────────────┬──────────────────────────────┘ │
|
||||
├─────────────────────────┼──────────────────────────────────────┤
|
||||
│ Kernel │ │
|
||||
│ snd-usb-audio (nrpacks=1) → xHCI (IRQ prio 95) │
|
||||
│ PREEMPT_RT 6.12.y, cores 1-3 isolated for audio │
|
||||
└─────────────────────────┼──────────────────────────────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ USB Audio I/F │
|
||||
│ (class UAC2) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Plugin Format Support
|
||||
|
||||
| Format | Carla Support | Build Requirement |
|
||||
|--------|--------------|-------------------|
|
||||
| LV2 | ✓ Native | lv2-dev, lilv |
|
||||
| LADSPA | ✓ Native | Built-in |
|
||||
| DSSI | ✓ Native | Built-in |
|
||||
| VST2 | ✓ Via bridge | VST2 SDK (optional) |
|
||||
| VST3 | ✓ Via bridge | VST3 SDK (/opt/vst3sdk) |
|
||||
| SF2/SFZ| ✓ Native | libfluidsynth-dev |
|
||||
| NAM | ✓ LV2 host | See §3 below |
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
| Parameter | Value | Rationale |
|
||||
|-----------|-------|-----------|
|
||||
| Carla process mode | MultipleClients | Each plugin = separate JACK client for independent patching |
|
||||
| Default channels | 8 (expandable) | 2 mic/line, 1 guitar, 2 aux, master |
|
||||
| Plugin format priority | LV2 | Native Linux, no bridge overhead, best ARM support |
|
||||
| GUI toolkit | PyQt5 (headless supported) | Carla can run without GUI on RPi4B |
|
||||
| ARM optimization | Cortex-A72 + NEON + LTO | -march=armv8-a+crc+crypto -mtune=cortex-a72 |
|
||||
| Transport sync | Disabled | Mixer doesn't need DAW transport |
|
||||
|
||||
---
|
||||
|
||||
## 2. Build Carla from Source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Working JACK2 setup (see parent task P2-R1: `docs/audio-stack-config.md`)
|
||||
- ~500MB free disk space for build
|
||||
- Internet connection for dependency download
|
||||
- **Run directly on the Raspberry Pi 4B** (not cross-compiled)
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cd /home/pi/raspberry-pi-mixer
|
||||
|
||||
# One-command build
|
||||
chmod +x scripts/carla-build.sh
|
||||
./scripts/carla-build.sh --install
|
||||
```
|
||||
|
||||
Estimated build time: 30-60 minutes on RPi4B (4 cores).
|
||||
|
||||
### What gets built
|
||||
|
||||
The script:
|
||||
1. Installs all build dependencies (Qt5, JACK, ALSA, FFmpeg, FluidSynth, OSC, etc.)
|
||||
2. Clones Carla from `https://github.com/falkTX/Carla.git`
|
||||
3. Patches the Makefile to add ARM Cortex-A72 optimization flags (`-march=armv8-a+crc+crypto -mtune=cortex-a72`)
|
||||
4. Builds with `WITH_LTO=true` for smaller, faster binary
|
||||
5. Optionally installs to `/usr/local`
|
||||
|
||||
### Cortex-A72 Optimization
|
||||
|
||||
The Makefile patch adds ARM64-specific flags to `source/Makefile.mk`:
|
||||
|
||||
```makefile
|
||||
ifeq ($(CPU_ARM64_OR_AARCH64),true)
|
||||
BASE_OPTS += -march=armv8-a+crc+crypto -mtune=cortex-a72
|
||||
endif
|
||||
```
|
||||
|
||||
The original Makefile only has NEON flags for ARM32 (`Cortex-A7`). ARM64/aarch64 uses the compiler's default NEON support (always available on ARMv8-A).
|
||||
|
||||
### VST3 Support
|
||||
|
||||
VST3 plugin hosting requires the Steinberg VST3 SDK:
|
||||
|
||||
```bash
|
||||
sudo git clone --recursive https://github.com/steinbergmedia/vst3sdk.git /opt/vst3sdk
|
||||
```
|
||||
|
||||
Then rebuild Carla. Without the SDK, LV2, LADSPA, and DSSI still work.
|
||||
|
||||
### Verification
|
||||
|
||||
```bash
|
||||
# Check binary
|
||||
file /usr/local/bin/carla
|
||||
# Expected: ELF 64-bit LSB executable, ARM aarch64
|
||||
|
||||
# Check features
|
||||
carla --help 2>&1 | head -20
|
||||
|
||||
# Test with JACK
|
||||
sudo audio-stack-start
|
||||
carla &
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Build Neural Amp Modeler (NAM) LV2
|
||||
|
||||
NAM uses the RTNeural library (real-time neural inference, header-only C++) wrapped in
|
||||
an LV2 plugin. It does NOT require libtorch/PyTorch.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cd /home/pi/raspberry-pi-mixer
|
||||
chmod +x scripts/nam-build.sh
|
||||
./scripts/nam-build.sh --install
|
||||
```
|
||||
|
||||
### Critical: Model Sizes Matter
|
||||
|
||||
| Model Size | Parameters | RPi4B Feasibility | xrun Risk at 128f |
|
||||
|------------|-----------|-------------------|-------------------|
|
||||
| Standard | ~500K+ | ✗ No | Guaranteed xruns |
|
||||
| Feather | ~100-200K | ⚠ Marginal | Occasional |
|
||||
| Nano | ~30-50K | ✓ Yes | Low |
|
||||
| Micro | ~10-20K | ✓ Yes | Very low |
|
||||
|
||||
**For RPi4B, use nano or feather models from [Tone3000](https://www.tone3000.com/search?sizes=feather).**
|
||||
|
||||
```bash
|
||||
# Download models
|
||||
mkdir -p ~/nam-models
|
||||
# Then download .nam files from Tone3000 and place them in ~/nam-models/
|
||||
```
|
||||
|
||||
### NAM + IR Chain in Carla
|
||||
|
||||
The default rack preset places NAM in Channel 3 followed by an IR loader
|
||||
for cabinet simulation:
|
||||
|
||||
```
|
||||
system:capture_3 → [Ch3 NAM] → [Ch3 IR Loader] → Master → system:playback_1/2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Carla Configuration
|
||||
|
||||
### Configuration File
|
||||
|
||||
Carla's settings are stored at `~/.config/falkTX/Carla.conf` (JSON-format).
|
||||
|
||||
Key settings for our mixer setup:
|
||||
|
||||
```json
|
||||
{
|
||||
"Main": {
|
||||
"AudioDriver": "JACK",
|
||||
"ProcessMode": "MultipleClients",
|
||||
"TransportMode": "Disabled"
|
||||
},
|
||||
"JACK": {
|
||||
"AutoConnectPorts": false,
|
||||
"RackExportPorts": true
|
||||
},
|
||||
"LV2": {
|
||||
"UIbridges": false,
|
||||
"BridgedWine": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `scripts/carla-jack-autoconnect` script handles port wiring instead of
|
||||
Carla's built-in auto-connect, giving us precise control.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# For headless operation (RPi4B without display)
|
||||
export CARLA_BRIDGE_DUMMY=1 # Disable plugin UIs
|
||||
export QT_QPA_PLATFORM=offscreen # Qt offscreen rendering
|
||||
|
||||
# Performance
|
||||
export JACK_NO_AUDIO_OUT=0 # Don't mute JACK when jackd starts
|
||||
```
|
||||
|
||||
### Carla Startup with the Rack
|
||||
|
||||
```bash
|
||||
# Start the full audio stack
|
||||
sudo audio-stack-start
|
||||
|
||||
# Launch Carla with the default mixer rack
|
||||
carla config/carla-8ch-default.carxp &
|
||||
|
||||
# Auto-wire JACK ports
|
||||
./scripts/carla-jack-autoconnect --watch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Default Mixer Rack & Patchbay
|
||||
|
||||
### Rack Layout (`config/carla-8ch-default.carxp`)
|
||||
|
||||
```
|
||||
Plugin Chain (signal flow top-to-bottom):
|
||||
─────────────────────────────────────────
|
||||
Channel 1: Gate → 12-Band EQ → Compressor
|
||||
Channel 2: Gate → 8-Band EQ → Compressor
|
||||
Channel 3: NAM → IR Loader (guitar)
|
||||
AUX 1: Hall Reverb
|
||||
AUX 2: Vintage Delay
|
||||
Master: Limiter (ceiling -1.0 dBFS)
|
||||
─────────────────────────────────────────
|
||||
```
|
||||
|
||||
All plugins use Calf Studio Gear LV2 suite for CPU-efficient DSP on ARM.
|
||||
|
||||
### Patchbay Rules (`config/carla-patchbay.xml`)
|
||||
|
||||
```
|
||||
system:capture_1 → Ch1 Gate input
|
||||
system:capture_2 → Ch2 Gate input
|
||||
system:capture_3 → Ch3 NAM input
|
||||
Master Limiter out 1 → system:playback_1
|
||||
Master Limiter out 2 → system:playback_2
|
||||
```
|
||||
|
||||
### Generating Custom Racks
|
||||
|
||||
```bash
|
||||
# 4-channel minimal rack
|
||||
python3 scripts/carla-preset-gen.py --channels 4
|
||||
|
||||
# Patchbay only
|
||||
python3 scripts/carla-preset-gen.py --patchbay
|
||||
```
|
||||
|
||||
### Adding/Removing Plugins
|
||||
|
||||
Edit the rack in Carla's GUI, or modify `scripts/carla-preset-gen.py` and regenerate.
|
||||
The preset uses standard LV2 URIs:
|
||||
- `http://calf.sourceforge.net/plugins/Gate`
|
||||
- `http://calf.sourceforge.net/plugins/Equalizer12Band`
|
||||
- `http://calf.sourceforge.net/plugins/Equalizer8Band`
|
||||
- `http://calf.sourceforge.net/plugins/Compressor`
|
||||
- `http://calf.sourceforge.net/plugins/Reverb`
|
||||
- `http://calf.sourceforge.net/plugins/VintageDelay`
|
||||
- `http://calf.sourceforge.net/plugins/Limiter`
|
||||
- `http://github.com/mikeoliphant/neural-amp-modeler-lv2`
|
||||
|
||||
---
|
||||
|
||||
## 6. JACK Auto-Connection
|
||||
|
||||
### One-shot Connection
|
||||
|
||||
```bash
|
||||
./scripts/carla-jack-autoconnect
|
||||
```
|
||||
|
||||
Connects all currently available Carla ports according to routing rules.
|
||||
|
||||
### Persistent Auto-Connect Daemon
|
||||
|
||||
```bash
|
||||
./scripts/carla-jack-autoconnect --watch
|
||||
```
|
||||
|
||||
Monitors JACK for new Carla plugin ports and wires them automatically.
|
||||
Run this in a tmux session or as a systemd user service:
|
||||
|
||||
```ini
|
||||
# ~/.config/systemd/user/carla-autoconnect.service
|
||||
[Unit]
|
||||
Description=Carla JACK Auto-Connect
|
||||
After=jackd.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/home/pi/raspberry-pi-mixer/scripts/carla-jack-autoconnect --watch
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now carla-autoconnect.service
|
||||
```
|
||||
|
||||
### Dry Run
|
||||
|
||||
```bash
|
||||
./scripts/carla-jack-autoconnect --dry-run
|
||||
```
|
||||
|
||||
Shows what would be connected without making changes.
|
||||
|
||||
### Disconnect All Carla Ports
|
||||
|
||||
```bash
|
||||
./scripts/carla-jack-autoconnect --disconnect-all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Plugin Compatibility Testing
|
||||
|
||||
### LV2 Lint Validation
|
||||
|
||||
The `lv2lint` tool checks LV2 plugin bundles for spec compliance:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/lv2lint-check.sh
|
||||
|
||||
# Check all installed LV2 plugins
|
||||
./scripts/lv2lint-check.sh
|
||||
|
||||
# Check only NAM
|
||||
./scripts/lv2lint-check.sh --nam-only
|
||||
|
||||
# Check a specific plugin
|
||||
./scripts/lv2lint-check.sh /usr/lib/lv2/calf.lv2
|
||||
|
||||
# List all installed plugins
|
||||
./scripts/lv2lint-check.sh --list
|
||||
```
|
||||
|
||||
### What gets checked
|
||||
|
||||
| Check | Description |
|
||||
|-------|-------------|
|
||||
| Manifest parsing | Valid `manifest.ttl` exists |
|
||||
| Binary architecture | Must be ARM aarch64 (not x86) |
|
||||
| Shared library deps | All `.so` dependencies resolvable |
|
||||
| URI validity | Unique, well-formed plugin URI |
|
||||
| Port definitions | Valid audio/CV/control port specs |
|
||||
| Parameter ranges | min/max/default values defined |
|
||||
| atom:Path support | Critical for NAM model selection |
|
||||
| Carla extensions | Carla-specific features if present |
|
||||
|
||||
### ARM-Specific Checks
|
||||
|
||||
- Architecture verification: ensures no x86 binaries are present
|
||||
- NEON SIMD detection: checks if plugins use NEON optimization
|
||||
- CPU feature requirements: verifies no SSSE3/AVX instructions
|
||||
|
||||
### Runtime Testing Procedure
|
||||
|
||||
```bash
|
||||
# 1. Start JACK
|
||||
sudo audio-stack-start
|
||||
|
||||
# 2. Run Carla with test rack
|
||||
carla config/carla-8ch-default.carxp &
|
||||
|
||||
# 3. Auto-connect
|
||||
sleep 2 && ./scripts/carla-jack-autoconnect
|
||||
|
||||
# 4. Check for xruns
|
||||
watch -n 1 "jack_cpu_load; echo '---'; tail -5 /tmp/jackd.log | grep -i xrun || echo 'no xruns'"
|
||||
|
||||
# 5. Verify audio path
|
||||
jack_lsp -c | grep -E "(Carla|system)"
|
||||
|
||||
# 6. Test each plugin bypass
|
||||
# Send MIDI CC or use OSC to toggle plugin bypass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Preset & Program Management
|
||||
|
||||
### CLI Interface
|
||||
|
||||
```bash
|
||||
python3 scripts/carla-preset-manager.py [command]
|
||||
```
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `list` | List all saved presets |
|
||||
| `save <name>` | Save current Carla state as preset |
|
||||
| `load <name>` | Load a preset (overwrites current state) |
|
||||
| `export <name> <file>` | Export preset + models as .tar.gz bundle |
|
||||
| `import <bundle>` | Import a preset bundle |
|
||||
| `set-model <plugin> <path>` | Set NAM model path via OSC |
|
||||
|
||||
### Preset Storage
|
||||
|
||||
- **User presets:** `~/carla-presets/*.carxp`
|
||||
- **System presets:** `config/*.carxp` (in repo)
|
||||
- **Metadata:** `~/carla-presets/*.meta.json`
|
||||
- **Models:** `~/nam-models/*.nam`
|
||||
|
||||
### Workflow: Save and Restore
|
||||
|
||||
```bash
|
||||
# Tweak your rack in Carla GUI, then:
|
||||
python3 scripts/carla-preset-manager.py save "my-live-set"
|
||||
|
||||
# Later, restore:
|
||||
python3 scripts/carla-preset-manager.py load "my-live-set"
|
||||
# Restart Carla to apply
|
||||
```
|
||||
|
||||
### Workflow: Export a Full Bundle
|
||||
|
||||
```bash
|
||||
# Export preset with model references
|
||||
python3 scripts/carla-preset-manager.py export "my-live-set" /tmp/my-set.tar.gz
|
||||
|
||||
# Transfer to another RPi4B
|
||||
scp /tmp/my-set.tar.gz pi@other-rpi:/tmp/
|
||||
ssh pi@other-rpi "python3 ~/raspberry-pi-mixer/scripts/carla-preset-manager.py import /tmp/my-set.tar.gz"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Performance Tuning for RPi4B
|
||||
|
||||
### CPU Governor
|
||||
|
||||
```bash
|
||||
# Must be 'performance' for audio
|
||||
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
|
||||
# Expected: performance
|
||||
```
|
||||
|
||||
### CPU Isolation
|
||||
|
||||
From parent task — cores 1-3 isolated for audio via `isolcpus=1-3` in cmdline.txt.
|
||||
Carla plugins run on isolated cores. Check with:
|
||||
|
||||
```bash
|
||||
taskset -cp $(pgrep carla)
|
||||
# Expected: affinity list includes 1-3
|
||||
```
|
||||
|
||||
### Carla Process Priority
|
||||
|
||||
After Carla starts:
|
||||
|
||||
```bash
|
||||
# Set Carla to SCHED_FIFO priority (JACK client)
|
||||
sudo chrt -f -p 80 $(pgrep carla)
|
||||
```
|
||||
|
||||
### NAM Performance Tuning
|
||||
|
||||
For the NAM plugin specifically:
|
||||
|
||||
1. **Use nano/feather models** — standard models cause xruns at any buffer
|
||||
2. **Increase buffer if needed** — edit `/etc/jackdrc` and change `-p 128` to `-p 256`
|
||||
3. **Disable GUI updates** — Carla GUI redraws consume CPU
|
||||
4. **Bypass unused plugins** — Carla's bypass is true DSP bypass (zero CPU)
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
# Real-time CPU load
|
||||
jack_cpu_load
|
||||
|
||||
# Per-process DSP load
|
||||
top -H -p $(pgrep carla)
|
||||
|
||||
# Xrun count
|
||||
grep -c xrun /tmp/jackd.log
|
||||
```
|
||||
|
||||
### Expected Performance
|
||||
|
||||
| Configuration | CPU Load (idle) | CPU Load (8ch active) | Headroom |
|
||||
|--------------|-----------------|-----------------------|----------|
|
||||
| No NAM, all Calf | ~8% | ~22% | 78% |
|
||||
| 1x Nano NAM | ~12% | ~35% | 65% |
|
||||
| 1x Feather NAM | ~18% | ~52% | 48% |
|
||||
| 1x Standard NAM | ~45% | ~95% | **xruns** |
|
||||
|
||||
---
|
||||
|
||||
## 10. Operational Procedures
|
||||
|
||||
### Startup Sequence
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Full audio stack startup with Carla
|
||||
|
||||
# 1. Start JACK + ALSA
|
||||
sudo audio-stack-start
|
||||
|
||||
# 2. Wait for JACK to stabilize
|
||||
sleep 3
|
||||
|
||||
# 3. Set CPU governor
|
||||
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
|
||||
|
||||
# 4. Launch Carla with default rack
|
||||
carla config/carla-8ch-default.carxp &
|
||||
CARLA_PID=$!
|
||||
|
||||
# 5. Set realtime priority
|
||||
sleep 2
|
||||
sudo chrt -f -p 80 $CARLA_PID 2>/dev/null || true
|
||||
|
||||
# 6. Auto-connect JACK ports
|
||||
./scripts/carla-jack-autoconnect &
|
||||
AUTOCONNECT_PID=$!
|
||||
|
||||
echo "Audio stack running. Carla PID: $CARLA_PID"
|
||||
```
|
||||
|
||||
### Shutdown Sequence
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Graceful shutdown
|
||||
|
||||
# 1. Disconnect all Carla ports
|
||||
./scripts/carla-jack-autoconnect --disconnect-all
|
||||
|
||||
# 2. Stop Carla
|
||||
pkill -f carla || true
|
||||
|
||||
# 3. Stop JACK
|
||||
sudo audio-stack-stop
|
||||
```
|
||||
|
||||
### Headless Operation
|
||||
|
||||
For RPi4B without a display, Carla can operate headlessly:
|
||||
|
||||
```bash
|
||||
# Run Carla without GUI
|
||||
QT_QPA_PLATFORM=offscreen carla config/carla-8ch-default.carxp &
|
||||
|
||||
# Control via OSC
|
||||
carla-control --osc-port 22752 set_parameter "Ch1 EQ" 0 0.5
|
||||
```
|
||||
|
||||
### OSC Remote Control
|
||||
|
||||
Carla exposes all parameters via OSC on port 22752 (configurable):
|
||||
|
||||
```bash
|
||||
# List plugins
|
||||
oscsend localhost 22752 /Carla/refresh
|
||||
|
||||
# Set parameter (plugin_id, param_index, value)
|
||||
oscsend localhost 22752 /Carla/set_parameter i 0 i 0 f 0.707
|
||||
|
||||
# Bypass plugin
|
||||
oscsend localhost 22752 /Carla/set_active i 0 i 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Troubleshooting
|
||||
|
||||
### Carla won't start
|
||||
|
||||
```bash
|
||||
# Check JACK is running
|
||||
pgrep jackd && echo "JACK OK" || echo "Start JACK first: sudo audio-stack-start"
|
||||
|
||||
# Check Carla binary
|
||||
ldd $(which carla) | grep "not found"
|
||||
# If any libraries missing, rebuild with: ./scripts/carla-build.sh --clean --install
|
||||
|
||||
# Run with debug output
|
||||
carla --debug 2>&1 | tail -20
|
||||
```
|
||||
|
||||
### No audio (silent, no xruns)
|
||||
|
||||
```bash
|
||||
# Check JACK connections
|
||||
jack_lsp -c | grep system
|
||||
|
||||
# Run auto-connect
|
||||
./scripts/carla-jack-autoconnect
|
||||
|
||||
# Check plugin bypass state — all bypassed = silence
|
||||
# In Carla GUI: check each plugin's bypass button
|
||||
```
|
||||
|
||||
### Xruns with NAM
|
||||
|
||||
```bash
|
||||
# 1. Switch to nano model
|
||||
# 2. Increase JACK buffer: edit /etc/jackdrc, change -p 128 to -p 256
|
||||
sudo systemctl restart jackd
|
||||
|
||||
# 3. Try feather models with buffer 256
|
||||
# 4. If still xrunning, skip NAM for live use — use it only for re-amping
|
||||
```
|
||||
|
||||
### Plugin not loading in Carla
|
||||
|
||||
```bash
|
||||
# Refresh LV2 plugin cache
|
||||
rm -f ~/.cache/carla/carla-plugin-cache
|
||||
carla --refresh &
|
||||
|
||||
# Check lv2ls
|
||||
lv2ls 2>&1 | grep -i <plugin-name>
|
||||
|
||||
# Validate the plugin
|
||||
./scripts/lv2lint-check.sh /usr/lib/lv2/<plugin-name>.lv2
|
||||
```
|
||||
|
||||
### VST3 plugins not showing
|
||||
|
||||
```bash
|
||||
# VST3 SDK was not installed during build
|
||||
ls /opt/vst3sdk && echo "SDK found" || echo "Install SDK and rebuild"
|
||||
# Rebuild: ./scripts/carla-build.sh --clean --install
|
||||
```
|
||||
|
||||
### High CPU / thermal throttling
|
||||
|
||||
```bash
|
||||
# Check CPU temp
|
||||
vcgencmd measure_temp
|
||||
|
||||
# Check if throttled
|
||||
vcgencmd get_throttled
|
||||
|
||||
# If throttling:
|
||||
# - Reduce plugin count
|
||||
# - Bypass unused plugins
|
||||
# - Add active cooling
|
||||
# - Use nano NAM models only
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. File Inventory
|
||||
|
||||
```
|
||||
raspberry-pi-mixer/
|
||||
├── scripts/
|
||||
│ ├── carla-build.sh # Build Carla from source (ARM Cortex-A72 optimized)
|
||||
│ ├── nam-build.sh # Build NAM LV2 plugin for ARM
|
||||
│ ├── carla-preset-gen.py # Generate default rack + patchbay presets
|
||||
│ ├── carla-jack-autoconnect # Auto-wire JACK ports to Carla plugins
|
||||
│ ├── carla-preset-manager.py # Save/load/export presets
|
||||
│ ├── lv2lint-check.sh # LV2 plugin validation suite
|
||||
│ ├── audio-stack-start # (from P2-R1) Full audio stack startup
|
||||
│ ├── audio-stack-stop # (from P2-R1) Full audio stack shutdown
|
||||
│ ├── jack-latency-test # (from P2-R1) Round-trip latency measurement
|
||||
│ └── jack-route-default # (from P2-R1) Default JACK routing
|
||||
├── config/
|
||||
│ ├── carla-8ch-default.carxp # Default 8-channel mixer rack preset
|
||||
│ ├── carla-patchbay.xml # Patchbay connection rules
|
||||
│ ├── jackdrc # (from P2-R1) JACK server config
|
||||
│ ├── alsa-usb.conf # (from P2-R1) ALSA USB config
|
||||
│ └── ... # (other P2-R1 configs)
|
||||
└── docs/
|
||||
├── audio-stack-config.md # (from P2-R1) Full audio stack docs
|
||||
└── carla-integration.md # THIS FILE — Carla integration docs
|
||||
```
|
||||
|
||||
### Lines of Code (new in this task)
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|------|-------|---------|
|
||||
| scripts/carla-build.sh | 196 | Carla build automation |
|
||||
| scripts/nam-build.sh | 184 | NAM LV2 build automation |
|
||||
| scripts/carla-preset-gen.py | 196 | Preset generation |
|
||||
| scripts/carla-jack-autoconnect | 208 | JACK port auto-wiring |
|
||||
| scripts/carla-preset-manager.py | 246 | Preset save/load/export |
|
||||
| scripts/lv2lint-check.sh | 217 | Plugin validation |
|
||||
| config/carla-8ch-default.carxp | auto-generated | Default rack |
|
||||
| config/carla-patchbay.xml | auto-generated | Patchbay rules |
|
||||
| docs/carla-integration.md | this file | Documentation |
|
||||
| **Total** | **~1,247 lines** | 7 scripts + 2 configs + 1 doc |
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Carla builds successfully on RPi4B (`./scripts/carla-build.sh --install`)
|
||||
- [ ] `carla --help` works and shows JACK audio driver
|
||||
- [ ] NAM LV2 builds successfully (`./scripts/nam-build.sh --install`)
|
||||
- [ ] `lv2ls | grep neural` shows NAM plugin
|
||||
- [ ] `./scripts/lv2lint-check.sh` passes with 0 errors
|
||||
- [ ] JACK starts: `sudo audio-stack-start`
|
||||
- [ ] Carla launches with rack: `carla config/carla-8ch-default.carxp`
|
||||
- [ ] Auto-connect wires ports: `./scripts/carla-jack-autoconnect`
|
||||
- [ ] Audio passes through: `jack_lsp -c` shows Carla ↔ system connections
|
||||
- [ ] No xruns at idle: `jack_cpu_load` < 10%
|
||||
- [ ] NAM loads a nano model without xruns (128f buffer)
|
||||
- [ ] Preset save/load works: `./scripts/carla-preset-manager.py save test && load test`
|
||||
- [ ] CPU governor is 'performance': `cat /sys/.../scaling_governor`
|
||||
- [ ] Carla runs at SCHED_FIFO prio 80: `chrt -p $(pgrep carla)`
|
||||
@@ -0,0 +1,516 @@
|
||||
# MIDI Controller Support — Binding + Learn Mode
|
||||
|
||||
**Date:** 2026-05-19
|
||||
**Task:** P4-R1
|
||||
**Target:** Raspberry Pi 4B, RPi OS Lite 64-bit, PREEMPT_RT kernel
|
||||
|
||||
Comprehensive MIDI controller mapping engine with learn mode, clock sync,
|
||||
and JACK MIDI bridge for the RPi Audio Mixer.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architecture Overview](#1-architecture-overview)
|
||||
2. [Hardware Setup — RPi as USB Host](#2-hardware-setup)
|
||||
3. [Supported Controllers](#3-supported-controllers)
|
||||
4. [Installation](#4-installation)
|
||||
5. [MIDI Learn Mode](#5-midi-learn-mode)
|
||||
6. [MIDI Clock Sync](#6-midi-clock-sync)
|
||||
7. [JACK MIDI Integration](#7-jack-midi-integration)
|
||||
8. [Mapping Persistence](#8-mapping-persistence)
|
||||
9. [Udev Rules — Hotplug](#9-udev-rules)
|
||||
10. [MIDI Mapper Daemon](#10-midi-mapper-daemon)
|
||||
11. [NRPN Support](#11-nrpn-support)
|
||||
12. [File Reference](#12-file-reference)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Physical World │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ X-Touch │ │ MIDImix │ │ FaderFox │ ... │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────────┼─────────────┘ │
|
||||
│ │ USB │
|
||||
├─────────────────────┼────────────────────────────────────────┤
|
||||
│ ┌──────▼──────┐ │
|
||||
│ Kernel │ xHCI USB │ udev rules (99-midi.rules) │
|
||||
│ │ Host Ctrl │ hotplug → ALSA seq clients │
|
||||
│ └──────┬──────┘ │
|
||||
│ │ /dev/snd/midi* │
|
||||
├─────────────────────┼────────────────────────────────────────┤
|
||||
│ ┌──────▼──────┐ │
|
||||
│ ALSA │ ALSA RawMIDI│ snd-usb-audio │
|
||||
│ │ / Sequencer │ snd-seq (clients 20+) │
|
||||
│ └──────┬──────┘ │
|
||||
│ │ │
|
||||
├─────────────────────┼────────────────────────────────────────┤
|
||||
│ ┌──────▼──────┐ │
|
||||
│ Python │ MIDI Engine │ midi_engine.py │
|
||||
│ (our code) │ │ - CC/NRPN parsing │
|
||||
│ │ ┌─────────┐ │ - Mapping lookup │
|
||||
│ │ │ Mapping │ │ - Value scaling │
|
||||
│ │ │ Table │ │ - Learn mode │
|
||||
│ │ └─────────┘ │ - Clock sync │
|
||||
│ └──┬───────┬───┘ │
|
||||
│ │ │ │
|
||||
│ ┌─────────▼─┐ ┌──▼──────────┐ │
|
||||
│ │ Parameter │ │ JACK MIDI │ │
|
||||
│ │ Registry │ │ Bridge │ │
|
||||
│ │ (DSP/UI) │ │ (4 ports) │ │
|
||||
│ └────────────┘ └──┬──────────┘ │
|
||||
│ │ JACK MIDI │
|
||||
├──────────────────────────┼────────────────────────────────────┤
|
||||
│ ┌──────▼──────┐ │
|
||||
│ JACK2 │ JACK MIDI │ a2jmidid (ALSA→JACK) │
|
||||
│ │ Ports │ │
|
||||
│ └──────┬──────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼──────┐ │
|
||||
│ Carla │ MIDI IN │ → soft synths, samplers │
|
||||
│ │ Ports │ │
|
||||
│ └─────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Data flow:**
|
||||
1. USB MIDI controller sends CC/NRPN messages over USB
|
||||
2. Linux kernel (snd-usb-audio) exposes them as ALSA sequencer clients
|
||||
3. Python MIDI engine reads from ALSA seq / rtmidi
|
||||
4. Engine matches messages against the mapping table
|
||||
5. Matched messages are:
|
||||
a. Sent to the Parameter Registry (for DSP processing / UI feedback)
|
||||
b. Forwarded to JACK MIDI output ports (for Carla routing)
|
||||
|
||||
---
|
||||
|
||||
## 2. Hardware Setup — RPi as USB Host
|
||||
|
||||
The Raspberry Pi 4B acts as a USB **host** (not device). USB MIDI controllers
|
||||
plug directly into the Pi's USB ports. No OTG adapter needed.
|
||||
|
||||
**Requirements:**
|
||||
- Raspberry Pi 4B with powered USB ports
|
||||
- USB MIDI class-compliant controller (99% of modern controllers)
|
||||
- No drivers needed — Linux UAC2 + snd-usb-audio handles everything
|
||||
|
||||
**Verify USB MIDI is working:**
|
||||
```bash
|
||||
# List raw MIDI devices
|
||||
ls -la /dev/snd/midi*
|
||||
|
||||
# List ALSA sequencer clients (MIDI controllers appear as clients 20+)
|
||||
cat /proc/asound/seq/clients
|
||||
|
||||
# Use our script
|
||||
./scripts/midi-mapper --list-devices
|
||||
|
||||
# Or with amidi
|
||||
amidi -l
|
||||
```
|
||||
|
||||
**Power considerations:**
|
||||
- RPi4B USB ports provide up to 1.2A total
|
||||
- Most MIDI controllers draw <100mA — safe
|
||||
- For bus-powered controllers + audio interface, use a powered USB hub
|
||||
|
||||
---
|
||||
|
||||
## 3. Supported Controllers
|
||||
|
||||
### Pre-configured profiles
|
||||
|
||||
| Manufacturer | Model | VID:PID | Faders | Knobs | Buttons | Transport |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Behringer | X-Touch Compact | 1397:00B4 | 9 | 16 | 39 | Yes |
|
||||
| Behringer | X-Touch (MCU) | 1397:00B5 | 9 | 8 | 92 | Yes |
|
||||
| FaderFox | UC4 | 16D0:0D27 | 8 | 0 | 32 | No |
|
||||
| Akai | MIDImix | 09E8:0031 | 9 | 24 | 16 | No |
|
||||
| Arturia | BeatStep | 1C75:0208 | 0 | 17 | 16 | Yes |
|
||||
| Novation | Launch Control XL | 1235:0061 | 8 | 24 | 16 | No |
|
||||
| Korg | nanoKONTROL2 | 0944:0117 | 8 | 8 | 32 | Yes |
|
||||
|
||||
### Adding a new controller
|
||||
|
||||
Add a `ControllerProfile` to `src/midi/controllers.py`:
|
||||
|
||||
```python
|
||||
ControllerProfile(
|
||||
manufacturer="YourBrand",
|
||||
model="YourModel",
|
||||
usb_vendor_id=0x1234,
|
||||
usb_product_id=0x5678,
|
||||
alsa_client_pattern="Your Model Name",
|
||||
num_faders=8,
|
||||
num_knobs=8,
|
||||
num_buttons=16,
|
||||
has_transport=True,
|
||||
controls=[
|
||||
ControllerControl("Fader 1", 0, 0, "fader"),
|
||||
# ... etc
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
To discover a controller's CC numbers:
|
||||
1. Install `aseqdump`: `sudo apt install alsa-utils`
|
||||
2. Run: `aseqdump -p <client_id>`
|
||||
3. Wiggle each control and note the CC number
|
||||
|
||||
---
|
||||
|
||||
## 4. Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# System packages
|
||||
sudo apt update
|
||||
sudo apt install -y python3 python3-pip python3-venv \
|
||||
alsa-utils a2jmidid jackd2
|
||||
|
||||
# Python dependencies
|
||||
pip install python-rtmidi # MIDI I/O via ALSA rawmidi
|
||||
pip install JACK-Client # JACK MIDI port creation
|
||||
pip install pyudev # Hotplug monitoring (optional)
|
||||
|
||||
# Install udev rules
|
||||
sudo cp config/99-midi.rules /etc/udev/rules.d/
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
### Start the JACK MIDI bridge
|
||||
|
||||
```bash
|
||||
# Start a2jmidid to bridge ALSA sequencer → JACK MIDI
|
||||
a2jmidid -e &
|
||||
|
||||
# Or let our bridge do it directly (preferred)
|
||||
./scripts/midi-mapper
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. MIDI Learn Mode
|
||||
|
||||
The interactive CLI lets you map physical controls to mixer parameters
|
||||
without editing config files.
|
||||
|
||||
```bash
|
||||
./scripts/midi-learn-cli
|
||||
```
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Plug in your MIDI controller** (before or during)
|
||||
2. **Start learn mode:** `./scripts/midi-learn-cli`
|
||||
3. **Pick a parameter:** type `l volume 0` (learn volume for channel 0)
|
||||
4. **Wiggle the control** on your physical controller
|
||||
5. **Confirm:** type `c` (or `d` to discard)
|
||||
6. **Save:** type `save` (or just `quit` — auto-saves)
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `l <param> [ch]` | Start learning for a parameter |
|
||||
| `b [ch]` | Batch learn all faders for a channel |
|
||||
| `c` | Confirm captured mapping |
|
||||
| `d` | Discard captured mapping |
|
||||
| `list` | Show all mappings |
|
||||
| `del <n>` | Delete mapping #n |
|
||||
| `save` | Save to disk |
|
||||
| `status` | Show learn state |
|
||||
| `quit` | Exit (auto-saves) |
|
||||
|
||||
### Example session
|
||||
|
||||
```
|
||||
$ ./scripts/midi-learn-cli --session live-set
|
||||
|
||||
midi-learn> l volume 0
|
||||
Listening for MIDI CC on any channel → volume CH0
|
||||
Wiggle the knob/fader you want to assign, then type 'confirm' or 'c'.
|
||||
[wiggle fader 1 on controller]
|
||||
midi-learn> c
|
||||
Confirmed: CH0 volume ← CC70
|
||||
|
||||
midi-learn> l pan 0
|
||||
Listening for MIDI CC on any channel → pan CH0
|
||||
[wiggle knob 1 on controller]
|
||||
midi-learn> c
|
||||
Confirmed: CH0 pan ← CC10
|
||||
|
||||
midi-learn> quit
|
||||
Auto-saved 2 mappings to 'live-set'.
|
||||
```
|
||||
|
||||
### Batch learn
|
||||
|
||||
Maps faders sequentially without typing between each one:
|
||||
|
||||
```
|
||||
midi-learn> b 0
|
||||
Batch learn for CH0: wiggle each control in order.
|
||||
[1/4] CH0 Vol — wiggle the fader now...
|
||||
[wiggle]
|
||||
[2/4] CH0 Pan — wiggle the pan knob now...
|
||||
[wiggle]
|
||||
[3/4] CH0 Mute — press the mute button now...
|
||||
[press]
|
||||
[4/4] CH0 Gain — wiggle the gain knob now...
|
||||
[wiggle]
|
||||
Batch learn complete: 4 mappings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. MIDI Clock Sync
|
||||
|
||||
The engine accepts MIDI clock (timing clock) messages and derives tempo (BPM).
|
||||
This can sync backing tracks, tempo-synced delays, LFOs, and sequencers.
|
||||
|
||||
### How it works
|
||||
|
||||
- MIDI clock sends **24 pulses per quarter note** (PPQN)
|
||||
- The engine measures inter-pulse intervals
|
||||
- A moving average window (96 pulses = 4 beats) smooths jitter
|
||||
- When the estimate stabilises (window half-full), `bpm_stable = True`
|
||||
- Tempo changes are reported on beat boundaries
|
||||
|
||||
### Usage in the daemon
|
||||
|
||||
The MIDI mapper daemon automatically tracks clock from any connected device:
|
||||
|
||||
```bash
|
||||
./scripts/midi-mapper
|
||||
|
||||
# Output:
|
||||
# 19:34:01 Transport: start
|
||||
# 19:34:02 Tempo: 128.5 BPM (stable=True)
|
||||
# 19:34:03 Tempo: 128.4 BPM (stable=True)
|
||||
```
|
||||
|
||||
### Transport control
|
||||
|
||||
| MIDI Message | Effect |
|
||||
|---|---|
|
||||
| START (0xFA) | Reset position, begin playing |
|
||||
| CONTINUE (0xFB) | Resume from current position |
|
||||
| STOP (0xFC) | Stop, hold position |
|
||||
| Song Position Pointer | Jump to beat position |
|
||||
|
||||
---
|
||||
|
||||
## 7. JACK MIDI Integration
|
||||
|
||||
The engine creates JACK MIDI output ports that Carla can connect to.
|
||||
|
||||
### Direct mode (preferred)
|
||||
|
||||
Uses the `jack` Python module to create JACK MIDI ports directly:
|
||||
|
||||
```bash
|
||||
./scripts/midi-mapper
|
||||
# Creates ports:
|
||||
# rpi-mixer:midi_out_0 (channel params: volume, pan, mute, etc.)
|
||||
# rpi-mixer:midi_out_1 (EQ, dynamics params)
|
||||
# rpi-mixer:midi_out_2 (FX sends/returns)
|
||||
# rpi-mixer:midi_out_3 (transport: play, stop, record)
|
||||
```
|
||||
|
||||
In Carla, connect `rpi-mixer:midi_out_*` to your soft synth's MIDI input.
|
||||
|
||||
### ALSA sequencer fallback
|
||||
|
||||
If `jack` module isn't available, the bridge uses ALSA sequencer ports.
|
||||
Run `a2jmidid -e` to bridge them to JACK:
|
||||
|
||||
```bash
|
||||
a2jmidid -e &
|
||||
./scripts/midi-mapper --no-jack
|
||||
# ALSA seq ports appear, a2jmidid auto-bridges to JACK
|
||||
```
|
||||
|
||||
### Routing mapped CCs
|
||||
|
||||
Mapped MIDI messages are forwarded to JACK ports based on parameter category:
|
||||
|
||||
| Parameter type | JACK port |
|
||||
|---|---|
|
||||
| Volume, Pan, Mute, Solo, Gain, Phase | `midi_out_0` |
|
||||
| EQ, Compressor, Gate | `midi_out_1` |
|
||||
| FX sends/returns | `midi_out_2` |
|
||||
| Transport (play, stop, record) | `midi_out_3` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Mapping Persistence
|
||||
|
||||
Mappings are stored as JSON in `~/.config/rpi-mixer/mappings/`.
|
||||
|
||||
### File format
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"session": "live-set",
|
||||
"updated": "2026-05-19T23:45:00+00:00",
|
||||
"mappings": [
|
||||
{
|
||||
"midi": {
|
||||
"type": "CONTROL_CHANGE",
|
||||
"channel": 0,
|
||||
"controller": 70,
|
||||
"is_nrpn": false,
|
||||
"nrpn_number": 0,
|
||||
"source_device": null
|
||||
},
|
||||
"target": {
|
||||
"parameter": "volume",
|
||||
"channel": 0
|
||||
},
|
||||
"value": {
|
||||
"midi_min": 0,
|
||||
"midi_max": 127,
|
||||
"param_min": -60.0,
|
||||
"param_max": 12.0,
|
||||
"invert": false,
|
||||
"curve": "linear"
|
||||
},
|
||||
"meta": {
|
||||
"label": "CH0 Vol",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Sessions
|
||||
|
||||
You can maintain multiple mapping configurations:
|
||||
|
||||
```bash
|
||||
./scripts/midi-learn-cli --session studio # Studio setup
|
||||
./scripts/midi-learn-cli --session live-set # Live performance setup
|
||||
./scripts/midi-mapper --session live-set # Launch with specific setup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Udev Rules — Hotplug
|
||||
|
||||
The udev rules in `config/99-midi.rules` provide:
|
||||
|
||||
1. **Automatic permissions:** MIDI devices owned by `audio` group (mode 0660)
|
||||
2. **Persistent symlinks:** Known controllers get stable names under `/dev/midi/`
|
||||
3. **Environment tagging:** `MIDI_CONTROLLER` env var set for known devices
|
||||
4. **Hotplug service trigger:** Systemd service can auto-restart on plug/unplug
|
||||
|
||||
Install:
|
||||
```bash
|
||||
sudo cp config/99-midi.rules /etc/udev/rules.d/
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
The Python hotplug monitor (`device_discovery.py`) uses `pyudev` to detect
|
||||
add/remove events and can re-open ports dynamically.
|
||||
|
||||
---
|
||||
|
||||
## 10. MIDI Mapper Daemon
|
||||
|
||||
### Starting
|
||||
|
||||
```bash
|
||||
# Basic usage
|
||||
./scripts/midi-mapper
|
||||
|
||||
# With specific session
|
||||
./scripts/midi-mapper --session live-set
|
||||
|
||||
# List devices
|
||||
./scripts/midi-mapper --list-devices
|
||||
|
||||
# Without JACK (headless test)
|
||||
./scripts/midi-mapper --no-jack --allow-no-device
|
||||
```
|
||||
|
||||
### Signal handling
|
||||
|
||||
- `SIGINT` (Ctrl+C): Graceful shutdown, outputs stats
|
||||
- `SIGTERM`: Same as SIGINT
|
||||
|
||||
### Stats output
|
||||
|
||||
Every 10 seconds:
|
||||
```
|
||||
19:35:01 Stats: 2847 events, 1423 mapped, 71.2 ev/s, 12 mappings active
|
||||
```
|
||||
|
||||
### systemd service (optional)
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/midi-mapper.service
|
||||
[Unit]
|
||||
Description=RPi Audio Mixer MIDI Mapper
|
||||
After=jackd.service sound.target
|
||||
Wants=jackd.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=pi
|
||||
ExecStart=/home/pi/rpi-mixer/scripts/midi-mapper --session default
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. NRPN Support
|
||||
|
||||
NRPN (Non-Registered Parameter Number) is a MIDI protocol extension that
|
||||
provides 14-bit resolution (16,384 steps vs 128 for CC). The engine
|
||||
transparently handles the NRPN state machine:
|
||||
|
||||
1. Controller sends CC 99 (NRPN MSB) + CC 98 (NRPN LSB) to select parameter
|
||||
2. Controller sends CC 6 (Data MSB) + CC 38 (Data LSB) for value
|
||||
3. Engine assembles into a 14-bit value
|
||||
4. Mapping lookup is on the full NRPN number
|
||||
|
||||
NRPN is transparent in learn mode — just wiggle the control and the
|
||||
engine detects the CC 99/98 preamble and captures the NRPN number.
|
||||
|
||||
---
|
||||
|
||||
## 12. File Reference
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|---|---|---|
|
||||
| `src/midi/types.py` | ~230 | MIDI message types, enums, data classes |
|
||||
| `src/midi/controllers.py` | ~350 | Known controller profiles database |
|
||||
| `src/midi/device_discovery.py` | ~250 | USB MIDI enumeration + hotplug monitor |
|
||||
| `src/midi/midi_engine.py` | ~260 | Core mapping engine + NRPN state machine |
|
||||
| `src/midi/midi_learn.py` | ~310 | Interactive learn mode state machine |
|
||||
| `src/midi/midi_clock.py` | ~210 | MIDI clock sync + tempo detection |
|
||||
| `src/midi/mapping_store.py` | ~140 | JSON persistence (save/load/list/delete) |
|
||||
| `src/midi/jack_midi_bridge.py` | ~240 | JACK MIDI port creation + ALSA fallback |
|
||||
| `src/midi/__init__.py` | ~40 | Package exports |
|
||||
| `src/mixer/__init__.py` | ~165 | Parameter registry + channel strip factory |
|
||||
| `scripts/midi-mapper` | ~290 | Main daemon entry point |
|
||||
| `scripts/midi-learn-cli` | ~295 | Interactive learn mode CLI |
|
||||
| `config/99-midi.rules` | ~60 | udev rules for USB MIDI hotplug |
|
||||
| `docs/midi-controller-support.md` | This file | Full documentation |
|
||||
|
||||
**Total: ~2,800 lines of Python + config + docs**
|
||||
@@ -0,0 +1,89 @@
|
||||
# Touchscreen UI Toolkit Evaluation
|
||||
|
||||
Raspberry Pi 4B, official 7" (800×480) or Waveshare 5" (720×720) DSI/HDMI
|
||||
touch displays. Requirements: faders, meters, mute/solo, routing matrix,
|
||||
plugin chain, touch gestures, DPI-aware layout.
|
||||
|
||||
## Candidates
|
||||
|
||||
### LVGL (Light and Versatile Graphics Library)
|
||||
- **Type**: C library, embedded-first, Python bindings via `lvgl` package
|
||||
- **RPi support**: Native framebuffer + SDL2/KMS backends, no X11 required
|
||||
- **Touch**: Native input device support (libinput/evdev), multi-touch, gestures
|
||||
- **DPI**: Pixel-based (px), no automatic scaling — manual math required
|
||||
- **Widgets**: Sliders, buttons, labels, charts, tables — good but basic
|
||||
- **Performance**: ⭐⭐⭐⭐⭐ — C rendering, 30-60 FPS on RPi 4
|
||||
- **Python ergonomics**: ⭐⭐ — Python bindings are auto-generated, not idiomatic;
|
||||
callbacks are C-style, error messages opaque; limited docs for Python usage
|
||||
- **Verdict**: Best performance, but Python API is friction-heavy. Good for C
|
||||
projects. Too much pain for a Python codebase.
|
||||
|
||||
### GTK 3/4
|
||||
- **Type**: C library, GObject introspection for Python (`PyGObject`)
|
||||
- **RPi support**: Requires X11 or Wayland compositor — adds RAM overhead
|
||||
- **Touch**: Mouse-emulated touch; not natively touch-optimized
|
||||
- **DPI**: CSS-based theming, but DPI scaling is complex on small screens
|
||||
- **Widgets**: Very rich (sliders, buttons, tree views, notebooks)
|
||||
- **Performance**: ⭐⭐ — X11/Wayland overhead, not optimized for embedded
|
||||
- **Python ergonomics**: ⭐⭐⭐ — GObject patterns leak through; verbose
|
||||
- **Verdict**: Desktop toolkit forced onto embedded. Heavy, poor touch UX.
|
||||
Best for desktop companion apps, not the primary mixer UI.
|
||||
|
||||
### SDL2
|
||||
- **Type**: C library, Python via `PySDL2`
|
||||
- **RPi support**: Framebuffer/KMSDRM backends, no X11 required
|
||||
- **Touch**: Raw input events only — no gesture recognition built in
|
||||
- **DPI**: None — raw pixels, manual scaling required
|
||||
- **Widgets**: None — you build every widget from primitives (rects, lines, text)
|
||||
- **Performance**: ⭐⭐⭐⭐ — low-level, fast rendering
|
||||
- **Python ergonomics**: ⭐ — no widgets, no layout engine, no theme system.
|
||||
Building a mixer UI in raw SDL2 means writing a UI toolkit from scratch.
|
||||
- **Verdict**: Great for games, terrible for complex UIs. Too much boilerplate.
|
||||
|
||||
### Kivy (selected)
|
||||
- **Type**: Python-native UI framework, OpenGL ES 2.0 backend
|
||||
- **RPi support**: Native RPi support via `kivy-rpi`; framebuffer or X11 backends;
|
||||
works with official 7" and Waveshare displays out of the box
|
||||
- **Touch**: First-class multi-touch, gesture recognition (swipe, pinch, long-press),
|
||||
mouse fallback for development
|
||||
- **DPI**: `dp()` and `sp()` (density-independent pixels/scaled pixels) —
|
||||
automatic scaling across 5" / 7" / desktop displays
|
||||
- **Widgets**: Sliders, buttons, toggles, labels, layouts, screens, tabbed panels
|
||||
- **Performance**: ⭐⭐⭐ — OpenGL-accelerated; 30-60 FPS on RPi 4 for
|
||||
typical mixer UI complexity; GPU handles rendering, CPU free for DSP
|
||||
- **Python ergonomics**: ⭐⭐⭐⭐⭐ — Pure Python, declarative KV language or
|
||||
programmatic widget construction; natural fit for the existing Python codebase
|
||||
- **Verdict**: Best balance of performance, touch UX, and Python ergonomics.
|
||||
Purpose-built for multi-touch interfaces. DPI-aware by default. Rich widget
|
||||
set plus easy custom widget creation for faders/meters.
|
||||
|
||||
## Decision: Kivy
|
||||
|
||||
| Criterion | LVGL | GTK | SDL2 | Kivy |
|
||||
|-----------|------|-----|------|------|
|
||||
| Touch UX | ★★★★ | ★★ | ★ | ★★★★★ |
|
||||
| Performance | ★★★★★ | ★★ | ★★★★ | ★★★ |
|
||||
| Python API | ★★ | ★★★ | ★ | ★★★★★ |
|
||||
| DPI scaling | ★ | ★★★ | ★ | ★★★★★ |
|
||||
| Widget richness | ★★★ | ★★★★★ | ★ | ★★★★ |
|
||||
| RPi setup | ★★★★ | ★★ | ★★★ | ★★★★ |
|
||||
|
||||
Kivy is the clear winner for a Python-project building a touch-optimized mixer
|
||||
UI on Raspberry Pi. The DPI-aware layout system means the same code works on
|
||||
5" and 7" displays without manual scaling math. Multi-touch and gestures
|
||||
come free. Custom widgets (faders, meters, knobs) are straightforward to
|
||||
build on Kivy's Canvas primitives.
|
||||
|
||||
### Installation (RPi)
|
||||
```bash
|
||||
sudo apt install python3-kivy # system package, includes RPi deps
|
||||
# or
|
||||
pip3 install kivy # + manual SDL2/OpenGL deps
|
||||
```
|
||||
|
||||
### Development (desktop)
|
||||
```bash
|
||||
pip3 install kivy
|
||||
# Run with: python3 main_touch.py
|
||||
# Mouse works as touch fallback
|
||||
```
|
||||
Reference in New Issue
Block a user