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#ifdef RUN_MESSAGE_TESTS
215 Mema::runTests();
216#endif
217
218 setSize(800, 400);
219
220#if defined JUCE_IOS
221 // iOS is updated via AppStore
222#define IGNORE_UPDATES
223#elif defined JUCE_ANDROID
224 // Android as well
225#define IGNORE_UPDATES
226#endif
227
228#if defined IGNORE_UPDATES
229#else
230 auto updater = JUCEAppBasics::WebUpdateDetector::getInstance();
231 updater->SetReferenceVersion(ProjectInfo::versionString);
232 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/mema/releases/latest");
233 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Mema/refs/heads/main/");
234#endif
235
236
237 // add this main component to watchers
238 m_config->addWatcher(this); // without initial update - that we have to do externally after lambdas were assigned
239
240 // we want keyboard focus for fullscreen toggle shortcut
241 setWantsKeyboardFocus(true);
242}
243
247
249{
250 auto safety = JUCEAppBasics::iOS_utils::getDeviceSafetyMargins();
251 auto safeBounds = getLocalBounds();
252 safeBounds.removeFromTop(safety._top);
253 safeBounds.removeFromBottom(safety._bottom);
254 safeBounds.removeFromLeft(safety._left);
255 safeBounds.removeFromRight(safety._right);
256
257 switch (m_currentStatus)
258 {
259 case Status::Monitoring:
260 m_connectingComponent->setVisible(false);
261 m_discoverComponent->setVisible(false);
262 m_monitorComponent->setVisible(true);
263 m_monitorComponent->setBounds(safeBounds);
264 break;
265 case Status::Connecting:
266 m_monitorComponent->setVisible(false);
267 m_discoverComponent->setVisible(false);
268 m_connectingComponent->setVisible(true);
269 m_connectingComponent->setBounds(safeBounds);
270 break;
271 case Status::Discovering:
272 default:
273 m_connectingComponent->setVisible(false);
274 m_monitorComponent->setVisible(false);
275 m_discoverComponent->setVisible(true);
276 m_discoverComponent->setBounds(safeBounds);
277 break;
278 }
279
280 m_aboutButton->setBounds(safeBounds.removeFromTop(35).removeFromLeft(30).removeFromBottom(30));
281 m_settingsButton->setBounds(safeBounds.removeFromTop(35).removeFromLeft(30).removeFromBottom(30));
282 m_disconnectButton->setBounds(safeBounds.removeFromTop(35).removeFromLeft(30).removeFromBottom(30));
283}
284
285void MainComponent::paint(juce::Graphics& g)
286{
287 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
288}
289
291{
292 auto aboutButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::question_mark_24dp_svg).get());
293 aboutButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
294 m_aboutButton->setImages(aboutButtonDrawable.get());
295
296 auto settingsDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
297 settingsDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
298 m_settingsButton->setImages(settingsDrawable.get());
299
300 auto disconnectDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::link_off_24dp_svg).get());
301 disconnectDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
302 m_disconnectButton->setImages(disconnectDrawable.get());
303
304 applyMeteringColour();
305}
306
308{
309 // use the settings menu item call infrastructure to set the option
310 handleSettingsMenuResult(option);
311}
312
313void MainComponent::handleSettingsMenuResult(int selectedId)
314{
315 if (0 == selectedId)
316 return; // nothing selected, dismiss
317 else if (MemaMoSettingsOption::LookAndFeel_First <= selectedId && MemaMoSettingsOption::LookAndFeel_Last >= selectedId)
318 handleSettingsLookAndFeelMenuResult(selectedId);
319 else if (MemaMoSettingsOption::OutputVisuType_First <= selectedId && MemaMoSettingsOption::OutputVisuType_Last >= selectedId)
320 handleSettingsOutputVisuTypeMenuResult(selectedId);
321 else if (MemaMoSettingsOption::MeteringColour_First <= selectedId && MemaMoSettingsOption::MeteringColour_Last >= selectedId)
322 handleSettingsMeteringColourMenuResult(selectedId);
323 else if (MemaMoSettingsOption::FullscreenWindowMode == selectedId)
324 handleSettingsFullscreenModeToggleResult();
325 else
326 jassertfalse; // unhandled menu entry!?
327}
328
329void MainComponent::handleSettingsLookAndFeelMenuResult(int selectedId)
330{
331 // helper internal function to avoid code clones
332 std::function<void(int, int, int)> setSettingsItemsCheckState = [=](int a, int b, int c) {
333 m_settingsItems[MemaMoSettingsOption::LookAndFeel_FollowHost].second = a;
334 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Dark].second = b;
335 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Light].second = c;
336 };
337
338 switch (selectedId)
339 {
340 case MemaMoSettingsOption::LookAndFeel_FollowHost:
342 if (onPaletteStyleChange && m_settingsHostLookAndFeelId != -1)
343 onPaletteStyleChange(m_settingsHostLookAndFeelId, false);
344 break;
345 case MemaMoSettingsOption::LookAndFeel_Dark:
348 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Dark, false);
349 break;
350 case MemaMoSettingsOption::LookAndFeel_Light:
353 onPaletteStyleChange(JUCEAppBasics::CustomLookAndFeel::PS_Light, false);
354 break;
355 default:
356 jassertfalse; // unknown id fed in unintentionally ?!
357 break;
358 }
359}
360
361void MainComponent::handleSettingsOutputVisuTypeMenuResult(int selectedId)
362{
363 // helper internal function to avoid code clones
364 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) {
365 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Meterbridge].second = a;
366 m_settingsItems[MemaMoSettingsOption::OutputVisuType_LRS].second = b;
367 m_settingsItems[MemaMoSettingsOption::OutputVisuType_LCRS].second = c;
368 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point0].second = d;
369 m_settingsItems[MemaMoSettingsOption::OutputVisuType_5point1].second = e;
371 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point0].second = g;
372 m_settingsItems[MemaMoSettingsOption::OutputVisuType_7point1].second = h;
376 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Waveform].second = l;
377 m_settingsItems[MemaMoSettingsOption::OutputVisuType_Spectrum].second = m;
378 };
379
380 switch (selectedId)
381 {
383 setSettingsItemsCheckState(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
384 if (m_monitorComponent)
385 m_monitorComponent->setOutputMeteringVisuActive();
386 break;
388 setSettingsItemsCheckState(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
389 if (m_monitorComponent)
390 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::createLRS());
391 break;
393 setSettingsItemsCheckState(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
394 if (m_monitorComponent)
395 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::createLCRS());
396 break;
398 setSettingsItemsCheckState(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
399 if (m_monitorComponent)
400 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create5point0());
401 break;
403 setSettingsItemsCheckState(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0);
404 if (m_monitorComponent)
405 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create5point1());
406 break;
408 setSettingsItemsCheckState(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
409 if (m_monitorComponent)
410 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create5point1point2());
411 break;
413 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
414 if (m_monitorComponent)
415 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create7point0());
416 break;
418 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
419 if (m_monitorComponent)
420 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create7point1());
421 break;
423 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0);
424 if (m_monitorComponent)
425 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create7point1point4());
426 break;
428 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);
429 if (m_monitorComponent)
430 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::create9point1point6());
431 break;
433 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);
434 if (m_monitorComponent)
435 m_monitorComponent->setOutputFieldVisuActive(juce::AudioChannelSet::quadraphonic());
436 break;
438 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
439 if (m_monitorComponent)
440 m_monitorComponent->setWaveformVisuActive();
441 break;
443 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
444 if (m_monitorComponent)
445 m_monitorComponent->setSpectrumVisuActive();
446 break;
447 default:
448 jassertfalse; // unknown id fed in unintentionally ?!
449 break;
450 }
451
452 resized();
453}
454
455void MainComponent::handleSettingsMeteringColourMenuResult(int selectedId)
456{
457 // helper internal function to avoid code clones
458 std::function<void(int, int, int, int, int)> setSettingsItemsCheckState = [=](int green, int red, int blue, int pink, int laser) {
459 m_settingsItems[MemaMoSettingsOption::MeteringColour_Green].second = green;
460 m_settingsItems[MemaMoSettingsOption::MeteringColour_Red].second = red;
461 m_settingsItems[MemaMoSettingsOption::MeteringColour_Blue].second = blue;
462 m_settingsItems[MemaMoSettingsOption::MeteringColour_Pink].second = pink;
463 m_settingsItems[MemaMoSettingsOption::MeteringColour_Laser].second = laser;
464 };
465
466 switch (selectedId)
467 {
469 setSettingsItemsCheckState(1, 0, 0, 0, 0);
470 setMeteringColour(juce::Colours::forestgreen);
471 break;
473 setSettingsItemsCheckState(0, 1, 0, 0, 0);
474 setMeteringColour(juce::Colours::orangered);
475 break;
477 setSettingsItemsCheckState(0, 0, 1, 0, 0);
478 setMeteringColour(juce::Colours::dodgerblue);
479 break;
481 setSettingsItemsCheckState(0, 0, 0, 1, 0);
482 setMeteringColour(juce::Colours::deeppink);
483 break;
485 setSettingsItemsCheckState(0, 0, 0, 0, 1);
486 setMeteringColour(juce::Colour(0xd1, 0xff, 0x4f));
487 break;
488 default:
489 break;
490 }
491}
492
493void MainComponent::handleSettingsFullscreenModeToggleResult()
494{
495 toggleFullscreenMode();
496}
497
498void MainComponent::toggleFullscreenMode()
499{
503}
504
505void MainComponent::setMeteringColour(const juce::Colour& meteringColour)
506{
507 m_meteringColour = meteringColour;
508
509 applyMeteringColour();
510
511 if (m_connectingComponent)
512 m_connectingComponent->lookAndFeelChanged();
513}
514
515void MainComponent::applyMeteringColour()
516{
517 auto customLookAndFeel = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getLookAndFeel());
519 {
520 switch (customLookAndFeel->getPaletteStyle())
521 {
522 case JUCEAppBasics::CustomLookAndFeel::PS_Light:
523 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_meteringColour.brighter());
524 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_meteringColour);
525 break;
526 case JUCEAppBasics::CustomLookAndFeel::PS_Dark:
527 default:
528 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_meteringColour.darker());
529 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_meteringColour);
530 break;
531 }
532 }
533}
534
535std::optional<int> MainComponent::getNumVisibleChannels()
536{
537 if (!m_monitorComponent)
538 return std::nullopt;
539 else if (-1 == m_monitorComponent->getNumVisibleChannels())
540 return std::nullopt;
541 else
542 return m_monitorComponent->getNumVisibleChannels();
543}
544
545void MainComponent::setStatus(const Status& s)
546{
547 m_currentStatus = s;
548 juce::MessageManager::callAsync([safeThis = juce::Component::SafePointer<MainComponent>(this)]() {
549 if (safeThis)
550 safeThis->resized();
551 });
552}
553
554const MainComponent::Status MainComponent::getStatus()
555{
556 return m_currentStatus;
557}
558
559void MainComponent::connectToMema()
560{
561 if (m_connectingComponent)
562 m_connectingComponent->setMasterServiceDescription(m_selectedService.description);
563 if (m_discoverComponent)
564 m_discoverComponent->setMasterServiceDescription(m_selectedService.description);
565
566 setStatus(Status::Connecting);
567
568 timerCallback(); // avoid codeclones by manually trigger the timed connection attempt once
569
570 // restart connection attempt after 5s, in case something got stuck...
571 startTimer(5000);
572}
573
575{
576 if (Status::Connecting == getStatus())
577 {
578 auto sl = m_discoverComponent->getAvailableServices();
579 auto const& iter = std::find_if(sl.begin(), sl.end(), [=](const auto& service) { return service.description == m_selectedService.description; });
580 if (iter != sl.end())
581 {
582 if ((m_selectedService.address != iter->address && m_selectedService.port != iter->port && m_selectedService.description != iter->description) || !m_networkConnection->isConnected())
583 {
584 m_selectedService = *iter;
585 if (m_networkConnection)
586 m_networkConnection->ConnectToSocket(m_selectedService.address.toString(), m_selectedService.port);
587 }
588 else if (m_networkConnection && !m_networkConnection->isConnected())
589 m_networkConnection->RetryConnectToSocket();
590 }
591 }
592 else
593 stopTimer();
594}
595
596bool MainComponent::keyPressed(const juce::KeyPress& key)
597{
598 // Ctrl+F11 on Windows/Linux, Cmd+Ctrl+F on macOS
599 if (key == juce::KeyPress(juce::KeyPress::F11Key, juce::ModifierKeys::ctrlModifier, 0) ||
600 key == juce::KeyPress('f', juce::ModifierKeys::commandModifier | juce::ModifierKeys::ctrlModifier, 0))
601 {
602 toggleFullscreenMode();
603 return true;
604 }
605 return false;
606}
607
609{
610 if (m_config)
611 {
612 // connection config
614
616 serviceDescriptionXmlElmement->addTextElement(m_selectedService.description);
618
620
621 // visu config
623
625 for (int i = MemaMoSettingsOption::LookAndFeel_First; i <= MemaMoSettingsOption::LookAndFeel_Last; i++)
626 {
627 if (m_settingsItems[i].second == 1)
628 lookAndFeelXmlElmement->addTextElement(juce::String(i));
629 }
630 visuConfigXmlElement->addChildElement(lookAndFeelXmlElmement.release());
631
634 {
635 if (m_settingsItems[i].second == 1)
636 outputVisuTypeXmlElmement->addTextElement(juce::String(i));
637 }
638 if (m_monitorComponent && m_monitorComponent->getNumVisibleChannels().has_value())
639 outputVisuTypeXmlElmement->setAttribute(MemaMoAppConfiguration::getAttributeName(MemaMoAppConfiguration::AttributeID::COUNT), int(m_monitorComponent->getNumVisibleChannels().value()));
640 visuConfigXmlElement->addChildElement(outputVisuTypeXmlElmement.release());
641
644 {
645 if (m_settingsItems[i].second == 1)
646 meteringColourXmlElmement->addTextElement(juce::String(i));
647 }
648 visuConfigXmlElement->addChildElement(meteringColourXmlElmement.release());
649
651 }
652}
653
655{
658 {
661 {
662 auto serviceDescription = serviceDescriptionXmlElement->getAllSubText();
663 if (serviceDescription.isNotEmpty() && m_selectedService.description != serviceDescription)
664 {
665 if (m_networkConnection)
666 m_networkConnection->disconnect();
667
668 m_selectedService = {};
669 m_selectedService.description = serviceDescription;
670
671 connectToMema();
672 }
673 }
674 }
675
677 if (visuConfigState)
678 {
681 {
682 auto lookAndFeelSettingsOptionId = lookAndFeelXmlElement->getAllSubText().getIntValue();
683 handleSettingsLookAndFeelMenuResult(lookAndFeelSettingsOptionId);
684 }
685
688 {
689 auto outputVisuTypeSettingsOptionId = outputVisuTypeXmlElement->getAllSubText().getIntValue();
690 handleSettingsOutputVisuTypeMenuResult(outputVisuTypeSettingsOptionId);
692 if (m_monitorComponent && -1 < numVisibleChannels)
693 m_monitorComponent->setNumVisibleChannels(std::uint16_t(numVisibleChannels));
694 }
695
698 {
699 auto meteringColourSettingsOptionId = meteringColourXmlElement->getAllSubText().getIntValue();
700 handleSettingsMeteringColourMenuResult(meteringColourSettingsOptionId);
701 }
702 }
703}
704
706{
707#if JUCE_WINDOWS
708 return juce::Desktop::getInstance().getKioskModeComponent() != nullptr;
709#elif JUCE_MAC
710 if (auto* topLevel = getTopLevelComponent())
711 if (auto* peer = topLevel->getPeer())
712 return peer->isFullScreen();
713
715 return false;
716#endif
717}
718
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.