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 updater = JUCEAppBasics::WebUpdateDetector::getInstance();
77 updater->SetReferenceVersion(ProjectInfo::versionString);
78 updater->SetDownloadUpdateWebAddress("https://github.com/christianahrens/mema/releases/latest");
79 updater->CheckForNewVersion(true, "https://raw.githubusercontent.com/ChristianAhrens/Mema/refs/heads/main/");
80#endif
81}
82
84{
85 if (m_MemaProcessor)
86 m_MemaProcessor->editorBeingDeleted(m_MemaProcessor->getActiveEditor());
87}
88
90{
91 if (m_MemaProcessor && m_MemaProcessor->getDeviceManager())
92 {
93 if (onCpuUsageUpdate)
94 onCpuUsageUpdate(int(m_MemaProcessor->getDeviceManager()->getCpuUsage() * 100.0));
95 if (onNetworkUsageUpdate)
96 onNetworkUsageUpdate(m_MemaProcessor->getNetworkHealth());
97 if (onServiceDiscoveryTopologyUpdate)
98 onServiceDiscoveryTopologyUpdate(m_MemaProcessor->getDiscoveredServicesTopology());
99 }
100}
101
103{
104 if (m_MemaProcessor)
105 {
106 if (nullptr == m_MemaProcessor->getActiveEditor())
107 m_MemaProcessor->createEditorIfNeeded();
108
109 if (auto editor = dynamic_cast<MemaProcessorEditor*>(m_MemaProcessor->getActiveEditor()))
110 {
111 jassert(onEditorSizeChangeRequested); // should be set before handling the ui component!
112 editor->onEditorSizeChangeRequested = onEditorSizeChangeRequested;
113 }
114
115 m_MemaProcessor->updateCommanders();
116
117 return m_MemaProcessor->getActiveEditor();
118 }
119 else
120 return nullptr;
121}
122
124{
125 if (m_audioDeviceSelectComponent)
126 return m_audioDeviceSelectComponent.get();
127 else
128 return nullptr;
129}
130
132{
133 onEditorSizeChangeRequested = nullptr;
134 onCpuUsageUpdate = nullptr;
135 onNetworkUsageUpdate = nullptr;
136 onServiceDiscoveryTopologyUpdate = nullptr;
137
138 if (auto editor = dynamic_cast<MemaProcessorEditor*>(m_MemaProcessor->getActiveEditor()))
139 editor->onEditorSizeChangeRequested = nullptr;
140}
141
143{
144 if (m_config)
145 {
146 auto stateXml = m_config->getConfigState();
147 if (stateXml)
148 {
149 if (m_MemaProcessor)
150 m_config->setConfigState(m_MemaProcessor->createStateXml(), MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG));
151 if (m_MemaUIConfigCache)
152 m_config->setConfigState(std::make_unique<juce::XmlElement>(*m_MemaUIConfigCache), MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::UICONFIG));
153 }
154 }
155}
156
158{
159 auto processorConfigState = m_config->getConfigState(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG));
160 if (processorConfigState && m_MemaProcessor)
161 {
162 m_MemaProcessor->setStateXml(processorConfigState.get());
163 }
164
165 auto uiConfigState = m_config->getConfigState(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::UICONFIG));
166 if (uiConfigState && m_MemaProcessor)
167 {
168 m_MemaUIConfigCache = std::make_unique<juce::XmlElement>(*uiConfigState);
169 }
170}
171
173{
174 if (m_MemaProcessor)
175 {
176 m_MemaProcessor->environmentChanged();
177
178 if (auto editor = dynamic_cast<MemaProcessorEditor*>(m_MemaProcessor->getActiveEditor()))
179 {
180 editor->lookAndFeelChanged();
181 }
182 }
183}
184
185void Mema::setUIConfigState(const std::unique_ptr<juce::XmlElement>& uiConfigState)
186{
187 m_MemaUIConfigCache = std::make_unique<juce::XmlElement>(*uiConfigState);
188}
189
190const std::unique_ptr<juce::XmlElement>& Mema::getUIConfigState()
191{
192 return m_MemaUIConfigCache;
193}
194
196{
197 m_loadSavefileChooser = std::make_unique<juce::FileChooser>(
198 "Please select the " + juce::JUCEApplication::getInstance()->getApplicationName() + " configuration file you want to load...",
199 juce::File::getSpecialLocation(juce::File::userHomeDirectory),
200 "*.config");
201
202 m_loadSavefileChooser->launchAsync(juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles,
203 [=](const juce::FileChooser& chooser) {
204 juce::File sourceFile(chooser.getResult());
205
206 if (!sourceFile.existsAsFile() || !sourceFile.hasReadAccess())
207 {
208 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
209 .withIconType(juce::MessageBoxIconType::WarningIcon)
210 .withTitle("Loading failed")
211 .withMessage("The file " + sourceFile.getFileName() + " cannot be accessed for reading.")
212 .withButton("Ok"), nullptr);
213 return false;
214 }
215
216 auto config = MemaAppConfiguration::getInstance();
217 if (!config)
218 {
219 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
220 .withIconType(juce::MessageBoxIconType::WarningIcon)
221 .withTitle("Loading failed")
222 .withMessage("There was an internal error with the configuration.")
223 .withButton("Ok"), nullptr);
224 return false;
225 }
226
227 auto xmlConfig = juce::parseXML(sourceFile);
228 if (!xmlConfig)
229 {
230 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
231 .withIconType(juce::MessageBoxIconType::WarningIcon)
232 .withTitle("Loading failed")
233 .withMessage("The file " + sourceFile.getFileName() + " has invalid contents.")
234 .withButton("Ok"), nullptr);
235 return false;
236 }
237
238 if (!MemaAppConfiguration::isValid(xmlConfig))
239 {
240 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
241 .withIconType(juce::MessageBoxIconType::WarningIcon)
242 .withTitle("Loading failed")
243 .withMessage("The file " + sourceFile.getFileName() + " has invalid contents.")
244 .withButton("Ok"), nullptr);
245 return false;
246 }
247
248 config->SetFlushAndUpdateDisabled();
249 if (!config->resetConfigState(std::move(xmlConfig)))
250 {
251 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
252 .withIconType(juce::MessageBoxIconType::WarningIcon)
253 .withTitle("Loading failed")
254 .withMessage("There was an internal error with applying the configuration.")
255 .withButton("Ok"), nullptr);
256 config->ResetFlushAndUpdateDisabled();
257 return false;
258 }
259 config->ResetFlushAndUpdateDisabled();
260
261 return true;
262 });
263}
264
266{
267 m_loadSavefileChooser = std::make_unique<juce::FileChooser>(
268 "Please select the " + juce::JUCEApplication::getInstance()->getApplicationName() + " configuration file target you want to save to...",
269 juce::File::getSpecialLocation(juce::File::userHomeDirectory).getChildFile(
270 juce::Time::getCurrentTime().toISO8601(true).substring(0, 10) + "_" +
271 juce::JUCEApplication::getInstance()->getApplicationName() + ".config"),
272 "*.config");
273
274 m_loadSavefileChooser->launchAsync(juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::canSelectFiles,
275 [=](const juce::FileChooser& chooser) {
276 juce::File targetFile(chooser.getResult());
277
278 // enforce the .config extension
279 if (targetFile.getFileExtension() != ".config")
280 targetFile = targetFile.withFileExtension(".config");
281
282 if (!targetFile.hasWriteAccess())
283 {
284 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
285 .withIconType(juce::MessageBoxIconType::WarningIcon)
286 .withTitle("Saving failed")
287 .withMessage("The file " + targetFile.getFileName() + " cannot be accessed for writing.")
288 .withButton("Ok"), nullptr);
289 return false;
290 }
291
292 auto config = MemaAppConfiguration::getInstance();
293 if (!config)
294 {
295 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
296 .withIconType(juce::MessageBoxIconType::WarningIcon)
297 .withTitle("Saving failed")
298 .withMessage("There was an internal error with the configuration (0x0).")
299 .withButton("Ok"), nullptr);
300 return false;
301 }
302
303 auto xmlConfig = config->getConfigState();
304 if (!xmlConfig)
305 {
306 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
307 .withIconType(juce::MessageBoxIconType::WarningIcon)
308 .withTitle("Saving failed")
309 .withMessage("There was an internal error with the configuration (0x1).")
310 .withButton("Ok"), nullptr);
311 return false;
312 }
313 else if (!xmlConfig->writeTo(targetFile))
314 {
315 juce::AlertWindow::showAsync(juce::MessageBoxOptions()
316 .withIconType(juce::MessageBoxIconType::WarningIcon)
317 .withTitle("Saving failed")
318 .withMessage("There was an error when writing the configuration to disk.")
319 .withButton("Ok"), nullptr);
320 return false;
321 }
322 else
323 return true;
324 });
325}
326
327
328}
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:89
void setUIConfigState(const std::unique_ptr< juce::XmlElement > &uiConfigState)
Stores a UI configuration state snapshot.
Definition Mema.cpp:185
~Mema() override
Definition Mema.cpp:83
void triggerPromptSaveConfig()
Opens a file chooser dialog to save the configuration file.
Definition Mema.cpp:265
void performConfigurationDump() override
Serializes the current configuration to file.
Definition Mema.cpp:142
juce::Component * getMemaProcessorEditor()
Returns the main processor editor component.
Definition Mema.cpp:102
juce::Component * getDeviceSetupComponent()
Returns the audio device setup component.
Definition Mema.cpp:123
void clearUICallbacks()
Clears all UI callback functions.
Definition Mema.cpp:131
const std::unique_ptr< juce::XmlElement > & getUIConfigState()
Returns the cached UI configuration state.
Definition Mema.cpp:190
void triggerPromptLoadConfig()
Opens a file chooser dialog to load a configuration file.
Definition Mema.cpp:195
void onConfigUpdated() override
Reacts to configuration changes and updates internal state.
Definition Mema.cpp:157
void propagateLookAndFeelChanged()
Propagates a look-and-feel change to all owned components.
Definition Mema.cpp:172
Definition Mema.cpp:27