Why This Release Matters
macOS 15 Sequoia introduced new CoreMIDI classes discoverable in the SDK headers: MIDIUMPEndpointManager for querying UMP endpoint metadata and MIDICIDeviceManager for observing CI-capable devices known to CoreMIDI. These classes were not highlighted in Apple’s release notes, but they are present in the SDK and usable at runtime. Note that MIDISendEventList — the MIDI 2.0 native send path — has been available since macOS 11 Big Sur; it is not new to macOS 15.
Meanwhile, macOS 26.4 Tahoe added Network MIDI 2.0 (UDP) support at the system level — the only CoreMIDI-related change confirmed in the official release notes for that version.
This MIDI2Kit release integrates with these APIs to make smarter decisions: which endpoints are UMP-native, which devices are already managed by the system, and which MIDI-CI participants are Apple’s own built-in clients. The result is fewer false positives, better routing, and a foundation ready for Apple’s evolving MIDI 2.0 subsystem.
CoreMIDI gives you the wire and a growing set of discovery primitives. MIDI2Kit gives you the full MIDI-CI protocol stack — Discovery, Property Exchange, Profile Configuration — that sits above it.
CoreMIDI APIs Used in This Release
Before diving into MIDI2Kit’s changes, here is what MIDI2Kit now uses from CoreMIDI and where each API comes from. Note: Apple did not prominently feature most of these in release notes — they are discoverable through SDK headers.
- MIDIUMPEndpointManager (macOS 15.0+) — A singleton that enumerates all UMP endpoints visible to the system. Each endpoint exposes function block information, MIDI version support, and a manufacturer-assigned name. Found in the macOS 15 SDK headers; not highlighted in official release notes.
- MIDICIDeviceManager (macOS 15.0+) — Provides a list of
MIDICIDeviceobjects discovered by CoreMIDI. In practice, we observe that this list includes CI participants with Apple’s manufacturer SysEx ID (0x11), which we use as a heuristic for auto-populating the MUID blacklist. Found in the macOS 15 SDK headers; not highlighted in official release notes. - MIDISendEventList (macOS 11.0+) — The MIDI 2.0 send path, available since macOS Big Sur. Not new to macOS 15, but central to MIDI2Kit’s smart SysEx routing for UMP-native destinations.
- Network MIDI 2.0 (UDP) (macOS 26.4) — The only CoreMIDI-related change confirmed in Apple’s official macOS 26.4 release notes. Extends the existing Network MIDI session model to carry UMP packets over UDP.
- MIDIUMPMutableEndpoint (macOS 15.0+) — Our testing on macOS 26.4 shows the
registerFunctionBlockserror (present on macOS 15) has been resolved, though with limited parameter support.
What MIDI2Kit Now Supports
1. UMP Endpoint Detection (#36)
MIDI2Kit’s transport layer now queries MIDIUMPEndpointManager when enumerating sources and destinations on macOS 15+. Each MIDISourceInfo and MIDIDestinationInfo object is enriched with a new umpEndpointInfo field that carries the UMP endpoint metadata Apple exposes.
A convenience computed property, isUMPNative, makes routing decisions straightforward: if the destination is UMP-native, MIDI2Kit can skip the legacy SysEx packaging path entirely. When the system reports a UMP endpoint update, the notification is forwarded through the existing setupChanged mechanism, so apps built on MIDI2Kit receive it without any code changes.
On macOS 14 and earlier, none of this code runs — all @available(macOS 15, *) guards are in place and the existing behavior is unchanged.
2. Smart SysEx Sending (#38)
MIDI-CI messages are SysEx, and sending SysEx to a UMP-native endpoint has historically required converting back to MIDI 1.0 byte streams. With MIDI2Kit’s new UMP endpoint detection (macOS 15+), the library can now route SysEx through MIDISendEventList (available since macOS 11) as Data 64 (SysEx7) packets for UMP-native destinations.
MIDI2Kit now exposes a SysExSendStrategy enum on CoreMIDITransport:
.legacyOnly— The default. UsesMIDISendwith a legacy MIDI 1.0 packet list. Safe for all devices, including KORG BLE-MIDI hardware with known UMP quirks..auto— ChecksisUMPNativeat send time. UMP-native destinations receive Data 64 packets viaMIDISendEventList; everything else falls back to the legacy path..umpOnly— Forces Data 64 delivery regardless of destination capability. Useful for testing or environments where all connected hardware is confirmed MIDI 2.0 native.
// Default: safe for all hardware (no change required)
let transport = try CoreMIDITransport(name: "MyApp")
// Opt in to smart routing on macOS 15+
transport.sysExSendStrategy = .auto
// Force UMP-native delivery for all destinations
transport.sysExSendStrategy = .umpOnly
The .korgBLEMIDI configuration preset in MIDI2Client continues to set .legacyOnly explicitly, preserving compatibility with KORG Module Pro and other BLE-MIDI devices that have not yet implemented the full UMP send path.
3. MIDICIDeviceManager Integration (#37)
One of the more subtle problems in MIDI-CI Discovery is the presence of CI participants whose MUIDs carry Apple’s manufacturer SysEx ID (0x11). In our testing, these participants appear in Discovery broadcasts but do not respond to Property Exchange requests, resulting in timeouts. The exact nature of these participants is not documented in Apple’s public release notes or developer documentation — we observe them empirically through the MIDICIDeviceManager class available in the macOS 15 SDK.
MIDI2Kit now queries MIDICIDeviceManager.shared.discoveredCIDevices at startup on macOS 15+ and uses a heuristic to blacklist MUIDs whose deviceInfo.manufacturerID matches Apple’s SysEx ID (0x11 0x00 0x00). This is an empirical workaround, not based on any official Apple guidance.
The integration is dynamic: MIDI2Kit observes MIDICIDeviceManager notifications and updates the blacklist as CI devices appear or disappear. Apps that previously needed to configure muidBlacklist manually can remove that code on macOS 15+, though the manual API remains available as a fallback.
4. MIDI-CI v1.2 Compliance (#41)
The MIDI-CI v1.2 specification (M2-101-UM, published by the MIDI Association in June 2023) introduces Management Messages: ACK/NAK responses at the CI protocol level, a revised Notify message type, and version negotiation fields in Discovery. This is a MIDI Association standard, not an Apple feature — MIDI2Kit’s v1.2 compliance work updates its own message builders and parsers to conform to this specification.
All message builders in CIMessageBuilder now accept an optional ciVersion parameter (defaulting to .v1_1 for backward compatibility). The DiscoveredDevice type gains a ciVersion field populated from the Discovery Reply, enabling per-device version tracking.
Key changes in the v1.2 implementation:
- ACK/NAK builders — New
ciAck()andciNak()methods onCIMessageBuilderfor Management Message responses. - PE Notify message type — Notify uses message type
0x38in CI v1.1 and0x3Fin CI v1.2. MIDI2Kit now selects the correct type at send time based on the negotiated version stored inDiscoveredDevice.ciVersion. - Version negotiation — The Discovery handshake records the remote device’s reported CI version. All subsequent messages to that device use the appropriate message format automatically.
// Per-device version is tracked automatically after Discovery
let device: DiscoveredDevice = ...
print(device.ciVersion) // .v1_1 or .v1_2
// Builders default to v1.1 for backward compatibility
let msg = CIMessageBuilder.discoveryInquiry(sourceMUID: myMUID)
// Opt in to v1.2 message format explicitly
let ackMsg = CIMessageBuilder.ciAck(
sourceMUID: myMUID,
destinationMUID: device.muid,
ciVersion: .v1_2
)
5. Profile Configuration (#42)
MIDI-CI Profile Configuration defines a mechanism for devices to advertise named behavioral profiles — “General MIDI”, “Piano”, “Drawbar Organ” — and for hosts to activate or deactivate them on specific channels. MIDI2Kit now includes a complete set of builders and parsers for the Profile Configuration message category.
New message builders in CIMessageBuilder:
profileInquiry()— Query a device for its supported profiles.profileReply()— Respond with enabled and disabled profile lists.setProfileOn()/setProfileOff()— Activate or deactivate a profile on a given channel.- Profile Enabled/Disabled Report — Unsolicited notifications sent by a device when a profile changes state.
All builders accept the numChannels field introduced in CI v1.2, which indicates how many channels a profile activation covers. The MIDICIProfileID struct captures all five bytes of a profile identifier in a type-safe wrapper.
macOS 26.4 Tahoe: Detailed Investigation Results
We ran the UMPEndpointPoC test harness (v3, Parameter Sweep) on macOS 26.4 (Build 25E246) without external MIDI devices connected. Here are the raw findings.
Test A: registerFunctionBlocks — 16-Pattern Sweep
On macOS 15, registerFunctionBlocks threw Foundation._GenericObjCError code=0 for all 16 parameter combinations. On macOS 26.4, the error is resolved: 0/16 register errors, 0/16 enable errors.
However, only 1 of 16 combinations produced a Function Block visible in MIDIUMPEndpointManager:
| Parameter | Working value |
|---|---|
| direction | .bidirectional |
| maxSysEx8Streams | 0 |
| midi1Info | .notMIDI1 |
| uiHint | .unknown |
| markAsStatic | true |
| Timing | register before enable |
The 15 failing combinations each changed exactly one parameter from the Baseline. The failures are silent — no error is returned, but functionBlocks.count is 0 in MIDIUMPEndpointManager. Changing any of the following from the Baseline value causes the FB to disappear:
directionto.inputor.outputmaxSysEx8Streamsto1midi1Infoto.unrestrictedor.restrictBWuiHintto.receiver,.sender, or.sndRcvmarkAsStatictofalse- Registration timing to after
setEnabled(true)
On macOS 15, all 16 showed direction=0 (Unknown) in EndpointManager. On macOS 26.4, Case #0 correctly shows direction=3 (bidirectional).
Test B: MIDIInputPort UMP Reception
An input port created with MIDIInputPortCreateWithProtocol(._2_0) connected to all sources. After 10 seconds of monitoring: 0 UMP packets received, 0 Data 64 (SysEx7) packets. No external MIDI devices were connected, so we could not verify whether CoreMIDI still filters CI SysEx from the UMP path. This remains an open question.
Test C: MIDIUMPEndpointManager Enumeration
Only 1 UMP endpoint appeared: the PoC’s own virtual endpoint. Without external devices, we could not verify whether KORG KeyStage (a MIDI 1.0 device) or other hardware appears in the UMP endpoint list.
Network MIDI 2.0 (UDP)
macOS 26.4’s official release notes confirm Network MIDI 2.0 support. MIDI2Kit’s receive path uses MIDIInputPortCreateWithProtocol(._2_0), which should handle Network MIDI 2.0 UMP streams transparently. The send path currently uses MIDISend (legacy MIDI 1.0 API) for SysEx; the new SysExSendStrategy.auto option routes to MIDISendEventList for UMP-native destinations, but this has not yet been tested over a Network MIDI 2.0 session. No external Network MIDI 2.0 peers were available during testing.
MIDIDriverKit
Investigated as a potential alternative for virtual MIDI 2.0 devices. Conclusion: not viable for SPM packages. MIDIDriverKit requires C++, Xcode project targets, DriverKit entitlements (Apple approval needed), and SIP-enabled environments. The existing MIDIDestinationCreateWithBlock / MIDIUMPMutableEndpoint approach remains the correct path for MIDI2Kit.
Overall Verdict: PARTIAL-GO
MIDIUMPMutableEndpoint is usable on macOS 26.4 with the Baseline configuration. Full FunctionBlock customization and Data 64 reception through the UMP path remain unverified. Production use of the legacy API continues to be recommended until external device testing confirms broader compatibility.
Code Example: UMP-Aware SysEx Sending
Here is a complete example showing how to configure MIDI2Kit to use smart SysEx routing on macOS 15+, with a safe fallback for older systems:
import MIDI2Kit
// Create a client with smart SysEx routing where available
var config = MIDI2ClientConfiguration.standard
if #available(macOS 15, *) {
config.sysExSendStrategy = .auto
}
let client = try MIDI2Client(name: "MyApp", configuration: config)
try await client.start()
// The transport now automatically selects the best send path
// for each destination based on its UMP capability
for await event in await client.makeEventStream() {
switch event {
case .deviceDiscovered(let device):
print("Found: \(device.displayName)")
// UMP-native devices will use MIDISendEventList
// Legacy devices will use MIDISend
let info = try await client.getDeviceInfo(from: device.muid)
print("Manufacturer: \(info.manufacturer)")
default:
break
}
}
And here is how to detect UMP endpoint metadata directly from the transport layer:
import MIDI2Transport
let transport = try CoreMIDITransport(name: "Inspector")
try await transport.start()
let destinations = await transport.destinations
for dest in destinations {
if let umpInfo = dest.umpEndpointInfo {
print("\(dest.name): UMP native")
print(" MIDI version: \(umpInfo.midiProtocol)")
print(" Function blocks: \(umpInfo.functionBlockCount)")
} else {
print("\(dest.name): MIDI 1.0 legacy")
}
}
Backward Compatibility
All macOS 15+ code paths are guarded with @available(macOS 15, *) checks. On macOS 14 and earlier, MIDI2Kit behaves identically to the previous release. No existing API surface has changed.
The following defaults are preserved to ensure safe upgrades:
sysExSendStrategydefaults to.legacyOnly— no behavior change without explicit opt-in.ciVersionon all builders defaults to.v1_1— existing device compatibility is unchanged.CIManagerConfiguration.registerFromInquiryremainsfalseby default — Discovery Reply filtering is unchanged.
All 705 tests pass on macOS 14 (Sonoma), macOS 15 (Sequoia), and macOS 26.4 (Tahoe) targets.
Get Started with MIDI2Kit
Open source, MIT licensed, and ready for macOS 15+. Add it to your project in seconds.