A modern web-based control interface for the Zoom L-20 mixer, built specifically for musicians to control their personal monitor mix volumes. This application provides real-time mixing capabilities with an intuitive UI for controlling faders, effects, and monitor sends directly from any device on the network.
- Real-time Fader Control: 18 channel faders with live volume adjustment
- Master & Effects Control: Dedicated controls for master, FX1, and FX2 tracks
- Effects Sends: Adjustable send levels to FX1 and FX2
- Responsive Design: Works seamlessly on desktop and tablet interfaces
- WebSocket Integration: Real-time synchronization with L20 hardware via Python backend
- Frontend: Next.js 16 with React 19
- Styling: Tailwind CSS with custom animations
- UI Components: shadcn/ui + Radix UI
- Real-time Communication: next-ws (WebSocket support)
- Backend: Python L20 controller (TCP socket communication)
- Node.js 18+ and npm/yarn/pnpm
- Python 3.7+ (for the L20 controller backend)
- L-20 audio mixer hardware
# Install dependencies
npm install
# Prepare Websocket for NextJS (next-ws)
npm run prepare
# Build NextJS
npm run build
# Run the server (runs on port 80)
npm run startThe app will be available at http://localhost:80
Set up the Python environment and run Backend Controller:
python -m venv .venv
pip install -r l20_controller/requirements.txt
python -m l20_controller.controllerThe Python controller handles BLE communication with the L20 mixer hardware and bridges it via TCP Socket to the NextJS backend.
The application uses JSON configuration files stored in the public/ directory:
public/faders.json: Defines fader channels and their properties (names, IDs, etc.)public/tracks.json: Specifies track layouts and configurations for the mixer interface
These files are loaded at runtime to configure the mixer interface without requiring code changes.
├── src/
│ ├── app/ # Next.js app router
│ │ ├── api/ws/ # WebSocket endpoint
│ │ ├── layout.tsx # Root layout
│ │ └── page.tsx # Main page
│ ├── components/ # React components
│ │ ├── ui/ # shadcn/ui custom components
│ │ ├── MainFaderDrawer.tsx
│ │ ├── TrackFader.tsx
│ │ ├── TrackFaderList.tsx
│ │ └── ...
│ ├── contexts/ # React contexts
│ │ ├── L20SocketContext.tsx # WebSocket state management
│ │ └── L20ChannelContext.tsx
│ ├── services/ # API/client services
│ │ ├── L20_client.ts # L20 controller communication
│ │ └── fetchFaders.ts
│ ├── constants.ts # App constants
│ └── types.ts # TypeScript type definitions
├── l20_controller/ # Python backend
│ ├── controller.py # Main L20 controller & BLE device management
│ ├── tcpsocket.py # TCP socket server
│ ├── protocol.py # Message protocol definitions
│ ├── decode.py # L20 protocol decoding & MIDI parsing
│ ├── json_messages.py # JSON message creation & parsing
│ ├── const.py # MIDI constants and channel mappings
│ ├── __init__.py # Package initialization
│ └── requirements.txt # Python dependencies (bleak, mido)
└── public/ # Static assets
├── faders.json
└── tracks.json
L20 Mixer (Hardware)
↓ (BLE Communication)
l20_controller/controller.py
↓ (TCP Socket)
Next.js API route (src/app/api/ws/route.ts)
↓ (Websocket)
L20SocketContext
↓ (State)
React Components (Faders, Effects)
The application maintains a bidirectional WebSocket connection with the backend. User interactions (volume changes, mute) are sent as commands and hardware state updates are received and applied to the UI in real-time.
The Python backend (l20_controller) includes:
- BLE Device Discovery: Automatic scanning and connection to L-20 mixer devices
- MIDI Protocol Handling: Full MIDI CC and SYSEX message processing for mixer control
- TCP Socket Server: Establishes TCP connection with the NextJS WebSocket endpoint
- Real-time State Synchronization: Tracks and transmits mixer channel information, volumes, and effects
- JSON Message Protocol: Structured communication between backend and frontend
npm run dev # Start development server on port 80
npm run build # Build for production
npm start # Start production server on port 80
npm run lint # Run ESLint
npm run prepare # Prepare Websocket for NextJS- The app runs on port 80 by default (can be changed in
package.json) - WebSocket patches are applied automatically via
next-ws patchin the prepare script - Component library uses Radix UI primitives with Tailwind CSS styling
- Theme support with next-themes
This project uses the PyL20 library by Robert Gröber (robig) for L20 BLE protocol communication. I am grateful for the excellent work on reverse-engineering and documenting the L20 protocol.

