|
NanoOcp
Minimal AES70 / OCP.1 TCP client/server library for d&b Soundscape devices
|
NanoOcp is a C++ library built on the JUCE framework that provides a minimal AES70 / OCP.1 TCP client and server, plus the message structures and device-specific object definitions needed to control AES70-compatible audio devices over a plain TCP connection.
Full API documentation is auto-generated from source and published at:
| Appveyor CI build status | NanoOcp1Demo |
|---|---|
| macOS Xcode | |
| Windows Visual Studio | |
| Linux makefile |
AES70 (also known as OCA — Open Control Architecture) is an open standard for controlling professional audio equipment over IP networks. It defines a rich class hierarchy of controllable objects (gains, mutes, delays, routing matrices, …) and two wire protocols:
| Protocol | Transport | Port |
|---|---|---|
| OCP.1 | TCP (framed) | device-specific; DS100 default: 50014 |
| OCP.3 | WebSocket | not supported by NanoOcp |
NanoOcp implements OCP.1 only and is intentionally minimal — no AES70 object database, no root-block enumeration, no full standards compliance. It provides just enough to:
AddSubscription commands so the device pushes property-change notifications.NanoOcp is structured in three layers:
NanoOcp1Base is the abstract base class that holds the target address/port and exposes three std::function callbacks:
| Callback | When fired |
|---|---|
onConnectionEstablished | TCP connect succeeded |
onConnectionLost | TCP connection dropped or failed |
onDataReceived(ByteVector) | A complete OCP.1 frame arrived |
**NanoOcp1Client** — inherits NanoOcp1Base, Ocp1Connection (raw socket), and juce::Timer. start() starts a periodic timer that retries connectToSocket() until it succeeds. Reconnects automatically after a disconnect.
**NanoOcp1Server** — inherits NanoOcp1Base and Ocp1ConnectionServer (accept loop). start() binds a port and waits for an incoming connection. Only one simultaneous peer is supported.
Ocp1Message is the abstract base for all five OCP.1 message types. Use the static factory Ocp1Message::UnmarshalOcp1Message(bytes) to parse incoming data, then dispatch on GetMessageType():
MessageType | Class | Direction |
|---|---|---|
Command (0) | Ocp1Message | Client → Device |
CommandResponseRequired (1) | Ocp1CommandResponseRequired | Client → Device |
Notification (2) | Ocp1Notification | Device → Client |
Response (3) | Ocp1Response | Device → Client |
KeepAlive (4) | Ocp1KeepAlive | Both |
Ocp1CommandDefinition is a plain struct that bundles the five fields needed to address any OCA property: target ONo, property data type, def-level, property index, and optional parameter bytes. Its four virtual factory methods produce ready-to-send command definitions:
AddSubscriptionCommand() — register for property-change notificationsRemoveSubscriptionCommand() — unregisterGetValueCommand() — read the current valueSetValueCommand(Variant) — write a new valueConcrete dbOcaObjectDef_* structs subclass Ocp1CommandDefinition. Each struct represents one controllable parameter on one class of device. Constructors accept the channel/record/object numbers and compute the correct ONo internally — callers never compose ONos manually.
Generic d&b amplifier objects (Ocp1ObjectDefinitions.h): covers AmpGeneric, Dx, Dy, 5D — power, gain, mute, delay, EQ bands, input select, …
DS100 signal engine objects (Ocp1DS100ObjectDefinitions.h, namespace NanoOcp1::DS100): covers all DS100 parameter boxes (MatrixInput, MatrixOutput, Positioning, CoordinateMapping, ReverbInput, Scene, …).
| Concept | Description |
|---|---|
| ONo (Object Number) | 32-bit identifier encoding device type, record, channel and box/object number. Computed by GetONo() / GetONoTy2(). |
| Def-level | Inheritance depth in the AES70 class hierarchy at which a property is defined (e.g. DefLevel_OcaGain = 4). |
| Command handle | Auto-incrementing 32-bit token assigned by Ocp1CommandResponseRequired. The device echoes it back in the matching Ocp1Response so responses can be correlated to commands. |
| AddSubscription | Command that asks the device to push a Notification every time a property changes. Must be sent once per property before notifications arrive. |
| KeepAlive | Heartbeat frame (carries a heartbeat interval). Both sides send it; absence triggers reconnection. |
NanoOcp1Client runs all socket I/O on a dedicated Ocp1Connection::ConnectionThread.
The callbacksOnMessageThread constructor parameter controls where callbacks fire:
| Value | Callback thread | Use when |
|---|---|---|
false | Socket thread (lower latency) | You manage your own thread safety in the callbacks |
true | JUCE message thread (via MessageManager::callAsync) | Callbacks directly update UI components |
NanoOcp has no CMake or Meson build system — it is designed to be added directly to a Projucer or CMake-based JUCE project.
Source/ directory to your project's include paths..cpp files from Source/ to your build target.juce_core and juce_events JUCE modules are linked.NanoOcp1Demo/ is a complete JUCE desktop application that demonstrates:
AddSubscription commands for power state and gain.SetValue commands from UI controls (power on/off button, gain slider).Open NanoOcp1Demo/NanoOcp1Demo.jucer in the Projucer, export a native project, and build with Xcode / Visual Studio / make.
The full API reference is generated by Doxygen using the doxygen-awesome-css theme and published automatically to GitHub Pages on every push to main via the .github/workflows/docs.yml workflow.
Browse the online docs: https://ChristianAhrens.github.io/NanoOcp/
To generate docs locally:
NanoOcp is distributed under the GNU Lesser General Public License v3.0. See LICENSE for details.