SwiftOCA is a pure Swift implementation of the AES70 control protocol, principally used for remote control of professional audio devices.
The package consists of three libraries:
- SwiftOCA: an OCA controller (client)
- SwiftOCAUI: SwiftUI views for binding to OCA classes (macOS/iOS)
- SwiftOCADevice: an OCA device (server)
All APIs are async-safe and support both macOS and Linux: on macOS, FlyingFox is used for socket I/O, and on Linux, IORingSwift.
| Platform | TCP | UDP client | UDP server | WS client | WS server | Local |
|---|---|---|---|---|---|---|
| macOS | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| Linux | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
- Device discovery:
OcaConnectionBrokerdiscovers AES70 devices via DNS-SD/Bonjour (usingNetServiceBrowseron Apple platforms, orlibdns_sdon Linux), with support for both TCP and UDP service types. Devices can also be registered manually for direct connection without DNS-SD. - Property observation:
@OcaPropertyand@OcaBoundedPropertywrappers expose property changes asAsyncSequencestreams, enabling reactive UI updates. - JSON serialization: read the full state of any remote object or block tree as a JSON-compatible dictionary via
jsonObject. - Automatic reconnection: optionally reconnect when a connection drops or a device's IP address changes via mDNS, with configurable options for subscription refresh and object cache retention.
- Full AES70 device implementation: host actuators, sensors, blocks, matrices, managers, and agents.
@OcaDeviceProperty: property wrapper that manages local state and notifies connected controllers on changes.- Block and matrix containers:
OcaBlockandOcaMatrixfor organizing objects into hierarchical or grid-based topologies. - JSON serialization/deserialization: persist and restore device state via
serialize/deserializeand the parameter dataset API. - Multiple transport endpoints: run TCP, UDP, WebSocket, and Unix domain socket endpoints concurrently.
- Automatic view dispatch: the
OcaViewprotocol andOcaDetailViewselect specialized views based on OCA class type. - Pre-built actuator views: gain slider (log-scaled), mute toggle, polarity switch, pan/balance knob, boolean and float actuators.
- Sensor views: level meter with PPM ballistics (color-coded bar graph), identification sensor, and generic sensor displays.
- Block navigation: drill-down sidebar for hierarchical blocks; grid layout for leaf blocks; matrix navigation support.
- Bonjour discovery view: ready-made device browser view for listing and connecting to discovered devices.
Sample code can be found in Examples:
- OCADevice — a sample AES70 device with a gain control, boolean actuator matrix, and multiple transport endpoints.
- OCABrowser — a macOS SwiftUI app that discovers devices via Bonjour and provides a navigable block browser with specialized control views.
- OCABrokerTest — a command-line tool that discovers devices and auto-connects as they appear.
ocacli is a command-line OCA controller that is implemented using SwiftOCA. SwiftOCA is also compatible with third-party OCA controllers such as AES70Explorer.
A Flutter wrapper is available here.
Connect to a device and recursively print the role path of every object:
import SwiftOCA let connection = try await Ocp1TCPConnection(deviceAddress: deviceAddress) try await connection.connect() for actionObject in try await connection.rootBlock.resolveActionObjectsRecursive() .compactMap({ $0.memberObject as? OcaOwnable }) { try? await print("- \(actionObject.rolePathString)") } try? await connection.disconnect()Subscribe to a gain property and react to changes:
import SwiftOCA let connection = try await Ocp1TCPConnection(deviceAddress: deviceAddress) try await connection.connect() let gain = try await connection.resolve(object: OcaGain.self, objectNumber: gainONo) for try await value in gain.$gain { print("gain changed to \(value) dB") }Create an AES70 device with a gain control and serve it over TCP:
import SwiftOCADevice let device = OcaDevice.shared try await device.initializeDefaultObjects() let gain = try await OcaGain( objectNumber: 10020, role: "Main Gain", deviceDelegate: device ) let endpoint = try await Ocp1FlyingSocksStreamDeviceEndpoint(address: listenAddress) try await endpoint.run()Use OcaConnectionBroker to discover devices on the network and connect automatically:
import SwiftOCA let broker = await OcaConnectionBroker( connectionOptions: Ocp1ConnectionOptions(flags: [ .automaticReconnect, .refreshSubscriptionsOnReconnection, ]) ) for try await event in await broker.events { switch event.eventType { case .deviceAdded: try await broker.connect(device: event.deviceIdentifier) print("connected to \(event.deviceIdentifier.name)") case .deviceRemoved: print("lost \(event.deviceIdentifier.name)") default: break } }Apache License 2.0. See LICENSE.md.
Luke Howard lukeh@lukktone.com
