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) 2024-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 "MemaMoComponent.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<MemaMoAppConfiguration>(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 connectToMema();
67
68 setStatus(Status::Connecting);
69 };
70 m_networkConnection->onMessageReceived = [=](const juce::MemoryBlock& message) {
71 auto knownMessage = Mema::SerializableMessage::initFromMemoryBlock(message);
72 if (auto const epm = dynamic_cast<const Mema::EnvironmentParametersMessage*>(knownMessage))
73 {
74 m_settingsHostLookAndFeelId = epm->getPaletteStyle();
75 jassert(m_settingsHostLookAndFeelId >= JUCEAppBasics::CustomLookAndFeel::PS_Dark && m_settingsHostLookAndFeelId <= JUCEAppBasics::CustomLookAndFeel::PS_Light);
76
77 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)
78 {
79 m_settingsItems[1].second = 1; // set ticked for setting 1 (follow host)
80 onPaletteStyleChange(m_settingsHostLookAndFeelId, false/*do not follow local style any more if a message was received via net once*/);
81 }
82 }
83 else if (m_monitorComponent && nullptr != knownMessage && Status::Monitoring == m_currentStatus)
84 {
85 m_monitorComponent->handleMessage(*knownMessage);
86 }
88 };
89
90 m_monitorComponent = std::make_unique<MemaMoComponent>();
91 m_monitorComponent->onExitClick = [=]() {
92 setStatus(Status::Discovering);
93 };
94 addAndMakeVisible(m_monitorComponent.get());
95
96 m_discoverComponent = std::make_unique<MemaClientDiscoverComponent>();
98 m_discoverComponent->onServiceSelected = [=](const JUCEAppBasics::SessionMasterAwareService& selectedService) {
99 m_selectedService = selectedService;
100
101 connectToMema();
102
103 if (m_config)
104 m_config->triggerConfigurationDump(false);
105 };
106 addAndMakeVisible(m_discoverComponent.get());
107
108 m_connectingComponent = std::make_unique<MemaClientConnectingComponent>();
109 addAndMakeVisible(m_connectingComponent.get());
110
111 m_aboutComponent = std::make_unique<AboutComponent>(BinaryData::MemaMoRect_png, BinaryData::MemaMoRect_pngSize);
112 m_aboutButton = std::make_unique<juce::DrawableButton>("About", juce::DrawableButton::ButtonStyle::ImageFitted);
113 m_aboutButton->setTooltip(juce::String("About") + juce::JUCEApplication::getInstance()->getApplicationName());
114 m_aboutButton->onClick = [this] {
115 juce::PopupMenu aboutMenu;
116 aboutMenu.addCustomItem(1, std::make_unique<CustomAboutItem>(m_aboutComponent.get(), juce::Rectangle<int>(250, 250)), nullptr, juce::String("Info about") + juce::JUCEApplication::getInstance()->getApplicationName());
117 aboutMenu.showMenuAsync(juce::PopupMenu::Options());
118 };
119 m_aboutButton->setAlwaysOnTop(true);
120 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
121 m_aboutButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
122 addAndMakeVisible(m_aboutButton.get());
123
124 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
125#if JUCE_WINDOWS
126 auto fullscreenShortCutHint = std::string(" (Ctrl+F11)");
127#elif JUCE_MAC
128 auto fullscreenShortCutHint = std::string(" (Cmd+Ctrl+F)");
129#endif
130
131 // default lookandfeel is follow local, therefor none selected
132 m_settingsItems[MemaMoSettingsOption::LookAndFeel_FollowHost] = std::make_pair("Follow Mema", 0);
133 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Dark] = std::make_pair("Dark", 1);
134 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Light] = std::make_pair("Light", 0);
135 // default output visu is normal meterbridge
136 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Meterbridge] = std::make_pair("Meterbridge", 1);
137 m_settingsItems[MemaMoSettingsOption::OutputVisuType_LRS] = std::make_pair(juce::AudioChannelSet::createLRS().getDescription().toStdString(), 0);
138 m_settingsItems[MemaMoSettingsOption::OutputVisuType_LCRS] = std::make_pair(juce::AudioChannelSet::createLCRS().getDescription().toStdString(), 0);
139 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point0] = std::make_pair(juce::AudioChannelSet::create5point0().getDescription().toStdString(), 0);
140 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point1] = std::make_pair(juce::AudioChannelSet::create5point1().getDescription().toStdString(), 0);
141 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point1point2] = std::make_pair(juce::AudioChannelSet::create5point1point2().getDescription().toStdString(), 0);
142 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point0] = std::make_pair(juce::AudioChannelSet::create7point0().getDescription().toStdString(), 0);
143 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point1] = std::make_pair(juce::AudioChannelSet::create7point1().getDescription().toStdString(), 0);
144 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point1point4] = std::make_pair(juce::AudioChannelSet::create7point1point4().getDescription().toStdString(), 0);
145 m_settingsItems[MemaMoSettingsOption::OutputVisuType_9point1point6] = std::make_pair(juce::AudioChannelSet::create9point1point6().getDescription().toStdString(), 0);
146 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Quadrophonic] = std::make_pair(juce::AudioChannelSet::quadraphonic().getDescription().toStdString(), 0);
147 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Waveform] = std::make_pair("Waveform", 0);
148 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Spectrum] = std::make_pair("Spectrum", 0);
149 // default metering colour is green
150 m_settingsItems[MemaMoSettingsOption::MeteringColour_Green] = std::make_pair("Green", 1);
151 m_settingsItems[MemaMoSettingsOption::MeteringColour_Red] = std::make_pair("Red", 0);
152 m_settingsItems[MemaMoSettingsOption::MeteringColour_Blue] = std::make_pair("Blue", 0);
153 m_settingsItems[MemaMoSettingsOption::MeteringColour_Pink] = std::make_pair("Anni Pink", 0);
154 m_settingsItems[MemaMoSettingsOption::MeteringColour_Laser] = std::make_pair("Laser", 0);
155#if JUCE_WINDOWS || JUCE_MAC
156 // fullscreen toggling
157 m_settingsItems[MemaMoSettingsOption::FullscreenWindowMode] = std::make_pair("Toggle fullscreen mode" + fullscreenShortCutHint, 0);
158#endif
159 // Further components
160 m_settingsButton = std::make_unique<juce::DrawableButton>("Settings", juce::DrawableButton::ButtonStyle::ImageFitted);
161 m_settingsButton->setTooltip(juce::String("Settings for") + juce::JUCEApplication::getInstance()->getApplicationName());
162 m_settingsButton->onClick = [this] {
163 juce::PopupMenu lookAndFeelSubMenu;
164 for (int i = MemaMoSettingsOption::LookAndFeel_First; i <= MemaMoSettingsOption::LookAndFeel_Last; i++)
165 lookAndFeelSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
166
167 juce::PopupMenu outputVisuTypeSubMenu;
169 outputVisuTypeSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
170
171 juce::PopupMenu meteringColourSubMenu;
173 meteringColourSubMenu.addItem(i, m_settingsItems[i].first, true, m_settingsItems[i].second == 1);
174
175 juce::PopupMenu settingsMenu;
176 settingsMenu.addSubMenu("LookAndFeel", lookAndFeelSubMenu);
177 settingsMenu.addSubMenu("Output monitoring", outputVisuTypeSubMenu);
178 settingsMenu.addSubMenu("Metering colour", meteringColourSubMenu);
179#if JUCE_WINDOWS || JUCE_MAC
180 settingsMenu.addSeparator();
181 settingsMenu.addItem(MemaMoSettingsOption::FullscreenWindowMode, m_settingsItems[MemaMoSettingsOption::FullscreenWindowMode].first, true, false);
182#endif
183 settingsMenu.showMenuAsync(juce::PopupMenu::Options(), [=](int selectedId) {
184 handleSettingsMenuResult(selectedId);
185 if (m_config)
186 m_config->triggerConfigurationDump();
187 });
188 };
189 m_settingsButton->setAlwaysOnTop(true);
190 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
191 m_settingsButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
192 addAndMakeVisible(m_settingsButton.get());
193
194 m_disconnectButton = std::make_unique<juce::DrawableButton>("Disconnect", juce::DrawableButton::ButtonStyle::ImageFitted);
195 m_disconnectButton->setTooltip(juce::String("Disconnect ") + juce::JUCEApplication::getInstance()->getApplicationName());
196 m_disconnectButton->onClick = [this] {
197 if (m_networkConnection)
198 m_networkConnection->disconnect();
199
200 m_selectedService = {};
201 if (m_discoverComponent)
202 m_discoverComponent->resetServices();
203
204 if (m_config)
205 m_config->triggerConfigurationDump();
206
207 setStatus(Status::Discovering);
208 };
209 m_disconnectButton->setAlwaysOnTop(true);
210 m_disconnectButton->setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack);
211 m_disconnectButton->setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack);
212 addAndMakeVisible(m_disconnectButton.get());
213
214 if (juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui"))
215 {
216 m_aboutButton->setVisible(false);
217 m_settingsButton->setVisible(false);
218 m_disconnectButton->setVisible(false);
219 }
220
221#ifdef RUN_MESSAGE_TESTS
222 Mema::runTests();
223#endif
224
225 setSize(800, 400);
226
227#if defined JUCE_IOS
228 // iOS is updated via AppStore
229#define IGNORE_UPDATES
230#elif defined JUCE_ANDROID
231 // Android as well
232#define IGNORE_UPDATES
233#endif
234
235#if defined IGNORE_UPDATES
236#else
237 auto noUpdates = juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noupdates");
238 if (!noUpdates)
239 {
240 auto updater = JUCEAppBasics::WebUpdateDetector::getInstance();
241 updater->SetReferenceVersion(ProjectInfo::versionString);
242 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/mema/releases/latest");
243 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Mema/refs/heads/main/");
244 }
245#endif
246
247
248 // add this main component to watchers
249 m_config->addWatcher(this); // without initial update - that we have to do externally after lambdas were assigned
250
251 // we want keyboard focus for fullscreen toggle shortcut
252 setWantsKeyboardFocus(true);
253
254 JUCEAppBasics::iOS_utils::initialise([this] { resized(); });
255}
256
258{
259 JUCEAppBasics::iOS_utils::deinitialise();
260}
261
263{
264 auto safety = JUCEAppBasics::iOS_utils::getDeviceSafetyMargins();
265 auto safeBounds = getLocalBounds();
266 safeBounds.removeFromTop(safety._top);
267 safeBounds.removeFromBottom(safety._bottom);
268 safeBounds.removeFromLeft(safety._left);
269 safeBounds.removeFromRight(safety._right);
270
271 switch (m_currentStatus)
272 {
273 case Status::Monitoring:
274 m_connectingComponent->setVisible(false);
275 m_discoverComponent->setVisible(false);
276 m_monitorComponent->setVisible(true);
277 m_monitorComponent->setBounds(safeBounds);
278 break;
279 case Status::Connecting:
280 m_monitorComponent->setVisible(false);
281 m_discoverComponent->setVisible(false);
282 m_connectingComponent->setVisible(true);
283 m_connectingComponent->setBounds(safeBounds);
284 break;
285 case Status::Discovering:
286 default:
287 m_connectingComponent->setVisible(false);
288 m_monitorComponent->setVisible(false);
289 m_discoverComponent->setVisible(true);
290 m_discoverComponent->setBounds(safeBounds);
291 break;
292 }
293
294 if (!juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noconfigui"))
295 {
296 m_aboutButton->setBounds(safeBounds.removeFromTop(35).removeFromLeft(30).removeFromBottom(30));
297 m_settingsButton->setBounds(safeBounds.removeFromTop(35).removeFromLeft(30).removeFromBottom(30));
298 m_disconnectButton->setBounds(safeBounds.removeFromTop(35).removeFromLeft(30).removeFromBottom(30));
299 }
300}
301
302void MainComponent::paint(juce::Graphics& g)
303{
304 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
305}
306
308{
309 auto aboutButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::question_mark_24dp_svg).get());
310 aboutButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
311 m_aboutButton->setImages(aboutButtonDrawable.get());
312
313 auto settingsDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
314 settingsDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
315 m_settingsButton->setImages(settingsDrawable.get());
316
317 auto disconnectDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::link_off_24dp_svg).get());
318 disconnectDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
319 m_disconnectButton->setImages(disconnectDrawable.get());
320
321 applyMeteringColour();
322}
323
325{
326 // use the settings menu item call infrastructure to set the option
327 handleSettingsMenuResult(option);
328}
329
330void MainComponent::handleSettingsMenuResult(int selectedId)
331{
332 if (0 == selectedId)
333 return; // nothing selected, dismiss
334 else if (MemaMoSettingsOption::LookAndFeel_First <= selectedId && MemaMoSettingsOption::LookAndFeel_Last >= selectedId)
335 handleSettingsLookAndFeelMenuResult(selectedId);
336 else if (MemaMoSettingsOption::OutputVisuType_First <= selectedId && MemaMoSettingsOption::OutputVisuType_Last >= selectedId)
337 handleSettingsOutputVisuTypeMenuResult(selectedId);
338 else if (MemaMoSettingsOption::MeteringColour_First <= selectedId && MemaMoSettingsOption::MeteringColour_Last >= selectedId)
339 handleSettingsMeteringColourMenuResult(selectedId);
340 else if (MemaMoSettingsOption::FullscreenWindowMode == selectedId)
341 handleSettingsFullscreenModeToggleResult();
342 else
343 jassertfalse; // unhandled menu entry!?
344}
345
346void MainComponent::handleSettingsLookAndFeelMenuResult(int selectedId)
347{
348 // helper internal function to avoid code clones
349 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int a, int b, int c) {
350 m_settingsItems[MemaMoSettingsOption::LookAndFeel_FollowHost].second = a;
351 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Dark].second = b;
352 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Light].second = c;
353 };
354
355 switch (selectedId)
356 {
357 case MemaMoSettingsOption::LookAndFeel_FollowHost:
359 if (onPaletteStyleChange && m_settingsHostLookAndFeelId != -1)
360 onPaletteStyleChange(m_settingsHostLookAndFeelId, false);
361 break;
362 case MemaMoSettingsOption::LookAndFeel_Dark:
365 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Dark, false);
366 break;
367 case MemaMoSettingsOption::LookAndFeel_Light:
370 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Light, false);
371 break;
372 default:
373 jassertfalse; // unknown id fed in unintentionally ?!
374 break;
375 }
376}
377
378void MainComponent::handleSettingsOutputVisuTypeMenuResult(int selectedId)
379{
380 // helper internal function to avoid code clones
381 std::function<void(int, 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, int m) {
382 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Meterbridge].second = a;
383 m_settingsItems[MemaMoSettingsOption::OutputVisuType_LRS].second = b;
384 m_settingsItems[MemaMoSettingsOption::OutputVisuType_LCRS].second = c;
385 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point0].second = d;
386 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point1].second = e;
388 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point0].second = g;
389 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point1].second = h;
393 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Waveform].second = l;
394 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Spectrum].second = m;
395 };
396
397 switch (selectedId)
398 {
400 setSettingsItemsCheckState(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
401 if (m_monitorComponent)
402 m_monitorComponent->setOutputMeteringVisuActive();
403 break;
405 setSettingsItemsCheckState(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
406 if (m_monitorComponent)
407 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::createLRS());
408 break;
410 setSettingsItemsCheckState(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
411 if (m_monitorComponent)
412 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::createLCRS());
413 break;
415 setSettingsItemsCheckState(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
416 if (m_monitorComponent)
417 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create5point0());
418 break;
420 setSettingsItemsCheckState(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
421 if (m_monitorComponent)
422 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create5point1());
423 break;
425 setSettingsItemsCheckState(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
426 if (m_monitorComponent)
427 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create5point1point2());
428 break;
430 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
431 if (m_monitorComponent)
432 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create7point0());
433 break;
435 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
436 if (m_monitorComponent)
437 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create7point1());
438 break;
440 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0);
441 if (m_monitorComponent)
442 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create7point1point4());
443 break;
445 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);
446 if (m_monitorComponent)
447 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create9point1point6());
448 break;
450 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);
451 if (m_monitorComponent)
452 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::quadraphonic());
453 break;
455 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
456 if (m_monitorComponent)
457 m_monitorComponent->setWaveformVisuActive();
458 break;
460 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
461 if (m_monitorComponent)
462 m_monitorComponent->setSpectrumVisuActive();
463 break;
464 default:
465 jassertfalse; // unknown id fed in unintentionally ?!
466 break;
467 }
468
469 resized();
470}
471
472void MainComponent::handleSettingsMeteringColourMenuResult(int selectedId)
473{
474 // helper internal function to avoid code clones
475 std::function<void(int, int, int, int, int)> setSettingsItemsCheckState = [=](int green, int red, int blue, int pink, int laser) {
476 m_settingsItems[MemaMoSettingsOption::MeteringColour_Green].second = green;
477 m_settingsItems[MemaMoSettingsOption::MeteringColour_Red].second = red;
478 m_settingsItems[MemaMoSettingsOption::MeteringColour_Blue].second = blue;
479 m_settingsItems[MemaMoSettingsOption::MeteringColour_Pink].second = pink;
480 m_settingsItems[MemaMoSettingsOption::MeteringColour_Laser].second = laser;
481 };
482
483 switch (selectedId)
484 {
486 setSettingsItemsCheckState(1, 0, 0, 0, 0);
487 setMeteringColour(juce::Colours::forestgreen);
488 break;
490 setSettingsItemsCheckState(0, 1, 0, 0, 0);
491 setMeteringColour(juce::Colours::orangered);
492 break;
494 setSettingsItemsCheckState(0, 0, 1, 0, 0);
495 setMeteringColour(juce::Colours::dodgerblue);
496 break;
498 setSettingsItemsCheckState(0, 0, 0, 1, 0);
499 setMeteringColour(juce::Colours::deeppink);
500 break;
502 setSettingsItemsCheckState(0, 0, 0, 0, 1);
503 setMeteringColour(juce::Colour(0xd1, 0xff, 0x4f));
504 break;
505 default:
506 break;
507 }
508}
509
510void MainComponent::handleSettingsFullscreenModeToggleResult()
511{
512 toggleFullscreenMode();
513}
514
515void MainComponent::toggleFullscreenMode()
516{
520}
521
522void MainComponent::setMeteringColour(const juce::Colour& meteringColour)
523{
524 m_meteringColour = meteringColour;
525
526 applyMeteringColour();
527
528 if (m_connectingComponent)
529 m_connectingComponent->lookAndFeelChanged();
530}
531
532void MainComponent::applyMeteringColour()
533{
534 auto customLookAndFeel = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getLookAndFeel());
536 {
537 switch (customLookAndFeel->getPaletteStyle())
538 {
539 case JUCEAppBasics::CustomLookAndFeel::PS_Light:
540 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_meteringColour.brighter());
541 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_meteringColour);
542 break;
543 case JUCEAppBasics::CustomLookAndFeel::PS_Dark:
544 default:
545 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_meteringColour.darker());
546 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_meteringColour);
547 break;
548 }
549 }
550}
551
552std::optional<int> MainComponent::getNumVisibleChannels()
553{
554 if (!m_monitorComponent)
555 return std::nullopt;
556 else if (-1 == m_monitorComponent->getNumVisibleChannels())
557 return std::nullopt;
558 else
559 return m_monitorComponent->getNumVisibleChannels();
560}
561
562void MainComponent::setStatus(const Status& s)
563{
564 m_currentStatus = s;
565 juce::MessageManager::callAsync([safeThis = juce::Component::SafePointer<MainComponent>(this)]() {
566 if (safeThis)
567 safeThis->resized();
568 });
569}
570
571const MainComponent::Status MainComponent::getStatus()
572{
573 return m_currentStatus;
574}
575
576void MainComponent::connectToMema()
577{
578 if (m_connectingComponent)
579 m_connectingComponent->setMasterServiceDescription(m_selectedService.description);
580 if (m_discoverComponent)
581 m_discoverComponent->setMasterServiceDescription(m_selectedService.description);
582
583 setStatus(Status::Connecting);
584
585 timerCallback(); // avoid codeclones by manually trigger the timed connection attempt once
586
587 // restart connection attempt after 5s, in case something got stuck...
588 startTimer(5000);
589}
590
592{
593 if (Status::Connecting == getStatus())
594 {
595 auto sl = m_discoverComponent->getAvailableServices();
596 auto const& iter = std::find_if(sl.begin(), sl.end(), [=](const auto& service) { return service.description == m_selectedService.description; });
597 if (iter != sl.end())
598 {
599 if ((m_selectedService.address != iter->address && m_selectedService.port != iter->port && m_selectedService.description != iter->description) || !m_networkConnection->isConnected())
600 {
601 m_selectedService = *iter;
602 if (m_networkConnection)
603 m_networkConnection->ConnectToSocket(m_selectedService.address.toString(), m_selectedService.port);
604 }
605 else if (m_networkConnection && !m_networkConnection->isConnected())
606 m_networkConnection->RetryConnectToSocket();
607 }
608 }
609 else
610 stopTimer();
611}
612
613bool MainComponent::keyPressed(const juce::KeyPress& key)
614{
615 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
616 if (key == juce::KeyPress(juce::KeyPress::F11Key, juce::ModifierKeys::ctrlModifier, 0) ||
617 key == juce::KeyPress('f', juce::ModifierKeys::commandModifier | juce::ModifierKeys::ctrlModifier, 0))
618 {
619 toggleFullscreenMode();
620 return true;
621 }
622 return false;
623}
624
626{
627 if (m_config)
628 {
629 // connection config
631
633 serviceDescriptionXmlElmement->addTextElement(m_selectedService.description);
635
637
638 // visu config
640
642 for (int i = MemaMoSettingsOption::LookAndFeel_First; i <= MemaMoSettingsOption::LookAndFeel_Last; i++)
643 {
644 if (m_settingsItems[i].second == 1)
645 lookAndFeelXmlElmement->addTextElement(juce::String(i));
646 }
647 visuConfigXmlElement->addChildElement(lookAndFeelXmlElmement.release());
648
651 {
652 if (m_settingsItems[i].second == 1)
653 outputVisuTypeXmlElmement->addTextElement(juce::String(i));
654 }
655 if (m_monitorComponent && m_monitorComponent->getNumVisibleChannels().has_value())
656 outputVisuTypeXmlElmement->setAttribute(MemaMoAppConfiguration::getAttributeName(MemaMoAppConfiguration::AttributeID::COUNT), int(m_monitorComponent->getNumVisibleChannels().value()));
657 visuConfigXmlElement->addChildElement(outputVisuTypeXmlElmement.release());
658
661 {
662 if (m_settingsItems[i].second == 1)
663 meteringColourXmlElmement->addTextElement(juce::String(i));
664 }
665 visuConfigXmlElement->addChildElement(meteringColourXmlElmement.release());
666
668 }
669}
670
672{
675 {
678 {
679 auto serviceDescription = serviceDescriptionXmlElement->getAllSubText();
680 if (serviceDescription.isNotEmpty() && m_selectedService.description != serviceDescription)
681 {
682 if (m_networkConnection)
683 m_networkConnection->disconnect();
684
685 m_selectedService = {};
686 m_selectedService.description = serviceDescription;
687
688 connectToMema();
689 }
690 }
691 }
692
694 if (visuConfigState)
695 {
698 {
699 auto lookAndFeelSettingsOptionId = lookAndFeelXmlElement->getAllSubText().getIntValue();
700 handleSettingsLookAndFeelMenuResult(lookAndFeelSettingsOptionId);
701 }
702
705 {
706 auto outputVisuTypeSettingsOptionId = outputVisuTypeXmlElement->getAllSubText().getIntValue();
707 handleSettingsOutputVisuTypeMenuResult(outputVisuTypeSettingsOptionId);
709 if (m_monitorComponent && -1 < numVisibleChannels)
710 m_monitorComponent->setNumVisibleChannels(std::uint16_t(numVisibleChannels));
711 }
712
715 {
716 auto meteringColourSettingsOptionId = meteringColourXmlElement->getAllSubText().getIntValue();
717 handleSettingsMeteringColourMenuResult(meteringColourSettingsOptionId);
718 }
719 }
720}
721
723{
724#if JUCE_WINDOWS
725 return juce::Desktop::getInstance().getKioskModeComponent() != nullptr;
726#elif JUCE_MAC
727 if (auto* topLevel = getTopLevelComponent())
728 if (auto* peer = topLevel->getPeer())
729 return peer->isFullScreen();
730
732 return false;
733#endif
734}
735
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.
MemaMoSettingsOption
Identifiers for all user-configurable settings exposed via the settings popup menu.
@ OutputVisuType_LRS
3-channel Left/Right/Surround 2-D field.
@ OutputVisuType_5point0
5.0 surround 2-D field.
@ OutputVisuType_LCRS
4-channel LCRS 2-D field.
@ OutputVisuType_Waveform
Scrolling waveform plot.
@ OutputVisuType_Spectrum
FFT frequency-spectrum display.
@ OutputVisuType_Meterbridge
Level-bar meterbridge (default).
@ OutputVisuType_7point1
7.1 surround 2-D field.
@ OutputVisuType_Quadrophonic
Classic 4-channel quadrophonic 2-D field.
@ OutputVisuType_7point0
7.0 surround 2-D field.
@ MeteringColour_Pink
Pink metering bars.
@ MeteringColour_Blue
Blue metering bars.
@ MeteringColour_Green
Green metering bars (default).
@ OutputVisuType_5point1point2
5.1.2 with two height channels.
@ OutputVisuType_9point1point6
9.1.6 ATMOS full-3D layout.
@ MeteringColour_Red
Red metering bars.
@ MeteringColour_Laser
High-visibility laser-green metering bars.
@ OutputVisuType_7point1point4
7.1.4 Dolby Atmos layout.
@ OutputVisuType_5point1
5.1 surround 2-D field.
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
@ METERINGCOLOUR
User-selected metering bar colour.
@ OUTPUTVISUTYPE
Active output visualisation mode (meterbridge, 2-D field, waveform, …).
@ VISUCONFIG
Root element for visualisation settings.
@ LOOKANDFEEL
Active look-and-feel (follow host / dark / light).
@ SERVICEDESCRIPTION
Stores the multicast service descriptor of the last connected Mema instance.
@ CONNECTIONCONFIG
Root element for connection settings (host, port).
static juce::String getAttributeName(AttributeID ID)
@ COUNT
Integer storing a channel or item count.
static juce::String getTagName(TagID ID)
Carries the active look-and-feel palette style from Mema to connected clients.
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.
@ AnalyzerParameters
Audio device sample rate and block size; lets clients initialise their local ProcessorDataAnalyzer.
@ ReinitIOCount
New input/output channel count; clients must rebuild their UI accordingly.
@ AudioInputBuffer
Raw PCM input buffer streamed from Mema to subscribed clients.
@ AudioOutputBuffer
Raw PCM output buffer streamed from Mema to subscribed clients.
@ EnvironmentParameters
Look-and-feel palette sent by Mema to clients on connect.
static juce::String getMonitorServiceTypeUID()
Returns the UID for the Mema.Mo monitor service.
static juce::String getServiceTypeUIDBase()
Returns the base string for building service type UIDs.