Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
MainComponent.cpp
Go to the documentation of this file.
1/* Copyright (c) 2025, Christian Ahrens
2 *
3 * This file is part of Mema <https://github.com/ChristianAhrens/Mema>
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
22#include "MemaReComponent.h"
26
27#include <AboutComponent.h>
28#include <CustomLookAndFeel.h>
29#include <WebUpdateDetector.h>
30
32
33#include <iOS_utils.h>
34
35
37 : juce::Component()
38{
39 // create the configuration object (is being initialized from disk automatically)
40 m_config = std::make_unique<MemaReAppConfiguration>(JUCEAppBasics::AppConfigurationBase::getDefaultConfigFilePath());
41 m_config->addDumper(this);
42
43 // check if config creation was able to read a valid config from disk...
44 if (!m_config->isValid())
45 {
46 m_config->ResetToDefault();
47 }
48
49 m_networkConnection = std::make_unique<InterprocessConnectionImpl>();
50 m_networkConnection->onConnectionMade = [=]() {
51 DBG(__FUNCTION__);
52
53 std::vector<Mema::SerializableMessage::SerializableMessageType> desiredTrafficTypes = {
60 m_networkConnection->sendMessage(std::make_unique<Mema::DataTrafficTypeSelectionMessage>(desiredTrafficTypes)->getSerializedMessage());
61
62 setStatus(Status::Monitoring);
63 };
64 m_networkConnection->onConnectionLost = [=]() {
65 DBG(__FUNCTION__);
66
67 if (m_remoteComponent)
68 m_remoteComponent->resetCtrl();
69
70 connectToMema();
71
72 setStatus(Status::Connecting);
73 };
74 m_networkConnection->onMessageReceived = [=](const juce::MemoryBlock& message) {
75 auto knownMessage = Mema::SerializableMessage::initFromMemoryBlock(message);
76 if (auto const epm = dynamic_cast<const Mema::EnvironmentParametersMessage*>(knownMessage))
77 {
78 m_settingsHostLookAndFeelId = epm->getPaletteStyle();
79 jassert(m_settingsHostLookAndFeelId >= JUCEAppBasics::CustomLookAndFeel::PS_Dark && m_settingsHostLookAndFeelId <= JUCEAppBasics::CustomLookAndFeel::PS_Light);
80
81 if (onPaletteStyleChange && !m_settingsItems[2].second && !m_settingsItems[3].second) // callback must be set and neither 2 nor 3 setting set (manual dark or light)
82 {
83 m_settingsItems[1].second = 1; // set ticked for setting 1 (follow host)
84 onPaletteStyleChange(m_settingsHostLookAndFeelId, false/*do not follow local style any more if a message was received via net once*/);
85 }
86 }
87 else if (m_remoteComponent && nullptr != knownMessage && Status::Monitoring == m_currentStatus)
88 {
89 m_remoteComponent->handleMessage(*knownMessage);
90 }
92 };
93
94 m_remoteComponent = std::make_unique<MemaReComponent>();
95 m_remoteComponent->onExitClick = [=]() {
96 setStatus(Status::Discovering);
97 };
98 m_remoteComponent->onMessageReadyToSend = [=](const juce::MemoryBlock& message) {
99 if (m_networkConnection)
100 m_networkConnection->sendMessage(message);
101 };
102 addAndMakeVisible(m_remoteComponent.get());
103
104 m_discoverComponent = std::make_unique<MemaClientDiscoverComponent>();
105 m_discoverComponent->setupServiceDiscovery(Mema::ServiceData::getServiceTypeUIDBase(), Mema::ServiceData::getRemoteServiceTypeUID());
106 m_discoverComponent->onServiceSelected = [=](const JUCEAppBasics::SessionMasterAwareService& selectedService) {
107 m_selectedService = selectedService;
108
109 connectToMema();
110
111 if (m_config)
112 m_config->triggerConfigurationDump(false);
113 };
114 addAndMakeVisible(m_discoverComponent.get());
115
116 m_connectingComponent = std::make_unique<MemaClientConnectingComponent>();
117 addAndMakeVisible(m_connectingComponent.get());
118
119 m_aboutComponent = std::make_unique<AboutComponent>(BinaryData::MemaReRect_png, BinaryData::MemaReRect_pngSize);
120 m_aboutButton = std::make_unique<juce::DrawableButton>("About", juce::DrawableButton::ButtonStyle::ImageFitted);
121 m_aboutButton->setTooltip(juce::String("About") + juce::JUCEApplication::getInstance()->getApplicationName());
122 m_aboutButton->onClick = [this] {
123 juce::PopupMenu aboutMenu;
124 aboutMenu.addCustomItem(1, std::make_unique<CustomAboutItem>(m_aboutComponent.get(), juce::Rectangle<int>(250, 250)), nullptr, juce::String("Info about") + juce::JUCEApplication::getInstance()->getApplicationName());
125 aboutMenu.showMenuAsync(juce::PopupMenu::Options());
126 };
127 m_aboutButton->setAlwaysOnTop(true);
128 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
129 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
130 addAndMakeVisible(m_aboutButton.get());
131
132 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
133#if JUCE_WINDOWS
134 auto fullscreenShortCutHint = std::string(" (Ctrl+F11)");
135#elif JUCE_MAC
136 auto fullscreenShortCutHint = std::string(" (Cmd+Ctrl+F)");
137#endif
138
139 // default lookandfeel is follow local, therefor none selected
140 m_settingsItems[MemaReSettingsOption::LookAndFeel_FollowHost] = std::make_pair("Follow Mema", 0);
141 m_settingsItems[MemaReSettingsOption::LookAndFeel_Dark] = std::make_pair("Dark", 1);
142 m_settingsItems[MemaReSettingsOption::LookAndFeel_Light] = std::make_pair("Light", 0);
143 // default output visu is normal meterbridge
144 m_settingsItems[MemaReSettingsOption::ControlFormat_RawChannels] = std::make_pair("Faderbank", 1);
145 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_LRS] = std::make_pair(juce::AudioChannelSet::createLRS().getDescription().toStdString(), 0);
146 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_LCRS] = std::make_pair(juce::AudioChannelSet::createLCRS().getDescription().toStdString(), 0);
147 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_5point0] = std::make_pair(juce::AudioChannelSet::create5point0().getDescription().toStdString(), 0);
148 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_5point1] = std::make_pair(juce::AudioChannelSet::create5point1().getDescription().toStdString(), 0);
149 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_5point1point2] = std::make_pair(juce::AudioChannelSet::create5point1point2().getDescription().toStdString(), 0);
150 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_7point0] = std::make_pair(juce::AudioChannelSet::create7point0().getDescription().toStdString(), 0);
151 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_7point1] = std::make_pair(juce::AudioChannelSet::create7point1().getDescription().toStdString(), 0);
152 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_7point1point4] = std::make_pair(juce::AudioChannelSet::create7point1point4().getDescription().toStdString(), 0);
153 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_9point1point6] = std::make_pair(juce::AudioChannelSet::create9point1point6().getDescription().toStdString(), 0);
154 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_Quadrophonic] = std::make_pair(juce::AudioChannelSet::quadraphonic().getDescription().toStdString(), 0);
155 m_settingsItems[MemaReSettingsOption::ControlFormat_PluginParameterControl] = std::make_pair("Plug-in parameter control", 1);
156 // default panning colour is green
157 m_settingsItems[MemaReSettingsOption::ControlColour_Green] = std::make_pair("Green", 1);
158 m_settingsItems[MemaReSettingsOption::ControlColour_Red] = std::make_pair("Red", 0);
159 m_settingsItems[MemaReSettingsOption::ControlColour_Blue] = std::make_pair("Blue", 0);
160 m_settingsItems[MemaReSettingsOption::ControlColour_Pink] = std::make_pair("Anni Pink", 0);
161 m_settingsItems[MemaReSettingsOption::ControlColour_Laser] = std::make_pair("Laser", 0);
162 // default controls size is S
163 m_settingsItems[MemaReSettingsOption::ControlsSize_S] = std::make_pair("S", 1);
164 m_settingsItems[MemaReSettingsOption::ControlsSize_M] = std::make_pair("M", 0);
165 m_settingsItems[MemaReSettingsOption::ControlsSize_L] = std::make_pair("L", 0);
166#if JUCE_WINDOWS || JUCE_MAC
167 // fullscreen toggling
168 m_settingsItems[MemaReSettingsOption::FullscreenWindowMode] = std::make_pair("Toggle fullscreen mode" + fullscreenShortCutHint, 0);
169#endif
170 // Further components
171 m_settingsButton = std::make_unique<juce::DrawableButton>("Settings", juce::DrawableButton::ButtonStyle::ImageFitted);
172 m_settingsButton->setTooltip(juce::String("Settings for") + juce::JUCEApplication::getInstance()->getApplicationName());
173 m_settingsButton->onClick = [this] {
174 juce::PopupMenu lookAndFeelSubMenu;
175 for (int i = MemaReSettingsOption::LookAndFeel_First; i <= MemaReSettingsOption::LookAndFeel_Last; i++)
176 lookAndFeelSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
177
178 juce::PopupMenu controlFormatSubMenu;
179 for (int i = MemaReSettingsOption::ControlFormat_First; i <= MemaReSettingsOption::ControlFormat_Last; i++)
180 controlFormatSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
181
182 juce::PopupMenu controlColourSubMenu;
183 for (int i = MemaReSettingsOption::ControlColour_First; i <= MemaReSettingsOption::ControlColour_Last; i++)
184 controlColourSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
185
186 juce::PopupMenu constrolsSizeSubMenu;
187 for (int i = MemaReSettingsOption::ControlsSize_First; i <= MemaReSettingsOption::ControlsSize_Last; i++)
188 constrolsSizeSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
189
190 juce::PopupMenu settingsMenu;
191 settingsMenu.addSubMenu("LookAndFeel", lookAndFeelSubMenu);
192 settingsMenu.addSubMenu("Control format", controlFormatSubMenu);
193 settingsMenu.addSubMenu("Control colour", controlColourSubMenu);
194 settingsMenu.addSubMenu("Controls size", constrolsSizeSubMenu);
195 settingsMenu.addSeparator();
196 settingsMenu.addItem(MemaReSettingsOption::ExternalControl, "External control...", true);
197#if JUCE_WINDOWS || JUCE_MAC
198 settingsMenu.addSeparator();
199 settingsMenu.addItem(MemaReSettingsOption::FullscreenWindowMode, m_settingsItems[MemaReSettingsOption::FullscreenWindowMode].first, true, false);
200#endif
201 settingsMenu.showMenuAsync(juce::PopupMenu::Options(), [=](int selectedId) {
202 handleSettingsMenuResult(selectedId);
203 if (m_config)
204 m_config->triggerConfigurationDump();
205 });
206 };
207 m_settingsButton->setAlwaysOnTop(true);
208 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
209 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
210 addAndMakeVisible(m_settingsButton.get());
211
212 m_disconnectButton = std::make_unique<juce::DrawableButton>("Disconnect", juce::DrawableButton::ButtonStyle::ImageFitted);
213 m_disconnectButton->setTooltip(juce::String("Disconnect ") + juce::JUCEApplication::getInstance()->getApplicationName() + " from " + (m_selectedService.description.isNotEmpty() ? m_selectedService.description : "Nothing :)"));
214 m_disconnectButton->onClick = [this] {
215 if (m_networkConnection)
216 m_networkConnection->disconnect();
217
218 if (m_remoteComponent)
219 m_remoteComponent->resetCtrl();
220
221 m_selectedService = {};
222 if (m_discoverComponent)
223 m_discoverComponent->resetServices();
224
225 if (m_config)
226 m_config->triggerConfigurationDump();
227
228 setStatus(Status::Discovering);
229 };
230 m_disconnectButton->setAlwaysOnTop(true);
231 m_disconnectButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
232 m_disconnectButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
233 addAndMakeVisible(m_disconnectButton.get());
234
235 if (juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui"))
236 {
237 m_aboutButton->setVisible(false);
238 m_settingsButton->setVisible(false);
239 m_disconnectButton->setVisible(false);
240 }
241
242#ifdef RUN_MESSAGE_TESTS
243 Mema::runTests();
244#endif
245
246 setSize(800, 600);
247
248#if defined JUCE_IOS
249 // iOS is updated via AppStore
250#define IGNORE_UPDATES
251#elif defined JUCE_ANDROID
252 // Android as well
253#define IGNORE_UPDATES
254#endif
255
256#if defined IGNORE_UPDATES
257#else
258 auto noUpdates = juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noupdates");
259 if (!noUpdates)
260 {
261 auto updater = JUCEAppBasics::WebUpdateDetector::getInstance();
262 updater->SetReferenceVersion(ProjectInfo::versionString);
263 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/mema/releases/latest");
264 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Mema/refs/heads/main/");
265 }
266#endif
267
268
269 // add this main component to watchers
270 m_config->addWatcher(this); // without initial update - that we have to do externally after lambdas were assigned
271
272 // we want keyboard focus for fullscreen toggle shortcut
273 setWantsKeyboardFocus(true);
274
275 JUCEAppBasics::iOS_utils::initialise([this] { resized(); });
276}
277
279{
280 JUCEAppBasics::iOS_utils::deinitialise();
281}
282
284{
285 auto safety = JUCEAppBasics::iOS_utils::getDeviceSafetyMargins();
286 auto safeBounds = getLocalBounds();
287 safeBounds.removeFromTop(safety._top);
288 safeBounds.removeFromBottom(safety._bottom);
289 safeBounds.removeFromLeft(safety._left);
290 safeBounds.removeFromRight(safety._right);
291
292 switch (m_currentStatus)
293 {
294 case Status::Monitoring:
295 m_connectingComponent->setVisible(false);
296 m_discoverComponent->setVisible(false);
297 m_remoteComponent->setVisible(true);
298 m_remoteComponent->setBounds(safeBounds);
299 break;
300 case Status::Connecting:
301 m_remoteComponent->setVisible(false);
302 m_discoverComponent->setVisible(false);
303 m_connectingComponent->setVisible(true);
304 m_connectingComponent->setBounds(safeBounds);
305 break;
306 case Status::Discovering:
307 default:
308 m_connectingComponent->setVisible(false);
309 m_remoteComponent->setVisible(false);
310 m_discoverComponent->setVisible(true);
311 m_discoverComponent->setBounds(safeBounds);
312 break;
313 }
314
315 if (!juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui"))
316 {
317 auto leftButtons = safeBounds.removeFromLeft(36);
318 auto rightButtons = safeBounds.removeFromLeft(36);
319 m_aboutButton->setBounds(leftButtons.removeFromTop(35).removeFromBottom(30));
320 m_settingsButton->setBounds(leftButtons.removeFromTop(35).removeFromBottom(30));
321 m_disconnectButton->setBounds(rightButtons.removeFromTop(35).removeFromBottom(30));
322 }
323}
324
325void MainComponent::paint(juce::Graphics& g)
326{
327 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
328}
329
331{
332 auto aboutButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::question_mark_24dp_svg).get());
333 aboutButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
334 m_aboutButton->setImages(aboutButtonDrawable.get());
335
336 auto settingsDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
337 settingsDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
338 m_settingsButton->setImages(settingsDrawable.get());
339
340 auto disconnectDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::link_off_24dp_svg).get());
341 disconnectDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
342 m_disconnectButton->setImages(disconnectDrawable.get());
343
344 applyControlColour();
345}
346
348{
349 // use the settings menu item call infrastructure to set the option
350 handleSettingsMenuResult(option);
351}
352
353void MainComponent::handleSettingsMenuResult(int selectedId)
354{
355 if (0 == selectedId)
356 return; // nothing selected, dismiss
358 handleSettingsLookAndFeelMenuResult(selectedId);
360 handleSettingsControlFormatMenuResult(selectedId);
362 handleSettingsControlColourMenuResult(selectedId);
364 handleSettingsControlsSizeMenuResult(selectedId);
366 showExternalControlSettings();
368 handleSettingsFullscreenModeToggleResult();
369 else
370 jassertfalse; // unhandled menu entry!?
371}
372
373void MainComponent::handleSettingsLookAndFeelMenuResult(int selectedId)
374{
375 // helper internal function to avoid code clones
376 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int a, int b, int c) {
377 m_settingsItems[MemaReSettingsOption::LookAndFeel_FollowHost].second = a;
378 m_settingsItems[MemaReSettingsOption::LookAndFeel_Dark].second = b;
379 m_settingsItems[MemaReSettingsOption::LookAndFeel_Light].second = c;
380 };
381
382 switch (selectedId)
383 {
386 if (onPaletteStyleChange && m_settingsHostLookAndFeelId != -1)
387 onPaletteStyleChange(m_settingsHostLookAndFeelId, false);
388 break;
392 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Dark, false);
393 break;
397 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Light, false);
398 break;
399 default:
400 jassertfalse; // unknown id fed in unintentionally ?!
401 break;
402 }
403}
404
405void MainComponent::handleSettingsControlFormatMenuResult(int selectedId)
406{
407 // helper internal function to avoid code clones
408 std::function<void(int, int, 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, int k, int l) {
409 m_settingsItems[MemaReSettingsOption::ControlFormat_RawChannels].second = a;
421 };
422
423 switch (selectedId)
424 {
426 setSettingsItemsCheckState(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
427 if (m_remoteComponent)
428 m_remoteComponent->setFaderbankCtrlActive();
429 break;
431 setSettingsItemsCheckState(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
432 if (m_remoteComponent)
433 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::createLRS());
434 break;
436 setSettingsItemsCheckState(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
437 if (m_remoteComponent)
438 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::createLCRS());
439 break;
441 setSettingsItemsCheckState(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
442 if (m_remoteComponent)
443 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create5point0());
444 break;
446 setSettingsItemsCheckState(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
447 if (m_remoteComponent)
448 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create5point1());
449 break;
451 setSettingsItemsCheckState(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
452 if (m_remoteComponent)
453 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create5point1point2());
454 break;
456 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
457 if (m_remoteComponent)
458 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create7point0());
459 break;
461 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0);
462 if (m_remoteComponent)
463 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create7point1());
464 break;
466 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);
467 if (m_remoteComponent)
468 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create7point1point4());
469 break;
471 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);
472 if (m_remoteComponent)
473 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create9point1point6());
474 break;
476 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
477 if (m_remoteComponent)
478 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::quadraphonic());
479 break;
481 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
482 if (m_remoteComponent)
483 m_remoteComponent->setPluginCtrlActive();
484 break;
485 default:
486 jassertfalse; // unknown id fed in unintentionally ?!
487 break;
488 }
489
490 resized();
491}
492
493void MainComponent::handleSettingsControlColourMenuResult(int selectedId)
494{
495 // helper internal function to avoid code clones
496 std::function<void(int, int, int, int, int)> setSettingsItemsCheckState = [=](int green, int red, int blue, int pink, int laser) {
497 m_settingsItems[MemaReSettingsOption::ControlColour_Green].second = green;
498 m_settingsItems[MemaReSettingsOption::ControlColour_Red].second = red;
499 m_settingsItems[MemaReSettingsOption::ControlColour_Blue].second = blue;
500 m_settingsItems[MemaReSettingsOption::ControlColour_Pink].second = pink;
501 m_settingsItems[MemaReSettingsOption::ControlColour_Laser].second = laser;
502 };
503
504 switch (selectedId)
505 {
507 setSettingsItemsCheckState(1, 0, 0, 0, 0);
508 setControlColour(juce::Colours::forestgreen);
509 break;
511 setSettingsItemsCheckState(0, 1, 0, 0, 0);
512 setControlColour(juce::Colours::orangered);
513 break;
515 setSettingsItemsCheckState(0, 0, 1, 0, 0);
516 setControlColour(juce::Colours::dodgerblue);
517 break;
519 setSettingsItemsCheckState(0, 0, 0, 1, 0);
520 setControlColour(juce::Colours::deeppink);
521 break;
523 setSettingsItemsCheckState(0, 0, 0, 0, 1);
524 setControlColour(juce::Colour(0xd1, 0xff, 0x4f));
525 break;
526 default:
527 jassertfalse; // unknown id fed in unintentionally ?!
528 break;
529 }
530}
531
532void MainComponent::handleSettingsControlsSizeMenuResult(int selectedId)
533{
534 // helper internal function to avoid code clones
535 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int small, int medium, int large) {
536 m_settingsItems[MemaReSettingsOption::ControlsSize_S].second = small;
537 m_settingsItems[MemaReSettingsOption::ControlsSize_M].second = medium;
538 m_settingsItems[MemaReSettingsOption::ControlsSize_L].second = large;
539 };
540
541 switch (selectedId)
542 {
546 break;
550 break;
554 break;
555 default:
556 jassertfalse; // unknown id fed in unintentionally ?!
557 break;
558 }
559}
560
561void MainComponent::handleSettingsFullscreenModeToggleResult()
562{
563 toggleFullscreenMode();
564}
565
566void MainComponent::toggleFullscreenMode()
567{
571}
572
573void MainComponent::showExternalControlSettings()
574{
575 m_messageBox = std::make_unique<juce::AlertWindow>(
576 "External control setup",
577 "Enter remote control parameters to externally connect to " + juce::JUCEApplication::getInstance()->getApplicationName() + " and control its parameters.\n" +
578 "Info: This machine uses IP " + juce::IPAddress::getLocalAddress().toString(),
579 juce::MessageBoxIconType::NoIcon);
580
581 m_messageBox->addTextBlock("\nADM-OSC connection parameters:");
582 if (m_remoteComponent)
583 {
584 auto admOscSettings = m_remoteComponent->getExternalAdmOscSettings();
585 m_messageBox->addTextEditor("ADM local port", juce::String(std::get<0>(admOscSettings)), "ADM-OSC port");
586 m_messageBox->addTextEditor("ADM remote IP", std::get<1>(admOscSettings).toString(), "Target IP");
587 m_messageBox->addTextEditor("ADM remote port", juce::String(std::get<2>(admOscSettings)), "Target port");
588 }
589
590 //m_messageBox->addTextBlock("\nOCP.1 connection parameters:");
591 //if (m_remoteComponent)
592 //{
593 // m_messageBox->addTextEditor("OCP.1 local port", juce::String(50014), "OCP.1 port");
594 //}
595
596 m_messageBox->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
597 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
598 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
599 if (returnValue == 1)
600 {
601 auto ADMOSCport = m_messageBox->getTextEditorContents("ADM local port").getIntValue();
602 auto ADMOSCremoteIP = juce::IPAddress(m_messageBox->getTextEditorContents("ADM remote IP"));
603 auto ADMOSCremotePort = m_messageBox->getTextEditorContents("ADM remote port").getIntValue();
604 if (m_remoteComponent)
605 {
606 m_remoteComponent->setExternalAdmOscSettings(ADMOSCport, ADMOSCremoteIP, ADMOSCremotePort);
607
608 if (m_config)
609 m_config->triggerConfigurationDump();
610 }
611 }
612
613 m_messageBox.reset();
614 }));
615}
616
617void MainComponent::setControlColour(const juce::Colour& controlColour)
618{
619 m_controlColour = controlColour;
620
621 applyControlColour();
622
623 if (m_connectingComponent)
624 m_connectingComponent->lookAndFeelChanged();
625}
626
627void MainComponent::applyControlColour()
628{
629 auto customLookAndFeel = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getLookAndFeel());
631 {
632 switch (customLookAndFeel->getPaletteStyle())
633 {
634 case JUCEAppBasics::CustomLookAndFeel::PS_Light:
635 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_controlColour.brighter());
636 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_controlColour);
637 break;
638 case JUCEAppBasics::CustomLookAndFeel::PS_Dark:
639 default:
640 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_controlColour.darker());
641 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_controlColour);
642 break;
643 }
644 }
645}
646
647void MainComponent::setControlsSize(const Mema::MemaClientControlComponentBase::ControlsSize& ctrlsSize)
648{
649 if (m_remoteComponent)
650 m_remoteComponent->setControlsSize(ctrlsSize);
651}
652
653void MainComponent::setStatus(const Status& s)
654{
655 m_currentStatus = s;
656 juce::MessageManager::callAsync([safeThis = juce::Component::SafePointer<MainComponent>(this)]() {
657 if (safeThis)
658 safeThis->resized();
659 });
660}
661
662const MainComponent::Status MainComponent::getStatus()
663{
664 return m_currentStatus;
665}
666
667void MainComponent::connectToMema()
668{
669 if (m_connectingComponent)
670 m_connectingComponent->setMasterServiceDescription(m_selectedService.description);
671 if (m_discoverComponent)
672 m_discoverComponent->setMasterServiceDescription(m_selectedService.description);
673
674 setStatus(Status::Connecting);
675
676 timerCallback(); // avoid codeclones by manually trigger the timed connection attempt once
677
678 // restart connection attempt after 5s, in case something got stuck...
679 startTimer(5000);
680}
681
683{
684 if (Status::Connecting == getStatus())
685 {
686 auto sl = m_discoverComponent->getAvailableServices();
687 auto const& iter = std::find_if(sl.begin(), sl.end(), [=](const auto& service) { return service.description == m_selectedService.description; });
688 if (iter != sl.end())
689 {
690 if ((m_selectedService.address != iter->address && m_selectedService.port != iter->port && m_selectedService.description != iter->description) || !m_networkConnection->isConnected())
691 {
692 m_selectedService = *iter;
693 if (m_networkConnection)
694 m_networkConnection->ConnectToSocket(m_selectedService.address.toString(), m_selectedService.port);
695 }
696 else if (m_networkConnection && !m_networkConnection->isConnected())
697 m_networkConnection->RetryConnectToSocket();
698 }
699 }
700 else
701 stopTimer();
702}
703
704bool MainComponent::keyPressed(const juce::KeyPress& key)
705{
706 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
707 if (key == juce::KeyPress(juce::KeyPress::F11Key, juce::ModifierKeys::ctrlModifier, 0) ||
708 key == juce::KeyPress('f', juce::ModifierKeys::commandModifier | juce::ModifierKeys::ctrlModifier, 0))
709 {
710 toggleFullscreenMode();
711 return true;
712 }
713 return false;
714}
715
717{
718 if (m_config)
719 {
720 // connection config
722
724 serviceDescriptionXmlElmement->addTextElement(m_selectedService.description);
726
728
729 // visu config
731
734 {
735 if (m_settingsItems[i].second == 1)
736 lookAndFeelXmlElmement->addTextElement(juce::String(i));
737 }
738 visuConfigXmlElement->addChildElement(lookAndFeelXmlElmement.release());
739
742 {
743 if (m_settingsItems[i].second == 1)
744 controlFormatXmlElmement->addTextElement(juce::String(i));
745 }
746 visuConfigXmlElement->addChildElement(controlFormatXmlElmement.release());
747
750 {
751 if (m_settingsItems[i].second == 1)
752 panningColourXmlElmement->addTextElement(juce::String(i));
753 }
754 visuConfigXmlElement->addChildElement(panningColourXmlElmement.release());
755
758 {
759 if (m_settingsItems[i].second == 1)
760 controlsSizeXmlElmement->addTextElement(juce::String(i));
761 }
762 visuConfigXmlElement->addChildElement(controlsSizeXmlElmement.release());
763
765
766 // external control config
768
770 if (m_remoteComponent)
771 admOscHostXmlElmement->setAttribute(MemaReAppConfiguration::getAttributeName(MemaReAppConfiguration::AttributeID::PORT), std::get<0>(m_remoteComponent->getExternalAdmOscSettings()));
772 extCtrlConfigXmlElement->addChildElement(admOscHostXmlElmement.release());
773
775 if (m_remoteComponent)
776 {
777 admOscClientXmlElmement->setAttribute(MemaReAppConfiguration::getAttributeName(MemaReAppConfiguration::AttributeID::IP), std::get<1>(m_remoteComponent->getExternalAdmOscSettings()).toString());
778 admOscClientXmlElmement->setAttribute(MemaReAppConfiguration::getAttributeName(MemaReAppConfiguration::AttributeID::PORT), std::get<2>(m_remoteComponent->getExternalAdmOscSettings()));
779 }
780 extCtrlConfigXmlElement->addChildElement(admOscClientXmlElmement.release());
781
783 }
784}
785
787{
790 {
793 {
794 auto serviceDescription = serviceDescriptionXmlElement->getAllSubText();
795 if (serviceDescription.isNotEmpty() && m_selectedService.description != serviceDescription)
796 {
797 if (m_networkConnection)
798 m_networkConnection->disconnect();
799
800 m_selectedService = {};
801 m_selectedService.description = serviceDescription;
802
803 connectToMema();
804 }
805 }
806 }
807
809 if (visuConfigState)
810 {
813 {
814 auto lookAndFeelSettingsOptionId = lookAndFeelXmlElement->getAllSubText().getIntValue();
815 handleSettingsLookAndFeelMenuResult(lookAndFeelSettingsOptionId);
816 }
817
820 {
821 auto controlFormatSettingsOptionId = controlFormatXmlElement->getAllSubText().getIntValue();
822 handleSettingsControlFormatMenuResult(controlFormatSettingsOptionId);
823 }
824
827 {
828 auto controlColourSettingsOptionId = controlColourXmlElement->getAllSubText().getIntValue();
829 handleSettingsControlColourMenuResult(controlColourSettingsOptionId);
830 }
831
834 {
835 auto controlsSizeSettingsOptionId = controlsSizeXmlElmement->getAllSubText().getIntValue();
836 handleSettingsControlsSizeMenuResult(controlsSizeSettingsOptionId);
837 }
838 }
839
842 {
843 int ADMOSCport = 0;
844 juce::IPAddress ADMOSCremoteIP = juce::IPAddress::local();
845 int ADMOSCremotePort = 0;
846
850
853 {
856 }
857
858 if (m_remoteComponent)
859 m_remoteComponent->setExternalAdmOscSettings(ADMOSCport, ADMOSCremoteIP, ADMOSCremotePort);
860 }
861}
862
864{
865#if JUCE_WINDOWS
866 return juce::Desktop::getInstance().getKioskModeComponent() != nullptr;
867#elif JUCE_MAC
868 if (auto* topLevel = getTopLevelComponent())
869 if (auto* peer = topLevel->getPeer())
870 return peer->isFullScreen();
871
873 return false;
874#endif
875}
876
void paint(juce::Graphics &g) override
Paints the background and any status overlay.
void performConfigurationDump() override
Serialises the current configuration to the XML file on disk.
std::function< void(int, bool)> onPaletteStyleChange
Called when the user changes the look-and-feel or metering colour.
void resized() override
Lays out the active child component to fill the window.
MemaReSettingsOption
Identifiers for all user-configurable settings exposed via the settings popup menu.
@ ControlsSize_M
Medium control elements.
@ ControlsSize_S
Small control elements.
@ ControlFormat_PanningType_5point0
2-D spatial panning — 5.0 surround.
@ LookAndFeel_Light
Force light look-and-feel.
@ ControlFormat_PanningType_7point1point4
2-D spatial panning — 7.1.4 Dolby Atmos.
@ ControlFormat_PanningType_9point1point6
2-D spatial panning — 9.1.6 ATMOS full-3D.
@ ControlColour_Blue
Blue accent colour.
@ LookAndFeel_FollowHost
Match the host OS appearance.
@ ControlFormat_PanningType_7point0
2-D spatial panning — 7.0 surround.
@ ControlColour_Laser
High-visibility laser-green accent colour.
@ ExternalControl
Opens the ADM-OSC external-control settings dialog.
@ LookAndFeel_Dark
Force dark look-and-feel.
@ ControlColour_Pink
Pink accent colour.
@ ControlFormat_PanningType_LCRS
2-D spatial panning — LCRS 4-channel.
@ ControlFormat_PanningType_LRS
2-D spatial panning — LRS 3-channel.
@ ControlFormat_PanningType_Quadrophonic
2-D spatial panning — classic 4-channel quad.
@ ControlsSize_L
Large control elements (good for touch screens).
@ ControlColour_Red
Red accent colour.
@ ControlColour_Green
Green accent colour (default).
@ ControlFormat_RawChannels
Faderbank crosspoint control (raw input × output).
@ ControlFormat_PanningType_5point1point2
2-D spatial panning — 5.1.2 with height.
@ ControlFormat_PluginParameterControl
Plugin-parameter control panel.
@ ControlFormat_PanningType_7point1
2-D spatial panning — 7.1 surround.
@ ControlFormat_PanningType_5point1
2-D spatial panning — 5.1 surround.
@ FullscreenWindowMode
Toggle between popup and fullscreen window mode.
Status
Connection/application phase driven by the TCP session lifecycle.
void lookAndFeelChanged() override
Propagates a look-and-feel change to all owned child components.
bool keyPressed(const juce::KeyPress &key) override
Handles keyboard shortcuts (e.g. Escape to disconnect).
void applySettingsOption(const MemaMoSettingsOption &option)
Applies a settings menu selection, updating look-and-feel, visualisation type, or colour.
void onConfigUpdated() override
Reacts to external configuration changes.
bool isFullscreenEnabled()
Returns whether the window is currently displayed in fullscreen mode.
void timerCallback() override
Periodic callback used to retry TCP connections and poll network status.
std::function< void(bool)> onSetFullscreenWindow
Called to request a fullscreen/windowed transition from the application shell.
~MainComponent() override
static juce::String getAttributeName(AttributeID ID)
@ CONTROLFORMAT
Active control mode (faderbank, 2-D panning layout, plugin parameters).
@ ADMOSCCLIENT
ADM-OSC client (remote IP and port for outgoing messages).
@ CONTROLSSIZE
Size category for control widgets (S / M / L).
@ VISUCONFIG
Root element for visualisation/control settings.
@ LOOKANDFEEL
Active look-and-feel (follow host / dark / light).
@ ADMOSCHOST
ADM-OSC host (local UDP listen port).
@ EXTCTRLCONFIG
Root element for external ADM-OSC controller settings.
@ CONNECTIONCONFIG
Root element for TCP connection settings.
@ SERVICEDESCRIPTION
Stored multicast service descriptor of the last connected Mema instance.
@ CONTROLCOLOUR
User-selected accent colour for control elements.
static juce::String getTagName(TagID ID)
@ PORT
UDP/TCP port number.
@ IP
IP address string (used for ADM-OSC remote client address).
Carries the active look-and-feel palette style from Mema to connected clients.
ControlsSize
Size category for rendered control elements.
@ S
Small — suited for desktop with many channels.
static void freeMessageData(SerializableMessage *message)
Type-correctly destroys a SerializableMessage* returned by initFromMemoryBlock().
static SerializableMessage * initFromMemoryBlock(const juce::MemoryBlock &blob)
Deserialises a raw TCP frame into the correct concrete SerializableMessage subclass.
@ PluginParameterValue
Single parameter value update sent from Mema.Re to Mema.
@ ReinitIOCount
New input/output channel count; clients must rebuild their UI accordingly.
@ PluginProcessingState
Plugin enabled and pre/post processing state; sent bidirectionally between Mema and Mema....
@ ControlParameters
Full routing-matrix state snapshot; sent by Mema on connect and echoed by Mema.Re on change.
@ EnvironmentParameters
Look-and-feel palette sent by Mema to clients on connect.
@ PluginParameterInfos
Plugin name and full parameter descriptor list; sent by Mema when a plugin is loaded or changed.
static juce::String getRemoteServiceTypeUID()
Returns the UID for the Mema.Re remote-control service.
static juce::String getServiceTypeUIDBase()
Returns the base string for building service type UIDs.