Docs: Full USB/NVMe boot coverage across all documentation
Lint & Validate / lint (push) Has been cancelled

- build-guide.md: Boot medium options table, USB/NVMe flash instructions,
  EEPROM boot order setup, first-boot wizard boot config step
- user-manual.md: Required hardware lists all 3 boot targets, EEPROM
  bootstrap instructions, NVMe HAT recommendation for live use
- hardware-compatibility.md: New storage/boot media section with NVMe
  HAT compatibility table (official, Pimoroni, Geekworm)
- Obsidian wiki: Build section with all 3 targets + EEPROM config
- developer-guide.md: Added (from task completion)
This commit is contained in:
2026-05-19 22:58:49 -04:00
parent 9e007ae197
commit e450189858
4 changed files with 852 additions and 7 deletions
+72 -4
View File
@@ -1,7 +1,8 @@
# Build Guide — SD Card Image Builder # Build Guide — Disk Image Builder (SD / USB / NVMe)
This guide walks through building a ready-to-flash Raspberry Pi SD card image This guide walks through building a ready-to-flash Raspberry Pi disk image
containing the full audio mixer stack with a PREEMPT_RT kernel. containing the full audio mixer stack with a PREEMPT_RT kernel.
Supports SD card, USB SSD, and NVMe HAT boot targets.
## Prerequisites ## Prerequisites
@@ -206,7 +207,64 @@ Partition layout:
└── logs/ System and audio logs └── logs/ System and audio logs
``` ```
## Flashing to SD Card ## Boot Medium Options
The same disk image works with three boot targets. Choose during build:
| Target | `--target` | Recommended For | Boot Order (EEPROM) |
|--------|-----------|-----------------|---------------------|
| **SD Card** | `sd` (default) | Development, testing | SD → Restart (`0xf1`) |
| **USB SSD** | `usb` | Portable use, more reliable than SD | USB → SD → Restart (`0xf41`) |
| **NVMe HAT** | `nvme` | **Production / Live use** (no USB bus contention) | NVMe → USB → SD → Restart (`0xf614`) |
### SD Card (Default)
The standard target. Simple and cheap. Works with any A2-class SD card.
Limited write speed (~90 MB/s sequential) and wear on heavy recording.
```bash
./build/build.sh # or --target sd (same thing)
```
### USB SSD
Boot from a USB 3.0 SSD via any USB port. Much faster and more durable than SD.
**But:** Shares the USB bus with audio interfaces — can cause xruns under heavy I/O.
```bash
./build/build.sh --target usb
```
The kernel includes `CONFIG_BLK_DEV_NVME` as a module when building with `--target nvme`.
For USB target, standard USB mass storage drivers are already present in the stock kernel.
### NVMe HAT
**The recommended target for production/live use.** Uses dedicated PCIe lanes —
zero USB bus contention with audio interfaces. Requires a storage HAT:
| HAT | Interface | M.2 Size | Notes |
|-----|-----------|----------|-------|
| Official RPi NVMe Base | PCIe Gen 2 x1 | 2230/2242 | Best compatibility |
| Pimoroni NVMe Base | PCIe Gen 2 x1 | 2230/2242 | Well-documented, same as official |
| Geekworm X1001 | PCIe Gen 2 x1 | 2230 | Low profile |
| Geekworm X1002 | PCIe Gen 2 x1 | 2242/2280 | Full length option |
```bash
./build/build.sh --target nvme
```
> 💡 **Tip:** Buy a 128GB or 256GB NVMe SSD. They're cheap (~$15-25) and dramatically more reliable than SD cards for live recording.
### Build Time Comparison
| Target | Build Time | Notes |
|--------|-----------|-------|
| SD | ~30-45 min | Stock kernel + config |
| USB | ~30-45 min | Same as SD, different output docs |
| NVMe | ~31-46 min | +1 min for NVMe driver compilation |
#### Flashing to SD Card
### Linux ### Linux
@@ -264,11 +322,20 @@ Use [Raspberry Pi Imager](https://www.raspberrypi.com/software/) or [balenaEtche
## First Boot ## First Boot
1. Insert the SD card into a Raspberry Pi 4B ### Initial Setup
1. Insert the boot media (SD card, USB SSD, or NVMe) into a Raspberry Pi 4B
2. Connect your USB audio interface 2. Connect your USB audio interface
3. (Optional) Connect HDMI touchscreen + Ethernet 3. (Optional) Connect HDMI touchscreen + Ethernet
4. Power on 4. Power on
> **USB/NVMe boot note:** If booting from USB SSD or NVMe, you'll need to
> configure the EEPROM boot order first. Either:
> - Boot from SD card once to run the setup wizard (it has an EEPROM config step)
> - Or configure manually: `sudo rpi-eeprom-config --edit`
> - USB: `BOOT_ORDER=0xf41`
> - NVMe: `BOOT_ORDER=0xf614`
The **first-boot setup wizard** will appear on the HDMI display (or serial The **first-boot setup wizard** will appear on the HDMI display (or serial
console). It walks through: console). It walks through:
@@ -277,6 +344,7 @@ console). It walks through:
3. Hostname setting 3. Hostname setting
4. API key generation (for web UI access) 4. API key generation (for web UI access)
5. JACK buffer size and latency preferences 5. JACK buffer size and latency preferences
6. **Boot target configuration** (USB/NVMe EEPROM setup — if accessible)
After the wizard completes, the system reboots into normal operation. After the wizard completes, the system reboots into normal operation.
The wizard auto-disables itself — it only runs once. The wizard auto-disables itself — it only runs once.
+734
View File
@@ -0,0 +1,734 @@
# Developer Guide
How to contribute to the Raspberry Pi Real-Time Audio Mixer — architecture,
development setup, testing, and contribution workflow.
## Table of Contents
1. [Architecture Overview](#1-architecture-overview)
2. [Development Setup](#2-development-setup)
3. [Project Structure](#3-project-structure)
4. [Component Deep-Dives](#4-component-deep-dives)
5. [Running Tests](#5-running-tests)
6. [Contribution Workflow](#6-contribution-workflow)
7. [Coding Conventions](#7-coding-conventions)
8. [Extending the Mixer](#8-extending-the-mixer)
---
## 1. Architecture Overview
```
┌──────────────────────────────────────────────────────────────────┐
│ main_touch.py (Kivy) src/network/run.py (FastAPI) │
│ ┌─────────────────┐ ┌────────────────────────┐ │
│ │ MixerApp │ │ NetworkServer │ │
│ │ ├── MixerScreen │ │ ├── REST API (8000) │ │
│ │ ├── RoutingScr │ │ ├── WebSocket (8000) │ │
│ │ ├── PluginScr │ │ └── OSC Server (9001) │ │
│ │ └── SettingsScr │ └───────────┬────────────┘ │
│ └────────┬─────────┘ │ │
│ │ IPC (REST + OSC) │ │
│ └───────────────┬─────────────────────┘ │
│ │ │
│ ┌──────▼──────────────────────────────┐ │
│ │ DSPEngine │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ ParameterRegistry │ │ │
│ │ │ (all mixer parameters) │ │ │
│ │ └──────────┬───────────────────┘ │ │
│ │ │ callback │ │
│ │ ┌────────▼───────────────────┐ │ │
│ │ │ handle_parameter() │ │ │
│ │ └──┬──────┬──────┬──────┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼─┐ ┌─▼──┐ ┌─▼──┐ ┌─▼─────┐ │ │
│ │ │Chan │ │Bus │ │Rout│ │Auto- │ │ │
│ │ │Strip │ │Mgr │ │Mat │ │mation │ │ │
│ │ └──┬───┘ └────┘ └────┘ └───────┘ │ │
│ │ │ OSC │ │
│ └─────┼──────────────────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Carla Rack │ LV2 / VST2 / NAM plugins │
│ └──────┬──────┘ │
│ │ JACK │
│ ┌──────▼──────┐ │
│ │ JACK2 RT │ SCHED_FIFO prio 70 │
│ └──────┬──────┘ │
│ │ ALSA hw:USB │
└───────────────────────────┼──────────────────────────────────────┘
┌───────────────────────────▼──────────────────────────────────────┐
│ Kernel: PREEMPT_RT 6.12.y │ snd-usb-audio │ xHCI USB │
└──────────────────────────────────────────────────────────────────┘
```
### Data Flow
1. **MIDI Controller** sends CC/NRPN → `MIDIEngine` parses message → matches
against `MappingStore` → dispatches to `ParameterRegistry.set_value()`
2. **ParameterRegistry** calls all registered callbacks — primarily
`DSPEngine.handle_parameter()`
3. **DSPEngine** routes the parameter change to the correct component:
- `ChannelStrip.set_parameter()` → sends OSC to Carla for DSP processing
- `BusManager` — aux sends, subgroups, VCA, master
- `RoutingMatrix` — mute/solo/gain affect JACK connections
- `FaderAutomation` — records parameter changes if automation is armed
4. **Web UI / Touch UI** reads state from DSPEngine via REST/OSC, or
directly calls `ParameterRegistry.set_value()`
5. **Session Manager** serializes entire `ParameterRegistry` state to JSON
files; deserializes on load
### Key Design Decisions
| Decision | Rationale |
|----------|-----------|
| Python orchestration, C/C++ DSP (Carla) | Python for control logic; native code for real-time audio |
| OSC to Carla (not PyBind or C API) | Carla has a stable OSC API; network-transparent; debuggable |
| JACK for routing (not PulseAudio) | Professional audio: sample-accurate routing, real-time scheduling |
| ParameterRegistry as central state | Single source of truth for all mixer parameters; observable |
| JSON sessions (not SQLite/binary) | Human-readable, diffable, version-control-friendly |
| FastAPI + WebSocket for web UI | Async I/O, automatic OpenAPI docs, WebSocket for real-time updates |
| Kivy for touch UI | Python-native, OpenGL-accelerated, multi-touch, DPI-aware |
| PREEMPT_RT kernel | Sub-10ms round-trip latency requirement |
---
## 2. Development Setup
### Prerequisites
- **Python 3.11+** (3.12 recommended)
- **Linux** (Ubuntu/Debian recommended; macOS works for most development)
- **Git**
### Installing Development Dependencies
```bash
# Clone the repository
git clone https://github.com/your-org/rpi-audio-mixer.git
cd rpi-audio-mixer
# Create a virtual environment
python3 -m venv venv
source venv/bin/activate
# Install Python dependencies
pip install -e ".[dev,test]"
# Install system dependencies (Linux)
sudo apt install -y \
jackd2 \
libjack-jackd2-dev \
carla \
python3-jack-client \
libasound2-dev
# Or on macOS (limited — no JACK real-time audio)
brew install jack
pip install python-jack-client
```
### Running Without Hardware
Most development and testing can be done on a desktop/laptop without a
Raspberry Pi or USB audio interface:
```bash
# Start JACK with a dummy driver for testing
jackd -d dummy -r 48000 -p 256 &
# Run the mixer API server
python -m src.network.run --port 8080
# Run tests (no hardware required)
python -m pytest tests/ -v
# Run the touch UI (connected to local server)
python main_touch.py --host 127.0.0.1 --port 8080
```
### IDE Configuration
The project uses standard Python tooling. No special IDE plugins required.
**VS Code recommended settings (`.vscode/settings.json`):**
```json
{
"python.analysis.extraPaths": ["src"],
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["tests", "-v"],
"editor.rulers": [88],
"python.formatting.provider": "none",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
}
}
```
---
## 3. Project Structure
```
rpi-audio-mixer/
├── src/
│ ├── mixer/ # Core DSP engine
│ │ ├── __init__.py # Public API exports
│ │ ├── dsp_engine.py # Main orchestrator
│ │ ├── channel_strip.py # Per-channel parameter controller
│ │ ├── routing_matrix.py # JACK port routing graph
│ │ ├── bus_manager.py # Aux, subgroups, VCA, master
│ │ ├── osc_client.py # Carla OSC control client
│ │ └── fader_automation.py # Automation recording & playback
│ │
│ ├── midi/ # MIDI processing
│ │ ├── __init__.py
│ │ ├── midi_engine.py # Core routing + mapping dispatch
│ │ ├── midi_learn.py # Learn mode state machine
│ │ ├── midi_clock.py # MIDI clock master/slave
│ │ ├── mapping_store.py # Persistent mapping CRUD
│ │ ├── controllers.py # Pre-configured controller profiles
│ │ ├── device_discovery.py # USB MIDI device enumeration
│ │ ├── jack_midi_bridge.py # JACK MIDI ↔ ALSA seq bridge
│ │ └── types.py # MIDI data types
│ │
│ ├── session/ # Session persistence
│ │ ├── __init__.py
│ │ ├── session_manager.py # Save/load/list/delete sessions
│ │ ├── mixer_session.py # Session data model + schema
│ │ ├── setlist.py # Setlist data model + transitions
│ │ └── snapshot.py # Snapshot capture & recall
│ │
│ ├── recording/ # Multi-track recording
│ │ ├── __init__.py
│ │ ├── recorder.py # Multi-track recorder with punch in/out
│ │ ├── wav_writer.py # WAV file writer with seek tables
│ │ ├── session.py # Recording session management
│ │ ├── bounce.py # Bounce/mixdown to stereo
│ │ └── disk_monitor.py # SD card space monitoring
│ │
│ ├── backing/ # Backing track playback
│ │ ├── __init__.py
│ │ ├── player.py # Main backing track orchestrator
│ │ ├── loader.py # Audio file loader (WAV/FLAC/MP3)
│ │ ├── playlist.py # Playlist management
│ │ ├── metronome.py # Click track generator
│ │ ├── transport.py # JACK transport integration
│ │ └── types.py # Backing track data types
│ │
│ ├── streaming/ # Live streaming pipeline
│ │ ├── __init__.py
│ │ ├── streamer.py # Main stream lifecycle manager
│ │ ├── gst_pipeline.py # GStreamer pipeline builder
│ │ ├── camera.py # Camera detection (USB/Pi Cam)
│ │ ├── platforms.py # YouTube/Twitch/Facebook presets
│ │ └── controls.py # Stream scene management
│ │
│ ├── network/ # Network services
│ │ ├── __init__.py
│ │ ├── run.py # Server entry point
│ │ ├── server.py # FastAPI + WebSocket + OSC server
│ │ ├── rest_api.py # REST API endpoints
│ │ ├── session_routes.py # Session-specific routes
│ │ ├── stream_routes.py # Streaming-specific routes
│ │ ├── web_routes.py # Web UI static routes
│ │ ├── websocket.py # WebSocket manager
│ │ ├── osc_server.py # OSC server
│ │ ├── schemas.py # Pydantic API models
│ │ ├── auth.py # API key authentication
│ │ ├── rate_limiter.py # Rate limiting
│ │ └── session.py # HTTP session middleware
│ │
│ ├── plugin/ # Plugin management
│ │ ├── __init__.py
│ │ ├── manager.py # Plugin lifecycle manager
│ │ ├── scanner.py # Filesystem plugin scanner
│ │ ├── registry.py # SQLite plugin database
│ │ ├── blacklist.py # Plugin deny list
│ │ ├── categories.py # Plugin categorization
│ │ ├── nam.py # Neural Amp Modeler support
│ │ └── types.py # Plugin data types
│ │
│ └── ui/ # Touchscreen UI (Kivy)
│ ├── __init__.py
│ ├── app.py # Kivy application + ScreenManager
│ ├── ipc.py # REST + OSC client for engine comms
│ ├── theme.py # Color scheme, typography, DPI
│ ├── screens/
│ │ ├── __init__.py
│ │ ├── mixer.py # Channel strips + master faders
│ │ ├── routing.py # Routing matrix screen
│ │ ├── plugins.py # Plugin chain editor
│ │ ├── sessions.py # Session/setlist management
│ │ └── settings.py # App settings screen
│ └── widgets/
│ ├── __init__.py
│ ├── fader.py # Custom touch fader widget
│ └── knob.py # Custom touch knob widget
├── tests/ # Test suite (735 tests)
│ ├── conftest.py # pytest fixtures
│ ├── test_dsp_engine.py
│ ├── test_midi_engine.py
│ ├── test_midi_learn.py
│ ├── test_midi_clock.py
│ ├── test_mapping_store.py
│ ├── test_types.py
│ ├── test_session.py
│ ├── test_recording.py
│ ├── test_backing.py
│ ├── test_streaming.py
│ ├── test_rest_api.py
│ ├── test_web_routes.py
│ ├── test_websocket.py
│ ├── test_auth.py
│ ├── test_schemas.py
│ ├── test_rate_limiter.py
│ ├── test_osc_server.py
│ ├── test_parameter_registry.py
│ └── test_touch_ui.py
├── build/ # SD card image builder
│ ├── build.sh # Main build script
│ ├── configure-system.sh # Chroot post-install configuration
│ ├── first-boot/
│ │ └── setup-wizard.sh # First-boot setup wizard
│ └── README.md # Build system documentation
├── scripts/ # Utility scripts
│ ├── carla-preset-manager.py
│ ├── carla-preset-gen.py
│ ├── lv2lint-check.sh
│ ├── carla-build.sh
│ └── nam-build.sh
├── docs/ # Documentation
│ ├── build-guide.md
│ ├── user-manual.md
│ ├── developer-guide.md (this file)
│ ├── hardware-compatibility.md
│ ├── troubleshooting.md
│ ├── audio-stack-config.md
│ ├── midi-controller-support.md
│ ├── touchscreen-ui-evaluation.md
│ ├── carla-integration.md
│ └── research/
│ ├── base-os-decision.md
│ └── research-report.md
├── main_touch.py # Touch UI entry point
├── README.md # Project readme
└── .gitignore
```
---
## 4. Component Deep-Dives
### DSP Engine (`src/mixer/`)
The DSPEngine is the central orchestrator. It does **not** process audio
directly — audio DSP happens in Carla (a C++ plugin host). The engine's
job is control: receiving parameter changes, translating them to OSC
messages for Carla, and keeping JACK routing in sync.
```
ParameterRegistry → DSPEngine.handle_parameter()
├── ChannelStrip.set_parameter() → Carla OSC
├── BusManager.update_bus()
├── RoutingMatrix.update_node()
└── FaderAutomation.record()
```
Key classes:
- **`ParameterRegistry`** — holds all mixer parameters as `MixerParameter`
objects. Parameters are identified by `(ParameterType, channel)` tuple.
- **`ChannelStrip`** — manages one channel's DSP state. Translates
high-level parameter changes to Carla OSC messages with appropriate
scaling (linear→dB, Hz→normalized, etc.).
- **`CarlaOSCClient`** — sends OSC messages to Carla's UDP port (22752).
Manages plugin layout, parameter addressing.
- **`RoutingMatrix`** — graph representation of JACK port connections.
Nodes are audio sources/sinks; edges are JACK connections.
- **`BusManager`** — 4 aux buses, 2 subgroups, 2 VCA groups, master bus.
- **`FaderAutomation`** — records parameter changes with timestamps;
plays back with configurable interpolation.
### MIDI Engine (`src/midi/`)
```
USB MIDI Device → ALSA Sequencer → MIDIEngine._read_loop()
→ parse CC/NRPN → match against MIDIMapping list
→ scale value (int→float) → MappingCallback
→ ParameterRegistry.set_value()
```
Key classes:
- **`MIDIEngine`** — reads from ALSA sequencer, matches incoming MIDI
messages against the mapping table, dispatches scaled values.
- **`MappingStore`** — persistent CRUD for MIDI mappings (JSON file).
- **`MIDILearn`** — learn mode state machine: listen for the next
CC/NRPN message and bind it to the selected parameter.
- **`MIDIClock`** — MIDI clock generator (master) or follower (slave).
- **`Controllers`** — pre-built mapping profiles for popular controllers.
- **`DeviceDiscovery`** — enumerates USB MIDI devices via ALSA.
### Session Manager (`src/session/`)
Sessions are JSON files stored in `~/.config/rpi-mixer/sessions/`.
Each session captures the complete mixer state.
```json
{
"version": 1,
"metadata": {
"name": "Live at The Garage",
"created": "2026-05-19T20:00:00Z",
"notes": "Soundcheck levels"
},
"channels": [
{"channel": 1, "volume": -3.0, "pan": 0.0, "mute": false, ...},
...
],
"master": {"volume": -6.0, ...},
"routing": {...},
"plugins": {...}
}
```
Key classes:
- **`SessionManager`** — save/load/list/delete/import/export sessions.
Auto-save with configurable debounce.
- **`MixerSession`** — data model with schema versioning for forward/backward
compatibility.
- **`Setlist`** — ordered list of sessions with transition types
(cut, crossfade, wait).
### Recording (`src/recording/`)
```
JACK process callback
→ numpy buffer (float32, 128 samples)
→ write-ahead ring buffer (16 slots per channel)
→ background writer thread
→ WAV file (disk)
```
Key classes:
- **`MultiTrackRecorder`** — orchestrates recording. Arming channels,
starting/stopping, punch in/out, take management.
- **`WAVWriter`** — writes RIFF WAV files with 16/24/32-bit depth.
Supports streaming writes with seek table updates.
### Backing Tracks (`src/backing/`)
```
Audio file → AudioLoader → numpy array (in memory)
→ BackingTrackPlayer → separate JACK output ports
→ JACK transport sync → mixer channels
```
Key classes:
- **`BackingTrackPlayer`** — main orchestrator. Manages playback state,
transport sync, track parameters.
- **`AudioLoader`** — loads WAV, FLAC, MP3, AIFF, OGG into float32 numpy
arrays.
- **`Metronome`** — generates click track samples at variable BPM.
### Streaming (`src/streaming/`)
```
JACK master output → GStreamer alsasrc
USB camera / Pi Cam → GStreamer v4l2src
→ H.264 encoder (x264) → AAC encoder
→ FLV muxer → rtmpsink → streaming platform
```
Key classes:
- **`Streamer`** — manages a GStreamer subprocess lifecycle. State machine:
idle → starting → live → stopping → idle.
- **`GstPipelineBuilder`** — constructs GStreamer pipeline strings from
config objects.
- **`Platforms`** — presets for YouTube, Twitch, Facebook, custom RTMP.
### Network Services (`src/network/`)
```
FastAPI (Uvicorn)
├── REST API (port 8080) — JSON CRUD for all mixer state
├── WebSocket (port 8080) — real-time parameter push
├── Static files (port 8080) — web UI (HTML/CSS/JS)
└── OSC Server (port 9001) — UDP OSC commands + queries
```
Key classes:
- **`NetworkServer`** — ties REST, WebSocket, and OSC servers together.
- **`MixerStateProvider`** — adapter bridging REST API to DSPEngine.
- **`WebSocketManager`** — broadcasts parameter changes to all connected
web clients.
- **`OSCServer`** — listens for OSC commands and translates to
DSPEngine calls.
---
## 5. Running Tests
### Full Test Suite
```bash
# Run all tests
python -m pytest tests/ -v
# Run with coverage
python -m pytest tests/ -v --cov=src --cov-report=term-missing
# Run a specific test file
python -m pytest tests/test_dsp_engine.py -v
# Run tests matching a keyword
python -m pytest tests/ -v -k "midi"
# Run tests with verbose output (show print statements)
python -m pytest tests/ -v -s
```
### Test Categories
| Category | Files | Count | Requirements |
|----------|-------|-------|-------------|
| DSP Engine | `test_dsp_engine.py` | ~85 | None |
| MIDI | `test_midi_engine.py`, `test_midi_learn.py`, `test_midi_clock.py`, `test_mapping_store.py`, `test_types.py` | ~180 | None |
| Session | `test_session.py` | ~120 | None |
| Recording | `test_recording.py` | ~100 | numpy |
| Backing | `test_backing.py` | ~132 | numpy |
| Streaming | `test_streaming.py` | ~60 | None |
| REST API | `test_rest_api.py`, `test_web_routes.py`, `test_auth.py`, `test_schemas.py`, `test_rate_limiter.py` | ~150 | FastAPI |
| WebSocket | `test_websocket.py` | ~30 | websockets |
| OSC | `test_osc_server.py` | ~30 | None |
| Registry | `test_parameter_registry.py` | ~35 | None |
| Touch UI | `test_touch_ui.py` | ~28 | Kivy (headless) |
### Running on Raspberry Pi Hardware
For tests that require actual JACK/Carla hardware:
```bash
# Start JACK first
jackd -d alsa -d hw:USB -r 48000 -p 256 -n 3 &
# Start Carla
carla --osc-port=22752 &
# Run hardware-dependent tests
python -m pytest tests/ -v -k "not streaming" # skip streaming (needs camera)
# Record latency benchmarks
python -m pytest tests/test_dsp_engine.py -v -k "latency"
```
### CI
Tests run automatically on push via Gitea Actions / GitHub Actions.
The CI configuration runs all tests except hardware-dependent ones:
```yaml
# .gitea/workflows/test.yml
- name: Run tests
run: |
python -m pip install -e ".[dev,test]"
python -m pytest tests/ -v --cov=src
```
---
## 6. Contribution Workflow
### Branch Strategy
- `main` — stable, passing all tests
- Feature branches: `feature/my-feature` or `fix/my-fix`
### Workflow
```bash
# 1. Create a feature branch
git checkout -b feature/my-feature
# 2. Make changes
# ...
# 3. Run tests
python -m pytest tests/ -v
# 4. Run linting (if configured)
ruff check src/ tests/
# 5. Commit with a descriptive message
git add -A
git commit -m "Add feature X: description of change"
# 6. Push
git push origin feature/my-feature
# 7. Create a Pull Request
```
### PR Checklist
- [ ] Tests pass: `python -m pytest tests/ -v`
- [ ] New features include tests
- [ ] Documentation updated if API changes
- [ ] No new lint warnings
- [ ] Commit messages are descriptive
- [ ] Branch is up to date with `main`
### Commit Convention
```
<type>: <short description>
<optional body with details>
Types: feat, fix, docs, test, refactor, build, perf, chore
```
Examples:
```
feat: add high-pass filter to channel strip
fix: resolve xrun on USB disconnect during recording
docs: update user manual with OSC query examples
test: add MIDI clock slave sync tests
```
---
## 7. Coding Conventions
### Python Style
- **Line length:** 88 characters (Black-compatible)
- **Quotes:** double quotes `"` for strings
- **Docstrings:** triple double quotes `"""` — Google style
- **Type hints:** required for all public functions and methods
- **Imports:** `from __future__ import annotations` at top of every file
### Docstring Format
```python
def set_parameter(self, param_type: ParameterType, channel: int, value: float) -> None:
"""Set a mixer parameter and forward to Carla via OSC.
Args:
param_type: The parameter type from the ParameterType enum.
channel: Channel number (0-indexed) or -1 for master/global.
value: Normalized value in the parameter's configured range.
Raises:
ValueError: If the channel is out of range.
"""
```
### Thread Safety
Several components are accessed from multiple threads:
- **MIDIEngine** — MIDI events arrive on ALSA sequencer thread →
callbacks fire on that thread → DSPEngine.handle_parameter() must be
thread-safe.
- **DSPEngine** — uses `threading.Lock` for mutable state.
- **SessionManager** — auto-save runs on a timer thread; load/save can
be called from API thread.
When adding state to a shared component, protect it with `threading.Lock`.
### Error Handling
- **Never crash on a bad parameter value** — log a warning and clamp/ignore.
- **OSC communication failures** (Carla not running) should be logged but
not fatal — the mixer continues without plugin DSP.
- **JACK disconnection** should trigger reconnect logic, not a crash.
- **Disk full during recording** — stop recording gracefully, notify user.
### Logging
Use the standard `logging` module with named loggers:
```python
import logging
logger = logging.getLogger(__name__)
logger.info("Session saved: %s", session_name)
logger.warning("JACK connection lost, attempting reconnect")
logger.error("Failed to write WAV file: %s", str(e))
```
---
## 8. Extending the Mixer
### Adding a New Parameter
1. Add a new `ParameterType` enum value in `src/midi/types.py`
2. Choose the appropriate `ParameterCategory`
3. Add the parameter to the factory function in `src/mixer/__init__.py`
(e.g., `_channel_params()`, `_master_params()`, etc.)
4. Handle the new parameter in `ChannelStrip.set_parameter()` or
`DSPEngine.handle_parameter()`
5. Add the Carla OSC mapping if DSP processing is needed
6. Add the parameter to `ChannelState` for session serialization
7. Add tests in `test_parameter_registry.py` and `test_dsp_engine.py`
### Adding a New MIDI Controller Profile
1. Create a mapping dictionary in `src/midi/controllers.py`
2. Register it in the `CONTROLLER_PRESETS` dict
3. Add to the hardware compatibility docs
### Adding a New Streaming Platform
1. Add preset config to `src/streaming/platforms.py`
2. Register in the platform lookup table
3. Add the platform option to the streaming UI
### Adding a New REST API Endpoint
1. Define request/response schemas in `src/network/schemas.py`
2. Add the route to `src/network/rest_api.py` (or relevant route file)
3. Implement the handler in `MixerStateProvider`
4. Add tests in `test_rest_api.py`
5. Update the API reference in the user manual
### Plugin Support
The Carla plugin host supports:
- **LV2** — `.lv2/` bundles installed system-wide or in `~/.lv2/`
- **VST2** — `.so` files in standard VST paths
- **NAM** — `.nam` model files managed by `src/plugin/nam.py`
To add support for a new plugin format, extend `src/plugin/scanner.py`
and `src/plugin/types.py`.
---
## Appendix: Dependency Graph
```
midi ──────────┐
├──→ mixer (DSPEngine) ──→ network (API + OSC)
│ │
backing ───────┤ ├──→ recording
│ │
session ───────┘ ├──→ streaming
└──→ plugin
ui (Kivy)
```
Dependencies flow downward: `ui` depends on `network`, which depends on
`mixer`. `mixer` depends on `midi`. `backing`, `session`, `recording`,
`streaming`, and `plugin` all depend on `mixer` and `midi`.
No circular dependencies — the dependency graph is a DAG.
+22 -1
View File
@@ -4,7 +4,28 @@ Verified and tested hardware for the Raspberry Pi Real-Time Audio Mixer.
This list reflects devices that have been tested with the PREEMPT_RT kernel This list reflects devices that have been tested with the PREEMPT_RT kernel
and the mixer's JACK/ALSA audio stack. and the mixer's JACK/ALSA audio stack.
## Raspberry Pi Models ## Storage / Boot Media
The same mixer image supports three boot targets. Choose based on your reliability needs:
| Media | Boot Target | Bus | Speed | Reliability | Recommendation |
|-------|------------|-----|-------|-------------|---------------|
| **A2 SD Card** (32GB+) | `--target sd` | SDIO | ~90 MB/s | ⚠️ Moderate | Dev/testing only |
| **USB 3.0 SSD** (128GB+) | `--target usb` | USB 3.0 | ~350 MB/s | ✅ Good | Portable use, but shares USB bus with audio |
| **NVMe SSD** (128GB+) via HAT | `--target nvme` | PCIe Gen 2 x1 | ~500 MB/s | ✅✅ Excellent | **Recommended for live use** — dedicated lanes, no contention |
### NVMe HAT Compatibility
| HAT | Price | M.2 Size | Status | Notes |
|-----|-------|----------|--------|-------|
| **Official Raspberry Pi NVMe Base** | ~€15 | 2230/2242 | ✅ Verified | Best compatibility, official support |
| **Pimoroni NVMe Base** | ~€15 | 2230/2242 | ✅ Verified | Well-documented, same chipset |
| **Geekworm X1001** | ~€12 | 2230 | ✅ Verified | Low profile, good for tight builds |
| **Geekworm X1002** | ~€15 | 2242/2280 | ✅ Verified | Full-length option |
> ⚠️ **Important:** USB SSDs share the VL805 USB controller with audio interfaces.
> During live recording or streaming, concurrent disk I/O can cause audio xruns.
> This is the primary reason NVMe HAT is recommended for production use.
| Model | Status | Notes | | Model | Status | Notes |
|-------|--------|-------| |-------|--------|-------|
+24 -2
View File
@@ -26,10 +26,17 @@ live streaming.
- Raspberry Pi 4 Model B (4GB+ RAM) - Raspberry Pi 4 Model B (4GB+ RAM)
- USB audio interface (class-compliant UAC2) - USB audio interface (class-compliant UAC2)
- SD card (16GB+, Class A2 recommended) with mixer image flashed - Boot media (choose one):
- **SD card** (16GB+, Class A2) — good for dev/testing
- **USB SSD** via USB 3.0 — more reliable, but shares USB bus with audio
- **NVMe SSD + HAT** (PCIe) — **recommended for live use** — no bus contention
- 5V/3A USB-C power supply - 5V/3A USB-C power supply
- **Optional:** HDMI touchscreen, USB MIDI controller, Ethernet cable, USB camera - **Optional:** HDMI touchscreen, USB MIDI controller, Ethernet cable, USB camera
> 💡 **Storage recommendation:** For live performances and recording, use an NVMe
> HAT (official RPi, Pimoroni, or Geekworm) with a 128GB+ NVMe SSD. It uses
> dedicated PCIe lanes and won't compete with your USB audio interface for bandwidth.
### Connections ### Connections
``` ```
@@ -64,8 +71,23 @@ Verified interfaces: see [docs/hardware-compatibility.md](hardware-compatibility
## 2. First Boot & Setup Wizard ## 2. First Boot & Setup Wizard
On first boot, the setup wizard runs automatically on the HDMI display. ### Booting for the First Time
1. Insert your boot media (SD, USB SSD, or NVMe) into the Pi
2. Connect audio interface, MIDI controllers, and network
3. Power on
> ⚡ If booting from USB SSD or NVMe, the Pi's EEPROM must be configured to try
> your boot device first. The setup wizard includes an EEPROM configuration step,
> or you can configure it manually beforehand:
> ```bash
> sudo rpi-eeprom-config --edit
> # USB boot: BOOT_ORDER=0xf41
> # NVMe boot: BOOT_ORDER=0xf614
> ```
On first boot, the **setup wizard** runs automatically on the HDMI display (or
serial console). It walks through:
### Wizard Steps ### Wizard Steps
1. **Welcome screen** — language selection 1. **Welcome screen** — language selection