Umsci
Upmix Spatial Control Interface — OCA/OCP.1 spatial audio utility
Loading...
Searching...
No Matches
DeviceController.cpp
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#include "DeviceController.h"
20
21#include <Ocp1DS100ObjectDefinitions.h>
22
23
24JUCE_IMPLEMENT_SINGLETON(DeviceController)
25
26
48{
49 // Pre-build definition maps before the connection is opened.
50 CreateKnownONosMap();
51
52 m_ocp1IPAddress = juce::IPAddress("127.0.0.1");
53 m_ocp1Port = 50014;
54 m_ocp1Timeout = 100;
55
56 // false = callbacks on socket thread, not message thread.
57 m_ocp1Connection = std::make_unique<NanoOcp1::NanoOcp1Client>(m_ocp1IPAddress.toString(), m_ocp1Port, false);
58
59 m_ocp1Connection->onConnectionEstablished = [=]() {
60 stopTimer(); // Connection succeeded — no more retries needed.
61
62 // Reset device-model state: the GUID may differ if the device was replaced.
63 m_ocp1DeviceGUID = "";
64 m_ocp1DeviceStackIdent = -1;
65 m_connectedDbDeviceModel = DbDeviceModel::InvalidDev;
66
67 // Query the GUID first. Subscriptions follow in ProcessGuidAndSubscribe()
68 // once we know which OCA revision the firmware supports.
69 QueryObjectValue(RemoteObject::Fixed_GUID, {});
70 };
71
72 m_ocp1Connection->onConnectionLost = [=]() {
73 DeleteObjectSubscriptions();
74 ClearPendingHandles();
75
76 m_ocp1DeviceGUID = "";
77 m_ocp1DeviceStackIdent = -1;
78 m_connectedDbDeviceModel = DbDeviceModel::InvalidDev;
79
80 if (getState() > State::Connecting) // lost a live connection — try to reconnect
81 {
82 postMessage(new StateChangeMessage(State::Connecting));
83 startTimer(m_ocp1Timeout * 20); // timer fires timerCallback() → connectToSocket()
84 }
85 };
86
87 m_ocp1Connection->onDataReceived = [=](const juce::MemoryBlock& data) {
88 return ocp1MessageReceived(data);
89 };
90}
91
93{
94 disconnect();
95
96 // this ensures that no dangling pointers are left when the
97 // singleton is deleted.
98 clearSingletonInstance();
99}
100
101void DeviceController::setState(const State& s, juce::NotificationType notificationType)
102{
103 std::function<juce::String(State)> stateToString = [=](State state) {
104 switch (state)
105 {
106 case Disconnected:
107 return "disco";
108 case Connecting:
109 return "cntng";
110 case Subscribing:
111 return "sbscrbng";
112 case Subscribed:
113 return "sbscrbd";
114 case GetValues:
115 return "gtvls";
116 case Connected:
117 return "cnctd";
118 default:
119 return "ERR";
120 }
121 };
122
123 if (m_currentState != s)
124 {
125 DBG(juce::String(__FUNCTION__) << " " << stateToString(s));
126 m_currentState = s;
127
128 if (onStateChanged && juce::NotificationType::sendNotification == notificationType)
130 }
131}
132
134{
135 return m_currentState;
136}
137
139{
141 {
142 DBG(juce::String(__FUNCTION__) << " - nothing to do as we're not disconnected");
143 return false;
144 }
145 DBG(__FUNCTION__);
146
147 setState(State::Connecting);
148
149 // prepare to restart connection attempt after some large timeout, in case something got stuck...
150 startTimer(m_ocp1Timeout * 20);
151
152 timerCallback(); // avoid codeclones by manually trigger the timed connection attempt once
153
154 return true;
155}
156
158{
159 DBG(__FUNCTION__);
160
161 setState(State::Disconnected);
162
163 if (m_ocp1Connection)
164 m_ocp1Connection->disconnect(m_ocp1Timeout);
165
166 stopTimer();
167}
168
169void DeviceController::setConnectionParameters(juce::IPAddress ip, int port, int timeoutMs)
170{
171 DBG(juce::String(__FUNCTION__) << " new connection params: " << ip.toString() << ":" << port << " (t:" << timeoutMs << ")");
172 m_ocp1IPAddress = ip;
173 m_ocp1Port = port;
174 jassert(0 < timeoutMs);
175 m_ocp1Timeout = timeoutMs;
176
178 {
179 disconnect();
180 connect();
181 }
182}
183
184const std::tuple<juce::IPAddress, int, int> DeviceController::getConnectionParameters()
185{
186 return { m_ocp1IPAddress, m_ocp1Port, m_ocp1Timeout };
187}
188
190{
192 {
193 m_ocp1Connection->connectToSocket(m_ocp1IPAddress.toString(), m_ocp1Port, m_ocp1Timeout);
194 }
195 else
196 {
197 jassertfalse;
198 disconnect();
199 }
200}
201
202void DeviceController::handleMessage(const juce::Message& message)
203{
204 if (auto const scm = dynamic_cast<const StateChangeMessage*>(&message))
205 {
206 setState(scm->getState());
207 }
208 else if (auto const rorm = dynamic_cast<const RemoteObjectReceivedMessage*>(&message))
209 {
211 onRemoteObjectReceived(rorm->getRemoteObject());
212 }
213}
214
231void DeviceController::CreateKnownONosMap()
232{
233 // Objects with no channel/record indexing.
234 m_ROIsToDefsMap[RemoteObject::Fixed_GUID][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Fixed_GUID();
235 m_ROIsToDefsMap[RemoteObject::Settings_DeviceName][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Settings_DeviceName();
236 m_ROIsToDefsMap[RemoteObject::Status_StatusText][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Status_StatusText();
237 m_ROIsToDefsMap[RemoteObject::Status_AudioNetworkSampleStatus][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Status_AudioNetworkSampleStatus();
238 m_ROIsToDefsMap[RemoteObject::Error_GnrlErr][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Error_GnrlErr();
239 m_ROIsToDefsMap[RemoteObject::Error_ErrorText][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Error_ErrorText();
240
241 m_ROIsToDefsMap[RemoteObject::MatrixSettings_ReverbRoomId][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_MatrixSettings_ReverbRoomId();
242 m_ROIsToDefsMap[RemoteObject::MatrixSettings_ReverbPredelayFactor][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_MatrixSettings_ReverbPredelayFactor();
243 m_ROIsToDefsMap[RemoteObject::MatrixSettings_ReverbRearLevel][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_MatrixSettings_ReverbRearLevel();
244 m_ROIsToDefsMap[RemoteObject::Scene_SceneIndex][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Scene_SceneIndex();
245 m_ROIsToDefsMap[RemoteObject::Scene_SceneName][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Scene_SceneName();
246 m_ROIsToDefsMap[RemoteObject::Scene_SceneComment][RemObjAddr()] = NanoOcp1::DS100::dbOcaObjectDef_Scene_SceneComment();
247
248 // definitions with channels: inputChannels (sound objects)
249 for (std::int16_t first = 1; first <= sc_MAX_INPUTS_CHANNELS; first++)
250 {
251 auto roa = RemObjAddr(first, RemObjAddr::sc_INV);
252 m_ROIsToDefsMap[RemoteObject::Positioning_SourcePosition][roa] = NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Position(first);
253 m_ROIsToDefsMap[RemoteObject::Positioning_SourceSpread][roa] = NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Spread(first);
254 m_ROIsToDefsMap[RemoteObject::Positioning_SourceDelayMode][roa] = NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_DelayMode(first);
255 m_ROIsToDefsMap[RemoteObject::MatrixInput_Mute][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Mute(first);
256 m_ROIsToDefsMap[RemoteObject::MatrixInput_Gain][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Gain(first);
257 m_ROIsToDefsMap[RemoteObject::MatrixInput_Delay][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Delay(first);
258 m_ROIsToDefsMap[RemoteObject::MatrixInput_DelayEnable][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_DelayEnable(first);
259 m_ROIsToDefsMap[RemoteObject::MatrixInput_EqEnable][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_EqEnable(first);
260 m_ROIsToDefsMap[RemoteObject::MatrixInput_Polarity][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Polarity(first);
261 m_ROIsToDefsMap[RemoteObject::MatrixInput_ChannelName][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_ChannelName(first);
262 m_ROIsToDefsMap[RemoteObject::MatrixInput_LevelMeterPreMute][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_LevelMeterPreMute(first);
263 m_ROIsToDefsMap[RemoteObject::MatrixInput_LevelMeterPostMute][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_LevelMeterPostMute(first);
264 m_ROIsToDefsMap[RemoteObject::MatrixInput_ReverbSendGain][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_ReverbSendGain(first);
265
266 // definitions with channels and records: mapping areas
267 for (std::uint16_t second = MappingAreaId::First; second <= MappingAreaId::Fourth; second++)
268 {
269 roa.sec = second;
270 m_ROIsToDefsMap[RemoteObject::CoordinateMapping_SourcePosition][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMapping_Source_Position(second, first);
271 }
272
273 // definitions with channels and records: function groups
274 for (std::uint16_t second = 1; second <= sc_MAX_FUNCTION_GROUPS; second++)
275 {
276 roa.sec = second;
277 m_ROIsToDefsMap[RemoteObject::SoundObjectRouting_Mute][roa] = NanoOcp1::DS100::dbOcaObjectDef_SoundObjectRouting_Mute(second, first);
278 m_ROIsToDefsMap[RemoteObject::SoundObjectRouting_Gain][roa] = NanoOcp1::DS100::dbOcaObjectDef_SoundObjectRouting_Gain(second, first);
279 }
280
281 // definitions with channels and records but second parameter for output channels
282 for (std::uint16_t second = 1; second <= sc_MAX_OUTPUT_CHANNELS; second++)
283 {
284 roa.sec = second;
285 m_ROIsToDefsMap[RemoteObject::MatrixNode_Enable][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_Enable(first, second);
286 m_ROIsToDefsMap[RemoteObject::MatrixNode_Gain][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_Gain(first, second);
287 m_ROIsToDefsMap[RemoteObject::MatrixNode_Delay][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_Delay(first, second);
288 m_ROIsToDefsMap[RemoteObject::MatrixNode_DelayEnable][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_DelayEnable(first, second);
289 }
290 }
291
292 // definitions with channels: matrix outputs
293 for (std::uint16_t first = 1; first <= sc_MAX_OUTPUT_CHANNELS; first++)
294 {
295 auto roa = RemObjAddr(first, RemObjAddr::sc_INV);
296 m_ROIsToDefsMap[RemoteObject::Positioning_SpeakerPosition][roa] = NanoOcp1::DS100::dbOcaObjectDef_Positioning_Speaker_Position(first);
297 m_ROIsToDefsMap[RemoteObject::MatrixOutput_Mute][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Mute(first);
298 m_ROIsToDefsMap[RemoteObject::MatrixOutput_Gain][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Gain(first);
299 m_ROIsToDefsMap[RemoteObject::MatrixOutput_Delay][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Delay(first);
300 m_ROIsToDefsMap[RemoteObject::MatrixOutput_DelayEnable][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_DelayEnable(first);
301 m_ROIsToDefsMap[RemoteObject::MatrixOutput_EqEnable][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_EqEnable(first);
302 m_ROIsToDefsMap[RemoteObject::MatrixOutput_Polarity][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Polarity(first);
303 m_ROIsToDefsMap[RemoteObject::MatrixOutput_ChannelName][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_ChannelName(first);
304 m_ROIsToDefsMap[RemoteObject::MatrixOutput_LevelMeterPreMute][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_LevelMeterPreMute(first);
305 m_ROIsToDefsMap[RemoteObject::MatrixOutput_LevelMeterPostMute][roa] = NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_LevelMeterPostMute(first);
306 }
307
308 // definitions with channels: function groups
309 for (std::uint16_t first = 1; first <= sc_MAX_FUNCTION_GROUPS; first++)
310 {
311 auto roa = RemObjAddr(first, RemObjAddr::sc_INV);
312 m_ROIsToDefsMap[RemoteObject::FunctionGroup_Name][roa] = NanoOcp1::DS100::dbOcaObjectDef_FunctionGroup_Name(first);
313 m_ROIsToDefsMap[RemoteObject::FunctionGroup_Delay][roa] = NanoOcp1::DS100::dbOcaObjectDef_FunctionGroup_Delay(first);
314 m_ROIsToDefsMap[RemoteObject::FunctionGroup_SpreadFactor][roa] = NanoOcp1::DS100::dbOcaObjectDef_FunctionGroup_SpreadFactor(first);
315 }
316
317 // definitions with channels: en-space zones
318 for (std::uint16_t first = 1; first <= sc_MAX_REVERB_ZONES; first++)
319 {
320 auto roa = RemObjAddr(first, RemObjAddr::sc_INV);
321 m_ROIsToDefsMap[RemoteObject::ReverbInputProcessing_Mute][roa] = NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_Mute(first);
322 m_ROIsToDefsMap[RemoteObject::ReverbInputProcessing_Gain][roa] = NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_Gain(first);
323 m_ROIsToDefsMap[RemoteObject::ReverbInputProcessing_EqEnable][roa] = NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_EqEnable(first);
324 m_ROIsToDefsMap[RemoteObject::ReverbInputProcessing_LevelMeter][roa] = NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_LevelMeter(first);
325
326 // definitions with channels and records: en-space zones with zone as first parameter = channel and sound object as second parameter = record
327 for (std::uint16_t second = 1; second <= sc_MAX_INPUTS_CHANNELS; second++)
328 {
329 roa.sec = second;
330 m_ROIsToDefsMap[RemoteObject::ReverbInput_Gain][roa] = NanoOcp1::DS100::dbOcaObjectDef_ReverbInput_Gain(second, first);
331 }
332 }
333
334 // definitions with records: mapping areas
335 for (std::uint16_t first = 1; first <= MappingAreaId::Fourth; first++)
336 {
337 auto roa = RemObjAddr(first, RemObjAddr::sc_INV);
338 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_P1real][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P1_real(first);
339 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_P2real][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P2_real(first);
340 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_P3real][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P3_real(first);
341 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_P4real][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P4_real(first);
342 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_P1virtual][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P1_virtual(first);
343 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_P3virtual][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P3_virtual(first);
344 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_Flip][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_Flip(first);
345 m_ROIsToDefsMap[RemoteObject::CoordinateMappingSettings_Name][roa] = NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_Name(first);
346 }
347
348 // Build reverse ONo → {ROI, addr} lookup from the completed m_ROIsToDefsMap.
349 // This must be the last step so all entries are present.
350 for (auto& roisKV : m_ROIsToDefsMap)
351 for (auto& objDefKV : roisKV.second)
352 m_ONoToROIMap[objDefKV.second.m_targetOno] = { roisKV.first, objDefKV.first };
353}
354
377std::optional<std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>> DeviceController::GetObjectDefinition(const RemoteObject::RemObjIdent& roi, const RemObjAddr& addr, bool useDefinitionRemapping)
378{
379 std::int32_t first = addr.pri;
380 std::int32_t second = addr.sec;
381
382 switch (roi)
383 {
385 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Fixed_GUID());
387 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Settings_DeviceName());
389 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Status_StatusText());
391 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Status_AudioNetworkSampleStatus());
393 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Error_GnrlErr());
395 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Error_ErrorText());
397 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_Name(first));
399 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_Flip(first));
401 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P1_real(first));
403 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P2_real(first));
405 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P3_real(first));
407 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P4_real(first));
409 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P1_virtual(first));
411 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMappingSettings_P3_virtual(first));
415 {
416 if (!useDefinitionRemapping)
417 {
418 DBG(juce::String(__FUNCTION__) + " skipping Positioning_SourcePosition X Y XY");
419 return {};
420 }
421 }
422 [[fallthrough]];
424 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Position(first));
428 {
429 if (!useDefinitionRemapping)
430 {
431 DBG(juce::String(__FUNCTION__) + " skipping CoordinateMapping_SourcePosition X Y XY");
432 return {};
433 }
434 }
435 [[fallthrough]];
436 case RemoteObject::CoordinateMapping_SourcePosition:return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_CoordinateMapping_Source_Position(second, first));
438 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Spread(first));
440 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_DelayMode(first));
442 if (m_ocp1DeviceStackIdent >= 1) // newer oca revision needs newer oca object definition
443 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Positioning_Speaker_Position(first));
444 else
445 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Speaker_Position(first));
447 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_FunctionGroup_Name(first));
449 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_FunctionGroup_Delay(first));
451 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_FunctionGroup_SpreadFactor(first));
453 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Mute(first));
455 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Gain(first));
457 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Delay(first));
459 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_DelayEnable(first));
461 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_EqEnable(first));
463 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_Polarity(first));
465 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_ChannelName(first));
467 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_LevelMeterPreMute(first));
469 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_LevelMeterPostMute(first));
471 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixInput_ReverbSendGain(first));
473 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_Enable(first, second));
475 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_Gain(first, second));
477 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_Delay(first, second));
479 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixNode_DelayEnable(first, second));
481 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Mute(first));
483 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Gain(first));
485 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Delay(first));
487 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_DelayEnable(first));
489 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_EqEnable(first));
491 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_Polarity(first));
493 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_ChannelName(first));
495 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_LevelMeterPreMute(first));
497 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixOutput_LevelMeterPostMute(first));
499 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixSettings_ReverbRoomId());
501 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixSettings_ReverbPredelayFactor());
503 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_MatrixSettings_ReverbRearLevel());
505 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_ReverbInput_Gain(second, first));
507 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_Mute(first));
509 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_Gain(first));
511 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_EqEnable(first));
513 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_ReverbInputProcessing_LevelMeter(first));
515 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Scene_SceneIndex());
517 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Scene_SceneName());
521 {
522 if (!useDefinitionRemapping)
523 return {};
524 else
525 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_SceneAgent());
526 }
528 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_Scene_SceneComment());
530 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_SoundObjectRouting_Mute(second, first));
532 return std::unique_ptr<NanoOcp1::Ocp1CommandDefinition>(new NanoOcp1::DS100::dbOcaObjectDef_SoundObjectRouting_Gain(second, first));
533 default:
534 DBG(juce::String(__FUNCTION__) << " " << RemoteObject::GetObjectDescription(roi) << " -> not implmented");
535 return {};
536 }
537}
538
539bool DeviceController::ocp1MessageReceived(const juce::MemoryBlock& data)
540{
541 std::unique_ptr<NanoOcp1::Ocp1Message> msgObj = NanoOcp1::Ocp1Message::UnmarshalOcp1Message(data);
542 if (msgObj)
543 {
544 switch (msgObj->GetMessageType())
545 {
546 case NanoOcp1::Ocp1Message::Notification:
547 {
548 NanoOcp1::Ocp1Notification* notifObj = static_cast<NanoOcp1::Ocp1Notification*>(msgObj.get());
549
550 if (UpdateObjectValue(notifObj))
551 return true;
552
553 DBG(juce::String(__FUNCTION__) << " Got an unhandled OCA notification for ONo 0x"
554 << juce::String::toHexString(notifObj->GetEmitterOno()));
555 return false;
556 }
557 case NanoOcp1::Ocp1Message::Response:
558 {
559 NanoOcp1::Ocp1Response* responseObj = static_cast<NanoOcp1::Ocp1Response*>(msgObj.get());
560
561 auto handle = responseObj->GetResponseHandle();
562 if (responseObj->GetResponseStatus() != 0)
563 {
564 DBG(juce::String(__FUNCTION__) << " Got an OCA response (handle:" << NanoOcp1::HandleToString(handle) <<
565 ") with status " << NanoOcp1::StatusToString(responseObj->GetResponseStatus()));
566
567 auto externalId = -1;
568 PopPendingSubscriptionHandle(handle);
569 PopPendingGetValueHandle(handle);
570 PopPendingSetValueHandle(handle, externalId);
571
572 return false;
573 }
574 else if (PopPendingSubscriptionHandle(handle))
575 {
576 if (!HasPendingSubscriptions())
577 {
578 // All subscriptions were confirmed
579 //DBG(juce::String(__FUNCTION__) << " All NanoOcp1 subscriptions were confirmed (handle:"
580 // << NanoOcp1::HandleToString(handle) << ")");
581
582 postMessage(new StateChangeMessage(State::Subscribed));
583 if (HasPendingGetValues())
584 postMessage(new StateChangeMessage(State::GetValues));
585 else
586 postMessage(new StateChangeMessage(State::Connected));
587 }
588 return true;
589 }
590 else
591 {
592 auto GetValONo = PopPendingGetValueHandle(handle);
593 if (0x00 != GetValONo)
594 {
595 if (!UpdateObjectValue(GetValONo, responseObj))
596 {
597 DBG(juce::String(__FUNCTION__) << " Got an unhandled OCA getvalue response message (handle:"
598 << NanoOcp1::HandleToString(handle) + ", targetONo:0x" << juce::String::toHexString(GetValONo) << ")");
599 return false;
600 }
601 else
602 {
603 if (!HasPendingGetValues())
604 {
605 // All getvalues were confirmed
606 //DBG(juce::String(__FUNCTION__) << " All pending NanoOcp1 getvalue commands were confirmed (handle:"
607 // << NanoOcp1::HandleToString(handle) << ")");
608
609 if (!HasPendingSubscriptions())
610 postMessage(new StateChangeMessage(State::Connected));
611 else
612 postMessage(new StateChangeMessage(State::Subscribing));
613 }
614 return true;
615 }
616 }
617
618 auto externalId = -1;
619 auto SetValONo = PopPendingSetValueHandle(handle, externalId);
620 if (0x00 != SetValONo)
621 {
622 if (!HasPendingSetValues())
623 {
624 // All subscriptions were confirmed
625 //DBG(juce::String(__FUNCTION__) << " All pending NanoOcp1 setvalue commands were confirmed (handle:"
626 // << NanoOcp1::HandleToString(handle) << ")");
627 }
628 return true;
629 }
630
631 DBG(juce::String(__FUNCTION__) << " Got an OCA response for UNKNOWN handle " << NanoOcp1::HandleToString(handle) <<
632 "; status " << NanoOcp1::StatusToString(responseObj->GetResponseStatus()) <<
633 "; paramCount " << juce::String(responseObj->GetParamCount()));
634
635 return false;
636 }
637 }
638 case NanoOcp1::Ocp1Message::KeepAlive:
639 {
640 jassertfalse; //todo
642 //if (m_messageListener)
643 //{
644 // m_messageListener->OnProtocolMessageReceived(this, RemoteObject::HeartbeatPong, RemoteObjectMessageData());
645 // return true;
646 //}
647 //else
648 // return false;
649 }
650 default:
651 break;
652 }
653 }
654
655 return false;
656}
657
658bool DeviceController::CreateObjectSubscriptions()
659{
660 if (!m_ocp1Connection || State::Disconnected == getState())
661 return false;
662
663 auto handle = std::uint32_t(0);
664 auto success = true;
665
666 for (auto const& activeObj : GetActiveRemoteObjects())
667 {
668 // Get the object definition
669 auto objDefOpt = GetObjectDefinition(activeObj.Id, activeObj.Addr);
670
671 // Sanity checks
672 jassert(objDefOpt); // Missing implementation!
673 if (!objDefOpt)
674 return false;
675 auto& objDef = objDefOpt.value();
676 if (!objDef)
677 return false;
678
679 success = success && m_ocp1Connection->sendData(NanoOcp1::Ocp1CommandResponseRequired(objDef->AddSubscriptionCommand(), handle).GetMemoryBlock());
680 //DBG(juce::String(__FUNCTION__) << " " << RemoteObject::GetObjectDescription(activeObj.Id) << "("
681 // << (activeObj.Addr.pri >= 0 ? (" pri:" + juce::String(activeObj.Addr.pri)) : "")
682 // << (activeObj.Addr.sec >= 0 ? (" sec:" + juce::String(activeObj.Addr.sec)) : "")
683 // << " handle:" << NanoOcp1::HandleToString(handle) << ")");
684
685 AddPendingSubscriptionHandle(handle);
686 }
687
688 postMessage(new StateChangeMessage(State::Subscribing));
689
690 return success;
691}
692
693bool DeviceController::DeleteObjectSubscriptions()
694{
695 return false;
696}
697
698bool DeviceController::QueryObjectValues()
699{
700 if (!m_ocp1Connection || State::Disconnected == getState())
701 return false;
702
703 auto success = true;
704
705 for (auto const& activeObj : GetActiveRemoteObjects())
706 {
707 success = QueryObjectValue(activeObj.Id, activeObj.Addr) && success;
708 }
709
710 postMessage(new StateChangeMessage(State::GetValues));
711
712 return success;
713}
714
715bool DeviceController::QueryObjectValue(const RemoteObject::RemObjIdent roi, const RemObjAddr& addr)
716{
717 auto handle = std::uint32_t(0);
718
719 // Get the object definition
720 auto objDefOpt = GetObjectDefinition(roi, addr, true);
721
722 // Sanity checks
723 jassert(objDefOpt); // Missing implementation!
724 if (!objDefOpt)
725 return false;
726 auto& objDef = objDefOpt.value();
727 if (!objDef)
728 return false;
729
730 // Send GetValue command
731 bool success = m_ocp1Connection->sendData(NanoOcp1::Ocp1CommandResponseRequired(objDef->GetValueCommand(), handle).GetMemoryBlock());
732 AddPendingGetValueHandle(handle, objDef->m_targetOno);
733 //DBG(juce::String(__FUNCTION__) + " " + RemoteObject::GetObjectDescription(roi) + "(handle: " + NanoOcp1::HandleToString(handle) + ")");
734 return success;
735}
736
738{
739 if (!m_ocp1Connection || getState() != State::Connected)
740 return false;
741
742 auto handle = std::uint32_t(0);
743
744 auto objDefOpt = GetObjectDefinition(remObj.Id, remObj.Addr, true);
745 jassert(objDefOpt); // Missing implementation for this object type!
746 if (!objDefOpt)
747 return false;
748 auto& objDef = objDefOpt.value();
749 if (!objDef)
750 return false;
751
752 bool success = m_ocp1Connection->sendData(NanoOcp1::Ocp1CommandResponseRequired(objDef->SetValueCommand(remObj.Var), handle).GetMemoryBlock());
753 AddPendingSetValueHandle(handle, objDef->m_targetOno, remObj.Addr.pri);
754
755 DBG(juce::String(__FUNCTION__) << " " << RemoteObject::GetObjectDescription(remObj.Id)
756 << "(" << remObj.Addr.toNiceString() << " handle:" << NanoOcp1::HandleToString(handle) << ")");
757
758 return success;
759}
760
761void DeviceController::AddPendingSubscriptionHandle(const std::uint32_t handle)
762{
763 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
764
765 //DBG(juce::String(__FUNCTION__)
766 // << " (handle:" << NanoOcp1::HandleToString(handle) << ")");
767 m_pendingSubscriptionHandles.push_back(handle);
768}
769
770bool DeviceController::PopPendingSubscriptionHandle(const std::uint32_t handle)
771{
772 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
773
774 auto it = std::find(m_pendingSubscriptionHandles.begin(), m_pendingSubscriptionHandles.end(), handle);
775 if (it != m_pendingSubscriptionHandles.end())
776 {
777 //DBG(juce::String(__FUNCTION__)
778 // << " (handle:" << NanoOcp1::HandleToString(handle) << ")");
779 m_pendingSubscriptionHandles.erase(it);
780 return true;
781 }
782 else
783 return false;
784}
785
786bool DeviceController::HasPendingSubscriptions()
787{
788 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
789
790 return !m_pendingSubscriptionHandles.empty();
791}
792
793void DeviceController::AddPendingGetValueHandle(const std::uint32_t handle, const std::uint32_t ONo)
794{
795 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
796
797 //DBG(juce::String(__FUNCTION__)
798 // << " (handle:" << NanoOcp1::HandleToString(handle)
799 // << ", targetONo:0x" << juce::String::toHexString(ONo) << ")");
800 m_pendingGetValueHandlesWithONo.insert(std::make_pair(handle, ONo));
801}
802
803const std::uint32_t DeviceController::PopPendingGetValueHandle(const std::uint32_t handle)
804{
805 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
806
807 auto it = std::find_if(m_pendingGetValueHandlesWithONo.begin(), m_pendingGetValueHandlesWithONo.end(), [handle](const auto& val) { return val.first == handle; });
808 if (it != m_pendingGetValueHandlesWithONo.end())
809 {
810 auto ONo = it->second;
811 //DBG(juce::String(__FUNCTION__)
812 // << " (handle:" << NanoOcp1::HandleToString(handle)
813 // << ", targetONo:0x" << juce::String::toHexString(ONo) << ")");
814 m_pendingGetValueHandlesWithONo.erase(it);
815 return ONo;
816 }
817 else
818 return 0x00;
819}
820
821bool DeviceController::HasPendingGetValues()
822{
823 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
824
825 return !m_pendingGetValueHandlesWithONo.empty();
826}
827
828void DeviceController::AddPendingSetValueHandle(const std::uint32_t handle, const std::uint32_t ONo, int externalId)
829{
830 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
831
832 //DBG(juce::String(__FUNCTION__)
833 // << " (handle:" << NanoOcp1::HandleToString(handle)
834 // << ", targetONo:0x" << juce::String::toHexString(ONo) << ")");
835 m_pendingSetValueHandlesWithONo.insert(std::make_pair(handle, std::make_pair(ONo, externalId)));
836}
837
838const std::uint32_t DeviceController::PopPendingSetValueHandle(const std::uint32_t handle, int& externalId)
839{
840 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
841
842 auto it = std::find_if(m_pendingSetValueHandlesWithONo.begin(), m_pendingSetValueHandlesWithONo.end(), [handle](const auto& val) { return val.first == handle; });
843 if (it != m_pendingSetValueHandlesWithONo.end())
844 {
845 auto ONo = it->second.first;
846 externalId = it->second.second;
847 //DBG(juce::String(__FUNCTION__)
848 // << " (handle:" << NanoOcp1::HandleToString(handle)
849 // << ", targetONo:0x" << juce::String::toHexString(ONo) << ")");
850 m_pendingSetValueHandlesWithONo.erase(it);
851 return ONo;
852 }
853 else
854 return 0x00;
855}
856
857bool DeviceController::HasPendingSetValues()
858{
859 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
860
861 return !m_pendingGetValueHandlesWithONo.empty();
862}
863
864const std::optional<std::pair<std::uint32_t, int>> DeviceController::HasPendingSetValue(const std::uint32_t ONo)
865{
866 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
867
868 auto it = std::find_if(m_pendingSetValueHandlesWithONo.begin(), m_pendingSetValueHandlesWithONo.end(), [ONo](const auto& val) { return val.second.first == ONo; });
869 if (it != m_pendingSetValueHandlesWithONo.end())
870 {
871 //DBG(juce::String(__FUNCTION__)
872 // << " (handle:" << juce::String(NanoOcp1::HandleToString(it->first))
873 // << ", targetONo:0x" << juce::String::toHexString(ONo) << ")");
874 return std::optional<std::pair<std::uint32_t, int>>(std::make_pair(it->first, it->second.second));
875 }
876 else
877 return std::optional<std::pair<std::uint32_t, int>>();
878}
879
880void DeviceController::ClearPendingHandles()
881{
882 std::lock_guard<std::mutex> l(m_pendingHandlesMutex); // NanoOcp callback on JUCE IPC thread, safety required!
883
884 m_pendingSubscriptionHandles.clear();
885 m_pendingGetValueHandlesWithONo.clear();
886 m_pendingSetValueHandlesWithONo.clear();
887}
888
889bool DeviceController::UpdateObjectValue(NanoOcp1::Ocp1Notification* notifObj)
890{
891 auto it = m_ONoToROIMap.find(notifObj->GetEmitterOno());
892 if (it != m_ONoToROIMap.end())
893 {
894 auto& [roi, addr] = it->second;
895 return UpdateObjectValue(roi, dynamic_cast<NanoOcp1::Ocp1Message*>(notifObj),
896 { addr, m_ROIsToDefsMap.at(roi).at(addr) });
897 }
898
899 return false;
900}
901
902bool DeviceController::UpdateObjectValue(const std::uint32_t ONo, NanoOcp1::Ocp1Response* responseObj)
903{
904 auto it = m_ONoToROIMap.find(ONo);
905 if (it != m_ONoToROIMap.end())
906 {
907 auto& [roi, addr] = it->second;
908 return UpdateObjectValue(roi, dynamic_cast<NanoOcp1::Ocp1Message*>(responseObj),
909 { addr, m_ROIsToDefsMap.at(roi).at(addr) });
910 }
911
912 return false;
913}
914
931bool DeviceController::UpdateObjectValue(const RemoteObject::RemObjIdent roi, NanoOcp1::Ocp1Message* msgObj, const std::pair<RemObjAddr, NanoOcp1::Ocp1CommandDefinition>& objectDetails)
932{
933 NanoOcp1::Ocp1DataType datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_NONE;
934 switch (roi)
935 {
937 {
938 bool ok = false;
939 auto guid = NanoOcp1::DataToString(msgObj->GetParameterData(), &ok);
940 if (ok)
941 ProcessGuidAndSubscribe(guid);
942
943 return ok;
944 }
952 datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT8;
953 break;
964 datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT16;
965 break;
967 datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_INT32;
968 break;
989 datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_FLOAT32;
990 break;
1001 datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_STRING;
1002 break;
1012 datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_DB_POSITION;
1013 break;
1014 default:
1015 DBG(juce::String(__FUNCTION__) << " unknown: " << RemoteObject::GetObjectDescription(roi)
1016 << " (" << static_cast<int>(objectDetails.first.pri) << "," << static_cast<int>(objectDetails.first.sec) << ") ");
1017 return false;
1018 }
1019
1020 auto val = NanoOcp1::Variant(msgObj->GetParameterData(), datatype);
1021 auto ro = RemoteObject(roi, objectDetails.first, val);
1022 postMessage(new RemoteObjectReceivedMessage(ro));
1023
1024 return true;
1025}
1026
1027const std::vector<DeviceController::RemoteObject>& DeviceController::GetActiveRemoteObjects()
1028{
1029 return m_activeRemoteObjects;
1030}
1031
1032bool DeviceController::SetActiveRemoteObjects(const std::vector<DeviceController::RemoteObject>& remObjs)
1033{
1035 return false;
1036
1037 m_activeRemoteObjects = remObjs;
1038
1039 return true;
1040}
1041
1059void DeviceController::ProcessGuidAndSubscribe(const juce::String newGuid)
1060{
1061 if (newGuid == m_ocp1DeviceGUID) // GUID reset to "" on connection, so this guard fires only for re-notifications
1062 return;
1063
1064 if (SetOcaRevisionAndDeviceModel(newGuid)) // validate guid and determines revision and device model
1065 m_ocp1DeviceGUID = newGuid;
1066 else
1067 return; // close connection ?
1068
1069 auto roa = RemObjAddr(RemObjAddr::sc_INV, RemObjAddr::sc_INV);
1070 if (m_ocp1DeviceStackIdent >= 1) // update internal map with the right definitions
1071 {
1072 for (std::uint16_t first = 1; first <= sc_MAX_OUTPUT_CHANNELS; first++)
1073 {
1074 roa.pri = first;
1075 m_ROIsToDefsMap[RemoteObject::Positioning_SpeakerPosition][roa] = NanoOcp1::DS100::dbOcaObjectDef_Positioning_Speaker_Position(first);
1076 }
1077 }
1078 else
1079 {
1080 for (std::uint16_t first = 1; first <= sc_MAX_OUTPUT_CHANNELS; first++)
1081 {
1082 roa.pri = first;
1083 m_ROIsToDefsMap[RemoteObject::Positioning_SpeakerPosition][roa] = NanoOcp1::DS100::dbOcaObjectDef_Positioning_Source_Speaker_Position(first);
1084 }
1085 }
1086 // after guid and device is known create subscriptions
1087 CreateObjectSubscriptions();
1088 QueryObjectValues();
1089}
1090
1108bool DeviceController::SetOcaRevisionAndDeviceModel(const juce::String& guid)
1109{
1110 if (guid.length() != 8) // d&b GUIDs are always exactly 8 characters
1111 return false;
1112 if (!guid.startsWith("DB00")) // mandatory d&b manufacturer prefix
1113 return false;
1114
1115 int deviceBytes = 2;
1116 DbDeviceModel dbDeviceModel; // last two characters decide the model
1117 if (guid.getLastCharacters(deviceBytes) == "D0") // DS100
1118 dbDeviceModel = DbDeviceModel::DS100;
1119 else if (guid.getLastCharacters(deviceBytes) == "D1") // DS100D
1120 dbDeviceModel = DbDeviceModel::DS100D;
1121 else if (guid.getLastCharacters(deviceBytes) == "D2") // DS100M
1122 dbDeviceModel = DbDeviceModel::DS100M;
1123 else
1124 return false;
1125
1126 int versionBytesIndexStart = 4; // in the Guid the version is after "DB00" and has two bytes/characters
1127 auto versionChars = guid.substring(versionBytesIndexStart, versionBytesIndexStart + 2); // will get two characters
1128 int ocp1DeviceStackIdent;
1129 switch (dbDeviceModel)
1130 {
1132 if (versionChars >= "0C") // DS100 added scalability with FW version "0C"
1133 ocp1DeviceStackIdent = 1;
1134 else
1135 ocp1DeviceStackIdent = 0;
1136 DBG(juce::String(__FUNCTION__) << " detected DS100 (ocp stack:" << ocp1DeviceStackIdent << ") " << guid);
1137 break;
1139 ocp1DeviceStackIdent = 1; // this was implemented pre-release of DS100D and assuming there will be no FW-version without scalability
1140 DBG(juce::String(__FUNCTION__) << " detected DS100D (ocp stack:" << ocp1DeviceStackIdent << ") " << guid);
1141 break;
1143 if (versionChars >= "02") // DS100M added scalability with FW version "02"
1144 ocp1DeviceStackIdent = 1;
1145 else
1146 ocp1DeviceStackIdent = 0;
1147 DBG(juce::String(__FUNCTION__) << " detected DS100M (ocp stack:" << ocp1DeviceStackIdent << ") " << guid);
1148 break;
1149 default:
1150 return false;
1151
1152 }
1153 m_connectedDbDeviceModel = dbDeviceModel;
1154 m_ocp1DeviceStackIdent = ocp1DeviceStackIdent;
1155 return true;
1156}
1157
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).
@ 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.
@ First
Mapping area 1.
@ Fourth
Mapping area 4.
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,...
std::int16_t pri
Primary index (channel, speaker, group, zone…). 0 = not used.
juce::String toNiceString() const
static constexpr std::int16_t sc_INV
A fully-qualified remote parameter including its type, address, and current value.
static juce::String GetObjectDescription(const RemObjIdent roi)
@ Fixed_GUID
Read-only 8-char device GUID; queried before any subscriptions.