The Test Lab development Mac profile now includes native Client/Server workflow tests compiled from the production Mac relay sources. They exercise real native compatibility endpoints and TCP transfer in both directions, message integrity, authentication rejection, disconnect/reconnect, server restart and cleanup. A separate required case exercises the default Apple Network MIDI mode through an existing enabled session; its absence leaves the stage incomplete. These tests create temporary endpoints on the development VM and must never be deployed to the physical Mac or included in offline profiles. Existing Forwarder sessions may be used for separately authorized real-device preset-change acceptance. A native codec or same-host relay pass does not establish packaged two-machine operation.
Current source release: 0.9.0 — late beta.
Supported platforms: Windows 10 and Windows 11 (x64) and macOS 26 and 27 (Apple Silicon). On Windows 10, the Client uses manually created loopMIDI cables; automatic port creation requires eligible Windows 11 MIDI Services.
Shared MIDI implementation: see dependency and setup notes. Current and legacy backends come from the MightBeUseful.Midi packages maintained in MIDIHelperLibrary; earlier implementation/package details below describe the pre-migration builds.
Both Client and Server use the DIN Send socket-and-arrow logo in their app headers, window/tray icons and published Windows/macOS application icons. Branding exports and verification notes are in Branding.
MIDI Forwarder makes a physical MIDI interface connected to a supported Windows PC or Mac available to an ordinary MIDI application on another supported computer. The local application sees a normal named MIDI input and output; it does not need to know that the device is remote.
The forwarder is MIDI-device and application agnostic. It transports complete MIDI 1.0 messages, including channel messages, system common/realtime messages, and SysEx, without interpreting manufacturer-specific bytes.
Run the two desktop applications as a pair:
- Server runs beside the physical MIDI hardware. It opens an existing Windows MIDI Services, WinMM or CoreMIDI hardware connection and listens for a client.
- Client runs beside the MIDI application. It uses Windows MIDI Services automatic ports, existing loopMIDI cables on Windows 10, or Apple's network MIDI driver, then connects to the server over TCP.
MIDI application
<-> MIDI Forwarder virtual input/output
<-> Client
<-> authenticated length-prefixed TCP stream
<-> Server
<-> physical MIDI input/output
<-> MIDI device
Both directions are pumped concurrently. A connection ends when either side
closes or either MIDI pump fails. TCP uses TCP_NODELAY and keep-alive for
responsive control traffic. The computer-to-computer connection uses TCP.
The macOS client uses a local RTP-MIDI connection to Apple's network MIDI driver.
In the server's Settings → Connection, enable Allow takeover, save, then start the server. This is off by default. When another authenticated client tries to connect to an occupied server, that incoming client asks Take over server connection? Choosing Take over disconnects the existing client and connects the new one after the server releases its MIDI endpoint. Choosing Cancel or closing the prompt leaves the existing connection running.
The displaced client pauses instead of automatically reclaiming the connection. Choose Disconnect, then Connect to request access again. A server with takeover disabled reports that it is busy and keeps the current client connected. Both ends need updated applications for the takeover prompt and pause behaviour. Older clients can still connect normally; updated clients also connect to older servers using the original protocol, without takeover support.
For published binaries:
- Windows 10 and Windows 11, x64. Automatic Client ports require Windows 11 25H2 (build 26200) or later with compatible Windows MIDI Services and its loopback transport. Windows binaries embed the required MIDI API DLL and resources inside each EXE and prepare their private copy automatically. Copying the EXE is sufficient; no separate API installer or DLL files are required. See the dependency notes.
- macOS 26 and 27, Apple Silicon (arm64). The Client's recommended workflow uses a user-configured Apple network MIDI session; see Mac setup.
Windows 10 supports the existing hardware MIDI path through WinMM and the Client's manual virtual-cable path. For the Client, run loopMIDI and create two cables as described in Windows 10 setup. Direct hardware use on the Server needs no loopMIDI. Earlier Windows 11 builds also retain the legacy hardware/manual-cable paths. Automatic virtual interfaces require the OS service/transport described above. Windows 10 never extracts or loads the bundled Microsoft MIDI Services API. The current shared provider also embeds the required Visual C++ runtime files; no separate Visual C++ Redistributable installation is required.
The selected API is shown in the application log and by --midi-backend.
Use --midi-api-directory to see the automatically extracted bundled API location.
The Windows MIDI Services API requires Windows 11 25H2 or later according to
Microsoft's requirements.
WinMM does not create virtual cables itself; the client does not install a driver.
For development:
- .NET 10 SDK.
- The Windows SDK reference and Windows MIDI Services packages pinned in
local-packages. - A Windows x64 build environment. Both apps compile against
net10.0-windows10.0.26100.0, withSupportedOSPlatformVersionexplicitly set to10.0.17763.0. The SDK version controls the available build-time API; the supported-platform version is retained compatibility metadata in the existing builds. It does not extend the supported operating systems listed above.
The published applications are self-contained single-file executables, so a separate .NET runtime is not required on the machine that runs them.
Set up the server first, then the client. The two sides must use the same shared token byte-for-byte.
- Start
MidiForwarder.Server.exe. - Choose the physical MIDI endpoint when using Windows MIDI Services, or the MIDI input that receives from the device and the MIDI output that sends to it when using WinMM. Refresh the ports after connecting a device.
- Leave TCP listen address as
tcp://0.0.0.0:5180for trusted-LAN use, or bind to a specific local address. The scheme must betcp://and the port must be between 1 and 65535. - Enter a long, unique Shared token. A token is required when listening beyond the local machine; loopback-only listeners may use an empty token.
- Click Start server.
- Allow inbound TCP port 5180 (or your chosen port) in Windows Firewall only from the development computer or trusted LAN.
The server accepts one active client at a time. Additional connections are closed while another client is relaying.
On Windows 11 25H2 or later with compatible Windows MIDI Services, loopback transport and the optional native API available:
- Start
MidiForwarder.Client.exe. - Enter a name for the virtual interface, such as
MIDI Forwarder. The name is limited to 31 characters so the public endpoint name fits the WinMM name limit. - Enter the server address, for example
tcp://192.168.2.155:5180. - Enter exactly the same shared token as on the server.
- Click Connect.
- In the local MIDI application, select the generated endpoint
MIDI Forwarderfor both MIDI input and MIDI output.
The input and output use the configured name without an (App) suffix. The
internal (Relay) endpoint does not create WinMM or WinRT MIDI 1.0 ports, so
it is absent from those applications' input/output selectors.
The client retries a dropped connection with
bounded exponential backoff (1, 2, 4, … up to 30 seconds) while keeping the
virtual endpoint alive.
Use the existing public Windows builds with two manually created loopMIDI cables. No teVirtualMIDI SDK, SDK integration or separate private build is needed. This setup also applies when the Windows 11 Client uses its legacy MIDI backend.
- Start loopMIDI on the computer running Client and the local MIDI application (install it first if needed). Run both applications under the same Windows user account.
- In loopMIDI, enter
To MIDI Forwarderin New port-name and click +. Repeat forFrom MIDI Forwarder. Each cable appears as an input and an output; a message sent to its output reaches applications listening to its input. - Start
MidiForwarder.Client.exe, open Settings → Connection, and click Refresh MIDI ports. Set From MIDI app (input) toTo MIDI Forwarderand To MIDI app (output) toFrom MIDI Forwarder. - Enter the Server's Server TCP address, for example
tcp://192.168.2.155:5180, and exactly the same Shared token as on the Server. Click Save, then Connect. Set up and start the Server as described above; it opens the hardware ports directly and needs no loopMIDI cables for that connection. - In the local MIDI application, select
To MIDI Forwarderas its MIDI output andFrom MIDI Forwarderas its MIDI input. Save those selections in the application.
| Application | Setting | Cable |
|---|---|---|
| Forwarder Client | From MIDI app (input) | To MIDI Forwarder |
| Forwarder Client | To MIDI app (output) | From MIDI Forwarder |
| Local MIDI application / editor | MIDI output | To MIDI Forwarder |
| Local MIDI application / editor | MIDI input | From MIDI Forwarder |
The outgoing route is MIDI app → To MIDI Forwarder → Client → TCP network → Server → hardware. Replies return through hardware → Server → TCP network → Client → From MIDI Forwarder → MIDI app. Use two different cables; selecting one cable for both Client directions or echoing the return cable into the sending cable can create feedback.
With Preset Sender: open Sender's Configure → MIDI Connection, click
Refresh Devices, and select To MIDI Forwarder as MIDI Out. Leave
MIDI In unselected for keyboard/keypad use. Set the output channel for the
remote hardware, click Connect, then Save. Sender does not need the
From MIDI Forwarder return cable, but the Client still requires both selections.
No third cable is needed. See Sender's Windows 10 setup
for sending to a local app without Forwarder.
What "create once" means: loopMIDI remembers the cable names and recreates them when it starts under the same Windows account. Enable autostart by right-clicking its tray icon. Closing loopMIDI's configuration window leaves it running in the tray; exiting it or signing out removes the ports. Keep loopMIDI running and start it before Client and the local MIDI application.
Client retains the cable selections after Save. Each session, click Connect, or enable Settings → Startup → Connect automatically on launch. Start with Windows separately controls launching Client at sign-in. Enabling autostart does not guarantee startup order: if the cables were unavailable when Client opened, start loopMIDI, click Refresh MIDI ports, then connect again. Refresh or restart any MIDI application that cached an earlier port list.
Both applications start with a configuration window on first launch. After a successful Start server/Connect, they can hide to the notification area. Closing the window hides it; it does not stop the relay. Use the tray menu or the window's Stop/Disconnect button to stop it. The window Quit button and tray Quit command stop the MIDI and TCP resources before the process exits.
The client's Settings → Startup page includes Connect automatically on launch. It defaults to off, including for older configurations without this preference. When off, the client loads the last saved settings and waits for Connect. When enabled, it starts trying to connect using those settings whenever the app opens, with the usual retries if the server is unavailable. The choice saves immediately, even while connected, without changing the current connection. Existing saved connection settings are preserved when changing this choice. Start with Windows controls launching the app at sign-in separately; enable both options to connect at sign-in.
The server has the matching Listen automatically on launch option in Settings → Startup, also off by default, including for older configurations. When enabled, launching the server starts listening with its last saved endpoint, TCP address, and token. When off, those settings load and the server waits for Start server. The choice saves immediately and can be changed while listening without stopping the server or changing its saved connection settings. Enable Start with Windows as well to start listening at sign-in.
Both apps also have Start minimized to tray in Settings → Startup, off by default for new and existing configurations. This choice saves immediately and applies on the next launch. It controls the window independently of automatic connection/listening and Start with Windows. Enable the automatic connection or listening option as well to run the relay in the background at launch. Choose Open Settings from the tray icon to restore the window. First-time setup and startup configuration/device errors still open the window.
The Startup page groups Start with Windows, automatic connection/listening, and Start minimized to tray as switches that save immediately. On macOS, Start with Windows is labeled Start at login.
Open Settings from the main window or Open Settings from the tray menu. The separate Settings window has three fixed pages: Connection, Startup, and Diagnostics. Connection changes require Save and can be saved without connecting or opening MIDI hardware. Closing Settings discards connection edits that have not been saved. Disconnect or stop the server before editing an active connection. The main window's Connect/Start server uses the saved configuration. Settings also has a Connect/Start server button that saves connection edits and starts the relay. It changes to Disconnect/Stop server while running and remains available on every settings page. The Diagnostics page fits the window, with scrolling confined to the connection log. Clicking the tray icon or choosing Open MIDI Forwarder restores the main window. Tray icons show a green check when the client is connected or the server is listening, an amber clock while the client connects or retries, and a gray dash when disconnected or stopped. Hover over an icon for its current status. The badges update even when the windows are hidden. A listening server stays green while waiting for a client; its hover text also indicates a connected client.
Settings are JSON files under:
%LOCALAPPDATA%\MIDI Forwarder\server.json
%LOCALAPPDATA%\MIDI Forwarder\client.json
The files contain the selected port names, TCP address, token, interface name,
and the startup preferences. Saving uses a temporary
file followed by replacement. Enabling startup writes a per-user entry under
HKCU\Software\Microsoft\Windows\CurrentVersion\Run; it works from a
published executable, not from dotnet run.
The client upgrades saved ws:// and wss:// addresses from older builds to
the current tcp:// form. No WebSocket listener is present in this build.
The client creates a transient Windows MIDI Services loopback association with two endpoints when that backend is selected:
<name> (Relay) - UMP-only internal side opened by MIDI Forwarder
<name> - public MIDI input/output (default: MIDI Forwarder)
Only the public side creates MIDI 1.0 input/output ports. The Relay side remains discoverable through the Windows MIDI Services UMP API and diagnostic tools; this is not a system-wide visibility or access restriction.
The association is removed when the client stops normally. If the process is killed or the Windows MIDI service crashes, Windows may retain the transient endpoint until the service or computer restarts. The client reports a startup error when Windows MIDI Services or its loopback transport is unavailable.
The WinMM backend instead opens the two selected pre-existing virtual cables. It closes its handles on stop and leaves ownership of those cables with their driver/software. Some WinMM drivers allow only one application per port; close other software using the server's hardware ports if opening them fails. MIDI 1.0 UMP messages from a Windows 11 peer are converted to MIDI 1.0 bytes, including multipart SysEx7, before sending to WinMM. MIDI 2.0 channel messages and SysEx8 cannot be sent through WinMM and produce an explicit error.
Because the adapter uses the Windows MIDI Services preview API, this build is intended for development and testing. Review Microsoft's distribution terms before shipping it as a production product, and revisit the dependency when a supported Windows MIDI API is broadly available.
Run these commands from a terminal in the published application's directory:
.\MidiForwarder.Server.exe --list-ports
.\MidiForwarder.Client.exe --list-ports
.\MidiForwarder.Client.exe --midi-backend
.\MidiForwarder.Server.exe --midi-backend--list-ports prints the WinMM input and output names with their indexes and
also lists MIDI Services endpoints when selected, then exits.
Normal Windows 10 setup uses the Client settings above.
Open Settings → Capture on the server or client and choose Start capture. It records complete traffic in both directions, with timestamps and action markers, without reconnecting. Choose Stop capture, then Open capture folder. Capture is off by default, stops at 100 MiB, and reports any lost records or disk errors. Files remain on this computer and can contain preset names and settings. See Full MIDI capture for recording steps and the file format.
Both applications use compact 520-pixel Fluent windows with shared light and dark themes. The server's main view shows the selected endpoint, client connection, server IP addresses with the TCP port, and two MIDI activity lights. The addresses can be selected and copied into a client; they refresh when network addresses change. Each light stays on briefly after a packet is successfully forwarded in that direction and returns to idle when traffic stops. Open Settings → Diagnostics for message/byte totals and timestamped connection logs; the last connection's totals remain available after disconnect. Counts reset when the server restarts or a new client successfully opens the MIDI endpoint. Settings → Connection contains the endpoint selection, listen address, and masked shared token. The Save button saves these settings; stop the server to edit them. First launch and configuration errors open the Settings window automatically.
The client has the same layout, with MIDI app → server and Server → MIDI app activity lights. Its Connect button starts the relay with the saved settings; Disconnect also stops automatic retries. Open Settings → Diagnostics for connection logs and per-connection message/byte totals. Totals remain available while disconnected or retrying and reset on the next connection. Settings → Connection retains Windows MIDI Services, legacy virtual-cable, and macOS Apple network MIDI options, with the shared token masked. On macOS, Audio MIDI Setup remains available while the client is waiting for its local MIDI connection.
The live configuration window shows timestamped status and connection messages. The server reports when it is waiting for a network client and opens the selected physical MIDI endpoint after that client authenticates. The Diagnostics log includes a message and byte summary for each active direction once per second, including single messages and short bursts. Pending summaries are also logged on disconnect; the totals and activity lights are measured independently. Server messages are also appended to:
%LOCALAPPDATA%\MIDI Forwarder\server.log
The server records startup, unhandled exceptions, selected physical ports, client authentication failures, connection transitions, and shutdown. The client keeps its live log in Diagnostics; its connection loop reports failures, retry delays, and traffic summaries there.
The transport is deliberately small and stream-safe:
- The client sends the ASCII magic
MIDIFWD1, a two-byte big-endian UTF-8 token length, and the token bytes. - The server validates the token using a fixed-time comparison and a five second authentication deadline.
- Each MIDI packet is sent as a four-byte big-endian signed length followed
by a one-byte format (
1for MIDI 1.0 bytes,2for UMP) and the packet bytes.
Frames must be non-empty and are limited to 1 MiB (1,048,576 bytes). The
length prefix allows arbitrary packet boundaries on TCP while preserving
message boundaries and byte order. MIDI Services peers can send UMP directly;
WinMM peers send complete MIDI 1.0 messages. The receiving backend converts the
format as needed, reassembling multipart SysEx7 before sending to WinMM.
The stream is not encrypted. The token authenticates the peer but does not provide confidentiality or protection against an attacker who can observe the LAN. Keep the listener on a trusted network or behind a VPN; never expose the TCP port directly to the public internet.
Build, test, sign and package locally on the appropriate Windows or Mac development machine. GitHub Actions is disabled. Keep the existing local commands below and the release instructions; use audited offline profiles when MIDI access is not authorized, and run native/hardware checks only with explicit authorization.
Restore uses only the sources configured in NuGet.Config, including the
repository's pinned packages:
.\Initialize-SharedMidi.ps1 -PrepareBuild
dotnet restore .\MIDI-Forwarder.slnx --configfile .\NuGet.Config
dotnet build .\MIDI-Forwarder.slnx --no-restore
dotnet test .\MIDI-Forwarder.slnx --no-restorePublish the two self-contained Win-x64 applications with:
.\tools\Publish-Windows.ps1The script automatically downloads and verifies the pinned MIDI SDK for the build, restores packages, and uses fresh intermediate folders for each executable. This is build preparation only; application users need no SDK or setup script.
The configured output locations are:
publish\server\MidiForwarder.Server.exe
publish\client\MidiForwarder.Client.exe
The publish script produces release files only. Configure optional Windows
signing as described in release signing.
Run .\tools\Test-WindowsPublish.ps1 for isolated publisher checks; build
preparation and compilation are simulated, and no application or MIDI is opened.
The projects enable compressed single-file publishing and native-library
self-extraction for win-x64. ReadyToRun is disabled to keep executables
smaller; initial startup can take longer while .NET compiles code. The UI
references only Avalonia's Windows backend and Skia renderer, and the WinMM
adapter references NAudio.WinMM instead of the full audio/WinForms package.
The executables still include the .NET runtime and Windows MIDI Services SDK.
Build output (bin, obj, and publish) is ignored by Git.
The macOS apps use the same TCP protocol as the Windows applications and require macOS 26 or 27. The client defaults to Apple network MIDI, using a loopback RTP-MIDI peer to supply a native driver-owned input/output. The server uses RtMidi's CoreMIDI backend and lists matching physical input/output names. These builds support MIDI 1.0, including MIDI clock and SysEx, and decode MIDI 1.0 UMP/SysEx7 from Windows peers. MIDI 2.0 channel messages and SysEx8 are not supported by the Mac backend.
One-time Mac client setup:
- In Audio MIDI Setup, open MIDI Studio → Configure Network Driver and create
an RTP session. Enable it; the default name/port are
RTP Session 1/5004. - Add a directory entry for
127.0.0.1, port55006, namedMIDI Forwarder. Set the session's Connection policy to Directory entries. Leave live routings as None. The client initiates and reconnects the local session; the persistent-peer checkbox is not needed. - In the client, choose Apple network MIDI (recommended), use the matching
endpoint (
Network RTP Session 1) and Apple session UDP port, and save the TCP server address and shared token. - In PresetMaestro or another MIDI app, select
Network RTP Session 1for both MIDI input and output. The oldMIDI Forwardervirtual ports belong to the compatibility backend.
The Virtual MIDI (compatibility) option retains the paced virtual ports. Apple network MIDI avoids that source's sustained-transfer restriction. See the macOS diagnosis and validation for the measured fourfold FM9 speed improvement and protocol checks.
Build and package both self-contained Apple Silicon apps from PowerShell:
.\tools\Publish-Mac.ps1
# Build and sign only the client on the development/signing Mac:
.\tools\Publish-Mac.ps1 -ClientOnly
dotnet restore .\MIDI-Forwarder.Mac.slnx --configfile .\NuGet.Config
dotnet test .\MIDI-Forwarder.Mac.slnx -c Release --no-restoreThe script publishes from fresh intermediate folders using the pinned shared MIDI
package, including RtMidi, DryWetMIDI, Avalonia, Skia, HarfBuzz and .NET inside
each .app. On the development/signing Mac it signs all native components before
creating each ZIP. Use -SigningIdentity and -NotaryProfile for Developer ID
signing and notarization. Ad hoc signatures are for lab testing only. A Windows
cross-build produces a marked signing input, not an end-user release.
Users copy the already signed .app into Applications and open it. No preparation
script, .NET, Homebrew, PowerShell, compiler or package manager is required.
The Apple network MIDI session is an OS configuration prerequisite for that
backend, as described above. Run --check-network-midi only after that session has been set up as a user would
(see docs/macos-midi-setup.md). No check creates MIDI ports.
The macOS UI uses Start at login and stores per-user LaunchAgents under
~/Library/LaunchAgents. Configure this after moving the apps to their final
location. The menu-bar icon provides settings, stop and quit commands.
src/MidiForwarder.Core/ framing, authentication, settings, codecs
src/MidiForwarder.Midi.WinMM/ physical WinMM ports and startup registration
src/MidiForwarder.Midi.WindowsServices/ virtual loopback and UMP/MIDI 1.0 conversion
src/MidiForwarder.Server/ server UI, listener, and relay lifecycle
src/MidiForwarder.Client/ client UI, reconnect loop, and tray lifecycle
tests/MidiForwarder.Core.Tests/ framing, codec, and bridge tests
assets/ application icon (.ico for Windows, .icns for macOS)
local-packages/ pinned offline restore packages
THIRD-PARTY-NOTICES.md dependency license summary
The MIDI adapter is behind IMidiDuplexPort, and the network relay is behind
the core framing code. This keeps transport and device-specific code separate
from MIDI message semantics.
No physical ports appear. Confirm Windows can see the device through a
WinMM MIDI application, then click Refresh MIDI Ports or run
--list-ports. The server matches the saved port name exactly (ignoring case);
duplicate device names are therefore ambiguous.
The client shows WinMM instead of creating a virtual interface. The OS or
installed MIDI Services does not provide the required API/loopback transport.
Check that Windows 11 is 25H2 or later and the compatible services, loopback
transport are available; the application supplies the native API. Run --midi-backend to see why
WinMM was selected and consult the dependency setup notes.
On Windows 10 this is expected: use the two-cable loopMIDI setup.
The Windows 10 Client or MIDI app cannot see its cables. Start loopMIDI under the same Windows account, confirm both named cables exist, then click Client's Settings → Connection → Refresh MIDI ports. Refresh or restart the MIDI app if it only scans ports at startup. Keep loopMIDI running in the tray; the Client does not create or own these cables. Check the direction table above if messages do not reach the hardware or replies do not reach the MIDI app.
The client keeps reconnecting. Check the server address, firewall rule, listener status, and token. The server log reports invalid-token/protocol rejections; the client status shows the next retry delay.
The application cannot see the automatically created Windows 11 MIDI Forwarder interface. Ensure the client is running
and connected or connecting, choose the configured interface name without a suffix,
and restart the MIDI application's port enumeration if it cached the old list.
If a crashed client left a stale endpoint, restart the Windows MIDI service or
Windows and run the virtual-MIDI check again.
After upgrading from a build that exposed (App) and (Relay), exit that old
client and start the updated client, then reselect MIDI Forwarder for input
and output. A still-running old client will continue to publish its old names.
A device works locally but not remotely. Verify that the server's selected input is the device-to-PC direction and its selected output is the PC-to-device direction. Test with a simple MIDI monitor on each side, and inspect the server/client status logs before changing the token or port assignment.
MIDI Forwarder, including its Client and Server applications, is licensed under the Apache License, Version 2.0. Third-party components retain their respective licences.
See THIRD-PARTY-NOTICES.md for the dependency and license summary. The Windows MIDI Services package is a preview dependency; its distribution terms should be checked independently before redistribution.