28#include <CustomLookAndFeel.h>
29#include <WebUpdateDetector.h>
40 m_config = std::make_unique<MemaMoAppConfiguration>(JUCEAppBasics::AppConfigurationBase::getDefaultConfigFilePath());
41 m_config->addDumper(
this);
44 if (!m_config->isValid())
46 m_config->ResetToDefault();
49 m_networkConnection = std::make_unique<InterprocessConnectionImpl>();
50 m_networkConnection->onConnectionMade = [=]() {
53 std::vector<Mema::SerializableMessage::SerializableMessageType> desiredTrafficTypes = {
59 m_networkConnection->sendMessage(std::make_unique<Mema::DataTrafficTypeSelectionMessage>(desiredTrafficTypes)->getSerializedMessage());
61 setStatus(Status::Monitoring);
63 m_networkConnection->onConnectionLost = [=]() {
68 setStatus(Status::Connecting);
70 m_networkConnection->onMessageReceived = [=](
const juce::MemoryBlock& message) {
74 m_settingsHostLookAndFeelId = epm->getPaletteStyle();
75 jassert(m_settingsHostLookAndFeelId >= JUCEAppBasics::CustomLookAndFeel::PS_Dark && m_settingsHostLookAndFeelId <= JUCEAppBasics::CustomLookAndFeel::PS_Light);
79 m_settingsItems[1].second = 1;
83 else if (m_monitorComponent &&
nullptr != knownMessage && Status::Monitoring == m_currentStatus)
85 m_monitorComponent->handleMessage(*knownMessage);
90 m_monitorComponent = std::make_unique<MemaMoComponent>();
91 m_monitorComponent->onExitClick = [=]() {
92 setStatus(Status::Discovering);
94 addAndMakeVisible(m_monitorComponent.get());
96 m_discoverComponent = std::make_unique<MemaClientDiscoverComponent>();
98 m_discoverComponent->onServiceSelected = [=](
const JUCEAppBasics::SessionMasterAwareService& selectedService) {
99 m_selectedService = selectedService;
104 m_config->triggerConfigurationDump(
false);
106 addAndMakeVisible(m_discoverComponent.get());
108 m_connectingComponent = std::make_unique<MemaClientConnectingComponent>();
109 addAndMakeVisible(m_connectingComponent.get());
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());
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());
126 auto fullscreenShortCutHint = std::string(
" (Ctrl+F11)");
128 auto fullscreenShortCutHint = std::string(
" (Cmd+Ctrl+F)");
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);
155#if JUCE_WINDOWS || JUCE_MAC
157 m_settingsItems[MemaMoSettingsOption::FullscreenWindowMode] = std::make_pair(
"Toggle fullscreen mode" + fullscreenShortCutHint, 0);
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);
167 juce::PopupMenu outputVisuTypeSubMenu;
169 outputVisuTypeSubMenu.addItem(i, m_settingsItems[i].first,
true, m_settingsItems[i].second == 1);
171 juce::PopupMenu meteringColourSubMenu;
173 meteringColourSubMenu.addItem(i, m_settingsItems[i].first,
true, m_settingsItems[i].second == 1);
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);
183 settingsMenu.showMenuAsync(juce::PopupMenu::Options(), [=](
int selectedId) {
184 handleSettingsMenuResult(selectedId);
186 m_config->triggerConfigurationDump();
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());
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();
200 m_selectedService = {};
201 if (m_discoverComponent)
202 m_discoverComponent->resetServices();
205 m_config->triggerConfigurationDump();
207 setStatus(Status::Discovering);
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());
214 if (juce::JUCEApplication::getInstance()->getCommandLineParameters().contains(
"--noconfigui"))
216 m_aboutButton->setVisible(
false);
217 m_settingsButton->setVisible(
false);
218 m_disconnectButton->setVisible(
false);
221#ifdef RUN_MESSAGE_TESTS
229#define IGNORE_UPDATES
230#elif defined JUCE_ANDROID
232#define IGNORE_UPDATES
235#if defined IGNORE_UPDATES
237 auto noUpdates = juce::JUCEApplication::getInstance()->getCommandLineParameters().contains(
"--noupdates");
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/");
249 m_config->addWatcher(
this);
252 setWantsKeyboardFocus(
true);
254 JUCEAppBasics::iOS_utils::initialise([
this] {
resized(); });
259 JUCEAppBasics::iOS_utils::deinitialise();
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);
271 switch (m_currentStatus)
273 case Status::Monitoring:
274 m_connectingComponent->setVisible(
false);
275 m_discoverComponent->setVisible(
false);
276 m_monitorComponent->setVisible(
true);
277 m_monitorComponent->setBounds(safeBounds);
279 case Status::Connecting:
280 m_monitorComponent->setVisible(
false);
281 m_discoverComponent->setVisible(
false);
282 m_connectingComponent->setVisible(
true);
283 m_connectingComponent->setBounds(safeBounds);
285 case Status::Discovering:
287 m_connectingComponent->setVisible(
false);
288 m_monitorComponent->setVisible(
false);
289 m_discoverComponent->setVisible(
true);
290 m_discoverComponent->setBounds(safeBounds);
294 if (!juce::JUCEApplication::getInstance()->getCommandLineParameters().contains(
"--noconfigui"))
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));
304 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
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());
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());
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());
321 applyMeteringColour();
327 handleSettingsMenuResult(option);
330void MainComponent::handleSettingsMenuResult(
int selectedId)
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();
346void MainComponent::handleSettingsLookAndFeelMenuResult(
int selectedId)
350 m_settingsItems[MemaMoSettingsOption::LookAndFeel_FollowHost].second =
a;
351 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Dark].second =
b;
352 m_settingsItems[MemaMoSettingsOption::LookAndFeel_Light].second =
c;
357 case MemaMoSettingsOption::LookAndFeel_FollowHost:
362 case MemaMoSettingsOption::LookAndFeel_Dark:
367 case MemaMoSettingsOption::LookAndFeel_Light:
378void MainComponent::handleSettingsOutputVisuTypeMenuResult(
int selectedId)
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) {
400 setSettingsItemsCheckState(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
401 if (m_monitorComponent)
402 m_monitorComponent->setOutputMeteringVisuActive();
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());
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());
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());
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());
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());
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());
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());
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());
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());
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());
455 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
456 if (m_monitorComponent)
457 m_monitorComponent->setWaveformVisuActive();
460 setSettingsItemsCheckState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
461 if (m_monitorComponent)
462 m_monitorComponent->setSpectrumVisuActive();
472void MainComponent::handleSettingsMeteringColourMenuResult(
int selectedId)
487 setMeteringColour(juce::Colours::forestgreen);
491 setMeteringColour(juce::Colours::orangered);
495 setMeteringColour(juce::Colours::dodgerblue);
499 setMeteringColour(juce::Colours::deeppink);
503 setMeteringColour(juce::Colour(0xd1, 0xff, 0x4f));
510void MainComponent::handleSettingsFullscreenModeToggleResult()
512 toggleFullscreenMode();
515void MainComponent::toggleFullscreenMode()
522void MainComponent::setMeteringColour(
const juce::Colour& meteringColour)
526 applyMeteringColour();
528 if (m_connectingComponent)
529 m_connectingComponent->lookAndFeelChanged();
532void MainComponent::applyMeteringColour()
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);
543 case JUCEAppBasics::CustomLookAndFeel::PS_Dark:
545 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId, m_meteringColour.darker());
546 getLookAndFeel().setColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId, m_meteringColour);
552std::optional<int> MainComponent::getNumVisibleChannels()
554 if (!m_monitorComponent)
556 else if (-1 == m_monitorComponent->getNumVisibleChannels())
559 return m_monitorComponent->getNumVisibleChannels();
562void MainComponent::setStatus(
const Status& s)
565 juce::MessageManager::callAsync([
safeThis = juce::Component::SafePointer<MainComponent>(
this)]() {
573 return m_currentStatus;
576void MainComponent::connectToMema()
578 if (m_connectingComponent)
579 m_connectingComponent->setMasterServiceDescription(m_selectedService.description);
580 if (m_discoverComponent)
581 m_discoverComponent->setMasterServiceDescription(m_selectedService.description);
583 setStatus(Status::Connecting);
593 if (Status::Connecting == getStatus())
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; });
599 if ((m_selectedService.address !=
iter->address && m_selectedService.port !=
iter->port && m_selectedService.description !=
iter->description) || !m_networkConnection->isConnected())
601 m_selectedService = *
iter;
602 if (m_networkConnection)
603 m_networkConnection->ConnectToSocket(m_selectedService.address.toString(), m_selectedService.port);
605 else if (m_networkConnection && !m_networkConnection->isConnected())
606 m_networkConnection->RetryConnectToSocket();
616 if (
key == juce::KeyPress(juce::KeyPress::F11Key, juce::ModifierKeys::ctrlModifier, 0) ||
617 key == juce::KeyPress(
'f', juce::ModifierKeys::commandModifier | juce::ModifierKeys::ctrlModifier, 0))
619 toggleFullscreenMode();
642 for (
int i = MemaMoSettingsOption::LookAndFeel_First;
i <= MemaMoSettingsOption::LookAndFeel_Last;
i++)
644 if (m_settingsItems[
i].
second == 1)
652 if (m_settingsItems[
i].
second == 1)
655 if (m_monitorComponent && m_monitorComponent->getNumVisibleChannels().has_value())
662 if (m_settingsItems[
i].
second == 1)
682 if (m_networkConnection)
683 m_networkConnection->disconnect();
685 m_selectedService = {};
725 return juce::Desktop::getInstance().getKioskModeComponent() !=
nullptr;
729 return peer->isFullScreen();
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.