Umsci
Upmix Spatial Control Interface — OCA/OCP.1 spatial audio utility
Loading...
Searching...
No Matches
MainComponent.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 "MainComponent.h"
20
21#include "DeviceController.h"
22
27
28#include "AboutComponent.h"
30
31#include <CustomLookAndFeel.h>
32#include <WebUpdateDetector.h>
33
34#include <iOS_utils.h>
35
36
38 : juce::Component()
39{
40 // create the configuration object (is being initialized from disk automatically)
41 m_config = std::make_unique<UmsciAppConfiguration>(JUCEAppBasics::AppConfigurationBase::getDefaultConfigFilePath());
42 m_config->addDumper(this);
43
44 // check if config creation was able to read a valid config from disk...
45 if (!m_config->isValid())
46 {
47 m_config->ResetToDefault();
48 }
49
50 // create different main components and add them
51 m_controlComponent = std::make_unique<UmsciControlComponent>();
52 addAndMakeVisible(m_controlComponent.get());
53
54 m_discoverHintComponent = std::make_unique<UmsciDiscoveringHintComponent>();
55 addAndMakeVisible(m_discoverHintComponent.get());
56
57 m_connectingComponent = std::make_unique<UmsciConnectingComponent>();
58 addAndMakeVisible(m_connectingComponent.get());
59
60 // initially activate the 'offline state' reg. component configuration
61 m_connectingComponent->setVisible(false);
62 m_controlComponent->setVisible(false);
63 m_discoverHintComponent->setVisible(true);
64
65 m_aboutComponent = std::make_unique<AboutComponent>(BinaryData::UmsciRect_png, BinaryData::UmsciRect_pngSize);
66 m_aboutButton = std::make_unique<juce::DrawableButton>("About", juce::DrawableButton::ButtonStyle::ImageFitted);
67 m_aboutButton->setTooltip(juce::String("About") + juce::JUCEApplication::getInstance()->getApplicationName());
68 m_aboutButton->onClick = [this] {
69 juce::PopupMenu aboutMenu;
70 aboutMenu.addCustomItem(1, std::make_unique<CustomAboutItem>(m_aboutComponent.get(), juce::Rectangle<int>(250, 250)), nullptr, juce::String("Info about") + juce::JUCEApplication::getInstance()->getApplicationName());
71 aboutMenu.showMenuAsync(juce::PopupMenu::Options());
72 };
73 m_aboutButton->setAlwaysOnTop(true);
74 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
75 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
76 addAndMakeVisible(m_aboutButton.get());
77
78 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
79#if JUCE_WINDOWS
80 auto fullscreenShortCutHint = std::string(" (Ctrl+F11)");
81#elif JUCE_MAC
82 auto fullscreenShortCutHint = std::string(" (Cmd+Ctrl+F)");
83#endif
84
85 // default lookandfeel is follow local, therefor none selected
86 m_settingsItems[UmsciSettingsOption::LookAndFeel_FollowHost] = std::make_pair("Follow host", 0);
87 m_settingsItems[UmsciSettingsOption::LookAndFeel_Dark] = std::make_pair("Dark", 1);
88 m_settingsItems[UmsciSettingsOption::LookAndFeel_Light] = std::make_pair("Light", 0);
89 // default output visu is 5.0
90 m_settingsItems[UmsciSettingsOption::ControlFormat_Stereo] = std::make_pair(juce::AudioChannelSet::stereo().getDescription().toStdString(), 0);
91 m_settingsItems[UmsciSettingsOption::ControlFormat_LRS] = std::make_pair(juce::AudioChannelSet::createLRS().getDescription().toStdString(), 0);
92 m_settingsItems[UmsciSettingsOption::ControlFormat_LCRS] = std::make_pair(juce::AudioChannelSet::createLCRS().getDescription().toStdString(), 0);
93 m_settingsItems[UmsciSettingsOption::ControlFormat_5point0] = std::make_pair(juce::AudioChannelSet::create5point0().getDescription().toStdString(), 1);
94 m_settingsItems[UmsciSettingsOption::ControlFormat_5point1] = std::make_pair(juce::AudioChannelSet::create5point1().getDescription().toStdString(), 0);
95 m_settingsItems[UmsciSettingsOption::ControlFormat_5point1point2] = std::make_pair(juce::AudioChannelSet::create5point1point2().getDescription().toStdString(), 0);
96 m_settingsItems[UmsciSettingsOption::ControlFormat_7point0] = std::make_pair(juce::AudioChannelSet::create7point0().getDescription().toStdString(), 0);
97 m_settingsItems[UmsciSettingsOption::ControlFormat_7point1] = std::make_pair(juce::AudioChannelSet::create7point1().getDescription().toStdString(), 0);
98 m_settingsItems[UmsciSettingsOption::ControlFormat_7point1point4] = std::make_pair(juce::AudioChannelSet::create7point1point4().getDescription().toStdString(), 0);
99 m_settingsItems[UmsciSettingsOption::ControlFormat_9point1point6] = std::make_pair(juce::AudioChannelSet::create9point1point6().getDescription().toStdString(), 0);
100 // default panning colour is green
101 m_settingsItems[UmsciSettingsOption::ControlColour_Green] = std::make_pair("Green", 1);
102 m_settingsItems[UmsciSettingsOption::ControlColour_Red] = std::make_pair("Red", 0);
103 m_settingsItems[UmsciSettingsOption::ControlColour_Blue] = std::make_pair("Blue", 0);
104 m_settingsItems[UmsciSettingsOption::ControlColour_Pink] = std::make_pair("Anni Pink", 0);
105 m_settingsItems[UmsciSettingsOption::ControlColour_Laser] = std::make_pair("Laser", 0);
106 // connection settings
107 m_settingsItems[UmsciSettingsOption::ConnectionSettings] = std::make_pair("Connection settings...", 0);
108 // upmix settings
109 m_settingsItems[UmsciSettingsOption::UpmixSettings] = std::make_pair("Upmix control settings...", 0);
110 // external (MIDI) control settings
111 m_settingsItems[UmsciSettingsOption::ExternalControlSettings] = std::make_pair("External control...", 0);
112 // control size
113 m_settingsItems[UmsciSettingsOption::ControlSize_S] = std::make_pair("S", 1);
114 m_settingsItems[UmsciSettingsOption::ControlSize_M] = std::make_pair("M", 0);
115 m_settingsItems[UmsciSettingsOption::ControlSize_L] = std::make_pair("L", 0);
116#if JUCE_WINDOWS || JUCE_MAC
117 // fullscreen toggling
118 m_settingsItems[UmsciSettingsOption::FullscreenWindowMode] = std::make_pair("Toggle fullscreen mode" + fullscreenShortCutHint, 0);
119#endif
120 // Further components
121 m_settingsButton = std::make_unique<juce::DrawableButton>("Settings", juce::DrawableButton::ButtonStyle::ImageFitted);
122 m_settingsButton->setTooltip(juce::String("Settings for ") + juce::JUCEApplication::getInstance()->getApplicationName());
123 m_settingsButton->onClick = [this] {
124 juce::PopupMenu lookAndFeelSubMenu;
126 lookAndFeelSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
127
128 juce::PopupMenu controlColourSubMenu;
130 controlColourSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
131
132 juce::PopupMenu controlSizeSubMenu;
134 controlSizeSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
135
136 juce::PopupMenu settingsMenu;
137 settingsMenu.addSubMenu("LookAndFeel", lookAndFeelSubMenu);
138 settingsMenu.addSubMenu("Control colour", controlColourSubMenu);
139 settingsMenu.addSubMenu("Control size", controlSizeSubMenu);
140 settingsMenu.addSeparator();
141 settingsMenu.addItem(UmsciSettingsOption::ConnectionSettings, m_settingsItems[UmsciSettingsOption::ConnectionSettings].first, true, false);
142 settingsMenu.addItem(UmsciSettingsOption::UpmixSettings, m_settingsItems[UmsciSettingsOption::UpmixSettings].first, true, false);
143 settingsMenu.addItem(UmsciSettingsOption::ExternalControlSettings, m_settingsItems[UmsciSettingsOption::ExternalControlSettings].first, true, false);
144#if JUCE_WINDOWS || JUCE_MAC
145 settingsMenu.addSeparator();
146 settingsMenu.addItem(UmsciSettingsOption::FullscreenWindowMode, m_settingsItems[UmsciSettingsOption::FullscreenWindowMode].first, true, false);
147#endif
148 settingsMenu.showMenuAsync(juce::PopupMenu::Options(), [=](int selectedId) {
149 handleSettingsMenuResult(selectedId);
150 if (m_config)
151 m_config->triggerConfigurationDump();
152 });
153 };
154 m_settingsButton->setAlwaysOnTop(true);
155 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
156 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
157 addAndMakeVisible(m_settingsButton.get());
158
159 m_connectionToggleButton = std::make_unique<juce::DrawableButton>("ConnectionToggle", juce::DrawableButton::ButtonStyle::ImageFitted);
160 m_connectionToggleButton->setTooltip("Toggle connection to device.");
161 m_connectionToggleButton->onClick = [this] {
162 if (m_connectionToggleButton->getToggleState())
163 DeviceController::getInstance()->connect();
164 else
165 {
166 DeviceController::getInstance()->disconnect();
167 m_controlComponent->resetData();
168 }
169
171
172 if (m_config)
173 m_config->triggerConfigurationDump();
174 };
175 m_connectionToggleButton->setAlwaysOnTop(true);
176 m_connectionToggleButton->setClickingTogglesState(true);
177 m_connectionToggleButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
178 m_connectionToggleButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
179 addAndMakeVisible(m_connectionToggleButton.get());
180
181 if (juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui"))
182 {
183 m_aboutButton->setVisible(false);
184 m_settingsButton->setVisible(false);
185 m_connectionToggleButton->setVisible(false);
186 }
187
188 m_upmixSnapshotStoreButton = std::make_unique<juce::DrawableButton>("SnapshotStore", juce::DrawableButton::ButtonStyle::ImageFitted);
189 m_upmixSnapshotStoreButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
190 m_upmixSnapshotStoreButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
191 m_upmixSnapshotStoreButton->setTooltip("Store upmix indicator state");
192 m_upmixSnapshotStoreButton->onClick = [this] {
193 if (m_controlComponent)
194 {
195 m_upmixSnapshot = UpmixSnapshot{
196 m_controlComponent->getUpmixRot(),
197 m_controlComponent->getUpmixTrans(),
198 m_controlComponent->getUpmixHeightTrans(),
199 m_controlComponent->getUpmixAngleStretch(),
200 m_controlComponent->getUpmixOffsetX(),
201 m_controlComponent->getUpmixOffsetY()
202 };
203 m_upmixSnapshotRecallButton->setEnabled(true);
204 if (m_config)
205 m_config->triggerConfigurationDump();
206 }
207 };
208 m_upmixSnapshotStoreButton->setAlwaysOnTop(true);
209 m_upmixSnapshotStoreButton->setVisible(false);
210 addAndMakeVisible(m_upmixSnapshotStoreButton.get());
211
212 m_upmixSnapshotRecallButton = std::make_unique<juce::DrawableButton>("SnapshotRecall", juce::DrawableButton::ButtonStyle::ImageFitted);
213 m_upmixSnapshotRecallButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
214 m_upmixSnapshotRecallButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
215 m_upmixSnapshotRecallButton->setTooltip("Recall upmix indicator state");
216 m_upmixSnapshotRecallButton->onClick = [this] {
217 if (m_controlComponent && m_upmixSnapshot.has_value())
218 {
219 const auto& s = *m_upmixSnapshot;
220 m_controlComponent->setUpmixTransform(s.rot, s.scale, s.heightScale, s.angleStretch);
221 m_controlComponent->setUpmixOffset(s.offsetX, s.offsetY);
222 if (m_controlComponent->getUpmixLiveMode())
223 m_controlComponent->triggerUpmixTransformApplied(); // sends positions to DS100 immediately
224 else
225 m_controlComponent->triggerUpmixFlashCheck(); // flashes indicator; user sends manually
226 if (m_config)
227 m_config->triggerConfigurationDump();
228 }
229 };
230 m_upmixSnapshotRecallButton->setAlwaysOnTop(true);
231 m_upmixSnapshotRecallButton->setEnabled(false);
232 m_upmixSnapshotRecallButton->setVisible(false);
233 addAndMakeVisible(m_upmixSnapshotRecallButton.get());
234
235 m_controlComponent->onUpmixTransformChanged = [this]() {
236 if (m_config)
237 m_config->triggerConfigurationDump();
238 };
239
240 DeviceController::getInstance()->onStateChanged = [=](DeviceController::State s) {
241 auto noconfigui = juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui");
242 switch (s)
243 {
245 m_connectionToggleButton->setToggleState(false, juce::dontSendNotification);
246 m_connectingComponent->setVisible(false);
247 m_connectingComponent->setConnectionStatus(UmsciConnectingComponent::Status::Connecting);
248 m_controlComponent->setVisible(false);
249 m_discoverHintComponent->setVisible(true);
250 m_upmixSnapshotStoreButton->setVisible(false);
251 m_upmixSnapshotRecallButton->setVisible(false);
252 break;
254 m_connectionToggleButton->setToggleState(true, juce::dontSendNotification);
255 m_controlComponent->setVisible(false);
256 m_discoverHintComponent->setVisible(false);
257 m_connectingComponent->setVisible(true);
258 m_connectingComponent->setConnectionStatus(UmsciConnectingComponent::Status::Connecting);
259 m_upmixSnapshotStoreButton->setVisible(false);
260 m_upmixSnapshotRecallButton->setVisible(false);
261 break;
263 m_connectionToggleButton->setToggleState(true, juce::dontSendNotification);
264 m_controlComponent->setVisible(false);
265 m_discoverHintComponent->setVisible(false);
266 m_connectingComponent->setVisible(true);
267 m_connectingComponent->setConnectionStatus(UmsciConnectingComponent::Status::Subscribing);
268 m_upmixSnapshotStoreButton->setVisible(false);
269 m_upmixSnapshotRecallButton->setVisible(false);
270 break;
272 m_connectionToggleButton->setToggleState(true, juce::dontSendNotification);
273 m_connectingComponent->setVisible(true);
274 m_connectingComponent->setConnectionStatus(UmsciConnectingComponent::Status::Reading);
275 m_discoverHintComponent->setVisible(false);
276 m_controlComponent->setVisible(false);
277 m_upmixSnapshotStoreButton->setVisible(false);
278 m_upmixSnapshotRecallButton->setVisible(false);
279 break;
281 m_connectionToggleButton->setToggleState(true, juce::dontSendNotification);
282 m_connectingComponent->setVisible(false);
283 m_discoverHintComponent->setVisible(false);
284 m_controlComponent->setVisible(true);
285 if (!noconfigui)
286 {
287 m_upmixSnapshotStoreButton->setVisible(true);
288 m_upmixSnapshotRecallButton->setVisible(true);
289 }
290 break;
291 default:
292 break;
293 }
294 };
295
296#ifdef RUN_MESSAGE_TESTS
297 Mema::runTests();
298#endif
299
300 setSize(800, 600);
301
302#if defined JUCE_IOS
303 // iOS is updated via AppStore
304#define IGNORE_UPDATES
305#elif defined JUCE_ANDROID
306 // Android as well
307#define IGNORE_UPDATES
308#endif
309
310#if defined IGNORE_UPDATES
311#else
312 auto noUpdates = juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noupdates");
313 if (!noUpdates)
314 {
315 auto updater = JUCEAppBasics::WebUpdateDetector::getInstance();
316 updater->SetReferenceVersion(ProjectInfo::versionString);
317 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/umsci/releases/latest");
318 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Umsci/refs/heads/main/");
319 }
320#endif
321
322
323 // MIDI and OSC controllers
324 m_midiController = std::make_unique<MidiController>();
325 m_midiController->onParamValueChanged = [this](UmsciExternalControlComponent::UpmixMidiParam param, float domainValue) {
326 applyUpmixParamValue(param, domainValue);
327 };
328
329 m_oscController = std::make_unique<OscController>();
330 m_oscController->onParamValueChanged = [this](UmsciExternalControlComponent::UpmixMidiParam param, float domainValue) {
331 applyUpmixParamValue(param, domainValue);
332 };
333
334 // add this main component to watchers
335 m_config->addWatcher(this); // without initial update - that we have to do externally after lambdas were assigned
336
337 // we want keyboard focus for fullscreen toggle shortcut
338 setWantsKeyboardFocus(true);
339
341
342 // On iOS/iPadOS, UIKit populates safeAreaInsets asynchronously after the
343 // window is shown. initialise() registers a callback so resized() is re-run
344 // once the insets are valid and on any subsequent geometry change
345 // (Stage Manager, split-screen, etc.). No-op on all other platforms.
346 JUCEAppBasics::iOS_utils::initialise([this] {
347 juce::MessageManager::callAsync([this] { resized(); });
348 });
349}
350
352{
353 JUCEAppBasics::iOS_utils::deinitialise();
354}
355
357{
358 auto safety = JUCEAppBasics::iOS_utils::getDeviceSafetyMargins();
359 auto safeBounds = getLocalBounds();
360 safeBounds.removeFromTop(safety._top);
361 safeBounds.removeFromBottom(safety._bottom);
362 safeBounds.removeFromLeft(safety._left);
363 safeBounds.removeFromRight(safety._right);
364
365 m_controlComponent->setBounds(safeBounds);
366 m_connectingComponent->setBounds(safeBounds);
367 m_discoverHintComponent->setBounds(safeBounds);
368
369 if (!juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui"))
370 {
371 auto leftButtons = safeBounds.removeFromLeft(36);
372 auto rightButtons = safeBounds.removeFromLeft(36);
373 m_aboutButton->setBounds(leftButtons.removeFromTop(35).removeFromBottom(30));
374 m_settingsButton->setBounds(leftButtons.removeFromTop(35).removeFromBottom(30));
375 m_connectionToggleButton->setBounds(rightButtons.removeFromTop(35).removeFromBottom(30));
376
377 m_upmixSnapshotStoreButton->setBounds(leftButtons.removeFromBottom(35).removeFromRight(30).removeFromTop(30));
378 m_upmixSnapshotRecallButton->setBounds(leftButtons.removeFromBottom(35).removeFromRight(30).removeFromTop(30));
379 }
380}
381
382void MainComponent::paint(juce::Graphics& g)
383{
384 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
385}
386
388{
389 auto aboutButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::question_mark_24dp_svg).get());
390 aboutButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
391 m_aboutButton->setImages(aboutButtonDrawable.get());
392
393 auto settingsDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
394 settingsDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
395 m_settingsButton->setImages(settingsDrawable.get());
396
397 auto connectedDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::link_off_24dp_svg).get());
398 connectedDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
399 auto disconnectedDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::link_24dp_svg).get());
400 disconnectedDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOffId));
401 if (m_connectionToggleButton->getToggleState())
402 m_connectionToggleButton->setImages(connectedDrawable.get());
403 else
404 m_connectionToggleButton->setImages(disconnectedDrawable.get());
405
406 auto snapshotStoreDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::variable_add_24dp_svg).get());
407 snapshotStoreDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
408 m_upmixSnapshotStoreButton->setImages(snapshotStoreDrawable.get());
409
410 auto snapshotRecallDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::variable_insert_24dp_svg).get());
411 snapshotRecallDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
412 m_upmixSnapshotRecallButton->setImages(snapshotRecallDrawable.get());
413
414 applyControlColour();
415}
416
418{
419 // use the settings menu item call infrastructure to set the option
420 handleSettingsMenuResult(option);
421}
422
423void MainComponent::handleSettingsMenuResult(int selectedId)
424{
425 if (0 == selectedId)
426 return; // nothing selected, dismiss
427 else if (UmsciSettingsOption::LookAndFeel_First <= selectedId && UmsciSettingsOption::LookAndFeel_Last >= selectedId)
428 handleSettingsLookAndFeelMenuResult(selectedId);
429 else if (UmsciSettingsOption::ControlColour_First <= selectedId && UmsciSettingsOption::ControlColour_Last >= selectedId)
430 handleSettingsControlColourMenuResult(selectedId);
431 else if (UmsciSettingsOption::ConnectionSettings == selectedId)
432 showConnectionSettings();
433 else if (UmsciSettingsOption::FullscreenWindowMode == selectedId)
434 handleSettingsFullscreenModeToggleResult();
435 else if (UmsciSettingsOption::ControlFormat_First <= selectedId && UmsciSettingsOption::ControlFormat_Last >= selectedId)
436 handleSettingsControlFormatMenuResult(selectedId);
437 else if (UmsciSettingsOption::UpmixSettings == selectedId)
438 showUpmixSettings();
440 showExternalControlSettings();
441 else if (UmsciSettingsOption::ControlSize_First <= selectedId && UmsciSettingsOption::ControlSize_Last >= selectedId)
442 handleSettingsControlSizeMenuResult(selectedId);
443 else
444 jassertfalse; // unhandled menu entry!?
445}
446
447void MainComponent::handleSettingsControlFormatMenuResult(int selectedId)
448{
449 // helper internal function to avoid code clones
450 std::function<void(int, int, int, int, int, int, int, int, int, int)> setSettingsItemsCheckState = [=](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
451 m_settingsItems[UmsciSettingsOption::ControlFormat_Stereo].second = a;
452 m_settingsItems[UmsciSettingsOption::ControlFormat_LRS].second = b;
453 m_settingsItems[UmsciSettingsOption::ControlFormat_LCRS].second = c;
454 m_settingsItems[UmsciSettingsOption::ControlFormat_5point0].second = d;
455 m_settingsItems[UmsciSettingsOption::ControlFormat_5point1].second = e;
456 m_settingsItems[UmsciSettingsOption::ControlFormat_5point1point2].second = f;
457 m_settingsItems[UmsciSettingsOption::ControlFormat_7point0].second = g;
458 m_settingsItems[UmsciSettingsOption::ControlFormat_7point1].second = h;
459 m_settingsItems[UmsciSettingsOption::ControlFormat_7point1point4].second = i;
460 m_settingsItems[UmsciSettingsOption::ControlFormat_9point1point6].second = j;
461 };
462
463 switch (selectedId)
464 {
466 setSettingsItemsCheckState(1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
467 if (m_controlComponent)
468 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::stereo());
469 break;
471 setSettingsItemsCheckState(0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
472 if (m_controlComponent)
473 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::createLRS());
474 break;
476 setSettingsItemsCheckState(0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
477 if (m_controlComponent)
478 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::createLCRS());
479 break;
481 setSettingsItemsCheckState(0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
482 if (m_controlComponent)
483 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create5point0());
484 break;
486 setSettingsItemsCheckState(0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
487 if (m_controlComponent)
488 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create5point1());
489 break;
491 setSettingsItemsCheckState(0, 0, 0, 0, 0, 1, 0, 0, 0, 0);
492 if (m_controlComponent)
493 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create5point1point2());
494 break;
496 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 1, 0, 0, 0);
497 if (m_controlComponent)
498 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create7point0());
499 break;
501 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 1, 0, 0);
502 if (m_controlComponent)
503 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create7point1());
504 break;
506 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
507 if (m_controlComponent)
508 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create7point1point4());
509 break;
511 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
512 if (m_controlComponent)
513 m_controlComponent->setUpmixChannelConfiguration(juce::AudioChannelSet::create9point1point6());
514 break;
515 default:
516 jassertfalse; // unknown id fed in unintentionally ?!
517 break;
518 }
519
520 resized();
521}
522
523void MainComponent::handleSettingsControlSizeMenuResult(int selectedId)
524{
525 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int s, int m, int l) {
526 m_settingsItems[UmsciSettingsOption::ControlSize_S].second = s;
527 m_settingsItems[UmsciSettingsOption::ControlSize_M].second = m;
528 m_settingsItems[UmsciSettingsOption::ControlSize_L].second = l;
529 };
530
531 switch (selectedId)
532 {
534 setSettingsItemsCheckState(1, 0, 0);
535 if (m_controlComponent)
536 m_controlComponent->setControlsSize(UmsciPaintNControlComponentBase::ControlsSize::S);
537 break;
539 setSettingsItemsCheckState(0, 1, 0);
540 if (m_controlComponent)
541 m_controlComponent->setControlsSize(UmsciPaintNControlComponentBase::ControlsSize::M);
542 break;
544 setSettingsItemsCheckState(0, 0, 1);
545 if (m_controlComponent)
546 m_controlComponent->setControlsSize(UmsciPaintNControlComponentBase::ControlsSize::L);
547 break;
548 default:
549 jassertfalse;
550 break;
551 }
552}
553
554void MainComponent::handleSettingsLookAndFeelMenuResult(int selectedId)
555{
556 // helper internal function to avoid code clones
557 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int a, int b, int c) {
558 m_settingsItems[UmsciSettingsOption::LookAndFeel_FollowHost].second = a;
559 m_settingsItems[UmsciSettingsOption::LookAndFeel_Dark].second = b;
560 m_settingsItems[UmsciSettingsOption::LookAndFeel_Light].second = c;
561 };
562
563 switch (selectedId)
564 {
566 setSettingsItemsCheckState(1, 0, 0);
567 if (onPaletteStyleChange && m_settingsHostLookAndFeelId != -1)
568 onPaletteStyleChange(m_settingsHostLookAndFeelId, false);
569 break;
571 setSettingsItemsCheckState(0, 1, 0);
573 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Dark, false);
574 break;
576 setSettingsItemsCheckState(0, 0, 1);
578 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Light, false);
579 break;
580 default:
581 jassertfalse; // unknown id fed in unintentionally ?!
582 break;
583 }
584}
585
586void MainComponent::handleSettingsControlColourMenuResult(int selectedId)
587{
588 // helper internal function to avoid code clones
589 std::function<void(int, int, int, int, int)> setSettingsItemsCheckState = [=](int green, int red, int blue, int pink, int laser) {
590 m_settingsItems[UmsciSettingsOption::ControlColour_Green].second = green;
591 m_settingsItems[UmsciSettingsOption::ControlColour_Red].second = red;
592 m_settingsItems[UmsciSettingsOption::ControlColour_Blue].second = blue;
593 m_settingsItems[UmsciSettingsOption::ControlColour_Pink].second = pink;
594 m_settingsItems[UmsciSettingsOption::ControlColour_Laser].second = laser;
595 };
596
597 switch (selectedId)
598 {
600 setSettingsItemsCheckState(1, 0, 0, 0, 0);
601 setControlColour(juce::Colours::forestgreen);
602 break;
604 setSettingsItemsCheckState(0, 1, 0, 0, 0);
605 setControlColour(juce::Colours::orangered);
606 break;
608 setSettingsItemsCheckState(0, 0, 1, 0, 0);
609 setControlColour(juce::Colours::dodgerblue);
610 break;
612 setSettingsItemsCheckState(0, 0, 0, 1, 0);
613 setControlColour(juce::Colours::deeppink);
614 break;
616 setSettingsItemsCheckState(0, 0, 0, 0, 1);
617 setControlColour(juce::Colour(0xd1, 0xff, 0x4f));
618 break;
619 default:
620 jassertfalse; // unknown id fed in unintentionally ?!
621 break;
622 }
623}
624
625void MainComponent::handleSettingsFullscreenModeToggleResult()
626{
627 toggleFullscreenMode();
628}
629
630void MainComponent::toggleFullscreenMode()
631{
632 auto enabled = isFullscreenEnabled();
634 onSetFullscreenWindow(!enabled);
635}
636
637void MainComponent::showConnectionSettings()
638{
639 m_messageBox = std::make_unique<juce::AlertWindow>(
640 "Control connection settings",
641 "Info: This machine uses IP " + juce::IPAddress::getLocalAddress().toString(),
642 juce::MessageBoxIconType::NoIcon);
643
644 auto currentOCP1connPar = DeviceController::getInstance()->getConnectionParameters();
645
646 m_messageBox->addTextBlock("\nOCA/OCP.1 connection parameters:");
647
648 m_zeroconfDiscoverComboComponent = std::make_unique<UmsciZeroconfDiscoverComboComponent>();
649 m_zeroconfDiscoverComboComponent->setSize(10, 26); // width is set dynamically via parentSizeChanged()
650 m_zeroconfDiscoverComboComponent->onServiceSelected = [this](const ZeroconfSearcher::ZeroconfSearcher::ServiceInfo& service) {
651 if (auto* ed = m_messageBox->getTextEditor("Device IP"))
652 ed->setText(juce::String(service.ip), juce::sendNotification);
653 if (auto* ed = m_messageBox->getTextEditor("Device port"))
654 ed->setText(juce::String(service.port), juce::sendNotification);
655 auto matrixSizeIt = service.txtRecords.find("db_matrixSize");
656 if (matrixSizeIt != service.txtRecords.end())
657 if (auto* ed = m_messageBox->getTextEditor("Device IO size"))
658 ed->setText(juce::String(matrixSizeIt->second), juce::sendNotification);
659 };
660 m_messageBox->addCustomComponent(m_zeroconfDiscoverComboComponent.get());
661
662 m_messageBox->addTextEditor("Device IP", std::get<0>(currentOCP1connPar).toString(), "OCP.1 IP");
663 m_messageBox->addTextEditor("Device port", juce::String(std::get<1>(currentOCP1connPar)), "OCP.1 port");
664 m_messageBox->addTextEditor("Device IO size", juce::String(m_controlComponent->getOcp1IOSize().first) + "x" + juce::String(m_controlComponent->getOcp1IOSize().second), "OCP.1 IOSize");
665
666 m_messageBox->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
667 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
668 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
669 if (returnValue == 1)
670 {
671 auto ocp1IP = juce::IPAddress(m_messageBox->getTextEditorContents("Device IP"));
672 auto ocp1Port = m_messageBox->getTextEditorContents("Device port").getIntValue();
673 auto ocp1IOsize = m_messageBox->getTextEditorContents("Device IO size");
674
675 if (m_connectingComponent)
676 m_connectingComponent->setConnectionParameters(ocp1IP, ocp1Port);
677
678 m_controlComponent->setOcp1IOSize({ ocp1IOsize.upToFirstOccurrenceOf("x", false, true).getIntValue(), ocp1IOsize.fromLastOccurrenceOf("x", false, true).getIntValue() });
679 DeviceController::getInstance()->setConnectionParameters(ocp1IP, ocp1Port);
680
681 if (m_config)
682 m_config->triggerConfigurationDump();
683 }
684
685 m_zeroconfDiscoverComboComponent.reset();
686 m_messageBox.reset();
687 }));
688}
689
690void MainComponent::showUpmixSettings()
691{
692 m_messageBox = std::make_unique<juce::AlertWindow>(
693 "Upmix control settings",
694 "Configure the upmix overlay control settings.",
695 juce::MessageBoxIconType::NoIcon);
696
697 juce::StringArray formatItems;
698 int currentFormatIndex = 0;
700 {
701 formatItems.add(m_settingsItems[i].first);
702 if (m_settingsItems[i].second == 1)
703 currentFormatIndex = i - UmsciSettingsOption::ControlFormat_First;
704 }
705 m_messageBox->addComboBox("Control format", formatItems, "Channel format");
706 if (auto* combo = m_messageBox->getComboBoxComponent("Control format"))
707 combo->setSelectedItemIndex(currentFormatIndex, juce::dontSendNotification);
708
709 juce::StringArray liveModeItems;
710 liveModeItems.add("Manual (double-click to apply)");
711 liveModeItems.add("Live (apply changes immediately)");
712 m_messageBox->addComboBox("Live mode", liveModeItems, "Control mode");
713 if (auto* combo = m_messageBox->getComboBoxComponent("Live mode"))
714 combo->setSelectedItemIndex(m_controlComponent->getUpmixLiveMode() ? 1 : 0,
715 juce::dontSendNotification);
716
717 juce::StringArray shapeItems;
718 shapeItems.add("Circle");
719 shapeItems.add("Rectangle");
720 m_messageBox->addComboBox("Shape", shapeItems, "Indicator shape");
721 if (auto* combo = m_messageBox->getComboBoxComponent("Shape"))
722 combo->setSelectedItemIndex(m_controlComponent->getUpmixShape() == UmsciUpmixIndicatorPaintNControlComponent::IndicatorShape::Rectangle ? 1 : 0,
723 juce::dontSendNotification);
724
725 m_messageBox->addTextEditor("Start soundobject ID",
726 juce::String(m_controlComponent->getUpmixSourceStartId()),
727 "First soundobject");
728
729 juce::StringArray showSourcesItems;
730 showSourcesItems.add("All");
731 showSourcesItems.add("Upmix controlled only");
732 m_messageBox->addComboBox("Show sources", showSourcesItems, "Visible soundobjects");
733 if (auto* combo = m_messageBox->getComboBoxComponent("Show sources"))
734 combo->setSelectedItemIndex(m_controlComponent->getShowAllSources() ? 0 : 1,
735 juce::dontSendNotification);
736
737 m_messageBox->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
738 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
739 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
740 if (returnValue == 1)
741 {
742 if (auto* combo = m_messageBox->getComboBoxComponent("Control format"))
743 handleSettingsControlFormatMenuResult(UmsciSettingsOption::ControlFormat_First + combo->getSelectedItemIndex());
744 if (auto* combo = m_messageBox->getComboBoxComponent("Live mode"))
745 m_controlComponent->setUpmixLiveMode(combo->getSelectedItemIndex() == 1);
746 if (auto* combo = m_messageBox->getComboBoxComponent("Shape"))
747 m_controlComponent->setUpmixShape(combo->getSelectedItemIndex() == 1
749 : UmsciUpmixIndicatorPaintNControlComponent::IndicatorShape::Circle);
750 auto startId = m_messageBox->getTextEditorContents("Start soundobject ID").getIntValue();
751 m_controlComponent->setUpmixSourceStartId(startId);
752 if (auto* combo = m_messageBox->getComboBoxComponent("Show sources"))
753 m_controlComponent->setShowAllSources(combo->getSelectedItemIndex() == 0);
754 if (m_config)
755 m_config->triggerConfigurationDump();
756 }
757 m_messageBox.reset();
758 }));
759}
760
761void MainComponent::setControlColour(const juce::Colour& controlColour)
762{
763 m_controlColour = controlColour;
764
766
767 if (m_connectingComponent)
768 m_connectingComponent->lookAndFeelChanged();
769}
770
771void MainComponent::applyControlColour()
772{
773 auto customLookAndFeel = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getLookAndFeel());
774 if (customLookAndFeel)
775 {
776 switch (customLookAndFeel->getPaletteStyle())
777 {
778 case JUCEAppBasics::CustomLookAndFeel::PS_Light:
779 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_controlColour.brighter());
780 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_controlColour);
781 break;
782 case JUCEAppBasics::CustomLookAndFeel::PS_Dark:
783 default:
784 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_controlColour.darker());
785 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_controlColour);
786 break;
787 }
788 }
789}
790
791bool MainComponent::keyPressed(const juce::KeyPress& key)
792{
793 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
794 if (key == juce::KeyPress(juce::KeyPress::F11Key, juce::ModifierKeys::ctrlModifier, 0) ||
795 key == juce::KeyPress('f', juce::ModifierKeys::commandModifier | juce::ModifierKeys::ctrlModifier, 0))
796 {
797 toggleFullscreenMode();
798 return true;
799 }
800 return false;
801}
802
804{
805 if (m_config)
806 {
807 // control config
808 if (m_controlComponent)
809 {
810 auto controlConfigXmlElement = m_controlComponent->createStateXml();
811 if (controlConfigXmlElement)
812 m_config->setConfigState(std::move(controlConfigXmlElement), UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLCONFIG));
813 }
814
815 // connection config
816 auto connectionConfigXmlElement = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONNECTIONCONFIG));
817 auto params = DeviceController::getInstance()->getConnectionParameters();
818 connectionConfigXmlElement->setAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::ENABLED), (m_connectionToggleButton ? (m_connectionToggleButton->getToggleState() ? 1 : 0) : 0));
819 connectionConfigXmlElement->setAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::IP), std::get<0>(params).toString());
820 connectionConfigXmlElement->setAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::PORT), std::get<1>(params));
821 m_config->setConfigState(std::move(connectionConfigXmlElement), UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONNECTIONCONFIG));
822
823 // visu config
824 auto visuConfigXmlElement = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::VISUCONFIG));
825
826 auto lookAndFeelXmlElmement = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::LOOKANDFEEL));
828 {
829 if (m_settingsItems[i].second == 1)
830 lookAndFeelXmlElmement->addTextElement(juce::String(i));
831 }
832 visuConfigXmlElement->addChildElement(lookAndFeelXmlElmement.release());
833
834 auto controlColourXmlElmement = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLCOLOUR));
836 {
837 if (m_settingsItems[i].second == 1)
838 controlColourXmlElmement->addTextElement(juce::String(i));
839 }
840 visuConfigXmlElement->addChildElement(controlColourXmlElmement.release());
841
842 auto controlFormatXmlElmement = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLFORMAT));
844 {
845 if (m_settingsItems[i].second == 1)
846 controlFormatXmlElmement->addTextElement(juce::String(i));
847 }
848 visuConfigXmlElement->addChildElement(controlFormatXmlElmement.release());
849
850 auto controlSizeXmlElement = std::make_unique<juce::XmlElement>(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLSIZE));
852 {
853 if (m_settingsItems[i].second == 1)
854 controlSizeXmlElement->addTextElement(juce::String(i));
855 }
856 visuConfigXmlElement->addChildElement(controlSizeXmlElement.release());
857
858 m_config->setConfigState(std::move(visuConfigXmlElement), UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::VISUCONFIG));
859
860 // upmix config
861 auto upmixConfigXmlElement = std::make_unique<juce::XmlElement>(
863 upmixConfigXmlElement->setAttribute(
865 (m_controlComponent ? m_controlComponent->getUpmixSourceStartId() : 1));
866 upmixConfigXmlElement->setAttribute(
868 (m_controlComponent ? (m_controlComponent->getUpmixLiveMode() ? 1 : 0) : 0));
869 upmixConfigXmlElement->setAttribute(
872 m_controlComponent ? m_controlComponent->getUpmixShape()
874 upmixConfigXmlElement->setAttribute(
876 (m_controlComponent ? (m_controlComponent->getShowAllSources() ? 1 : 0) : 1));
877 upmixConfigXmlElement->addTextElement(UpmixSnapshot{
878 m_controlComponent ? m_controlComponent->getUpmixRot() : 0.0f,
879 m_controlComponent ? m_controlComponent->getUpmixTrans() : 1.0f,
880 m_controlComponent ? m_controlComponent->getUpmixHeightTrans() : 0.6f,
881 m_controlComponent ? m_controlComponent->getUpmixAngleStretch() : 1.0f,
882 m_controlComponent ? m_controlComponent->getUpmixOffsetX() : 0.0f,
883 m_controlComponent ? m_controlComponent->getUpmixOffsetY() : 0.0f
884 }.toString());
885
886 m_config->setConfigState(std::move(upmixConfigXmlElement),
888
889 // upmix snapshot (optional — only written when a snapshot has been stored)
890 if (m_upmixSnapshot.has_value())
891 {
892 const auto& s = *m_upmixSnapshot;
893 auto snapshotXmlElement = std::make_unique<juce::XmlElement>(
895 snapshotXmlElement->addTextElement(s.toString());
896 m_config->setConfigState(std::move(snapshotXmlElement),
898 }
899
900 // external control (MIDI) config
901 static const UmsciAppConfiguration::TagID midiParamTagIds[] = {
908 };
909
910 auto extCtrlXmlElement = std::make_unique<juce::XmlElement>(
912
913 auto midiDeviceXmlElement = std::make_unique<juce::XmlElement>(
915 midiDeviceXmlElement->addTextElement(m_midiController->getDeviceIdentifier());
916 extCtrlXmlElement->addChildElement(midiDeviceXmlElement.release());
917
919 {
920 auto assiXmlElement = std::make_unique<juce::XmlElement>(
921 UmsciAppConfiguration::getTagName(midiParamTagIds[i]));
922 assiXmlElement->addTextElement(m_midiController->getAssignment(
923 static_cast<UmsciExternalControlComponent::UpmixMidiParam>(i)).serializeToHexString());
924 extCtrlXmlElement->addChildElement(assiXmlElement.release());
925 }
926
927 auto oscPortXmlElement = std::make_unique<juce::XmlElement>(
929 oscPortXmlElement->addTextElement(juce::String(m_oscController->getPort()));
930 extCtrlXmlElement->addChildElement(oscPortXmlElement.release());
931
932 static const UmsciAppConfiguration::TagID oscParamTagIds[] = {
939 };
940
942 {
943 auto addrXmlElement = std::make_unique<juce::XmlElement>(
944 UmsciAppConfiguration::getTagName(oscParamTagIds[i]));
945 addrXmlElement->addTextElement(m_oscController->getAddress(
947 extCtrlXmlElement->addChildElement(addrXmlElement.release());
948 }
949
950 m_config->setConfigState(std::move(extCtrlXmlElement),
952 }
953}
954
956{
957 // control config
958 auto controlConfigState = m_config->getConfigState(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLCONFIG));
959 if (controlConfigState && m_controlComponent)
960 m_controlComponent->setStateXml(controlConfigState.get());
961
962 // connection config
963 auto connectionConfigState = m_config->getConfigState(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONNECTIONCONFIG));
964 if (connectionConfigState)
965 {
966 auto ocp1ConnectionEnabled = 1 == connectionConfigState->getIntAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::ENABLED));
967 auto ocp1IP = juce::IPAddress(connectionConfigState->getStringAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::IP)));
968 auto ocp1Port = connectionConfigState->getIntAttribute(UmsciAppConfiguration::getAttributeName(UmsciAppConfiguration::AttributeID::PORT));
969
970 if (m_connectingComponent)
971 m_connectingComponent->setConnectionParameters(ocp1IP, ocp1Port);
972
973 auto ocp1ConParams = DeviceController::getInstance()->getConnectionParameters();
974 if (std::get<0>(ocp1ConParams) != ocp1IP || std::get<1>(ocp1ConParams) != ocp1Port)
975 {
976 DeviceController::getInstance()->setConnectionParameters(ocp1IP, ocp1Port);
977 }
978
979 if (ocp1ConnectionEnabled != m_connectionToggleButton->getToggleState())
980 {
981 m_connectionToggleButton->setToggleState(ocp1ConnectionEnabled, juce::dontSendNotification);
982 if (ocp1ConnectionEnabled)
983 DeviceController::getInstance()->connect();
984 else
985 DeviceController::getInstance()->disconnect();
986 }
987 }
988
989 // visu config
990 auto visuConfigState = m_config->getConfigState(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::VISUCONFIG));
991 if (visuConfigState)
992 {
993 auto lookAndFeelXmlElement = visuConfigState->getChildByName(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::LOOKANDFEEL));
994 if (lookAndFeelXmlElement)
995 {
996 auto lookAndFeelSettingsOptionId = lookAndFeelXmlElement->getAllSubText().getIntValue();
997 handleSettingsLookAndFeelMenuResult(lookAndFeelSettingsOptionId);
998 }
999
1000 auto controlColourXmlElement = visuConfigState->getChildByName(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLCOLOUR));
1001 if (controlColourXmlElement)
1002 {
1003 auto controlColourSettingsOptionId = controlColourXmlElement->getAllSubText().getIntValue();
1004 handleSettingsControlColourMenuResult(controlColourSettingsOptionId);
1005 }
1006
1007 auto controlFormatXmlElement = visuConfigState->getChildByName(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLFORMAT));
1008 if (controlFormatXmlElement)
1009 {
1010 auto controlFormatSettingsOptionId = controlFormatXmlElement->getAllSubText().getIntValue();
1011 handleSettingsControlFormatMenuResult(controlFormatSettingsOptionId);
1012 }
1013
1014 auto controlSizeXmlElement = visuConfigState->getChildByName(UmsciAppConfiguration::getTagName(UmsciAppConfiguration::TagID::CONTROLSIZE));
1015 if (controlSizeXmlElement)
1016 {
1017 auto controlSizeSettingsOptionId = controlSizeXmlElement->getAllSubText().getIntValue();
1018 handleSettingsControlSizeMenuResult(controlSizeSettingsOptionId);
1019 }
1020 }
1021
1022 // upmix config
1023 auto upmixConfigState = m_config->getConfigState(
1025 if (upmixConfigState && m_controlComponent)
1026 {
1027 auto startId = upmixConfigState->getIntAttribute(
1029 m_controlComponent->setUpmixSourceStartId(startId);
1030 auto showAllSources = upmixConfigState->getIntAttribute(
1032 m_controlComponent->setShowAllSources(showAllSources);
1033 auto liveMode = upmixConfigState->getIntAttribute(
1035 m_controlComponent->setUpmixLiveMode(liveMode);
1037 upmixConfigState->getStringAttribute(
1039 m_controlComponent->setUpmixShape(upmixShape);
1040 auto upmixParams = UpmixSnapshot::fromString(upmixConfigState->getAllSubText());
1041 m_controlComponent->setUpmixTransform(upmixParams.rot, upmixParams.scale,
1042 upmixParams.heightScale, upmixParams.angleStretch);
1043 m_controlComponent->setUpmixOffset(upmixParams.offsetX, upmixParams.offsetY);
1044 }
1045
1046 // upmix snapshot (optional — absent in config means no snapshot stored)
1047 auto upmixSnapshotState = m_config->getConfigState(
1049 if (upmixSnapshotState)
1050 {
1051 m_upmixSnapshot = UpmixSnapshot::fromString(upmixSnapshotState->getAllSubText());
1052 if (m_upmixSnapshotRecallButton)
1053 m_upmixSnapshotRecallButton->setEnabled(true);
1054 }
1055
1056 // external control (MIDI) config
1057 auto extCtrlState = m_config->getConfigState(
1059 if (extCtrlState)
1060 {
1061 if (auto* midiDevXml = extCtrlState->getChildByName(
1063 m_midiController->openDevice(midiDevXml->getAllSubText());
1064
1065 static const UmsciAppConfiguration::TagID midiParamTagIds[] = {
1072 };
1073
1075 {
1076 if (auto* assiXml = extCtrlState->getChildByName(
1077 UmsciAppConfiguration::getTagName(midiParamTagIds[i])))
1078 {
1079 auto hexStr = assiXml->getAllSubText();
1080 if (hexStr.isNotEmpty())
1081 {
1082 JUCEAppBasics::MidiCommandRangeAssignment assi;
1083 assi.deserializeFromHexString(hexStr);
1084 m_midiController->setAssignment(
1085 static_cast<UmsciExternalControlComponent::UpmixMidiParam>(i), assi);
1086 }
1087 }
1088 }
1089
1090 if (auto* portXml = extCtrlState->getChildByName(
1092 m_oscController->openPort(portXml->getAllSubText().getIntValue());
1093
1094 static const UmsciAppConfiguration::TagID oscParamTagIds[] = {
1101 };
1102
1104 {
1105 if (auto* addrXml = extCtrlState->getChildByName(
1106 UmsciAppConfiguration::getTagName(oscParamTagIds[i])))
1107 {
1108 auto addr = addrXml->getAllSubText();
1109 if (addr.isNotEmpty())
1110 m_oscController->setAddress(
1111 static_cast<UmsciExternalControlComponent::UpmixMidiParam>(i), addr);
1112 }
1113 }
1114 }
1115}
1116
1118{
1119#if JUCE_WINDOWS
1120 return juce::Desktop::getInstance().getKioskModeComponent() != nullptr;
1121#elif JUCE_MAC
1122 if (auto* topLevel = getTopLevelComponent())
1123 if (auto* peer = topLevel->getPeer())
1124 return peer->isFullScreen();
1125
1126 jassertfalse;
1127 return false;
1128#endif
1129}
1130
1131void MainComponent::showExternalControlSettings()
1132{
1133 m_messageBox = std::make_unique<juce::AlertWindow>(
1134 "External control...",
1135 "Configure MIDI and OSC assignments for upmix transform parameters.",
1136 juce::MessageBoxIconType::NoIcon);
1137
1138 m_externalControlComponent = std::make_unique<UmsciExternalControlComponent>();
1139 m_externalControlComponent->setMidiInputDeviceIdentifier(m_midiController->getDeviceIdentifier());
1141 m_externalControlComponent->setMidiAssi(
1143 m_midiController->getAssignment(static_cast<UmsciExternalControlComponent::UpmixMidiParam>(i)));
1144 m_externalControlComponent->setOscInputPort(m_oscController->getPort());
1146 m_externalControlComponent->setOscAddr(
1148 m_oscController->getAddress(static_cast<UmsciExternalControlComponent::UpmixMidiParam>(i)));
1149 m_messageBox->addCustomComponent(m_externalControlComponent.get());
1150
1151 m_messageBox->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
1152 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
1153 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
1154 if (returnValue == 1)
1155 {
1156 m_midiController->openDevice(m_externalControlComponent->getMidiInputDeviceIdentifier());
1158 m_midiController->setAssignment(
1160 m_externalControlComponent->getMidiAssi(
1162 m_oscController->openPort(m_externalControlComponent->getOscInputPort());
1164 m_oscController->setAddress(
1166 m_externalControlComponent->getOscAddr(
1168 if (m_config)
1169 m_config->triggerConfigurationDump();
1170 }
1171 m_externalControlComponent.reset();
1172 m_messageBox.reset();
1173 }));
1174}
1175
1176void MainComponent::applyUpmixParamValue(UmsciExternalControlComponent::UpmixMidiParam param,
1177 float domainValue)
1178{
1179 if (!m_controlComponent)
1180 return;
1181
1182 switch (param)
1183 {
1185 m_controlComponent->setUpmixTransform(domainValue,
1186 m_controlComponent->getUpmixTrans(),
1187 m_controlComponent->getUpmixHeightTrans(),
1188 m_controlComponent->getUpmixAngleStretch());
1189 break;
1191 m_controlComponent->setUpmixTransform(m_controlComponent->getUpmixRot(),
1192 domainValue,
1193 m_controlComponent->getUpmixHeightTrans(),
1194 m_controlComponent->getUpmixAngleStretch());
1195 break;
1197 m_controlComponent->setUpmixTransform(m_controlComponent->getUpmixRot(),
1198 m_controlComponent->getUpmixTrans(),
1199 domainValue,
1200 m_controlComponent->getUpmixAngleStretch());
1201 break;
1203 m_controlComponent->setUpmixTransform(m_controlComponent->getUpmixRot(),
1204 m_controlComponent->getUpmixTrans(),
1205 m_controlComponent->getUpmixHeightTrans(),
1206 domainValue);
1207 break;
1209 m_controlComponent->setUpmixOffset(domainValue, m_controlComponent->getUpmixOffsetY());
1210 break;
1212 m_controlComponent->setUpmixOffset(m_controlComponent->getUpmixOffsetX(), domainValue);
1213 break;
1214 default:
1215 break;
1216 }
1217
1218 // Fire live-mode position updates and trigger config persistence via the
1219 // callback chain: notifyTransformChanged → onTransformChanged → onUpmixTransformChanged
1220 // → triggerConfigurationDump. This mirrors what the interactive drag handlers do.
1221 m_controlComponent->triggerUpmixTransformApplied();
1222}
1223
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.
void paint(juce::Graphics &g) override
void performConfigurationDump() override
UmsciAppConfiguration::Dumper — serialises all current settings to XML.
void resized() override
void applySettingsOption(const UmsciSettingsOption &option)
Applies a single settings option (called from both menu handlers and config restore).
std::function< void(int, bool)> onPaletteStyleChange
Fired when the look-and-feel palette changes, so the host application (if this is used as a plugin) c...
void lookAndFeelChanged() override
UmsciSettingsOption
Enumerates every user-selectable setting exposed via the settings menu.
@ UpmixSettings
Opens the upmix settings dialog.
@ ControlSize_L
Large icons.
@ ControlSize_M
Medium icons.
@ FullscreenWindowMode
Toggles fullscreen / windowed.
@ LookAndFeel_Light
Force light colour scheme.
@ ControlColour_Laser
Bright laser-style highlight.
@ ExternalControlSettings
Opens the external (MIDI) control settings dialog.
@ ConnectionSettings
Opens the connection settings dialog.
@ LookAndFeel_FollowHost
Inherit host application L&F.
@ ControlColour_Green
Green source icons.
@ LookAndFeel_Dark
Force dark colour scheme.
@ ControlSize_S
Small icons.
bool keyPressed(const juce::KeyPress &key) override
Handles the Escape key (exits fullscreen) and F key (toggles fullscreen).
void onConfigUpdated() override
UmsciAppConfiguration::Watcher — called when the config XML changes on disk.
std::function< void(bool)> onSetFullscreenWindow
Fired when the user requests fullscreen mode; the host window applies it.
~MainComponent() override
@ IP
String: target device IP address.
@ UPMIXLIVEMODE
Boolean: follow live DS100 source positions.
@ ENABLED
Boolean: whether the connection is active.
@ UPMIXSHOWALLSOURCES
Boolean: render all sources or only upmix group.
@ UPMIXSHAPE
String: "Circle" or "Rectangle".
@ PORT
Integer: target OCP.1 port (default 50014).
@ UPMIXSOURCESTARTID
Integer: 1-based first DS100 channel for upmix inputs.
static juce::String getTagName(TagID ID)
static juce::String getAttributeName(AttributeID ID)
TagID
Enumerates every XML element tag used in the config file.
@ MIDI_UPMIXOFFSETX
MIDI assignment for upmix X offset.
@ MIDI_UPMIXHEIGHTSCALE
MIDI assignment for upmix height translation.
@ CONTROLCONFIG
Control-component settings.
@ OSC_UPMIXHEIGHTSCALE
OSC address for upmix height translation.
@ CONNECTIONCONFIG
OCP.1 connection parameters.
@ OSC_UPMIXANGLESTRETCH
OSC address for upmix angle stretch.
@ MIDIINPUTDEVICE
Selected MIDI input device identifier.
@ UPMIXSNAPSHOTCONFIG
Upmix snapshot container (optional; absent = no snapshot stored).
@ CONTROLCOLOUR
Source/speaker icon colour.
@ OSC_UPMIXOFFSETX
OSC address for upmix X offset.
@ OSC_UPMIXROT
OSC address for upmix rotation.
@ MIDI_UPMIXSCALE
MIDI assignment for upmix translation (radial scale).
@ EXTERNALCONTROLCONFIG
External (MIDI) control assignments container.
@ CONTROLSIZE
Icon size (S/M/L).
@ MIDI_UPMIXOFFSETY
MIDI assignment for upmix Y offset.
@ CONTROLFORMAT
Upmix channel format.
@ MIDI_UPMIXANGLESTRETCH
MIDI assignment for upmix angle stretch.
@ OSC_UPMIXOFFSETY
OSC address for upmix Y offset.
@ OSCINPUTPORT
OSC UDP listen port.
@ MIDI_UPMIXROT
MIDI assignment for upmix rotation.
@ OSC_UPMIXSCALE
OSC address for upmix translation (radial scale).
@ LOOKANDFEEL
Look-and-feel variant.
@ UPMIXCONFIG
Upmix behaviour settings.
@ VISUCONFIG
Visual appearance settings.
@ Subscribing
AddSubscription commands sent, waiting for ACKs.
@ Connecting
TCP connect in progress.
@ Reading
GetValue responses being collected (DeviceController::GetValues state).
UpmixMidiParam
Identifies each controllable upmix transform parameter.
@ UpmixMidiParam_OffsetX
m_upmixOffsetX — ring centre X offset.
@ UpmixMidiParam_HeightTranslation
m_upmixHeightTrans — height ring fraction.
@ UpmixMidiParam_Translation
m_upmixTrans — radial scale factor.
@ UpmixMidiParam_OffsetY
m_upmixOffsetY — ring centre Y offset.
@ UpmixMidiParam_Rotation
m_upmixRot — ring rotation (−π – +π rad).
@ UpmixMidiParam_AngleStretch
m_upmixAngleStretch — angular spread.
The top layer of the UmsciControlComponent stack — renders an interactive upmix speaker ring and lets...
static IndicatorShape getShapeForName(const juce::String &name)