MIDI2Transport

Transport Module

MIDI I/O abstraction layer. Production CoreMIDI implementation and MockTransport for testing.

MIDITransport Protocol

The core protocol that abstracts MIDI I/O operations. Both production and test implementations conform to this protocol.

public protocol MIDITransport: Sendable

Required Methods

MethodDescription
send(_:to:)Send MIDI data to a destination
connect(to:)Connect to a MIDI source
disconnect(from:)Disconnect from a source
connectToAllSources()Connect to all available sources
findMatchingDestination(for:)Find destination for a source (same entity)
shutdown()Shut down and finish all streams

Required Properties

PropertyTypeDescription
receivedAsyncStream<MIDIReceivedData>Stream of incoming MIDI data
sources[MIDISourceInfo]Available MIDI sources
destinations[MIDIDestinationInfo]Available MIDI destinations
setupChangedAsyncStream<Void>Setup change notifications

CoreMIDITransport

Production implementation using Apple's CoreMIDI framework.

public class CoreMIDITransport: MIDITransport
// Create transport
let transport = try CoreMIDITransport(clientName: "MyApp")

// Connect to all sources
try await transport.connectToAllSources()

// Send data
let data: [UInt8] = [0xF0, 0x7E, 0x7F, ...]
try await transport.send(data, to: destinationID)

// Receive data
for await received in transport.received {
    print("Received \(received.data.count) bytes from \(received.sourceID)")
}

// Handle setup changes (device connected/disconnected)
for await _ in transport.setupChanged {
    print("MIDI setup changed")
    // Refresh device list
}

⚠️ Important: MIDISourceID and MIDIDestinationID are session-scoped handles, not persistent IDs. They may change across reboots or device reconnections. For persistent identification, use uniqueID from endpoint info.

MockMIDITransport

Mock implementation for unit testing without real MIDI hardware.

public actor MockMIDITransport: MIDITransport
// Create mock transport
let mockTransport = MockMIDITransport()

// Add mock endpoints
await mockTransport.addMockSource(MIDISourceInfo(
    sourceID: MIDISourceID(1),
    name: "Test Device",
    uniqueID: 12345
))
await mockTransport.addMockDestination(MIDIDestinationInfo(
    destinationID: MIDIDestinationID(1),
    name: "Test Device",
    uniqueID: 12345
))

// Simulate receiving data
await mockTransport.simulateReceive(
    [0xF0, 0x7E, 0x7F, ...],
    from: MIDISourceID(1)
)

// Check sent messages
let sent = await mockTransport.sentMessages
XCTAssertEqual(sent.count, 1)
XCTAssertEqual(sent[0].data, expectedData)

Test Helpers

MethodDescription
simulateReceive(_:from:)Simulate incoming MIDI data
addMockSource(_:)Add a mock MIDI source
addMockDestination(_:)Add a mock MIDI destination
sentMessagesArray of all sent messages for verification
clearSentMessages()Clear the sent messages array

Endpoint Types

MIDISourceID / MIDIDestinationID

Type-safe wrappers for CoreMIDI endpoint references.

public struct MIDISourceID: Sendable, Hashable {
    public let value: UInt32
}

public struct MIDIDestinationID: Sendable, Hashable {
    public let value: UInt32
}

MIDISourceInfo / MIDIDestinationInfo

Endpoint metadata including name, manufacturer, and persistent ID.

PropertyTypeDescription
sourceID / destinationIDMIDISourceID / MIDIDestinationIDSession-scoped handle
nameStringEndpoint display name
manufacturerString?Manufacturer name
isOnlineBoolTrue if currently available
uniqueIDInt32?Persistent ID across sessions

MIDIReceivedData

Incoming MIDI data with source information.

public struct MIDIReceivedData: Sendable {
    public let data: [UInt8]       // Raw MIDI bytes
    public let sourceID: MIDISourceID?  // Source endpoint
    public let timestamp: UInt64    // CoreMIDI timestamp
}

Error Types

public enum MIDITransportError: Error, Sendable
CaseDescription
notInitializedTransport not initialized
clientCreationFailed(Int32)Failed to create MIDI client
portCreationFailed(Int32)Failed to create MIDI port
sendFailed(Int32)Failed to send MIDI data
connectionFailed(Int32)Failed to connect to source
destinationNotFound(UInt32)Destination endpoint not found
sourceNotFound(UInt32)Source endpoint not found