Files
oplabs-mixer-app/README.md
T
shawn d19e0ea7a8 Add MasterBus component + enhance BusStrip with routing selector
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.
2026-06-23 12:15:09 -04:00

10 KiB

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:

  1. Mixer Daemon (primary): C++ daemon running the real-time mixer engine (MixerEngine, MixerChannelStrip, MixerBus). Communicates with FastAPI via Unix domain socket IPC.
  2. 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