e450189858
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)
735 lines
28 KiB
Markdown
735 lines
28 KiB
Markdown
# 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.
|