First release
This release does not support authentication. A host on this PC accepts connections based on your approval choice, not on a password. Details are in Authentication is not in this release below.
Network MIDI 2.0 carries MIDI over your existing Ethernet or Wi-Fi network, with no MIDI cables and no audio interface in between. A device connected this way appears in Windows like any other MIDI device, so your DAW and other MIDI software can use it straight away.
Windows can work in either direction, and both at once:
To set any of this up, use the Network MIDI Setup app. This page does not repeat what that app does; see Network MIDI Setup for the walkthrough.
The two ends must be on the same network and the same subnet. Devices announce themselves locally, and those announcements do not normally cross between separate networks, or between a guest network and your main one.
To accept incoming connections, the service must be allowed through the firewall. This is the single most common reason other devices can see this PC but cannot connect to it: they see the advertisement, and their connection request is dropped before the service ever sees it. The Firewall page in Network MIDI Setup does it for you. To do it by hand, see Adding Network MIDI 2 to your firewall.
Connecting out from this PC does not need a firewall change.
Wired is better than Wi-Fi for anything timing-critical. A Wi-Fi radio sleeps between messages, so the first message after a quiet moment waits for it to wake. Most wired networks give round trip times under a millisecond.
The Network MIDI 2.0 specification defines two optional authentication schemes: a shared password for a host, and a user name and password per user. Neither is supported in this release of Windows MIDI Services. Support is planned as a later update.
What that means in practice:
"authentication": "password" or "user", that host is refused at configuration time with AuthenticationNotImplemented. It is deliberately not downgraded to an unprotected host, because silently removing protection somebody asked for would be worse than refusing.If you need to keep other devices off a host, put it on a network you control, or use Ask me first and approve only the devices you recognize.
Discovery is automatic. Devices that advertise themselves appear on their own, usually within a few seconds. There is nothing to scan.
Network MIDI 2.0 stays off the network until you set something up. With no hosts and no connections, the MIDI service opens no network ports and sends nothing for it. The service only listens for device announcements while a connection needs one to find its device. Apps that list the devices on your network, like Network MIDI Setup, do their own listening while they’re open.
Connecting can need action at the other end. Many devices will not simply accept an incoming connection; they wait for you to approve it on a web page, a companion app, or a physical button. If a connection sits at “Connecting”, check the device itself. Windows keeps trying, so once you allow it at the other end the connection completes on its own.
Connections are remembered and re-established on their own. A connection you set up survives a restart of the service or the PC, with no app running. If the device is switched off, the entry stays and connects when the device reappears.
The endpoint goes away while the device is away. When a connection ends, Windows removes the device’s endpoint and its MIDI 1.0 ports, and makes them again when the device comes back. Unlike RTP-MIDI and Bluetooth MIDI, there’s no setting to keep them. When a Network MIDI 2.0 endpoint first appears, Windows asks the device to describe itself, and the two agree on which MIDI protocol to use. Windows only does that for a new endpoint, and a device that was switched off can come back with different answers. Keeping the endpoint would need a big exception to how that works, so Windows doesn’t offer it.
A device that does not advertise can be connected by address. Advertised connections are more efficient, because Windows waits for an announcement rather than repeatedly probing an address.
A device can connect to more than one host on this PC. Each connection gets an endpoint of its own, so apps see a separate device for each one. What’s turned down is one device connected both ways at once, as a client of a host here and as a host this PC is a client of, when it uses the same name and product instance id both ways. That would be two endpoints for one device, so the second connection is refused.
A device with more than one address is tried at each one in turn. A device can have an IPv4 address and several IPv6 addresses, and its announcement doesn’t say which one to use. Windows tries them in the same order it uses for every other app on this PC. That’s usually IPv6 first, but an IPv6 address starting with fd comes after IPv4, and an address this PC has no way to reach is skipped. If the device doesn’t answer at one address, Windows tries the next one right away. Once a connection works, Windows tries that address first next time. To always use one particular address, connect to the device by that address. An administrator can also make the whole PC prefer IPv4 over IPv6.
Naming. Leave the name empty and Windows uses whatever the device calls itself. Give a name and that is what appears everywhere in Windows, including your DAW’s device list.
MIDI 1.0 ports are created by default. Alongside the modern UMP endpoint, Windows creates classic MIDI 1.0 ports so that older software, which does not understand the newer combined API, can use the device. Most apps today fall into that category. This applies in both directions: to devices this PC connects out to, and to devices that connect in to a host here. You can turn it off per entry if you only want the UMP endpoint.
A device that loses data can be sent to more slowly. Some devices can’t keep up when a lot of data arrives at once, like a long SysEx dump. Set a sending speed for the host or the device in Network MIDI Setup. A single note is never held back. See Sending speed.
The rest of this page is for people who want to understand the transport itself, or who edit the configuration file by hand.
The transport implements the MIDI Association Network MIDI 2.0 (UDP Transport) specification, M2-124-UM version 1.0.
| Area | State |
|---|---|
| UDP transport, sessions, invitations | Implemented |
| Retransmission and forward error correction | Implemented |
| mDNS / DNS-SD discovery and advertisement | Implemented |
| Client role and host role, simultaneously | Implemented |
| Remote client approval, allow and deny lists | Implemented |
| Authentication (shared secret, user credential) | Not implemented |
A command the transport does not recognize is answered inside an established session with NAK, reason CommandNotSupported, and is ignored outside one. Commands added to the specification later are therefore refused cleanly rather than half-handled.
A host is advertised over DNS-SD as _midi2._udp.local.
| Item | Rule |
|---|---|
| Service instance name | A single DNS label, so at most 63 bytes once encoded as UTF-8. Must be unique among hosts on this PC |
| UMP endpoint name | Up to 98 characters, per the MIDI 2.0 specification |
| Product instance id | Up to 42 characters, per the MIDI 2.0 specification |
| Port | A specific port, or allocated automatically |
Datagrams are capped at a 1400 byte UDP payload, which keeps the whole packet inside a standard 1500 byte Ethernet MTU for both IPv4 and IPv6. DontFragment is set, so an oversized datagram is dropped rather than fragmented.
Settings live in C:\ProgramData\Microsoft\MIDI\WindowsMidiServices.midiconfig.json, under the Network MIDI 2.0 transport’s identifier.
To move your setup to another PC, don’t copy this file. A copy replaces the other PC’s whole configuration, and brings this PC’s host names, host identity, and network adapter with it. Share a package from the Configurations window in Windows MIDI Settings instead, and import it on the other PC. Importing adds your hosts and connections to what that PC already has, gives a host named after this PC the other PC’s name, and lets a host use any network adapter there.
A few rules apply to the whole file:
{C95DCD1F-...}."_comment" key may appear anywhere. It is written by the tools to make the file readable and is never read back by the service.{
"endpointTransportPluginSettings": {
"{C95DCD1F-CDE3-4C2D-913C-528CB8A4CBE6}": {
"_comment": "Network MIDI 2.0 (UDP)",
"transportSettings": {
"maxForwardErrorCorrectionCommandPackets": 2,
"maxRetransmitBufferCommandPackets": 250,
"outboundPingInterval": 2000,
"invitationPendingTimeout": 120000,
"maxHostConnections": 64,
"directConnectionScanInterval": 20000
},
"create": {
"hosts": {
"{8E2F1A44-0C7B-4E5D-9A31-6B0F2D8C1E77}": {
"name": "Windows MIDI Services Host",
"serviceInstanceName": "studio-pc",
"productInstanceId": "3263827-5150Net2Preview",
"networkProtocol": "udp",
"port": "auto",
"allowPortFallback": true,
"enabled": true,
"advertise": true,
"authentication": "none",
"remoteClientPolicy": "requireApproval",
"createMidi1Ports": true,
"fallbackMidi1PortCount": 1,
"sendSpeedLimit": 0,
"reduceSendSpeedAutomatically": false,
"allowedClients": [
{ "umpEndpointName": "Bome BomeBox", "productInstanceId": "kb7C5D0A_1" }
],
"deniedClients": []
}
},
"clients": {
"{971DA520-4559-4A2F-A72E-F9BEA17521CC}": {
"networkProtocol": "udp",
"createMidi1Ports": true,
"fallbackMidi1PortCount": 1,
"sendSpeedLimit": 4,
"reduceSendSpeedAutomatically": true,
"match": {
"directHostNameOrIP": "192.168.1.253",
"directPort": "5004"
}
}
}
}
}
}
}
These apply to the transport as a whole rather than to one host or client.
| Key | Default | Range | When it takes effect |
|---|---|---|---|
maxForwardErrorCorrectionCommandPackets |
2 | 0 – 10 | New connections |
maxRetransmitBufferCommandPackets |
250 | 0 – 1000 | New connections |
outboundPingInterval |
2000 ms | 250 – 120000 | Within one interval, including open sessions |
invitationPendingTimeout |
120000 ms | 1000 – 600000 | Invitations from that point on |
maxHostConnections |
64 | 1 – 512 | Immediately, checked per invitation |
directConnectionScanInterval |
20000 ms | 250 – 300000 | Next scan |
Two behaviors are worth knowing:
Values are clamped, never refused. A value out of range becomes the nearest bound, and a missing or wrong-typed value becomes the default. A warning is written to the trace when anything was corrected. Read the settings back to see what was actually taken.
Sending a partial transportSettings object resets the keys you left out. Parsing starts from the defaults each time; it is not a merge. Read the current settings, change what you need, and send the whole object back.
Lowering maxHostConnections does not disconnect clients that are already connected.
maxRetransmitBufferCommandPackets is how many recent packets each connection keeps, so it can send one again when the other device missed it. Each connection also keeps no more than 256 KB of them, so a connection sending large SysEx keeps fewer.
| Key | Type | Notes |
|---|---|---|
name |
string | The UMP endpoint name other devices see |
serviceInstanceName |
string | DNS-SD label, max 63 UTF-8 bytes, unique on this PC |
productInstanceId |
string | Max 42 characters |
networkProtocol |
string | Only "udp" |
port |
string | "auto", or a port number as a string |
allowPortFallback |
boolean | Default true. With a specific port, falls back to an automatic one rather than refusing to start |
enabled |
boolean | |
advertise |
boolean | Announce over mDNS. With this off, devices must be given the address |
authentication |
string | "none" only. "password" and "user" are refused in this release |
remoteClientPolicy |
string | "allowAny" or "requireApproval" |
createMidi1Ports |
boolean | Default true. Create classic MIDI 1.0 ports for connected devices |
fallbackMidi1PortCount |
number | Default 1, range 1 – 16. Source and destination ports to create for a device that declares no function blocks. See MIDI 1.0 ports |
fallbackMidi1PortGroups |
array | The groups to create MIDI 1.0 ports for, and their names, for a device that declares no function blocks and has none of its own. Used instead of fallbackMidi1PortCount when it isn’t empty. See MIDI 1.0 ports |
sendSpeedLimit |
number | Default 0, no limit. How fast this host sends to each connected device, as a multiple of MIDI 1.0 wire speed: 1, 2, 4, 8, 16 or 32. See Sending speed |
reduceSendSpeedAutomatically |
boolean | Default false. Send more slowly while a device keeps asking for data again. See Slowing down by itself |
remoteClientSettings |
array | Devices with settings of their own, used instead of the host’s. Each has the device’s umpEndpointName and productInstanceId, and any of sendSpeedLimit, reduceSendSpeedAutomatically, skipInBetweenValuesToCatchUp, useHostSendSpeed and fallbackMidi1PortGroups. See Sending speed and On a host |
allowedClients |
array | Identity objects that may connect without asking |
deniedClients |
array | Identity objects that are refused without asking |
A client identity is { "umpEndpointName": "...", "productInstanceId": "..." }. Identity is used rather than IP address, because a device’s address moves and its identity does not.
| Key | Type | Notes |
|---|---|---|
networkProtocol |
string | Only "udp" |
umpEndpointName |
string | The name this PC gives the device. Missing or empty uses the computer name, which is also what a new host on this PC is called. The device knows this PC by this name and the product instance id, so it’s kept after a restart |
createMidi1Ports |
boolean | Default true. Create classic MIDI 1.0 ports for this device |
fallbackMidi1PortCount |
number | Default 1, range 1 – 16. Source and destination ports to create when the device declares no function blocks. See MIDI 1.0 ports |
fallbackMidi1PortGroups |
array | The groups to create MIDI 1.0 ports for, and their names, when the device declares no function blocks. Used instead of fallbackMidi1PortCount when it isn’t empty. See MIDI 1.0 ports |
sendSpeedLimit |
number | Default 0, no limit. How fast this PC sends to the device, as a multiple of MIDI 1.0 wire speed: 1, 2, 4, 8, 16 or 32. See Sending speed |
reduceSendSpeedAutomatically |
boolean | Default false. Send more slowly while the device keeps asking for data again. See Slowing down by itself |
match |
object | How to find the device |
match carries either an advertised identity or a direct address:
| Key | Notes |
|---|---|
id |
Windows device id from discovery, such as DnsSd#kb7C5D0A_1._midi2._udp.local#0 |
serviceInstance |
The advertised instance name, such as kb7C5D0A_1 |
umpEndpointName |
The device’s own endpoint name |
umpProductInstanceId |
The device’s product instance id |
directHostNameOrIP |
An address or host name. Requires directPort |
directPort |
Port number as a string. Requires directHostNameOrIP |
A configured client is in one of four states:
| State | Meaning |
|---|---|
pending |
Configured, waiting for the service to connect. Also where an entry waits after a host said it was busy, and where a direct connection waits between tries |
live |
Connected, endpoint created |
failed |
The entry itself is not valid, or the host turned the connection down, so retrying cannot help |
unavailable |
Not reported at present. A direct connection that stops answering goes back to pending and is tried again |
A Network MIDI 2.0 device describes itself with function blocks, which it sends when Windows asks it to during endpoint discovery. Those say how many groups the device uses and in which direction, and Windows creates one MIDI 1.0 source and one destination per group from them. A device that describes itself properly needs nothing configured here.
Not every device answers. Another Windows PC never does, and neither do some bridges, so Windows would have nothing to build ports from. For that case the transport describes the connection itself, with one group terminal block for each group that should have ports. Windows creates those ports as soon as the connection opens. There are two ways to say which groups they are:
fallbackMidi1PortCount creates groups 1 to N, each with a source and a destination, named after the endpoint. It defaults to 1, because a device which does not describe itself is usually a bridge with a single cable, and 16 would mean 32 ports of clutter in every MIDI 1.0 application.fallbackMidi1PortGroups lists the groups one by one. Each group can have a source, a destination or both, and a name of its own. When the list isn’t empty, it’s used instead of the count."fallbackMidi1PortGroups": [
{ "groupIndex": 0, "name": "Juno", "hasSourcePort": true, "hasDestinationPort": true },
{ "groupIndex": 1, "name": "TR-8", "hasSourcePort": false, "hasDestinationPort": true },
{ "groupIndex": 3, "name": "", "hasSourcePort": true, "hasDestinationPort": false }
]
| Key | Notes |
|---|---|
groupIndex |
Required. 0 to 15, so 0 is group 1. A later entry for the same group replaces an earlier one |
name |
What MIDI 1.0 apps call the group’s ports. Empty or missing means Windows names them after the endpoint, as it does for the count. Control characters are removed, spaces at either end are trimmed, and a name is cut to 31 characters, which is the longest name a MIDI 1.0 app can show |
hasSourcePort |
Default true. A source port, which apps open as a MIDI input to receive what the device sends |
hasDestinationPort |
Default true. A destination port, which apps open as a MIDI output to send to the device |
An entry with neither port creates nothing and is skipped. A name you give works with either port naming style, because Windows always uses a name you gave a port before one it makes up.
The fallback is ignored once the device describes itself. Function blocks take precedence, so for a device that declares three groups, the count or the list only decides its ports until discovery finishes. Then the device’s own groups replace them. A name you gave a group stays with that group number, so if the device uses that group too, its port keeps your name.
A host has fallbackMidi1PortCount and fallbackMidi1PortGroups too, for every device that connects to it. To give one device ports of its own, with names, add fallbackMidi1PortGroups to the device’s entry in the host’s remoteClientSettings. The host uses that list for the device instead of its own. The host knows the device by its name and product instance id, the same way it knows a device’s own sending speed. An entry which is only there for the ports sets useHostSendSpeed to true, so the device still gets the host’s sending speed:
"remoteClientSettings": [
{
"umpEndpointName": "Studio PC",
"productInstanceId": "...",
"useHostSendSpeed": true,
"fallbackMidi1PortGroups": [
{ "groupIndex": 0, "name": "Studio Juno" },
{ "groupIndex": 1, "name": "Studio TR-8" }
]
}
]
When two Windows PCs connect, each one names its own end. The PC that connects names the ports it creates for the host, and the host names the ports it creates for each device that connects.
| Setting | When it takes effect |
|---|---|
fallbackMidi1PortCount and fallbackMidi1PortGroups |
Straight away, on connections that are already up. Ports are added, removed or renamed without the session being interrupted |
createMidi1Ports |
The next time the endpoint is created, so disconnect and reconnect, or restart the service |
The difference is not arbitrary. Whether an endpoint has MIDI 1.0 ports at all is settled when the endpoint is built and cannot be changed underneath a running one; how many ports it has is driven by properties the service watches, so that can be rewritten live.
A transport from before groups could be chosen ignores fallbackMidi1PortGroups and uses the count. Network MIDI Setup also saves a count that reaches the highest group in the list, so a device still gets enough ports there. An app can check for the fallbackMidi1PortGroups capability to find out whether the installed transport takes the list.
Some devices lose data when a lot of it arrives at once. A hardware synth taking a long SysEx dump, or a network to DIN bridge with a small buffer, may have been built for the speed of a MIDI 1.0 cable. A network is many times faster than that.
Every host and every client entry has a sending speed. It limits how fast this PC sends to the other device. It doesn’t change what this PC receives.
A host can also give one device a speed of its own. The host uses that speed for the device instead of its own, so a slow synth can get MIDI 1.0 wire speed while a computer connected to the same host gets no limit. The host knows the device by its name and product instance id, ignoring uppercase and lowercase differences, the same way it remembers whether to let the device in. So the device gets its own speed every time it connects, and right away if it’s already connected. Network MIDI Setup shows each connected device’s speed under its host, with a Speed… button. In PowerShell, use Set-MidiNetworkRemoteClientSendSpeed.
sendSpeedLimit |
Speed |
|---|---|
0 |
No limit. This is the default |
1 |
MIDI 1.0 wire speed: 31,250 bits a second, the speed of a DIN cable |
2, 4, 8, 16, 32 |
That many times MIDI 1.0 wire speed. 32 is about 1 megabit a second |
A number above 32 means no limit. The speed is measured in the bytes the same messages would take on a MIDI 1.0 cable, so at 1 a SysEx dump takes about as long as it would over a cable.
A single message is never held back. After a quiet moment, a short burst goes out at once: 64 bytes at wire speed, and 64 more for each step up. Only what comes after that is spaced out. So somebody playing a keyboard won’t notice a limit, while a 3,000 byte SysEx dump at wire speed takes about a second, as it would on a cable.
Windows holds the extra messages and sends them as fast as the limit allows. Notes, program changes, SysEx and other messages are never dropped to keep to the limit. If an app keeps sending faster than that, its sends slow down to match.
A fast move is thinned out so the device can catch up. Moving a fader or a pitch wheel, or pressing on a key with aftertouch, can send hundreds of messages a second, which is more than a slow limit carries. When more than about a tenth of a second of messages is waiting, Windows skips some of the steps in the middle of each control change, pressure or pitch bend move. Where each move starts, every point where it turns around, and where it ends are always sent, and so is the value right before and after each note. So the device gets to the right value sooner, instead of playing every step late. A value sent several times in a row is always kept, so a button that sends the same value each time it’s pressed still works.
Some devices use these messages in other ways, such as an endless knob that sends how far it turned rather than where it is, or a control surface that sends meter levels as pressure. Those messages go up and down in short runs, and the first 16 steps of every run are always sent, so they’re left alone in nearly every case. If a device still loses steps, give it a faster speed or none, or turn off skipping for it.
You can turn the skipping off. Every host, every client entry and every device with a speed of its own has a Skip in-between values to catch up setting, which is on by default. Turn it off for a device that needs every message, and Windows sends them all, however late they get. In Network MIDI Setup, it’s a check box next to the sending speed. In the API, it’s SkipInBetweenValuesToCatchUp, and in PowerShell it’s -NoSkippingInBetweenValues. RTP-MIDI has the same setting.
A change applies right away, including to connections that are already up, without disconnecting them.
When a device misses a packet, it asks for it again. With reduceSendSpeedAutomatically turned on, Windows takes that as a sign the device can’t keep up:
sendSpeedLimit.Each connection keeps its own speed. Network MIDI Setup shows when a connection has slowed down, and so does CurrentSendSpeedLimit in the API.
A PC can be on several networks at once. A host can run on all of them, which is the default, or be limited to one network adapter. A host limited to one adapter advertises itself only on that adapter, and only answers devices that reach it through that adapter.
Windows knows each adapter by a GUID. A USB network adapter often gets a new GUID when it’s plugged into a different USB port, so the adapter’s hardware address is remembered too. When no adapter has the GUID anymore, the adapter with that hardware address is used instead.
An adapter counts as missing when it’s gone, turned off, or doesn’t have an IP address yet. What the host does then depends on its fallback setting:
| Fallback | While the adapter is missing |
|---|---|
| On, the default | The host runs on every adapter, and moves back to its own adapter when it’s back |
| Off | The host doesn’t run. It starts by itself when the adapter is back |
A host that’s waiting for its adapter shows a warning in Network MIDI Setup, and MIDI Notifications tells you about it. Changing a host’s adapter restarts the host, which ends its connections.
When a host uses requireApproval, an unknown device is held pending until somebody answers. A decision has a scope:
| Scope | Effect |
|---|---|
once |
This connection only. Held in memory |
untilRestart |
Until the service restarts. Held in memory |
always |
Written to allowedClients or deniedClients in the configuration file |
A device left pending is dropped after invitationPendingTimeout, which defaults to two minutes because it is scaled to somebody walking over to a device rather than to a network round trip.