MIDI2Kit
High-Level APIUnified 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
| Method | Description |
|---|---|
start() | Start device discovery and event processing |
stop() | Stop all operations and release resources |
makeEventStream() | Create AsyncStream of MIDI2ClientEvent |
Properties
| Property | Type | Description |
|---|---|---|
name | String | Client name used in MIDI-CI |
muid | MUID | This client's unique identifier |
isRunning | Bool | Whether the client is currently running |
configuration | MIDI2ClientConfiguration | Current 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
| Property | Type | Description |
|---|---|---|
muid | MUID | Device's unique identifier |
displayName | String | Human-readable name for UI |
identity | DeviceIdentity | Manufacturer, model, version |
supportsPropertyExchange | Bool | Whether PE is supported |
manufacturerName | String? | 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
| Preset | Description |
|---|---|
.korgModulePro | Simulates KORG Module Pro with typical PE resources |
.generic | Generic MIDI 2.0 device with basic resources |
.rolandStyle | Roland-style device configuration |
.yamahaStyle | Yamaha-style device configuration |
.minimal | Minimal 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
| Property | Type | Default | Description |
|---|---|---|---|
discoveryInterval | Duration | 10s | How often to send Discovery Inquiry |
deviceTimeout | Duration | 60s | Time before marking device as lost |
autoStartDiscovery | Bool | true | Start discovery on client start |
Property Exchange Settings
| Property | Type | Default | Description |
|---|---|---|---|
peTimeout | Duration | 5s | Timeout for PE requests |
maxInflightPerDevice | Int | 2 | Concurrent requests per device |
warmUpBeforeResourceList | Bool | true | Send warm-up request before ResourceList |
Resilience Settings
| Property | Type | Default | Description |
|---|---|---|---|
maxRetries | Int | 2 | Number of retry attempts |
retryDelay | Duration | 500ms | Delay between retries |
Configuration Presets
Pre-configured settings optimized for common scenarios.
| Preset | Description |
|---|---|
.standard | Default settings for most devices |
.korgBLEMIDI | Optimized for KORG Module Pro and BLE MIDI (extended timeouts, warm-up enabled) |
.explorer | For 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
| Case | Description |
|---|---|
.deviceDiscovered(MIDI2Device) | New device found |
.deviceLost(MUID) | Device no longer responding |
.deviceUpdated(MIDI2Device) | Device info changed |
.discoveryStarted | Discovery process started |
.discoveryStopped | Discovery process stopped |
.notification(PENotification) | Property subscription update |
.started | Client started successfully |
.stopped | Client 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
| Case | Description |
|---|---|
.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 |
.clientNotRunning | Operation attempted on stopped client |
.cancelled | Operation 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)")
}
}