Umsci
Upmix Spatial Control Interface — OCA/OCP.1 spatial audio utility
Loading...
Searching...
No Matches
UmsciControlComponent.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
20
24
25#if JUCE_IOS
26#include <iOS_utils.h>
27#endif
28
29
31 : juce::Component()
32{
33 setOcp1IOSize({ 64, 64 });
34
35 jassert(!DeviceController::getInstance()->onRemoteObjectReceived); // this lambda can only be used once, and we expect that to be here
36 DeviceController::getInstance()->onRemoteObjectReceived = [=](const DeviceController::RemoteObject& obj) {
37 setRemoteObject(obj);
38 return true;
39 };
40
41 m_loudspeakersInAreaPaintComponent = std::make_unique<UmsciLoudspeakersPaintComponent>();
42 addAndMakeVisible(m_loudspeakersInAreaPaintComponent.get());
43 m_soundobjectsInAreaPaintComponent = std::make_unique<UmsciSoundobjectsPaintComponent>();
44 addAndMakeVisible(m_soundobjectsInAreaPaintComponent.get());
45 m_soundobjectsInAreaPaintComponent->onSourcePositionChanged = [this](std::int16_t sourceId, std::array<std::float_t, 3> position) {
46 m_sourcePosition[sourceId] = position;
47 DeviceController::getInstance()->SetObjectValue(
51 NanoOcp1::Variant(position.at(0), position.at(1), position.at(2))
52 )
53 );
54 };
55 m_upmixIndicatorPaintAndControlComponent = std::make_unique<UmsciUpmixIndicatorPaintNControlComponent>();
56 addAndMakeVisible(m_upmixIndicatorPaintAndControlComponent.get());
57 m_upmixIndicatorPaintAndControlComponent->onTransformChanged = [this]() {
60 };
61 m_upmixIndicatorPaintAndControlComponent->onSourcePositionChanged = [this](std::int16_t sourceId, std::array<std::float_t, 3> position) {
62 m_sourcePosition[sourceId] = position;
63 m_soundobjectsInAreaPaintComponent->setSourcePosition(sourceId, position);
64 DeviceController::getInstance()->SetObjectValue(
68 NanoOcp1::Variant(position.at(0), position.at(1), position.at(2))
69 )
70 );
71 };
72
73 // Synchronise viewport zoom across all three overlaid paint components:
74 // when any one component receives a wheel/pinch zoom, all siblings get the same zoom applied.
75 auto syncViewportZoom = [this](float factor, juce::Point<float> panOffset) {
76 m_loudspeakersInAreaPaintComponent->setZoom(factor, panOffset);
77 m_soundobjectsInAreaPaintComponent->setZoom(factor, panOffset);
78 m_upmixIndicatorPaintAndControlComponent->setZoom(factor, panOffset);
79 };
80 m_loudspeakersInAreaPaintComponent->onViewportZoomChanged = syncViewportZoom;
81 m_soundobjectsInAreaPaintComponent->onViewportZoomChanged = syncViewportZoom;
82 m_upmixIndicatorPaintAndControlComponent->onViewportZoomChanged = syncViewportZoom;
83}
84
86{
87#if JUCE_IOS
88 if (m_nativePinchViewHandle)
89 JUCEAppBasics::iOS_utils::unregisterNativePinchOnView(m_nativePinchViewHandle);
90#endif
91}
92
94{
95 g.fillAll(getLookAndFeel().findColour(juce::Slider::ColourIds::backgroundColourId));
96
97 if (!isDatabaseComplete())
98 {
99 g.setColour(getLookAndFeel().findColour(juce::TextEditor::ColourIds::textColourId));
100 g.drawFittedText("Data not ready... misconfigured IO size?", getLocalBounds().reduced(35), juce::Justification::centred, 2);
101 return;
102 }
103}
104
106{
107 auto bounds = getLocalBounds();
108
109 if (m_loudspeakersInAreaPaintComponent && m_loudspeakersInAreaPaintComponent->isVisible())
110 m_loudspeakersInAreaPaintComponent->setBounds(bounds);
111 if (m_soundobjectsInAreaPaintComponent && m_soundobjectsInAreaPaintComponent->isVisible())
112 m_soundobjectsInAreaPaintComponent->setBounds(bounds);
113 if (m_upmixIndicatorPaintAndControlComponent && m_upmixIndicatorPaintAndControlComponent->isVisible())
114 m_upmixIndicatorPaintAndControlComponent->setBounds(bounds);
115}
116
118{
119#if JUCE_IOS
120 auto* peer = getPeer();
121 auto nativeHandle = peer ? peer->getNativeHandle() : nullptr;
122
123 if (nativeHandle && !m_nativePinchViewHandle)
124 {
125 m_nativePinchViewHandle = nativeHandle;
126 JUCEAppBasics::iOS_utils::registerNativePinchOnView(
127 nativeHandle,
128 [this](float incrementalScale, float cx, float cy) {
129 // cx, cy are in the peer UIView's coordinate space (= screen logical points).
130 // Convert to the paint component's local coordinate space before applying zoom.
131 auto localPt = m_loudspeakersInAreaPaintComponent->getLocalPoint(
132 nullptr, juce::Point<float>(cx, cy));
133 // Calling simulatePinchZoom on any one component fires onViewportZoomChanged,
134 // which the syncViewportZoom lambda propagates to all three siblings via setZoom().
135 m_loudspeakersInAreaPaintComponent->simulatePinchZoom(incrementalScale, localPt);
136 });
137 }
138 else if (!nativeHandle && m_nativePinchViewHandle)
139 {
140 JUCEAppBasics::iOS_utils::unregisterNativePinchOnView(m_nativePinchViewHandle);
141 m_nativePinchViewHandle = nullptr;
142 }
143#endif
144}
145
146std::unique_ptr<XmlElement> UmsciControlComponent::createStateXml()
147{
148 auto controlConfigStateXml = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLCONFIG));
149
150 controlConfigStateXml->setAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::IOSIZE), juce::String(getOcp1IOSize().first) + "x" + juce::String(getOcp1IOSize().second));
151
152 return controlConfigStateXml;
153}
154
155bool UmsciControlComponent::setStateXml(XmlElement* stateXml)
156{
157 if (!stateXml || (stateXml->getTagName() != UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLCONFIG)))
158 return false;
159
160 auto ocp1IOSize = stateXml->getStringAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::IOSIZE));
161 auto newIoSize = std::make_pair(ocp1IOSize.upToFirstOccurrenceOf("x", false, true).getIntValue(), ocp1IOSize.fromLastOccurrenceOf("x", false, true).getIntValue());
162 if (getOcp1IOSize() != newIoSize)
163 setOcp1IOSize(newIoSize);
164
165 return true;
166}
167
168const std::pair<int, int>& UmsciControlComponent::getOcp1IOSize()
169{
170 return m_ocp1IOSize;
171}
172
173void UmsciControlComponent::setOcp1IOSize(const std::pair<int, int>& ioSize)
174{
175 m_ocp1IOSize = ioSize;
176
177 // todo react to changes
178 rebuildOcp1ObjectTree();
179}
180
181void UmsciControlComponent::rebuildOcp1ObjectTree()
182{
183 // reset the expected database
184 setDatabaseComplete(false);
185 m_deviceName.clear();
186 m_speakerPosition.clear();
187 m_sourcePosition.clear();
188 m_sourceDelayMode.clear();
189 m_sourceSpread.clear();
190 m_sourceName.clear();
191 m_speakerName.clear();
192
193 // rebuild the object tree
194 auto ocp1ObjectTree = std::vector<DeviceController::RemoteObject>();
195
197 for (std::int16_t i = 1; i <= m_ocp1IOSize.first; i++)
198 {
205 }
206 for (std::int16_t o = 1; o <= m_ocp1IOSize.second; o++)
207 {
212 }
213
214 DeviceController::getInstance()->SetActiveRemoteObjects(ocp1ObjectTree);
215}
216
217void UmsciControlComponent::setRemoteObject(const DeviceController::RemoteObject& obj)
218{
219 DBG(juce::String(__FUNCTION__) << " " << DeviceController::RemoteObject::GetObjectDescription(obj.Id) << " " << obj.Addr.toNiceString());
220
221 switch (obj.Id)
222 {
224 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_STRING == obj.Var.GetDataType());
225 setDeviceName(obj.Var.ToString());
226 break;
228 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_STRING == obj.Var.GetDataType());
229 setSourceName(obj.Addr.pri, obj.Var.ToString());
230 break;
232 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT8 == obj.Var.GetDataType());
233 setSourceMute(obj.Addr.pri, obj.Var.ToUInt8());
234 break;
236 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_FLOAT32 == obj.Var.GetDataType());
237 setSourceGain(obj.Addr.pri, obj.Var.ToFloat());
238 break;
240 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_BLOB /*OCP1DATATYPE_DB_POSITION ?!*/ == obj.Var.GetDataType());
241 setSourcePosition(obj.Addr.pri, obj.Var.ToPosition());
242 break;
244 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT16 == obj.Var.GetDataType());
245 setSourceDelayMode(obj.Addr.pri, obj.Var.ToUInt16());
246 break;
248 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_FLOAT32 == obj.Var.GetDataType());
249 setSourceSpread(obj.Addr.pri, obj.Var.ToFloat());
250 break;
252 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_STRING == obj.Var.GetDataType());
253 setSpeakerName(obj.Addr.pri, obj.Var.ToString());
254 break;
256 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT8 == obj.Var.GetDataType());
257 setSpeakerMute(obj.Addr.pri, obj.Var.ToUInt8());
258 break;
260 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_FLOAT32 == obj.Var.GetDataType());
261 setSpeakerGain(obj.Addr.pri, obj.Var.ToFloat());
262 break;
264 jassert(NanoOcp1::Ocp1DataType::OCP1DATATYPE_BLOB /*OCP1DATATYPE_DB_POSITION ?!*/ == obj.Var.GetDataType());
265 setSpeakerPosition(obj.Addr.pri, obj.Var.ToAimingAndPosition());
266 break;
267 //all below fallthrough as unhandled
277 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT16;
280 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT8;
298 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_FLOAT32;
307 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_STRING;
315 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_DB_POSITION;
317 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_INT32;
319 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT8;
322 //datatype = NanoOcp1::Ocp1DataType::OCP1DATATYPE_UINT8;
323 default:
324 DBG(juce::String(__FUNCTION__) << " unhandled/unkown: " << DeviceController::RemoteObject::GetObjectDescription(obj.Id)
325 << " (" << static_cast<int>(obj.Addr.pri) << "," << static_cast<int>(obj.Addr.sec) << ") ");
326 break;
327 }
328
329 if (!m_databaseComplete && checkIsDatabaseComplete())
330 {
331 setDatabaseComplete(true);
332 }
333}
334
335bool UmsciControlComponent::checkIsDatabaseComplete()
336{
337 bool complete = true;
338
339 complete = complete && !m_deviceName.empty();
340
341 complete = complete && m_sourceName.size() == m_ocp1IOSize.first;
342 complete = complete && m_sourceMute.size() == m_ocp1IOSize.first;
343 complete = complete && m_sourceGain.size() == m_ocp1IOSize.first;
344 complete = complete && m_sourceSpread.size() == m_ocp1IOSize.first;
345 complete = complete && m_sourceDelayMode.size() == m_ocp1IOSize.first;
346 complete = complete && m_sourcePosition.size() == m_ocp1IOSize.first;
347
348 complete = complete && m_speakerName.size() == m_ocp1IOSize.second;
349 complete = complete && m_speakerMute.size() == m_ocp1IOSize.second;
350 complete = complete && m_speakerGain.size() == m_ocp1IOSize.second;
351 complete = complete && m_speakerPosition.size() == m_ocp1IOSize.second;
352
353 return complete;
354}
355
356bool UmsciControlComponent::isDatabaseComplete()
357{
358 return m_databaseComplete;
359}
360
361void UmsciControlComponent::updatePaintComponents()
362{
363 auto realBoundingRect = getRealBoundingRect();
364 // Expand proportionally per axis so the aspect ratio of the reference rect is preserved,
365 // keeping the real-to-screen coordinate mapping undistorted.
366 m_boundsRealRef = realBoundingRect.expanded(realBoundingRect.getWidth() * 0.2f,
367 realBoundingRect.getHeight() * 0.2f);
368
369 m_loudspeakersInAreaPaintComponent->setBoundsRealRef(m_boundsRealRef);
370 m_loudspeakersInAreaPaintComponent->setSpeakerPositions(m_speakerPosition);
371
372 m_soundobjectsInAreaPaintComponent->setBoundsRealRef(m_boundsRealRef);
373 m_soundobjectsInAreaPaintComponent->setSourcePositions(m_sourcePosition);
374
375 m_upmixIndicatorPaintAndControlComponent->setSpeakersRealBoundingCube(getRealBoundingCube());
376 m_upmixIndicatorPaintAndControlComponent->setBoundsRealRef(m_boundsRealRef);
377 m_upmixIndicatorPaintAndControlComponent->setSourcePositions(m_sourcePosition);
378
379 resized();
380}
381
382void UmsciControlComponent::setDatabaseComplete(bool complete)
383{
384 DBG(juce::String(__FUNCTION__) << (complete ? " compl." : " incmplt."));
385 m_databaseComplete = complete;
386
387 if (complete)
388 {
389 updatePaintComponents();
390
393 }
394 else
395 {
396 m_deviceName.clear();
397
398 m_sourceName.clear();
399 m_sourceMute.clear();
400 m_sourceGain.clear();
401 m_sourceSpread.clear();
402 m_sourceDelayMode.clear();
403 m_sourcePosition.clear();
404
405 m_speakerName.clear();
406 m_speakerMute.clear();
407 m_speakerGain.clear();
408 m_speakerPosition.clear();
409 }
410}
411
412const juce::Rectangle<float> UmsciControlComponent::getRealBoundingRect()
413{
414 // A fully-zeroed 6DOF entry (both orientation and position all zero) is uninitialized and excluded.
415 auto isValid = [](const std::array<std::float_t, 6>& pos) {
416 return pos.at(0) != 0.0f || pos.at(1) != 0.0f || pos.at(2) != 0.0f
417 || pos.at(3) != 0.0f || pos.at(4) != 0.0f || pos.at(5) != 0.0f;
418 };
419
420 auto it = std::find_if(m_speakerPosition.begin(), m_speakerPosition.end(),
421 [&](const auto& kv) { return isValid(kv.second); });
422 if (it == m_speakerPosition.end())
423 return {};
424
425 juce::Range<float> xRange = { it->second.at(3), it->second.at(3) };
426 juce::Range<float> yRange = { it->second.at(4), it->second.at(4) };
427 juce::Range<float> zRange = { it->second.at(5), it->second.at(5) };
428
429 for (auto const& speakerPosition : m_speakerPosition)
430 {
431 if (!isValid(speakerPosition.second))
432 continue;
433 xRange = xRange.getUnionWith(speakerPosition.second.at(3));
434 yRange = yRange.getUnionWith(speakerPosition.second.at(4));
435 zRange = zRange.getUnionWith(speakerPosition.second.at(5));
436 }
437
438 // Build rect with screen-space-aligned semantics:
439 // getX()/getWidth() = d&b y span (across stage = screen horizontal)
440 // getY()/getHeight() = d&b x span (towards audience = screen vertical)
441 return juce::Rectangle<float>({ yRange.getStart(), xRange.getStart() }, { yRange.getEnd(), xRange.getEnd() });
442}
443
444
445const std::array<float, 6> UmsciControlComponent::getRealBoundingCube()
446{
447 // A fully-zeroed 6DOF entry (both orientation and position all zero) is uninitialized and excluded.
448 auto isValid = [](const std::array<std::float_t, 6>& pos) {
449 return pos.at(0) != 0.0f || pos.at(1) != 0.0f || pos.at(2) != 0.0f
450 || pos.at(3) != 0.0f || pos.at(4) != 0.0f || pos.at(5) != 0.0f;
451 };
452
453 auto it = std::find_if(m_speakerPosition.begin(), m_speakerPosition.end(),
454 [&](const auto& kv) { return isValid(kv.second); });
455 if (it == m_speakerPosition.end())
456 return {};
457
458 juce::Range<float> xRange = { it->second.at(3), it->second.at(3) };
459 juce::Range<float> yRange = { it->second.at(4), it->second.at(4) };
460 juce::Range<float> zRange = { it->second.at(5), it->second.at(5) };
461
462 for (auto const& speakerPosition : m_speakerPosition)
463 {
464 if (!isValid(speakerPosition.second))
465 continue;
466 xRange = xRange.getUnionWith(speakerPosition.second.at(3));
467 yRange = yRange.getUnionWith(speakerPosition.second.at(4));
468 zRange = zRange.getUnionWith(speakerPosition.second.at(5));
469 }
470
471 return std::array<float, 6>({ xRange.getStart(), yRange.getStart(), zRange.getStart(), xRange.getEnd(), yRange.getEnd(), zRange.getEnd() });
472}
473
475{
476 setDatabaseComplete(false);
477}
478
479void UmsciControlComponent::setDeviceName(const std::string& name)
480{
481 m_deviceName = name;
482}
483
484void UmsciControlComponent::setSourceName(std::int16_t sourceId, const std::string& name)
485{
486 m_sourceName[sourceId] = name;
487}
488
489void UmsciControlComponent::setSourceMute(std::int16_t sourceId, const std::uint8_t& ocp1MuteValue)
490{
491 switch (ocp1MuteValue)
492 {
493 case 2:
494 m_sourceMute[sourceId] = false;
495 break;
496 case 1:
497 default:
498 m_sourceMute[sourceId] = true;
499 break;
500 }
501
502}
503
504void UmsciControlComponent::setSourceGain(std::int16_t sourceId, const std::float_t& gain)
505{
506 m_sourceGain[sourceId] = gain;
507}
508
509void UmsciControlComponent::setSourcePosition(std::int16_t sourceId, const std::array<std::float_t, 3>& position)
510{
511 m_sourcePosition[sourceId] = position;
512
513 if (m_databaseComplete)
514 {
515 m_soundobjectsInAreaPaintComponent->setSourcePosition(sourceId, position);
516 m_upmixIndicatorPaintAndControlComponent->setSourcePosition(sourceId, position);
517 }
518}
519
520void UmsciControlComponent::setSourceDelayMode(std::int16_t sourceId, const std::uint16_t& delayMode)
521{
522 m_sourceDelayMode[sourceId] = delayMode;
523}
524
525void UmsciControlComponent::setSourceSpread(std::int16_t sourceId, const std::float_t& spread)
526{
527 m_sourceSpread[sourceId] = spread;
528}
529
530void UmsciControlComponent::setSpeakerName(std::int16_t speakerId, const std::string& name)
531{
532 m_speakerName[speakerId] = name;
533}
534
535void UmsciControlComponent::setSpeakerMute(std::int16_t speakerId, const std::uint8_t& ocp1MuteValue)
536{
537 switch (ocp1MuteValue)
538 {
539 case 2:
540 m_speakerMute[speakerId] = false;
541 break;
542 case 1:
543 default:
544 m_speakerMute[speakerId] = true;
545 break;
546 }
547}
548
549void UmsciControlComponent::setSpeakerGain(std::int16_t speakerId, const std::float_t& gain)
550{
551 m_speakerGain[speakerId] = gain;
552}
553
554void UmsciControlComponent::setSpeakerPosition(std::int16_t speakerId, const std::array<std::float_t, 6>& position)
555{
556 m_speakerPosition[speakerId] = position;
557
558 if (m_databaseComplete)
559 {
560 // Speaker positions define the coordinate space — recalculate bounds and propagate to all components
561 auto realBoundingRect = getRealBoundingRect();
562 auto expandAmount = ((realBoundingRect.getAspectRatio() > 1.0f) ? realBoundingRect.getWidth() * 0.2f : realBoundingRect.getHeight() * 0.2f);
563 m_boundsRealRef = realBoundingRect.expanded(expandAmount, expandAmount);
564
565 m_loudspeakersInAreaPaintComponent->setBoundsRealRef(m_boundsRealRef);
566 m_loudspeakersInAreaPaintComponent->setSpeakerPosition(speakerId, position);
567
568 m_soundobjectsInAreaPaintComponent->setBoundsRealRef(m_boundsRealRef);
569
570 m_upmixIndicatorPaintAndControlComponent->setSpeakersRealBoundingCube(getRealBoundingCube());
571 m_upmixIndicatorPaintAndControlComponent->setBoundsRealRef(m_boundsRealRef);
572
573 resized(); // re-maps all screen positions in all children via their resized() callbacks
574 }
575}
576
577void UmsciControlComponent::setUpmixChannelConfiguration(const juce::AudioChannelSet& upmixChannelConfig)
578{
579 if (m_upmixIndicatorPaintAndControlComponent)
580 m_upmixIndicatorPaintAndControlComponent->setChannelConfiguration(upmixChannelConfig);
581 updateSourceIdFilter();
582}
583
585{
586 if (m_upmixIndicatorPaintAndControlComponent)
587 return m_upmixIndicatorPaintAndControlComponent->getChannelConfiguration();
588 else
589 return {};
590}
591
593{
594 if (m_upmixIndicatorPaintAndControlComponent)
595 m_upmixIndicatorPaintAndControlComponent->setSourceStartId(startId);
596 updateSourceIdFilter();
597}
598
600{
601 if (m_upmixIndicatorPaintAndControlComponent)
602 return m_upmixIndicatorPaintAndControlComponent->getSourceStartId();
603 return 1;
604}
605
607{
608 if (m_upmixIndicatorPaintAndControlComponent)
609 m_upmixIndicatorPaintAndControlComponent->setLiveMode(liveMode);
610}
611
613{
614 if (m_upmixIndicatorPaintAndControlComponent)
615 return m_upmixIndicatorPaintAndControlComponent->getLiveMode();
616 return false;
617}
618
620{
621 if (m_upmixIndicatorPaintAndControlComponent)
622 m_upmixIndicatorPaintAndControlComponent->setShape(shape);
623}
624
626{
627 if (m_upmixIndicatorPaintAndControlComponent)
628 return m_upmixIndicatorPaintAndControlComponent->getShape();
630}
631
633{
634 if (m_loudspeakersInAreaPaintComponent)
635 m_loudspeakersInAreaPaintComponent->setControlsSize(size);
636 if (m_soundobjectsInAreaPaintComponent)
637 m_soundobjectsInAreaPaintComponent->setControlsSize(size);
638 if (m_upmixIndicatorPaintAndControlComponent)
639 m_upmixIndicatorPaintAndControlComponent->setControlsSize(size);
640}
641
643{
644 if (m_loudspeakersInAreaPaintComponent)
645 return m_loudspeakersInAreaPaintComponent->getControlsSize();
647}
648
649void UmsciControlComponent::setUpmixTransform(float rot, float trans, float heightTrans, float angleStretch)
650{
651 if (m_upmixIndicatorPaintAndControlComponent)
652 m_upmixIndicatorPaintAndControlComponent->setUpmixTransform(rot, trans, heightTrans, angleStretch);
653}
654
656{
657 if (m_upmixIndicatorPaintAndControlComponent)
658 return m_upmixIndicatorPaintAndControlComponent->getUpmixRot();
659 return 0.0f;
660}
661
663{
664 if (m_upmixIndicatorPaintAndControlComponent)
665 return m_upmixIndicatorPaintAndControlComponent->getUpmixTrans();
666 return 1.0f;
667}
668
670{
671 if (m_upmixIndicatorPaintAndControlComponent)
672 return m_upmixIndicatorPaintAndControlComponent->getUpmixHeightTrans();
673 return 0.6f;
674}
675
677{
678 if (m_upmixIndicatorPaintAndControlComponent)
679 return m_upmixIndicatorPaintAndControlComponent->getUpmixAngleStretch();
680 return 1.0f;
681}
682
684{
685 if (m_upmixIndicatorPaintAndControlComponent)
686 m_upmixIndicatorPaintAndControlComponent->setUpmixOffset(x, y);
687}
688
690{
691 if (m_upmixIndicatorPaintAndControlComponent)
692 m_upmixIndicatorPaintAndControlComponent->notifyTransformChanged();
693}
694
696{
697 if (m_upmixIndicatorPaintAndControlComponent)
698 m_upmixIndicatorPaintAndControlComponent->triggerFlashCheck();
699}
700
702{
703 if (m_upmixIndicatorPaintAndControlComponent)
704 return m_upmixIndicatorPaintAndControlComponent->getUpmixOffsetX();
705 return 0.0f;
706}
707
709{
710 if (m_upmixIndicatorPaintAndControlComponent)
711 return m_upmixIndicatorPaintAndControlComponent->getUpmixOffsetY();
712 return 0.0f;
713}
714
716{
717 m_showAllSources = showAll;
718 updateSourceIdFilter();
719}
720
722{
723 return m_showAllSources;
724}
725
726void UmsciControlComponent::updateSourceIdFilter()
727{
728 if (!m_soundobjectsInAreaPaintComponent)
729 return;
730
731 if (m_showAllSources)
732 {
733 m_soundobjectsInAreaPaintComponent->setSourceIdFilter({});
734 }
735 else
736 {
737 auto startId = getUpmixSourceStartId();
738 auto channelCount = getUpmixChannelConfiguration().size();
739 std::set<std::int16_t> allowedIds;
740 for (int i = 0; i < channelCount; ++i)
741 allowedIds.insert(static_cast<std::int16_t>(startId + i));
742 m_soundobjectsInAreaPaintComponent->setSourceIdFilter(allowedIds);
743 }
744}
745
@ IOSIZE
String: "inputs,outputs" channel count pair.
static juce::String getTagName(TagID ID)
static juce::String getAttributeName(AttributeID ID)
@ CONTROLCONFIG
Control-component settings.
float getUpmixTrans() const
Radial scale factor.
void setUpmixChannelConfiguration(const juce::AudioChannelSet &upmixChannelConfig)
void setSpeakerGain(std::int16_t speakerId, const std::float_t &gain)
void setUpmixTransform(float rot, float trans, float heightTrans, float angleStretch=1.0f)
void setUpmixShape(UmsciUpmixIndicatorPaintNControlComponent::IndicatorShape shape)
UmsciUpmixIndicatorPaintNControlComponent::IndicatorShape getUpmixShape() const
void setSpeakerPosition(std::int16_t speakerId, const std::array< std::float_t, 6 > &position)
Sets the 6-component speaker position: {X, Y, Z, horizontal angle, vertical angle,...
void setSourceName(std::int16_t sourceId, const std::string &name)
void setSourceDelayMode(std::int16_t sourceId, const std::uint16_t &delayMode)
Delay mode: 0 = off, 1 = compensate, 2 = reflect (DS100-specific enum).
void setShowAllSources(bool showAll)
When false, only sound objects that are part of the upmix group (i.e. channels >= sourceStartId withi...
std::unique_ptr< XmlElement > createStateXml() override
void setControlsSize(UmsciPaintNControlComponentBase::ControlsSize size)
Sets the visual size of source/speaker icons (small / medium / large).
void setUpmixOffset(float x, float y)
std::function< void()> onUpmixTransformChanged
Fired on the message thread whenever the user changes any upmix transform parameter via the on-screen...
const juce::AudioChannelSet getUpmixChannelConfiguration()
const std::pair< int, int > & getOcp1IOSize()
Sets the DS100 input/output channel count for this session.
void triggerUpmixTransformApplied()
Fires live-mode position callbacks and onUpmixTransformChanged after a programmatic transform update ...
bool setStateXml(XmlElement *stateXml) override
float getUpmixHeightTrans() const
Height (Z) translation.
void setUpmixLiveMode(bool liveMode)
true = follow live DS100 positions.
void setSourceGain(std::int16_t sourceId, const std::float_t &gain)
Gain in dB.
void setOcp1IOSize(const std::pair< int, int > &ioSize)
void resetData()
Clears all cached source/speaker data and marks the database incomplete.
void setSourcePosition(std::int16_t sourceId, const std::array< std::float_t, 3 > &position)
Sets the 3-component (X, Y, Z) absolute position in normalised real-world coordinates.
void triggerUpmixFlashCheck()
Starts the flash animation on the upmix indicator if the ideal ring positions diverge from the curren...
void setSpeakerName(std::int16_t speakerId, const std::string &name)
void setSpeakerMute(std::int16_t speakerId, const std::uint8_t &mute)
void setUpmixSourceStartId(int startId)
1-based DS100 channel of the first upmix input.
void setDeviceName(const std::string &name)
Updates the device name label (shown as a title/overlay in the UI).
std::function< void()> onDatabaseComplete
Fired on the message thread when all initially subscribed values have been received from the DS100 (i...
float getUpmixAngleStretch() const
Per-angle stretch factor (1.0 = uniform).
UmsciPaintNControlComponentBase::ControlsSize getControlsSize() const
void setSourceMute(std::int16_t sourceId, const std::uint8_t &mute)
0 = unmuted, nonzero = muted.
void paint(juce::Graphics &g) override
void setSourceSpread(std::int16_t sourceId, const std::float_t &spread)
Spread factor 0.0–1.0 (0 = point source, 1 = full spread).
float getUpmixRot() const
Ring rotation in normalised units (0–1 → 0–360°).
ControlsSize
Visual size of source/speaker icons. Multiplier accessible via getControlsSizeMultiplier().
IndicatorShape
The geometric shape used to draw the upmix speaker ring.
Two-dimensional address of a remote object on the DS100.
std::int16_t pri
Primary index (channel, speaker, group, zone…). 0 = not used.
juce::String toNiceString() const
static constexpr std::int16_t sc_INV
std::int16_t sec
Secondary index (mapping area, output ch, group…). 0 = not used.
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.