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, bool enabled, bool post, 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, enabled, post, parameterInfos)->getSerializedMessage(), sendIds);
111 }
112 }
113
114 void setPluginProcessingState(bool enabled, bool post, int userId = -1) override
115 {
116 if (m_networkServer && m_networkServer->hasActiveConnections())
117 {
118 auto sendIds = m_networkServer->getActiveConnectionIds();
119 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
120 m_networkServer->enqueueMessage(std::make_unique<PluginProcessingStateMessage>(enabled, post)->getSerializedMessage(), sendIds);
121 }
122 }
123
124 void setPluginParameterValue(std::uint16_t index, std::string id, float currentValue, int userId = -1) override
125 {
126 if (m_networkServer && m_networkServer->hasActiveConnections())
127 {
128 DBG(juce::String(__FUNCTION__) << " sending to net: " << int(index) << " > " << currentValue);
129 auto sendIds = m_networkServer->getActiveConnectionIds();
130 sendIds.erase(std::remove(sendIds.begin(), sendIds.end(), userId), sendIds.end());
131 m_networkServer->enqueueMessage(std::make_unique<PluginParameterValueMessage>(index, id, currentValue)->getSerializedMessage(), sendIds);
132 }
133 }
134
135 void setIOCount(std::uint16_t inputCount, std::uint16_t outputCount) override
136 {
137 if (m_networkServer && m_networkServer->hasActiveConnections())
138 {
139 m_networkServer->enqueueMessage(std::make_unique<ReinitIOCountMessage>(inputCount, outputCount)->getSerializedMessage());
140 }
141 };
142
143 void setNetworkConnection(const std::shared_ptr<InterprocessConnectionServerImpl>& networkServer)
144 {
145 m_networkServer = networkServer;
146 }
147
148private:
149 void setChannelCount(std::uint16_t channelCount) override { ignoreUnused(channelCount); };
150
151private:
152 std::shared_ptr<InterprocessConnectionServerImpl> m_networkServer;
153
154};
155
156//==============================================================================
157MemaProcessor::MemaProcessor(XmlElement* stateXml) :
158 juce::AudioProcessor()
159{
160#ifdef RUN_MESSAGE_TESTS
161 runTests();
162#endif
163
164 // prepare max sized processing data buffer
165 m_processorChannels = new float* [s_maxChannelCount];
166 for (auto i = 0; i < s_maxChannelCount; i++)
167 {
168 m_processorChannels[i] = new float[s_maxNumSamples];
169 for (auto j = 0; j < s_maxNumSamples; j++)
170 {
171 m_processorChannels[i][j] = 0.0f;
172 }
173 }
174
175 m_inputDataAnalyzer = std::make_unique<ProcessorDataAnalyzer>();
176 m_inputDataAnalyzer->setUseProcessingTypes(true, false, false);
177 m_outputDataAnalyzer = std::make_unique<ProcessorDataAnalyzer>();
178 m_outputDataAnalyzer->setUseProcessingTypes(true, false, false);
179
180 m_deviceManager = std::make_unique<AudioDeviceManager>();
181 m_deviceManager->addAudioCallback(this);
182 m_deviceManager->addChangeListener(this);
183
184 if (!setStateXml(stateXml))
185 {
186 setStateXml(nullptr); // call without actual xml config causes default init
187 triggerConfigurationUpdate(false);
188 }
189
190 // init the announcement of this app instance as discoverable service
191 m_serviceTopologyManager = std::make_unique<JUCEAppBasics::ServiceTopologyManager>(
194 JUCEAppBasics::ServiceTopologyManager::getServiceDescription(),
195 JUCEAppBasics::ServiceTopologyManager::getServiceDescription(),
198
199 m_networkServer = std::make_shared<InterprocessConnectionServerImpl>();
200 m_networkServer->beginWaitingForSocket(Mema::ServiceData::getConnectionPort());
201 m_networkServer->onConnectionCreated = [=](int connectionId) {
202 auto connection = dynamic_cast<InterprocessConnectionImpl*>(m_networkServer->getActiveConnection(connectionId).get());
203 if (connection)
204 {
205 connection->onConnectionLost = [=](int connectionId) { DBG(juce::String(__FUNCTION__) << " connection " << connectionId << " lost");
206 m_trafficTypesPerConnection.erase(connectionId);
207 };
208 connection->onConnectionMade = [=](int connectionId ) { DBG(juce::String(__FUNCTION__) << " connection " << connectionId << " made");
209 m_trafficTypesPerConnection[connectionId].clear();
210 if (m_networkServer && m_networkServer->hasActiveConnection(connectionId))
211 {
212 auto paletteStyle = JUCEAppBasics::CustomLookAndFeel::PaletteStyle::PS_Dark;
213 if (getActiveEditor())
214 if (auto claf = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getActiveEditor()->getLookAndFeel()))
215 paletteStyle = claf->getPaletteStyle();
216
217 std::map<std::uint16_t, bool> inputMuteStates;
218 std::map<std::uint16_t, bool> outputMuteStates;
219 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
220 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
221 {
222 const ScopedLock sl(m_audioDeviceIOCallbackLock);
223 inputMuteStates = m_inputMuteStates;
224 outputMuteStates = m_outputMuteStates;
225 matrixCrosspointStates = m_matrixCrosspointStates;
226 matrixCrosspointValues = m_matrixCrosspointValues;
227 }
228
229 auto sendIds = std::vector<int>{ connectionId };
230 auto success = true;
231 success = success && m_networkServer->enqueueMessage(std::make_unique<AnalyzerParametersMessage>(int(getSampleRate()), getBlockSize())->getSerializedMessage(), sendIds);
232 success = success && m_networkServer->enqueueMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount)->getSerializedMessage(), sendIds);
233 success = success && m_networkServer->enqueueMessage(std::make_unique<EnvironmentParametersMessage>(paletteStyle)->getSerializedMessage(), sendIds);
234 success = success && m_networkServer->enqueueMessage(std::make_unique<ControlParametersMessage>(inputMuteStates, outputMuteStates, matrixCrosspointStates, matrixCrosspointValues)->getSerializedMessage(), sendIds);
235 {
236 auto orderedParams = m_pluginParameterInfos;
237 if (!m_pluginParameterDisplayOrder.empty() && m_pluginParameterDisplayOrder.size() == m_pluginParameterInfos.size())
238 {
239 orderedParams.clear();
240 for (int idx : m_pluginParameterDisplayOrder)
241 if (idx >= 0 && idx < static_cast<int>(m_pluginParameterInfos.size()))
242 orderedParams.push_back(m_pluginParameterInfos[idx]);
243 if (orderedParams.size() != m_pluginParameterInfos.size())
244 orderedParams = m_pluginParameterInfos;
245 }
246 success = success && m_networkServer->enqueueMessage(std::make_unique<PluginParameterInfosMessage>(m_pluginInstance ? m_pluginInstance->getName().toStdString() : "", m_pluginEnabled, m_pluginPost, orderedParams)->getSerializedMessage(), sendIds);
247 }
248 if (!success)
249 m_networkServer->cleanupDeadConnections();
250 }
251 };
252 connection->onMessageReceived = [=](int connectionId, const juce::MemoryBlock& message) {
253 if (auto knownMessage = Mema::SerializableMessage::initFromMemoryBlock(message))
254 {
255 knownMessage->setId(connectionId);
256 postMessage(knownMessage);
257 }
258 else
259 DBG(juce::String(__FUNCTION__) + " ignoring unknown message");
260 };
261 }
262 };
263 m_networkCommanderWrapper = std::make_unique<MemaNetworkClientCommanderWrapper>();
264 m_networkCommanderWrapper->setNetworkConnection(m_networkServer);
265 addInputCommander(static_cast<MemaInputCommander*>(m_networkCommanderWrapper.get()));
266 addOutputCommander(static_cast<MemaOutputCommander*>(m_networkCommanderWrapper.get()));
267 addCrosspointCommander(static_cast<MemaCrosspointCommander*>(m_networkCommanderWrapper.get()));
268 addPluginCommander(static_cast<MemaPluginCommander*>(m_networkCommanderWrapper.get()));
269
270 m_timedConfigurationDumper = std::make_unique<juce::TimedCallback>([=]() {
272 {
273 auto config = JUCEAppBasics::AppConfigurationBase::getInstance();
274 if (config != nullptr)
275 config->triggerConfigurationDump(false);
277 }
278 });
279 m_timedConfigurationDumper->startTimer(100);
280}
281
283{
284 m_timedConfigurationDumper->stopTimer();
286 {
287 auto config = JUCEAppBasics::AppConfigurationBase::getInstance();
288 if (config != nullptr)
289 config->triggerConfigurationDump(false);
291 }
292
293 m_networkServer->stop();
294
295 m_deviceManager->removeAudioCallback(this);
296
297 // cleanup processing data buffer
298 for (auto i = 0; i < s_maxChannelCount; i++)
299 delete[] m_processorChannels[i];
300 delete[] m_processorChannels;
301}
302
303std::unique_ptr<juce::XmlElement> MemaProcessor::createStateXml()
304{
305 auto stateXml = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG));
306
307 auto devConfElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::DEVCONFIG));
308 if (m_deviceManager)
309 devConfElm->addChildElement(m_deviceManager->createStateXml().release());
310 stateXml->addChildElement(devConfElm.release());
311
312 auto plgConfElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINCONFIG));
313 plgConfElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::ENABLED), m_pluginEnabled ? 1 : 0);
314 plgConfElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::POST), m_pluginPost ? 1 : 0);
315 if (m_pluginInstance)
316 {
317 plgConfElm->addChildElement(m_pluginInstance->getPluginDescription().createXml().release());
318
319 juce::MemoryBlock destData;
320 m_pluginInstance->getStateInformation(destData);
321 plgConfElm->addTextElement(juce::Base64::toBase64(destData.getData(), destData.getSize()));
322 }
323 for (auto const& plgParam : getPluginParameterInfos())
324 {
325 auto plgParamElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINPARAM));
326 plgParamElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::IDX), plgParam.index);
328 plgParamElm->addTextElement(plgParam.toString());
329 plgConfElm->addChildElement(plgParamElm.release());
330 }
331 if (!m_pluginParameterDisplayOrder.empty())
332 {
333 juce::StringArray orderStrs;
334 for (int idx : m_pluginParameterDisplayOrder)
335 orderStrs.add(juce::String(idx));
336 plgConfElm->setAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::PARAMORDER), orderStrs.joinIntoString(","));
337 }
338 stateXml->addChildElement(plgConfElm.release());
339
340 std::map<std::uint16_t, bool> inputMuteStates;
341 std::map<std::uint16_t, bool> outputMuteStates;
342 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
343 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
344 {
345 // copy the processing relevant variables to not block audio thread during all the xml handling
346 const ScopedLock sl(m_audioDeviceIOCallbackLock);
347 inputMuteStates = m_inputMuteStates;
348 outputMuteStates = m_outputMuteStates;
349 matrixCrosspointStates = m_matrixCrosspointStates;
350 matrixCrosspointValues = m_matrixCrosspointValues;
351 }
352
353 auto inputMutesElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::INPUTMUTES));
354 juce::StringArray imutestatestr;
355 for (auto const& mutestate : inputMuteStates)
356 imutestatestr.add(juce::String(mutestate.first) + "," + juce::String(mutestate.second ? 1 : 0));
357 inputMutesElm->addTextElement(imutestatestr.joinIntoString(";"));
358 stateXml->addChildElement(inputMutesElm.release());
359
360 auto outputMutesElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::OUTPUTMUTES));
361 juce::StringArray omutestatestr;
362 for (auto const& mutestate : outputMuteStates)
363 omutestatestr.add(juce::String(mutestate.first) + "," + juce::String(mutestate.second ? 1 : 0));
364 outputMutesElm->addTextElement(omutestatestr.joinIntoString(";"));
365 stateXml->addChildElement(outputMutesElm.release());
366
367 auto crosspointGainsElm = std::make_unique<juce::XmlElement>(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::CROSSPOINTGAINS));
368 juce::StringArray cgainstatestr;
369 for (auto const& insKV : matrixCrosspointStates)
370 for (auto const& outsKV : insKV.second)
371 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;"
372 crosspointGainsElm->addTextElement(cgainstatestr.joinIntoString(";"));
373 stateXml->addChildElement(crosspointGainsElm.release());
374
375 return stateXml;
376}
377
378bool MemaProcessor::setStateXml(juce::XmlElement* stateXml)
379{
380 if (nullptr == stateXml || stateXml->getTagName() != MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PROCESSORCONFIG))
381 {
382 jassertfalse;
383 return false;
384 }
385
386 juce::XmlElement* deviceSetupXml = nullptr;
387 auto devConfElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::DEVCONFIG));
388 if (nullptr != devConfElm)
389 deviceSetupXml = devConfElm->getChildByName("DEVICESETUP");
390
391 if (m_deviceManager)
392 {
393 // Skip the (expensive, audio-interrupting) device manager re-initialisation when the
394 // DEVCONFIG section of the config is identical to the one that was last successfully
395 // applied. This prevents audio dropouts caused by non-audio config changes (e.g.
396 // UI colour / LookAndFeel) that trigger a full config dump+reload cycle.
397 bool deviceConfigChanged = true;
398 if (m_lastAppliedDeviceConfigXml && devConfElm)
399 deviceConfigChanged = !devConfElm->isEquivalentTo(m_lastAppliedDeviceConfigXml.get(), true);
400 else if (!m_lastAppliedDeviceConfigXml && !devConfElm)
401 deviceConfigChanged = false; // both absent — nothing to change
402
403 if (deviceConfigChanged)
404 {
405 // Hacky bit of device manager initialization:
406 // We first intialize it to be able to get a valid device setup,
407 // then initialize with a dummy xml config to trigger the internal xml structure being reset
408 // and finally apply the original initialized device setup again to have the audio running correctly.
409 // If we did not do so, either the internal xml would not be present as long as the first configuration change was made
410 // and therefor no valid config file could be written by the application or the audio would not be running
411 // on first start and manual config would be required.
412 m_deviceManager->initialiseWithDefaultDevices(s_maxChannelCount, s_maxChannelCount);
413 auto audioDeviceSetup = m_deviceManager->getAudioDeviceSetup();
414 auto result = m_deviceManager->initialise(s_maxChannelCount, s_maxChannelCount, deviceSetupXml, true, {}, &audioDeviceSetup);
415 if (result.isNotEmpty())
416 {
417 // The saved audio device is unavailable; the device manager has already fallen back
418 // to the system default via initialiseWithDefaultDevices above. Warn the user but
419 // continue restoring plugin and routing state from the config — those are independent
420 // of which audio device is active and must not be discarded just because the
421 // preferred interface is currently unplugged.
422 juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::WarningIcon, juce::JUCEApplication::getInstance()->getApplicationName() + " device init failed", result);
423 }
424#if JUCE_IOS
425 if (audioDeviceSetup.bufferSize < 512)
426 audioDeviceSetup.bufferSize = 512; // temp. workaround for iOS where buffersizes <512 lead to no sample data being delivered?
427 m_deviceManager->setAudioDeviceSetup(audioDeviceSetup, true);
428#endif
429 m_lastAppliedDeviceConfigXml = devConfElm ? std::make_unique<juce::XmlElement>(*devConfElm) : nullptr;
430 }
431 }
432 else
433 return false;
434
435
436 auto plgConfElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINCONFIG));
437 if (nullptr != plgConfElm)
438 {
441 auto pluginDescriptionXml = plgConfElm->getChildByName("PLUGIN");
442 if (nullptr != pluginDescriptionXml)
443 {
444 auto pluginDescription = juce::PluginDescription();
445 pluginDescription.loadFromXml(*pluginDescriptionXml);
446 setPlugin(pluginDescription);
447 if (m_pluginInstance)
448 {
449 juce::MemoryOutputStream destDataStream;
450 juce::Base64::convertFromBase64(destDataStream, pluginDescriptionXml->getAllSubText());
451 m_pluginInstance->setStateInformation(destDataStream.getData(), int(destDataStream.getDataSize()));
452 }
453 }
454
455 auto orderAttr = plgConfElm->getStringAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::PARAMORDER));
456 if (orderAttr.isNotEmpty())
457 {
458 m_pluginParameterDisplayOrder.clear();
459 juce::StringArray orderStrs;
460 orderStrs.addTokens(orderAttr, ",", "");
461 for (auto const& s : orderStrs)
462 m_pluginParameterDisplayOrder.push_back(s.getIntValue());
463 }
464
465 for (auto* plgParamElm : plgConfElm->getChildWithTagNameIterator(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::PLUGINPARAM)))
466 {
467 if (nullptr != plgParamElm)
468 {
469 auto index = plgParamElm->getIntAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::IDX));
470 auto paramString = plgParamElm->getAllSubText();
471 auto paramInfo = PluginParameterInfo::fromString(paramString);
472 paramInfo.isRemoteControllable = (plgParamElm->getIntAttribute(MemaAppConfiguration::getAttributeName(MemaAppConfiguration::AttributeID::CONTROLLABLE)) == 1);
473 jassert(paramInfo.index == index);
474 // update the pluginparameterinfo
475 auto iter = std::find_if(getPluginParameterInfos().begin(), getPluginParameterInfos().end(), [=](const auto& info) { return info.index == index; });
476 if (iter != getPluginParameterInfos().end())
477 iter->initializeFromString(paramString);
478 // set the parameter
479 auto* param = getPluginParameter(index);
480 if (param)
481 {
482 jassert(paramInfo.name == param->getName(100));
483 jassert(paramInfo.label == param->getLabel());
484 //jassert(paramInfo.defaultValue == param->getDefaultValue()); //float rounding issue prevents this from making sense
485 jassert(paramInfo.isAutomatable == param->isAutomatable());
486 jassert(paramInfo.category == param->getCategory());
487 if (auto* rangedParam = dynamic_cast<const juce::RangedAudioParameter*>(param))
488 {
489 auto range = rangedParam->getNormalisableRange();
490 jassert(paramInfo.minValue == range.start);
491 jassert(paramInfo.maxValue == range.end);
492 jassert(paramInfo.stepSize == range.interval);
493 jassert(paramInfo.isDiscrete == range.interval > 0.0f);
494 }
495
496 setPluginParameterRemoteControlInfos(paramInfo.index, paramInfo.isRemoteControllable, paramInfo.type, paramInfo.stepCount);
497 param->setValue(paramInfo.currentValue);
498 }
499 }
500 }
501 }
502
503 std::map<std::uint16_t, bool> inputMuteStates;
504 std::map<std::uint16_t, bool> outputMuteStates;
505 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
506 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
507 auto inputMutesElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::INPUTMUTES));
508 if (nullptr != inputMutesElm)
509 {
510 juce::StringArray inMutestateStrList;
511 auto inputMutesElmTxt = inputMutesElm->getAllSubText();
512 inMutestateStrList.addTokens(inputMutesElmTxt, ";", "");
513 jassert(inMutestateStrList.size() == m_inputChannelCount);
514 if (inMutestateStrList.size() != m_inputChannelCount)
515 {
516 // if the config data is insane, initialize with defaults
517 DBG(juce::String(__FUNCTION__) + " config data for input mutes is insane -> resetting to default.");
518 for (auto in = std::uint16_t(1); in <= m_inputChannelCount; in++)
519 inputMuteStates[in] = false;
520 }
521 else
522 {
523 // otherwise use what is available from config
524 for (auto const& inMutestateStr : inMutestateStrList)
525 {
526 juce::StringArray inMutestateStrSplit;
527 inMutestateStrSplit.addTokens(inMutestateStr, ",", "");
528 jassert(2 == inMutestateStrSplit.size());
529 if (2 == inMutestateStrSplit.size())
530 inputMuteStates[std::uint16_t(inMutestateStrSplit[0].getIntValue())] = (1 == std::uint16_t(inMutestateStrSplit[1].getIntValue()));
531 }
532 }
533 }
534 auto outputMutesElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::OUTPUTMUTES));
535 if (nullptr != outputMutesElm)
536 {
537 juce::StringArray outMutestateStrList;
538 auto outputMutesElmTxt = outputMutesElm->getAllSubText();
539 outMutestateStrList.addTokens(outputMutesElmTxt, ";", "");
540 jassert(outMutestateStrList.size() == m_outputChannelCount);
541 if (outMutestateStrList.size() != m_outputChannelCount)
542 {
543 // if the config data is insane, initialize with defaults
544 DBG(juce::String(__FUNCTION__) + " config data for output mutes is insane -> resetting to default.");
545 for (auto out = std::uint16_t(1); out <= m_outputChannelCount; out++)
546 outputMuteStates[out] = false;
547 }
548 else
549 {
550 // otherwise use what is available from config
551 for (auto const& outMutestateStr : outMutestateStrList)
552 {
553 juce::StringArray outMutestateStrSplit;
554 outMutestateStrSplit.addTokens(outMutestateStr, ",", "");
555 jassert(2 == outMutestateStrSplit.size());
556 if (2 == outMutestateStrSplit.size())
557 outputMuteStates[std::uint16_t(outMutestateStrSplit[0].getIntValue())] = (1 == std::uint16_t(outMutestateStrSplit[1].getIntValue()));
558 }
559 }
560 }
561 auto crosspointGainsElm = stateXml->getChildByName(MemaAppConfiguration::getTagName(MemaAppConfiguration::TagID::CROSSPOINTGAINS));
562 if (nullptr != crosspointGainsElm)
563 {
564 juce::StringArray crossGainstateStrList;
565 auto crossGainsElmTxt = crosspointGainsElm->getAllSubText();
566 crossGainstateStrList.addTokens(crossGainsElmTxt, ";", "");
567 jassert(crossGainstateStrList.size() == (m_inputChannelCount * m_outputChannelCount));
568 if (crossGainstateStrList.size() != (m_inputChannelCount * m_outputChannelCount))
569 {
570 // if the config data is insane, initialize with defaults
571 DBG(juce::String(__FUNCTION__) + " config data for crosspoints is insane -> resetting to default.");
572 for (auto in = std::uint16_t(1); in <= m_inputChannelCount; in++)
573 {
574 for (auto out = std::uint16_t(1); out <= m_outputChannelCount; out++)
575 {
576 auto state = false;
577 auto value = 0.0f;
578 if (in == out)
579 {
580 state = true;
581 value = 1.0f;
582 }
583 matrixCrosspointStates[in][out] = state;
584 matrixCrosspointValues[in][out] = value;
585 }
586 }
587 }
588 else
589 {
590 // otherwise use what is available from config
591 for (auto const& crossGainstateStr : crossGainstateStrList)
592 {
593 juce::StringArray crossGainstateStrSplit;
594 crossGainstateStrSplit.addTokens(crossGainstateStr, ",", "");
595 jassert(4 == crossGainstateStrSplit.size());
596 if (4 == crossGainstateStrSplit.size())
597 {
598 matrixCrosspointStates[std::uint16_t(crossGainstateStrSplit[0].getIntValue())][std::uint16_t(crossGainstateStrSplit[1].getIntValue())] = 1 == std::uint16_t(crossGainstateStrSplit[2].getIntValue());
599 matrixCrosspointValues[std::uint16_t(crossGainstateStrSplit[0].getIntValue())][std::uint16_t(crossGainstateStrSplit[1].getIntValue())] = crossGainstateStrSplit[3].getFloatValue();
600 }
601 }
602 }
603 }
604#ifdef DEBUG
605 // sanity check symmetry of crosspoint states
606 auto crosspointStateOutCount = size_t(0);
607 if (0 != matrixCrosspointStates.size())
608 {
609 crosspointStateOutCount = matrixCrosspointStates.begin()->second.size();
610 for (auto const& cpStatKV : matrixCrosspointStates)
611 {
612 jassert(crosspointStateOutCount == cpStatKV.second.size());
613 }
614 }
615 else
616 jassertfalse; // empty crosspoint matrix ?!
617
618 // sanity check symmetry of crosspoint values
619 auto crosspointValOutCount = size_t(0);
620 if (0 != matrixCrosspointValues.size())
621 {
622 crosspointValOutCount = matrixCrosspointValues.begin()->second.size();
623 for (auto const& cpValKV : matrixCrosspointValues)
624 {
625 jassert(crosspointValOutCount == cpValKV.second.size());
626 }
627 }
628 else
629 jassertfalse; // empty crosspoint matrix ?!
630#endif
631 {
632 // copy the processing relevant variables from temp vars here to not block audio thread during all the xml handling above
633 const ScopedLock sl(m_audioDeviceIOCallbackLock);
634 m_inputMuteStates = inputMuteStates;
635 m_outputMuteStates = outputMuteStates;
636 m_matrixCrosspointStates = matrixCrosspointStates;
637 m_matrixCrosspointValues = matrixCrosspointValues;
638 }
639
640 return true;
641}
642
644{
645 auto paletteStyle = JUCEAppBasics::CustomLookAndFeel::PaletteStyle::PS_Dark;
646 if (getActiveEditor())
647 if (auto claf = dynamic_cast<JUCEAppBasics::CustomLookAndFeel*>(&getActiveEditor()->getLookAndFeel()))
648 paletteStyle = claf->getPaletteStyle();
649
650 postMessage(std::make_unique<EnvironmentParametersMessage>(paletteStyle).release());
651}
652
654{
655 postMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount).release());
656}
657
659{
660 if (m_inputDataAnalyzer)
661 m_inputDataAnalyzer->addListener(listener);
662}
663
665{
666 if (m_inputDataAnalyzer)
667 m_inputDataAnalyzer->removeListener(listener);
668}
669
671{
672 if (m_outputDataAnalyzer)
673 m_outputDataAnalyzer->addListener(listener);
674}
675
677{
678 if (m_outputDataAnalyzer)
679 m_outputDataAnalyzer->removeListener(listener);
680}
681
683{
684 if (commander == nullptr)
685 return;
686
687 if (std::find(m_inputCommanders.begin(), m_inputCommanders.end(), commander) == m_inputCommanders.end())
688 {
689 initializeInputCommander(commander);
690
691 m_inputCommanders.push_back(commander);
692 commander->setInputMuteChangeCallback([=](MemaChannelCommander* sender, int channel, bool state) { return setInputMuteState(std::uint16_t(channel), state, sender); } );
693 }
694}
695
697{
698 if (nullptr != commander)
699 {
700 const ScopedLock sl(m_audioDeviceIOCallbackLock);
701 for (auto const& inputMuteStatesKV : m_inputMuteStates)
702 commander->setInputMute(inputMuteStatesKV.first, inputMuteStatesKV.second);
703 }
704}
705
707{
708 if (commander == nullptr)
709 return;
710
711 auto existingInputCommander = std::find(m_inputCommanders.begin(), m_inputCommanders.end(), commander);
712 if (existingInputCommander != m_inputCommanders.end())
713 m_inputCommanders.erase(existingInputCommander);
714}
715
717{
718 if (commander == nullptr)
719 return;
720
721 if (std::find(m_outputCommanders.begin(), m_outputCommanders.end(), commander) == m_outputCommanders.end())
722 {
723 initializeOutputCommander(commander);
724
725 m_outputCommanders.push_back(commander);
726 commander->setOutputMuteChangeCallback([=](MemaChannelCommander* sender, int channel, bool state) { return setOutputMuteState(std::uint16_t(channel), state, sender); });
727 }
728}
729
731{
732 if (nullptr != commander)
733 {
734 const ScopedLock sl(m_audioDeviceIOCallbackLock);
735 for (auto const& outputMuteStatesKV : m_outputMuteStates)
736 commander->setOutputMute(outputMuteStatesKV.first, outputMuteStatesKV.second);
737 }
738}
739
741{
742 if (commander == nullptr)
743 return;
744
745 auto existingOutputCommander = std::find(m_outputCommanders.begin(), m_outputCommanders.end(), commander);
746 if (existingOutputCommander != m_outputCommanders.end())
747 m_outputCommanders.erase(existingOutputCommander);
748}
749
751{
752 if (commander == nullptr)
753 return;
754
755 if (std::find(m_crosspointCommanders.begin(), m_crosspointCommanders.end(), commander) == m_crosspointCommanders.end())
756 {
758
759 m_crosspointCommanders.push_back(commander);
760 commander->setCrosspointEnabledChangeCallback([=](MemaChannelCommander* sender, int input, int output, bool state) { return setMatrixCrosspointEnabledValue(std::uint16_t(input), std::uint16_t(output), state, sender); });
761 commander->setCrosspointFactorChangeCallback([=](MemaChannelCommander* sender, int input, int output, float factor) { return setMatrixCrosspointFactorValue(std::uint16_t(input), std::uint16_t(output), factor, sender); });
762 }
763}
764
766{
767 if (nullptr != commander)
768 {
769 auto matrixCrosspointStates = std::map<std::uint16_t, std::map<std::uint16_t, bool>>();
770 auto matrixCrosspointValues = std::map<std::uint16_t, std::map<std::uint16_t, float>>();
771 {
772 const ScopedLock sl(m_audioDeviceIOCallbackLock);
773 matrixCrosspointStates = m_matrixCrosspointStates;
774 matrixCrosspointValues = m_matrixCrosspointValues;
775 }
776 for (auto const& matrixCrosspointStateKV : matrixCrosspointStates)
777 {
778 for (auto const& matrixCrosspointStateNodeKV : matrixCrosspointStateKV.second)
779 {
780 auto& input = matrixCrosspointStateKV.first;
781 auto& output = matrixCrosspointStateNodeKV.first;
782 auto& enabled = matrixCrosspointStateNodeKV.second;
783 commander->setCrosspointEnabledValue(input, output, enabled);
784 }
785 }
786 for (auto const& matrixCrosspointValKV : matrixCrosspointValues)
787 {
788 for (auto const& matrixCrosspointValNodeKV : matrixCrosspointValKV.second)
789 {
790 auto& input = matrixCrosspointValKV.first;
791 auto& output = matrixCrosspointValNodeKV.first;
792 auto& val = matrixCrosspointValNodeKV.second;
793 commander->setCrosspointFactorValue(input, output, val);
794 }
795 }
796 }
797}
798
800{
801 if (commander == nullptr)
802 return;
803
804 auto existingCrosspointCommander = std::find(m_crosspointCommanders.begin(), m_crosspointCommanders.end(), commander);
805 if (existingCrosspointCommander != m_crosspointCommanders.end())
806 m_crosspointCommanders.erase(existingCrosspointCommander);
807}
808
810{
811 if (commander == nullptr)
812 return;
813
814 if (std::find(m_pluginCommanders.begin(), m_pluginCommanders.end(), commander) == m_pluginCommanders.end())
815 {
816 initializePluginCommander(commander);
817
818 m_pluginCommanders.push_back(commander);
819 commander->setPluginParameterValueChangeCallback([=](MemaPluginCommander* sender, std::uint16_t index, std::string id, float value) { return setPluginParameterValue(index, id, value, sender); });
820 }
821}
822
824{
825 if (nullptr != commander)
826 {
828 //for (auto const& inputMuteStatesKV : m_inputMuteStates)
829 // commander->setInputMute(inputMuteStatesKV.first, inputMuteStatesKV.second);
830 }
831}
832
834{
835 if (commander == nullptr)
836 return;
837
838 auto existingPluginCommander = std::find(m_pluginCommanders.begin(), m_pluginCommanders.end(), commander);
839 if (existingPluginCommander != m_pluginCommanders.end())
840 m_pluginCommanders.erase(existingPluginCommander);
841}
842
844{
845 for (auto const& ic : m_inputCommanders)
846 {
847 ic->setChannelCount(m_inputChannelCount > 0 ? std::uint16_t(m_inputChannelCount) : 0);
849 }
850 for (auto const& cc : m_crosspointCommanders)
851 {
852 cc->setIOCount(m_inputChannelCount > 0 ? std::uint16_t(m_inputChannelCount) : 0, m_outputChannelCount > 0 ? std::uint16_t(m_outputChannelCount) : 0);
854 }
855 for (auto const& oc : m_outputCommanders)
856 {
857 oc->setChannelCount(m_outputChannelCount > 0 ? std::uint16_t(m_outputChannelCount) : 0);
859 }
860 for (auto const& pc : m_pluginCommanders)
861 {
863 }
864}
865
866bool MemaProcessor::getInputMuteState(std::uint16_t inputChannelNumber)
867{
868 jassert(inputChannelNumber > 0);
869 const ScopedLock sl(m_audioDeviceIOCallbackLock);
870 return m_inputMuteStates[inputChannelNumber];
871}
872
873void MemaProcessor::setInputMuteState(std::uint16_t inputChannelNumber, bool muted, MemaChannelCommander* sender, int userId)
874{
875 jassert(inputChannelNumber > 0);
876
877 for (auto const& inputCommander : m_inputCommanders)
878 {
879 if (inputCommander != reinterpret_cast<MemaInputCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
880 inputCommander->setInputMute(inputChannelNumber, muted, userId);
881 }
882
883 {
884 const ScopedLock sl(m_audioDeviceIOCallbackLock);
885 m_inputMuteStates[inputChannelNumber] = muted;
886 }
887
889}
890
891bool MemaProcessor::getMatrixCrosspointEnabledValue(std::uint16_t inputNumber, std::uint16_t outputNumber)
892{
893 jassert(inputNumber > 0 && outputNumber > 0);
894 {
895 const ScopedLock sl(m_audioDeviceIOCallbackLock);
896 if (m_matrixCrosspointStates.count(inputNumber) != 0)
897 if (m_matrixCrosspointStates.at(inputNumber).count(outputNumber) != 0)
898 return m_matrixCrosspointStates.at(inputNumber).at(outputNumber);
899 }
900 jassertfalse;
901 return false;
902}
903
904void MemaProcessor::setMatrixCrosspointEnabledValue(std::uint16_t inputNumber, std::uint16_t outputNumber, bool enabled, MemaChannelCommander* sender, int userId)
905{
906 jassert(inputNumber > 0 && outputNumber > 0);
907
908 for (auto const& crosspointCommander : m_crosspointCommanders)
909 {
910 if (crosspointCommander != reinterpret_cast<MemaCrosspointCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
911 crosspointCommander->setCrosspointEnabledValue(inputNumber, outputNumber, enabled, userId);
912 }
913
914 {
915 const ScopedLock sl(m_audioDeviceIOCallbackLock);
916#ifdef DEBUG
917 // check crosspoint existance - if it is not existing, we should somehow verify afterwards that the matrix symmetry is still given...
918 jassert(0 != m_matrixCrosspointStates.count(inputNumber));
919 if (0 != m_matrixCrosspointStates.count(inputNumber))
920 {
921 jassert(0 != m_matrixCrosspointStates.at(inputNumber).count(outputNumber));
922 }
923#endif
924 m_matrixCrosspointStates[inputNumber][outputNumber] = enabled;
925 }
926
928}
929
930float MemaProcessor::getMatrixCrosspointFactorValue(std::uint16_t inputNumber, std::uint16_t outputNumber)
931{
932 jassert(inputNumber > 0 && outputNumber > 0);
933 {
934 const ScopedLock sl(m_audioDeviceIOCallbackLock);
935 if (m_matrixCrosspointValues.count(inputNumber) != 0)
936 if (m_matrixCrosspointValues.at(inputNumber).count(outputNumber) != 0)
937 return m_matrixCrosspointValues.at(inputNumber).at(outputNumber);
938 }
939 jassertfalse;
940 return 0.0f;
941}
942
943void MemaProcessor::setMatrixCrosspointFactorValue(std::uint16_t inputNumber, std::uint16_t outputNumber, float factor, MemaChannelCommander* sender, int userId)
944{
945 jassert(inputNumber > 0 && outputNumber > 0);
946
947 for (auto const& crosspointCommander : m_crosspointCommanders)
948 {
949 if (crosspointCommander != reinterpret_cast<MemaCrosspointCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
950 crosspointCommander->setCrosspointFactorValue(inputNumber, outputNumber, factor, userId);
951 }
952
953 {
954 const ScopedLock sl(m_audioDeviceIOCallbackLock);
955#ifdef DEBUG
956 // check crosspoint existance - if it is not existing, we should somehow verify afterwards that the matrix symmetry is still given...
957 jassert(0 != m_matrixCrosspointValues.count(inputNumber));
958 if (0 != m_matrixCrosspointValues.count(inputNumber))
959 {
960 jassert(0 != m_matrixCrosspointValues.at(inputNumber).count(outputNumber));
961 }
962#endif
963 m_matrixCrosspointValues[inputNumber][outputNumber] = factor;
964 }
965
967}
968
969bool MemaProcessor::getOutputMuteState(std::uint16_t outputChannelNumber)
970{
971 jassert(outputChannelNumber > 0);
972 const ScopedLock sl(m_audioDeviceIOCallbackLock);
973 return m_outputMuteStates[outputChannelNumber];
974}
975
976void MemaProcessor::setOutputMuteState(std::uint16_t outputChannelNumber, bool muted, MemaChannelCommander* sender, int userId)
977{
978 jassert(outputChannelNumber > 0);
979
980 for (auto const& outputCommander : m_outputCommanders)
981 {
982 if (outputCommander != reinterpret_cast<MemaOutputCommander*>(sender) || nullptr != reinterpret_cast<MemaNetworkClientCommanderWrapper*>(sender))
983 outputCommander->setOutputMute(outputChannelNumber, muted, userId);
984 }
985
986 {
987 const ScopedLock sl(m_audioDeviceIOCallbackLock);
988 m_outputMuteStates[outputChannelNumber] = muted;
989 }
990
992}
993
994void MemaProcessor::setChannelCounts(std::uint16_t inputChannelCount, std::uint16_t outputChannelCount)
995{
996 auto reinitRequired = false;
997 if (m_inputChannelCount != inputChannelCount)
998 {
999 m_inputChannelCount = inputChannelCount;
1000 reinitRequired = true;
1001 }
1002 if (m_outputChannelCount != outputChannelCount)
1003 {
1004 m_outputChannelCount = outputChannelCount;
1005 reinitRequired = true;
1006 }
1007 if (reinitRequired)
1008 {
1009 // Reconfigure plugin channel layout whenever either count changes: the correct
1010 // count depends on the current pre/post position (see configurePluginForCurrentPosition).
1011 {
1012 const ScopedLock sl(m_pluginProcessingLock);
1013 configurePluginForCurrentPosition();
1014 }
1015 postMessage(new ReinitIOCountMessage(m_inputChannelCount, m_outputChannelCount));
1016 }
1017}
1018
1019void MemaProcessor::configurePluginForCurrentPosition()
1020{
1021 // Must be called under m_pluginProcessingLock.
1022 if (!m_pluginInstance)
1023 return;
1024
1025 // AU plugins assert inside canApplyBusesLayout if setBusesLayout is called
1026 // while the plugin is in the prepared state. Release first so negotiation
1027 // always starts from a clean slate; prepareToPlay() re-prepares at the end.
1028 m_pluginInstance->releaseResources();
1029
1030 // Pre-matrix: device input count is the upper bound for plugin I/O.
1031 // Post-matrix: device output count is the upper bound for plugin I/O.
1032 auto channelLimit = m_pluginPost ? m_outputChannelCount : m_inputChannelCount;
1033
1034 // Walk from the widest possible count down to mono. At each count, all
1035 // named sets (speaker-labelled, including Atmos, ambisonics, etc.) are
1036 // offered first via channelSetsWithNumberOfChannels(), which covers every
1037 // format JUCE knows about for that count without requiring a hand-written
1038 // list. A generic discrete fallback is tried last for counts that have no
1039 // named set or whose named sets were all rejected.
1040 // This works correctly for any device channel count — 12, 32, 128, or more.
1041 auto tryLayout = [&](const juce::AudioChannelSet& candidate) -> bool
1042 {
1043 juce::AudioProcessor::BusesLayout layout;
1044 layout.inputBuses.add(candidate);
1045 layout.outputBuses.add(candidate);
1046
1047 if (m_pluginInstance->setBusesLayout(layout))
1048 {
1049 m_pluginConfiguredChannelCount = candidate.size();
1050 m_pluginInstance->prepareToPlay(getSampleRate(), getBlockSize());
1051 return true;
1052 }
1053 return false;
1054 };
1055
1056 for (int count = channelLimit; count >= 1; --count)
1057 {
1058 for (auto const& named : juce::AudioChannelSet::channelSetsWithNumberOfChannels(count))
1059 if (tryLayout(named))
1060 return;
1061
1062 if (tryLayout(juce::AudioChannelSet::discreteChannels(count)))
1063 return;
1064 }
1065
1066 // Absolute fallback: no layout was accepted at all — use the legacy API.
1067 m_pluginInstance->setPlayConfigDetails(channelLimit, channelLimit, getSampleRate(), getBlockSize());
1068 m_pluginConfiguredChannelCount = channelLimit;
1069 m_pluginInstance->prepareToPlay(getSampleRate(), getBlockSize());
1070}
1071
1072bool MemaProcessor::setPlugin(const juce::PluginDescription& pluginDescription)
1073{
1074 juce::AudioPluginFormatManager formatManager;
1075 addDefaultFormatsToManager(formatManager);
1076 auto registeredFormats = formatManager.getFormats();
1077
1078 auto success = false;
1079 juce::String errorMessage = "Unsupported plug-in format.";
1080
1081 for (auto const& format : registeredFormats)
1082 {
1083 if (format->getName() == pluginDescription.pluginFormatName)
1084 {
1086
1087 // threadsafe locking in scope to access plugin
1088 {
1089 const ScopedLock sl(m_pluginProcessingLock);
1090
1091 // Detach listeners from the outgoing plugin instance before replacing it
1092 if (m_pluginInstance)
1093 {
1094 for (auto const& param : m_pluginInstance->getParameters())
1095 param->removeListener(this);
1096 }
1097
1098 m_pluginParameterInfos.clear();
1099 m_pluginParameterDisplayOrder.clear();
1100
1101 m_pluginInstance = format->createInstanceFromDescription(pluginDescription, getSampleRate(), getBlockSize(), errorMessage);
1102 if (m_pluginInstance)
1103 {
1104 // Size the plugin correctly for its current pre/post position:
1105 // pre-matrix → inputChannelCount × inputChannelCount
1106 // post-matrix → outputChannelCount × outputChannelCount
1107 configurePluginForCurrentPosition();
1108
1109 // Extract parameters here
1110 m_pluginParameterInfos.clear();
1111 for (auto const& param : m_pluginInstance->getParameters())
1112 m_pluginParameterInfos.push_back(PluginParameterInfo::fromAudioProcessorParameter(*param));
1113 postMessage(std::make_unique<PluginParameterInfosChangedMessage>().release());
1114
1115 // Attach listeners to track parameter changes
1116 for (auto const& param : m_pluginInstance->getParameters())
1117 param->addListener(this);
1118 }
1119 }
1120 success = errorMessage.isEmpty();
1121 break;
1122 }
1123 }
1124
1125 if (!success)
1126 juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::WarningIcon, "Loading error", "Loading of the selected plug-in " + pluginDescription.name + " failed.\n" + errorMessage);
1127 else if (onPluginSet)
1128 onPluginSet(pluginDescription);
1129
1130 triggerConfigurationUpdate(false);
1131
1132 return success;
1133}
1134
1136{
1137 if (m_pluginInstance)
1138 return m_pluginInstance->getPluginDescription();
1139
1140 return {};
1141}
1142
1144{
1145 // threadsafe locking in scope to access plugin enabled
1146 {
1147 const ScopedLock sl(m_pluginProcessingLock);
1148 m_pluginEnabled = enabled;
1149 }
1150
1151 for (auto& pluginCommander : m_pluginCommanders)
1152 pluginCommander->setPluginProcessingState(m_pluginEnabled, m_pluginPost);
1153
1154 triggerConfigurationUpdate(false);
1155}
1156
1158{
1159 return m_pluginEnabled;
1160}
1161
1163{
1164 // threadsafe locking in scope to access plugin
1165 {
1166 const ScopedLock sl(m_pluginProcessingLock);
1167 if (m_pluginPost != post)
1168 {
1169 m_pluginPost = post;
1170 // Reconfigure channel layout for the new position before the audio thread
1171 // can route any buffers through the plugin:
1172 // pre-matrix → inputChannelCount × inputChannelCount
1173 // post-matrix → outputChannelCount × outputChannelCount
1174 configurePluginForCurrentPosition();
1175 }
1176 }
1177
1178 for (auto& pluginCommander : m_pluginCommanders)
1179 pluginCommander->setPluginProcessingState(m_pluginEnabled, m_pluginPost);
1180
1181 triggerConfigurationUpdate(false);
1182}
1183
1185{
1186 return m_pluginPost;
1187}
1188
1190{
1192
1193 // threadsafe locking in scope to access plugin
1194 {
1195 const ScopedLock sl(m_pluginProcessingLock);
1196 m_pluginInstance.reset();
1197 m_pluginParameterInfos.clear();
1198 m_pluginParameterDisplayOrder.clear();
1199 }
1200
1201 postMessage(std::make_unique<PluginParameterInfosChangedMessage>().release());
1202
1203 if (onPluginSet)
1204 onPluginSet(juce::PluginDescription());
1205
1206 setPluginEnabledState(false);
1207}
1208
1210{
1211 if (m_pluginInstance)
1212 {
1213 auto pluginEditorInstance = m_pluginInstance->createEditorIfNeeded();
1214 if (pluginEditorInstance && !m_pluginEditorWindow)
1215 {
1216 m_pluginEditorWindow = std::make_unique<ResizeableWindowWithTitleBarAndCloseCallback>(juce::JUCEApplication::getInstance()->getApplicationName() + " : " + m_pluginInstance->getName(), true);
1217 m_pluginEditorWindow->setResizable(false, false);
1218 m_pluginEditorWindow->setContentOwned(pluginEditorInstance, true);
1219 m_pluginEditorWindow->onClosed = [=]() { closePluginEditor(true); };
1220 m_pluginEditorWindow->setVisible(true);
1221 }
1222 }
1223}
1224
1225void MemaProcessor::closePluginEditor(bool deleteEditorWindow)
1226{
1227 if (m_pluginInstance)
1228 std::unique_ptr<juce::AudioProcessorEditor>(m_pluginInstance->getActiveEditor()).reset();
1229 if (deleteEditorWindow)
1230 m_pluginEditorWindow.reset();
1231}
1232
1233std::vector<PluginParameterInfo>& MemaProcessor::getPluginParameterInfos()
1234{
1235 return m_pluginParameterInfos;
1236}
1237
1238void MemaProcessor::setPluginParameterRemoteControlInfos(int parameterIndex, bool remoteControllable, ParameterControlType type, int steps)
1239{
1240 if (parameterIndex >= 0 && parameterIndex < m_pluginParameterInfos.size())
1241 {
1242 m_pluginParameterInfos[parameterIndex].isRemoteControllable = remoteControllable;
1243 m_pluginParameterInfos[parameterIndex].type = type;
1244 m_pluginParameterInfos[parameterIndex].stepCount = steps;
1245
1246 jassert(m_pluginInstance);
1247 if (m_pluginInstance)
1248 {
1249 m_pluginParameterInfos[parameterIndex].stepNames.clear();
1250 auto param = m_pluginInstance->getParameters()[parameterIndex];
1251 if (param && steps > 1)
1252 {
1253 auto stepSize = 1.0f / (steps - 1);
1254 for (int i = 0; i < steps; i++)
1255 {
1256 m_pluginParameterInfos[parameterIndex].stepNames.push_back(param->getText(i * stepSize, 100).toStdString());
1257 }
1258 }
1259 }
1260
1261 triggerConfigurationUpdate(false);
1262 postMessage(std::make_unique<PluginParameterInfosChangedMessage>().release());
1263 }
1264}
1265
1266void MemaProcessor::setPluginParameterDisplayOrder(const std::vector<int>& order)
1267{
1268 m_pluginParameterDisplayOrder = order;
1269 triggerConfigurationUpdate(false);
1270 postMessage(std::make_unique<PluginParameterInfosChangedMessage>().release());
1271}
1272
1274{
1275 return m_pluginParameterDisplayOrder;
1276}
1277
1279{
1280 if (parameterIndex >= 0 && parameterIndex < m_pluginParameterInfos.size())
1281 return m_pluginParameterInfos[parameterIndex].isRemoteControllable;
1282 else
1283 return false;
1284}
1285
1286float MemaProcessor::getPluginParameterValue(std::uint16_t parameterIndex) const
1287{
1288 if (!m_pluginInstance)
1289 return 0.0f;
1290
1291 auto& parameters = m_pluginInstance->getParameters();
1292 auto idxInRange = parameterIndex >= 0 && parameterIndex < parameters.size();
1293 jassert(idxInRange);
1294 if (!idxInRange)
1295 return 0.0f;
1296
1297 const ScopedLock sl(m_pluginProcessingLock);
1298 return parameters[parameterIndex]->getValue();
1299}
1300
1301void MemaProcessor::setPluginParameterValue(std::uint16_t parameterIndex, std::string id, float normalizedValue, MemaPluginCommander* sender, int userId)
1302{
1303 if (!m_pluginInstance)
1304 return;
1305
1306 auto& parameters = m_pluginInstance->getParameters();
1307 auto idxInRange = parameterIndex >= 0 && parameterIndex < parameters.size();
1308 jassert(idxInRange);
1309 if (!idxInRange)
1310 return;
1311
1312 for (auto const& pluginCommander : m_pluginCommanders)
1313 {
1314 if (pluginCommander != reinterpret_cast<MemaPluginCommander*>(sender) || nullptr != static_cast<MemaNetworkClientCommanderWrapper*>(sender))
1315 pluginCommander->setPluginParameterValue(parameterIndex, id, normalizedValue, userId);
1316 }
1317
1318
1319 {
1320 const ScopedLock sl(m_pluginProcessingLock);
1321
1322 parameters[parameterIndex]->setValue(normalizedValue);
1323
1324 // Update cached value
1325 if (parameterIndex < m_pluginParameterInfos.size())
1326 m_pluginParameterInfos[parameterIndex].currentValue = normalizedValue;
1327 }
1328
1330}
1331
1332juce::AudioProcessorParameter* MemaProcessor::getPluginParameter(int parameterIndex) const
1333{
1334 const ScopedLock sl(m_pluginProcessingLock);
1335
1336 if (!m_pluginInstance)
1337 return nullptr;
1338
1339 auto& parameters = m_pluginInstance->getParameters();
1340
1341 if (parameterIndex >= 0 && parameterIndex < parameters.size())
1342 return parameters[parameterIndex];
1343
1344 return nullptr;
1345}
1346
1348{
1349 if (m_deviceManager)
1350 return m_deviceManager.get();
1351 else
1352 return nullptr;
1353}
1354
1355std::map<int, std::pair<double, bool>> MemaProcessor::getNetworkHealth()
1356{
1357 if (m_networkServer)
1358 return m_networkServer->getListHealth();
1359 else
1360 return {};
1361}
1362
1363JUCEAppBasics::SessionServiceTopology MemaProcessor::getDiscoveredServicesTopology()
1364{
1365 if (m_serviceTopologyManager)
1366 return m_serviceTopologyManager->getDiscoveredServiceTopology();
1367 else
1368 return {};
1369}
1370
1371//==============================================================================
1372const String MemaProcessor::getName() const
1373{
1374 return m_Name;
1375}
1376
1377void MemaProcessor::prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock)
1378{
1379 setRateAndBufferSizeDetails(sampleRate, maximumExpectedSamplesPerBlock);
1380
1381 // threadsafe locking in scope to access plugin
1382 {
1383 const ScopedLock sl(m_pluginProcessingLock);
1384 // configurePluginForCurrentPosition calls setPlayConfigDetails before prepareToPlay,
1385 // ensuring the AU plugin's bus layout (and thus preparedChannels) matches the buffer
1386 // channel count. Calling prepareToPlay directly risks the AU reinitializing with its
1387 // default layout (e.g. stereo), causing a preparedChannels mismatch assertion.
1388 configurePluginForCurrentPosition();
1389 }
1390
1391 if (m_inputDataAnalyzer)
1392 m_inputDataAnalyzer->initializeParameters(sampleRate, maximumExpectedSamplesPerBlock);
1393 if (m_outputDataAnalyzer)
1394 m_outputDataAnalyzer->initializeParameters(sampleRate, maximumExpectedSamplesPerBlock);
1395
1396 postMessage(std::make_unique<AnalyzerParametersMessage>(int(sampleRate), maximumExpectedSamplesPerBlock).release());
1397}
1398
1400{
1401 // threadsafe locking in scope to access plugin
1402 {
1403 const ScopedLock sl(m_pluginProcessingLock);
1404 if (m_pluginInstance)
1405 m_pluginInstance->releaseResources();
1406 }
1407
1408 if (m_inputDataAnalyzer)
1409 m_inputDataAnalyzer->clearParameters();
1410 if (m_outputDataAnalyzer)
1411 m_outputDataAnalyzer->clearParameters();
1412}
1413
1414void MemaProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
1415{
1416 ignoreUnused(midiMessages);
1417
1418 // the lock is currently gloablly taken in audioDeviceIOCallback which is calling this method
1419 //const ScopedLock sl(m_audioDeviceIOCallbackLock);
1420
1421 auto reinitRequired = false;
1422
1423 jassert(s_minInputsCount <= m_inputChannelCount);
1424 jassert(s_minOutputsCount <= m_outputChannelCount);
1425
1426 if (m_inputChannelCount > m_inputMuteStates.size())
1427 reinitRequired = true;
1428
1429 for (std::uint16_t input = 1; input <= m_inputChannelCount; input++)
1430 {
1431 if (m_inputMuteStates.count(input) != 0 && m_inputMuteStates.at(input))
1432 {
1433 auto channelIdx = input - 1;
1434 buffer.clear(channelIdx, 0, buffer.getNumSamples());
1435 }
1436 }
1437
1438 postMessage(std::make_unique<AudioInputBufferMessage>(buffer).release());
1439
1440 // threadsafe locking in scope to access plugin - processing only takes place if NOT set to post matrix
1441 {
1442 const ScopedLock sl(m_pluginProcessingLock);
1443 if (m_pluginInstance && m_pluginEnabled && !m_pluginPost)
1444 {
1445 // Pass a sub-buffer view sized to the negotiated plugin channel count.
1446 // This may be narrower than m_inputChannelCount when the plugin only
1447 // accepted a layout smaller than the device input count.
1448 juce::AudioBuffer<float> pluginBuffer(buffer.getArrayOfWritePointers(), m_pluginConfiguredChannelCount, buffer.getNumSamples());
1449 m_pluginInstance->processBlock(pluginBuffer, midiMessages);
1450 }
1451 }
1452
1453 // process data in buffer to be what shall be used as output
1454 juce::AudioBuffer<float> processedBuffer;
1455 processedBuffer.setSize(m_outputChannelCount, buffer.getNumSamples(), false, true, true);
1456 for (std::uint16_t inputIdx = 0; inputIdx < m_inputChannelCount; inputIdx++)
1457 {
1458 for (std::uint16_t outputIdx = 0; outputIdx < m_outputChannelCount; outputIdx++)
1459 {
1460 if (0 != m_matrixCrosspointStates.count(inputIdx + 1) && 0 != m_matrixCrosspointValues.count(inputIdx + 1))
1461 {
1462 if (0 != m_matrixCrosspointStates[inputIdx + 1].count(outputIdx + 1) && 0 != m_matrixCrosspointValues[inputIdx + 1].count(outputIdx + 1))
1463 {
1464 auto& enabled = m_matrixCrosspointStates.at(inputIdx + 1).at(outputIdx + 1);
1465 auto& factor = m_matrixCrosspointValues.at(inputIdx + 1).at(outputIdx + 1);
1466 auto gain = !enabled ? 0.0f : factor;
1467 processedBuffer.addFrom(outputIdx, 0, buffer.getReadPointer(inputIdx), buffer.getNumSamples(), gain);
1468 }
1469 }
1470 }
1471 }
1472 buffer.makeCopyOf(processedBuffer, true);
1473
1474 if (m_outputChannelCount > m_outputMuteStates.size())
1475 reinitRequired = true;
1476
1477 // threadsafe locking in scope to access plugin - processing only takes place if set to post matrix
1478 {
1479 const ScopedLock sl(m_pluginProcessingLock);
1480 if (m_pluginInstance && m_pluginEnabled && m_pluginPost)
1481 {
1482 // Pass a sub-buffer view sized to the negotiated plugin channel count.
1483 // This may be narrower than m_outputChannelCount when the plugin only
1484 // accepted a layout smaller than the device output count.
1485 juce::AudioBuffer<float> pluginBuffer(buffer.getArrayOfWritePointers(), m_pluginConfiguredChannelCount, buffer.getNumSamples());
1486 m_pluginInstance->processBlock(pluginBuffer, midiMessages);
1487 }
1488 }
1489
1490 for (std::uint16_t output = 1; output <= m_outputChannelCount; output++)
1491 {
1492 if (m_outputMuteStates.count(output) != 0 && m_outputMuteStates.at(output))
1493 {
1494 auto channelIdx = output - 1;
1495 buffer.clear(channelIdx, 0, buffer.getNumSamples());
1496 }
1497 }
1498
1499 postMessage(std::make_unique<AudioOutputBufferMessage>(buffer).release());
1500
1501 if (reinitRequired)
1502 postMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount).release());
1503}
1504
1505void MemaProcessor::handleMessage(const Message& message)
1506{
1508 juce::MemoryBlock serializedMessageMemoryBlock;
1509
1510 auto origId = -1;
1511 if (auto const sm = dynamic_cast<const SerializableMessage*>(&message))
1512 origId = sm->getId();
1513
1514 if (auto const epm = dynamic_cast<const EnvironmentParametersMessage*>(&message))
1515 {
1516 serializedMessageMemoryBlock = epm->getSerializedMessage();
1517 tId = epm->getType();
1518 }
1519 else if (auto const apm = dynamic_cast<const AnalyzerParametersMessage*>(&message))
1520 {
1521 serializedMessageMemoryBlock = apm->getSerializedMessage();
1522 tId = apm->getType();
1523 }
1524 else if (auto const iom = dynamic_cast<const ReinitIOCountMessage*> (&message))
1525 {
1526 auto inputCount = iom->getInputCount();
1527 jassert(inputCount > 0);
1528 auto outputCount = iom->getOutputCount();
1529 jassert(outputCount > 0);
1530
1531 for (auto const& inputCommander : m_inputCommanders)
1532 inputCommander->setChannelCount(inputCount);
1533
1534 for (auto const& outputCommander : m_outputCommanders)
1535 outputCommander->setChannelCount(outputCount);
1536
1537 for (auto const& crosspointCommander : m_crosspointCommanders)
1538 crosspointCommander->setIOCount(inputCount, outputCount);
1539
1540 initializeCtrlValues(iom->getInputCount(), iom->getOutputCount());
1541
1542 serializedMessageMemoryBlock = iom->getSerializedMessage();
1543
1544 tId = iom->getType();
1545 }
1546 else if (auto m = dynamic_cast<const AudioBufferMessage*> (&message))
1547 {
1548 if (m->getFlowDirection() == AudioBufferMessage::FlowDirection::Input && m_inputDataAnalyzer)
1549 {
1550 m_inputDataAnalyzer->analyzeData(m->getAudioBuffer());
1551 }
1552 else if (m->getFlowDirection() == AudioBufferMessage::FlowDirection::Output && m_outputDataAnalyzer)
1553 {
1554 m_outputDataAnalyzer->analyzeData(m->getAudioBuffer());
1555 }
1556
1557 serializedMessageMemoryBlock = m->getSerializedMessage();
1558
1559 tId = m->getType();
1560 }
1561 else if (auto const cpm = dynamic_cast<const Mema::ControlParametersMessage*>(&message))
1562 {
1563 DBG(juce::String(__FUNCTION__) << " i:" << cpm->getInputMuteStates().size() << " o:" << cpm->getOutputMuteStates().size() << " c:" << cpm->getCrosspointStates().size());
1564 for (auto const& inputMuteState : cpm->getInputMuteStates())
1565 setInputMuteState(inputMuteState.first, inputMuteState.second, static_cast<MemaInputCommander*>(m_networkCommanderWrapper.get()));
1566 for (auto const& outputMuteState : cpm->getOutputMuteStates())
1567 setOutputMuteState(outputMuteState.first, outputMuteState.second, static_cast<MemaOutputCommander*>(m_networkCommanderWrapper.get()));
1568 for (auto const& crosspointStateKV : cpm->getCrosspointStates())
1569 {
1570 auto& inputNumber = crosspointStateKV.first;
1571 for (auto const& crosspointOStateKV : crosspointStateKV.second)
1572 {
1573 auto& outputNumber = crosspointOStateKV.first;
1574 setMatrixCrosspointEnabledValue(inputNumber, outputNumber, crosspointOStateKV.second, static_cast<MemaCrosspointCommander*>(m_networkCommanderWrapper.get()), origId);
1575 }
1576 }
1577 for (auto const& crosspointValueKV : cpm->getCrosspointValues())
1578 {
1579 auto& inputNumber = crosspointValueKV.first;
1580 for (auto const& crosspointOValueKV : crosspointValueKV.second)
1581 {
1582 auto& outputNumber = crosspointOValueKV.first;
1583 setMatrixCrosspointFactorValue(inputNumber, outputNumber, crosspointOValueKV.second, static_cast<MemaCrosspointCommander*>(m_networkCommanderWrapper.get()), origId);
1584 }
1585 }
1586
1587 tId = cpm->getType();
1588 }
1589 else if (auto const dtsm = dynamic_cast<const Mema::DataTrafficTypeSelectionMessage*>(&message))
1590 {
1591 if (!dtsm->hasUserId())
1592 DBG("Incoming DataTrafficTypeSelecitonMessage cannot be associated with a connection");
1593 else
1594 setTrafficTypesForConnectionId(dtsm->getTrafficTypes(), origId);
1595
1596 tId = dtsm->getType();
1597 }
1598 else if (auto const ppim = dynamic_cast<const PluginParameterInfosMessage*>(&message))
1599 {
1600 DBG(juce::String(__FUNCTION__) << " ppiCnt:" << ppim->getParameterInfos().size());
1601
1602 jassertfalse; // why would parameterinfos of a plugin loaded in mema be changed externally?
1603
1604 serializedMessageMemoryBlock = ppim->getSerializedMessage();
1605
1606 tId = ppim->getType();
1607 }
1608 else if (auto const ppvm = dynamic_cast<const PluginParameterValueMessage*>(&message))
1609 {
1610 DBG(juce::String(__FUNCTION__) << " ppvIdx:" << int(ppvm->getParameterIndex()) << " > " << ppvm->getCurrentValue());
1611
1612 setPluginParameterValue(ppvm->getParameterIndex(), ppvm->getParameterId().toStdString(), ppvm->getCurrentValue(), static_cast<MemaPluginCommander*>(m_networkCommanderWrapper.get()), origId);
1613
1614 serializedMessageMemoryBlock = ppvm->getSerializedMessage();
1615
1616 tId = ppvm->getType();
1617 }
1618 else if (auto const pesm = dynamic_cast<const PluginProcessingStateMessage*>(&message))
1619 {
1620 DBG(juce::String(__FUNCTION__) << " pluginEnabled:" << int(pesm->isEnabled()) << " pluginPost:" << int(pesm->isPost()));
1621
1622 // Update state directly to avoid triggering the commander broadcasts (which would double-relay to other clients).
1623 // The fallthrough sendMessageToClients below handles relaying to other Mema.Re clients.
1624 {
1625 const ScopedLock sl(m_pluginProcessingLock);
1626 m_pluginEnabled = pesm->isEnabled();
1627 if (m_pluginPost != pesm->isPost())
1628 {
1629 m_pluginPost = pesm->isPost();
1630 configurePluginForCurrentPosition();
1631 }
1632 }
1633 triggerConfigurationUpdate(false);
1634
1636 onPluginProcessingStateChanged(m_pluginEnabled, m_pluginPost);
1637
1638 serializedMessageMemoryBlock = pesm->getSerializedMessage();
1639
1640 tId = pesm->getType();
1641 }
1642 // exception - totally different message type, to decouple callback from processing asynchronously...
1643 else if (auto const ppicm = dynamic_cast<const PluginParameterInfosChangedMessage*>(&message))
1644 {
1645 // Build a copy of the parameter list in user-defined display order (falls back to natural order)
1646 auto orderedParams = m_pluginParameterInfos;
1647 if (!m_pluginParameterDisplayOrder.empty() && m_pluginParameterDisplayOrder.size() == m_pluginParameterInfos.size())
1648 {
1649 orderedParams.clear();
1650 for (int idx : m_pluginParameterDisplayOrder)
1651 if (idx >= 0 && idx < static_cast<int>(m_pluginParameterInfos.size()))
1652 orderedParams.push_back(m_pluginParameterInfos[idx]);
1653 if (orderedParams.size() != m_pluginParameterInfos.size())
1654 orderedParams = m_pluginParameterInfos; // safety fallback
1655 }
1656
1657 // Broadcast updated parameter infos to all connected network clients
1658 for (auto& pluginCommander : m_pluginCommanders)
1659 pluginCommander->setPluginParameterInfos(orderedParams,
1660 m_pluginInstance ? m_pluginInstance->getName().toStdString() : "",
1661 m_pluginEnabled, m_pluginPost);
1662
1665
1666 return; // ...abort further handling below here therefor
1667 }
1668
1669 std::vector<int> sendIds;
1670 for (auto const& cId : m_trafficTypesPerConnection)
1671 {
1672 if (cId.first != origId) // avoid spamming the originator of the data with a resend
1673 {
1674 // if the connection did not define relevant traffic types, add its connectionId to list of recipients
1675 if (cId.second.empty())
1676 sendIds.push_back(cId.first);
1677 // if the traffic type is active for a connection, add the connectionId to list of recipients
1678 else if (cId.second.end() != std::find(cId.second.begin(), cId.second.end(), tId))
1679 sendIds.push_back(cId.first);
1680 }
1681 }
1682 if (!sendIds.empty())
1683 sendMessageToClients(serializedMessageMemoryBlock, sendIds);
1684}
1685
1686void MemaProcessor::parameterValueChanged(int parameterIndex, float newValue)
1687{
1688 auto iter = std::find_if(getPluginParameterInfos().begin(), getPluginParameterInfos().end(), [=](const auto& info) { return info.index == parameterIndex; });
1689 if (iter != getPluginParameterInfos().end())
1690 {
1691 // Update commanders (UIs and Remotes)
1692 for (auto const& pluginCommander : m_pluginCommanders)
1693 pluginCommander->setPluginParameterValue(static_cast<std::uint16_t>(parameterIndex), iter->id.toStdString(), newValue);
1694
1695 // Update cached value
1696 if (parameterIndex < m_pluginParameterInfos.size())
1697 m_pluginParameterInfos[parameterIndex].currentValue = newValue;
1698 }
1699}
1700
1701void MemaProcessor::parameterGestureChanged(int parameterIndex, bool gestureIsStarting)
1702{
1703 ignoreUnused(parameterIndex);
1704 ignoreUnused(gestureIsStarting);
1705}
1706
1707void MemaProcessor::sendMessageToClients(const MemoryBlock& messageMemoryBlock, const std::vector<int>& sendIds)
1708{
1709 if (m_networkServer && m_networkServer->hasActiveConnections())
1710 {
1711 if (!messageMemoryBlock.isEmpty() && !m_networkServer->enqueueMessage(messageMemoryBlock, sendIds))
1712 {
1713 auto deadConnectionIds = m_networkServer->cleanupDeadConnections();
1714 if (!deadConnectionIds.empty())
1715 {
1716 for (auto const& dcId : deadConnectionIds)
1717 m_trafficTypesPerConnection.erase(dcId);
1718 }
1719 }
1720 }
1721}
1722
1724{
1725 /*dbg*/return 0.0;
1726}
1727
1729{
1730 return false;
1731}
1732
1734{
1735 return false;
1736}
1737
1738AudioProcessorEditor* MemaProcessor::createEditor()
1739{
1740 if (!m_processorEditor)
1741 m_processorEditor = std::make_unique<MemaProcessorEditor>(this);
1742
1743 m_processorEditor->onResetToUnity = [=]() { initializeCtrlValuesToUnity(m_inputChannelCount, m_outputChannelCount); };
1744
1745 return m_processorEditor.get();
1746}
1747
1749{
1750 return !!m_processorEditor;
1751}
1752
1754{
1755 /*dbg*/return 0;
1756}
1757
1759{
1760 /*dbg*/return 0;
1761}
1762
1764{
1765 /*dbg*/ignoreUnused(index);
1766}
1767
1768const String MemaProcessor::getProgramName(int index)
1769{
1770 /*dbg*/ignoreUnused(index);
1771 /*dbg*/return String();
1772}
1773
1774void MemaProcessor::changeProgramName(int index, const String& newName)
1775{
1776 /*dbg*/ignoreUnused(index);
1777 /*dbg*/ignoreUnused(newName);
1778}
1779
1780void MemaProcessor::getStateInformation(juce::MemoryBlock& destData)
1781{
1782 /*dbg*/ignoreUnused(destData);
1783}
1784
1785void MemaProcessor::setStateInformation(const void* data, int sizeInBytes)
1786{
1787 /*dbg*/ignoreUnused(data);
1788 /*dbg*/ignoreUnused(sizeInBytes);
1789}
1790
1791void MemaProcessor::audioDeviceIOCallbackWithContext(const float* const* inputChannelData, int numInputChannels,
1792 float* const* outputChannelData, int numOutputChannels, int numSamples, const AudioIODeviceCallbackContext& context)
1793{
1794 ignoreUnused(context);
1795
1796 const juce::ScopedLock sl(m_audioDeviceIOCallbackLock);
1797
1798 if (m_inputChannelCount != numInputChannels || m_outputChannelCount != numOutputChannels)
1799 {
1800 m_inputChannelCount = numInputChannels;
1801 m_outputChannelCount = numOutputChannels;
1802 postMessage(std::make_unique<ReinitIOCountMessage>(m_inputChannelCount, m_outputChannelCount).release());
1803 }
1804
1805 auto maxActiveChannels = std::max(numInputChannels, numOutputChannels);
1806
1807 if (s_maxChannelCount < maxActiveChannels)
1808 {
1809 jassertfalse;
1810 return;
1811 }
1812
1813 // copy incoming data to processing data buffer
1814 for (auto i = 0; i < numInputChannels && i < maxActiveChannels; i++)
1815 {
1816 memcpy(m_processorChannels[i], inputChannelData[i], (size_t)numSamples * sizeof(float));
1817 }
1818
1819 // 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
1820 juce::AudioBuffer<float> audioBufferToProcess(m_processorChannels, maxActiveChannels, numSamples);
1821 juce::MidiBuffer midiBufferToProcess;
1822 processBlock(audioBufferToProcess, midiBufferToProcess);
1823
1824 // copy the processed data buffer data to outgoing data
1825 auto processedChannelCount = audioBufferToProcess.getNumChannels();
1826 auto processedSampleCount = audioBufferToProcess.getNumSamples();
1827 auto processedData = audioBufferToProcess.getArrayOfReadPointers();
1828 jassert(processedSampleCount == numSamples);
1829 for (auto i = 0; i < numOutputChannels && i < processedChannelCount; i++)
1830 {
1831 memcpy(outputChannelData[i], processedData[i], (size_t)processedSampleCount * sizeof(float));
1832 }
1833
1834}
1835
1837{
1838 if (device)
1839 {
1840 auto activeInputs = device->getActiveInputChannels();
1841 auto inputChannelCnt = std::uint16_t(activeInputs.getHighestBit() + 1); // from JUCE documentation
1842 auto activeOutputs = device->getActiveOutputChannels();
1843 auto outputChannelCnt = std::uint16_t(activeOutputs.getHighestBit() + 1); // from JUCE documentation
1844 auto sampleRate = device->getCurrentSampleRate();
1845 auto bufferSize = device->getCurrentBufferSizeSamples();
1846 //auto bitDepth = device->getCurrentBitDepth();
1847
1848 setPlayConfigDetails(inputChannelCnt, outputChannelCnt, sampleRate, bufferSize); // redundant to what happens in prepareToPlay to some extent...harmful?
1849 setChannelCounts(inputChannelCnt, outputChannelCnt);
1850 prepareToPlay(sampleRate, bufferSize);
1851 }
1852}
1853
1858
1859void MemaProcessor::changeListenerCallback(ChangeBroadcaster* source)
1860{
1861 if (source == m_deviceManager.get())
1863}
1864
1865void MemaProcessor::initializeCtrlValues(int inputCount, int outputCount)
1866{
1867 std::map<std::uint16_t, bool> inputMuteStates;
1868 std::map<std::uint16_t, bool> outputMuteStates;
1869 std::map<std::uint16_t, std::map<std::uint16_t, bool>> matrixCrosspointStates;
1870 std::map<std::uint16_t, std::map<std::uint16_t, float>> matrixCrosspointValues;
1871 {
1872 // copy the processing relevant variables to not block audio thread during all the xml handling
1873 const ScopedLock sl(m_audioDeviceIOCallbackLock);
1874 inputMuteStates = m_inputMuteStates;
1875 outputMuteStates = m_outputMuteStates;
1876 matrixCrosspointValues = m_matrixCrosspointValues;
1877 matrixCrosspointStates = m_matrixCrosspointStates;
1878 }
1879
1880 auto inputChannelCount = std::uint16_t((inputCount > s_minInputsCount) ? inputCount : s_minInputsCount);
1881 for (std::uint16_t channel = 1; channel <= inputChannelCount; channel++)
1882 for (auto& inputCommander : m_inputCommanders)
1883 inputCommander->setInputMute(channel, inputMuteStates[channel]);
1884
1885 auto outputChannelCount = std::uint16_t((outputCount > s_minOutputsCount) ? outputCount : s_minOutputsCount);
1886 for (std::uint16_t channel = 1; channel <= outputChannelCount; channel++)
1887 for (auto& outputCommander : m_outputCommanders)
1888 outputCommander->setOutputMute(channel, outputMuteStates[channel]);
1889
1890 for (std::uint16_t in = 1; in <= inputChannelCount; in++)
1891 {
1892 for (std::uint16_t out = 1; out <= outputChannelCount; out++)
1893 {
1894 for (auto& crosspointCommander : m_crosspointCommanders)
1895 {
1896 crosspointCommander->setCrosspointEnabledValue(in, out, matrixCrosspointStates[in][out]);
1897 crosspointCommander->setCrosspointFactorValue(in, out, matrixCrosspointValues[in][out]);
1898 }
1899 }
1900 }
1901
1902 juce::Array<juce::AudioProcessorParameter*> pluginParameters;
1903 juce::String pluginName;
1904 if (m_pluginInstance)
1905 {
1906 // copy the processing relevant data to not block audio thread during all the xml handling
1907 const ScopedLock sl(m_pluginProcessingLock);
1908 pluginName = m_pluginInstance->getName();
1909 pluginParameters = m_pluginInstance->getParameters();
1910 }
1911 auto pluginParameterInfos = Mema::PluginParameterInfo::parametersToInfos(pluginParameters);
1912 for (auto& pluginCommander : m_pluginCommanders)
1913 pluginCommander->setPluginParameterInfos(pluginParameterInfos, pluginName.toStdString(), m_pluginEnabled, m_pluginPost);
1914}
1915
1916void MemaProcessor::initializeCtrlValuesToUnity(int inputCount, int outputCount)
1917{
1918 {
1919 const ScopedLock sl(m_audioDeviceIOCallbackLock);
1920 m_inputMuteStates.clear();
1921 m_outputMuteStates.clear();
1922 m_matrixCrosspointStates.clear();
1923 m_matrixCrosspointValues.clear();
1924 }
1925
1926 auto inputChannelCount = (inputCount > s_minInputsCount) ? inputCount : s_minInputsCount;
1927 for (std::uint16_t channel = 1; channel <= inputChannelCount; channel++)
1928 setInputMuteState(channel, false);
1929
1930 auto outputChannelCount = (outputCount > s_minOutputsCount) ? outputCount : s_minOutputsCount;
1931 for (std::uint16_t channel = 1; channel <= outputChannelCount; channel++)
1932 setOutputMuteState(channel, false);
1933
1934 for (std::uint16_t in = 1; in <= inputChannelCount; in++)
1935 {
1936 for (std::uint16_t out = 1; out <= outputChannelCount; out++)
1937 {
1938 setMatrixCrosspointEnabledValue(in, out, in == out);
1939 setMatrixCrosspointFactorValue(in, out, 1.0f);
1940 }
1941 }
1942}
1943
1945{
1946 initializeCtrlValuesToUnity(m_inputChannelCount, m_outputChannelCount);
1947}
1948
1949void MemaProcessor::setTrafficTypesForConnectionId(const std::vector<SerializableMessage::SerializableMessageType>& trafficTypes, int connectionId)
1950{
1951 DBG(juce::String(__FUNCTION__) << " " << connectionId << " chose " << trafficTypes.size() << " types");
1952 for (auto const& tt : trafficTypes)
1953 {
1954 if (m_trafficTypesPerConnection[connectionId].end() == std::find(m_trafficTypesPerConnection[connectionId].begin(), m_trafficTypesPerConnection[connectionId].end(), tt))
1955 m_trafficTypesPerConnection[connectionId].push_back(tt);
1956 }
1957}
1958
1959
1960} // 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.
@ PARAMORDER
Comma-separated list of parameter indices defining the display order.
@ 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 setPluginProcessingState(bool enabled, bool post, 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 setPluginParameterInfos(const std::vector< PluginParameterInfo > &parameterInfos, const std::string &name, bool enabled, bool post, int userId=-1) override
void setIOCount(std::uint16_t inputCount, std::uint16_t outputCount) 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 setPluginParameterDisplayOrder(const std::vector< int > &order)
Sets the display order in which plugin parameters are presented to Mema.Re clients.
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.
std::function< void(bool enabled, bool post)> onPluginProcessingStateChanged
Fired when the plugin enabled or pre/post state changes externally (e.g. from a Mema....
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.
const std::vector< int > & getPluginParameterDisplayOrder() const
Returns the current display order vector, or an empty vector if none has been set.
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.
Carries the plugin enabled and pre/post insertion state between Mema and Mema.Re.
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.
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.