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 = {
59 m_networkConnection->sendMessage(std::make_unique<Mema::DataTrafficTypeSelectionMessage>(desiredTrafficTypes)->getSerializedMessage());
60
61 setStatus(Status::Monitoring);
62 };
63 m_networkConnection->onConnectionLost = [=]() {
64 DBG(__FUNCTION__);
65
66 if (m_remoteComponent)
67 m_remoteComponent->resetCtrl();
68
69 connectToMema();
70
71 setStatus(Status::Connecting);
72 };
73 m_networkConnection->onMessageReceived = [=](const juce::MemoryBlock& message) {
74 auto knownMessage = Mema::SerializableMessage::initFromMemoryBlock(message);
75 if (auto const epm = dynamic_cast<const Mema::EnvironmentParametersMessage*>(knownMessage))
76 {
77 m_settingsHostLookAndFeelId = epm->getPaletteStyle();
78 jassert(m_settingsHostLookAndFeelId >= JUCEAppBasics::CustomLookAndFeel::PS_Dark && m_settingsHostLookAndFeelId <= JUCEAppBasics::CustomLookAndFeel::PS_Light);
79
80 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)
81 {
82 m_settingsItems[1].second = 1; // set ticked for setting 1 (follow host)
83 onPaletteStyleChange(m_settingsHostLookAndFeelId, false/*do not follow local style any more if a message was received via net once*/);
84 }
85 }
86 else if (m_remoteComponent && nullptr != knownMessage && Status::Monitoring == m_currentStatus)
87 {
88 m_remoteComponent->handleMessage(*knownMessage);
89 }
91 };
92
93 m_remoteComponent = std::make_unique<MemaReComponent>();
94 m_remoteComponent->onExitClick = [=]() {
95 setStatus(Status::Discovering);
96 };
97 m_remoteComponent->onMessageReadyToSend = [=](const juce::MemoryBlock& message) {
98 if (m_networkConnection)
99 m_networkConnection->sendMessage(message);
100 };
101 addAndMakeVisible(m_remoteComponent.get());
102
103 m_discoverComponent = std::make_unique<MemaClientDiscoverComponent>();
104 m_discoverComponent->setupServiceDiscovery(Mema::ServiceData::getServiceTypeUIDBase(), Mema::ServiceData::getRemoteServiceTypeUID());
105 m_discoverComponent->onServiceSelected = [=](const JUCEAppBasics::SessionMasterAwareService& selectedService) {
106 m_selectedService = selectedService;
107
108 connectToMema();
109
110 if (m_config)
111 m_config->triggerConfigurationDump(false);
112 };
113 addAndMakeVisible(m_discoverComponent.get());
114
115 m_connectingComponent = std::make_unique<MemaClientConnectingComponent>();
116 addAndMakeVisible(m_connectingComponent.get());
117
118 m_aboutComponent = std::make_unique<AboutComponent>(BinaryData::MemaReRect_png, BinaryData::MemaReRect_pngSize);
119 m_aboutButton = std::make_unique<juce::DrawableButton>("About", juce::DrawableButton::ButtonStyle::ImageFitted);
120 m_aboutButton->setTooltip(juce::String("About") + juce::JUCEApplication::getInstance()->getApplicationName());
121 m_aboutButton->onClick = [this] {
122 juce::PopupMenu aboutMenu;
123 aboutMenu.addCustomItem(1, std::make_unique<CustomAboutItem>(m_aboutComponent.get(), juce::Rectangle<int>(250, 250)), nullptr, juce::String("Info about") + juce::JUCEApplication::getInstance()->getApplicationName());
124 aboutMenu.showMenuAsync(juce::PopupMenu::Options());
125 };
126 m_aboutButton->setAlwaysOnTop(true);
127 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
128 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
129 addAndMakeVisible(m_aboutButton.get());
130
131 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
132#if JUCE_WINDOWS
133 auto fullscreenShortCutHint = std::string(" (Ctrl+F11)");
134#elif JUCE_MAC
135 auto fullscreenShortCutHint = std::string(" (Cmd+Ctrl+F)");
136#endif
137
138 // default lookandfeel is follow local, therefor none selected
139 m_settingsItems[MemaReSettingsOption::LookAndFeel_FollowHost] = std::make_pair("Follow Mema", 0);
140 m_settingsItems[MemaReSettingsOption::LookAndFeel_Dark] = std::make_pair("Dark", 1);
141 m_settingsItems[MemaReSettingsOption::LookAndFeel_Light] = std::make_pair("Light", 0);
142 // default output visu is normal meterbridge
143 m_settingsItems[MemaReSettingsOption::ControlFormat_RawChannels] = std::make_pair("Faderbank", 1);
144 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_LRS] = std::make_pair(juce::AudioChannelSet::createLRS().getDescription().toStdString(), 0);
145 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_LCRS] = std::make_pair(juce::AudioChannelSet::createLCRS().getDescription().toStdString(), 0);
146 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_5point0] = std::make_pair(juce::AudioChannelSet::create5point0().getDescription().toStdString(), 0);
147 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_5point1] = std::make_pair(juce::AudioChannelSet::create5point1().getDescription().toStdString(), 0);
148 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_5point1point2] = std::make_pair(juce::AudioChannelSet::create5point1point2().getDescription().toStdString(), 0);
149 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_7point0] = std::make_pair(juce::AudioChannelSet::create7point0().getDescription().toStdString(), 0);
150 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_7point1] = std::make_pair(juce::AudioChannelSet::create7point1().getDescription().toStdString(), 0);
151 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_7point1point4] = std::make_pair(juce::AudioChannelSet::create7point1point4().getDescription().toStdString(), 0);
152 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_9point1point6] = std::make_pair(juce::AudioChannelSet::create9point1point6().getDescription().toStdString(), 0);
153 m_settingsItems[MemaReSettingsOption::ControlFormat_PanningType_Quadrophonic] = std::make_pair(juce::AudioChannelSet::quadraphonic().getDescription().toStdString(), 0);
154 m_settingsItems[MemaReSettingsOption::ControlFormat_PluginParameterControl] = std::make_pair("Plug-in parameter control", 1);
155 // default panning colour is green
156 m_settingsItems[MemaReSettingsOption::ControlColour_Green] = std::make_pair("Green", 1);
157 m_settingsItems[MemaReSettingsOption::ControlColour_Red] = std::make_pair("Red", 0);
158 m_settingsItems[MemaReSettingsOption::ControlColour_Blue] = std::make_pair("Blue", 0);
159 m_settingsItems[MemaReSettingsOption::ControlColour_Pink] = std::make_pair("Anni Pink", 0);
160 m_settingsItems[MemaReSettingsOption::ControlColour_Laser] = std::make_pair("Laser", 0);
161 // default controls size is S
162 m_settingsItems[MemaReSettingsOption::ControlsSize_S] = std::make_pair("S", 1);
163 m_settingsItems[MemaReSettingsOption::ControlsSize_M] = std::make_pair("M", 0);
164 m_settingsItems[MemaReSettingsOption::ControlsSize_L] = std::make_pair("L", 0);
165#if JUCE_WINDOWS || JUCE_MAC
166 // fullscreen toggling
167 m_settingsItems[MemaReSettingsOption::FullscreenWindowMode] = std::make_pair("Toggle fullscreen mode" + fullscreenShortCutHint, 0);
168#endif
169 // Further components
170 m_settingsButton = std::make_unique<juce::DrawableButton>("Settings", juce::DrawableButton::ButtonStyle::ImageFitted);
171 m_settingsButton->setTooltip(juce::String("Settings for") + juce::JUCEApplication::getInstance()->getApplicationName());
172 m_settingsButton->onClick = [this] {
173 juce::PopupMenu lookAndFeelSubMenu;
174 for (int i = MemaReSettingsOption::LookAndFeel_First; i <= MemaReSettingsOption::LookAndFeel_Last; i++)
175 lookAndFeelSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
176
177 juce::PopupMenu controlFormatSubMenu;
178 for (int i = MemaReSettingsOption::ControlFormat_First; i <= MemaReSettingsOption::ControlFormat_Last; i++)
179 controlFormatSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
180
181 juce::PopupMenu controlColourSubMenu;
182 for (int i = MemaReSettingsOption::ControlColour_First; i <= MemaReSettingsOption::ControlColour_Last; i++)
183 controlColourSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
184
185 juce::PopupMenu constrolsSizeSubMenu;
186 for (int i = MemaReSettingsOption::ControlsSize_First; i <= MemaReSettingsOption::ControlsSize_Last; i++)
187 constrolsSizeSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
188
189 juce::PopupMenu settingsMenu;
190 settingsMenu.addSubMenu("LookAndFeel", lookAndFeelSubMenu);
191 settingsMenu.addSubMenu("Control format", controlFormatSubMenu);
192 settingsMenu.addSubMenu("Control colour", controlColourSubMenu);
193 settingsMenu.addSubMenu("Controls size", constrolsSizeSubMenu);
194 settingsMenu.addSeparator();
195 settingsMenu.addItem(MemaReSettingsOption::ExternalControl, "External control...", true);
196#if JUCE_WINDOWS || JUCE_MAC
197 settingsMenu.addSeparator();
198 settingsMenu.addItem(MemaReSettingsOption::FullscreenWindowMode, m_settingsItems[MemaReSettingsOption::FullscreenWindowMode].first, true, false);
199#endif
200 settingsMenu.showMenuAsync(juce::PopupMenu::Options(), [=](int selectedId) {
201 handleSettingsMenuResult(selectedId);
202 if (m_config)
203 m_config->triggerConfigurationDump();
204 });
205 };
206 m_settingsButton->setAlwaysOnTop(true);
207 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
208 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
209 addAndMakeVisible(m_settingsButton.get());
210
211 m_disconnectButton = std::make_unique<juce::DrawableButton>("Disconnect", juce::DrawableButton::ButtonStyle::ImageFitted);
212 m_disconnectButton->setTooltip(juce::String("Disconnect ") + juce::JUCEApplication::getInstance()->getApplicationName() + " from " + (m_selectedService.description.isNotEmpty() ? m_selectedService.description : "Nothing :)"));
213 m_disconnectButton->onClick = [this] {
214 if (m_networkConnection)
215 m_networkConnection->disconnect();
216
217 if (m_remoteComponent)
218 m_remoteComponent->resetCtrl();
219
220 m_selectedService = {};
221 if (m_discoverComponent)
222 m_discoverComponent->resetServices();
223
224 if (m_config)
225 m_config->triggerConfigurationDump();
226
227 setStatus(Status::Discovering);
228 };
229 m_disconnectButton->setAlwaysOnTop(true);
230 m_disconnectButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
231 m_disconnectButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
232 addAndMakeVisible(m_disconnectButton.get());
233
234#ifdef RUN_MESSAGE_TESTS
235 Mema::runTests();
236#endif
237
238 setSize(800, 600);
239
240#if defined JUCE_IOS
241 // iOS is updated via AppStore
242#define IGNORE_UPDATES
243#elif defined JUCE_ANDROID
244 // Android as well
245#define IGNORE_UPDATES
246#endif
247
248#if defined IGNORE_UPDATES
249#else
250 auto updater = JUCEAppBasics::WebUpdateDetector::getInstance();
251 updater->SetReferenceVersion(ProjectInfo::versionString);
252 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/mema/releases/latest");
253 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Mema/refs/heads/main/");
254#endif
255
256
257 // add this main component to watchers
258 m_config->addWatcher(this); // without initial update - that we have to do externally after lambdas were assigned
259
260 // we want keyboard focus for fullscreen toggle shortcut
261 setWantsKeyboardFocus(true);
262}
263
265{
266}
267
269{
270 auto safety = JUCEAppBasics::iOS_utils::getDeviceSafetyMargins();
271 auto safeBounds = getLocalBounds();
272 safeBounds.removeFromTop(safety._top);
273 safeBounds.removeFromBottom(safety._bottom);
274 safeBounds.removeFromLeft(safety._left);
275 safeBounds.removeFromRight(safety._right);
276
277 switch (m_currentStatus)
278 {
279 case Status::Monitoring:
280 m_connectingComponent->setVisible(false);
281 m_discoverComponent->setVisible(false);
282 m_remoteComponent->setVisible(true);
283 m_remoteComponent->setBounds(safeBounds);
284 break;
285 case Status::Connecting:
286 m_remoteComponent->setVisible(false);
287 m_discoverComponent->setVisible(false);
288 m_connectingComponent->setVisible(true);
289 m_connectingComponent->setBounds(safeBounds);
290 break;
291 case Status::Discovering:
292 default:
293 m_connectingComponent->setVisible(false);
294 m_remoteComponent->setVisible(false);
295 m_discoverComponent->setVisible(true);
296 m_discoverComponent->setBounds(safeBounds);
297 break;
298 }
299
300 auto leftButtons = safeBounds.removeFromLeft(36);
301 auto rightButtons = safeBounds.removeFromLeft(36);
302 m_aboutButton->setBounds(leftButtons.removeFromTop(35).removeFromBottom(30));
303 m_settingsButton->setBounds(leftButtons.removeFromTop(35).removeFromBottom(30));
304 m_disconnectButton->setBounds(rightButtons.removeFromTop(35).removeFromBottom(30));
305}
306
307void MainComponent::paint(juce::Graphics& g)
308{
309 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
310}
311
313{
314 auto aboutButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::question_mark_24dp_svg).get());
315 aboutButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
316 m_aboutButton->setImages(aboutButtonDrawable.get());
317
318 auto settingsDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
319 settingsDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
320 m_settingsButton->setImages(settingsDrawable.get());
321
322 auto disconnectDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::link_off_24dp_svg).get());
323 disconnectDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
324 m_disconnectButton->setImages(disconnectDrawable.get());
325
326 applyControlColour();
327}
328
330{
331 // use the settings menu item call infrastructure to set the option
332 handleSettingsMenuResult(option);
333}
334
335void MainComponent::handleSettingsMenuResult(int selectedId)
336{
337 if (0 == selectedId)
338 return; // nothing selected, dismiss
340 handleSettingsLookAndFeelMenuResult(selectedId);
342 handleSettingsControlFormatMenuResult(selectedId);
344 handleSettingsControlColourMenuResult(selectedId);
346 handleSettingsControlsSizeMenuResult(selectedId);
348 showExternalControlSettings();
350 handleSettingsFullscreenModeToggleResult();
351 else
352 jassertfalse; // unhandled menu entry!?
353}
354
355void MainComponent::handleSettingsLookAndFeelMenuResult(int selectedId)
356{
357 // helper internal function to avoid code clones
358 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int a, int b, int c) {
359 m_settingsItems[MemaReSettingsOption::LookAndFeel_FollowHost].second = a;
360 m_settingsItems[MemaReSettingsOption::LookAndFeel_Dark].second = b;
361 m_settingsItems[MemaReSettingsOption::LookAndFeel_Light].second = c;
362 };
363
364 switch (selectedId)
365 {
368 if (onPaletteStyleChange && m_settingsHostLookAndFeelId != -1)
369 onPaletteStyleChange(m_settingsHostLookAndFeelId, false);
370 break;
374 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Dark, false);
375 break;
379 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Light, false);
380 break;
381 default:
382 jassertfalse; // unknown id fed in unintentionally ?!
383 break;
384 }
385}
386
387void MainComponent::handleSettingsControlFormatMenuResult(int selectedId)
388{
389 // helper internal function to avoid code clones
390 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) {
391 m_settingsItems[MemaReSettingsOption::ControlFormat_RawChannels].second = a;
403 };
404
405 switch (selectedId)
406 {
408 setSettingsItemsCheckState(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
409 if (m_remoteComponent)
410 m_remoteComponent->setFaderbankCtrlActive();
411 break;
413 setSettingsItemsCheckState(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
414 if (m_remoteComponent)
415 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::createLRS());
416 break;
418 setSettingsItemsCheckState(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
419 if (m_remoteComponent)
420 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::createLCRS());
421 break;
423 setSettingsItemsCheckState(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
424 if (m_remoteComponent)
425 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create5point0());
426 break;
428 setSettingsItemsCheckState(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
429 if (m_remoteComponent)
430 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create5point1());
431 break;
433 setSettingsItemsCheckState(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
434 if (m_remoteComponent)
435 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create5point1point2());
436 break;
438 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
439 if (m_remoteComponent)
440 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create7point0());
441 break;
443 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0);
444 if (m_remoteComponent)
445 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create7point1());
446 break;
448 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);
449 if (m_remoteComponent)
450 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create7point1point4());
451 break;
453 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);
454 if (m_remoteComponent)
455 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::create9point1point6());
456 break;
458 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
459 if (m_remoteComponent)
460 m_remoteComponent->setOutputPanningCtrlActive(juce::AudioChannelSet::quadraphonic());
461 break;
463 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
464 if (m_remoteComponent)
465 m_remoteComponent->setPluginCtrlActive();
466 break;
467 default:
468 jassertfalse; // unknown id fed in unintentionally ?!
469 break;
470 }
471
472 resized();
473}
474
475void MainComponent::handleSettingsControlColourMenuResult(int selectedId)
476{
477 // helper internal function to avoid code clones
478 std::function<void(int, int, int, int, int)> setSettingsItemsCheckState = [=](int green, int red, int blue, int pink, int laser) {
479 m_settingsItems[MemaReSettingsOption::ControlColour_Green].second = green;
480 m_settingsItems[MemaReSettingsOption::ControlColour_Red].second = red;
481 m_settingsItems[MemaReSettingsOption::ControlColour_Blue].second = blue;
482 m_settingsItems[MemaReSettingsOption::ControlColour_Pink].second = pink;
483 m_settingsItems[MemaReSettingsOption::ControlColour_Laser].second = laser;
484 };
485
486 switch (selectedId)
487 {
489 setSettingsItemsCheckState(1, 0, 0, 0, 0);
490 setControlColour(juce::Colours::forestgreen);
491 break;
493 setSettingsItemsCheckState(0, 1, 0, 0, 0);
494 setControlColour(juce::Colours::orangered);
495 break;
497 setSettingsItemsCheckState(0, 0, 1, 0, 0);
498 setControlColour(juce::Colours::dodgerblue);
499 break;
501 setSettingsItemsCheckState(0, 0, 0, 1, 0);
502 setControlColour(juce::Colours::deeppink);
503 break;
505 setSettingsItemsCheckState(0, 0, 0, 0, 1);
506 setControlColour(juce::Colour(0xd1, 0xff, 0x4f));
507 break;
508 default:
509 jassertfalse; // unknown id fed in unintentionally ?!
510 break;
511 }
512}
513
514void MainComponent::handleSettingsControlsSizeMenuResult(int selectedId)
515{
516 // helper internal function to avoid code clones
517 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int small, int medium, int large) {
518 m_settingsItems[MemaReSettingsOption::ControlsSize_S].second = small;
519 m_settingsItems[MemaReSettingsOption::ControlsSize_M].second = medium;
520 m_settingsItems[MemaReSettingsOption::ControlsSize_L].second = large;
521 };
522
523 switch (selectedId)
524 {
528 break;
532 break;
536 break;
537 default:
538 jassertfalse; // unknown id fed in unintentionally ?!
539 break;
540 }
541}
542
543void MainComponent::handleSettingsFullscreenModeToggleResult()
544{
545 toggleFullscreenMode();
546}
547
548void MainComponent::toggleFullscreenMode()
549{
553}
554
555void MainComponent::showExternalControlSettings()
556{
557 m_messageBox = std::make_unique<juce::AlertWindow>(
558 "External control setup",
559 "Enter remote control parameters to externally connect to " + juce::JUCEApplication::getInstance()->getApplicationName() + " and control its parameters.\n" +
560 "Info: This machine uses IP " + juce::IPAddress::getLocalAddress().toString(),
561 juce::MessageBoxIconType::NoIcon);
562
563 m_messageBox->addTextBlock("\nADM-OSC connection parameters:");
564 if (m_remoteComponent)
565 {
566 auto admOscSettings = m_remoteComponent->getExternalAdmOscSettings();
567 m_messageBox->addTextEditor("ADM local port", juce::String(std::get<0>(admOscSettings)), "ADM-OSC port");
568 m_messageBox->addTextEditor("ADM remote IP", std::get<1>(admOscSettings).toString(), "Target IP");
569 m_messageBox->addTextEditor("ADM remote port", juce::String(std::get<2>(admOscSettings)), "Target port");
570 }
571
572 //m_messageBox->addTextBlock("\nOCP.1 connection parameters:");
573 //if (m_remoteComponent)
574 //{
575 // m_messageBox->addTextEditor("OCP.1 local port", juce::String(50014), "OCP.1 port");
576 //}
577
578 m_messageBox->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
579 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
580 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
581 if (returnValue == 1)
582 {
583 auto ADMOSCport = m_messageBox->getTextEditorContents("ADM local port").getIntValue();
584 auto ADMOSCremoteIP = juce::IPAddress(m_messageBox->getTextEditorContents("ADM remote IP"));
585 auto ADMOSCremotePort = m_messageBox->getTextEditorContents("ADM remote port").getIntValue();
586 if (m_remoteComponent)
587 {
588 m_remoteComponent->setExternalAdmOscSettings(ADMOSCport, ADMOSCremoteIP, ADMOSCremotePort);
589
590 if (m_config)
591 m_config->triggerConfigurationDump();
592 }
593 }
594
595 m_messageBox.reset();
596 }));
597}
598
599void MainComponent::setControlColour(const juce::Colour& controlColour)
600{
601 m_controlColour = controlColour;
602
603 applyControlColour();
604
605 if (m_connectingComponent)
606 m_connectingComponent->lookAndFeelChanged();
607}
608
609void MainComponent::applyControlColour()
610{
611 auto customLookAndFeel = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getLookAndFeel());
613 {
614 switch (customLookAndFeel->getPaletteStyle())
615 {
616 case JUCEAppBasics::CustomLookAndFeel::PS_Light:
617 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_controlColour.brighter());
618 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_controlColour);
619 break;
620 case JUCEAppBasics::CustomLookAndFeel::PS_Dark:
621 default:
622 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_controlColour.darker());
623 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_controlColour);
624 break;
625 }
626 }
627}
628
629void MainComponent::setControlsSize(const Mema::MemaClientControlComponentBase::ControlsSize& ctrlsSize)
630{
631 if (m_remoteComponent)
632 m_remoteComponent->setControlsSize(ctrlsSize);
633}
634
635void MainComponent::setStatus(const Status& s)
636{
637 m_currentStatus = s;
638 juce::MessageManager::callAsync([safeThis = juce::Component::SafePointer<MainComponent>(this)]() {
639 if (safeThis)
640 safeThis->resized();
641 });
642}
643
644const MainComponent::Status MainComponent::getStatus()
645{
646 return m_currentStatus;
647}
648
649void MainComponent::connectToMema()
650{
651 if (m_connectingComponent)
652 m_connectingComponent->setMasterServiceDescription(m_selectedService.description);
653 if (m_discoverComponent)
654 m_discoverComponent->setMasterServiceDescription(m_selectedService.description);
655
656 setStatus(Status::Connecting);
657
658 timerCallback(); // avoid codeclones by manually trigger the timed connection attempt once
659
660 // restart connection attempt after 5s, in case something got stuck...
661 startTimer(5000);
662}
663
665{
666 if (Status::Connecting == getStatus())
667 {
668 auto sl = m_discoverComponent->getAvailableServices();
669 auto const& iter = std::find_if(sl.begin(), sl.end(), [=](const auto& service) { return service.description == m_selectedService.description; });
670 if (iter != sl.end())
671 {
672 if ((m_selectedService.address != iter->address && m_selectedService.port != iter->port && m_selectedService.description != iter->description) || !m_networkConnection->isConnected())
673 {
674 m_selectedService = *iter;
675 if (m_networkConnection)
676 m_networkConnection->ConnectToSocket(m_selectedService.address.toString(), m_selectedService.port);
677 }
678 else if (m_networkConnection && !m_networkConnection->isConnected())
679 m_networkConnection->RetryConnectToSocket();
680 }
681 }
682 else
683 stopTimer();
684}
685
686bool MainComponent::keyPressed(const juce::KeyPress& key)
687{
688 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
689 if (key == juce::KeyPress(juce::KeyPress::F11Key, juce::ModifierKeys::ctrlModifier, 0) ||
690 key == juce::KeyPress('f', juce::ModifierKeys::commandModifier | juce::ModifierKeys::ctrlModifier, 0))
691 {
692 toggleFullscreenMode();
693 return true;
694 }
695 return false;
696}
697
699{
700 if (m_config)
701 {
702 // connection config
704
706 serviceDescriptionXmlElmement->addTextElement(m_selectedService.description);
708
710
711 // visu config
713
716 {
717 if (m_settingsItems[i].second == 1)
718 lookAndFeelXmlElmement->addTextElement(juce::String(i));
719 }
720 visuConfigXmlElement->addChildElement(lookAndFeelXmlElmement.release());
721
724 {
725 if (m_settingsItems[i].second == 1)
726 controlFormatXmlElmement->addTextElement(juce::String(i));
727 }
728 visuConfigXmlElement->addChildElement(controlFormatXmlElmement.release());
729
732 {
733 if (m_settingsItems[i].second == 1)
734 panningColourXmlElmement->addTextElement(juce::String(i));
735 }
736 visuConfigXmlElement->addChildElement(panningColourXmlElmement.release());
737
740 {
741 if (m_settingsItems[i].second == 1)
742 controlsSizeXmlElmement->addTextElement(juce::String(i));
743 }
744 visuConfigXmlElement->addChildElement(controlsSizeXmlElmement.release());
745
747
748 // external control config
750
752 if (m_remoteComponent)
753 admOscHostXmlElmement->setAttribute(MemaReAppConfiguration::getAttributeName(MemaReAppConfiguration::AttributeID::PORT), std::get<0>(m_remoteComponent->getExternalAdmOscSettings()));
754 extCtrlConfigXmlElement->addChildElement(admOscHostXmlElmement.release());
755
757 if (m_remoteComponent)
758 {
759 admOscClientXmlElmement->setAttribute(MemaReAppConfiguration::getAttributeName(MemaReAppConfiguration::AttributeID::IP), std::get<1>(m_remoteComponent->getExternalAdmOscSettings()).toString());
760 admOscClientXmlElmement->setAttribute(MemaReAppConfiguration::getAttributeName(MemaReAppConfiguration::AttributeID::PORT), std::get<2>(m_remoteComponent->getExternalAdmOscSettings()));
761 }
762 extCtrlConfigXmlElement->addChildElement(admOscClientXmlElmement.release());
763
765 }
766}
767
769{
772 {
775 {
776 auto serviceDescription = serviceDescriptionXmlElement->getAllSubText();
777 if (serviceDescription.isNotEmpty() && m_selectedService.description != serviceDescription)
778 {
779 if (m_networkConnection)
780 m_networkConnection->disconnect();
781
782 m_selectedService = {};
783 m_selectedService.description = serviceDescription;
784
785 connectToMema();
786 }
787 }
788 }
789
791 if (visuConfigState)
792 {
795 {
796 auto lookAndFeelSettingsOptionId = lookAndFeelXmlElement->getAllSubText().getIntValue();
797 handleSettingsLookAndFeelMenuResult(lookAndFeelSettingsOptionId);
798 }
799
802 {
803 auto controlFormatSettingsOptionId = controlFormatXmlElement->getAllSubText().getIntValue();
804 handleSettingsControlFormatMenuResult(controlFormatSettingsOptionId);
805 }
806
809 {
810 auto controlColourSettingsOptionId = controlColourXmlElement->getAllSubText().getIntValue();
811 handleSettingsControlColourMenuResult(controlColourSettingsOptionId);
812 }
813
816 {
817 auto controlsSizeSettingsOptionId = controlsSizeXmlElmement->getAllSubText().getIntValue();
818 handleSettingsControlsSizeMenuResult(controlsSizeSettingsOptionId);
819 }
820 }
821
824 {
825 int ADMOSCport = 0;
826 juce::IPAddress ADMOSCremoteIP = juce::IPAddress::local();
827 int ADMOSCremotePort = 0;
828
832
835 {
838 }
839
840 if (m_remoteComponent)
841 m_remoteComponent->setExternalAdmOscSettings(ADMOSCport, ADMOSCremoteIP, ADMOSCremotePort);
842 }
843}
844
846{
847#if JUCE_WINDOWS
848 return juce::Desktop::getInstance().getKioskModeComponent() != nullptr;
849#elif JUCE_MAC
850 if (auto* topLevel = getTopLevelComponent())
851 if (auto* peer = topLevel->getPeer())
852 return peer->isFullScreen();
853
855 return false;
856#endif
857}
858
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.
@ 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.