d19e0ea7a8bd5f4189c5ee51f8b554d03efafee5
MasterBus: stereo master fader, mute, level meters in PiPedal theme (purple accent per mixer-engine branch reference). BusStrip: added per-channel routing selector (ch1Route..ch8Route) for selecting output bus target (Main, Bus 2-8). MixerPage: integrated MasterBus with levels derived from bus master data. Local state for volume/mute until backend exposes master output.
OPLabs Mixer App
A standalone digital mixer application with a C++ real-time audio engine, Python/FastAPI backend, and React frontend.
Architecture
REST / WS
Browser (React SPA) ───────── FastAPI Backend (:8081)
│ │
│ WS /ws/pipedal │ WS /pipedal (upstream, legacy)
└── PiPedal (192.168.0.245) └── Legacy PiPedal scenes
│ │
│ WS /ws/mixer │ Unix Socket IPC
└── Mixer Daemon Relay ──────┴── /tmp/oplabs-mixer.sock
│
C++ Mixer Daemon
(MixerEngine + JACK)
Two parallel backends:
- Mixer Daemon (primary): C++ daemon running the real-time mixer engine (MixerEngine, MixerChannelStrip, MixerBus). Communicates with FastAPI via Unix domain socket IPC.
- PiPedal (legacy): WebSocket proxy to PiPedal pedalboard for backward compatibility with existing OPLabsBandChannel/Bus LV2 plugins.
Prerequisites
- Python 3.10+
- Node.js 18+
- C++20 compiler (gcc 11+ or clang 14+)
- CMake 3.16+
- JACK Audio Connection Kit (libjack-jackd2-dev)
- PiPedal at
192.168.0.245:8080(optional, for legacy mode)
Setup
Backend
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Frontend
cd frontend
npm install
npm run build
Mixer Daemon (C++)
cd daemon
mkdir build && cd build
cmake ..
make
The daemon requires the MixerEngine sources from the op-pedal mixer-engine branch.
Either:
- Build inside the op-pedal CMake tree, or
- Set
-DMIXER_ENGINE_SOURCE_DIR=/path/to/op-pedal/src/
Running
1. Start JACK (if using the daemon with audio I/O)
jackd -d alsa -r 48000 -p 512 -n 2 &
2. Start the Mixer Daemon
./daemon/build/oplabs-mixer-daemon \
--socket-path /tmp/oplabs-mixer.sock \
--sample-rate 48000 \
--buffer-size 512
3. Start the Backend
cd backend
source venv/bin/activate
python app.py # runs on port 8081
Open http://localhost:8081 in your browser.
Or use the convenience script:
./start.sh
IPC Protocol (C++ Mixer Daemon <-> Python FastAPI)
Transport
- Type: Unix domain socket (
AF_UNIX,SOCK_STREAM) - Path:
/tmp/oplabs-mixer.sock - Permissions: 0666 (read/write for owner and group)
Framing
Every message is prefixed with a 4-byte big-endian unsigned integer (uint32_t) specifying the length of the JSON payload in bytes.
[4-byte length BE] [UTF-8 JSON payload]
Request Format
{
"id": 1,
"method": "getState",
"params": {}
}
Response Format
{
"id": 1,
"result": { ... },
"error": null
}
On error, result is null and error contains a string description.
Push Messages (Daemon -> Client)
Unsolicited messages from the daemon are tagged with "type": "push":
{
"type": "push",
"event": "vuMeter",
"data": {
"channels": [{"index": 0, "left": -12.5, "right": -10.2}, ...],
"buses": [{"id": 1, "left": -6.0, "right": -5.8}]
}
}
Message Reference
| Method | Params | Result |
|---|---|---|
getState |
{} |
Full mixer state object (channels, buses, routes, output routes) |
setChannelVolume |
{"channelIndex": int, "volumeDb": float} |
{} |
setChannelPan |
{"channelIndex": int, "pan": float} |
{} |
setChannelMute |
{"channelIndex": int, "mute": bool} |
{} |
setChannelSolo |
{"channelIndex": int, "solo": bool} |
{} |
addChannel |
{"physicalInputIndex": int} |
{"channelIndex": int} |
removeChannel |
{"channelIndex": int} |
{} |
getOutputRoutes |
{} |
{"routes": [sourceBusId, sourceStartChannel, targetStartChannel, channels]} |
setOutputRoutes |
{"routes": [sourceBusId, sourceStartChannel, targetStartChannel, channels]} |
{} |
saveScene |
{"name": string} |
{"id": int, "name": string} |
loadScene |
{"sceneId": string} |
{"ok": bool} |
listScenes |
{} |
{"scenes": [{"id": int, "name": string}, ...]} |
deleteScene |
{"sceneId": string} |
{"ok": bool} |
getState Result Schema
{
"channels": [
{
"channelIndex": 0,
"volume": -6.0,
"pan": 0.0,
"mute": false,
"solo": false,
"type": "Instrument",
"label": "Channel 1",
"hpEnabled": false,
"hpFrequency": 80.0,
"vuLeft": -12.5,
"vuRight": -10.2
}
],
"buses": [
{
"id": 1,
"name": "Master",
"type": "Master",
"volume": 0.0,
"mute": false
}
],
"routes": [
{
"sourceId": 1001,
"targetBusId": 1,
"level": 0.0,
"sourceType": "channel"
}
],
"outputRoutes": [
{
"sourceBusId": 1,
"sourceStartChannel": 0,
"targetStartChannel": 0,
"channels": 2
}
]
}
REST API
Mixer Daemon Endpoints (new)
| Endpoint | Method | Description |
|---|---|---|
/api/mixer/state |
GET | Get full mixer state |
/api/mixer/channel/{index}/volume |
POST | Set channel volume |
/api/mixer/channel/{index}/pan |
POST | Set channel pan |
/api/mixer/channel/{index}/mute |
POST | Set channel mute |
/api/mixer/channel/{index}/solo |
POST | Set channel solo |
/api/mixer/channel/add |
POST | Add a new channel |
/api/mixer/channel/{index} |
DELETE | Remove a channel |
/api/mixer/output-routes |
GET | Get output routing table |
/api/mixer/output-routes |
POST | Set output routing table |
/api/mixer/scenes |
GET | List all scenes |
/api/mixer/scenes |
POST | Save current state as scene |
/api/mixer/scenes/{id}/load |
POST | Load (recall) a scene |
/api/mixer/scenes/{id} |
DELETE | Delete a scene |
PiPedal Endpoints (legacy)
| Endpoint | Method | Description |
|---|---|---|
/api/ping |
GET | Health check + connection status |
/api/discover |
GET | Discover OPLabs plugin instances from PiPedal |
/api/plugins |
GET | List all available plugins from PiPedal |
/api/scenes |
GET | List saved scenes (legacy file-based) |
/api/scenes |
POST | Create new scene |
/api/scenes/{id} |
PUT | Update scene |
/api/scenes/{id} |
DELETE | Delete scene |
/api/scenes/{id}/recall |
POST | Recall scene via PiPedal |
WebSocket Endpoints
| Endpoint | Description |
|---|---|
/ws/pipedal |
Proxy WebSocket: browser <-> FastAPI <-> PiPedal |
/ws/mixer |
Direct mixer daemon control (JSON method calls) |
/ws/mixer Protocol
The mixer WebSocket accepts JSON messages in the same format as the IPC protocol:
// Request
{"method": "getState", "id": 1}
{"method": "setChannelVolume", "params": {"channelIndex": 0, "volumeDb": -6.0}, "id": 2}
// Response
{"id": 1, "result": {...}, "error": null}
{"id": 2, "result": {}, "error": null}
Project Structure
~/projects/oplabs-mixer-app/
├── backend/
│ ├── app.py # FastAPI app (mixer + legacy PiPedal routes)
│ ├── mixer_ipc.py # Unix socket IPC client for Mixer Daemon
│ ├── ws_proxy.py # PiPedal WebSocket proxy
│ └── requirements.txt
├── daemon/
│ ├── CMakeLists.txt # Build configuration
│ ├── MixerIpcServer.hpp # C++ Unix socket IPC server
│ ├── MixerIpcServer.cpp # IPC server implementation
│ ├── MixerDaemon.cpp # Main entry point
│ └── oplabs-mixer-daemon.service.in # Systemd service template
├── frontend/
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── index.html
│ └── src/
├── README.md
└── start.sh
Description
Languages
TypeScript
45.2%
Python
25.1%
CSS
17.3%
C++
10.6%
CMake
0.9%
Other
0.8%