Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
Mema.cpp
Go to the documentation of this file.
1/* Copyright (c) 2024, 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 "Mema.h"
20
23
24#include <WebUpdateDetector.h>
25
26namespace Mema
27{
28
29
30JUCE_IMPLEMENT_SINGLETON(Mema)
31
32//==============================================================================
33Mema::Mema() : juce::Timer()
34{
35 // create the configuration object (is being initialized from disk automatically)
36 m_config = std::make_unique<MemaAppConfiguration>(JUCEAppBasics::AppConfigurationBase::getDefaultConfigFilePath());
37 m_config->addDumper(this);
38
39 // check if config creation was able to read a valid config from disk...
40 if (!m_config->isValid())
41 {
42 m_config->ResetToDefault();
43 }
44
45 // add this main component to watchers
46 m_config->addWatcher(this, true); // this initial update cannot yet reach all parts of the app, esp. settings page that relies on fully initialized pagecomponentmanager, therefor a manual watcher update is triggered below
47
48 m_MemaProcessor = std::make_unique<MemaProcessor>(m_config->getConfigState(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG)).get());
49
50 m_audioDeviceSelectComponent = std::make_unique<AudioSelectComponent>(m_MemaProcessor->getDeviceManager(),
55 false, false, false, false);
56 m_audioDeviceSelectComponent->onAudioDeviceChangedDuringAudioSelection = [=]() {
57 if (m_MemaProcessor)
58 m_MemaProcessor->initializeCtrlValuesToUnity();
59 };
60
61 // do the initial update for the whole application with config contents
62 m_config->triggerWatcherUpdate();
63
64 startTimer(500);
65
66#if defined JUCE_IOS
67// iOS is updated via AppStore
68#define IGNORE_UPDATES
69#elif defined JUCE_ANDROID
70// Android as well
71#define IGNORE_UPDATES
72#endif
73
74#if defined IGNORE_UPDATES
75#else
76 auto noUpdates = juce::JUCEApplication::getInstance()->getCommandLineParameters().contains("--noupdates");
77 if (!noUpdates)
78 {
79 auto updater = JUCEAppBasics::WebUpdateDetector::getInstance();
80 updater->SetReferenceVersion(ProjectInfo::versionString);
81 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/mema/releases/latest");
82 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Mema/refs/heads/main/");
83 }
84#endif
85}
86
88{
89 if (m_MemaProcessor)
90 m_MemaProcessor->editorBeingDeleted(m_MemaProcessor->getActiveEditor());
91}
92
94{
95 if (m_MemaProcessor && m_MemaProcessor->getDeviceManager())
96 {
97 if (onCpuUsageUpdate)
98 onCpuUsageUpdate(int(m_MemaProcessor->getDeviceManager()->getCpuUsage() * 100.0));
99 if (onNetworkUsageUpdate)
100 onNetworkUsageUpdate(m_MemaProcessor->getNetworkHealth());
101 if (onServiceDiscoveryTopologyUpdate)
102 onServiceDiscoveryTopologyUpdate(m_MemaProcessor->getDiscoveredServicesTopology());
103 }
104}
105
107{
108 if (m_MemaProcessor)
109 {
110 if (nullptr == m_MemaProcessor->getActiveEditor())
111 m_MemaProcessor->createEditorIfNeeded();
112
113 if (auto editor = dynamic_cast<MemaProcessorEditor*>(m_MemaProcessor->getActiveEditor()))
114 {
115 jassert(onEditorSizeChangeRequested); // should be set before handling the ui component!
116 editor->onEditorSizeChangeRequested = onEditorSizeChangeRequested;
117 }
118
119 m_MemaProcessor->updateCommanders();
120
121 return m_MemaProcessor->getActiveEditor();
122 }
123 else
124 return nullptr;
125}
126
128{
129 if (m_audioDeviceSelectComponent)
130 return m_audioDeviceSelectComponent.get();
131 else
132 return nullptr;
133}
134
135const std::unique_ptr<MemaProcessor>& Mema::getMemaProcessor() const
136{
137 return m_MemaProcessor;
138}
139
141{
142 onEditorSizeChangeRequested = nullptr;
143 onCpuUsageUpdate = nullptr;
144 onNetworkUsageUpdate = nullptr;
145 onServiceDiscoveryTopologyUpdate = nullptr;
146
147 if (auto editor = dynamic_cast<MemaProcessorEditor*>(m_MemaProcessor->getActiveEditor()))
148 editor->onEditorSizeChangeRequested = nullptr;
149}
150
152{
153 if (m_config)
154 {
155 auto stateXml = m_config->getConfigState();
156 if (stateXml)
157 {
158 if (m_MemaProcessor)
159 m_config->setConfigState(m_MemaProcessor->createStateXml(), MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG));
160 if (m_MemaUIConfigCache)
161 m_config->setConfigState(std::make_unique<juce::XmlElement>(*m_MemaUIConfigCache), MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::UICONFIG));
162 }
163 }
164}
165
167{
168 auto processorConfigState = m_config->getConfigState(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG));
169 if (processorConfigState && m_MemaProcessor)
170 {
171 m_MemaProcessor->setStateXml(processorConfigState.get());
172 }
173
174 auto uiConfigState = m_config->getConfigState(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::UICONFIG));
175 if (uiConfigState && m_MemaProcessor)
176 {
177 m_MemaUIConfigCache = std::make_unique<juce::XmlElement>(*uiConfigState);
178 }
179}
180
182{
183 if (m_MemaProcessor)
184 {
185 m_MemaProcessor->environmentChanged();
186
187 if (auto editor = dynamic_cast<MemaProcessorEditor*>(m_MemaProcessor->getActiveEditor()))
188 {
189 editor->lookAndFeelChanged();
190 }
191 }
192}
193
194void Mema::setUIConfigState(const std::unique_ptr<juce::XmlElement>& uiConfigState)
195{
196 m_MemaUIConfigCache = std::make_unique<juce::XmlElement>(*uiConfigState);
197}
198
199const std::unique_ptr<juce::XmlElement>& Mema::getUIConfigState()
200{
201 return m_MemaUIConfigCache;
202}
203
205{
206 m_loadSavefileChooser = std::make_unique<juce::FileChooser>(
207 "Please select the " + juce::JUCEApplication::getInstance()->getApplicationName() + " configuration file you want to load...",
208 juce::File::getSpecialLocation(juce::File::userHomeDirectory),
209 "*.config");
210
211 m_loadSavefileChooser->launchAsync(juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles,
212 [=](const juce::FileChooser& chooser) {
213 juce::File sourceFile(chooser.getResult());
214
215 if (!sourceFile.existsAsFile() || !sourceFile.hasReadAccess())
216 {
217 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
218 .withIconType(juce::MessageBoxIconType::WarningIcon)
219 .withTitle("Loading failed")
220 .withMessage("The file " + sourceFile.getFileName() + " cannot be accessed for reading.")
221 .withButton("Ok"), nullptr);
222 return false;
223 }
224
225 auto config = MemaAppConfiguration::getInstance();
226 if (!config)
227 {
228 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
229 .withIconType(juce::MessageBoxIconType::WarningIcon)
230 .withTitle("Loading failed")
231 .withMessage("There was an internal error with the configuration.")
232 .withButton("Ok"), nullptr);
233 return false;
234 }
235
236 auto xmlConfig = juce::parseXML(sourceFile);
237 if (!xmlConfig)
238 {
239 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
240 .withIconType(juce::MessageBoxIconType::WarningIcon)
241 .withTitle("Loading failed")
242 .withMessage("The file " + sourceFile.getFileName() + " has invalid contents.")
243 .withButton("Ok"), nullptr);
244 return false;
245 }
246
247 if (!MemaAppConfiguration::isValid(xmlConfig))
248 {
249 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
250 .withIconType(juce::MessageBoxIconType::WarningIcon)
251 .withTitle("Loading failed")
252 .withMessage("The file " + sourceFile.getFileName() + " has invalid contents.")
253 .withButton("Ok"), nullptr);
254 return false;
255 }
256
257 config->SetFlushAndUpdateDisabled();
258 if (!config->resetConfigState(std::move(xmlConfig)))
259 {
260 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
261 .withIconType(juce::MessageBoxIconType::WarningIcon)
262 .withTitle("Loading failed")
263 .withMessage("There was an internal error with applying the configuration.")
264 .withButton("Ok"), nullptr);
265 config->ResetFlushAndUpdateDisabled();
266 return false;
267 }
268 config->ResetFlushAndUpdateDisabled();
269
270 return true;
271 });
272}
273
275{
276 m_loadSavefileChooser = std::make_unique<juce::FileChooser>(
277 "Please select the " + juce::JUCEApplication::getInstance()->getApplicationName() + " configuration file target you want to save to...",
278 juce::File::getSpecialLocation(juce::File::userHomeDirectory).getChildFile(
279 juce::Time::getCurrentTime().toISO8601(true).substring(0, 10) + "_" +
280 juce::JUCEApplication::getInstance()->getApplicationName() + ".config"),
281 "*.config");
282
283 m_loadSavefileChooser->launchAsync(juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::canSelectFiles,
284 [=](const juce::FileChooser& chooser) {
285 juce::File targetFile(chooser.getResult());
286
287 // enforce the .config extension
288 if (targetFile.getFileExtension() != ".config")
289 targetFile = targetFile.withFileExtension(".config");
290
291 if (!targetFile.hasWriteAccess())
292 {
293 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
294 .withIconType(juce::MessageBoxIconType::WarningIcon)
295 .withTitle("Saving failed")
296 .withMessage("The file " + targetFile.getFileName() + " cannot be accessed for writing.")
297 .withButton("Ok"), nullptr);
298 return false;
299 }
300
301 auto config = MemaAppConfiguration::getInstance();
302 if (!config)
303 {
304 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
305 .withIconType(juce::MessageBoxIconType::WarningIcon)
306 .withTitle("Saving failed")
307 .withMessage("There was an internal error with the configuration (0x0).")
308 .withButton("Ok"), nullptr);
309 return false;
310 }
311
312 auto xmlConfig = config->getConfigState();
313 if (!xmlConfig)
314 {
315 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
316 .withIconType(juce::MessageBoxIconType::WarningIcon)
317 .withTitle("Saving failed")
318 .withMessage("There was an internal error with the configuration (0x1).")
319 .withButton("Ok"), nullptr);
320 return false;
321 }
322 else if (!xmlConfig->writeTo(targetFile))
323 {
324 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
325 .withIconType(juce::MessageBoxIconType::WarningIcon)
326 .withTitle("Saving failed")
327 .withMessage("There was an error when writing the configuration to disk.")
328 .withButton("Ok"), nullptr);
329 return false;
330 }
331 else
332 return true;
333 });
334}
335
336
337}
static juce::String getTagName(TagID ID)
@ PROCESSORCONFIG
Audio processor settings.
@ UICONFIG
UI layout and appearance.
bool isValid() override
Returns true when the loaded XML contains all required configuration nodes.
Top-level editor component for the Mema processor — composes the input, crosspoint,...
static constexpr int s_maxChannelCount
Maximum number of input or output channels supported by the routing matrix.
static constexpr int s_minOutputsCount
Minimum number of output channels (always at least 1).
static constexpr int s_minInputsCount
Minimum number of input channels (always at least 1).
void timerCallback() override
Periodic timer callback used to poll CPU usage and trigger deferred dumps.
Definition Mema.cpp:93
const std::unique_ptr< MemaProcessor > & getMemaProcessor() const
Provides read access to the owned MemaProcessor instance.
Definition Mema.cpp:135
void setUIConfigState(const std::unique_ptr< juce::XmlElement > &uiConfigState)
Stores a UI configuration state snapshot.
Definition Mema.cpp:194
~Mema() override
Definition Mema.cpp:87
void triggerPromptSaveConfig()
Opens a file chooser dialog to save the configuration file.
Definition Mema.cpp:274
void performConfigurationDump() override
Serializes the current configuration to file.
Definition Mema.cpp:151
juce::Component * getMemaProcessorEditor()
Returns the main processor editor component.
Definition Mema.cpp:106
juce::Component * getDeviceSetupComponent()
Returns the audio device setup component.
Definition Mema.cpp:127
void clearUICallbacks()
Clears all UI callback functions.
Definition Mema.cpp:140
const std::unique_ptr< juce::XmlElement > & getUIConfigState()
Returns the cached UI configuration state.
Definition Mema.cpp:199
void triggerPromptLoadConfig()
Opens a file chooser dialog to load a configuration file.
Definition Mema.cpp:204
void onConfigUpdated() override
Reacts to configuration changes and updates internal state.
Definition Mema.cpp:166
void propagateLookAndFeelChanged()
Propagates a look-and-feel change to all owned components.
Definition Mema.cpp:181