- 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)
28 KiB
Developer Guide
How to contribute to the Raspberry Pi Real-Time Audio Mixer — architecture, development setup, testing, and contribution workflow.
Table of Contents
- Architecture Overview
- Development Setup
- Project Structure
- Component Deep-Dives
- Running Tests
- Contribution Workflow
- Coding Conventions
- 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
- MIDI Controller sends CC/NRPN →
MIDIEngineparses message → matches againstMappingStore→ dispatches toParameterRegistry.set_value() - ParameterRegistry calls all registered callbacks — primarily
DSPEngine.handle_parameter() - DSPEngine routes the parameter change to the correct component:
ChannelStrip.set_parameter()→ sends OSC to Carla for DSP processingBusManager— aux sends, subgroups, VCA, masterRoutingMatrix— mute/solo/gain affect JACK connectionsFaderAutomation— records parameter changes if automation is armed
- Web UI / Touch UI reads state from DSPEngine via REST/OSC, or
directly calls
ParameterRegistry.set_value() - Session Manager serializes entire
ParameterRegistrystate 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
# 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:
# 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):
{
"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 asMixerParameterobjects. 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.
{
"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
# 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:
# 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:
# .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-featureorfix/my-fix
Workflow
# 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 annotationsat top of every file
Docstring Format
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.Lockfor 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:
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
- Add a new
ParameterTypeenum value insrc/midi/types.py - Choose the appropriate
ParameterCategory - Add the parameter to the factory function in
src/mixer/__init__.py(e.g.,_channel_params(),_master_params(), etc.) - Handle the new parameter in
ChannelStrip.set_parameter()orDSPEngine.handle_parameter() - Add the Carla OSC mapping if DSP processing is needed
- Add the parameter to
ChannelStatefor session serialization - Add tests in
test_parameter_registry.pyandtest_dsp_engine.py
Adding a New MIDI Controller Profile
- Create a mapping dictionary in
src/midi/controllers.py - Register it in the
CONTROLLER_PRESETSdict - Add to the hardware compatibility docs
Adding a New Streaming Platform
- Add preset config to
src/streaming/platforms.py - Register in the platform lookup table
- Add the platform option to the streaming UI
Adding a New REST API Endpoint
- Define request/response schemas in
src/network/schemas.py - Add the route to
src/network/rest_api.py(or relevant route file) - Implement the handler in
MixerStateProvider - Add tests in
test_rest_api.py - 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 —
.sofiles in standard VST paths - NAM —
.nammodel files managed bysrc/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.