A client/server system for sharing MIDI between multiple machines over a network. All MIDI messages received from any client are broadcast to every other connected client — a mix-minus hub for MIDI.
On each client machine, the client spawns a virtual MIDI port so any local MIDI app (DAW, Pd, Max, SuperCollider, etc.) can connect to it without extra configuration.
| Component | What it does | Where it runs |
|---|---|---|
server.py |
TCP hub — receives MIDI bytes from any client and forwards them to all others | Always-on machine or Docker |
client.py |
Bridges your local MIDI ports to the server over TCP | Your music machine |
Dependencies: server.py is pure Python stdlib. client.py requires mido + python-rtmidi to talk to real MIDI hardware.
Download the latest binaries from the Releases page. Each release ships self-contained executables — no Python or pip needed.
network-midi-hub-osx.tgz → client, server (macOS)
network-midi-hub-linux.tgz → client, server (Linux x86-64)
network-midi-hub-linux-arm64.tgz → client, server (Linux arm64 — Raspberry Pi 3/4/5, ARM desktops/servers, AWS Graviton, etc.)
network-midi-hub-windows.tgz → client.exe, server.exe (Windows)
Start the server on any always-on machine:
./server # listens on 0.0.0.0:8141
./server --port 9000 # custom port
./server --bind 192.168.1.10 # bind to specific interfaceStart the client on each music machine:
./client # prompts for server IP
./client --host 192.168.1.10 # connect directly
./client --thru # echo received MIDI back to local output tooThe server can run as a Docker container (no Python required on the host):
docker build -t network-midi-hub-server .
docker run -p 8141:8141 --rm network-midi-hub-serverWindows does not support virtual MIDI ports natively. Use loopMidi as a workaround.
-
Open loopMidi, remove any existing ports, then add two ports exactly as shown:
-
Configure your DAW's MIDI ports similarly:
If it doesn't work, try swapping input/output — MIDI port direction naming varies by app.
The client detects Windows automatically and uses the loopMidi port names instead of creating virtual ports.
brew install pyenv pipenv # macOSpipenv sync --dev
pipenv shellpipenv sync installs everything from Pipfile.lock — including mido and python-rtmidi (needed by client.py) and dev tools (pytest, pyinstaller, etc.).
Note:
requirements.txtis a Pipenv-generated export used only by the Docker build forserver.py. It intentionally omitsmido/python-rtmidibecause the server doesn't use them and they require ALSA headers to compile. Always usepipenv syncfor local development.
# Server
pipenv run python server.py
# Client
pipenv run python client.py --host <server-ip>pipenv run pytestLinux requires ALSA and JACK headers before building:
sudo apt install libasound2-dev libjack-devBuild client and server executables:
pipenv run pyinstaller -F --noconfirm --hiddenimport mido.backends.rtmidi client.py
pipenv run pyinstaller -F --noconfirm --hiddenimport mido.backends.rtmidi server.pyOutputs land in dist/. The --hiddenimport mido.backends.rtmidi flag is required because pyinstaller can't auto-detect the rtmidi backend that mido loads at runtime.
Modify Pipfile, then regenerate the lock file and export the server-only subset:
pipenv lock
pipenv requirements > requirements.txt # server deps only (no mido/rtmidi)
pipenv requirements --dev-only > requirements-dev.txt


