Umsci
Upmix Spatial Control Interface — OCA/OCP.1 spatial audio utility
Loading...
Searching...
No Matches
DeviceController.h
Go to the documentation of this file.
1/* Copyright (c) 2026, Christian Ahrens
2 *
3 * This file is part of Umsci <https://github.com/ChristianAhrens/Umsci>
4 *
5 * This tool is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License version 3.0 as published
7 * by the Free Software Foundation.
8 *
9 * This tool is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this tool; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#pragma once
20
21#include <JuceHeader.h>
22
23#include <NanoOcp1.h>
24#include <Ocp1ObjectDefinitions.h>
25#include <Variant.h>
26
27
94class DeviceController : public juce::Timer, public juce::MessageListener
95{
96public:
113
137
160
161 static constexpr std::uint16_t sc_MAX_INPUTS_CHANNELS = 128;
162 static constexpr std::uint16_t sc_MAX_OUTPUT_CHANNELS = 64;
163 static constexpr std::uint16_t sc_MAX_FUNCTION_GROUPS = 32;
164 static constexpr std::uint16_t sc_MAX_REVERB_ZONES = 4;
165
183 {
184 std::int16_t pri;
185 std::int16_t sec;
186
187 static constexpr std::int16_t sc_INV = 0;
188
190 {
191 pri = sc_INV;
192 sec = sc_INV;
193 };
195 {
196 *this = rhs;
197 };
198 RemObjAddr(std::int16_t a, std::int16_t b)
199 {
200 pri = a;
201 sec = b;
202 };
203 juce::String toNiceString() const
204 {
205 if (sc_INV != pri && sc_INV != sec)
206 return juce::String(sec) + "/" + juce::String(pri);
207 else if (sc_INV != pri)
208 return juce::String(pri);
209 else if (sc_INV != sec)
210 return juce::String(sec);
211 else
212 return juce::String();
213 }
214 juce::String toString() const
215 {
216 return juce::String(pri) + "," + juce::String(sec);
217 }
218 static juce::String toString(const std::vector<RemObjAddr>& RemObjAddrs)
219 {
220 auto objectListString = juce::String();
221
222 for (auto const& objectAddressing : RemObjAddrs)
223 objectListString << objectAddressing.toString() << ";";
224
225 return objectListString;
226 }
227 bool fromString(const juce::String& commaseparatedStringRepresentation)
228 {
229 juce::StringArray sa;
230 auto tokens = sa.addTokens(commaseparatedStringRepresentation, ",", "");
231 if (tokens != 2 || sa.size() != 2)
232 return false;
233
234 pri = std::int16_t(sa[0].getIntValue());
235 sec = std::int16_t(sa[1].getIntValue());
236
237 return true;
238 }
239 static RemObjAddr createFromString(const juce::String& commaseparatedStringRepresentation)
240 {
241 juce::StringArray sa;
242 sa.addTokens(commaseparatedStringRepresentation.trimCharactersAtEnd(","), ",", "");
243 if (sa.size() != 2)
244 return RemObjAddr();
245
246 return RemObjAddr(std::int16_t(sa[0].getIntValue()), std::int16_t(sa[1].getIntValue()));
247 }
248 static std::vector<RemObjAddr> createFromListString(const juce::String& objectListStringRepresentation)
249 {
250 auto remoteObjects = std::vector<RemObjAddr>();
251
252 juce::StringArray sa;
253 sa.addTokens(objectListStringRepresentation.trimCharactersAtEnd(";"), ";", "");
254
255 for (auto const& commaseparatedStringRepresentation : sa)
256 remoteObjects.push_back(RemObjAddr::createFromString(commaseparatedStringRepresentation));
257
258 return remoteObjects;
259 }
260 bool operator==(const RemObjAddr& rhs) const
261 {
262 return (pri == rhs.pri) && (sec == rhs.sec);
263 }
264 bool operator!=(const RemObjAddr& rhs) const
265 {
266 return !(*this == rhs);
267 }
268 bool operator<(const RemObjAddr& rhs) const
269 {
270 return (pri < rhs.pri) || ((pri == rhs.pri) && (sec < rhs.sec));
271 }
272 bool operator>(const RemObjAddr& rhs) const
273 {
274 // not less and not equal is greater
275 return (!(*this < rhs) && (*this != rhs));
276 }
278 {
279 if (this != &rhs)
280 {
281 pri = rhs.pri;
282 sec = rhs.sec;
283 }
284
285 return *this;
286 }
287
288 JUCE_LEAK_DETECTOR(RemObjAddr)
289 };
290
304 {
354 {
357 Invalid,
424 Positioning_SpeakerPosition, // 6-float loudspeaker position (x, y, z, hor, vert, rot)
429 };
430
433 NanoOcp1::Variant Var;
434
439 {
440 *this = rhs;
441 };
442 RemoteObject(RemObjIdent id, RemObjAddr addr, NanoOcp1::Variant v)
443 : Id(id), Addr(addr), Var(v)
444 {
445 };
446 bool operator==(const RemoteObject& other) const
447 {
448 return (Id == other.Id && Addr == other.Addr && Var == other.Var);
449 };
450 bool operator!=(const RemoteObject& other) const
451 {
452 return !(*this == other);
453 }
454 bool operator<(const RemoteObject& other) const
455 {
456 return (!(*this > other) && (*this != other));
457 }
458 bool operator>(const RemoteObject& other) const
459 {
460 return (Id > other.Id) || ((Id == other.Id) && (Addr > other.Addr));
461 }
463 {
464 if (this != &other)
465 {
466 Id = other.Id;
467 Addr = other.Addr;
468 Var = other.Var;
469 }
470
471 return *this;
472 }
473
474 static juce::String GetObjectDescription(const RemObjIdent roi)
475 {
476 switch (roi)
477 {
478 case HeartbeatPing:
479 return "PING";
480 case HeartbeatPong:
481 return "PONG";
482 case Fixed_GUID:
483 return "Fixed Guid";
485 return "Device Name";
486 case Error_GnrlErr:
487 return "General Error";
488 case Error_ErrorText:
489 return "Error Text";
491 return "Status Text";
493 return "Status AudioNetworkSampleStatus";
494 case MatrixInput_Mute:
495 return "Matrix Input Mute";
496 case MatrixInput_Gain:
497 return "Matrix Input Gain";
499 return "Matrix Input Delay";
501 return "Matrix Input DelayEnable";
503 return "Matrix Input EqEnable";
505 return "Matrix Input Polarity";
507 return "Matrix Input ChannelName";
509 return "Matrix Input LevelMeterPreMute";
511 return "Matrix Input LevelMeterPostMute";
513 return "Matrix Node Enable";
514 case MatrixNode_Gain:
515 return "Matrix Node Gain";
517 return "Matrix Node DelayEnable";
518 case MatrixNode_Delay:
519 return "Matrix Node Delay";
521 return "Matrix Output Mute";
523 return "Matrix Output Gain";
525 return "Matrix Output Delay";
527 return "Matrix Output DelayEnable";
529 return "Matrix Output EqEnable";
531 return "Matrix Output Polarity";
533 return "Matrix Output ChannelName";
535 return "Matrix Output LevelMeterPreMute";
537 return "Matrix Output LevelMeterPostMute";
539 return "Sound Object Spread";
541 return "Sound Object Delay Mode";
543 return "Absolute Sound Object Position XYZ";
545 return "Absolute Sound Object Position XY";
547 return "Absolute Sound Object Position X";
549 return "Absolute Sound Object Position Y";
551 return "Mapped Sound Object Position XYZ";
553 return "Mapped Sound Object Position XY";
555 return "Mapped Sound Object Position X";
557 return "Mapped Sound Object Position Y";
559 return "Matrix Settings ReverbRoomId";
561 return "Matrix Settings ReverbPredelayFactor";
563 return "Matrix Settings ReverbRearLevel";
565 return "Matrix Input ReverbSendGain";
567 return "FunctionGroup Name";
569 return "FunctionGroup Delay";
571 return "FunctionGroup SpreadFactor";
572 case ReverbInput_Gain:
573 return "Reverb Input Gain";
575 return "Reverb Input Processing Mute";
577 return "Reverb Input Processing Gain";
579 return "Reverb Input Processing LevelMeter";
581 return "Reverb Input Processing EqEnable";
582 case Device_Clear:
583 return "Device Clear";
584 case Scene_Previous:
585 return "Scene Previous";
586 case Scene_Next:
587 return "Scene Next";
588 case Scene_Recall:
589 return "Scene Recall";
590 case Scene_SceneIndex:
591 return "Scene SceneIndex";
592 case Scene_SceneName:
593 return "Scene SceneName";
595 return "Scene SceneComment";
597 return "Mapping Area P1 real";
599 return "Mapping Area P2 real";
601 return "Mapping Area P3 real";
603 return "Mapping Area P4 real";
605 return "Mapping Area P1 virt";
607 return "Mapping Area P3 virt";
609 return "Mapping Area flip";
611 return "Mapping Area name";
613 return "Speaker Position";
615 return "Soundobject Routing Mute";
617 return "Soundobject Routing Gain";
618 case Invalid:
619 return "INVALID";
620 default:
621 jassertfalse;
622 return "";
623 }
624 }
625
626 JUCE_LEAK_DETECTOR(RemoteObject)
627 };
628
638 class StateChangeMessage : public juce::Message
639 {
640 public:
643 virtual ~StateChangeMessage() = default;
644
645 void setState(State s) { m_state = s; };
646 State getState() const { return m_state; };
647
648 private:
649 State m_state = Disconnected;
650 };
651
660 class RemoteObjectReceivedMessage : public juce::Message
661 {
662 public:
665 virtual ~RemoteObjectReceivedMessage() = default;
666
667 void setRemoteObject(const RemoteObject& r) { m_remoteObject = r; };
668 const RemoteObject& getRemoteObject() const { return m_remoteObject; };
669
670 private:
671 RemoteObject m_remoteObject;
672 };
673
674public:
676 virtual ~DeviceController();
677
678 JUCE_DECLARE_SINGLETON(DeviceController, false)
679
680 //==============================================================================
682 void timerCallback() override;
683
684 //==============================================================================
692 void handleMessage(const juce::Message& message) override;
693
694 //==============================================================================
699 bool connect();
700
705 void disconnect();
706
713 void setConnectionParameters(juce::IPAddress ip, int port, int timeoutMs = 150);
714
718 const std::tuple<juce::IPAddress, int, int> getConnectionParameters();
719
720 //==============================================================================
722 const State getState() const;
723
724 //==============================================================================
736 bool SetActiveRemoteObjects(const std::vector<RemoteObject>& remObjs);
737
739 const std::vector<RemoteObject>& GetActiveRemoteObjects();
740
741 //==============================================================================
752 bool SetObjectValue(const RemoteObject& remObj);
753
754 //==============================================================================
762 std::function<bool(const RemoteObject&)> onRemoteObjectReceived;
763
770 std::function<void(const State state)> onStateChanged;
771
772private:
773 //==============================================================================
781 void setState(const State& s, juce::NotificationType notificationType = juce::sendNotification);
782
783 //==============================================================================
795 void CreateKnownONosMap();
796
811 std::optional<std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>> GetObjectDefinition(const RemoteObject::RemObjIdent& roi, const RemObjAddr& addr, bool useDefinitionRemapping = false);
812
813 //==============================================================================
825 bool ocp1MessageReceived(const juce::MemoryBlock& data);
826
831 bool CreateObjectSubscriptions();
832
837 bool DeleteObjectSubscriptions();
838
843 bool QueryObjectValues();
844
849 bool QueryObjectValue(const RemoteObject::RemObjIdent roi, const RemObjAddr& addr);
850
851 //==============================================================================
862 void AddPendingSubscriptionHandle(const std::uint32_t handle);
863 bool PopPendingSubscriptionHandle(const std::uint32_t handle);
864 bool HasPendingSubscriptions();
865
866 void AddPendingGetValueHandle(const std::uint32_t handle, const std::uint32_t ONo);
867 const std::uint32_t PopPendingGetValueHandle(const std::uint32_t handle);
868 bool HasPendingGetValues();
869
870 void AddPendingSetValueHandle(const std::uint32_t handle, const std::uint32_t ONo, const int externalId);
871 const std::uint32_t PopPendingSetValueHandle(const std::uint32_t handle, int& externalId);
872 bool HasPendingSetValues();
874 const std::optional<std::pair<std::uint32_t, int>> HasPendingSetValue(const std::uint32_t ONo);
875
877 void ClearPendingHandles();
880 //==============================================================================
885 bool UpdateObjectValue(NanoOcp1::Ocp1Notification* notifObj);
886
891 bool UpdateObjectValue(const std::uint32_t ONo, NanoOcp1::Ocp1Response* responseObj);
892
900 bool UpdateObjectValue(const RemoteObject::RemObjIdent roi, NanoOcp1::Ocp1Message* msgObj,
901 const std::pair<RemObjAddr, NanoOcp1::Ocp1CommandDefinition>& objectDetails);
902
903 //==============================================================================
917 void ProcessGuidAndSubscribe(const juce::String newGuid);
918
942 bool SetOcaRevisionAndDeviceModel(const juce::String& guid);
943
944 //==============================================================================
946 std::mutex m_pendingHandlesMutex;
948 std::vector<std::uint32_t> m_pendingSubscriptionHandles;
950 std::map<std::uint32_t, std::uint32_t> m_pendingGetValueHandlesWithONo;
952 std::map<std::uint32_t, std::pair<std::uint32_t, int>> m_pendingSetValueHandlesWithONo;
953
954 //==============================================================================
962 std::map<RemoteObject::RemObjIdent, std::map<RemObjAddr, NanoOcp1::Ocp1CommandDefinition>> m_ROIsToDefsMap;
963
971 std::unordered_map<std::uint32_t, std::pair<RemoteObject::RemObjIdent, RemObjAddr>> m_ONoToROIMap;
972
974 std::vector<RemoteObject> m_activeRemoteObjects;
975
976 //==============================================================================
978 std::unique_ptr<NanoOcp1::NanoOcp1Client> m_ocp1Connection;
979 juce::IPAddress m_ocp1IPAddress;
980 int m_ocp1Port;
981 int m_ocp1Timeout;
982
984 juce::String m_ocp1DeviceGUID;
989 int m_ocp1DeviceStackIdent = -1;
991 DbDeviceModel m_connectedDbDeviceModel = DbDeviceModel::InvalidDev;
992
993 State m_currentState = State::Disconnected;
994
995
996};
997
Carries a decoded RemoteObject across the thread boundary via JUCE's message queue.
Carries a State value across the thread boundary via JUCE's message queue.
Singleton that owns the OCP.1 (AES70) TCP connection to a d&b audiotechnik DS100 device and exposes a...
const State getState() const
Returns the current connection/subscription state.
void timerCallback() override
juce::Timer callback — retries connectToSocket() while in Connecting state.
std::function< bool(const RemoteObject &)> onRemoteObjectReceived
Called on the JUCE message thread when a notification or get-value response is decoded for a subscrib...
static constexpr std::uint16_t sc_MAX_INPUTS_CHANNELS
DbDeviceModel
Identifies which DS100 hardware variant is connected.
@ DS100M
DS100M (Milan network audio variant).
@ DS100D
DS100D (Dante network audio variant).
@ InvalidDev
Not yet determined or unsupported device.
@ DS100
Standard DS100.
const std::tuple< juce::IPAddress, int, int > getConnectionParameters()
Returns the current connection parameters as {ip, port, timeoutMs}.
bool connect()
Initiates the TCP connection. No-op if not in Disconnected state.
static constexpr std::uint16_t sc_MAX_FUNCTION_GROUPS
void handleMessage(const juce::Message &message) override
juce::MessageListener callback — dispatches messages posted from the socket thread.
bool SetObjectValue(const RemoteObject &remObj)
Sends a SetValue command to the device for the given remote object.
static constexpr std::uint16_t sc_MAX_OUTPUT_CHANNELS
bool SetActiveRemoteObjects(const std::vector< RemoteObject > &remObjs)
Sets the list of remote objects to subscribe to on the next connection.
MappingAreaId
Identifies a DS100 coordinate-mapping area (1–4).
@ Third
Mapping area 3.
@ First
Mapping area 1.
@ Fourth
Mapping area 4.
@ InvalidMapId
Sentinel for "no mapping area".
@ Second
Mapping area 2.
State
Represents the logical phase of the OCP.1 connection.
@ Connected
All subscriptions confirmed and all initial values received.
@ Connecting
TCP connect in progress; timer retries until success.
@ GetValues
GetValue responses being collected.
@ Subscribing
AddSubscription commands sent, awaiting acknowledgements.
@ Disconnected
No TCP connection exists; no resources are allocated.
@ Subscribed
All subscriptions confirmed; GetValue queries still pending.
void setConnectionParameters(juce::IPAddress ip, int port, int timeoutMs=150)
Updates connection parameters and reconnects if currently connected.
const std::vector< RemoteObject > & GetActiveRemoteObjects()
Returns the active subscription list last set by SetActiveRemoteObjects().
static constexpr std::uint16_t sc_MAX_REVERB_ZONES
std::function< void(const State state)> onStateChanged
Called on the JUCE message thread whenever the connection state changes.
void disconnect()
Closes the TCP connection and resets state to Disconnected. Safe to call from any state,...
Two-dimensional address of a remote object on the DS100.
static std::vector< RemObjAddr > createFromListString(const juce::String &objectListStringRepresentation)
bool operator!=(const RemObjAddr &rhs) const
static juce::String toString(const std::vector< RemObjAddr > &RemObjAddrs)
std::int16_t pri
Primary index (channel, speaker, group, zone…). 0 = not used.
juce::String toNiceString() const
static constexpr std::int16_t sc_INV
bool operator>(const RemObjAddr &rhs) const
std::int16_t sec
Secondary index (mapping area, output ch, group…). 0 = not used.
bool operator==(const RemObjAddr &rhs) const
static RemObjAddr createFromString(const juce::String &commaseparatedStringRepresentation)
bool fromString(const juce::String &commaseparatedStringRepresentation)
RemObjAddr(const RemObjAddr &rhs)
RemObjAddr(std::int16_t a, std::int16_t b)
bool operator<(const RemObjAddr &rhs) const
juce::String toString() const
RemObjAddr & operator=(const RemObjAddr &rhs)
A fully-qualified remote parameter including its type, address, and current value.
bool operator!=(const RemoteObject &other) const
RemoteObject(RemObjIdent id, RemObjAddr addr, NanoOcp1::Variant v)
RemoteObject(const RemoteObject &rhs)
bool operator<(const RemoteObject &other) const
static juce::String GetObjectDescription(const RemObjIdent roi)
RemObjIdent
Enumerates every controllable or monitorable parameter on the DS100.
@ Fixed_GUID
Read-only 8-char device GUID; queried before any subscriptions.
bool operator==(const RemoteObject &other) const
bool operator>(const RemoteObject &other) const
RemoteObject & operator=(const RemoteObject &other)