MIDI2Kit

High-Level API

Unified high-level API for MIDI 2.0 applications. MIDI2Client provides automatic device discovery, Property Exchange, and event streaming.

Recommended for most use cases. MIDI2Kit combines MIDI2CI and MIDI2PE into a simple, unified API with automatic caching and optimized presets.

MIDI2Client

The main entry point for MIDI 2.0 applications. Manages device discovery, Property Exchange, and event streaming.

public actor MIDI2Client

Initialization

// Basic initialization
let client = try MIDI2Client(name: "MyApp")

// With custom configuration
var config = MIDI2ClientConfiguration()
config.discoveryInterval = .seconds(10)
config.peTimeout = .seconds(5)
let client = try MIDI2Client(name: "MyApp", configuration: config)

// With preset for KORG BLE MIDI devices
let client = try MIDI2Client(name: "MyApp", preset: .korgBLEMIDI)

Lifecycle Methods

MethodDescription
start()Start device discovery and event processing
stop()Stop all operations and release resources
makeEventStream()Create AsyncStream of MIDI2ClientEvent

Properties

PropertyTypeDescription
nameStringClient name used in MIDI-CI
muidMUIDThis client's unique identifier
isRunningBoolWhether the client is currently running
configurationMIDI2ClientConfigurationCurrent configuration

Property Exchange Methods

// Get device info
let info = try await client.getDeviceInfo(from: device.muid)

// Get resource list
let resources = try await client.getResourceList(from: device.muid)

// Generic GET
let response = try await client.get("Volume", from: device.muid)

// Generic SET
try await client.set("Volume", data: ["level": 80], to: device.muid)

Diagnostics

// Get comprehensive diagnostics
let diagnostics = await client.diagnostics
print(diagnostics.lastDestinationDiagnostics)
print(diagnostics.lastCommunicationTrace)

MIDI2Device

Wrapper for discovered MIDI-CI devices with automatic caching and type-safe property access.

public actor MIDI2Device

Properties

PropertyTypeDescription
muidMUIDDevice's unique identifier
displayNameStringHuman-readable name for UI
identityDeviceIdentityManufacturer, model, version
supportsPropertyExchangeBoolWhether PE is supported
manufacturerNameString?Known manufacturer name

Cached Properties

// These are automatically cached after first fetch
let info = try await device.deviceInfo       // DeviceInfo
let resources = try await device.resourceList // [ResourceListEntry]

// Invalidate cache to force refresh
await device.invalidateCache()

Type-Safe Property Access

// Define your property type
struct VolumeInfo: Codable {
    let level: Int
    let muted: Bool
}

// Get with type inference
if let volume = try await device.getProperty("Volume", as: VolumeInfo.self) {
    print("Level: \(volume.level), Muted: \(volume.muted)")
}

// Set property
try await device.setProperty("Volume", value: VolumeInfo(level: 80, muted: false))

MockDevice

Built-in MIDI-CI Responder for testing without hardware. Simulate devices with customizable presets.

public actor MockDevice

New in v1.0.5. MockDevice allows testing MIDI-CI and Property Exchange without physical MIDI hardware.

Creating a MockDevice

import MIDI2Kit
import MIDI2Transport

// Create loopback transport pair
let (initiatorTransport, responderTransport) = LoopbackTransport.createPair()

// Create mock device with preset
let mockDevice = MockDevice(
    transport: responderTransport,
    preset: .korgModulePro
)
try await mockDevice.start()

Available Presets

PresetDescription
.korgModuleProSimulates KORG Module Pro with typical PE resources
.genericGeneric MIDI 2.0 device with basic resources
.rolandStyleRoland-style device configuration
.yamahaStyleYamaha-style device configuration
.minimalMinimal device for basic testing

Testing with MockDevice

// Set up initiator side
let ciManager = CIManager(transport: initiatorTransport, ...)
let peManager = PEManager(transport: initiatorTransport, ...)
try await ciManager.start()

// Discover the mock device
await ciManager.startDiscovery()
// MockDevice responds to Discovery Inquiry automatically

// PE operations work exactly like with real devices
let response = try await peManager.get("DeviceInfo", from: mockDevice.handle)

// Custom resources
await mockDevice.setResource("CustomProp", value: ["key": "value"])

MIDI2ClientConfiguration

Configuration options for MIDI2Client behavior.

public struct MIDI2ClientConfiguration: Sendable

Discovery Settings

PropertyTypeDefaultDescription
discoveryIntervalDuration10sHow often to send Discovery Inquiry
deviceTimeoutDuration60sTime before marking device as lost
autoStartDiscoveryBooltrueStart discovery on client start

Property Exchange Settings

PropertyTypeDefaultDescription
peTimeoutDuration5sTimeout for PE requests
maxInflightPerDeviceInt2Concurrent requests per device
warmUpBeforeResourceListBooltrueSend warm-up request before ResourceList

Resilience Settings

PropertyTypeDefaultDescription
maxRetriesInt2Number of retry attempts
retryDelayDuration500msDelay between retries

Configuration Presets

Pre-configured settings optimized for common scenarios.

PresetDescription
.standardDefault settings for most devices
.korgBLEMIDIOptimized for KORG Module Pro and BLE MIDI (extended timeouts, warm-up enabled)
.explorerFor debugging and device exploration (verbose logging)
// Use preset
let client = try MIDI2Client(name: "MyApp", preset: .korgBLEMIDI)

// Or apply preset to existing config
var config = MIDI2ClientConfiguration()
config.applyPreset(.korgBLEMIDI)
config.peTimeout = .seconds(15)  // Further customize
let client = try MIDI2Client(name: "MyApp", configuration: config)

MIDI2ClientEvent

Events emitted by MIDI2Client during operation.

public enum MIDI2ClientEvent: Sendable
CaseDescription
.deviceDiscovered(MIDI2Device)New device found
.deviceLost(MUID)Device no longer responding
.deviceUpdated(MIDI2Device)Device info changed
.discoveryStartedDiscovery process started
.discoveryStoppedDiscovery process stopped
.notification(PENotification)Property subscription update
.startedClient started successfully
.stoppedClient stopped
.error(MIDI2Error)Error occurred
for await event in await client.makeEventStream() {
    switch event {
    case .deviceDiscovered(let device):
        print("Found: \(device.displayName)")
    case .deviceLost(let muid):
        print("Lost: \(muid)")
    case .notification(let notification):
        print("Property changed: \(notification.resource)")
    case .error(let error):
        print("Error: \(error)")
    default:
        break
    }
}

MIDI2Error

Structured errors for MIDI2Kit operations.

public enum MIDI2Error: Error, Sendable
CaseDescription
.deviceNotResponding(muid:resource:timeout:)Device did not respond within timeout
.propertyNotSupported(resource:)Requested property not available
.communicationFailed(underlying:)Low-level communication error
.deviceNotFound(muid:)Device MUID not in known devices
.clientNotRunningOperation attempted on stopped client
.cancelledOperation was cancelled
.transportError(Error)CoreMIDI transport error
.invalidConfiguration(String)Invalid configuration value
do {
    let info = try await client.getDeviceInfo(from: device.muid)
} catch let error as MIDI2Error {
    switch error {
    case .deviceNotResponding(let muid, let resource, let timeout):
        print("Device \(muid) timed out after \(timeout)")
    case .propertyNotSupported(let resource):
        print("Property '\(resource)' not supported")
    default:
        print("Error: \(error)")
    }
}