From 6743921cea1d910511a16dfa2ad17308d90df0a5 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 1 Nov 2017 22:35:49 +0100 Subject: [PATCH] Re-add patchbay mode to backend --- source/backend/CarlaEngine.hpp | 2 +- source/backend/engine/CarlaEngine.cpp | 181 ++- source/backend/engine/CarlaEngineData.cpp | 2 +- source/backend/engine/CarlaEngineGraph.cpp | 1238 +++++++++++++++-- source/backend/engine/CarlaEngineGraph.hpp | 48 + source/backend/engine/CarlaEngineInternal.cpp | 8 + source/backend/engine/CarlaEngineInternal.hpp | 22 +- source/backend/engine/CarlaEngineJack.cpp | 81 +- source/backend/engine/CarlaEngineNative.cpp | 256 +++- source/backend/engine/CarlaEnginePorts.cpp | 13 + source/backend/engine/CarlaEngineRtAudio.cpp | 37 +- .../water/containers/juce_NamedValueSet.cpp | 42 - .../modules/water/containers/juce_Variant.cpp | 425 ------ .../modules/water/containers/juce_Variant.h | 168 --- .../water/processors/juce_AudioProcessor.h | 6 +- .../processors/juce_AudioProcessorGraph.cpp | 6 - .../processors/juce_AudioProcessorGraph.h | 2 - source/modules/water/water.cpp | 6 +- source/utils/CarlaBackendUtils.hpp | 2 + source/utils/CarlaEngineUtils.hpp | 91 ++ 20 files changed, 1838 insertions(+), 798 deletions(-) diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index 4a1256db8..c1f10679c 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -1150,7 +1150,7 @@ protected: * Do not free returned data. */ virtual const char* const* getPatchbayConnections(const bool external) const; - virtual void restorePatchbayConnection(const bool external, const char* const sourcePort, const char* const targetPort); + virtual void restorePatchbayConnection(const bool external, const char* const sourcePort, const char* const targetPort, const bool sendCallback); #endif // ------------------------------------------------------------------- diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 2d34bc177..20389253e 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -604,6 +604,19 @@ bool CarlaEngine::addPlugin(const BinaryType btype, const PluginType ptype, canRun = false; } } + else if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + /**/ if (plugin->getMidiInCount() > 1 || plugin->getMidiOutCount() > 1) + { + setLastError("Carla's patchbay mode cannot work with plugins that have multiple MIDI ports, sorry!"); + canRun = false; + } + else if (plugin->getCVInCount() > 0 || plugin->getCVInCount() > 0) + { + setLastError("CV ports in patchbay mode is still TODO"); + canRun = false; + } + } if (! canRun) { @@ -630,6 +643,9 @@ bool CarlaEngine::addPlugin(const BinaryType btype, const PluginType ptype, const ScopedThreadStopper sts(this); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.replacePlugin(oldPlugin, plugin); + const bool wasActive = oldPlugin->getInternalParameterValue(PARAMETER_ACTIVE) >= 0.5f; const float oldDryWet = oldPlugin->getInternalParameterValue(PARAMETER_DRYWET); const float oldVolume = oldPlugin->getInternalParameterValue(PARAMETER_VOLUME); @@ -655,6 +671,11 @@ bool CarlaEngine::addPlugin(const BinaryType btype, const PluginType ptype, ++pData->curPluginCount; callback(ENGINE_CALLBACK_PLUGIN_ADDED, id, 0, 0, 0.0f, plugin->getName()); + +#ifndef BUILD_BRIDGE + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.addPlugin(plugin); +#endif } return true; @@ -684,6 +705,9 @@ bool CarlaEngine::removePlugin(const uint id) const ScopedThreadStopper sts(this); #ifndef BUILD_BRIDGE + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.removePlugin(plugin); + const bool lockWait(isRunning() /*&& pData->options.processMode != ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS*/); const ScopedActionLock sal(this, kEnginePostActionRemovePlugin, id, 0, lockWait); @@ -728,12 +752,17 @@ bool CarlaEngine::removeAllPlugins() const uint curPluginCount(pData->curPluginCount); -#if defined(HAVE_LIBLO) && ! defined(BUILD_BRIDGE) +#ifndef BUILD_BRIDGE + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.removeAllPlugins(); + +# ifdef HAVE_LIBLO if (isOscControlRegistered()) { for (uint i=0; i < curPluginCount; ++i) oscSend_control_remove_plugin(curPluginCount-i-1); } +# endif #endif const bool lockWait(isRunning()); @@ -782,6 +811,9 @@ const char* CarlaEngine::renamePlugin(const uint id, const char* const newName) plugin->setName(uniqueName); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.renamePlugin(plugin, uniqueName); + delete[] uniqueName; return plugin->getName(); } @@ -867,6 +899,9 @@ bool CarlaEngine::switchPlugins(const uint idA, const uint idB) noexcept const ScopedThreadStopper sts(this); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.replacePlugin(pluginA, pluginB); + const bool lockWait(isRunning() /*&& pData->options.processMode != ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS*/); const ScopedActionLock sal(this, kEnginePostActionSwitchPlugins, idA, idB, lockWait); @@ -1610,8 +1645,11 @@ void CarlaEngine::bufferSizeChanged(const uint32_t newBufferSize) carla_debug("CarlaEngine::bufferSizeChanged(%i)", newBufferSize); #ifndef BUILD_BRIDGE - if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { pData->graph.setBufferSize(newBufferSize); + } #endif pData->time.updateAudioValues(newBufferSize, pData->sampleRate); @@ -1631,6 +1669,14 @@ void CarlaEngine::sampleRateChanged(const double newSampleRate) { carla_debug("CarlaEngine::sampleRateChanged(%g)", newSampleRate); +#ifndef BUILD_BRIDGE + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + pData->graph.setSampleRate(newSampleRate); + } +#endif + pData->time.updateAudioValues(pData->bufferSize, newSampleRate); for (uint i=0; i < pData->curPluginCount; ++i) @@ -1649,8 +1695,11 @@ void CarlaEngine::offlineModeChanged(const bool isOfflineNow) carla_debug("CarlaEngine::offlineModeChanged(%s)", bool2str(isOfflineNow)); #ifndef BUILD_BRIDGE - if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { pData->graph.setOffline(isOfflineNow); + } #endif for (uint i=0; i < pData->curPluginCount; ++i) @@ -1763,29 +1812,57 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons plugin->setCustomData(CUSTOM_DATA_TYPE_STRING, "__CarlaPingOnOff__", "true", false); } + // save internal connections + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + if (const char* const* const patchbayConns = getPatchbayConnections(false)) + { + MemoryOutputStream outPatchbay(2048); + + outPatchbay << "\n \n"; + + for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) + { + const char* const connSource(patchbayConns[i]); + const char* const connTarget(patchbayConns[i+1]); + + CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); + CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); + + outPatchbay << " \n"; + outPatchbay << " " << xmlSafeString(connSource, true) << "\n"; + outPatchbay << " " << xmlSafeString(connTarget, true) << "\n"; + outPatchbay << " \n"; + } + + outPatchbay << " \n"; + outStream << outPatchbay; + } + } + // if we're running inside some session-manager (and using JACK), let them handle the connections - bool saveConnections; + bool saveExternalConnections; /**/ if (isPlugin) - saveConnections = false; + saveExternalConnections = false; else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) - saveConnections = true; + saveExternalConnections = true; else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) - saveConnections = false; + saveExternalConnections = false; else if (std::getenv("LADISH_APP_NAME") != nullptr) - saveConnections = false; + saveExternalConnections = false; else if (std::getenv("NSM_URL") != nullptr) - saveConnections = false; + saveExternalConnections = false; else - saveConnections = true; + saveExternalConnections = true; - if (saveConnections) + if (saveExternalConnections) { - if (const char* const* const patchbayConns = getPatchbayConnections()) + if (const char* const* const patchbayConns = getPatchbayConnections(true)) { MemoryOutputStream outPatchbay(2048); - outPatchbay << "\n \n"; + outPatchbay << "\n \n"; for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) { @@ -1801,7 +1878,7 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons outPatchbay << " \n"; } - outPatchbay << " \n"; + outPatchbay << " \n"; outStream << outPatchbay; } } @@ -2132,6 +2209,11 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) plugin->setEnabled(true); callback(ENGINE_CALLBACK_PLUGIN_ADDED, pluginId, 0, 0, 0.0f, plugin->getName()); + +#ifndef BUILD_BRIDGE + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + pData->graph.addPlugin(plugin); +#endif } else { @@ -2163,30 +2245,79 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) if (pData->aboutToClose) return true; - // if we're running inside some session-manager (and using JACK), let them handle the connections - bool loadConnections; + // handle connections (internal) + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + const bool isUsingExternal(pData->graph.isUsingExternal()); + + for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) + { + const String& tagName(elem->getTagName()); + + if (! tagName.equalsIgnoreCase("patchbay")) + continue; + + CarlaString sourcePort, targetPort; + + for (XmlElement* patchElem = elem->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) + { + const String& patchTag(patchElem->getTagName()); + + sourcePort.clear(); + targetPort.clear(); + + if (! patchTag.equalsIgnoreCase("connection")) + continue; + + for (XmlElement* connElem = patchElem->getFirstChildElement(); connElem != nullptr; connElem = connElem->getNextElement()) + { + const String& tag(connElem->getTagName()); + const String text(connElem->getAllSubText().trim()); + + /**/ if (tag.equalsIgnoreCase("source")) + sourcePort = xmlSafeString(text, false).toRawUTF8(); + else if (tag.equalsIgnoreCase("target")) + targetPort = xmlSafeString(text, false).toRawUTF8(); + } + + if (sourcePort.isNotEmpty() && targetPort.isNotEmpty()) + restorePatchbayConnection(false, sourcePort, targetPort, !isUsingExternal); + } + break; + } + + callback(ENGINE_CALLBACK_IDLE, 0, 0, 0, 0.0f, nullptr); + + if (pData->aboutToClose) + return true; + } + + // if we're running inside some session-manager (and using JACK), let them handle the external connections + bool loadExternalConnections; /**/ if (isPlugin) - loadConnections = false; + loadExternalConnections = false; else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) - loadConnections = true; + loadExternalConnections = true; else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) - loadConnections = false; + loadExternalConnections = false; else if (std::getenv("LADISH_APP_NAME") != nullptr) - loadConnections = false; + loadExternalConnections = false; else if (std::getenv("NSM_URL") != nullptr) - loadConnections = false; + loadExternalConnections = false; else - loadConnections = true; + loadExternalConnections = true; // handle connections - if (loadConnections) + if (loadExternalConnections) { + const bool isUsingExternal(pData->graph.isUsingExternal()); + for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) { const String& tagName(elem->getTagName()); - if (! tagName.equalsIgnoreCase("patchbay") && ! tagName.equalsIgnoreCase("externalpatchbay")) + if (! tagName.equalsIgnoreCase("externalpatchbay")) continue; CarlaString sourcePort, targetPort; @@ -2213,7 +2344,7 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) } if (sourcePort.isNotEmpty() && targetPort.isNotEmpty()) - restorePatchbayConnection(sourcePort, targetPort); + restorePatchbayConnection(true, sourcePort, targetPort, isUsingExternal); } break; } diff --git a/source/backend/engine/CarlaEngineData.cpp b/source/backend/engine/CarlaEngineData.cpp index bdcdfe226..8e6d54d6c 100644 --- a/source/backend/engine/CarlaEngineData.cpp +++ b/source/backend/engine/CarlaEngineData.cpp @@ -187,7 +187,7 @@ EngineOptions::EngineOptions() noexcept : processMode(ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS), transportMode(ENGINE_TRANSPORT_MODE_JACK), #else - : processMode(ENGINE_PROCESS_MODE_CONTINUOUS_RACK), + : processMode(ENGINE_PROCESS_MODE_PATCHBAY), transportMode(ENGINE_TRANSPORT_MODE_INTERNAL), #endif transportExtra(nullptr), diff --git a/source/backend/engine/CarlaEngineGraph.cpp b/source/backend/engine/CarlaEngineGraph.cpp index 28534be60..6d1d2c3da 100644 --- a/source/backend/engine/CarlaEngineGraph.cpp +++ b/source/backend/engine/CarlaEngineGraph.cpp @@ -20,8 +20,16 @@ #include "CarlaPlugin.hpp" #include "CarlaMathUtils.hpp" +#include "CarlaJuceUtils.hpp" + #include "CarlaMIDI.h" +using water::jmax; +using water::AudioProcessor; +using water::MidiBuffer; +using water::String; +using water::StringArray; + CARLA_BACKEND_START_NAMESPACE // ----------------------------------------------------------------------- @@ -971,147 +979,1207 @@ void RackGraph::processHelper(CarlaEngine::ProtectedData* const data, const floa } // ----------------------------------------------------------------------- -// InternalGraph +// Patchbay Graph stuff -EngineInternalGraph::EngineInternalGraph(CarlaEngine* const engine) noexcept - : fIsReady(false), - fRack(nullptr), - kEngine(engine) {} +static const uint32_t kAudioInputPortOffset = MAX_PATCHBAY_PLUGINS*1; +static const uint32_t kAudioOutputPortOffset = MAX_PATCHBAY_PLUGINS*2; +static const uint32_t kMidiInputPortOffset = MAX_PATCHBAY_PLUGINS*3; +static const uint32_t kMidiOutputPortOffset = MAX_PATCHBAY_PLUGINS*3+1; -EngineInternalGraph::~EngineInternalGraph() noexcept +static const uint kMidiChannelIndex = static_cast(AudioProcessorGraph::midiChannelIndex); + +static inline +bool adjustPatchbayPortIdForJuce(uint& portId) { - CARLA_SAFE_ASSERT(! fIsReady); - CARLA_SAFE_ASSERT(fRack == nullptr); + CARLA_SAFE_ASSERT_RETURN(portId >= kAudioInputPortOffset, false); + CARLA_SAFE_ASSERT_RETURN(portId <= kMidiOutputPortOffset, false); + + if (portId == kMidiInputPortOffset) + { + portId = kMidiChannelIndex; + return true; + } + if (portId == kMidiOutputPortOffset) + { + portId = kMidiChannelIndex; + return true; + } + if (portId >= kAudioOutputPortOffset) + { + portId -= kAudioOutputPortOffset; + return true; + } + if (portId >= kAudioInputPortOffset) + { + portId -= kAudioInputPortOffset; + return true; + } + + return false; } -void EngineInternalGraph::create(const uint32_t inputs, const uint32_t outputs) +static inline +const String getProcessorFullPortName(AudioProcessor* const proc, const uint32_t portId) { - CARLA_SAFE_ASSERT_RETURN(fRack == nullptr,); - fRack = new RackGraph(kEngine, inputs, outputs); - fIsReady = true; + CARLA_SAFE_ASSERT_RETURN(proc != nullptr, String()); + CARLA_SAFE_ASSERT_RETURN(portId >= kAudioInputPortOffset, String()); + CARLA_SAFE_ASSERT_RETURN(portId <= kMidiOutputPortOffset, String()); + + String fullPortName(proc->getName()); + + if (portId == kMidiOutputPortOffset) + { + fullPortName += ":events-out"; + } + else if (portId == kMidiInputPortOffset) + { + fullPortName += ":events-in"; + } + else if (portId >= kAudioOutputPortOffset) + { + CARLA_SAFE_ASSERT_RETURN(proc->getTotalNumOutputChannels() > 0, String()); + fullPortName += ":" + proc->getOutputChannelName(static_cast(portId-kAudioOutputPortOffset)); + } + else if (portId >= kAudioInputPortOffset) + { + CARLA_SAFE_ASSERT_RETURN(proc->getTotalNumInputChannels() > 0, String()); + fullPortName += ":" + proc->getInputChannelName(static_cast(portId-kAudioInputPortOffset)); + } + else + { + return String(); + } + + return fullPortName; } -void EngineInternalGraph::destroy() noexcept +static inline +void addNodeToPatchbay(CarlaEngine* const engine, const uint32_t groupId, const int clientId, const AudioProcessor* const proc) { - if (! fIsReady) + CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); + CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); + + const int icon((clientId >= 0) ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE); + engine->callback(ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, groupId, icon, clientId, 0.0f, proc->getName().toRawUTF8()); + + for (int i=0, numInputs=proc->getTotalNumInputChannels(); icallback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, groupId, static_cast(kAudioInputPortOffset)+i, + PATCHBAY_PORT_TYPE_AUDIO|PATCHBAY_PORT_IS_INPUT, 0.0f, proc->getInputChannelName(i).toRawUTF8()); } - CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); - delete fRack; - fRack = nullptr; + for (int i=0, numOutputs=proc->getTotalNumOutputChannels(); icallback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, groupId, static_cast(kAudioOutputPortOffset)+i, + PATCHBAY_PORT_TYPE_AUDIO, 0.0f, proc->getOutputChannelName(i).toRawUTF8()); + } - fIsReady = false; + if (proc->acceptsMidi()) + { + engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, groupId, static_cast(kMidiInputPortOffset), + PATCHBAY_PORT_TYPE_MIDI|PATCHBAY_PORT_IS_INPUT, 0.0f, "events-in"); + } + + if (proc->producesMidi()) + { + engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, groupId, static_cast(kMidiOutputPortOffset), + PATCHBAY_PORT_TYPE_MIDI, 0.0f, "events-out"); + } } -void EngineInternalGraph::setBufferSize(const uint32_t bufferSize) +static inline +void removeNodeFromPatchbay(CarlaEngine* const engine, const uint32_t groupId, const AudioProcessor* const proc) { - ScopedValueSetter svs(fIsReady, false, true); + CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); + CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); - CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); - fRack->setBufferSize(bufferSize); + for (int i=0, numInputs=proc->getTotalNumInputChannels(); icallback(ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, groupId, static_cast(kAudioInputPortOffset)+i, + 0, 0.0f, nullptr); + } + + for (int i=0, numOutputs=proc->getTotalNumOutputChannels(); icallback(ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, groupId, static_cast(kAudioOutputPortOffset)+i, + 0, 0.0f, nullptr); + } + + if (proc->acceptsMidi()) + { + engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, groupId, static_cast(kMidiInputPortOffset), + 0, 0.0f, nullptr); + } + + if (proc->producesMidi()) + { + engine->callback(ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, groupId, static_cast(kMidiOutputPortOffset), + 0, 0.0f, nullptr); + } + + engine->callback(ENGINE_CALLBACK_PATCHBAY_CLIENT_REMOVED, groupId, 0, 0, 0.0f, nullptr); } -void EngineInternalGraph::setOffline(const bool offline) +// ----------------------------------------------------------------------- + +class CarlaPluginInstance : public AudioProcessor { - ScopedValueSetter svs(fIsReady, false, true); +public: + CarlaPluginInstance(CarlaEngine* const engine, CarlaPlugin* const plugin) + : kEngine(engine), + fPlugin(plugin) + { + setPlayConfigDetails(static_cast(fPlugin->getAudioInCount()), + static_cast(fPlugin->getAudioOutCount()), + getSampleRate(), getBlockSize()); + } - CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); - fRack->setOffline(offline); -} + ~CarlaPluginInstance() override + { + } -bool EngineInternalGraph::isReady() const noexcept + void invalidatePlugin() noexcept + { + fPlugin = nullptr; + } + + // ------------------------------------------------------------------- + + const String getName() const override + { + return fPlugin->getName(); + } + + void processBlock(AudioSampleBuffer& audio, MidiBuffer& midi) override + { + if (fPlugin == nullptr || ! fPlugin->isEnabled()) + { + audio.clear(); + midi.clear(); + return; + } + + if (! fPlugin->tryLock(kEngine->isOffline())) + { + audio.clear(); + midi.clear(); + return; + } + + fPlugin->initBuffers(); + + if (CarlaEngineEventPort* const port = fPlugin->getDefaultEventInPort()) + { + EngineEvent* const engineEvents(port->fBuffer); + CARLA_SAFE_ASSERT_RETURN(engineEvents != nullptr,); + + carla_zeroStructs(engineEvents, kMaxEngineEventInternalCount); + fillEngineEventsFromWaterMidiBuffer(engineEvents, midi); + } + + midi.clear(); + + // TODO - CV support + + const int numSamples(audio.getNumSamples()); + + if (const int numChan = audio.getNumChannels()) + { + if (fPlugin->getAudioInCount() == 0) + audio.clear(); + + float* audioBuffers[numChan]; + + for (int i=0; i range; + + for (int i=static_cast(jmin(fPlugin->getAudioInCount(), 2U)); --i>=0;) + { + range = FloatVectorOperations::findMinAndMax(audioBuffers[i], numSamples); + inPeaks[i] = carla_maxLimited(std::abs(range.getStart()), std::abs(range.getEnd()), 1.0f); + } +#endif + + fPlugin->process(const_cast(audioBuffers), audioBuffers, nullptr, nullptr, static_cast(numSamples)); + +#if 0 + for (int i=static_cast(jmin(fPlugin->getAudioOutCount(), 2U)); --i>=0;) + { + range = FloatVectorOperations::findMinAndMax(audioBuffers[i], numSamples); + outPeaks[i] = carla_maxLimited(std::abs(range.getStart()), std::abs(range.getEnd()), 1.0f); + } + + kEngine->setPluginPeaks(fPlugin->getId(), inPeaks, outPeaks); +#endif + } + else + { + fPlugin->process(nullptr, nullptr, nullptr, nullptr, static_cast(numSamples)); + } + + midi.clear(); + + if (CarlaEngineEventPort* const port = fPlugin->getDefaultEventOutPort()) + { + /*const*/ EngineEvent* const engineEvents(port->fBuffer); + CARLA_SAFE_ASSERT_RETURN(engineEvents != nullptr,); + + fillWaterMidiBufferFromEngineEvents(midi, engineEvents); + carla_zeroStructs(engineEvents, kMaxEngineEventInternalCount); + } + + fPlugin->unlock(); + } + + const String getInputChannelName(int i) const override + { + CARLA_SAFE_ASSERT_RETURN(i >= 0, String()); + CarlaEngineClient* const client(fPlugin->getEngineClient()); + return client->getAudioPortName(true, static_cast(i)); + } + + const String getOutputChannelName(int i) const override + { + CARLA_SAFE_ASSERT_RETURN(i >= 0, String()); + CarlaEngineClient* const client(fPlugin->getEngineClient()); + return client->getAudioPortName(false, static_cast(i)); + } + + void prepareToPlay(double, int) override {} + void releaseResources() override {} + + bool acceptsMidi() const override { return fPlugin->getDefaultEventInPort() != nullptr; } + bool producesMidi() const override { return fPlugin->getDefaultEventOutPort() != nullptr; } + +private: + CarlaEngine* const kEngine; + CarlaPlugin* fPlugin; + + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginInstance) +}; + +// ----------------------------------------------------------------------- +// Patchbay Graph + +class NamedAudioGraphIOProcessor : public AudioProcessorGraph::AudioGraphIOProcessor { - return fIsReady; +public: + NamedAudioGraphIOProcessor(const IODeviceType iotype) + : AudioProcessorGraph::AudioGraphIOProcessor(iotype), + inputNames(), + outputNames() {} + + const String getInputChannelName (int index) const override + { + if (index < inputNames.size()) + return inputNames[index]; + return String("Playback ") + String(index+1); + } + + const String getOutputChannelName (int index) const override + { + if (index < outputNames.size()) + return outputNames[index]; + return String("Capture ") + String(index+1); + } + + void setNames(const bool setInputNames, const StringArray& names) + { + if (setInputNames) + inputNames = names; + else + outputNames = names; + } + +private: + StringArray inputNames; + StringArray outputNames; +}; + +PatchbayGraph::PatchbayGraph(CarlaEngine* const engine, const uint32_t ins, const uint32_t outs) + : connections(), + graph(), + audioBuffer(), + midiBuffer(), + inputs(carla_fixedValue(0U, 32U, ins)), + outputs(carla_fixedValue(0U, 32U, outs)), + retCon(), + usingExternal(false), + extGraph(engine), + kEngine(engine) +{ + const int bufferSize(static_cast(engine->getBufferSize())); + const double sampleRate(engine->getSampleRate()); + + graph.setPlayConfigDetails(static_cast(inputs), static_cast(outputs), sampleRate, bufferSize); + graph.prepareToPlay(sampleRate, bufferSize); + + audioBuffer.setSize(static_cast(jmax(inputs, outputs)), bufferSize); + + midiBuffer.ensureSize(kMaxEngineEventInternalCount*2); + midiBuffer.clear(); + + StringArray channelNames; + + switch (inputs) + { + case 2: + channelNames.add("Left"); + channelNames.add("Right"); + break; + case 3: + channelNames.add("Left"); + channelNames.add("Right"); + channelNames.add("Sidechain"); + break; + } + + { + NamedAudioGraphIOProcessor* const proc( + new NamedAudioGraphIOProcessor(NamedAudioGraphIOProcessor::audioInputNode)); + proc->setNames(false, channelNames); + + AudioProcessorGraph::Node* const node(graph.addNode(proc)); + node->properties.set("isPlugin", false); + node->properties.set("isOutput", false); + node->properties.set("isAudio", true); + node->properties.set("isCV", false); + node->properties.set("isMIDI", false); + node->properties.set("isOSC", false); + } + + { + NamedAudioGraphIOProcessor* const proc( + new NamedAudioGraphIOProcessor(NamedAudioGraphIOProcessor::audioOutputNode)); + proc->setNames(true, channelNames); + + AudioProcessorGraph::Node* const node(graph.addNode(proc)); + node->properties.set("isPlugin", false); + node->properties.set("isOutput", false); + node->properties.set("isAudio", true); + node->properties.set("isCV", false); + node->properties.set("isMIDI", false); + node->properties.set("isOSC", false); + } + + { + NamedAudioGraphIOProcessor* const proc( + new NamedAudioGraphIOProcessor(NamedAudioGraphIOProcessor::midiInputNode)); + AudioProcessorGraph::Node* const node(graph.addNode(proc)); + node->properties.set("isPlugin", false); + node->properties.set("isOutput", false); + node->properties.set("isAudio", false); + node->properties.set("isCV", false); + node->properties.set("isMIDI", true); + node->properties.set("isOSC", false); + } + + { + NamedAudioGraphIOProcessor* const proc( + new NamedAudioGraphIOProcessor(NamedAudioGraphIOProcessor::midiOutputNode)); + AudioProcessorGraph::Node* const node(graph.addNode(proc)); + node->properties.set("isPlugin", false); + node->properties.set("isOutput", true); + node->properties.set("isAudio", false); + node->properties.set("isCV", false); + node->properties.set("isMIDI", true); + node->properties.set("isOSC", false); + } } -RackGraph* EngineInternalGraph::getGraph() const noexcept +PatchbayGraph::~PatchbayGraph() { - return fRack; + connections.clear(); + extGraph.clear(); + + graph.releaseResources(); + graph.clear(); + audioBuffer.clear(); } -void EngineInternalGraph::process(CarlaEngine::ProtectedData* const data, const float* const* const inBuf, float* const* const outBuf, const uint32_t frames) +void PatchbayGraph::setBufferSize(const uint32_t bufferSize) { - CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); - fRack->processHelper(data, inBuf, outBuf, frames); + const int bufferSizei(static_cast(bufferSize)); + + graph.releaseResources(); + graph.prepareToPlay(kEngine->getSampleRate(), bufferSizei); + audioBuffer.setSize(audioBuffer.getNumChannels(), bufferSizei); } -void EngineInternalGraph::processRack(CarlaEngine::ProtectedData* const data, const float* inBuf[2], float* outBuf[2], const uint32_t frames) +void PatchbayGraph::setSampleRate(const double sampleRate) { - CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); - fRack->process(data, inBuf, outBuf, frames); + graph.releaseResources(); + graph.prepareToPlay(sampleRate, static_cast(kEngine->getBufferSize())); } -// ----------------------------------------------------------------------- -// CarlaEngine Patchbay stuff +void PatchbayGraph::setOffline(const bool offline) +{ + graph.setNonRealtime(offline); +} -bool CarlaEngine::patchbayConnect(const uint groupA, const uint portA, const uint groupB, const uint portB) +void PatchbayGraph::addPlugin(CarlaPlugin* const plugin) { - CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK, false); - CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); - carla_debug("CarlaEngine::patchbayConnect(%u, %u, %u, %u)", groupA, portA, groupB, portB); + CARLA_SAFE_ASSERT_RETURN(plugin != nullptr,); + carla_debug("PatchbayGraph::addPlugin(%p)", plugin); - RackGraph* const graph = pData->graph.getGraph(); - CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + CarlaPluginInstance* const instance(new CarlaPluginInstance(kEngine, plugin)); + AudioProcessorGraph::Node* const node(graph.addNode(instance)); + CARLA_SAFE_ASSERT_RETURN(node != nullptr,); - return graph->connect(groupA, portA, groupB, portB); + plugin->setPatchbayNodeId(node->nodeId); + + node->properties.set("isPlugin", true); + node->properties.set("pluginId", static_cast(plugin->getId())); + + if (! usingExternal) + addNodeToPatchbay(plugin->getEngine(), node->nodeId, static_cast(plugin->getId()), instance); } -bool CarlaEngine::patchbayDisconnect(const uint connectionId) +void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) { - CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK, false); - CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); - carla_debug("CarlaEngine::patchbayDisconnect(%u)", connectionId); + CARLA_SAFE_ASSERT_RETURN(oldPlugin != nullptr,); + CARLA_SAFE_ASSERT_RETURN(newPlugin != nullptr,); + CARLA_SAFE_ASSERT_RETURN(oldPlugin != newPlugin,); + CARLA_SAFE_ASSERT_RETURN(oldPlugin->getId() == newPlugin->getId(),); - RackGraph* const graph = pData->graph.getGraph(); - CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + AudioProcessorGraph::Node* const oldNode(graph.getNodeForId(oldPlugin->getPatchbayNodeId())); + CARLA_SAFE_ASSERT_RETURN(oldNode != nullptr,); + + if (! usingExternal) + { + disconnectInternalGroup(oldNode->nodeId); + removeNodeFromPatchbay(kEngine, oldNode->nodeId, oldNode->getProcessor()); + } + + ((CarlaPluginInstance*)oldNode->getProcessor())->invalidatePlugin(); + + graph.removeNode(oldNode->nodeId); - return graph->disconnect(connectionId); + CarlaPluginInstance* const instance(new CarlaPluginInstance(kEngine, newPlugin)); + AudioProcessorGraph::Node* const node(graph.addNode(instance)); + CARLA_SAFE_ASSERT_RETURN(node != nullptr,); + + newPlugin->setPatchbayNodeId(node->nodeId); + + node->properties.set("isPlugin", true); + node->properties.set("pluginId", static_cast(newPlugin->getId())); + + if (! usingExternal) + addNodeToPatchbay(newPlugin->getEngine(), node->nodeId, static_cast(newPlugin->getId()), instance); } -bool CarlaEngine::patchbayRefresh() +void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) { - // This is implemented in engine subclasses - setLastError("Unsupported operation"); - return false; -} + CARLA_SAFE_ASSERT_RETURN(plugin != nullptr,); + carla_debug("PatchbayGraph::renamePlugin(%p)", plugin, newName); -// ----------------------------------------------------------------------- + AudioProcessorGraph::Node* const node(graph.getNodeForId(plugin->getPatchbayNodeId())); + CARLA_SAFE_ASSERT_RETURN(node != nullptr,); + + if (! usingExternal) + kEngine->callback(ENGINE_CALLBACK_PATCHBAY_CLIENT_RENAMED, node->nodeId, 0, 0, 0.0f, newName); +} -const char* const* CarlaEngine::getPatchbayConnections() const +void PatchbayGraph::removePlugin(CarlaPlugin* const plugin) { - CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK, nullptr); - CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), nullptr); - carla_debug("CarlaEngine::getPatchbayConnections(%s)", bool2str(external)); + CARLA_SAFE_ASSERT_RETURN(plugin != nullptr,); + carla_debug("PatchbayGraph::removePlugin(%p)", plugin); + + AudioProcessorGraph::Node* const node(graph.getNodeForId(plugin->getPatchbayNodeId())); + CARLA_SAFE_ASSERT_RETURN(node != nullptr,); + + if (! usingExternal) + { + disconnectInternalGroup(node->nodeId); + removeNodeFromPatchbay(kEngine, node->nodeId, node->getProcessor()); + } + + ((CarlaPluginInstance*)node->getProcessor())->invalidatePlugin(); - RackGraph* const graph = pData->graph.getGraph(); - CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr); + // Fix plugin Ids properties + for (uint i=plugin->getId()+1, count=kEngine->getCurrentPluginCount(); igetPlugin(i)); + CARLA_SAFE_ASSERT_BREAK(plugin2 != nullptr); + + if (AudioProcessorGraph::Node* const node2 = graph.getNodeForId(plugin2->getPatchbayNodeId())) + { + CARLA_SAFE_ASSERT_CONTINUE(node2->properties.getWithDefault("pluginId", -1) != water::var(-1)); + node2->properties.set("pluginId", static_cast(i-1)); + } + } - return graph->getConnections(); + CARLA_SAFE_ASSERT_RETURN(graph.removeNode(node->nodeId),); } -void CarlaEngine::restorePatchbayConnection(const char* const sourcePort, const char* const targetPort) +void PatchbayGraph::removeAllPlugins() { - CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK,); - CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(),); - CARLA_SAFE_ASSERT_RETURN(sourcePort != nullptr && sourcePort[0] != '\0',); - CARLA_SAFE_ASSERT_RETURN(targetPort != nullptr && targetPort[0] != '\0',); - carla_debug("CarlaEngine::restorePatchbayConnection(\"%s\", \"%s\")", sourcePort, targetPort); + carla_debug("PatchbayGraph::removeAllPlugins()"); - uint groupA, portA; - uint groupB, portB; + for (uint i=0, count=kEngine->getCurrentPluginCount(); igetPlugin(i)); + CARLA_SAFE_ASSERT_CONTINUE(plugin != nullptr); - RackGraph* const graph = pData->graph.getGraph(); - CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); + AudioProcessorGraph::Node* const node(graph.getNodeForId(plugin->getPatchbayNodeId())); + CARLA_SAFE_ASSERT_CONTINUE(node != nullptr); - if (! graph->getGroupAndPortIdFromFullName(sourcePort, groupA, portA)) - return; - if (! graph->getGroupAndPortIdFromFullName(targetPort, groupB, portB)) - return; + if (! usingExternal) + { + disconnectInternalGroup(node->nodeId); + removeNodeFromPatchbay(kEngine, node->nodeId, node->getProcessor()); + } + + ((CarlaPluginInstance*)node->getProcessor())->invalidatePlugin(); - graph->connect(groupA, portA, groupB, portB); + graph.removeNode(node->nodeId); + } +} + +bool PatchbayGraph::connect(const bool external, const uint groupA, const uint portA, const uint groupB, const uint portB, const bool sendCallback) +{ + if (external) + return extGraph.connect(groupA, portA, groupB, portB, sendCallback); + + uint adjustedPortA = portA; + uint adjustedPortB = portB; + + if (! adjustPatchbayPortIdForJuce(adjustedPortA)) + return false; + if (! adjustPatchbayPortIdForJuce(adjustedPortB)) + return false; + + if (! graph.addConnection(groupA, static_cast(adjustedPortA), groupB, static_cast(adjustedPortB))) + { + kEngine->setLastError("Failed from juce"); + return false; + } + + ConnectionToId connectionToId; + connectionToId.setData(++connections.lastId, groupA, portA, groupB, portB); + + char strBuf[STR_MAX+1]; + strBuf[STR_MAX] = '\0'; + std::snprintf(strBuf, STR_MAX, "%u:%u:%u:%u", groupA, portA, groupB, portB); + + if (sendCallback) + kEngine->callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf); + + connections.list.append(connectionToId); + return true; +} + +bool PatchbayGraph::disconnect(const uint connectionId) +{ + if (usingExternal) + return extGraph.disconnect(connectionId); + + for (LinkedList::Itenerator it=connections.list.begin2(); it.valid(); it.next()) + { + static const ConnectionToId fallback = { 0, 0, 0, 0, 0 }; + + const ConnectionToId& connectionToId(it.getValue(fallback)); + CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id > 0); + + if (connectionToId.id != connectionId) + continue; + + uint adjustedPortA = connectionToId.portA; + uint adjustedPortB = connectionToId.portB; + + if (! adjustPatchbayPortIdForJuce(adjustedPortA)) + return false; + if (! adjustPatchbayPortIdForJuce(adjustedPortB)) + return false; + + if (! graph.removeConnection(connectionToId.groupA, static_cast(adjustedPortA), + connectionToId.groupB, static_cast(adjustedPortB))) + return false; + + kEngine->callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED, connectionToId.id, 0, 0, 0.0f, nullptr); + + connections.list.remove(it); + return true; + } + + kEngine->setLastError("Failed to find connection"); + return false; +} + +void PatchbayGraph::disconnectInternalGroup(const uint groupId) noexcept +{ + CARLA_SAFE_ASSERT(! usingExternal); + + for (LinkedList::Itenerator it=connections.list.begin2(); it.valid(); it.next()) + { + static const ConnectionToId fallback = { 0, 0, 0, 0, 0 }; + + const ConnectionToId& connectionToId(it.getValue(fallback)); + CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id > 0); + + if (connectionToId.groupA != groupId && connectionToId.groupB != groupId) + continue; + + /* + uint adjustedPortA = connectionToId.portA; + uint adjustedPortB = connectionToId.portB; + + if (! adjustPatchbayPortIdForJuce(adjustedPortA)) + return false; + if (! adjustPatchbayPortIdForJuce(adjustedPortB)) + return false; + + graph.removeConnection(connectionToId.groupA, static_cast(adjustedPortA), + connectionToId.groupB, static_cast(adjustedPortB)); + */ + + if (! usingExternal) + kEngine->callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED, connectionToId.id, 0, 0, 0.0f, nullptr); + + connections.list.remove(it); + } +} + +void PatchbayGraph::refresh(const char* const deviceName) +{ + if (usingExternal) + return extGraph.refresh(deviceName); + + CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,); + + connections.clear(); + graph.removeIllegalConnections(); + + for (int i=0, count=graph.getNumNodes(); igetProcessor()); + CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); + + int clientId = -1; + + // plugin node + if (node->properties.getWithDefault("isPlugin", false) == water::var(true)) + clientId = node->properties.getWithDefault("pluginId", -1); + + addNodeToPatchbay(kEngine, node->nodeId, clientId, proc); + } + + char strBuf[STR_MAX+1]; + strBuf[STR_MAX] = '\0'; + + for (int i=0, count=graph.getNumConnections(); isourceChannelIndex >= 0); + CARLA_SAFE_ASSERT_CONTINUE(conn->destChannelIndex >= 0); + + const uint groupA = conn->sourceNodeId; + const uint groupB = conn->destNodeId; + + uint portA = static_cast(conn->sourceChannelIndex); + uint portB = static_cast(conn->destChannelIndex); + + if (portA == kMidiChannelIndex) + portA = kMidiOutputPortOffset; + else + portA += kAudioOutputPortOffset; + + if (portB == kMidiChannelIndex) + portB = kMidiInputPortOffset; + else + portB += kAudioInputPortOffset; + + ConnectionToId connectionToId; + connectionToId.setData(++connections.lastId, groupA, portA, groupB, portB); + + std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", groupA, portA, groupB, portB); + + kEngine->callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf); + + connections.list.append(connectionToId); + } +} + +const char* const* PatchbayGraph::getConnections(const bool external) const +{ + if (external) + return extGraph.getConnections(); + + if (connections.list.count() == 0) + return nullptr; + + CarlaStringList connList; + + for (LinkedList::Itenerator it=connections.list.begin2(); it.valid(); it.next()) + { + static const ConnectionToId fallback = { 0, 0, 0, 0, 0 }; + + const ConnectionToId& connectionToId(it.getValue(fallback)); + CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id > 0); + + AudioProcessorGraph::Node* const nodeA(graph.getNodeForId(connectionToId.groupA)); + CARLA_SAFE_ASSERT_CONTINUE(nodeA != nullptr); + + AudioProcessorGraph::Node* const nodeB(graph.getNodeForId(connectionToId.groupB)); + CARLA_SAFE_ASSERT_CONTINUE(nodeB != nullptr); + + AudioProcessor* const procA(nodeA->getProcessor()); + CARLA_SAFE_ASSERT_CONTINUE(procA != nullptr); + + AudioProcessor* const procB(nodeB->getProcessor()); + CARLA_SAFE_ASSERT_CONTINUE(procB != nullptr); + + String fullPortNameA(getProcessorFullPortName(procA, connectionToId.portA)); + CARLA_SAFE_ASSERT_CONTINUE(fullPortNameA.isNotEmpty()); + + String fullPortNameB(getProcessorFullPortName(procB, connectionToId.portB)); + CARLA_SAFE_ASSERT_CONTINUE(fullPortNameB.isNotEmpty()); + + connList.append(fullPortNameA.toRawUTF8()); + connList.append(fullPortNameB.toRawUTF8()); + } + + if (connList.count() == 0) + return nullptr; + + retCon = connList.toCharStringListPtr(); + + return retCon; +} + +bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const +{ + if (external) + return extGraph.getGroupAndPortIdFromFullName(fullPortName, groupId, portId); + + String groupName(String(fullPortName).upToFirstOccurrenceOf(":", false, false)); + String portName(String(fullPortName).fromFirstOccurrenceOf(":", false, false)); + + for (int i=0, count=graph.getNumNodes(); igetProcessor()); + CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); + + if (proc->getName() != groupName) + continue; + + groupId = node->nodeId; + + if (portName == "events-in") + { + portId = kMidiInputPortOffset; + return true; + } + + if (portName == "events-out") + { + portId = kMidiOutputPortOffset; + return true; + } + + for (int j=0, numInputs=proc->getTotalNumInputChannels(); jgetInputChannelName(j) != portName) + continue; + + portId = kAudioInputPortOffset+static_cast(j); + return true; + } + + for (int j=0, numOutputs=proc->getTotalNumOutputChannels(); jgetOutputChannelName(j) != portName) + continue; + + portId = kAudioOutputPortOffset+static_cast(j); + return true; + } + } + + return false; +} + +void PatchbayGraph::process(CarlaEngine::ProtectedData* const data, const float* const* const inBuf, float* const* const outBuf, const int frames) +{ + CARLA_SAFE_ASSERT_RETURN(data != nullptr,); + CARLA_SAFE_ASSERT_RETURN(data->events.in != nullptr,); + CARLA_SAFE_ASSERT_RETURN(data->events.out != nullptr,); + CARLA_SAFE_ASSERT_RETURN(frames > 0,); + + // put events in juce buffer + { + midiBuffer.clear(); + fillWaterMidiBufferFromEngineEvents(midiBuffer, data->events.in); + } + + // put carla audio in juce buffer + { + int i=0; + + for (; i < static_cast(inputs); ++i) + carla_copyFloats(audioBuffer.getWritePointer(i), inBuf[i], frames); + + // clear remaining channels + for (const int count=audioBuffer.getNumChannels(); i(outputs); ++i) + carla_copyFloats(outBuf[i], audioBuffer.getReadPointer(i), frames); + } + + // put juce events in carla buffer + { + carla_zeroStructs(data->events.out, kMaxEngineEventInternalCount); + fillEngineEventsFromWaterMidiBuffer(data->events.out, midiBuffer); + midiBuffer.clear(); + } +} + +// ----------------------------------------------------------------------- +// InternalGraph + +EngineInternalGraph::EngineInternalGraph(CarlaEngine* const engine) noexcept + : fIsReady(false), + fRack(nullptr), + kEngine(engine) {} + +EngineInternalGraph::~EngineInternalGraph() noexcept +{ + CARLA_SAFE_ASSERT(! fIsReady); + CARLA_SAFE_ASSERT(fRack == nullptr); +} + +void EngineInternalGraph::create(const uint32_t inputs, const uint32_t outputs) +{ + fIsRack = (kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK); + + if (fIsRack) + { + CARLA_SAFE_ASSERT_RETURN(fRack == nullptr,); + fRack = new RackGraph(kEngine, inputs, outputs); + } + else + { + CARLA_SAFE_ASSERT_RETURN(fPatchbay == nullptr,); + fPatchbay = new PatchbayGraph(kEngine, inputs, outputs); + } + + fIsReady = true; +} + +void EngineInternalGraph::destroy() noexcept +{ + if (! fIsReady) + { + CARLA_SAFE_ASSERT(fRack == nullptr); + return; + } + + if (fIsRack) + { + CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); + delete fRack; + fRack = nullptr; + } + else + { + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + delete fPatchbay; + fPatchbay = nullptr; + } + + fIsReady = false; +} + +void EngineInternalGraph::setBufferSize(const uint32_t bufferSize) +{ + ScopedValueSetter svs(fIsReady, false, true); + + if (fIsRack) + { + CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); + fRack->setBufferSize(bufferSize); + } + else + { + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->setBufferSize(bufferSize); + } +} + +void EngineInternalGraph::setSampleRate(const double sampleRate) +{ + ScopedValueSetter svs(fIsReady, false, true); + + if (fIsRack) + { + CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); + } + else + { + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->setSampleRate(sampleRate); + } +} + +void EngineInternalGraph::setOffline(const bool offline) +{ + ScopedValueSetter svs(fIsReady, false, true); + + if (fIsRack) + { + CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); + fRack->setOffline(offline); + } + else + { + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->setOffline(offline); + } +} + +bool EngineInternalGraph::isReady() const noexcept +{ + return fIsReady; +} + +RackGraph* EngineInternalGraph::getRackGraph() const noexcept +{ + CARLA_SAFE_ASSERT_RETURN(fIsRack, nullptr); + return fRack; +} + +PatchbayGraph* EngineInternalGraph::getPatchbayGraph() const noexcept +{ + CARLA_SAFE_ASSERT_RETURN(! fIsRack, nullptr); + return fPatchbay; +} + +void EngineInternalGraph::process(CarlaEngine::ProtectedData* const data, const float* const* const inBuf, float* const* const outBuf, const uint32_t frames) +{ + if (fIsRack) + { + CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); + fRack->processHelper(data, inBuf, outBuf, frames); + } + else + { + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->process(data, inBuf, outBuf, static_cast(frames)); + } +} + +void EngineInternalGraph::processRack(CarlaEngine::ProtectedData* const data, const float* inBuf[2], float* outBuf[2], const uint32_t frames) +{ + CARLA_SAFE_ASSERT_RETURN(fIsRack,); + CARLA_SAFE_ASSERT_RETURN(fRack != nullptr,); + + fRack->process(data, inBuf, outBuf, frames); +} + +// ----------------------------------------------------------------------- +// used for internal patchbay mode + +void EngineInternalGraph::addPlugin(CarlaPlugin* const plugin) +{ + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->addPlugin(plugin); +} + +void EngineInternalGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) +{ + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->replacePlugin(oldPlugin, newPlugin); +} + +void EngineInternalGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) +{ + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->renamePlugin(plugin, newName); +} + +void EngineInternalGraph::removePlugin(CarlaPlugin* const plugin) +{ + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->removePlugin(plugin); +} + +void EngineInternalGraph::removeAllPlugins() +{ + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->removeAllPlugins(); +} + +bool EngineInternalGraph::isUsingExternal() const noexcept +{ + if (fIsRack) + return true; + + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr, false); + return fPatchbay->usingExternal; +} + +void EngineInternalGraph::setUsingExternal(const bool usingExternal) noexcept +{ + CARLA_SAFE_ASSERT_RETURN(! fIsRack,); + CARLA_SAFE_ASSERT_RETURN(fPatchbay != nullptr,); + fPatchbay->usingExternal = usingExternal; +} + +// ----------------------------------------------------------------------- +// CarlaEngine Patchbay stuff + +bool CarlaEngine::patchbayConnect(const uint groupA, const uint portA, const uint groupB, const uint portB) +{ + CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY, false); + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); + carla_debug("CarlaEngine::patchbayConnect(%u, %u, %u, %u)", groupA, portA, groupB, portB); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + RackGraph* const graph = pData->graph.getRackGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + return graph->connect(groupA, portA, groupB, portB); + } + else + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + return graph->connect(graph->usingExternal, groupA, portA, groupB, portB, true); + } + + return false; +} + +bool CarlaEngine::patchbayDisconnect(const uint connectionId) +{ + CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY, false); + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); + carla_debug("CarlaEngine::patchbayDisconnect(%u)", connectionId); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + RackGraph* const graph = pData->graph.getRackGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + return graph->disconnect(connectionId); + } + else + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + return graph->disconnect(connectionId); + } + + return false; +} + +bool CarlaEngine::patchbayRefresh(const bool external) +{ + // subclasses should handle this + CARLA_SAFE_ASSERT_RETURN(! external, false); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + // This is implemented in engine subclasses + setLastError("Unsupported operation"); + return false; + } + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + graph->refresh(""); + return true; + } + + setLastError("Unsupported operation"); + return false; +} + +// ----------------------------------------------------------------------- + +const char* const* CarlaEngine::getPatchbayConnections(const bool external) const +{ + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), nullptr); + carla_debug("CarlaEngine::getPatchbayConnections(%s)", bool2str(external)); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + RackGraph* const graph = pData->graph.getRackGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr); + CARLA_SAFE_ASSERT_RETURN(external, nullptr); + + return graph->getConnections(); + } + else + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr); + + return graph->getConnections(external); + } + + return nullptr; +} + +void CarlaEngine::restorePatchbayConnection(const bool external, const char* const sourcePort, const char* const targetPort, const bool sendCallback) +{ + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(),); + CARLA_SAFE_ASSERT_RETURN(sourcePort != nullptr && sourcePort[0] != '\0',); + CARLA_SAFE_ASSERT_RETURN(targetPort != nullptr && targetPort[0] != '\0',); + carla_debug("CarlaEngine::restorePatchbayConnection(%s, \"%s\", \"%s\")", bool2str(external), sourcePort, targetPort); + + uint groupA, portA; + uint groupB, portB; + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + RackGraph* const graph = pData->graph.getRackGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); + CARLA_SAFE_ASSERT_RETURN(external,); + + if (! graph->getGroupAndPortIdFromFullName(sourcePort, groupA, portA)) + return; + if (! graph->getGroupAndPortIdFromFullName(targetPort, groupB, portB)) + return; + + graph->connect(groupA, portA, groupB, portB); + } + else + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); + + if (! graph->getGroupAndPortIdFromFullName(external, sourcePort, groupA, portA)) + return; + if (! graph->getGroupAndPortIdFromFullName(external, targetPort, groupB, portB)) + return; + + graph->connect(external, groupA, portA, groupB, portB, sendCallback); + } } // ----------------------------------------------------------------------- @@ -1121,7 +2189,7 @@ bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint CARLA_SAFE_ASSERT_RETURN(connectionType != 0 || (portName != nullptr && portName[0] != '\0'), false); CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK, false); - RackGraph* const graph(pData->graph.getGraph()); + RackGraph* const graph(pData->graph.getRackGraph()); CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); const CarlaRecursiveMutexLocker cml(graph->audioBuffers.mutex); @@ -1146,7 +2214,7 @@ bool CarlaEngine::disconnectExternalGraphPort(const uint connectionType, const u CARLA_SAFE_ASSERT_RETURN(connectionType != 0 || (portName != nullptr && portName[0] != '\0'), false); CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK, false); - RackGraph* const graph(pData->graph.getGraph()); + RackGraph* const graph(pData->graph.getRackGraph()); CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); const CarlaRecursiveMutexLocker cml(graph->audioBuffers.mutex); diff --git a/source/backend/engine/CarlaEngineGraph.hpp b/source/backend/engine/CarlaEngineGraph.hpp index e068cddd1..87c9cffad 100644 --- a/source/backend/engine/CarlaEngineGraph.hpp +++ b/source/backend/engine/CarlaEngineGraph.hpp @@ -23,6 +23,12 @@ #include "CarlaPatchbayUtils.hpp" #include "CarlaStringList.hpp" +#include "water/water.h" + +using water::AudioProcessorGraph; +using water::AudioSampleBuffer; +using water::MidiBuffer; + CARLA_BACKEND_START_NAMESPACE // ----------------------------------------------------------------------- @@ -136,6 +142,48 @@ struct RackGraph { CARLA_DECLARE_NON_COPY_CLASS(RackGraph) }; +// ----------------------------------------------------------------------- +// PatchbayGraph + +struct PatchbayGraph { + PatchbayConnectionList connections; + AudioProcessorGraph graph; + AudioSampleBuffer audioBuffer; + MidiBuffer midiBuffer; + const uint32_t inputs; + const uint32_t outputs; + mutable CharStringListPtr retCon; + bool usingExternal; + + ExternalGraph extGraph; + + PatchbayGraph(CarlaEngine* const engine, const uint32_t inputs, const uint32_t outputs); + ~PatchbayGraph(); + + void setBufferSize(const uint32_t bufferSize); + void setSampleRate(const double sampleRate); + void setOffline(const bool offline); + + void addPlugin(CarlaPlugin* const plugin); + void replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin); + void renamePlugin(CarlaPlugin* const plugin, const char* const newName); + void removePlugin(CarlaPlugin* const plugin); + void removeAllPlugins(); + + bool connect(const bool external, const uint groupA, const uint portA, const uint groupB, const uint portB, const bool sendCallback); + bool disconnect(const uint connectionId); + void disconnectInternalGroup(const uint groupId) noexcept; + void refresh(const char* const deviceName); + + const char* const* getConnections(const bool external) const; + bool getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const; + + void process(CarlaEngine::ProtectedData* const data, const float* const* const inBuf, float* const* const outBuf, const int frames); + + CarlaEngine* const kEngine; + CARLA_DECLARE_NON_COPY_CLASS(PatchbayGraph) +}; + // ----------------------------------------------------------------------- CARLA_BACKEND_END_NAMESPACE diff --git a/source/backend/engine/CarlaEngineInternal.cpp b/source/backend/engine/CarlaEngineInternal.cpp index f8a198268..75f1c8075 100644 --- a/source/backend/engine/CarlaEngineInternal.cpp +++ b/source/backend/engine/CarlaEngineInternal.cpp @@ -468,6 +468,13 @@ bool CarlaEngine::ProtectedData::init(const char* const clientName) switch (options.processMode) { + case ENGINE_PROCESS_MODE_CONTINUOUS_RACK: + maxPluginNumber = MAX_RACK_PLUGINS; + options.forceStereo = true; + break; + case ENGINE_PROCESS_MODE_PATCHBAY: + maxPluginNumber = MAX_PATCHBAY_PLUGINS; + break; case ENGINE_PROCESS_MODE_BRIDGE: maxPluginNumber = 1; break; @@ -479,6 +486,7 @@ bool CarlaEngine::ProtectedData::init(const char* const clientName) switch (options.processMode) { case ENGINE_PROCESS_MODE_CONTINUOUS_RACK: + case ENGINE_PROCESS_MODE_PATCHBAY: case ENGINE_PROCESS_MODE_BRIDGE: events.in = new EngineEvent[kMaxEngineEventInternalCount]; events.out = new EngineEvent[kMaxEngineEventInternalCount]; diff --git a/source/backend/engine/CarlaEngineInternal.hpp b/source/backend/engine/CarlaEngineInternal.hpp index 1713b60c7..46fab2081 100644 --- a/source/backend/engine/CarlaEngineInternal.hpp +++ b/source/backend/engine/CarlaEngineInternal.hpp @@ -71,19 +71,37 @@ public: void destroy() noexcept; void setBufferSize(const uint32_t bufferSize); + void setSampleRate(const double sampleRate); void setOffline(const bool offline); bool isReady() const noexcept; - RackGraph* getGraph() const noexcept; + + RackGraph* getRackGraph() const noexcept; + PatchbayGraph* getPatchbayGraph() const noexcept; void process(CarlaEngine::ProtectedData* const data, const float* const* const inBuf, float* const* const outBuf, const uint32_t frames); // special direct process with connections already handled, used in JACK and Plugin void processRack(CarlaEngine::ProtectedData* const data, const float* inBuf[2], float* outBuf[2], const uint32_t frames); + // used for internal patchbay mode + void addPlugin(CarlaPlugin* const plugin); + void replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin); + void renamePlugin(CarlaPlugin* const plugin, const char* const newName); + void removePlugin(CarlaPlugin* const plugin); + void removeAllPlugins(); + + bool isUsingExternal() const noexcept; + void setUsingExternal(const bool usingExternal) noexcept; + private: + bool fIsRack; bool fIsReady; - RackGraph* fRack; + + union { + RackGraph* fRack; + PatchbayGraph* fPatchbay; + }; CarlaEngine* const kEngine; diff --git a/source/backend/engine/CarlaEngineJack.cpp b/source/backend/engine/CarlaEngineJack.cpp index 64737908c..079ca110c 100644 --- a/source/backend/engine/CarlaEngineJack.cpp +++ b/source/backend/engine/CarlaEngineJack.cpp @@ -806,6 +806,7 @@ public: CarlaThread("CarlaEngineJackCallbacks"), #endif fClient(nullptr), + fExternalPatchbay(true), fFreewheel(false), #ifdef BUILD_BRIDGE fIsRunning(false) @@ -882,7 +883,8 @@ public: CARLA_SAFE_ASSERT_RETURN(jackbridge_is_ok(), false); carla_debug("CarlaEngineJack::init(\"%s\")", clientName); - fFreewheel = false; + fFreewheel = false; + fExternalPatchbay = true; CarlaString truncatedClientName(clientName); truncatedClientName.truncate(getMaxClientNameSize()); @@ -949,14 +951,16 @@ public: fTimebaseMaster = jackbridge_set_timebase_callback(fClient, true, carla_jack_timebase_callback, this); - initJackPatchbay(jackClientName); + if (pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY) + initJackPatchbay(jackClientName); jackbridge_set_client_registration_callback(fClient, carla_jack_client_registration_callback, this); jackbridge_set_port_registration_callback(fClient, carla_jack_port_registration_callback, this); jackbridge_set_port_connect_callback(fClient, carla_jack_port_connect_callback, this); jackbridge_set_port_rename_callback(fClient, carla_jack_port_rename_callback, this); - if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) { fRackPorts[kRackPortAudioIn1] = jackbridge_port_register(fClient, "audio-in1", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); fRackPorts[kRackPortAudioIn2] = jackbridge_port_register(fClient, "audio-in2", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); @@ -965,8 +969,16 @@ public: fRackPorts[kRackPortEventIn] = jackbridge_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); fRackPorts[kRackPortEventOut] = jackbridge_port_register(fClient, "events-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); - // FIXME - pData->graph.create(0, 0); + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + // FIXME? + pData->graph.create(0, 0); + } + else + { + pData->graph.create(2, 2); + patchbayRefresh(false); + } } if (jackbridge_activate(fClient)) @@ -976,8 +988,11 @@ public: return true; } - if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { pData->graph.destroy(); + } pData->close(); jackbridge_client_close(fClient); @@ -1016,7 +1031,8 @@ public: fPostPonedEvents.clear(); // clear rack/patchbay stuff - if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) { if (deactivated) { @@ -1108,8 +1124,11 @@ public: #ifndef BUILD_BRIDGE const char* renamePlugin(const uint id, const char* const newName) override { - if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { return CarlaEngine::renamePlugin(id, newName); + } CARLA_SAFE_ASSERT_RETURN(pData->plugins != nullptr, nullptr); CARLA_SAFE_ASSERT_RETURN(pData->curPluginCount != 0, nullptr); @@ -1257,6 +1276,9 @@ public: { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! fExternalPatchbay) + return CarlaEngine::patchbayConnect(groupA, portA, groupB, portB); + const char* const fullPortNameA = fUsedPorts.getFullPortName(groupA, portA); CARLA_SAFE_ASSERT_RETURN(fullPortNameA != nullptr && fullPortNameA[0] != '\0', false); @@ -1276,6 +1298,9 @@ public: { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! fExternalPatchbay) + return CarlaEngine::patchbayDisconnect(connectionId); + ConnectionToId connectionToId = { 0, 0, 0, 0, 0 }; { @@ -1312,10 +1337,19 @@ public: return true; } - bool patchbayRefresh() override + bool patchbayRefresh(const bool external) override { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + fExternalPatchbay = external; + pData->graph.setUsingExternal(external); + + if (! external) + return CarlaEngine::patchbayRefresh(false); + } + fUsedGroups.clear(); fUsedPorts.clear(); fUsedConnections.clear(); @@ -1403,10 +1437,13 @@ public: // ------------------------------------------------------------------- // Patchbay stuff - const char* const* getPatchbayConnections() const override + const char* const* getPatchbayConnections(const bool external) const override { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, nullptr); - carla_debug("CarlaEngineJack::getPatchbayConnections()"); + carla_debug("CarlaEngineJack::getPatchbayConnections(%s)", bool2str(external)); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external) + return CarlaEngine::getPatchbayConnections(external); CarlaStringList connList; @@ -1442,13 +1479,16 @@ public: return fRetConns; } - void restorePatchbayConnection(const char* const connSource, const char* const connTarget) override + void restorePatchbayConnection(const bool external, const char* const connSource, const char* const connTarget, const bool sendCallback) override { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr,); CARLA_SAFE_ASSERT_RETURN(connSource != nullptr && connSource[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(connTarget != nullptr && connTarget[0] != '\0',); carla_debug("CarlaEngineJack::restorePatchbayConnection(\"%s\", \"%s\")", connSource, connTarget); + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external) + return CarlaEngine::restorePatchbayConnection(external, connSource, connTarget, sendCallback); + if (const jack_port_t* const port = jackbridge_port_by_name(fClient, connSource)) { if (jackbridge_port_by_name(fClient, connTarget) == nullptr) @@ -1599,7 +1639,8 @@ protected: } } } - else if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + else if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) { CARLA_SAFE_ASSERT_RETURN(pData->events.in != nullptr,); CARLA_SAFE_ASSERT_RETURN(pData->events.out != nullptr,); @@ -1719,6 +1760,9 @@ protected: { CARLA_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0',); + // ignore this if on internal patchbay mode + if (! fExternalPatchbay) return; + // do nothing on client registration, wait for first port if (reg) return; @@ -1736,6 +1780,9 @@ protected: void handleJackPortRegistrationCallback(const jack_port_id_t port, const bool reg) { + // ignore this if on internal patchbay mode + if (! fExternalPatchbay) return; + const jack_port_t* const jackPort(jackbridge_port_by_id(fClient, port)); CARLA_SAFE_ASSERT_RETURN(jackPort != nullptr,); @@ -1788,6 +1835,9 @@ protected: void handleJackPortConnectCallback(const jack_port_id_t a, const jack_port_id_t b, const bool connect) { + // ignore this if on internal patchbay mode + if (! fExternalPatchbay) return; + const jack_port_t* const jackPortA(jackbridge_port_by_id(fClient, a)); CARLA_SAFE_ASSERT_RETURN(jackPortA != nullptr,); @@ -1850,6 +1900,9 @@ protected: void handleJackPortRenameCallback(const jack_port_id_t port, const char* const oldFullName, const char* const newFullName) { + // ignore this if on internal patchbay mode + if (! fExternalPatchbay) return; + CARLA_SAFE_ASSERT_RETURN(oldFullName != nullptr && oldFullName[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(newFullName != nullptr && newFullName[0] != '\0',); @@ -1937,6 +1990,7 @@ protected: private: jack_client_t* fClient; + bool fExternalPatchbay; bool fFreewheel; // ------------------------------------------------------------------- @@ -2017,6 +2071,7 @@ private: void initJackPatchbay(const char* const ourName) { + CARLA_SAFE_ASSERT_RETURN(pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY || fExternalPatchbay,); CARLA_SAFE_ASSERT_RETURN(ourName != nullptr && ourName[0] != '\0',); CarlaStringList parsedGroups; diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 781bb1ace..e56fc3d6b 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -158,7 +158,7 @@ protected: else if (std::strcmp(msg, "patchbay_refresh") == 0) { try { - ok = fEngine->patchbayRefresh(); + ok = fEngine->patchbayRefresh(false); } CARLA_SAFE_EXCEPTION("patchbayRefresh"); } else if (std::strcmp(msg, "transport_play") == 0) @@ -586,9 +586,10 @@ private: class CarlaEngineNative : public CarlaEngine { public: - CarlaEngineNative(const NativeHostDescriptor* const host, const uint32_t inChan = 2, uint32_t outChan = 0) + CarlaEngineNative(const NativeHostDescriptor* const host, const bool isPatchbay, const uint32_t inChan = 2, uint32_t outChan = 0) : CarlaEngine(), pHost(host), + kIsPatchbay(isPatchbay), fIsActive(false), fIsRunning(false), fUiServer(this), @@ -606,15 +607,29 @@ public: if (outChan == 0) outChan = inChan; - CARLA_SAFE_ASSERT(inChan == 2); - CARLA_SAFE_ASSERT(outChan == 2); - pData->options.processMode = ENGINE_PROCESS_MODE_CONTINUOUS_RACK; - pData->options.transportMode = ENGINE_TRANSPORT_MODE_PLUGIN; - pData->options.forceStereo = true; - pData->options.preferPluginBridges = false; - pData->options.preferUiBridges = false; - init("Carla-Rack"); - pData->graph.create(0, 0); // FIXME? + // set-up engine + if (kIsPatchbay) + { + pData->options.processMode = ENGINE_PROCESS_MODE_PATCHBAY; + pData->options.transportMode = ENGINE_TRANSPORT_MODE_PLUGIN; + pData->options.forceStereo = false; + pData->options.preferPluginBridges = false; + pData->options.preferUiBridges = false; + init("Carla-Patchbay"); + pData->graph.create(inChan, outChan); + } + else + { + CARLA_SAFE_ASSERT(inChan == 2); + CARLA_SAFE_ASSERT(outChan == 2); + pData->options.processMode = ENGINE_PROCESS_MODE_CONTINUOUS_RACK; + pData->options.transportMode = ENGINE_TRANSPORT_MODE_PLUGIN; + pData->options.forceStereo = true; + pData->options.preferPluginBridges = false; + pData->options.preferUiBridges = false; + init("Carla-Rack"); + pData->graph.create(0, 0); // FIXME? + } if (pData->options.resourceDir != nullptr) delete[] pData->options.resourceDir; @@ -1341,7 +1356,7 @@ protected: // --------------------------------------------------------------- // Do nothing if no plugins and rack mode - if (pData->curPluginCount == 0) + if (pData->curPluginCount == 0 && ! kIsPatchbay) { if (outBuffer[0] != inBuffer[0]) carla_copyFloats(outBuffer[0], inBuffer[0], frames); @@ -1382,6 +1397,14 @@ protected: } } + if (kIsPatchbay) + { + // ----------------------------------------------------------- + // process + + pData->graph.process(pData, inBuffer, outBuffer, frames); + } + else { // ----------------------------------------------------------- // create audio buffers @@ -1456,7 +1479,10 @@ protected: CarlaString path(pHost->resourceDir); - path += CARLA_OS_SEP_STR "carla-plugin"; + if (kIsPatchbay) + path += CARLA_OS_SEP_STR "carla-plugin-patchbay"; + else + path += CARLA_OS_SEP_STR "carla-plugin"; #ifdef CARLA_OS_WIN path += ".exe"; #endif @@ -1486,6 +1512,9 @@ protected: } } + if (kIsPatchbay) + patchbayRefresh(false); + if (fWaitForReadyMsg) { carla_stdout("Using Carla plugin embedded, waiting for it to be ready..."); @@ -1653,6 +1682,26 @@ public: return new CarlaEngineNative(host, false); } + static NativePluginHandle _instantiatePatchbay(const NativeHostDescriptor* host) + { + return new CarlaEngineNative(host, true); + } + + static NativePluginHandle _instantiatePatchbay3s(const NativeHostDescriptor* host) + { + return new CarlaEngineNative(host, true, 3, 2); + } + + static NativePluginHandle _instantiatePatchbay16(const NativeHostDescriptor* host) + { + return new CarlaEngineNative(host, true, 16); + } + + static NativePluginHandle _instantiatePatchbay32(const NativeHostDescriptor* host) + { + return new CarlaEngineNative(host, true, 32); + } + static void _cleanup(NativePluginHandle handle) { delete handlePtr; @@ -1776,6 +1825,7 @@ public: private: const NativeHostDescriptor* const pHost; + const bool kIsPatchbay; // rack if false bool fIsActive, fIsRunning; CarlaEngineNativeUI fUiServer; @@ -1843,6 +1893,174 @@ static const NativePluginDescriptor carlaRackDesc = { CarlaEngineNative::_dispatcher }; +static const NativePluginDescriptor carlaPatchbayDesc = { + /* category */ NATIVE_PLUGIN_CATEGORY_OTHER, + /* hints */ static_cast(NATIVE_PLUGIN_IS_SYNTH + |NATIVE_PLUGIN_HAS_UI + //|NATIVE_PLUGIN_NEEDS_FIXED_BUFFERS + |NATIVE_PLUGIN_NEEDS_UI_MAIN_THREAD + |NATIVE_PLUGIN_USES_STATE + |NATIVE_PLUGIN_USES_TIME), + /* supports */ static_cast(NATIVE_PLUGIN_SUPPORTS_EVERYTHING), + /* audioIns */ 2, + /* audioOuts */ 2, + /* midiIns */ 1, + /* midiOuts */ 1, + /* paramIns */ 0, + /* paramOuts */ 0, + /* name */ "Carla-Patchbay", + /* label */ "carlapatchbay", + /* maker */ "falkTX", + /* copyright */ "GNU GPL v2+", + CarlaEngineNative::_instantiatePatchbay, + CarlaEngineNative::_cleanup, + CarlaEngineNative::_get_parameter_count, + CarlaEngineNative::_get_parameter_info, + CarlaEngineNative::_get_parameter_value, + CarlaEngineNative::_get_midi_program_count, + CarlaEngineNative::_get_midi_program_info, + CarlaEngineNative::_set_parameter_value, + CarlaEngineNative::_set_midi_program, + /* _set_custom_data */ nullptr, + CarlaEngineNative::_ui_show, + CarlaEngineNative::_ui_idle, + /* _ui_set_parameter_value */ nullptr, + /* _ui_set_midi_program */ nullptr, + /* _ui_set_custom_data */ nullptr, + CarlaEngineNative::_activate, + CarlaEngineNative::_deactivate, + CarlaEngineNative::_process, + CarlaEngineNative::_get_state, + CarlaEngineNative::_set_state, + CarlaEngineNative::_dispatcher +}; + +static const NativePluginDescriptor carlaPatchbay3sDesc = { + /* category */ NATIVE_PLUGIN_CATEGORY_OTHER, + /* hints */ static_cast(NATIVE_PLUGIN_IS_SYNTH + |NATIVE_PLUGIN_HAS_UI + //|NATIVE_PLUGIN_NEEDS_FIXED_BUFFERS + |NATIVE_PLUGIN_NEEDS_UI_MAIN_THREAD + |NATIVE_PLUGIN_USES_STATE + |NATIVE_PLUGIN_USES_TIME), + /* supports */ static_cast(NATIVE_PLUGIN_SUPPORTS_EVERYTHING), + /* audioIns */ 3, + /* audioOuts */ 2, + /* midiIns */ 1, + /* midiOuts */ 1, + /* paramIns */ 0, + /* paramOuts */ 0, + /* name */ "Carla-Patchbay (sidechain)", + /* label */ "carlapatchbay3s", + /* maker */ "falkTX", + /* copyright */ "GNU GPL v2+", + CarlaEngineNative::_instantiatePatchbay3s, + CarlaEngineNative::_cleanup, + CarlaEngineNative::_get_parameter_count, + CarlaEngineNative::_get_parameter_info, + CarlaEngineNative::_get_parameter_value, + CarlaEngineNative::_get_midi_program_count, + CarlaEngineNative::_get_midi_program_info, + CarlaEngineNative::_set_parameter_value, + CarlaEngineNative::_set_midi_program, + /* _set_custom_data */ nullptr, + CarlaEngineNative::_ui_show, + CarlaEngineNative::_ui_idle, + /* _ui_set_parameter_value */ nullptr, + /* _ui_set_midi_program */ nullptr, + /* _ui_set_custom_data */ nullptr, + CarlaEngineNative::_activate, + CarlaEngineNative::_deactivate, + CarlaEngineNative::_process, + CarlaEngineNative::_get_state, + CarlaEngineNative::_set_state, + CarlaEngineNative::_dispatcher +}; + +static const NativePluginDescriptor carlaPatchbay16Desc = { + /* category */ NATIVE_PLUGIN_CATEGORY_OTHER, + /* hints */ static_cast(NATIVE_PLUGIN_IS_SYNTH + |NATIVE_PLUGIN_HAS_UI + //|NATIVE_PLUGIN_NEEDS_FIXED_BUFFERS + |NATIVE_PLUGIN_NEEDS_UI_MAIN_THREAD + |NATIVE_PLUGIN_USES_STATE + |NATIVE_PLUGIN_USES_TIME), + /* supports */ static_cast(NATIVE_PLUGIN_SUPPORTS_EVERYTHING), + /* audioIns */ 16, + /* audioOuts */ 16, + /* midiIns */ 1, + /* midiOuts */ 1, + /* paramIns */ 0, + /* paramOuts */ 0, + /* name */ "Carla-Patchbay (16chan)", + /* label */ "carlapatchbay16", + /* maker */ "falkTX", + /* copyright */ "GNU GPL v2+", + CarlaEngineNative::_instantiatePatchbay16, + CarlaEngineNative::_cleanup, + CarlaEngineNative::_get_parameter_count, + CarlaEngineNative::_get_parameter_info, + CarlaEngineNative::_get_parameter_value, + CarlaEngineNative::_get_midi_program_count, + CarlaEngineNative::_get_midi_program_info, + CarlaEngineNative::_set_parameter_value, + CarlaEngineNative::_set_midi_program, + /* _set_custom_data */ nullptr, + CarlaEngineNative::_ui_show, + CarlaEngineNative::_ui_idle, + /* _ui_set_parameter_value */ nullptr, + /* _ui_set_midi_program */ nullptr, + /* _ui_set_custom_data */ nullptr, + CarlaEngineNative::_activate, + CarlaEngineNative::_deactivate, + CarlaEngineNative::_process, + CarlaEngineNative::_get_state, + CarlaEngineNative::_set_state, + CarlaEngineNative::_dispatcher +}; + +static const NativePluginDescriptor carlaPatchbay32Desc = { + /* category */ NATIVE_PLUGIN_CATEGORY_OTHER, + /* hints */ static_cast(NATIVE_PLUGIN_IS_SYNTH + |NATIVE_PLUGIN_HAS_UI + //|NATIVE_PLUGIN_NEEDS_FIXED_BUFFERS + |NATIVE_PLUGIN_NEEDS_UI_MAIN_THREAD + |NATIVE_PLUGIN_USES_STATE + |NATIVE_PLUGIN_USES_TIME), + /* supports */ static_cast(NATIVE_PLUGIN_SUPPORTS_EVERYTHING), + /* audioIns */ 32, + /* audioOuts */ 32, + /* midiIns */ 1, + /* midiOuts */ 1, + /* paramIns */ 0, + /* paramOuts */ 0, + /* name */ "Carla-Patchbay (32chan)", + /* label */ "carlapatchbay32", + /* maker */ "falkTX", + /* copyright */ "GNU GPL v2+", + CarlaEngineNative::_instantiatePatchbay32, + CarlaEngineNative::_cleanup, + CarlaEngineNative::_get_parameter_count, + CarlaEngineNative::_get_parameter_info, + CarlaEngineNative::_get_parameter_value, + CarlaEngineNative::_get_midi_program_count, + CarlaEngineNative::_get_midi_program_info, + CarlaEngineNative::_set_parameter_value, + CarlaEngineNative::_set_midi_program, + /* _set_custom_data */ nullptr, + CarlaEngineNative::_ui_show, + CarlaEngineNative::_ui_idle, + /* _ui_set_parameter_value */ nullptr, + /* _ui_set_midi_program */ nullptr, + /* _ui_set_custom_data */ nullptr, + CarlaEngineNative::_activate, + CarlaEngineNative::_deactivate, + CarlaEngineNative::_process, + CarlaEngineNative::_get_state, + CarlaEngineNative::_set_state, + CarlaEngineNative::_dispatcher +}; + CARLA_BACKEND_END_NAMESPACE // ----------------------------------------------------------------------- @@ -1854,6 +2072,10 @@ void carla_register_native_plugin_carla() { CARLA_BACKEND_USE_NAMESPACE; carla_register_native_plugin(&carlaRackDesc); + carla_register_native_plugin(&carlaPatchbayDesc); + carla_register_native_plugin(&carlaPatchbay3sDesc); + carla_register_native_plugin(&carlaPatchbay16Desc); + carla_register_native_plugin(&carlaPatchbay32Desc); } // ----------------------------------------------------------------------- @@ -1866,6 +2088,14 @@ const NativePluginDescriptor* carla_get_native_rack_plugin() return &carlaRackDesc; } +CARLA_EXPORT +const NativePluginDescriptor* carla_get_native_patchbay_plugin(); +const NativePluginDescriptor* carla_get_native_patchbay_plugin() +{ + CARLA_BACKEND_USE_NAMESPACE; + return &carlaPatchbayDesc; +} + // ----------------------------------------------------------------------- // Extra stuff for linking purposes diff --git a/source/backend/engine/CarlaEnginePorts.cpp b/source/backend/engine/CarlaEnginePorts.cpp index 1c5050bcf..f3aca651b 100644 --- a/source/backend/engine/CarlaEnginePorts.cpp +++ b/source/backend/engine/CarlaEnginePorts.cpp @@ -89,17 +89,30 @@ CarlaEngineEventPort::CarlaEngineEventPort(const CarlaEngineClient& client, cons kProcessMode(client.getEngine().getProccessMode()) { carla_debug("CarlaEngineEventPort::CarlaEngineEventPort(%s)", bool2str(isInputPort)); + + if (kProcessMode == ENGINE_PROCESS_MODE_PATCHBAY) + fBuffer = new EngineEvent[kMaxEngineEventInternalCount]; } CarlaEngineEventPort::~CarlaEngineEventPort() noexcept { carla_debug("CarlaEngineEventPort::~CarlaEngineEventPort()"); + + if (kProcessMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr,); + + delete[] fBuffer; + fBuffer = nullptr; + } } void CarlaEngineEventPort::initBuffer() noexcept { if (kProcessMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || kProcessMode == ENGINE_PROCESS_MODE_BRIDGE) fBuffer = kClient.getEngine().getInternalEventBuffer(kIsInput); + else if (kProcessMode == ENGINE_PROCESS_MODE_PATCHBAY && ! kIsInput) + carla_zeroStructs(fBuffer, kMaxEngineEventInternalCount); } uint32_t CarlaEngineEventPort::getEventCount() const noexcept diff --git a/source/backend/engine/CarlaEngineRtAudio.cpp b/source/backend/engine/CarlaEngineRtAudio.cpp index 672ae939a..7ae59908a 100644 --- a/source/backend/engine/CarlaEngineRtAudio.cpp +++ b/source/backend/engine/CarlaEngineRtAudio.cpp @@ -183,7 +183,7 @@ public: CARLA_SAFE_ASSERT_RETURN(clientName != nullptr && clientName[0] != '\0', false); carla_debug("CarlaEngineRtAudio::init(\"%s\")", clientName); - if (pData->options.processMode != ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + if (pData->options.processMode != ENGINE_PROCESS_MODE_CONTINUOUS_RACK && pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY) { setLastError("Invalid process mode"); return false; @@ -293,7 +293,10 @@ public: return false; } - patchbayRefresh(); + patchbayRefresh(false); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + refreshExternalGraphPorts(pData->graph.getPatchbayGraph(), false); callback(ENGINE_CALLBACK_ENGINE_STARTED, 0, pData->options.processMode, pData->options.transportMode, 0.0f, getCurrentDriverName()); return true; @@ -402,11 +405,9 @@ public: // ------------------------------------------------------------------- // Patchbay - bool patchbayRefresh() override + template + bool refreshExternalGraphPorts(Graph* const graph, const bool sendCallback) { - CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); - - RackGraph* const graph(pData->graph.getGraph()); CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); char strBuf[STR_MAX+1]; @@ -473,7 +474,8 @@ public: // --------------------------------------------------------------- // now refresh - graph->refresh(fDeviceName.buffer()); + if (sendCallback) + graph->refresh(fDeviceName.buffer()); // --------------------------------------------------------------- // add midi connections @@ -495,7 +497,8 @@ public: extGraph.connections.list.append(connectionToId); - callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf); + if (sendCallback) + callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf); } fMidiOutMutex.lock(); @@ -517,7 +520,8 @@ public: extGraph.connections.list.append(connectionToId); - callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf); + if (sendCallback) + callback(ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, connectionToId.id, 0, 0, 0.0f, strBuf); } fMidiOutMutex.unlock(); @@ -525,6 +529,21 @@ public: return true; } + bool patchbayRefresh(const bool external) override + { + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + return refreshExternalGraphPorts(pData->graph.getRackGraph(), true); + + pData->graph.setUsingExternal(external); + + if (external) + return refreshExternalGraphPorts(pData->graph.getPatchbayGraph(), true); + + return CarlaEngine::patchbayRefresh(false); + } + // ------------------------------------------------------------------- protected: diff --git a/source/modules/water/containers/juce_NamedValueSet.cpp b/source/modules/water/containers/juce_NamedValueSet.cpp index 60110b98b..c763a3451 100644 --- a/source/modules/water/containers/juce_NamedValueSet.cpp +++ b/source/modules/water/containers/juce_NamedValueSet.cpp @@ -207,45 +207,3 @@ var* NamedValueSet::getVarPointerAt (int index) const noexcept return nullptr; } - -void NamedValueSet::setFromXmlAttributes (const XmlElement& xml) -{ - values.clearQuick(); - - for (const XmlElement::XmlAttributeNode* att = xml.attributes; att != nullptr; att = att->nextListItem) - { - if (att->name.toString().startsWith ("base64:")) - { - MemoryBlock mb; - - if (mb.fromBase64Encoding (att->value)) - { - values.add (NamedValue (att->name.toString().substring (7), var (mb))); - continue; - } - } - - values.add (NamedValue (att->name, var (att->value))); - } -} - -void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const -{ - for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) - { - if (const MemoryBlock* mb = i->value.getBinaryData()) - { - xml.setAttribute ("base64:" + i->name.toString(), mb->toBase64Encoding()); - } - else - { - // These types can't be stored as XML! - jassert (! i->value.isObject()); - jassert (! i->value.isMethod()); - jassert (! i->value.isArray()); - - xml.setAttribute (i->name.toString(), - i->value.toString()); - } - } -} diff --git a/source/modules/water/containers/juce_Variant.cpp b/source/modules/water/containers/juce_Variant.cpp index 82e7d424e..1559d48ae 100644 --- a/source/modules/water/containers/juce_Variant.cpp +++ b/source/modules/water/containers/juce_Variant.cpp @@ -36,8 +36,6 @@ enum VariantStreamMarkers varMarker_Double = 4, varMarker_String = 5, varMarker_Int64 = 6, - varMarker_Array = 7, - varMarker_Binary = 8, varMarker_Undefined = 9 }; @@ -53,9 +51,6 @@ public: virtual double toDouble (const ValueUnion&) const noexcept { return 0; } virtual String toString (const ValueUnion&) const { return String(); } virtual bool toBool (const ValueUnion&) const noexcept { return false; } - virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } - virtual Array* toArray (const ValueUnion&) const noexcept { return nullptr; } - virtual MemoryBlock* toBinary (const ValueUnion&) const noexcept { return nullptr; } virtual var clone (const var& original) const { return original; } virtual bool isVoid() const noexcept { return false; } @@ -65,10 +60,6 @@ public: virtual bool isBool() const noexcept { return false; } virtual bool isDouble() const noexcept { return false; } virtual bool isString() const noexcept { return false; } - virtual bool isObject() const noexcept { return false; } - virtual bool isArray() const noexcept { return false; } - virtual bool isBinary() const noexcept { return false; } - virtual bool isMethod() const noexcept { return false; } virtual void cleanUp (ValueUnion&) const noexcept {} virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } @@ -259,167 +250,6 @@ private: static inline String* getString (ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } }; -//============================================================================== -class var::VariantType_Object : public var::VariantType -{ -public: - VariantType_Object() noexcept {} - static const VariantType_Object instance; - - void cleanUp (ValueUnion& data) const noexcept override { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } - - void createCopy (ValueUnion& dest, const ValueUnion& source) const override - { - dest.objectValue = source.objectValue; - if (dest.objectValue != nullptr) - dest.objectValue->incReferenceCount(); - } - - String toString (const ValueUnion& data) const override { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } - bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != nullptr; } - ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept override { return data.objectValue; } - bool isObject() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override - { - return otherType.toObject (otherData) == data.objectValue; - } - - var clone (const var& original) const override - { - if (DynamicObject* d = original.getDynamicObject()) - return d->clone().get(); - - jassertfalse; // can only clone DynamicObjects! - return var(); - } - - void writeToStream (const ValueUnion&, OutputStream& output) const override - { - jassertfalse; // Can't write an object to a stream! - output.writeCompressedInt (0); - } -}; - -//============================================================================== -class var::VariantType_Array : public var::VariantType_Object -{ -public: - VariantType_Array() noexcept {} - static const VariantType_Array instance; - - String toString (const ValueUnion&) const override { return "[Array]"; } - ReferenceCountedObject* toObject (const ValueUnion&) const noexcept override { return nullptr; } - bool isArray() const noexcept override { return true; } - - Array* toArray (const ValueUnion& data) const noexcept override - { - if (RefCountedArray* a = dynamic_cast (data.objectValue)) - return &(a->array); - - return nullptr; - } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override - { - const Array* const thisArray = toArray (data); - const Array* const otherArray = otherType.toArray (otherData); - return thisArray == otherArray || (thisArray != nullptr && otherArray != nullptr && *otherArray == *thisArray); - } - - var clone (const var& original) const override - { - Array arrayCopy; - - if (const Array* array = toArray (original.value)) - for (int i = 0; i < array->size(); ++i) - arrayCopy.add (array->getReference(i).clone()); - - return var (arrayCopy); - } - - void writeToStream (const ValueUnion& data, OutputStream& output) const override - { - if (const Array* array = toArray (data)) - { - MemoryOutputStream buffer (512); - const int numItems = array->size(); - buffer.writeCompressedInt (numItems); - - for (int i = 0; i < numItems; ++i) - array->getReference(i).writeToStream (buffer); - - output.writeCompressedInt (1 + (int) buffer.getDataSize()); - output.writeByte (varMarker_Array); - output << buffer; - } - } - - struct RefCountedArray : public ReferenceCountedObject - { - RefCountedArray (const Array& a) : array (a) { incReferenceCount(); } - #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS - RefCountedArray (Array&& a) : array (static_cast&&> (a)) { incReferenceCount(); } - #endif - Array array; - }; -}; - -//============================================================================== -class var::VariantType_Binary : public var::VariantType -{ -public: - VariantType_Binary() noexcept {} - - static const VariantType_Binary instance; - - void cleanUp (ValueUnion& data) const noexcept override { delete data.binaryValue; } - void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.binaryValue = new MemoryBlock (*source.binaryValue); } - - String toString (const ValueUnion& data) const override { return data.binaryValue->toBase64Encoding(); } - bool isBinary() const noexcept override { return true; } - MemoryBlock* toBinary (const ValueUnion& data) const noexcept override { return data.binaryValue; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override - { - const MemoryBlock* const otherBlock = otherType.toBinary (otherData); - return otherBlock != nullptr && *otherBlock == *data.binaryValue; - } - - void writeToStream (const ValueUnion& data, OutputStream& output) const override - { - output.writeCompressedInt (1 + (int) data.binaryValue->getSize()); - output.writeByte (varMarker_Binary); - output << *data.binaryValue; - } -}; - -//============================================================================== -class var::VariantType_Method : public var::VariantType -{ -public: - VariantType_Method() noexcept {} - static const VariantType_Method instance; - - void cleanUp (ValueUnion& data) const noexcept override { if (data.methodValue != nullptr ) delete data.methodValue; } - void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.methodValue = new NativeFunction (*source.methodValue); } - - String toString (const ValueUnion&) const override { return "Method"; } - bool toBool (const ValueUnion& data) const noexcept override { return data.methodValue != nullptr; } - bool isMethod() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override - { - return otherType.isMethod() && otherData.methodValue == data.methodValue; - } - - void writeToStream (const ValueUnion&, OutputStream& output) const override - { - jassertfalse; // Can't write a method to a stream! - output.writeCompressedInt (0); - } -}; - //============================================================================== const var::VariantType_Void var::VariantType_Void::instance; const var::VariantType_Undefined var::VariantType_Undefined::instance; @@ -428,10 +258,6 @@ const var::VariantType_Int64 var::VariantType_Int64::instance; const var::VariantType_Bool var::VariantType_Bool::instance; const var::VariantType_Double var::VariantType_Double::instance; const var::VariantType_String var::VariantType_String::instance; -const var::VariantType_Object var::VariantType_Object::instance; -const var::VariantType_Array var::VariantType_Array::instance; -const var::VariantType_Binary var::VariantType_Binary::instance; -const var::VariantType_Method var::VariantType_Method::instance; //============================================================================== @@ -449,32 +275,8 @@ var::var (const int v) noexcept : type (&VariantType_Int::instance) { v var::var (const int64 v) noexcept : type (&VariantType_Int64::instance) { value.int64Value = v; } var::var (const bool v) noexcept : type (&VariantType_Bool::instance) { value.boolValue = v; } var::var (const double v) noexcept : type (&VariantType_Double::instance) { value.doubleValue = v; } -var::var (NativeFunction m) noexcept : type (&VariantType_Method::instance) { value.methodValue = new NativeFunction (m); } -var::var (const Array& v) : type (&VariantType_Array::instance) { value.objectValue = new VariantType_Array::RefCountedArray(v); } var::var (const String& v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } var::var (const char* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } -var::var (const wchar_t* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } -var::var (const void* v, size_t sz) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v, sz); } -var::var (const MemoryBlock& v) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v); } - -var::var (const StringArray& v) : type (&VariantType_Array::instance) -{ - Array strings; - - const int n = v.size(); - for (int i = 0; i < n; ++i) - strings.add (var (v[i])); - - value.objectValue = new VariantType_Array::RefCountedArray(strings); -} - -var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) -{ - value.objectValue = object; - - if (object != nullptr) - object->incReferenceCount(); -} var var::undefined() noexcept { return var (VariantType_Undefined::instance); } @@ -486,10 +288,6 @@ bool var::isInt64() const noexcept { return type->isInt64(); } bool var::isBool() const noexcept { return type->isBool(); } bool var::isDouble() const noexcept { return type->isDouble(); } bool var::isString() const noexcept { return type->isString(); } -bool var::isObject() const noexcept { return type->isObject(); } -bool var::isArray() const noexcept { return type->isArray(); } -bool var::isBinaryData() const noexcept { return type->isBinary(); } -bool var::isMethod() const noexcept { return type->isMethod(); } var::operator int() const noexcept { return type->toInt (value); } var::operator int64() const noexcept { return type->toInt64 (value); } @@ -498,10 +296,6 @@ var::operator float() const noexcept { return (float) type->t var::operator double() const noexcept { return type->toDouble (value); } String var::toString() const { return type->toString (value); } var::operator String() const { return type->toString (value); } -ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); } -Array* var::getArray() const noexcept { return type->toArray (value); } -MemoryBlock* var::getBinaryData() const noexcept { return type->toBinary (value); } -DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast (getObject()); } //============================================================================== void var::swapWith (var& other) noexcept @@ -516,12 +310,7 @@ var& var::operator= (const int64 v) { type->cleanUp (value); type = var& var::operator= (const bool v) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = v; return *this; } var& var::operator= (const double v) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = v; return *this; } var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } -var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } -var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &VariantType_Binary::instance; value.binaryValue = new MemoryBlock (v); return *this; } -var& var::operator= (const Array& v) { var v2 (v); swapWith (v2); return *this; } -var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } -var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; } #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS var::var (var&& other) noexcept @@ -542,16 +331,6 @@ var::var (String&& v) : type (&VariantType_String::instance) new (value.stringValue) String (static_cast (v)); } -var::var (MemoryBlock&& v) : type (&VariantType_Binary::instance) -{ - value.binaryValue = new MemoryBlock (static_cast (v)); -} - -var::var (Array&& v) : type (&VariantType_Array::instance) -{ - value.objectValue = new VariantType_Array::RefCountedArray (static_cast&&> (v)); -} - var& var::operator= (String&& v) { type->cleanUp (value); @@ -589,207 +368,3 @@ var var::clone() const noexcept { return type->clone (*this); } - -//============================================================================== -const var& var::operator[] (const Identifier& propertyName) const -{ - if (DynamicObject* const o = getDynamicObject()) - return o->getProperty (propertyName); - - return getNullVarRef(); -} - -const var& var::operator[] (const char* const propertyName) const -{ - return operator[] (Identifier (propertyName)); -} - -var var::getProperty (const Identifier& propertyName, const var& defaultReturnValue) const -{ - if (DynamicObject* const o = getDynamicObject()) - return o->getProperties().getWithDefault (propertyName, defaultReturnValue); - - return defaultReturnValue; -} - -var::NativeFunction var::getNativeFunction() const -{ - return isMethod() && (value.methodValue != nullptr) ? *value.methodValue : nullptr; -} - -var var::invoke (const Identifier& method, const var* arguments, int numArguments) const -{ - if (DynamicObject* const o = getDynamicObject()) - return o->invokeMethod (method, var::NativeFunctionArgs (*this, arguments, numArguments)); - - return var(); -} - -var var::call (const Identifier& method) const -{ - return invoke (method, nullptr, 0); -} - -var var::call (const Identifier& method, const var& arg1) const -{ - return invoke (method, &arg1, 1); -} - -var var::call (const Identifier& method, const var& arg1, const var& arg2) const -{ - var args[] = { arg1, arg2 }; - return invoke (method, args, 2); -} - -var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3) -{ - var args[] = { arg1, arg2, arg3 }; - return invoke (method, args, 3); -} - -var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const -{ - var args[] = { arg1, arg2, arg3, arg4 }; - return invoke (method, args, 4); -} - -var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const -{ - var args[] = { arg1, arg2, arg3, arg4, arg5 }; - return invoke (method, args, 5); -} - -//============================================================================== -int var::size() const -{ - if (const Array* const array = getArray()) - return array->size(); - - return 0; -} - -const var& var::operator[] (int arrayIndex) const -{ - const Array* const array = getArray(); - - // When using this method, the var must actually be an array, and the index - // must be in-range! - jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); - - return array->getReference (arrayIndex); -} - -var& var::operator[] (int arrayIndex) -{ - const Array* const array = getArray(); - - // When using this method, the var must actually be an array, and the index - // must be in-range! - jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); - - return array->getReference (arrayIndex); -} - -Array* var::convertToArray() -{ - if (Array* array = getArray()) - return array; - - Array tempVar; - if (! isVoid()) - tempVar.add (*this); - - *this = tempVar; - return getArray(); -} - -void var::append (const var& n) -{ - convertToArray()->add (n); -} - -void var::remove (const int index) -{ - if (Array* const array = getArray()) - array->remove (index); -} - -void var::insert (const int index, const var& n) -{ - convertToArray()->insert (index, n); -} - -void var::resize (const int numArrayElementsWanted) -{ - convertToArray()->resize (numArrayElementsWanted); -} - -int var::indexOf (const var& n) const -{ - if (const Array* const array = getArray()) - return array->indexOf (n); - - return -1; -} - -//============================================================================== -void var::writeToStream (OutputStream& output) const -{ - type->writeToStream (value, output); -} - -var var::readFromStream (InputStream& input) -{ - const int numBytes = input.readCompressedInt(); - - if (numBytes > 0) - { - switch (input.readByte()) - { - case varMarker_Int: return var (input.readInt()); - case varMarker_Int64: return var (input.readInt64()); - case varMarker_BoolTrue: return var (true); - case varMarker_BoolFalse: return var (false); - case varMarker_Double: return var (input.readDouble()); - case varMarker_String: - { - MemoryOutputStream mo; - mo.writeFromInputStream (input, numBytes - 1); - return var (mo.toUTF8()); - } - - case varMarker_Binary: - { - MemoryBlock mb ((size_t) numBytes - 1); - - if (numBytes > 1) - { - const int numRead = input.read (mb.getData(), numBytes - 1); - mb.setSize ((size_t) numRead); - } - - return var (mb); - } - - case varMarker_Array: - { - var v; - Array* const destArray = v.convertToArray(); - - for (int i = input.readCompressedInt(); --i >= 0;) - destArray->add (readFromStream (input)); - - return v; - } - - default: - input.skipNextBytes (numBytes - 1); break; - } - } - - return var(); -} - -var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int numArgs) noexcept - : thisObject (t), arguments (args), numArguments (numArgs) -{} diff --git a/source/modules/water/containers/juce_Variant.h b/source/modules/water/containers/juce_Variant.h index ec1e1c341..34e348cad 100644 --- a/source/modules/water/containers/juce_Variant.h +++ b/source/modules/water/containers/juce_Variant.h @@ -48,27 +48,6 @@ class JUCE_API var { public: - //============================================================================== - /** This structure is passed to a NativeFunction callback, and contains invocation - details about the function's arguments and context. - */ - struct NativeFunctionArgs - { - NativeFunctionArgs (const var& thisObject, const var* args, int numArgs) noexcept; - - const var& thisObject; - const var* arguments; - int numArguments; - - JUCE_DECLARE_NON_COPYABLE (NativeFunctionArgs) - }; - - #if JUCE_COMPILER_SUPPORTS_LAMBDAS - using NativeFunction = std::function; - #else - typedef var (*NativeFunction) (const NativeFunctionArgs&); - #endif - //============================================================================== /** Creates a void variant. */ var() noexcept; @@ -82,14 +61,7 @@ public: var (bool value) noexcept; var (double value) noexcept; var (const char* value); - var (const wchar_t* value); var (const String& value); - var (const Array& value); - var (const StringArray& value); - var (ReferenceCountedObject* object); - var (NativeFunction method) noexcept; - var (const void* binaryData, size_t dataSize); - var (const MemoryBlock& binaryData); var& operator= (const var& valueToCopy); var& operator= (int value); @@ -97,18 +69,11 @@ public: var& operator= (bool value); var& operator= (double value); var& operator= (const char* value); - var& operator= (const wchar_t* value); var& operator= (const String& value); - var& operator= (const MemoryBlock& value); - var& operator= (const Array& value); - var& operator= (ReferenceCountedObject* object); - var& operator= (NativeFunction method); #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS var (var&&) noexcept; var (String&&); - var (MemoryBlock&&); - var (Array&&); var& operator= (var&&) noexcept; var& operator= (String&&); #endif @@ -127,25 +92,6 @@ public: operator String() const; String toString() const; - /** If this variant holds an array, this provides access to it. - NOTE: Beware when you use this - the array pointer is only valid for the lifetime - of the variant that returned it, so be very careful not to call this method on temporary - var objects that are the return-value of a function, and which may go out of scope before - you use the array! - */ - Array* getArray() const noexcept; - - /** If this variant holds a memory block, this provides access to it. - NOTE: Beware when you use this - the MemoryBlock pointer is only valid for the lifetime - of the variant that returned it, so be very careful not to call this method on temporary - var objects that are the return-value of a function, and which may go out of scope before - you use the MemoryBlock! - */ - MemoryBlock* getBinaryData() const noexcept; - - ReferenceCountedObject* getObject() const noexcept; - DynamicObject* getDynamicObject() const noexcept; - //============================================================================== bool isVoid() const noexcept; bool isUndefined() const noexcept; @@ -154,10 +100,6 @@ public: bool isBool() const noexcept; bool isDouble() const noexcept; bool isString() const noexcept; - bool isObject() const noexcept; - bool isArray() const noexcept; - bool isBinaryData() const noexcept; - bool isMethod() const noexcept; /** Returns true if this var has the same value as the one supplied. Note that this ignores the type, so a string var "123" and an integer var with the @@ -181,109 +123,6 @@ public: */ var clone() const noexcept; - //============================================================================== - /** If the var is an array, this returns the number of elements. - If the var isn't actually an array, this will return 0. - */ - int size() const; - - /** If the var is an array, this can be used to return one of its elements. - To call this method, you must make sure that the var is actually an array, and - that the index is a valid number. If these conditions aren't met, behaviour is - undefined. - For more control over the array's contents, you can call getArray() and manipulate - it directly as an Array\. - */ - const var& operator[] (int arrayIndex) const; - - /** If the var is an array, this can be used to return one of its elements. - To call this method, you must make sure that the var is actually an array, and - that the index is a valid number. If these conditions aren't met, behaviour is - undefined. - For more control over the array's contents, you can call getArray() and manipulate - it directly as an Array\. - */ - var& operator[] (int arrayIndex); - - /** Appends an element to the var, converting it to an array if it isn't already one. - If the var isn't an array, it will be converted to one, and if its value was non-void, - this value will be kept as the first element of the new array. The parameter value - will then be appended to it. - For more control over the array's contents, you can call getArray() and manipulate - it directly as an Array\. - */ - void append (const var& valueToAppend); - - /** Inserts an element to the var, converting it to an array if it isn't already one. - If the var isn't an array, it will be converted to one, and if its value was non-void, - this value will be kept as the first element of the new array. The parameter value - will then be inserted into it. - For more control over the array's contents, you can call getArray() and manipulate - it directly as an Array\. - */ - void insert (int index, const var& value); - - /** If the var is an array, this removes one of its elements. - If the index is out-of-range or the var isn't an array, nothing will be done. - For more control over the array's contents, you can call getArray() and manipulate - it directly as an Array\. - */ - void remove (int index); - - /** Treating the var as an array, this resizes it to contain the specified number of elements. - If the var isn't an array, it will be converted to one, and if its value was non-void, - this value will be kept as the first element of the new array before resizing. - For more control over the array's contents, you can call getArray() and manipulate - it directly as an Array\. - */ - void resize (int numArrayElementsWanted); - - /** If the var is an array, this searches it for the first occurrence of the specified value, - and returns its index. - If the var isn't an array, or if the value isn't found, this returns -1. - */ - int indexOf (const var& value) const; - - //============================================================================== - /** If this variant is an object, this returns one of its properties. */ - const var& operator[] (const Identifier& propertyName) const; - /** If this variant is an object, this returns one of its properties. */ - const var& operator[] (const char* propertyName) const; - /** If this variant is an object, this returns one of its properties, or a default - fallback value if the property is not set. */ - var getProperty (const Identifier& propertyName, const var& defaultReturnValue) const; - - /** Invokes a named method call with no arguments. */ - var call (const Identifier& method) const; - /** Invokes a named method call with one argument. */ - var call (const Identifier& method, const var& arg1) const; - /** Invokes a named method call with 2 arguments. */ - var call (const Identifier& method, const var& arg1, const var& arg2) const; - /** Invokes a named method call with 3 arguments. */ - var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3); - /** Invokes a named method call with 4 arguments. */ - var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const; - /** Invokes a named method call with 5 arguments. */ - var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const; - /** Invokes a named method call with a list of arguments. */ - var invoke (const Identifier& method, const var* arguments, int numArguments) const; - /** If this object is a method, this returns the function pointer. */ - NativeFunction getNativeFunction() const; - - //============================================================================== - /** Writes a binary representation of this value to a stream. - The data can be read back later using readFromStream(). - @see JSON - */ - void writeToStream (OutputStream& output) const; - - /** Reads back a stored binary representation of a value. - The data in the stream must have been written using writeToStream(), or this - will have unpredictable results. - @see JSON - */ - static var readFromStream (InputStream& input); - private: //============================================================================== class VariantType; friend class VariantType; @@ -294,10 +133,6 @@ private: class VariantType_Double; friend class VariantType_Double; class VariantType_Bool; friend class VariantType_Bool; class VariantType_String; friend class VariantType_String; - class VariantType_Object; friend class VariantType_Object; - class VariantType_Array; friend class VariantType_Array; - class VariantType_Binary; friend class VariantType_Binary; - class VariantType_Method; friend class VariantType_Method; union ValueUnion { @@ -306,9 +141,6 @@ private: bool boolValue; double doubleValue; char stringValue [sizeof (String)]; - ReferenceCountedObject* objectValue; - MemoryBlock* binaryValue; - NativeFunction* methodValue; }; const VariantType* type; diff --git a/source/modules/water/processors/juce_AudioProcessor.h b/source/modules/water/processors/juce_AudioProcessor.h index f619127c8..b507a8c14 100644 --- a/source/modules/water/processors/juce_AudioProcessor.h +++ b/source/modules/water/processors/juce_AudioProcessor.h @@ -246,9 +246,6 @@ public: */ void setLatencySamples (int newLatency); - /** Returns the length of the filter's tail, in seconds. */ - virtual double getTailLengthSeconds() const = 0; - /** Returns true if the processor wants midi messages. */ virtual bool acceptsMidi() const = 0; @@ -261,6 +258,9 @@ public: /** Returns true if this is a midi effect plug-in and does no audio processing. */ virtual bool isMidiEffect() const { return false; } + virtual const String getInputChannelName (int) const { return String(); } + virtual const String getOutputChannelName (int) const { return String(); } + //============================================================================== /** This returns a critical section that will automatically be locked while the host is calling the processBlock() method. diff --git a/source/modules/water/processors/juce_AudioProcessorGraph.cpp b/source/modules/water/processors/juce_AudioProcessorGraph.cpp index c645e3845..9fd08d584 100644 --- a/source/modules/water/processors/juce_AudioProcessorGraph.cpp +++ b/source/modules/water/processors/juce_AudioProcessorGraph.cpp @@ -1390,7 +1390,6 @@ void AudioProcessorGraph::processAudio (AudioSampleBuffer& buffer, MidiBuffer& m midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); } -double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } bool AudioProcessorGraph::acceptsMidi() const { return true; } bool AudioProcessorGraph::producesMidi() const { return true; } @@ -1516,11 +1515,6 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer processAudio (buffer, midiMessages); } -double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const -{ - return 0; -} - bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const { return type == midiOutputNode; diff --git a/source/modules/water/processors/juce_AudioProcessorGraph.h b/source/modules/water/processors/juce_AudioProcessorGraph.h index 414ca55fb..1163813ab 100644 --- a/source/modules/water/processors/juce_AudioProcessorGraph.h +++ b/source/modules/water/processors/juce_AudioProcessorGraph.h @@ -319,7 +319,6 @@ public: void releaseResources() override; void processBlock (AudioSampleBuffer&, MidiBuffer&) override; - double getTailLengthSeconds() const override; bool acceptsMidi() const override; bool producesMidi() const override; @@ -346,7 +345,6 @@ public: void setNonRealtime (bool) noexcept override; void setPlayHead (AudioPlayHead*) override; - double getTailLengthSeconds() const override; bool acceptsMidi() const override; bool producesMidi() const override; diff --git a/source/modules/water/water.cpp b/source/modules/water/water.cpp index 40e2bf68f..d0e567a97 100644 --- a/source/modules/water/water.cpp +++ b/source/modules/water/water.cpp @@ -101,8 +101,8 @@ static int64 juce_fileSetPosition (void* handle, int64 pos) #include "text/juce_CharacterFunctions.cpp" #include "text/juce_String.cpp" -//#include "containers/juce_NamedValueSet.cpp" -//#include "containers/juce_Variant.cpp" +#include "containers/juce_NamedValueSet.cpp" +#include "containers/juce_Variant.cpp" #include "files/juce_DirectoryIterator.cpp" #include "files/juce_File.cpp" @@ -115,7 +115,7 @@ static int64 juce_fileSetPosition (void* handle, int64 pos) #include "misc/juce_Result.cpp" #include "processors/juce_AudioProcessor.cpp" -//#include "processors/juce_AudioProcessorGraph.cpp" +#include "processors/juce_AudioProcessorGraph.cpp" #include "streams/juce_FileInputSource.cpp" #include "streams/juce_FileInputStream.cpp" diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp index 5ddc226d5..483d20aae 100644 --- a/source/utils/CarlaBackendUtils.hpp +++ b/source/utils/CarlaBackendUtils.hpp @@ -358,6 +358,8 @@ const char* EngineProcessMode2Str(const EngineProcessMode mode) noexcept return "ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS"; case ENGINE_PROCESS_MODE_CONTINUOUS_RACK: return "ENGINE_PROCESS_MODE_CONTINUOUS_RACK"; + case ENGINE_PROCESS_MODE_PATCHBAY: + return "ENGINE_PROCESS_MODE_PATCHBAY"; case ENGINE_PROCESS_MODE_BRIDGE: return "ENGINE_PROCESS_MODE_BRIDGE"; } diff --git a/source/utils/CarlaEngineUtils.hpp b/source/utils/CarlaEngineUtils.hpp index 0b4d67546..783f7bb07 100644 --- a/source/utils/CarlaEngineUtils.hpp +++ b/source/utils/CarlaEngineUtils.hpp @@ -20,6 +20,9 @@ #include "CarlaEngine.hpp" #include "CarlaUtils.hpp" +#include "CarlaMIDI.h" + +#include "water/water.h" CARLA_BACKEND_START_NAMESPACE @@ -110,6 +113,94 @@ const char* EngineControlEventType2Str(const EngineControlEventType type) noexce return nullptr; } +// ----------------------------------------------------------------------- + +static inline +void fillEngineEventsFromWaterMidiBuffer(EngineEvent engineEvents[kMaxEngineEventInternalCount], const water::MidiBuffer& midiBuffer) +{ + const uint8_t* midiData; + int numBytes, sampleNumber; + ushort engineEventIndex = 0; + + for (ushort i=0; i < kMaxEngineEventInternalCount; ++i) + { + const EngineEvent& engineEvent(engineEvents[i]); + + if (engineEvent.type != kEngineEventTypeNull) + continue; + + engineEventIndex = i; + break; + } + + for (water::MidiBuffer::Iterator midiBufferIterator(midiBuffer); midiBufferIterator.getNextEvent(midiData, numBytes, sampleNumber) && engineEventIndex < kMaxEngineEventInternalCount;) + { + CARLA_SAFE_ASSERT_CONTINUE(numBytes > 0); + CARLA_SAFE_ASSERT_CONTINUE(sampleNumber >= 0); + CARLA_SAFE_ASSERT_CONTINUE(numBytes < 0xFF /* uint8_t max */); + + EngineEvent& engineEvent(engineEvents[engineEventIndex++]); + + engineEvent.time = static_cast(sampleNumber); + engineEvent.fillFromMidiData(static_cast(numBytes), midiData, 0); + } +} + +// ----------------------------------------------------------------------- + +static inline +void fillWaterMidiBufferFromEngineEvents(water::MidiBuffer& midiBuffer, const EngineEvent engineEvents[kMaxEngineEventInternalCount]) +{ + uint8_t size = 0; + uint8_t mdata[3] = { 0, 0, 0 }; + const uint8_t* mdataPtr = mdata; + uint8_t mdataTmp[EngineMidiEvent::kDataSize]; + + for (ushort i=0; i < kMaxEngineEventInternalCount; ++i) + { + const EngineEvent& engineEvent(engineEvents[i]); + + if (engineEvent.type == kEngineEventTypeNull) + { + break; + } + else if (engineEvent.type == kEngineEventTypeControl) + { + const EngineControlEvent& ctrlEvent(engineEvent.ctrl); + + ctrlEvent.convertToMidiData(engineEvent.channel, size, mdata); + mdataPtr = mdata; + } + else if (engineEvent.type == kEngineEventTypeMidi) + { + const EngineMidiEvent& midiEvent(engineEvent.midi); + + size = midiEvent.size; + + if (size > EngineMidiEvent::kDataSize && midiEvent.dataExt != nullptr) + { + mdataPtr = midiEvent.dataExt; + } + else + { + // copy + carla_copy(mdataTmp, midiEvent.data, size); + // add channel + mdataTmp[0] = static_cast(mdataTmp[0] | (engineEvent.channel & MIDI_CHANNEL_BIT)); + // done + mdataPtr = mdataTmp; + } + } + else + { + continue; + } + + if (size > 0) + midiBuffer.addEvent(mdataPtr, static_cast(size), static_cast(engineEvent.time)); + } +} + // ------------------------------------------------------------------- // Helper classes