Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
MemaProcessor.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 "MemaProcessor.h"
20
22#include "MemaCommanders.h"
23#include "MemaServiceData.h"
24#include "../MemaAppConfiguration.h"
25
26#include <CustomLookAndFeel.h>
27
28namespace Mema
29{
30
31
32//==============================================================================
34{
35public:
36 void setInputMute(std::uint16_t channel, bool muteState, int userId) override
37 {
38 if (m_networkServer && m_networkServer->hasActiveConnections())
39 {
40 std::map<std::uint16_t, bool> inputMuteStates;
41 std::map<std::uint16_t, bool> outputMuteStates;
42 std::map<std::uint16_t, std::map<std::uint16_t, bool>> crosspointStates;
43 std::map<std::uint16_t, std::map<std::uint16_t, float>> crosspointValues;
44
45 inputMuteStates[channel] = muteState;
46
47 auto sendIds = m_networkServer->getActiveConnectionIds();
48 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
49 m_networkServer->enqueueMessage(std::make_unique<ControlParametersMessage>(inputMuteStates, outputMuteStates, crosspointStates, crosspointValues)->getSerializedMessage(), sendIds);
50 }
51 };
52
53 void setOutputMute(std::uint16_t channel, bool muteState, int userId) override
54 {
55 if (m_networkServer && m_networkServer->hasActiveConnections())
56 {
57 std::map<std::uint16_t, bool> inputMuteStates;
58 std::map<std::uint16_t, bool> outputMuteStates;
59 std::map<std::uint16_t, std::map<std::uint16_t, bool>> crosspointStates;
60 std::map<std::uint16_t, std::map<std::uint16_t, float>> crosspointValues;
61
62 outputMuteStates[channel] = muteState;
63
64 auto sendIds = m_networkServer->getActiveConnectionIds();
65 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
66 m_networkServer->enqueueMessage(std::make_unique<ControlParametersMessage>(inputMuteStates, outputMuteStates, crosspointStates, crosspointValues)->getSerializedMessage(), sendIds);
67 }
68 };
69
70 void setCrosspointEnabledValue(std::uint16_t input, std::uint16_t output, bool enabledState, int userId) override
71 {
72 if (m_networkServer && m_networkServer->hasActiveConnections())
73 {
74 std::map<std::uint16_t, bool> inputMuteStates;
75 std::map<std::uint16_t, bool> outputMuteStates;
76 std::map<std::uint16_t, std::map<std::uint16_t, bool>> crosspointStates;
77 std::map<std::uint16_t, std::map<std::uint16_t, float>> crosspointValues;
78
79 crosspointStates[input][output] = enabledState;
80
81 auto sendIds = m_networkServer->getActiveConnectionIds();
82 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
83 m_networkServer->enqueueMessage(std::make_unique<ControlParametersMessage>(inputMuteStates, outputMuteStates, crosspointStates, crosspointValues)->getSerializedMessage(), sendIds);
84 }
85 };
86
87 void setCrosspointFactorValue(std::uint16_t input, std::uint16_t output, float factor, int userId) override
88 {
89 if (m_networkServer && m_networkServer->hasActiveConnections())
90 {
91 std::map<std::uint16_t, bool> inputMuteStates;
92 std::map<std::uint16_t, bool> outputMuteStates;
93 std::map<std::uint16_t, std::map<std::uint16_t, bool>> crosspointStates;
94 std::map<std::uint16_t, std::map<std::uint16_t, float>> crosspointValues;
95
96 crosspointValues[input][output] = factor;
97
98 auto sendIds = m_networkServer->getActiveConnectionIds();
99 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
100 m_networkServer->enqueueMessage(std::make_unique<ControlParametersMessage>(inputMuteStates, outputMuteStates, crosspointStates, crosspointValues)->getSerializedMessage(), sendIds);
101 }
102 };
103
104 void setPluginParameterInfos(const std::vector<PluginParameterInfo>& parameterInfos, const std::string& name, int userId = -1) override
105 {
106 if (m_networkServer && m_networkServer->hasActiveConnections())
107 {
108 auto sendIds = m_networkServer->getActiveConnectionIds();
109 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
110 m_networkServer->enqueueMessage(std::make_unique<PluginParameterInfosMessage>(name, parameterInfos)->getSerializedMessage(), sendIds);
111 }
112 }
113
114 void setPluginParameterValue(std::uint16_t index, std::string id, float currentValue, int userId = -1) override
115 {
116 if (m_networkServer && m_networkServer->hasActiveConnections())
117 {
118 DBG(juce::String(__FUNCTION__) << " sending to net: " << int(index) << " > " << currentValue);
119 auto sendIds = m_networkServer->getActiveConnectionIds();
120 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
121 m_networkServer->enqueueMessage(std::make_unique<PluginParameterValueMessage>(index, id, currentValue)->getSerializedMessage(), sendIds);
122 }
123 }
124
125 void setIOCount(std::uint16_t inputCount, std::uint16_t outputCount) override
126 {
127 if (m_networkServer && m_networkServer->hasActiveConnections())
128 {
129 m_networkServer->enqueueMessage(std::make_unique<ReinitIOCountMessage>(inputCount, outputCount)->getSerializedMessage());
130 }
131 };
132
133 void setNetworkConnection(const std::shared_ptr<InterprocessConnectionServerImpl>& networkServer)
134 {
135 m_networkServer = networkServer;
136 }
137
138private:
139 void setChannelCount(std::uint16_t channelCount) override { ignoreUnused(channelCount); };
140
141private:
142 std::shared_ptr<InterprocessConnectionServerImpl> m_networkServer;
143
144};
145
146//==============================================================================
147MemaProcessor::MemaProcessor(XmlElement* stateXml) :
148 juce::AudioProcessor()
149{
150#ifdef RUN_MESSAGE_TESTS
151 runTests();
152#endif
153
154 // prepare max sized processing data buffer
155 m_processorChannels = new float* [s_maxChannelCount];
156 for (auto i = 0; i < s_maxChannelCount; i++)
157 {
158 m_processorChannels[i] = new float[s_maxNumSamples];
159 for (auto j = 0; j < s_maxNumSamples; j++)
160 {
161 m_processorChannels[i][j] = 0.0f;
162 }
163 }
164
165 m_inputDataAnalyzer = std::make_unique<ProcessorDataAnalyzer>();
166 m_inputDataAnalyzer->setUseProcessingTypes(true, false, false);
167 m_outputDataAnalyzer = std::make_unique<ProcessorDataAnalyzer>();
168 m_outputDataAnalyzer->setUseProcessingTypes(true, false, false);
169
170 m_deviceManager = std::make_unique<AudioDeviceManager>();
171 m_deviceManager->addAudioCallback(this);
172 m_deviceManager->addChangeListener(this);
173
174 if (!setStateXml(stateXml))
175 {
176 setStateXml(nullptr); // call without actual xml config causes default init
177 triggerConfigurationUpdate(false);
178 }
179
180 // init the announcement of this app instance as discoverable service
181 m_serviceTopologyManager = std::make_unique<JUCEAppBasics::ServiceTopologyManager>(
184 JUCEAppBasics::ServiceTopologyManager::getServiceDescription(),
185 JUCEAppBasics::ServiceTopologyManager::getServiceDescription(),
188
189 m_networkServer = std::make_shared<InterprocessConnectionServerImpl>();
190 m_networkServer->beginWaitingForSocket(Mema::ServiceData::getConnectionPort());
191 m_networkServer->onConnectionCreated = [=](int connectionId) {
192 auto connection = dynamic_cast<InterprocessConnectionImpl*>(m_networkServer->getActiveConnection(connectionId).get());
193 if (connection)
194 {
195 connection->onConnectionLost = [=](int connectionId) { DBG(juce::String(__FUNCTION__) << " connection " << connectionId << " lost");
196 m_trafficTypesPerConnection.erase(connectionId);
197 };
198 connection->onConnectionMade = [=](int connectionId ) { DBG(juce::String(__FUNCTION__) << " connection " << connectionId << " made");
199 m_trafficTypesPerConnection[connectionId].clear();
200 if (m_networkServer && m_networkServer->hasActiveConnection(connectionId))
201 {
202 auto paletteStyle = JUCEAppBasics::CustomLookAndFeel::PaletteStyle::PS_Dark;
203 if (getActiveEditor())
204 if (auto claf = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getActiveEditor()->getLookAndFeel()))
205 paletteStyle = claf->getPaletteStyle();
206
207 std::map<std::uint16_t, bool> inputMuteStates;
208 std::map<std::uint16_t, bool> outputMuteStates;
209 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
210 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
211 {
212 const ScopedLock sl(m_audioDeviceIOCallbackLock);
213 inputMuteStates = m_inputMuteStates;
214 outputMuteStates = m_outputMuteStates;
215 matrixCrosspointStates = m_matrixCrosspointStates;
216 matrixCrosspointValues = m_matrixCrosspointValues;
217 }
218
219 auto sendIds = std::vector<int>{ connectionId };
220 auto success = true;
221 success = success && m_networkServer->enqueueMessage(std::make_unique<AnalyzerParametersMessage>(int(getSampleRate()), getBlockSize())->getSerializedMessage(), sendIds);
222 success = success && m_networkServer->enqueueMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount)->getSerializedMessage(), sendIds);
223 success = success && m_networkServer->enqueueMessage(std::make_unique<EnvironmentParametersMessage>(paletteStyle)->getSerializedMessage(), sendIds);
224 success = success && m_networkServer->enqueueMessage(std::make_unique<ControlParametersMessage>(inputMuteStates, outputMuteStates, matrixCrosspointStates, matrixCrosspointValues)->getSerializedMessage(), sendIds);
225 success = success && m_networkServer->enqueueMessage(std::make_unique<PluginParameterInfosMessage>(m_pluginInstance ? m_pluginInstance->getName().toStdString() : "", m_pluginParameterInfos)->getSerializedMessage(), sendIds);
226 if (!success)
227 m_networkServer->cleanupDeadConnections();
228 }
229 };
230 connection->onMessageReceived = [=](int connectionId, const juce::MemoryBlock& message) {
231 if (auto knownMessage = Mema::SerializableMessage::initFromMemoryBlock(message))
232 {
233 knownMessage->setId(connectionId);
234 postMessage(knownMessage);
235 }
236 else
237 DBG(juce::String(__FUNCTION__) + " ignoring unknown message");
238 };
239 }
240 };
241 m_networkCommanderWrapper = std::make_unique<MemaNetworkClientCommanderWrapper>();
242 m_networkCommanderWrapper->setNetworkConnection(m_networkServer);
243 addInputCommander(static_cast<MemaInputCommander*>(m_networkCommanderWrapper.get()));
244 addOutputCommander(static_cast<MemaOutputCommander*>(m_networkCommanderWrapper.get()));
245 addCrosspointCommander(static_cast<MemaCrosspointCommander*>(m_networkCommanderWrapper.get()));
246 addPluginCommander(static_cast<MemaPluginCommander*>(m_networkCommanderWrapper.get()));
247
248 m_timedConfigurationDumper = std::make_unique<juce::TimedCallback>([=]() {
250 {
251 auto config = JUCEAppBasics::AppConfigurationBase::getInstance();
252 if (config != nullptr)
253 config->triggerConfigurationDump(false);
255 }
256 });
257 m_timedConfigurationDumper->startTimer(100);
258}
259
261{
262 m_timedConfigurationDumper->stopTimer();
264 {
265 auto config = JUCEAppBasics::AppConfigurationBase::getInstance();
266 if (config != nullptr)
267 config->triggerConfigurationDump(false);
269 }
270
271 m_networkServer->stop();
272
273 m_deviceManager->removeAudioCallback(this);
274
275 // cleanup processing data buffer
276 for (auto i = 0; i < s_maxChannelCount; i++)
277 delete[] m_processorChannels[i];
278 delete[] m_processorChannels;
279}
280
281std::unique_ptr<juce::XmlElement> MemaProcessor::createStateXml()
282{
283 auto stateXml = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG));
284
285 auto devConfElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::DEVCONFIG));
286 if (m_deviceManager)
287 devConfElm->addChildElement(m_deviceManager->createStateXml().release());
288 stateXml->addChildElement(devConfElm.release());
289
290 auto plgConfElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINCONFIG));
291 plgConfElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::ENABLED), m_pluginEnabled ? 1 : 0);
292 plgConfElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::POST), m_pluginPost ? 1 : 0);
293 if (m_pluginInstance)
294 {
295 plgConfElm->addChildElement(m_pluginInstance->getPluginDescription().createXml().release());
296
297 juce::MemoryBlock destData;
298 m_pluginInstance->getStateInformation(destData);
299 plgConfElm->addTextElement(juce::Base64::toBase64(destData.getData(), destData.getSize()));
300 }
301 for (auto const plgParam : getPluginParameterInfos())
302 {
303 auto plgParamElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINPARAM));
304 plgParamElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::IDX), plgParam.index);
306 plgParamElm->addTextElement(plgParam.toString());
307 plgConfElm->addChildElement(plgParamElm.release());
308 }
309 stateXml->addChildElement(plgConfElm.release());
310
311 std::map<std::uint16_t, bool> inputMuteStates;
312 std::map<std::uint16_t, bool> outputMuteStates;
313 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
314 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
315 {
316 // copy the processing relevant variables to not block audio thread during all the xml handling
317 const ScopedLock sl(m_audioDeviceIOCallbackLock);
318 inputMuteStates = m_inputMuteStates;
319 outputMuteStates = m_outputMuteStates;
320 matrixCrosspointStates = m_matrixCrosspointStates;
321 matrixCrosspointValues = m_matrixCrosspointValues;
322 }
323
324 auto inputMutesElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::INPUTMUTES));
325 juce::StringArray imutestatestr;
326 for (auto const& mutestate : inputMuteStates)
327 imutestatestr.add(juce::String(mutestate.first) + "," + juce::String(mutestate.second ? 1 : 0));
328 inputMutesElm->addTextElement(imutestatestr.joinIntoString(";"));
329 stateXml->addChildElement(inputMutesElm.release());
330
331 auto outputMutesElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::OUTPUTMUTES));
332 juce::StringArray omutestatestr;
333 for (auto const& mutestate : outputMuteStates)
334 omutestatestr.add(juce::String(mutestate.first) + "," + juce::String(mutestate.second ? 1 : 0));
335 outputMutesElm->addTextElement(omutestatestr.joinIntoString(";"));
336 stateXml->addChildElement(outputMutesElm.release());
337
338 auto crosspointGainsElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::CROSSPOINTGAINS));
339 juce::StringArray cgainstatestr;
340 for (auto const& insKV : matrixCrosspointStates)
341 for (auto const& outsKV : insKV.second)
342 cgainstatestr.add(juce::String(insKV.first) + "," + juce::String(outsKV.first) + "," + juce::String(outsKV.second ? 1 : 0) + "," + juce::String(matrixCrosspointValues[insKV.first][outsKV.first])); // "in,out,enabled,gain;"
343 crosspointGainsElm->addTextElement(cgainstatestr.joinIntoString(";"));
344 stateXml->addChildElement(crosspointGainsElm.release());
345
346 return stateXml;
347}
348
349bool MemaProcessor::setStateXml(juce::XmlElement* stateXml)
350{
351 if (nullptr == stateXml || stateXml->getTagName() != MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG))
352 {
353 jassertfalse;
354 return false;
355 }
356
357 juce::XmlElement* deviceSetupXml = nullptr;
358 auto devConfElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::DEVCONFIG));
359 if (nullptr != devConfElm)
360 deviceSetupXml = devConfElm->getChildByName("DEVICESETUP");
361
362 if (m_deviceManager)
363 {
364 // Hacky bit of device manager initialization:
365 // We first intialize it to be able to get a valid device setup,
366 // then initialize with a dummy xml config to trigger the internal xml structure being reset
367 // and finally apply the original initialized device setup again to have the audio running correctly.
368 // If we did not do so, either the internal xml would not be present as long as the first configuration change was made
369 // and therefor no valid config file could be written by the application or the audio would not be running
370 // on first start and manual config would be required.
371 m_deviceManager->initialiseWithDefaultDevices(s_maxChannelCount, s_maxChannelCount);
372 auto audioDeviceSetup = m_deviceManager->getAudioDeviceSetup();
373 auto result = m_deviceManager->initialise(s_maxChannelCount, s_maxChannelCount, deviceSetupXml, true, {}, &audioDeviceSetup);
374 if (result.isNotEmpty())
375 {
376 juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::WarningIcon, juce::JUCEApplication::getInstance()->getApplicationName() + " device init failed", result);
377 return false;
378 }
379 else
380 {
381#if JUCE_IOS
382 if (audioDeviceSetup.bufferSize < 512)
383 audioDeviceSetup.bufferSize = 512; // temp. workaround for iOS where buffersizes <512 lead to no sample data being delivered?
384 m_deviceManager->setAudioDeviceSetup(audioDeviceSetup, true);
385#endif
386 }
387 }
388 else
389 return false;
390
391
392 auto plgConfElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINCONFIG));
393 if (nullptr != plgConfElm)
394 {
397 auto pluginDescriptionXml = plgConfElm->getChildByName("PLUGIN");
398 if (nullptr != pluginDescriptionXml)
399 {
400 auto pluginDescription = juce::PluginDescription();
401 pluginDescription.loadFromXml(*pluginDescriptionXml);
402 setPlugin(pluginDescription);
403 if (m_pluginInstance)
404 {
405 juce::MemoryOutputStream destDataStream;
406 juce::Base64::convertFromBase64(destDataStream, pluginDescriptionXml->getAllSubText());
407 m_pluginInstance->setStateInformation(destDataStream.getData(), int(destDataStream.getDataSize()));
408 }
409 }
410
411 for (auto* plgParamElm : plgConfElm->getChildWithTagNameIterator(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINPARAM)))
412 {
413 if (nullptr != plgParamElm)
414 {
415 auto index = plgParamElm->getIntAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::IDX));
416 auto paramString = plgParamElm->getAllSubText();
417 auto paramInfo = PluginParameterInfo::fromString(paramString);
418 paramInfo.isRemoteControllable = (plgParamElm->getIntAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::CONTROLLABLE)) == 1);
419 jassert(paramInfo.index == index);
420 // update the pluginparameterinfo
421 auto iter = std::find_if(getPluginParameterInfos().begin(), getPluginParameterInfos().end(), [=](const auto& info) { return info.index == index; });
422 if (iter != getPluginParameterInfos().end())
423 iter->initializeFromString(paramString);
424 // set the parameter
425 auto* param = getPluginParameter(index);
426 if (param)
427 {
428 jassert(paramInfo.name == param->getName(100));
429 jassert(paramInfo.label == param->getLabel());
430 //jassert(paramInfo.defaultValue == param->getDefaultValue()); //float rounding issue prevents this from making sense
431 jassert(paramInfo.isAutomatable == param->isAutomatable());
432 jassert(paramInfo.category == param->getCategory());
433 if (auto* rangedParam = dynamic_cast<const juce::RangedAudioParameter*>(param))
434 {
435 auto range = rangedParam->getNormalisableRange();
436 jassert(paramInfo.minValue == range.start);
437 jassert(paramInfo.maxValue == range.end);
438 jassert(paramInfo.stepSize == range.interval);
439 jassert(paramInfo.isDiscrete == range.interval > 0.0f);
440 }
441
442 setPluginParameterRemoteControlInfos(paramInfo.index, paramInfo.isRemoteControllable, paramInfo.type, paramInfo.stepCount);
443 param->setValue(paramInfo.currentValue);
444 }
445 }
446 }
447 }
448
449 std::map<std::uint16_t, bool> inputMuteStates;
450 std::map<std::uint16_t, bool> outputMuteStates;
451 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
452 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
453 auto inputMutesElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::INPUTMUTES));
454 if (nullptr != inputMutesElm)
455 {
456 juce::StringArray inMutestateStrList;
457 auto inputMutesElmTxt = inputMutesElm->getAllSubText();
458 inMutestateStrList.addTokens(inputMutesElmTxt, ";", "");
459 jassert(inMutestateStrList.size() == m_inputChannelCount);
460 if (inMutestateStrList.size() != m_inputChannelCount)
461 {
462 // if the config data is insane, initialize with defaults
463 DBG(juce::String(__FUNCTION__) + " config data for input mutes is insane -> resetting to default.");
464 for (auto in = std::uint16_t(1); in <= m_inputChannelCount; in++)
465 inputMuteStates[in] = false;
466 }
467 else
468 {
469 // otherwise use what is available from config
470 for (auto const& inMutestateStr : inMutestateStrList)
471 {
472 juce::StringArray inMutestateStrSplit;
473 inMutestateStrSplit.addTokens(inMutestateStr, ",", "");
474 jassert(2 == inMutestateStrSplit.size());
475 if (2 == inMutestateStrSplit.size())
476 inputMuteStates[std::uint16_t(inMutestateStrSplit[0].getIntValue())] = (1 == std::uint16_t(inMutestateStrSplit[1].getIntValue()));
477 }
478 }
479 }
480 auto outputMutesElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::OUTPUTMUTES));
481 if (nullptr != outputMutesElm)
482 {
483 juce::StringArray outMutestateStrList;
484 auto outputMutesElmTxt = outputMutesElm->getAllSubText();
485 outMutestateStrList.addTokens(outputMutesElmTxt, ";", "");
486 jassert(outMutestateStrList.size() == m_outputChannelCount);
487 if (outMutestateStrList.size() != m_outputChannelCount)
488 {
489 // if the config data is insane, initialize with defaults
490 DBG(juce::String(__FUNCTION__) + " config data for output mutes is insane -> resetting to default.");
491 for (auto out = std::uint16_t(1); out <= m_outputChannelCount; out++)
492 outputMuteStates[out] = false;
493 }
494 else
495 {
496 // otherwise use what is available from config
497 for (auto const& outMutestateStr : outMutestateStrList)
498 {
499 juce::StringArray outMutestateStrSplit;
500 outMutestateStrSplit.addTokens(outMutestateStr, ",", "");
501 jassert(2 == outMutestateStrSplit.size());
502 if (2 == outMutestateStrSplit.size())
503 outputMuteStates[std::uint16_t(outMutestateStrSplit[0].getIntValue())] = (1 == std::uint16_t(outMutestateStrSplit[1].getIntValue()));
504 }
505 }
506 }
507 auto crosspointGainsElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::CROSSPOINTGAINS));
508 if (nullptr != crosspointGainsElm)
509 {
510 juce::StringArray crossGainstateStrList;
511 auto crossGainsElmTxt = crosspointGainsElm->getAllSubText();
512 crossGainstateStrList.addTokens(crossGainsElmTxt, ";", "");
513 jassert(crossGainstateStrList.size() == (m_inputChannelCount * m_outputChannelCount));
514 if (crossGainstateStrList.size() != (m_inputChannelCount * m_outputChannelCount))
515 {
516 // if the config data is insane, initialize with defaults
517 DBG(juce::String(__FUNCTION__) + " config data for crosspoints is insane -> resetting to default.");
518 for (auto in = std::uint16_t(1); in <= m_inputChannelCount; in++)
519 {
520 for (auto out = std::uint16_t(1); out <= m_outputChannelCount; out++)
521 {
522 auto state = false;
523 auto value = 0.0f;
524 if (in == out)
525 {
526 state = true;
527 value = 1.0f;
528 }
529 matrixCrosspointStates[in][out] = state;
530 matrixCrosspointValues[in][out] = value;
531 }
532 }
533 }
534 else
535 {
536 // otherwise use what is available from config
537 for (auto const& crossGainstateStr : crossGainstateStrList)
538 {
539 juce::StringArray crossGainstateStrSplit;
540 crossGainstateStrSplit.addTokens(crossGainstateStr, ",", "");
541 jassert(4 == crossGainstateStrSplit.size());
542 if (4 == crossGainstateStrSplit.size())
543 {
544 matrixCrosspointStates[std::uint16_t(crossGainstateStrSplit[0].getIntValue())][std::uint16_t(crossGainstateStrSplit[1].getIntValue())] = 1 == std::uint16_t(crossGainstateStrSplit[2].getIntValue());
545 matrixCrosspointValues[std::uint16_t(crossGainstateStrSplit[0].getIntValue())][std::uint16_t(crossGainstateStrSplit[1].getIntValue())] = crossGainstateStrSplit[3].getFloatValue();
546 }
547 }
548 }
549 }
550#ifdef DEBUG
551 // sanity check symmetry of crosspoint states
552 auto crosspointStateOutCount = size_t(0);
553 if (0 != matrixCrosspointStates.size())
554 {
555 crosspointStateOutCount = matrixCrosspointStates.begin()->second.size();
556 for (auto const& cpStatKV : matrixCrosspointStates)
557 {
558 jassert(crosspointStateOutCount == cpStatKV.second.size());
559 }
560 }
561 else
562 jassertfalse; // empty crosspoint matrix ?!
563
564 // sanity check symmetry of crosspoint values
565 auto crosspointValOutCount = size_t(0);
566 if (0 != matrixCrosspointValues.size())
567 {
568 crosspointValOutCount = matrixCrosspointValues.begin()->second.size();
569 for (auto const& cpValKV : matrixCrosspointValues)
570 {
571 jassert(crosspointValOutCount == cpValKV.second.size());
572 }
573 }
574 else
575 jassertfalse; // empty crosspoint matrix ?!
576#endif
577 {
578 // copy the processing relevant variables from temp vars here to not block audio thread during all the xml handling above
579 const ScopedLock sl(m_audioDeviceIOCallbackLock);
580 m_inputMuteStates = inputMuteStates;
581 m_outputMuteStates = outputMuteStates;
582 m_matrixCrosspointStates = matrixCrosspointStates;
583 m_matrixCrosspointValues = matrixCrosspointValues;
584 }
585
586 return true;
587}
588
590{
591 auto paletteStyle = JUCEAppBasics::CustomLookAndFeel::PaletteStyle::PS_Dark;
592 if (getActiveEditor())
593 if (auto claf = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getActiveEditor()->getLookAndFeel()))
594 paletteStyle = claf->getPaletteStyle();
595
596 postMessage(std::make_unique<EnvironmentParametersMessage>(paletteStyle).release());
597}
598
600{
601 postMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount).release());
602}
603
605{
606 if (m_inputDataAnalyzer)
607 m_inputDataAnalyzer->addListener(listener);
608}
609
611{
612 if (m_inputDataAnalyzer)
613 m_inputDataAnalyzer->removeListener(listener);
614}
615
617{
618 if (m_outputDataAnalyzer)
619 m_outputDataAnalyzer->addListener(listener);
620}
621
623{
624 if (m_outputDataAnalyzer)
625 m_outputDataAnalyzer->removeListener(listener);
626}
627
629{
630 if (commander == nullptr)
631 return;
632
633 if (std::find(m_inputCommanders.begin(), m_inputCommanders.end(), commander) == m_inputCommanders.end())
634 {
635 initializeInputCommander(commander);
636
637 m_inputCommanders.push_back(commander);
638 commander->setInputMuteChangeCallback([=](MemaChannelCommander* sender, int channel, bool state) { return setInputMuteState(std::uint16_t(channel), state, sender); } );
639 }
640}
641
643{
644 if (nullptr != commander)
645 {
646 const ScopedLock sl(m_audioDeviceIOCallbackLock);
647 for (auto const& inputMuteStatesKV : m_inputMuteStates)
648 commander->setInputMute(inputMuteStatesKV.first, inputMuteStatesKV.second);
649 }
650}
651
653{
654 if (commander == nullptr)
655 return;
656
657 auto existingInputCommander = std::find(m_inputCommanders.begin(), m_inputCommanders.end(), commander);
658 if (existingInputCommander != m_inputCommanders.end())
659 m_inputCommanders.erase(existingInputCommander);
660}
661
663{
664 if (commander == nullptr)
665 return;
666
667 if (std::find(m_outputCommanders.begin(), m_outputCommanders.end(), commander) == m_outputCommanders.end())
668 {
669 initializeOutputCommander(commander);
670
671 m_outputCommanders.push_back(commander);
672 commander->setOutputMuteChangeCallback([=](MemaChannelCommander* sender, int channel, bool state) { return setOutputMuteState(std::uint16_t(channel), state, sender); });
673 }
674}
675
677{
678 if (nullptr != commander)
679 {
680 const ScopedLock sl(m_audioDeviceIOCallbackLock);
681 for (auto const& outputMuteStatesKV : m_outputMuteStates)
682 commander->setOutputMute(outputMuteStatesKV.first, outputMuteStatesKV.second);
683 }
684}
685
687{
688 if (commander == nullptr)
689 return;
690
691 auto existingOutputCommander = std::find(m_outputCommanders.begin(), m_outputCommanders.end(), commander);
692 if (existingOutputCommander != m_outputCommanders.end())
693 m_outputCommanders.erase(existingOutputCommander);
694}
695
697{
698 if (commander == nullptr)
699 return;
700
701 if (std::find(m_crosspointCommanders.begin(), m_crosspointCommanders.end(), commander) == m_crosspointCommanders.end())
702 {
704
705 m_crosspointCommanders.push_back(commander);
706 commander->setCrosspointEnabledChangeCallback([=](MemaChannelCommander* sender, int input, int output, bool state) { return setMatrixCrosspointEnabledValue(std::uint16_t(input), std::uint16_t(output), state, sender); });
707 commander->setCrosspointFactorChangeCallback([=](MemaChannelCommander* sender, int input, int output, float factor) { return setMatrixCrosspointFactorValue(std::uint16_t(input), std::uint16_t(output), factor, sender); });
708 }
709}
710
712{
713 if (nullptr != commander)
714 {
715 auto matrixCrosspointStates = std::map<std::uint16_t, std::map<std::uint16_t, bool>>();
716 auto matrixCrosspointValues = std::map<std::uint16_t, std::map<std::uint16_t, float>>();
717 {
718 const ScopedLock sl(m_audioDeviceIOCallbackLock);
719 matrixCrosspointStates = m_matrixCrosspointStates;
720 matrixCrosspointValues = m_matrixCrosspointValues;
721 }
722 for (auto const& matrixCrosspointStateKV : matrixCrosspointStates)
723 {
724 for (auto const& matrixCrosspointStateNodeKV : matrixCrosspointStateKV.second)
725 {
726 auto& input = matrixCrosspointStateKV.first;
727 auto& output = matrixCrosspointStateNodeKV.first;
728 auto& enabled = matrixCrosspointStateNodeKV.second;
729 commander->setCrosspointEnabledValue(input, output, enabled);
730 }
731 }
732 for (auto const& matrixCrosspointValKV : matrixCrosspointValues)
733 {
734 for (auto const& matrixCrosspointValNodeKV : matrixCrosspointValKV.second)
735 {
736 auto& input = matrixCrosspointValKV.first;
737 auto& output = matrixCrosspointValNodeKV.first;
738 auto& val = matrixCrosspointValNodeKV.second;
739 commander->setCrosspointFactorValue(input, output, val);
740 }
741 }
742 }
743}
744
746{
747 if (commander == nullptr)
748 return;
749
750 auto existingCrosspointCommander = std::find(m_crosspointCommanders.begin(), m_crosspointCommanders.end(), commander);
751 if (existingCrosspointCommander != m_crosspointCommanders.end())
752 m_crosspointCommanders.erase(existingCrosspointCommander);
753}
754
756{
757 if (commander == nullptr)
758 return;
759
760 if (std::find(m_pluginCommanders.begin(), m_pluginCommanders.end(), commander) == m_pluginCommanders.end())
761 {
762 initializePluginCommander(commander);
763
764 m_pluginCommanders.push_back(commander);
765 commander->setPluginParameterValueChangeCallback([=](MemaPluginCommander* sender, std::uint16_t index, std::string id, float value) { return setPluginParameterValue(index, id, value, sender); });
766 }
767}
768
770{
771 if (nullptr != commander)
772 {
774 //for (auto const& inputMuteStatesKV : m_inputMuteStates)
775 // commander->setInputMute(inputMuteStatesKV.first, inputMuteStatesKV.second);
776 }
777}
778
780{
781 if (commander == nullptr)
782 return;
783
784 auto existingPluginCommander = std::find(m_pluginCommanders.begin(), m_pluginCommanders.end(), commander);
785 if (existingPluginCommander != m_pluginCommanders.end())
786 m_pluginCommanders.erase(existingPluginCommander);
787}
788
790{
791 for (auto const& ic : m_inputCommanders)
792 {
793 ic->setChannelCount(m_inputChannelCount > 0 ? std::uint16_t(m_inputChannelCount) : 0);
795 }
796 for (auto const& cc : m_crosspointCommanders)
797 {
798 cc->setIOCount(m_inputChannelCount > 0 ? std::uint16_t(m_inputChannelCount) : 0, m_outputChannelCount > 0 ? std::uint16_t(m_outputChannelCount) : 0);
800 }
801 for (auto const& oc : m_outputCommanders)
802 {
803 oc->setChannelCount(m_outputChannelCount > 0 ? std::uint16_t(m_outputChannelCount) : 0);
805 }
806 for (auto const& pc : m_pluginCommanders)
807 {
809 }
810}
811
812bool MemaProcessor::getInputMuteState(std::uint16_t inputChannelNumber)
813{
814 jassert(inputChannelNumber > 0);
815 const ScopedLock sl(m_audioDeviceIOCallbackLock);
816 return m_inputMuteStates[inputChannelNumber];
817}
818
819void MemaProcessor::setInputMuteState(std::uint16_t inputChannelNumber, bool muted, MemaChannelCommander* sender, int userId)
820{
821 jassert(inputChannelNumber > 0);
822
823 for (auto const& inputCommander : m_inputCommanders)
824 {
825 if (inputCommander != reinterpret_cast<MemaInputCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
826 inputCommander->setInputMute(inputChannelNumber, muted, userId);
827 }
828
829 {
830 const ScopedLock sl(m_audioDeviceIOCallbackLock);
831 m_inputMuteStates[inputChannelNumber] = muted;
832 }
833
835}
836
837bool MemaProcessor::getMatrixCrosspointEnabledValue(std::uint16_t inputNumber, std::uint16_t outputNumber)
838{
839 jassert(inputNumber > 0 && outputNumber > 0);
840 {
841 const ScopedLock sl(m_audioDeviceIOCallbackLock);
842 if (m_matrixCrosspointStates.count(inputNumber) != 0)
843 if (m_matrixCrosspointStates.at(inputNumber).count(outputNumber) != 0)
844 return m_matrixCrosspointStates.at(inputNumber).at(outputNumber);
845 }
846 jassertfalse;
847 return false;
848}
849
850void MemaProcessor::setMatrixCrosspointEnabledValue(std::uint16_t inputNumber, std::uint16_t outputNumber, bool enabled, MemaChannelCommander* sender, int userId)
851{
852 jassert(inputNumber > 0 && outputNumber > 0);
853
854 for (auto const& crosspointCommander : m_crosspointCommanders)
855 {
856 if (crosspointCommander != reinterpret_cast<MemaCrosspointCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
857 crosspointCommander->setCrosspointEnabledValue(inputNumber, outputNumber, enabled, userId);
858 }
859
860 {
861 const ScopedLock sl(m_audioDeviceIOCallbackLock);
862#ifdef DEBUG
863 // check crosspoint existance - if it is not existing, we should somehow verify afterwards that the matrix symmetry is still given...
864 jassert(0 != m_matrixCrosspointStates.count(inputNumber));
865 if (0 != m_matrixCrosspointStates.count(inputNumber))
866 {
867 jassert(0 != m_matrixCrosspointStates.at(inputNumber).count(outputNumber));
868 }
869#endif
870 m_matrixCrosspointStates[inputNumber][outputNumber] = enabled;
871 }
872
874}
875
876float MemaProcessor::getMatrixCrosspointFactorValue(std::uint16_t inputNumber, std::uint16_t outputNumber)
877{
878 jassert(inputNumber > 0 && outputNumber > 0);
879 {
880 const ScopedLock sl(m_audioDeviceIOCallbackLock);
881 if (m_matrixCrosspointValues.count(inputNumber) != 0)
882 if (m_matrixCrosspointValues.at(inputNumber).count(outputNumber) != 0)
883 return m_matrixCrosspointValues.at(inputNumber).at(outputNumber);
884 }
885 jassertfalse;
886 return 0.0f;
887}
888
889void MemaProcessor::setMatrixCrosspointFactorValue(std::uint16_t inputNumber, std::uint16_t outputNumber, float factor, MemaChannelCommander* sender, int userId)
890{
891 jassert(inputNumber > 0 && outputNumber > 0);
892
893 for (auto const& crosspointCommander : m_crosspointCommanders)
894 {
895 if (crosspointCommander != reinterpret_cast<MemaCrosspointCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
896 crosspointCommander->setCrosspointFactorValue(inputNumber, outputNumber, factor, userId);
897 }
898
899 {
900 const ScopedLock sl(m_audioDeviceIOCallbackLock);
901#ifdef DEBUG
902 // check crosspoint existance - if it is not existing, we should somehow verify afterwards that the matrix symmetry is still given...
903 jassert(0 != m_matrixCrosspointValues.count(inputNumber));
904 if (0 != m_matrixCrosspointValues.count(inputNumber))
905 {
906 jassert(0 != m_matrixCrosspointValues.at(inputNumber).count(outputNumber));
907 }
908#endif
909 m_matrixCrosspointValues[inputNumber][outputNumber] = factor;
910 }
911
913}
914
915bool MemaProcessor::getOutputMuteState(std::uint16_t outputChannelNumber)
916{
917 jassert(outputChannelNumber > 0);
918 const ScopedLock sl(m_audioDeviceIOCallbackLock);
919 return m_outputMuteStates[outputChannelNumber];
920}
921
922void MemaProcessor::setOutputMuteState(std::uint16_t outputChannelNumber, bool muted, MemaChannelCommander* sender, int userId)
923{
924 jassert(outputChannelNumber > 0);
925
926 for (auto const& outputCommander : m_outputCommanders)
927 {
928 if (outputCommander != reinterpret_cast<MemaOutputCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
929 outputCommander->setOutputMute(outputChannelNumber, muted, userId);
930 }
931
932 {
933 const ScopedLock sl(m_audioDeviceIOCallbackLock);
934 m_outputMuteStates[outputChannelNumber] = muted;
935 }
936
938}
939
940void MemaProcessor::setChannelCounts(std::uint16_t inputChannelCount, std::uint16_t outputChannelCount)
941{
942 auto reinitRequired = false;
943 if (m_inputChannelCount != inputChannelCount)
944 {
945 m_inputChannelCount = inputChannelCount;
946 reinitRequired = true;
947
948 // threadsafe locking in scope to access plugin
949 {
950 const ScopedLock sl(m_pluginProcessingLock);
951 if (m_pluginInstance)
952 m_pluginInstance->setPlayConfigDetails(m_inputChannelCount, m_inputChannelCount, getSampleRate(), getBlockSize());
953 }
954 }
955 if (m_outputChannelCount != outputChannelCount)
956 {
957 m_outputChannelCount = outputChannelCount;
958 reinitRequired = true;
959 }
960 if (reinitRequired)
961 postMessage(new ReinitIOCountMessage(m_inputChannelCount, m_outputChannelCount));
962}
963
964bool MemaProcessor::setPlugin(const juce::PluginDescription& pluginDescription)
965{
966 juce::AudioPluginFormatManager formatManager;
967 addDefaultFormatsToManager(formatManager);
968 auto registeredFormats = formatManager.getFormats();
969
970 auto success = false;
971 juce::String errorMessage = "Unsupported plug-in format.";
972
973 for (auto const& format : registeredFormats)
974 {
975 if (format->getName() == pluginDescription.pluginFormatName)
976 {
978
979 // threadsafe locking in scope to access plugin
980 {
981 const ScopedLock sl(m_pluginProcessingLock);
982
983 // Clean up old parameter listeners in case another plugin was already loaded
984 if (m_pluginInstance)
985 {
986 for (auto const& param : m_pluginInstance->getParameters())
987 param->addListener(this);
988 }
989
990 m_pluginParameterInfos.clear();
991
992 m_pluginInstance = format->createInstanceFromDescription(pluginDescription, getSampleRate(), getBlockSize(), errorMessage);
993 if (m_pluginInstance)
994 {
995 m_pluginInstance->setPlayConfigDetails(m_inputChannelCount, m_inputChannelCount, getSampleRate(), getBlockSize());
996
997 // Extract parameters here
998 m_pluginParameterInfos.clear();
999 for (auto const& param : m_pluginInstance->getParameters())
1000 m_pluginParameterInfos.push_back(PluginParameterInfo::fromAudioProcessorParameter(*param));
1001 postMessage(std::make_unique<PluginParameterInfosChangedMessage>().release());
1002
1003 // Attach listeners to track parameter changes
1004 for (auto const& param : m_pluginInstance->getParameters())
1005 param->addListener(this);
1006
1007 m_pluginInstance->prepareToPlay(getSampleRate(), getBlockSize());
1008 }
1009 }
1010 success = errorMessage.isEmpty();
1011 break;
1012 }
1013 }
1014
1015 if (!success)
1016 juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::WarningIcon, "Loading error", "Loading of the selected plug-in " + pluginDescription.name + " failed.\n" + errorMessage);
1017 else if (onPluginSet)
1018 onPluginSet(pluginDescription);
1019
1020 triggerConfigurationUpdate(false);
1021
1022 return success;
1023}
1024
1026{
1027 if (m_pluginInstance)
1028 return m_pluginInstance->getPluginDescription();
1029
1030 return {};
1031}
1032
1034{
1035 // threadsafe locking in scope to access plugin enabled
1036 {
1037 const ScopedLock sl(m_pluginProcessingLock);
1038 m_pluginEnabled = enabled;
1039 }
1040
1041 triggerConfigurationUpdate(false);
1042}
1043
1045{
1046 return m_pluginEnabled;
1047}
1048
1050{
1051 // threadsafe locking in scope to access plugin enabled
1052 {
1053 const ScopedLock sl(m_pluginProcessingLock);
1054 m_pluginPost = post;
1055 }
1056
1057 triggerConfigurationUpdate(false);
1058}
1059
1061{
1062 return m_pluginPost;
1063}
1064
1066{
1068
1069 // threadsafe locking in scope to access plugin
1070 {
1071 const ScopedLock sl(m_pluginProcessingLock);
1072 m_pluginInstance.reset();
1073 m_pluginParameterInfos.clear();
1074 }
1075
1076 if (onPluginSet)
1077 onPluginSet(juce::PluginDescription());
1078
1079 setPluginEnabledState(false);
1080}
1081
1083{
1084 if (m_pluginInstance)
1085 {
1086 auto pluginEditorInstance = m_pluginInstance->createEditorIfNeeded();
1087 if (pluginEditorInstance && !m_pluginEditorWindow)
1088 {
1089 m_pluginEditorWindow = std::make_unique<ResizeableWindowWithTitleBarAndCloseCallback>(juce::JUCEApplication::getInstance()->getApplicationName() + " : " + m_pluginInstance->getName(), true);
1090 m_pluginEditorWindow->setResizable(false, false);
1091 m_pluginEditorWindow->setContentOwned(pluginEditorInstance, true);
1092 m_pluginEditorWindow->onClosed = [=]() { closePluginEditor(true); };
1093 m_pluginEditorWindow->setVisible(true);
1094 }
1095 }
1096}
1097
1098void MemaProcessor::closePluginEditor(bool deleteEditorWindow)
1099{
1100 if (m_pluginInstance)
1101 std::unique_ptr<juce::AudioProcessorEditor>(m_pluginInstance->getActiveEditor()).reset();
1102 if (deleteEditorWindow)
1103 m_pluginEditorWindow.reset();
1104}
1105
1106std::vector<PluginParameterInfo>& MemaProcessor::getPluginParameterInfos()
1107{
1108 return m_pluginParameterInfos;
1109}
1110
1111void MemaProcessor::setPluginParameterRemoteControlInfos(int parameterIndex, bool remoteControllable, ParameterControlType type, int steps)
1112{
1113 if (parameterIndex >= 0 && parameterIndex < m_pluginParameterInfos.size())
1114 {
1115 m_pluginParameterInfos[parameterIndex].isRemoteControllable = remoteControllable;
1116 m_pluginParameterInfos[parameterIndex].type = type;
1117 m_pluginParameterInfos[parameterIndex].stepCount = steps;
1118
1119 jassert(m_pluginInstance);
1120 if (m_pluginInstance)
1121 {
1122 m_pluginParameterInfos[parameterIndex].stepNames.clear();
1123 auto param = m_pluginInstance->getParameters()[parameterIndex];
1124 if (param && steps > 1)
1125 {
1126 auto stepSize = 1.0f / (steps - 1);
1127 for (int i = 0; i < steps; i++)
1128 {
1129 m_pluginParameterInfos[parameterIndex].stepNames.push_back(param->getText(i * stepSize, 100).toStdString());
1130 }
1131 }
1132 }
1133
1134 triggerConfigurationUpdate(false);
1135 }
1136}
1137
1139{
1140 if (parameterIndex >= 0 && parameterIndex < m_pluginParameterInfos.size())
1141 return m_pluginParameterInfos[parameterIndex].isRemoteControllable;
1142 else
1143 return false;
1144}
1145
1146float MemaProcessor::getPluginParameterValue(std::uint16_t parameterIndex) const
1147{
1148 if (!m_pluginInstance)
1149 return 0.0f;
1150
1151 auto& parameters = m_pluginInstance->getParameters();
1152 auto idxInRange = parameterIndex >= 0 && parameterIndex < parameters.size();
1153 jassert(idxInRange);
1154 if (!idxInRange)
1155 return 0.0f;
1156
1157 const ScopedLock sl(m_pluginProcessingLock);
1158 return parameters[parameterIndex]->getValue();
1159}
1160
1161void MemaProcessor::setPluginParameterValue(std::uint16_t parameterIndex, std::string id, float normalizedValue, MemaPluginCommander* sender, int userId)
1162{
1163 if (!m_pluginInstance)
1164 return;
1165
1166 auto& parameters = m_pluginInstance->getParameters();
1167 auto idxInRange = parameterIndex >= 0 && parameterIndex < parameters.size();
1168 jassert(idxInRange);
1169 if (!idxInRange)
1170 return;
1171
1172 for (auto const& pluginCommander : m_pluginCommanders)
1173 {
1174 if (pluginCommander != reinterpret_cast<MemaPluginCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
1175 pluginCommander->setPluginParameterValue(parameterIndex, id, normalizedValue, userId);
1176 }
1177
1178
1179 {
1180 const ScopedLock sl(m_pluginProcessingLock);
1181
1182 parameters[parameterIndex]->setValue(normalizedValue);
1183
1184 // Update cached value
1185 if (parameterIndex < m_pluginParameterInfos.size())
1186 m_pluginParameterInfos[parameterIndex].currentValue = normalizedValue;
1187 }
1188
1190}
1191
1192juce::AudioProcessorParameter* MemaProcessor::getPluginParameter(int parameterIndex) const
1193{
1194 const ScopedLock sl(m_pluginProcessingLock);
1195
1196 if (!m_pluginInstance)
1197 return nullptr;
1198
1199 auto& parameters = m_pluginInstance->getParameters();
1200
1201 if (parameterIndex >= 0 && parameterIndex < parameters.size())
1202 return parameters[parameterIndex];
1203
1204 return nullptr;
1205}
1206
1208{
1209 if (m_deviceManager)
1210 return m_deviceManager.get();
1211 else
1212 return nullptr;
1213}
1214
1215std::map<int, std::pair<double, bool>> MemaProcessor::getNetworkHealth()
1216{
1217 if (m_networkServer)
1218 return m_networkServer->getListHealth();
1219 else
1220 return {};
1221}
1222
1223JUCEAppBasics::SessionServiceTopology MemaProcessor::getDiscoveredServicesTopology()
1224{
1225 if (m_serviceTopologyManager)
1226 return m_serviceTopologyManager->getDiscoveredServiceTopology();
1227 else
1228 return {};
1229}
1230
1231//==============================================================================
1232const String MemaProcessor::getName() const
1233{
1234 return m_Name;
1235}
1236
1237void MemaProcessor::prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock)
1238{
1239 setRateAndBufferSizeDetails(sampleRate, maximumExpectedSamplesPerBlock);
1240
1241 // threadsafe locking in scope to access plugin
1242 {
1243 const ScopedLock sl(m_pluginProcessingLock);
1244 if (m_pluginInstance)
1245 m_pluginInstance->prepareToPlay(sampleRate, maximumExpectedSamplesPerBlock);
1246 }
1247
1248 if (m_inputDataAnalyzer)
1249 m_inputDataAnalyzer->initializeParameters(sampleRate, maximumExpectedSamplesPerBlock);
1250 if (m_outputDataAnalyzer)
1251 m_outputDataAnalyzer->initializeParameters(sampleRate, maximumExpectedSamplesPerBlock);
1252
1253 postMessage(std::make_unique<AnalyzerParametersMessage>(int(sampleRate), maximumExpectedSamplesPerBlock).release());
1254}
1255
1257{
1258 // threadsafe locking in scope to access plugin
1259 {
1260 const ScopedLock sl(m_pluginProcessingLock);
1261 if (m_pluginInstance)
1262 m_pluginInstance->releaseResources();
1263 }
1264
1265 if (m_inputDataAnalyzer)
1266 m_inputDataAnalyzer->clearParameters();
1267 if (m_outputDataAnalyzer)
1268 m_outputDataAnalyzer->clearParameters();
1269}
1270
1271void MemaProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
1272{
1273 ignoreUnused(midiMessages);
1274
1275 // the lock is currently gloablly taken in audioDeviceIOCallback which is calling this method
1276 //const ScopedLock sl(m_audioDeviceIOCallbackLock);
1277
1278 auto reinitRequired = false;
1279
1280 jassert(s_minInputsCount <= m_inputChannelCount);
1281 jassert(s_minOutputsCount <= m_outputChannelCount);
1282
1283 if (m_inputChannelCount > m_inputMuteStates.size())
1284 reinitRequired = true;
1285
1286 for (std::uint16_t input = 1; input <= m_inputChannelCount; input++)
1287 {
1288 if (m_inputMuteStates.count(input) != 0 && m_inputMuteStates.at(input))
1289 {
1290 auto channelIdx = input - 1;
1291 buffer.clear(channelIdx, 0, buffer.getNumSamples());
1292 }
1293 }
1294
1295 // threadsafe locking in scope to access plugin - processing only takes place if NOT set to post matrix
1296 {
1297 const ScopedLock sl(m_pluginProcessingLock);
1298 if (m_pluginInstance && m_pluginEnabled && !m_pluginPost)
1299 m_pluginInstance->processBlock(buffer, midiMessages);
1300 }
1301
1302 postMessage(std::make_unique<AudioInputBufferMessage>(buffer).release());
1303
1304 // process data in buffer to be what shall be used as output
1305 juce::AudioBuffer<float> processedBuffer;
1306 processedBuffer.setSize(m_outputChannelCount, buffer.getNumSamples(), false, true, true);
1307 for (std::uint16_t inputIdx = 0; inputIdx < m_inputChannelCount; inputIdx++)
1308 {
1309 for (std::uint16_t outputIdx = 0; outputIdx < m_outputChannelCount; outputIdx++)
1310 {
1311 if (0 != m_matrixCrosspointStates.count(inputIdx + 1) && 0 != m_matrixCrosspointValues.count(inputIdx + 1))
1312 {
1313 if (0 != m_matrixCrosspointStates[inputIdx + 1].count(outputIdx + 1) && 0 != m_matrixCrosspointValues[inputIdx + 1].count(outputIdx + 1))
1314 {
1315 auto& enabled = m_matrixCrosspointStates.at(inputIdx + 1).at(outputIdx + 1);
1316 auto& factor = m_matrixCrosspointValues.at(inputIdx + 1).at(outputIdx + 1);
1317 auto gain = !enabled ? 0.0f : factor;
1318 processedBuffer.addFrom(outputIdx, 0, buffer.getReadPointer(inputIdx), buffer.getNumSamples(), gain);
1319 }
1320 }
1321 }
1322 }
1323 buffer.makeCopyOf(processedBuffer, true);
1324
1325 if (m_outputChannelCount > m_outputMuteStates.size())
1326 reinitRequired = true;
1327
1328 // threadsafe locking in scope to access plugin - processing only takes place if set to post matrix
1329 {
1330 const ScopedLock sl(m_pluginProcessingLock);
1331 if (m_pluginInstance && m_pluginEnabled && m_pluginPost)
1332 m_pluginInstance->processBlock(buffer, midiMessages);
1333 }
1334
1335 for (std::uint16_t output = 1; output <= m_outputChannelCount; output++)
1336 {
1337 if (m_outputMuteStates.count(output) != 0 && m_outputMuteStates.at(output))
1338 {
1339 auto channelIdx = output - 1;
1340 buffer.clear(channelIdx, 0, buffer.getNumSamples());
1341 }
1342 }
1343
1344 postMessage(std::make_unique<AudioOutputBufferMessage>(buffer).release());
1345
1346 if (reinitRequired)
1347 postMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount).release());
1348}
1349
1350void MemaProcessor::handleMessage(const Message& message)
1351{
1353 juce::MemoryBlock serializedMessageMemoryBlock;
1354
1355 auto origId = -1;
1356 if (auto const sm = dynamic_cast<const SerializableMessage*>(&message))
1357 origId = sm->getId();
1358
1359 if (auto const epm = dynamic_cast<const EnvironmentParametersMessage*>(&message))
1360 {
1361 serializedMessageMemoryBlock = epm->getSerializedMessage();
1362 tId = epm->getType();
1363 }
1364 else if (auto const apm = dynamic_cast<const AnalyzerParametersMessage*>(&message))
1365 {
1366 serializedMessageMemoryBlock = apm->getSerializedMessage();
1367 tId = apm->getType();
1368 }
1369 else if (auto const iom = dynamic_cast<const ReinitIOCountMessage*> (&message))
1370 {
1371 auto inputCount = iom->getInputCount();
1372 jassert(inputCount > 0);
1373 auto outputCount = iom->getOutputCount();
1374 jassert(outputCount > 0);
1375
1376 for (auto const& inputCommander : m_inputCommanders)
1377 inputCommander->setChannelCount(inputCount);
1378
1379 for (auto const& outputCommander : m_outputCommanders)
1380 outputCommander->setChannelCount(outputCount);
1381
1382 for (auto const& crosspointCommander : m_crosspointCommanders)
1383 crosspointCommander->setIOCount(inputCount, outputCount);
1384
1385 initializeCtrlValues(iom->getInputCount(), iom->getOutputCount());
1386
1387 serializedMessageMemoryBlock = iom->getSerializedMessage();
1388
1389 tId = iom->getType();
1390 }
1391 else if (auto m = dynamic_cast<const AudioBufferMessage*> (&message))
1392 {
1393 if (m->getFlowDirection() == AudioBufferMessage::FlowDirection::Input && m_inputDataAnalyzer)
1394 {
1395 m_inputDataAnalyzer->analyzeData(m->getAudioBuffer());
1396 }
1397 else if (m->getFlowDirection() == AudioBufferMessage::FlowDirection::Output && m_outputDataAnalyzer)
1398 {
1399 m_outputDataAnalyzer->analyzeData(m->getAudioBuffer());
1400 }
1401
1402 serializedMessageMemoryBlock = m->getSerializedMessage();
1403
1404 tId = m->getType();
1405 }
1406 else if (auto const cpm = dynamic_cast<const Mema::ControlParametersMessage*>(&message))
1407 {
1408 DBG(juce::String(__FUNCTION__) << " i:" << cpm->getInputMuteStates().size() << " o:" << cpm->getOutputMuteStates().size() << " c:" << cpm->getCrosspointStates().size());
1409 for (auto const& inputMuteState : cpm->getInputMuteStates())
1410 setInputMuteState(inputMuteState.first, inputMuteState.second, static_cast<MemaInputCommander*>(m_networkCommanderWrapper.get()));
1411 for (auto const& outputMuteState : cpm->getOutputMuteStates())
1412 setOutputMuteState(outputMuteState.first, outputMuteState.second, static_cast<MemaOutputCommander*>(m_networkCommanderWrapper.get()));
1413 for (auto const& crosspointStateKV : cpm->getCrosspointStates())
1414 {
1415 auto& inputNumber = crosspointStateKV.first;
1416 for (auto const& crosspointOStateKV : crosspointStateKV.second)
1417 {
1418 auto& outputNumber = crosspointOStateKV.first;
1419 setMatrixCrosspointEnabledValue(inputNumber, outputNumber, crosspointOStateKV.second, static_cast<MemaCrosspointCommander*>(m_networkCommanderWrapper.get()), origId);
1420 }
1421 }
1422 for (auto const& crosspointValueKV : cpm->getCrosspointValues())
1423 {
1424 auto& inputNumber = crosspointValueKV.first;
1425 for (auto const& crosspointOValueKV : crosspointValueKV.second)
1426 {
1427 auto& outputNumber = crosspointOValueKV.first;
1428 setMatrixCrosspointFactorValue(inputNumber, outputNumber, crosspointOValueKV.second, static_cast<MemaCrosspointCommander*>(m_networkCommanderWrapper.get()), origId);
1429 }
1430 }
1431
1432 tId = cpm->getType();
1433 }
1434 else if (auto const dtsm = dynamic_cast<const Mema::DataTrafficTypeSelectionMessage*>(&message))
1435 {
1436 if (!dtsm->hasUserId())
1437 DBG("Incoming DataTrafficTypeSelecitonMessage cannot be associated with a connection");
1438 else
1439 setTrafficTypesForConnectionId(dtsm->getTrafficTypes(), origId);
1440
1441 tId = dtsm->getType();
1442 }
1443 else if (auto const ppim = dynamic_cast<const PluginParameterInfosMessage*>(&message))
1444 {
1445 DBG(juce::String(__FUNCTION__) << " ppiCnt:" << ppim->getParameterInfos().size());
1446
1447 jassertfalse; // why would parameterinfos of a plugin loaded in mema be changed externally?
1448
1449 serializedMessageMemoryBlock = ppim->getSerializedMessage();
1450
1451 tId = ppim->getType();
1452 }
1453 else if (auto const ppvm = dynamic_cast<const PluginParameterValueMessage*>(&message))
1454 {
1455 DBG(juce::String(__FUNCTION__) << " ppvIdx:" << int(ppvm->getParameterIndex()) << " > " << ppvm->getCurrentValue());
1456
1457 setPluginParameterValue(ppvm->getParameterIndex(), ppvm->getParameterId().toStdString(), ppvm->getCurrentValue(), static_cast<MemaPluginCommander*>(m_networkCommanderWrapper.get()), origId);
1458
1459 serializedMessageMemoryBlock = ppvm->getSerializedMessage();
1460
1461 tId = ppvm->getType();
1462 }
1463 // exception - totally different message type, to decouple callback from processing asynchronously...
1464 else if (auto const ppicm = dynamic_cast<const PluginParameterInfosChangedMessage*>(&message))
1465 {
1468
1469 return; // ...abort further handling below here therefor
1470 }
1471
1472 std::vector<int> sendIds;
1473 for (auto const& cId : m_trafficTypesPerConnection)
1474 {
1475 if (cId.first != origId) // avoid spamming the originator of the data with a resend
1476 {
1477 // if the connection did not define relevant traffic types, add its connectionId to list of recipients
1478 if (cId.second.empty())
1479 sendIds.push_back(cId.first);
1480 // if the traffic type is active for a connection, add the connectionId to list of recipients
1481 else if (cId.second.end() != std::find(cId.second.begin(), cId.second.end(), tId))
1482 sendIds.push_back(cId.first);
1483 }
1484 }
1485 if (!sendIds.empty())
1486 sendMessageToClients(serializedMessageMemoryBlock, sendIds);
1487}
1488
1489void MemaProcessor::parameterValueChanged(int parameterIndex, float newValue)
1490{
1491 auto iter = std::find_if(getPluginParameterInfos().begin(), getPluginParameterInfos().end(), [=](const auto& info) { return info.index == parameterIndex; });
1492 if (iter != getPluginParameterInfos().end())
1493 {
1494 // Update commanders (UIs and Remotes)
1495 for (auto const& pluginCommander : m_pluginCommanders)
1496 pluginCommander->setPluginParameterValue(static_cast<std::uint16_t>(parameterIndex), iter->id.toStdString(), newValue);
1497
1498 // Update cached value
1499 if (parameterIndex < m_pluginParameterInfos.size())
1500 m_pluginParameterInfos[parameterIndex].currentValue = newValue;
1501 }
1502}
1503
1504void MemaProcessor::parameterGestureChanged(int parameterIndex, bool gestureIsStarting)
1505{
1506 ignoreUnused(parameterIndex);
1507 ignoreUnused(gestureIsStarting);
1508}
1509
1510void MemaProcessor::sendMessageToClients(const MemoryBlock& messageMemoryBlock, const std::vector<int>& sendIds)
1511{
1512 if (m_networkServer && m_networkServer->hasActiveConnections())
1513 {
1514 if (!messageMemoryBlock.isEmpty() && !m_networkServer->enqueueMessage(messageMemoryBlock, sendIds))
1515 {
1516 auto deadConnectionIds = m_networkServer->cleanupDeadConnections();
1517 if (!deadConnectionIds.empty())
1518 {
1519 for (auto const& dcId : deadConnectionIds)
1520 m_trafficTypesPerConnection.erase(dcId);
1521 }
1522 }
1523 }
1524}
1525
1527{
1528 /*dbg*/return 0.0;
1529}
1530
1532{
1533 return false;
1534}
1535
1537{
1538 return false;
1539}
1540
1541AudioProcessorEditor* MemaProcessor::createEditor()
1542{
1543 if (!m_processorEditor)
1544 m_processorEditor = std::make_unique<MemaProcessorEditor>(this);
1545
1546 m_processorEditor->onResetToUnity = [=]() { initializeCtrlValuesToUnity(m_inputChannelCount, m_outputChannelCount); };
1547
1548 return m_processorEditor.get();
1549}
1550
1552{
1553 return !!m_processorEditor;
1554}
1555
1557{
1558 /*dbg*/return 0;
1559}
1560
1562{
1563 /*dbg*/return 0;
1564}
1565
1567{
1568 /*dbg*/ignoreUnused(index);
1569}
1570
1571const String MemaProcessor::getProgramName(int index)
1572{
1573 /*dbg*/ignoreUnused(index);
1574 /*dbg*/return String();
1575}
1576
1577void MemaProcessor::changeProgramName(int index, const String& newName)
1578{
1579 /*dbg*/ignoreUnused(index);
1580 /*dbg*/ignoreUnused(newName);
1581}
1582
1583void MemaProcessor::getStateInformation(juce::MemoryBlock& destData)
1584{
1585 /*dbg*/ignoreUnused(destData);
1586}
1587
1588void MemaProcessor::setStateInformation(const void* data, int sizeInBytes)
1589{
1590 /*dbg*/ignoreUnused(data);
1591 /*dbg*/ignoreUnused(sizeInBytes);
1592}
1593
1594void MemaProcessor::audioDeviceIOCallbackWithContext(const float* const* inputChannelData, int numInputChannels,
1595 float* const* outputChannelData, int numOutputChannels, int numSamples, const AudioIODeviceCallbackContext& context)
1596{
1597 ignoreUnused(context);
1598
1599 const juce::ScopedLock sl(m_audioDeviceIOCallbackLock);
1600
1601 if (m_inputChannelCount != numInputChannels || m_outputChannelCount != numOutputChannels)
1602 {
1603 m_inputChannelCount = numInputChannels;
1604 m_outputChannelCount = numOutputChannels;
1605 postMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount).release());
1606 }
1607
1608 auto maxActiveChannels = std::max(numInputChannels, numOutputChannels);
1609
1610 if (s_maxChannelCount < maxActiveChannels)
1611 {
1612 jassertfalse;
1613 return;
1614 }
1615
1616 // copy incoming data to processing data buffer
1617 for (auto i = 0; i < numInputChannels && i < maxActiveChannels; i++)
1618 {
1619 memcpy(m_processorChannels[i], inputChannelData[i], (size_t)numSamples * sizeof(float));
1620 }
1621
1622 // from juce doxygen: buffer must be the size of max(inCh, outCh) and feeds the input data into the method and is returned with output data
1623 juce::AudioBuffer<float> audioBufferToProcess(m_processorChannels, maxActiveChannels, numSamples);
1624 juce::MidiBuffer midiBufferToProcess;
1625 processBlock(audioBufferToProcess, midiBufferToProcess);
1626
1627 // copy the processed data buffer data to outgoing data
1628 auto processedChannelCount = audioBufferToProcess.getNumChannels();
1629 auto processedSampleCount = audioBufferToProcess.getNumSamples();
1630 auto processedData = audioBufferToProcess.getArrayOfReadPointers();
1631 jassert(processedSampleCount == numSamples);
1632 for (auto i = 0; i < numOutputChannels && i < processedChannelCount; i++)
1633 {
1634 memcpy(outputChannelData[i], processedData[i], (size_t)processedSampleCount * sizeof(float));
1635 }
1636
1637}
1638
1640{
1641 if (device)
1642 {
1643 auto activeInputs = device->getActiveInputChannels();
1644 auto inputChannelCnt = std::uint16_t(activeInputs.getHighestBit() + 1); // from JUCE documentation
1645 auto activeOutputs = device->getActiveOutputChannels();
1646 auto outputChannelCnt = std::uint16_t(activeOutputs.getHighestBit() + 1); // from JUCE documentation
1647 auto sampleRate = device->getCurrentSampleRate();
1648 auto bufferSize = device->getCurrentBufferSizeSamples();
1649 //auto bitDepth = device->getCurrentBitDepth();
1650
1651 setPlayConfigDetails(inputChannelCnt, outputChannelCnt, sampleRate, bufferSize); // redundant to what happens in prepareToPlay to some extent...harmful?
1652 setChannelCounts(inputChannelCnt, outputChannelCnt);
1653 prepareToPlay(sampleRate, bufferSize);
1654 }
1655}
1656
1661
1662void MemaProcessor::changeListenerCallback(ChangeBroadcaster* source)
1663{
1664 if (source == m_deviceManager.get())
1666}
1667
1668void MemaProcessor::initializeCtrlValues(int inputCount, int outputCount)
1669{
1670 std::map<std::uint16_t, bool> inputMuteStates;
1671 std::map<std::uint16_t, bool> outputMuteStates;
1672 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
1673 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
1674 {
1675 // copy the processing relevant variables to not block audio thread during all the xml handling
1676 const ScopedLock sl(m_audioDeviceIOCallbackLock);
1677 inputMuteStates = m_inputMuteStates;
1678 outputMuteStates = m_outputMuteStates;
1679 matrixCrosspointValues = m_matrixCrosspointValues;
1680 matrixCrosspointStates = m_matrixCrosspointStates;
1681 }
1682
1683 auto inputChannelCount = std::uint16_t((inputCount > s_minInputsCount) ? inputCount : s_minInputsCount);
1684 for (std::uint16_t channel = 1; channel <= inputChannelCount; channel++)
1685 for (auto& inputCommander : m_inputCommanders)
1686 inputCommander->setInputMute(channel, inputMuteStates[channel]);
1687
1688 auto outputChannelCount = std::uint16_t((outputCount > s_minOutputsCount) ? outputCount : s_minOutputsCount);
1689 for (std::uint16_t channel = 1; channel <= outputChannelCount; channel++)
1690 for (auto& outputCommander : m_outputCommanders)
1691 outputCommander->setOutputMute(channel, outputMuteStates[channel]);
1692
1693 for (std::uint16_t in = 1; in <= inputChannelCount; in++)
1694 {
1695 for (std::uint16_t out = 1; out <= outputChannelCount; out++)
1696 {
1697 for (auto& crosspointCommander : m_crosspointCommanders)
1698 {
1699 crosspointCommander->setCrosspointEnabledValue(in, out, matrixCrosspointStates[in][out]);
1700 crosspointCommander->setCrosspointFactorValue(in, out, matrixCrosspointValues[in][out]);
1701 }
1702 }
1703 }
1704
1705 juce::Array<juce::AudioProcessorParameter*> pluginParameters;
1706 juce::String pluginName;
1707 if (m_pluginInstance)
1708 {
1709 // copy the processing relevant data to not block audio thread during all the xml handling
1710 const ScopedLock sl(m_pluginProcessingLock);
1711 pluginName = m_pluginInstance->getName();
1712 pluginParameters = m_pluginInstance->getParameters();
1713 }
1714 auto pluginParameterInfos = Mema::PluginParameterInfo::parametersToInfos(pluginParameters);
1715 for (auto& pluginCommander : m_pluginCommanders)
1716 pluginCommander->setPluginParameterInfos(pluginParameterInfos, pluginName.toStdString());
1717}
1718
1719void MemaProcessor::initializeCtrlValuesToUnity(int inputCount, int outputCount)
1720{
1721 {
1722 const ScopedLock sl(m_audioDeviceIOCallbackLock);
1723 m_inputMuteStates.clear();
1724 m_outputMuteStates.clear();
1725 m_matrixCrosspointStates.clear();
1726 m_matrixCrosspointValues.clear();
1727 }
1728
1729 auto inputChannelCount = (inputCount > s_minInputsCount) ? inputCount : s_minInputsCount;
1730 for (std::uint16_t channel = 1; channel <= inputChannelCount; channel++)
1731 setInputMuteState(channel, false);
1732
1733 auto outputChannelCount = (outputCount > s_minOutputsCount) ? outputCount : s_minOutputsCount;
1734 for (std::uint16_t channel = 1; channel <= outputChannelCount; channel++)
1735 setOutputMuteState(channel, false);
1736
1737 for (std::uint16_t in = 1; in <= inputChannelCount; in++)
1738 {
1739 for (std::uint16_t out = 1; out <= outputChannelCount; out++)
1740 {
1741 setMatrixCrosspointEnabledValue(in, out, in == out);
1742 setMatrixCrosspointFactorValue(in, out, 1.0f);
1743 }
1744 }
1745}
1746
1748{
1749 initializeCtrlValuesToUnity(m_inputChannelCount, m_outputChannelCount);
1750}
1751
1752void MemaProcessor::setTrafficTypesForConnectionId(const std::vector<SerializableMessage::SerializableMessageType>& trafficTypes, int connectionId)
1753{
1754 DBG(juce::String(__FUNCTION__) << " " << connectionId << " chose " << trafficTypes.size() << " types");
1755 for (auto const& tt : trafficTypes)
1756 {
1757 if (m_trafficTypesPerConnection[connectionId].end() == std::find(m_trafficTypesPerConnection[connectionId].begin(), m_trafficTypesPerConnection[connectionId].end(), tt))
1758 m_trafficTypesPerConnection[connectionId].push_back(tt);
1759 }
1760}
1761
1762
1763} // namespace Mema
Carries audio-device parameters (sample rate, block size) from Mema to clients.
Base message carrying a serialised audio buffer and its flow-direction metadata.
@ Input
Pre-matrix input samples (as seen by the input analyzers).
@ Output
Post-matrix output samples (as seen by the output analyzers).
Full routing-matrix state snapshot exchanged bidirectionally between Mema and Mema....
Sent by a client to opt in to receiving specific message types from Mema.
Carries the active look-and-feel palette style from Mema to connected clients.
Client-side TCP connection wrapper that forwards JUCE IPC events to std::function callbacks.
std::function< void(int)> onConnectionLost
static juce::String getTagName(TagID ID)
@ PLUGINCONFIG
Plugin host settings.
@ PROCESSORCONFIG
Audio processor settings.
@ CROSSPOINTGAINS
Crosspoint matrix gain values.
@ DEVCONFIG
Audio device configuration.
@ OUTPUTMUTES
Per-channel output mute states.
@ PLUGINPARAM
Individual plugin parameter entry.
@ INPUTMUTES
Per-channel input mute states.
static juce::String getAttributeName(AttributeID ID)
@ ENABLED
Boolean enabled flag.
@ POST
Post-matrix plugin insertion flag.
@ CONTROLLABLE
Whether a plugin parameter is remotely controllable.
@ IDX
Channel or parameter index.
virtual void setCrosspointFactorValue(std::uint16_t input, std::uint16_t output, float factor, int userId=-1)=0
void setCrosspointEnabledChangeCallback(const std::function< void(MemaCrosspointCommander *sender, std::uint16_t, std::uint16_t, bool)> &callback)
virtual void setCrosspointEnabledValue(std::uint16_t input, std::uint16_t output, bool enabledState, int userId=-1)=0
void setCrosspointFactorChangeCallback(const std::function< void(MemaCrosspointCommander *sender, std::uint16_t, std::uint16_t, float)> &callback)
void setInputMuteChangeCallback(const std::function< void(MemaInputCommander *sender, std::uint16_t, bool)> &callback)
virtual void setInputMute(std::uint16_t channel, bool muteState, int userId=-1)=0
void setPluginParameterValue(std::uint16_t index, std::string id, float currentValue, int userId=-1) override
void setCrosspointFactorValue(std::uint16_t input, std::uint16_t output, float factor, int userId) override
void setCrosspointEnabledValue(std::uint16_t input, std::uint16_t output, bool enabledState, int userId) override
void setInputMute(std::uint16_t channel, bool muteState, int userId) override
void setNetworkConnection(const std::shared_ptr< InterprocessConnectionServerImpl > &networkServer)
void setOutputMute(std::uint16_t channel, bool muteState, int userId) override
void setIOCount(std::uint16_t inputCount, std::uint16_t outputCount) override
void setPluginParameterInfos(const std::vector< PluginParameterInfo > &parameterInfos, const std::string &name, int userId=-1) override
void setOutputMuteChangeCallback(const std::function< void(MemaOutputCommander *sender, std::uint16_t, bool)> &callback)
virtual void setOutputMute(std::uint16_t channel, bool muteState, int userId=-1)=0
void setPluginParameterValueChangeCallback(const std::function< void(MemaPluginCommander *sender, std::uint16_t, std::string, float)> &callback)
bool getOutputMuteState(std::uint16_t channelNumber)
Returns the mute state of a specific output channel.
bool acceptsMidi() const override
void openPluginEditor()
Opens (or raises) the plugin's editor UI in a floating ResizeableWindowWithTitleBarAndCloseCallback w...
std::function< void(const juce::PluginDescription &)> onPluginSet
Invoked on the message thread after a new plugin has been successfully loaded.
void addOutputCommander(MemaOutputCommander *commander)
Adds an output commander and immediately pushes the current mute states to it.
static constexpr int s_maxChannelCount
Maximum number of input or output channels supported by the routing matrix.
void changeListenerCallback(ChangeBroadcaster *source) override
Receives notifications from the AudioDeviceManager when the device configuration changes.
bool isTimedConfigurationDumpPending()
Returns true when a deferred XML configuration dump has been scheduled.
void initializePluginCommander(MemaPluginCommander *commander)
Pushes the current plugin parameter descriptors and values to an already-registered commander.
void setTrafficTypesForConnectionId(const std::vector< SerializableMessage::SerializableMessageType > &trafficTypes, int connectionId)
Updates the set of message types a specific TCP client has subscribed to receive.
void audioDeviceIOCallbackWithContext(const float *const *inputChannelData, int numInputChannels, float *const *outputChannelData, int numOutputChannels, int numSamples, const AudioIODeviceCallbackContext &context) override
Hot audio callback — implements the complete Mema signal chain.
void clearPlugin()
Unloads the hosted plugin, closes its editor window, and resets all plugin commander state.
double getTailLengthSeconds() const override
juce::PluginDescription getPluginDescription()
Returns the JUCE description of the currently loaded plugin.
bool getMatrixCrosspointEnabledValue(std::uint16_t inputNumber, std::uint16_t outputNumber)
Returns whether a specific crosspoint node is enabled (routing active).
void setMatrixCrosspointFactorValue(std::uint16_t inputNumber, std::uint16_t outputNumber, float factor, MemaChannelCommander *sender=nullptr, int userId=-1)
Sets the linear gain factor of a crosspoint node.
void initializeCtrlValuesToUnity()
Resets all crosspoint gains to 1.0 (unity) and enables all crosspoints. Used when creating a default ...
bool hasEditor() const override
void setCurrentProgram(int index) override
static constexpr int s_maxNumSamples
Maximum audio block size in samples.
void setInputMuteState(std::uint16_t channelNumber, bool muted, MemaChannelCommander *sender=nullptr, int userId=-1)
Sets the mute state of an input channel and notifies all commanders except the sender.
AudioDeviceManager * getDeviceManager()
Returns a raw pointer to the JUCE AudioDeviceManager. Used by the audio-setup UI component.
int getNumPrograms() override
bool producesMidi() const override
void setOutputMuteState(std::uint16_t channelNumber, bool muted, MemaChannelCommander *sender=nullptr, int userId=-1)
Sets the mute state of an output channel and notifies all commanders except the sender.
void addOutputListener(ProcessorDataAnalyzer::Listener *listener)
Registers a listener to receive output-channel level/spectrum data from the output analyzer.
void parameterValueChanged(int parameterIndex, float newValue) override
Called by the hosted plugin when a parameter value changes.
void removeInputCommander(MemaInputCommander *commander)
Removes a previously registered input commander.
bool setStateXml(XmlElement *stateXml) override
Restores the processor state from a previously serialised <PROCESSORCONFIG> XmlElement.
juce::AudioProcessorParameter * getPluginParameter(int parameterIndex) const
Returns a pointer to the underlying JUCE AudioProcessorParameter at the given index.
bool isPluginPost()
Returns true when the plugin is inserted post-matrix.
void setPluginEnabledState(bool enabled)
Enables or disables plugin processing without unloading the plugin instance.
void updateCommanders()
Forces all registered commanders to re-synchronise with the current processor state.
void addInputCommander(MemaInputCommander *commander)
Adds an input commander and immediately pushes the current mute states to it.
bool getInputMuteState(std::uint16_t channelNumber)
Returns the mute state of a specific input channel.
std::map< int, std::pair< double, bool > > getNetworkHealth()
Returns per-client network health metrics.
void setStateInformation(const void *data, int sizeInBytes) override
int getCurrentProgram() override
void handleMessage(const Message &message) override
Dispatches JUCE messages posted to the message thread.
void setTimedConfigurationDumpPending()
Schedules a deferred XML configuration dump (called on state change to avoid excessive disk I/O).
JUCEAppBasics::SessionServiceTopology getDiscoveredServicesTopology()
Returns the most recent multicast service topology snapshot from ServiceTopologyManager.
void changeProgramName(int index, const String &newName) override
void resetTimedConfigurationDumpPending()
Clears the deferred dump flag after the dump has been performed.
void setPluginParameterValue(std::uint16_t pluginParameterIndex, std::string id, float normalizedValue, MemaPluginCommander *sender=nullptr, int userId=-1)
Sets a hosted plugin parameter to a normalised value.
void addInputListener(ProcessorDataAnalyzer::Listener *listener)
Registers a listener to receive input-channel level/spectrum data from the input analyzer.
void audioDeviceAboutToStart(AudioIODevice *device) override
Called when the audio device is about to start streaming.
void setChannelCounts(std::uint16_t inputChannelCount, std::uint16_t outputChannelCount)
Resizes all internal routing structures for a new input/output channel count.
void initializeCrosspointCommander(MemaCrosspointCommander *commander)
Pushes the current crosspoint enable/gain matrix to an already-registered commander.
float getMatrixCrosspointFactorValue(std::uint16_t inputNumber, std::uint16_t outputNumber)
Returns the linear gain factor of a crosspoint node.
void closePluginEditor(bool deleteEditorWindow=true)
Closes the plugin editor window.
void initializeCtrlValues(int inputCount, int outputCount)
MemaProcessor(XmlElement *stateXml)
Constructs the processor, optionally restoring state from XML.
void removePluginCommander(MemaPluginCommander *commander)
Removes a previously registered plugin commander.
float getPluginParameterValue(std::uint16_t pluginParameterIndex) const
Returns the current normalised value of a hosted plugin parameter.
void addPluginCommander(MemaPluginCommander *commander)
Adds a plugin commander and immediately pushes the current parameter infos and values.
void environmentChanged()
Called when the OS look-and-feel or palette changes; broadcasts EnvironmentParametersMessage to all c...
bool setPlugin(const juce::PluginDescription &pluginDescription)
Loads and instantiates a plugin from the given description.
void addCrosspointCommander(MemaCrosspointCommander *commander)
Adds a crosspoint commander and immediately pushes the full crosspoint state to it.
std::unique_ptr< XmlElement > createStateXml() override
Serialises the current processor state (mutes, crosspoints, plugin settings) to XML.
void getStateInformation(juce::MemoryBlock &destData) override
void initializeOutputCommander(MemaOutputCommander *commander)
Pushes the current output mute states to an already-registered commander.
static constexpr int s_minOutputsCount
Minimum number of output channels (always at least 1).
void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override
Called by the hosted plugin when a gesture (e.g. mouse drag) starts or ends.
bool isPluginParameterRemoteControllable(int parameterIndex)
Returns true if the given parameter is flagged as remotely controllable.
std::vector< PluginParameterInfo > & getPluginParameterInfos()
Returns a mutable reference to the loaded plugin's parameter descriptor list.
void triggerIOUpdate()
Forces a full re-broadcast of device parameters and routing state to all connected clients.
void prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock) override
Called by the JUCE audio engine before playback starts.
const String getProgramName(int index) override
const String getName() const override
Returns the processor name ("Mema").
void releaseResources() override
Called when playback stops; releases audio processing resources.
void audioDeviceStopped() override
Called when the audio device stops; notifies analyzers to clear their state.
void setPluginParameterRemoteControlInfos(int pluginParameterIndex, bool remoteControllable, ParameterControlType type, int steps)
Marks a plugin parameter as remotely controllable (or not) and sets its control widget type.
void removeOutputCommander(MemaOutputCommander *comander)
Removes a previously registered output commander.
void processBlock(AudioBuffer< float > &buffer, MidiBuffer &midiMessages) override
Standard JUCE AudioProcessor entry point — not used for live audio.
void removeOutputListener(ProcessorDataAnalyzer::Listener *listener)
Unregisters a previously added output analyzer listener.
static constexpr int s_minInputsCount
Minimum number of input channels (always at least 1).
void setMatrixCrosspointEnabledValue(std::uint16_t inputNumber, std::uint16_t outputNumber, bool enabled, MemaChannelCommander *sender=nullptr, int userId=-1)
Enables or disables a crosspoint routing node.
AudioProcessorEditor * createEditor() override
std::function< void()> onPluginParameterInfosChanged
Fired when the set of exposed plugin parameters changes (plugin load/unload or controllability settin...
bool isPluginEnabled()
Returns true when a plugin is loaded and its processing is enabled.
void removeInputListener(ProcessorDataAnalyzer::Listener *listener)
Unregisters a previously added input analyzer listener.
void setPluginPrePostState(bool post)
Selects whether the plugin processes audio before or after the crosspoint matrix.
void initializeInputCommander(MemaInputCommander *commander)
Pushes the current input mute states to a commander that was already registered.
void removeCrosspointCommander(MemaCrosspointCommander *comander)
Removes a previously registered crosspoint commander.
Internal JUCE message posted to the message thread when plugin parameter descriptors change.
Carries the plugin name and complete parameter descriptor list from Mema to Mema.Re clients.
Carries a single normalised plugin parameter value from Mema.Re to Mema.
Instructs clients to tear down and rebuild their UI for a new channel count.
Base class for all messages exchanged between Mema, Mema.Mo, and Mema.Re over TCP.
static SerializableMessage * initFromMemoryBlock(const juce::MemoryBlock &blob)
Deserialises a raw TCP frame into the correct concrete SerializableMessage subclass.
@ None
Sentinel / uninitialised type.
Definition Mema.cpp:27
static PluginParameterInfo fromString(const juce::String &parameterString)
static PluginParameterInfo fromAudioProcessorParameter(juce::AudioProcessorParameter &processorParameter)
static std::vector< PluginParameterInfo > parametersToInfos(juce::Array< juce::AudioProcessorParameter * > processorParameters)
static juce::String getMasterServiceTypeUID()
Returns the UID for the Mema master (server) service.
static int getConnectionPort()
Returns the TCP port used for client connections (55668).
static juce::String getServiceTypeUIDBase()
Returns the base string for building service type UIDs.
static int getBroadcastPort()
Returns the UDP port used for multicast service announcements.