Signed-off-by: falkTX <falktx@falktx.com>tags/v2.2.0-RC1
| @@ -1141,7 +1141,17 @@ typedef enum { | |||
| * @a value1 Parameter index | |||
| * @a valueStr New mapped range as "%f:%f" syntax | |||
| */ | |||
| ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 | |||
| ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46, | |||
| /*! | |||
| * A patchbay client position has changed. | |||
| * @a pluginId Client Id | |||
| * @a value1 X position 1 | |||
| * @a value2 Y position 1 | |||
| * @a value3 X position 2 | |||
| * @a valuef Y position 2 | |||
| */ | |||
| ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47, | |||
| } EngineCallbackOpcode; | |||
| @@ -1168,6 +1168,12 @@ public: | |||
| */ | |||
| virtual bool patchbayDisconnect(bool external, uint connectionId); | |||
| /*! | |||
| * Set the position of a group. | |||
| */ | |||
| virtual bool patchbaySetGroupPos(bool sendHost, bool sendOSC, bool external, | |||
| uint groupId, int x1, int y1, int x2, int y2); | |||
| /*! | |||
| * Force the engine to resend all patchbay clients, ports and connections again. | |||
| */ | |||
| @@ -1348,10 +1354,11 @@ protected: | |||
| * Virtual functions for handling patchbay state. | |||
| * Do not free returned data. | |||
| */ | |||
| struct PatchbayPosition { const char* name; int x1, y1, x2, y2; bool dealloc; }; | |||
| virtual const char* const* getPatchbayConnections(bool external) const; | |||
| virtual void restorePatchbayConnection(bool external, | |||
| const char* sourcePort, | |||
| const char* targetPort); | |||
| virtual const PatchbayPosition* getPatchbayPositions(bool external, uint& count) const; | |||
| virtual void restorePatchbayConnection(bool external, const char* sourcePort, const char* targetPort); | |||
| virtual void restorePatchbayGroupPosition(bool external, const PatchbayPosition& ppos); | |||
| /*! | |||
| * Virtual functions for handling external graph ports. | |||
| @@ -550,6 +550,15 @@ CARLA_EXPORT bool carla_patchbay_connect(CarlaHostHandle handle, bool external, | |||
| */ | |||
| CARLA_EXPORT bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint connectionId); | |||
| /*! | |||
| * Set the position of a group. | |||
| * This is purely cached and saved in the project file, Carla backend does nothing with the value. | |||
| * When loading a project, callbacks are used to inform of the previously saved positions. | |||
| * @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED | |||
| */ | |||
| CARLA_EXPORT bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, | |||
| uint groupId, int x1, int y1, int x2, int y2); | |||
| /*! | |||
| * Force the engine to resend all patchbay clients, ports and connections again. | |||
| * @param external Wherever to show external/hardware ports instead of internal ones. | |||
| @@ -970,6 +970,16 @@ bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint conne | |||
| return handle->engine->patchbayDisconnect(external, connectionId); | |||
| } | |||
| bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, uint groupId, int x1, int y1, int x2, int y2) | |||
| { | |||
| CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); | |||
| carla_debug("carla_patchbay_set_group_pos(%p, %s, %u, %i, %i, %i, %i)", | |||
| handle, bool2str(external), groupId, x1, y1, x2, y2); | |||
| return handle->engine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2); | |||
| } | |||
| bool carla_patchbay_refresh(CarlaHostHandle handle, bool external) | |||
| { | |||
| CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); | |||
| @@ -2198,67 +2198,149 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons | |||
| // save internal connections | |||
| if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | |||
| { | |||
| if (const char* const* const patchbayConns = getPatchbayConnections(false)) | |||
| uint posCount = 0; | |||
| const char* const* const patchbayConns = getPatchbayConnections(false); | |||
| const PatchbayPosition* const patchbayPos = getPatchbayPositions(false, posCount); | |||
| if (patchbayConns != nullptr || patchbayPos != nullptr) | |||
| { | |||
| MemoryOutputStream outPatchbay(2048); | |||
| outPatchbay << "\n <Patchbay>\n"; | |||
| for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) | |||
| if (patchbayConns != nullptr) | |||
| { | |||
| const char* const connSource(patchbayConns[i]); | |||
| const char* const connTarget(patchbayConns[i+1]); | |||
| 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'); | |||
| CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); | |||
| CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); | |||
| outPatchbay << " <Connection>\n"; | |||
| outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||
| outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||
| outPatchbay << " </Connection>\n"; | |||
| outPatchbay << " <Connection>\n"; | |||
| outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||
| outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||
| outPatchbay << " </Connection>\n"; | |||
| } | |||
| } | |||
| if (patchbayPos != nullptr && posCount != 0) | |||
| { | |||
| outPatchbay << " <Positions>\n"; | |||
| for (uint i=0; i<posCount; ++i) | |||
| { | |||
| const PatchbayPosition& ppos(patchbayPos[i]); | |||
| CARLA_SAFE_ASSERT_CONTINUE(ppos.name != nullptr && ppos.name[0] != '\0'); | |||
| outPatchbay << " <Position x1=\"" << ppos.x1 << "\" y1=\"" << ppos.y1; | |||
| if (ppos.x2 != 0 || ppos.y2 != 0) | |||
| outPatchbay << "\" x2=\"" << ppos.x2 << "\" y2=\"" << ppos.y2; | |||
| outPatchbay << "\">\n"; | |||
| outPatchbay << " <Name>" << xmlSafeString(ppos.name, true) << "</Name>\n"; | |||
| outPatchbay << " </Position>\n"; | |||
| if (ppos.dealloc) | |||
| delete[] ppos.name; | |||
| } | |||
| outPatchbay << " </Positions>\n"; | |||
| } | |||
| outPatchbay << " </Patchbay>\n"; | |||
| outStream << outPatchbay; | |||
| delete[] patchbayPos; | |||
| } | |||
| } | |||
| // if we're running inside some session-manager (and using JACK), let them handle the connections | |||
| bool saveExternalConnections; | |||
| bool saveExternalConnections, saveExternalPositions = true; | |||
| /**/ if (isPlugin) | |||
| { | |||
| saveExternalConnections = false; | |||
| saveExternalPositions = false; | |||
| } | |||
| else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) | |||
| { | |||
| saveExternalConnections = true; | |||
| } | |||
| else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) | |||
| { | |||
| saveExternalConnections = false; | |||
| } | |||
| else if (std::getenv("LADISH_APP_NAME") != nullptr) | |||
| { | |||
| saveExternalConnections = false; | |||
| } | |||
| else if (std::getenv("NSM_URL") != nullptr) | |||
| { | |||
| saveExternalConnections = false; | |||
| } | |||
| else | |||
| { | |||
| saveExternalConnections = true; | |||
| } | |||
| if (saveExternalConnections) | |||
| if (saveExternalConnections || saveExternalPositions) | |||
| { | |||
| if (const char* const* const patchbayConns = getPatchbayConnections(true)) | |||
| uint posCount = 0; | |||
| const char* const* const patchbayConns = saveExternalConnections | |||
| ? getPatchbayConnections(true) | |||
| : nullptr; | |||
| const PatchbayPosition* const patchbayPos = saveExternalPositions | |||
| ? getPatchbayPositions(true, posCount) | |||
| : nullptr; | |||
| if (patchbayConns != nullptr || patchbayPos != nullptr) | |||
| { | |||
| MemoryOutputStream outPatchbay(2048); | |||
| outPatchbay << "\n <ExternalPatchbay>\n"; | |||
| for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) | |||
| if (patchbayConns != nullptr) | |||
| { | |||
| const char* const connSource(patchbayConns[i]); | |||
| const char* const connTarget(patchbayConns[i+1]); | |||
| 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'); | |||
| CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); | |||
| CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); | |||
| outPatchbay << " <Connection>\n"; | |||
| outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||
| outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||
| outPatchbay << " </Connection>\n"; | |||
| outPatchbay << " <Connection>\n"; | |||
| outPatchbay << " <Source>" << xmlSafeString(connSource, true) << "</Source>\n"; | |||
| outPatchbay << " <Target>" << xmlSafeString(connTarget, true) << "</Target>\n"; | |||
| outPatchbay << " </Connection>\n"; | |||
| } | |||
| } | |||
| if (patchbayPos != nullptr && posCount != 0) | |||
| { | |||
| outPatchbay << " <Positions>\n"; | |||
| for (uint i=0; i<posCount; ++i) | |||
| { | |||
| const PatchbayPosition& ppos(patchbayPos[i]); | |||
| CARLA_SAFE_ASSERT_CONTINUE(ppos.name != nullptr && ppos.name[0] != '\0'); | |||
| outPatchbay << " <Position x1=\"" << ppos.x1 << "\" y1=\"" << ppos.y1; | |||
| if (ppos.x2 != 0 || ppos.y2 != 0) | |||
| outPatchbay << "\" x2=\"" << ppos.x2 << "\" y2=\"" << ppos.y2; | |||
| outPatchbay << "\">\n"; | |||
| outPatchbay << " <Name>" << xmlSafeString(ppos.name, true) << "</Name>\n"; | |||
| outPatchbay << " </Position>\n"; | |||
| if (ppos.dealloc) | |||
| delete[] ppos.name; | |||
| } | |||
| outPatchbay << " </Positions>\n"; | |||
| } | |||
| outPatchbay << " </ExternalPatchbay>\n"; | |||
| @@ -2843,8 +2925,9 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) | |||
| // plus external connections too | |||
| if (loadExternalConnections) | |||
| { | |||
| const bool loadingAsExternal = pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && | |||
| hasInternalConnections; | |||
| bool loadingAsExternal = hasInternalConnections && | |||
| (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || | |||
| pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY); | |||
| for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) | |||
| { | |||
| @@ -2857,7 +2940,11 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) | |||
| continue; | |||
| } | |||
| // or load external patchbay connections | |||
| else if (tagName != "ExternalPatchbay") | |||
| else if (tagName == "ExternalPatchbay") | |||
| { | |||
| loadingAsExternal = true; | |||
| } | |||
| else | |||
| { | |||
| continue; | |||
| } | |||
| @@ -2891,6 +2978,82 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) | |||
| break; | |||
| } | |||
| } | |||
| // finally, we handle positions | |||
| if (XmlElement* const elemPatchbay = xmlElement->getChildByName("Patchbay")) | |||
| { | |||
| if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions")) | |||
| { | |||
| String name; | |||
| PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false }; | |||
| for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) | |||
| { | |||
| const String& patchTag(patchElem->getTagName()); | |||
| if (patchTag != "Position") | |||
| continue; | |||
| XmlElement* const patchName = patchElem->getChildByName("Name"); | |||
| CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr); | |||
| const String nameText(patchName->getAllSubText().trim()); | |||
| name = xmlSafeString(nameText, false); | |||
| ppos.name = name.toRawUTF8(); | |||
| ppos.x1 = patchElem->getIntAttribute("x1"); | |||
| ppos.y1 = patchElem->getIntAttribute("y1"); | |||
| ppos.x2 = patchElem->getIntAttribute("x2"); | |||
| ppos.y2 = patchElem->getIntAttribute("y2"); | |||
| if (name.isNotEmpty()) | |||
| restorePatchbayGroupPosition(false, ppos); | |||
| } | |||
| callback(true, true, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr); | |||
| if (pData->aboutToClose) | |||
| return true; | |||
| if (pData->actionCanceled) | |||
| { | |||
| setLastError("Project load canceled"); | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| if (XmlElement* const elemPatchbay = xmlElement->getChildByName("ExternalPatchbay")) | |||
| { | |||
| if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions")) | |||
| { | |||
| String name; | |||
| PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false }; | |||
| for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) | |||
| { | |||
| const String& patchTag(patchElem->getTagName()); | |||
| if (patchTag != "Position") | |||
| continue; | |||
| XmlElement* const patchName = patchElem->getChildByName("Name"); | |||
| CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr); | |||
| const String nameText(patchName->getAllSubText().trim()); | |||
| name = xmlSafeString(nameText, false); | |||
| ppos.name = name.toRawUTF8(); | |||
| ppos.x1 = patchElem->getIntAttribute("x1"); | |||
| ppos.y1 = patchElem->getIntAttribute("y1"); | |||
| ppos.x2 = patchElem->getIntAttribute("x2"); | |||
| ppos.y2 = patchElem->getIntAttribute("y2"); | |||
| if (name.isNotEmpty()) | |||
| restorePatchbayGroupPosition(true, ppos); | |||
| } | |||
| } | |||
| } | |||
| #endif | |||
| if (pData->options.resetXruns) | |||
| @@ -134,8 +134,12 @@ ExternalGraph::ExternalGraph(CarlaEngine* const engine) noexcept | |||
| : connections(), | |||
| audioPorts(), | |||
| midiPorts(), | |||
| positions(), | |||
| retCon(), | |||
| kEngine(engine) {} | |||
| kEngine(engine) | |||
| { | |||
| carla_zeroStruct(positions); | |||
| } | |||
| void ExternalGraph::clear() noexcept | |||
| { | |||
| @@ -313,11 +317,24 @@ bool ExternalGraph::disconnect(const bool sendHost, const bool sendOSC, | |||
| return false; | |||
| } | |||
| void ExternalGraph::setGroupPos(const bool sendHost, const bool sendOSC, | |||
| const uint groupId, const int x1, const int y1, const int x2, const int y2) | |||
| { | |||
| CARLA_SAFE_ASSERT_UINT_RETURN(groupId >= kExternalGraphGroupCarla && groupId < kExternalGraphGroupMax, groupId,); | |||
| positions[groupId] = { true, x1, y1, x2, y2 }; | |||
| kEngine->callback(sendHost, sendOSC, | |||
| ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, | |||
| groupId, x1, y1, x2, static_cast<float>(y2), | |||
| nullptr); | |||
| } | |||
| void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,); | |||
| const bool isRack(kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK); | |||
| const bool isRack = kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK; | |||
| // Main | |||
| { | |||
| @@ -591,6 +608,39 @@ const char* const* ExternalGraph::getConnections() const noexcept | |||
| return retCon; | |||
| } | |||
| bool ExternalGraph::getGroupFromName(const char* groupName, uint& groupId) const noexcept | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(groupName != nullptr && groupName[0] != '\0', false); | |||
| if (std::strcmp(groupName, "Carla") == 0) | |||
| { | |||
| groupId = kExternalGraphGroupCarla; | |||
| return true; | |||
| } | |||
| if (std::strcmp(groupName, "AudioIn") == 0) | |||
| { | |||
| groupId = kExternalGraphGroupAudioIn; | |||
| return true; | |||
| } | |||
| if (std::strcmp(groupName, "AudioOut") == 0) | |||
| { | |||
| groupId = kExternalGraphGroupAudioOut; | |||
| return true; | |||
| } | |||
| if (std::strcmp(groupName, "MidiIn") == 0) | |||
| { | |||
| groupId = kExternalGraphGroupMidiIn; | |||
| return true; | |||
| } | |||
| if (std::strcmp(groupName, "MidiOut") == 0) | |||
| { | |||
| groupId = kExternalGraphGroupMidiOut; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool ExternalGraph::getGroupAndPortIdFromFullName(const char* const fullPortName, uint& groupId, uint& portId) const noexcept | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(fullPortName != nullptr && fullPortName[0] != '\0', false); | |||
| @@ -1230,16 +1280,19 @@ const String getProcessorFullPortName(AudioProcessor* const proc, const uint32_t | |||
| static inline | |||
| void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* const engine, | |||
| const uint32_t groupId, const int clientId, const AudioProcessor* const proc) | |||
| AudioProcessorGraph::Node* const node, const int pluginId, const AudioProcessor* const proc) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); | |||
| CARLA_SAFE_ASSERT_RETURN(node != nullptr,); | |||
| CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); | |||
| const uint groupId = node->nodeId; | |||
| engine->callback(sendHost, sendOSC, | |||
| ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, | |||
| groupId, | |||
| clientId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE, | |||
| clientId, | |||
| pluginId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE, | |||
| pluginId, | |||
| 0, 0.0f, | |||
| proc->getName().toRawUTF8()); | |||
| @@ -1308,6 +1361,18 @@ void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* con | |||
| 0, 0.0f, | |||
| proc->getOutputChannelName(AudioProcessor::ChannelTypeMIDI, i).toRawUTF8()); | |||
| } | |||
| if (node->properties.contains("x1")) | |||
| { | |||
| engine->callback(sendHost, sendOSC, | |||
| ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, | |||
| groupId, | |||
| node->properties.getWithDefault("x1", 0), | |||
| node->properties.getWithDefault("y1", 0), | |||
| node->properties.getWithDefault("x2", 0), | |||
| static_cast<float>(node->properties.getWithDefault("y2", 0)), | |||
| nullptr); | |||
| } | |||
| } | |||
| static inline | |||
| @@ -1813,7 +1878,7 @@ void PatchbayGraph::addPlugin(CarlaPlugin* const plugin) | |||
| node->properties.set("isPlugin", true); | |||
| node->properties.set("pluginId", static_cast<int>(plugin->getId())); | |||
| addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast<int>(plugin->getId()), instance); | |||
| addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast<int>(plugin->getId()), instance); | |||
| } | |||
| void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) | |||
| @@ -1845,7 +1910,7 @@ void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* con | |||
| node->properties.set("isPlugin", true); | |||
| node->properties.set("pluginId", static_cast<int>(newPlugin->getId())); | |||
| addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast<int>(newPlugin->getId()), instance); | |||
| addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast<int>(newPlugin->getId()), instance); | |||
| } | |||
| void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) | |||
| @@ -2093,6 +2158,26 @@ void PatchbayGraph::disconnectInternalGroup(const uint groupId) noexcept | |||
| } | |||
| } | |||
| void PatchbayGraph::setGroupPos(const bool sendHost, const bool sendOSC, const bool external, | |||
| uint groupId, int x1, int y1, int x2, int y2) | |||
| { | |||
| if (external) | |||
| return extGraph.setGroupPos(sendHost, sendOSC, groupId, x1, y1, x2, y2); | |||
| AudioProcessorGraph::Node* const node(graph.getNodeForId(groupId)); | |||
| CARLA_SAFE_ASSERT_RETURN(node != nullptr,); | |||
| node->properties.set("x1", x1); | |||
| node->properties.set("y1", y1); | |||
| node->properties.set("x2", x2); | |||
| node->properties.set("y2", y2); | |||
| kEngine->callback(sendHost, sendOSC, | |||
| ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, | |||
| groupId, x1, y1, x2, static_cast<float>(y2), | |||
| nullptr); | |||
| } | |||
| void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName) | |||
| { | |||
| if (external) | |||
| @@ -2111,13 +2196,13 @@ void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool | |||
| AudioProcessor* const proc(node->getProcessor()); | |||
| CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | |||
| int clientId = -1; | |||
| int pluginId = -1; | |||
| // plugin node | |||
| if (node->properties.getWithDefault("isPlugin", false) == water::var(true)) | |||
| clientId = node->properties.getWithDefault("pluginId", -1); | |||
| pluginId = node->properties.getWithDefault("pluginId", -1); | |||
| addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, clientId, proc); | |||
| addNodeToPatchbay(sendHost, sendOSC, kEngine, node, pluginId, proc); | |||
| } | |||
| char strBuf[STR_MAX+1]; | |||
| @@ -2212,6 +2297,115 @@ const char* const* PatchbayGraph::getConnections(const bool external) const | |||
| return retCon; | |||
| } | |||
| const CarlaEngine::PatchbayPosition* PatchbayGraph::getPositions(bool external, uint& count) const | |||
| { | |||
| CarlaEngine::PatchbayPosition* ret; | |||
| if (external) | |||
| { | |||
| try { | |||
| ret = new CarlaEngine::PatchbayPosition[kExternalGraphGroupMax]; | |||
| } CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); | |||
| count = 0; | |||
| for (uint i=kExternalGraphGroupCarla; i<kExternalGraphGroupMax; ++i) | |||
| { | |||
| const PatchbayPosition& eppos(extGraph.positions[i]); | |||
| if (! eppos.active) | |||
| continue; | |||
| CarlaEngine::PatchbayPosition& ppos(ret[count++]); | |||
| switch (i) | |||
| { | |||
| case kExternalGraphGroupCarla: | |||
| ppos.name = kEngine->getName(); | |||
| break; | |||
| case kExternalGraphGroupAudioIn: | |||
| ppos.name = "Capture"; | |||
| break; | |||
| case kExternalGraphGroupAudioOut: | |||
| ppos.name = "Playback"; | |||
| break; | |||
| case kExternalGraphGroupMidiIn: | |||
| ppos.name = "Readable MIDI ports"; | |||
| break; | |||
| case kExternalGraphGroupMidiOut: | |||
| ppos.name = "Writable MIDI ports"; | |||
| break; | |||
| } | |||
| ppos.dealloc = false; | |||
| ppos.x1 = eppos.x1; | |||
| ppos.y1 = eppos.y1; | |||
| ppos.x2 = eppos.x2; | |||
| ppos.y2 = eppos.y2; | |||
| } | |||
| return ret; | |||
| } | |||
| else | |||
| { | |||
| const int numNodes = graph.getNumNodes(); | |||
| CARLA_SAFE_ASSERT_RETURN(numNodes > 0, nullptr); | |||
| try { | |||
| ret = new CarlaEngine::PatchbayPosition[numNodes]; | |||
| } CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); | |||
| count = 0; | |||
| for (int i=numNodes; --i >= 0;) | |||
| { | |||
| AudioProcessorGraph::Node* const node(graph.getNode(i)); | |||
| CARLA_SAFE_ASSERT_CONTINUE(node != nullptr); | |||
| if (! node->properties.contains("x1")) | |||
| continue; | |||
| AudioProcessor* const proc(node->getProcessor()); | |||
| CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | |||
| CarlaEngine::PatchbayPosition& ppos(ret[count++]); | |||
| ppos.name = carla_strdup(proc->getName().toRawUTF8()); | |||
| ppos.dealloc = true; | |||
| ppos.x1 = node->properties.getWithDefault("x1", 0); | |||
| ppos.y1 = node->properties.getWithDefault("y1", 0); | |||
| ppos.x2 = node->properties.getWithDefault("x2", 0); | |||
| ppos.y2 = node->properties.getWithDefault("y2", 0); | |||
| } | |||
| return ret; | |||
| } | |||
| } | |||
| bool PatchbayGraph::getGroupFromName(bool external, const char* groupName, uint& groupId) const | |||
| { | |||
| if (external) | |||
| return extGraph.getGroupFromName(groupName, groupId); | |||
| for (int i=0, count=graph.getNumNodes(); i<count; ++i) | |||
| { | |||
| AudioProcessorGraph::Node* const node(graph.getNode(i)); | |||
| CARLA_SAFE_ASSERT_CONTINUE(node != nullptr); | |||
| AudioProcessor* const proc(node->getProcessor()); | |||
| CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); | |||
| if (proc->getName() != groupName) | |||
| continue; | |||
| groupId = node->nodeId; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const | |||
| { | |||
| if (external) | |||
| @@ -2607,8 +2801,6 @@ bool CarlaEngine::patchbayConnect(const bool external, | |||
| return graph->connect(external, groupA, portA, groupB, portB); | |||
| } | |||
| return false; | |||
| } | |||
| bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionId) | |||
| @@ -2631,8 +2823,28 @@ bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionI | |||
| return graph->disconnect(external, connectionId); | |||
| } | |||
| } | |||
| return false; | |||
| bool CarlaEngine::patchbaySetGroupPos(const bool sendHost, const bool sendOSC, const bool external, | |||
| const uint groupId, const int x1, const int y1, const int x2, const int y2) | |||
| { | |||
| 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::patchbaySetGroupPos(%u, %i, %i, %i, %i)", groupId, x1, y1, x2, y2); | |||
| if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) | |||
| { | |||
| // we don't bother to save position in this case, there is only midi in/out | |||
| return true; | |||
| } | |||
| else | |||
| { | |||
| PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); | |||
| CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); | |||
| graph->setGroupPos(sendHost, sendOSC, external, groupId, x1, y1, x2, y2); | |||
| return true; | |||
| } | |||
| } | |||
| bool CarlaEngine::patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) | |||
| @@ -2687,6 +2899,22 @@ const char* const* CarlaEngine::getPatchbayConnections(const bool external) cons | |||
| return nullptr; | |||
| } | |||
| const CarlaEngine::PatchbayPosition* CarlaEngine::getPatchbayPositions(bool external, uint& count) const | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), nullptr); | |||
| carla_debug("CarlaEngine::getPatchbayPositions(%s)", bool2str(external)); | |||
| if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | |||
| { | |||
| PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); | |||
| CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr); | |||
| return graph->getPositions(external, count); | |||
| } | |||
| return nullptr; | |||
| } | |||
| void CarlaEngine::restorePatchbayConnection(const bool external, | |||
| const char* const sourcePort, | |||
| const char* const targetPort) | |||
| @@ -2726,6 +2954,23 @@ void CarlaEngine::restorePatchbayConnection(const bool external, | |||
| } | |||
| } | |||
| void CarlaEngine::restorePatchbayGroupPosition(const bool external, const PatchbayPosition& ppos) | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(),); | |||
| CARLA_SAFE_ASSERT_RETURN(ppos.name != nullptr && ppos.name[0] != '\0',); | |||
| if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) | |||
| { | |||
| PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); | |||
| CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); | |||
| uint groupId; | |||
| CARLA_SAFE_ASSERT_RETURN(graph->getGroupFromName(external, ppos.name, groupId),); | |||
| graph->setGroupPos(true, true, external, groupId, ppos.x1, ppos.y1, ppos.x2, ppos.y2); | |||
| } | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) | |||
| @@ -33,6 +33,13 @@ using water::MidiBuffer; | |||
| CARLA_BACKEND_START_NAMESPACE | |||
| // ----------------------------------------------------------------------- | |||
| struct PatchbayPosition { | |||
| bool active; | |||
| int x1, y1, x2, y2; | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| // External Graph stuff | |||
| @@ -80,6 +87,7 @@ struct ExternalGraphPorts { | |||
| struct ExternalGraph { | |||
| PatchbayConnectionList connections; | |||
| ExternalGraphPorts audioPorts, midiPorts; | |||
| PatchbayPosition positions[kExternalGraphGroupMax]; | |||
| mutable CharStringListPtr retCon; | |||
| ExternalGraph(CarlaEngine* engine) noexcept; | |||
| @@ -89,10 +97,13 @@ struct ExternalGraph { | |||
| uint groupA, uint portA, uint groupB, uint portB) noexcept; | |||
| bool disconnect(bool sendHost, bool sendOSC, | |||
| uint connectionId) noexcept; | |||
| void setGroupPos(bool sendHost, bool sendOSC, | |||
| uint groupId, int x1, int y1, int x2, int y2); | |||
| void refresh(bool sendHost, bool sendOSC, | |||
| const char* deviceName); | |||
| const char* const* getConnections() const noexcept; | |||
| bool getGroupFromName(const char* groupName, uint& groupId) const noexcept; | |||
| bool getGroupAndPortIdFromFullName(const char* fullPortName, uint& groupId, uint& portId) const noexcept; | |||
| CarlaEngine* const kEngine; | |||
| @@ -190,9 +201,12 @@ public: | |||
| bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB); | |||
| bool disconnect(bool external, uint connectionId); | |||
| void disconnectInternalGroup(uint groupId) noexcept; | |||
| void setGroupPos(bool sendHost, bool sendOsc, bool external, uint groupId, int x1, int y1, int x2, int y2); | |||
| void refresh(bool sendHost, bool sendOsc, bool external, const char* deviceName); | |||
| const char* const* getConnections(bool external) const; | |||
| const CarlaEngine::PatchbayPosition* getPositions(bool external, uint& count) const; | |||
| bool getGroupFromName(bool external, const char* groupName, uint& groupId) const; | |||
| bool getGroupAndPortIdFromFullName(bool external, const char* fullPortName, uint& groupId, uint& portId) const; | |||
| void process(CarlaEngine::ProtectedData* data, | |||
| @@ -1816,6 +1816,22 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept | |||
| ok = fEngine->patchbayDisconnect(external, connectionId); | |||
| } CARLA_SAFE_EXCEPTION("patchbayDisconnect"); | |||
| } | |||
| else if (std::strcmp(msg, "patchbay_set_group_pos") == 0) | |||
| { | |||
| bool external; | |||
| uint32_t groupId; | |||
| int x1, y1, x2, y2; | |||
| CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(external), true); | |||
| CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(groupId), true); | |||
| CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x1), true); | |||
| CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y1), true); | |||
| CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x2), true); | |||
| CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y2), true); | |||
| try { | |||
| ok = fEngine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2); | |||
| } CARLA_SAFE_EXCEPTION("patchbaySetGroupPos"); | |||
| } | |||
| else if (std::strcmp(msg, "patchbay_refresh") == 0) | |||
| { | |||
| bool external; | |||
| @@ -818,6 +818,14 @@ ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED = 45 | |||
| # @a valueStr New mapped range as "%f:%f" syntax | |||
| ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 | |||
| # A patchbay client position has changed. | |||
| # @a pluginId Client Id | |||
| # @a value1 X position 1 | |||
| # @a value2 Y position 1 | |||
| # @a value3 X position 2 | |||
| # @a valuef Y position 2 | |||
| ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47 | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # NSM Callback Opcode | |||
| # NSM callback opcodes. | |||
| @@ -1678,6 +1686,14 @@ class CarlaHostMeta(object): | |||
| def patchbay_disconnect(self, external, connectionId): | |||
| raise NotImplementedError | |||
| # Set the position of a group. | |||
| # This is purely cached and saved in the project file, Carla backend does nothing with the value. | |||
| # When loading a project, callbacks are used to inform of the previously saved positions. | |||
| # @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED | |||
| @abstractmethod | |||
| def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||
| raise NotImplementedError | |||
| # Force the engine to resend all patchbay clients, ports and connections again. | |||
| # @param external Wherever to show external/hardware ports instead of internal ones. | |||
| # Only valid in patchbay engine mode, other modes will ignore this. | |||
| @@ -2306,6 +2322,9 @@ class CarlaHostNull(CarlaHostMeta): | |||
| def patchbay_disconnect(self, external, connectionId): | |||
| return False | |||
| def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||
| return False | |||
| def patchbay_refresh(self, external): | |||
| return False | |||
| @@ -2624,6 +2643,9 @@ class CarlaHostDLL(CarlaHostMeta): | |||
| self.lib.carla_patchbay_disconnect.argtypes = (c_void_p, c_bool, c_uint) | |||
| self.lib.carla_patchbay_disconnect.restype = c_bool | |||
| self.lib.carla_patchbay_set_group_pos.argtypes = (c_void_p, c_bool, c_uint, c_int, c_int, c_int, c_int) | |||
| self.lib.carla_patchbay_set_group_pos.restype = c_bool | |||
| self.lib.carla_patchbay_refresh.argtypes = (c_void_p, c_bool) | |||
| self.lib.carla_patchbay_refresh.restype = c_bool | |||
| @@ -2933,6 +2955,9 @@ class CarlaHostDLL(CarlaHostMeta): | |||
| def patchbay_disconnect(self, external, connectionId): | |||
| return bool(self.lib.carla_patchbay_disconnect(self.handle, external, connectionId)) | |||
| def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||
| return bool(self.lib.carla_patchbay_set_group_pos(self.handle, external, groupId, x1, y1, x2, y2)) | |||
| def patchbay_refresh(self, external): | |||
| return bool(self.lib.carla_patchbay_refresh(self.handle, external)) | |||
| @@ -3325,6 +3350,9 @@ class CarlaHostPlugin(CarlaHostMeta): | |||
| def patchbay_disconnect(self, external, connectionId): | |||
| return self.sendMsgAndSetError(["patchbay_disconnect", external, connectionId]) | |||
| def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): | |||
| return self.sendMsgAndSetError(["patchbay_set_group_pos", external, groupId, x1, y1, x2, y2]) | |||
| def patchbay_refresh(self, external): | |||
| return self.sendMsgAndSetError(["patchbay_refresh", external]) | |||
| @@ -56,6 +56,7 @@ class CarlaHostSignals(QObject): | |||
| PatchbayClientRemovedCallback = pyqtSignal(int) | |||
| PatchbayClientRenamedCallback = pyqtSignal(int, str) | |||
| PatchbayClientDataChangedCallback = pyqtSignal(int, int, int) | |||
| PatchbayClientPositionChangedCallback = pyqtSignal(int, int, int, int, int) | |||
| PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str) | |||
| PatchbayPortRemovedCallback = pyqtSignal(int, int) | |||
| PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str) | |||
| @@ -505,7 +505,6 @@ class HostWindow(QMainWindow): | |||
| self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) | |||
| self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) | |||
| self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) | |||
| self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved) | |||
| self.scene.pluginSelected.connect(self.slot_canvasPluginSelected) | |||
| self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged) | |||
| @@ -533,6 +532,7 @@ class HostWindow(QMainWindow): | |||
| host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback) | |||
| host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback) | |||
| host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback) | |||
| host.PatchbayClientPositionChangedCallback.connect(self.slot_handlePatchbayClientPositionChangedCallback) | |||
| host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback) | |||
| host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback) | |||
| host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback) | |||
| @@ -713,9 +713,6 @@ class HostWindow(QMainWindow): | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # Files | |||
| def makeExtraFilename(self): | |||
| return self.fProjectFilename.rsplit(".",1)[0]+".json" | |||
| def loadProjectNow(self): | |||
| if not self.fProjectFilename: | |||
| return qCritical("ERROR: loading project without filename set") | |||
| @@ -748,14 +745,6 @@ class HostWindow(QMainWindow): | |||
| QMessageBox.Ok, QMessageBox.Ok) | |||
| return | |||
| if not self.fWithCanvas: | |||
| return | |||
| with open(self.makeExtraFilename(), 'w') as fh: | |||
| json.dump({ | |||
| 'canvas': patchcanvas.saveGroupPositions(), | |||
| }, fh) | |||
| def projectLoadingStarted(self): | |||
| self.ui.rack.setEnabled(False) | |||
| self.ui.graphicsView.setEnabled(False) | |||
| @@ -769,7 +758,7 @@ class HostWindow(QMainWindow): | |||
| QTimer.singleShot(1000, self.slot_canvasRefresh) | |||
| extrafile = self.makeExtraFilename() | |||
| extrafile = self.fProjectFilename.rsplit(".",1)[0]+".json" | |||
| if not os.path.exists(extrafile): | |||
| return | |||
| @@ -1431,7 +1420,7 @@ class HostWindow(QMainWindow): | |||
| pFeatures.group_rename = False | |||
| pFeatures.port_info = False | |||
| pFeatures.port_rename = False | |||
| pFeatures.handle_group_pos = True | |||
| pFeatures.handle_group_pos = False | |||
| patchcanvas.setOptions(pOptions) | |||
| patchcanvas.setFeatures(pFeatures) | |||
| @@ -1567,10 +1556,6 @@ class HostWindow(QMainWindow): | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # Canvas (canvas callbacks) | |||
| @pyqtSlot(int, int, QPointF) | |||
| def slot_canvasItemMoved(self, group_id, split_mode, pos): | |||
| self.updateMiniCanvasLater() | |||
| @pyqtSlot() | |||
| def slot_canvasSelectionChanged(self): | |||
| self.updateMiniCanvasLater() | |||
| @@ -1668,6 +1653,10 @@ class HostWindow(QMainWindow): | |||
| patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) | |||
| @pyqtSlot(int, int, int, int, int) | |||
| def slot_handlePatchbayClientPositionChangedCallback(self, clientId, x1, y1, x2, y2): | |||
| patchcanvas.setGroupPosFull(clientId, x1, y1, x2, y2) | |||
| @pyqtSlot(int, int, int, int, str) | |||
| def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName): | |||
| if portFlags & PATCHBAY_PORT_IS_INPUT: | |||
| @@ -2720,6 +2709,9 @@ class HostWindow(QMainWindow): | |||
| def canvasCallback(action, value1, value2, valueStr): | |||
| host = gCarla.gui.host | |||
| if gCarla.gui.fCustomStopAction == HostWindow.CUSTOM_ACTION_APP_CLOSE: | |||
| return | |||
| if action == patchcanvas.ACTION_GROUP_INFO: | |||
| pass | |||
| @@ -2736,6 +2728,14 @@ def canvasCallback(action, value1, value2, valueStr): | |||
| patchcanvas.joinGroup(groupId) | |||
| gCarla.gui.updateMiniCanvasLater() | |||
| elif action == patchcanvas.ACTION_GROUP_POSITION: | |||
| if gCarla.gui.fIsProjectLoading or not host.is_engine_running(): | |||
| return | |||
| groupId = value1 | |||
| x1, y1, x2, y2 = tuple(int(i) for i in valueStr.split(":")) | |||
| host.patchbay_set_group_pos(gCarla.gui.fExternalPatchbay, groupId, x1, y1, x2, y2) | |||
| gCarla.gui.updateMiniCanvasLater() | |||
| elif action == patchcanvas.ACTION_PORT_INFO: | |||
| pass | |||
| @@ -2743,7 +2743,7 @@ def canvasCallback(action, value1, value2, valueStr): | |||
| pass | |||
| elif action == patchcanvas.ACTION_PORTS_CONNECT: | |||
| gOut, pOut, gIn, pIn = [int(i) for i in valueStr.split(":")] | |||
| gOut, pOut, gIn, pIn = tuple(int(i) for i in valueStr.split(":")) | |||
| if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn): | |||
| print("Connection failed:", host.get_last_error()) | |||
| @@ -2875,6 +2875,8 @@ def engineCallback(host, action, pluginId, value1, value2, value3, valuef, value | |||
| host.PatchbayClientRenamedCallback.emit(pluginId, valueStr) | |||
| elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED: | |||
| host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2) | |||
| elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: | |||
| host.PatchbayClientPositionChangedCallback.emit(pluginId, value1, value2, value3, int(round(valuef))) | |||
| elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED: | |||
| host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr) | |||
| elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED: | |||
| @@ -49,18 +49,19 @@ ACTION_GROUP_INFO = 0 # group_id, N, N | |||
| ACTION_GROUP_RENAME = 1 # group_id, N, N | |||
| ACTION_GROUP_SPLIT = 2 # group_id, N, N | |||
| ACTION_GROUP_JOIN = 3 # group_id, N, N | |||
| ACTION_PORT_INFO = 4 # group_id, port_id, N | |||
| ACTION_PORT_RENAME = 5 # group_id, port_id, N | |||
| ACTION_PORTS_CONNECT = 6 # N, N, "outG:outP:inG:inP" | |||
| ACTION_PORTS_DISCONNECT = 7 # conn_id, N, N | |||
| ACTION_PLUGIN_CLONE = 8 # plugin_id, N, N | |||
| ACTION_PLUGIN_EDIT = 9 # plugin_id, N, N | |||
| ACTION_PLUGIN_RENAME = 10 # plugin_id, N, N | |||
| ACTION_PLUGIN_REPLACE = 11 # plugin_id, N, N | |||
| ACTION_PLUGIN_REMOVE = 12 # plugin_id, N, N | |||
| ACTION_PLUGIN_SHOW_UI = 13 # plugin_id, N, N | |||
| ACTION_BG_RIGHT_CLICK = 14 # N, N, N | |||
| ACTION_INLINE_DISPLAY = 15 # plugin_id, N, N | |||
| ACTION_GROUP_POSITION = 4 # group_id, N, N, "x1:y1:x2:y2" | |||
| ACTION_PORT_INFO = 5 # group_id, port_id, N | |||
| ACTION_PORT_RENAME = 6 # group_id, port_id, N | |||
| ACTION_PORTS_CONNECT = 7 # N, N, "outG:outP:inG:inP" | |||
| ACTION_PORTS_DISCONNECT = 8 # conn_id, N, N | |||
| ACTION_PLUGIN_CLONE = 9 # plugin_id, N, N | |||
| ACTION_PLUGIN_EDIT = 10 # plugin_id, N, N | |||
| ACTION_PLUGIN_RENAME = 11 # plugin_id, N, N | |||
| ACTION_PLUGIN_REPLACE = 12 # plugin_id, N, N | |||
| ACTION_PLUGIN_REMOVE = 13 # plugin_id, N, N | |||
| ACTION_PLUGIN_SHOW_UI = 14 # plugin_id, N, N | |||
| ACTION_BG_RIGHT_CLICK = 15 # N, N, N | |||
| ACTION_INLINE_DISPLAY = 16 # plugin_id, N, N | |||
| # Icon | |||
| ICON_APPLICATION = 0 | |||
| @@ -134,6 +135,7 @@ class Canvas(object): | |||
| self.connection_list = [] | |||
| self.animation_list = [] | |||
| self.group_plugin_map = {} | |||
| self.old_group_pos = {} | |||
| self.callback = self.callback | |||
| self.debug = False | |||
| @@ -22,9 +22,9 @@ | |||
| from sip import voidptr | |||
| from struct import pack | |||
| from PyQt5.QtCore import qCritical, Qt, QPointF, QRectF, QTimer | |||
| from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer | |||
| from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen | |||
| from PyQt5.QtWidgets import QGraphicsItem, QMenu | |||
| from PyQt5.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Imports (Custom) | |||
| @@ -75,13 +75,17 @@ class cb_line_t(object): | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| class CanvasBox(QGraphicsItem): | |||
| class CanvasBox(QGraphicsObject): | |||
| # signals | |||
| positionChanged = pyqtSignal(int, bool, int, int) | |||
| # enums | |||
| INLINE_DISPLAY_DISABLED = 0 | |||
| INLINE_DISPLAY_ENABLED = 1 | |||
| INLINE_DISPLAY_CACHED = 2 | |||
| def __init__(self, group_id, group_name, icon, parent=None): | |||
| QGraphicsItem.__init__(self) | |||
| QGraphicsObject.__init__(self) | |||
| self.setParentItem(parent) | |||
| # Save Variables, useful for later | |||
| @@ -109,6 +113,7 @@ class CanvasBox(QGraphicsItem): | |||
| self.m_inline_data = None | |||
| self.m_inline_image = None | |||
| self.m_inline_scaling = 1.0 | |||
| self.m_will_signal_pos_change = False | |||
| self.m_port_list_ids = [] | |||
| self.m_connection_lines = [] | |||
| @@ -153,6 +158,10 @@ class CanvasBox(QGraphicsItem): | |||
| self.updatePositions() | |||
| self.visibleChanged.connect(self.slot_signalPositionChangedLater) | |||
| self.xChanged.connect(self.slot_signalPositionChangedLater) | |||
| self.yChanged.connect(self.slot_signalPositionChangedLater) | |||
| canvas.scene.addItem(self) | |||
| QTimer.singleShot(0, self.fixPos) | |||
| @@ -270,21 +279,31 @@ class CanvasBox(QGraphicsItem): | |||
| return | |||
| qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id) | |||
| def checkItemPos(self): | |||
| if not canvas.size_rect.isNull(): | |||
| pos = self.scenePos() | |||
| if not (canvas.size_rect.contains(pos) and | |||
| canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))): | |||
| if pos.x() < canvas.size_rect.x(): | |||
| self.setPos(canvas.size_rect.x(), pos.y()) | |||
| elif pos.x() + self.p_width > canvas.size_rect.width(): | |||
| self.setPos(canvas.size_rect.width() - self.p_width, pos.y()) | |||
| pos = self.scenePos() | |||
| if pos.y() < canvas.size_rect.y(): | |||
| self.setPos(pos.x(), canvas.size_rect.y()) | |||
| elif pos.y() + self.p_height > canvas.size_rect.height(): | |||
| self.setPos(pos.x(), canvas.size_rect.height() - self.p_height) | |||
| def checkItemPos(self, blockSignals): | |||
| if canvas.size_rect.isNull(): | |||
| return | |||
| pos = self.scenePos() | |||
| if (canvas.size_rect.contains(pos) and | |||
| canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))): | |||
| return | |||
| if blockSignals: | |||
| self.blockSignals(True) | |||
| if pos.x() < canvas.size_rect.x(): | |||
| self.setPos(canvas.size_rect.x(), pos.y()) | |||
| elif pos.x() + self.p_width > canvas.size_rect.width(): | |||
| self.setPos(canvas.size_rect.width() - self.p_width, pos.y()) | |||
| pos = self.scenePos() | |||
| if pos.y() < canvas.size_rect.y(): | |||
| self.setPos(pos.x(), canvas.size_rect.y()) | |||
| elif pos.y() + self.p_height > canvas.size_rect.height(): | |||
| self.setPos(pos.x(), canvas.size_rect.height() - self.p_height) | |||
| if blockSignals: | |||
| self.blockSignals(False) | |||
| def removeIconFromScene(self): | |||
| if self.icon_svg is None: | |||
| @@ -389,6 +408,17 @@ class CanvasBox(QGraphicsItem): | |||
| connection.widget.setZValue(z_value) | |||
| def triggerSignalPositionChanged(self): | |||
| self.positionChanged.emit(self.m_group_id, self.m_splitted, self.x(), self.y()) | |||
| self.m_will_signal_pos_change = False | |||
| @pyqtSlot() | |||
| def slot_signalPositionChangedLater(self): | |||
| if self.m_will_signal_pos_change: | |||
| return | |||
| self.m_will_signal_pos_change = True | |||
| QTimer.singleShot(0, self.triggerSignalPositionChanged) | |||
| def type(self): | |||
| return CanvasBoxType | |||
| @@ -570,14 +600,14 @@ class CanvasBox(QGraphicsItem): | |||
| event.accept() | |||
| canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "") | |||
| return | |||
| QGraphicsItem.keyPressEvent(self, event) | |||
| QGraphicsObject.keyPressEvent(self, event) | |||
| def hoverEnterEvent(self, event): | |||
| if options.auto_select_items: | |||
| if len(canvas.scene.selectedItems()) > 0: | |||
| canvas.scene.clearSelection() | |||
| self.setSelected(True) | |||
| QGraphicsItem.hoverEnterEvent(self, event) | |||
| QGraphicsObject.hoverEnterEvent(self, event) | |||
| def mouseDoubleClickEvent(self, event): | |||
| if self.m_plugin_id >= 0: | |||
| @@ -585,7 +615,7 @@ class CanvasBox(QGraphicsItem): | |||
| canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "") | |||
| return | |||
| QGraphicsItem.mouseDoubleClickEvent(self, event) | |||
| QGraphicsObject.mouseDoubleClickEvent(self, event) | |||
| def mousePressEvent(self, event): | |||
| canvas.last_z_value += 1 | |||
| @@ -612,7 +642,7 @@ class CanvasBox(QGraphicsItem): | |||
| else: | |||
| self.m_mouse_down = False | |||
| QGraphicsItem.mousePressEvent(self, event) | |||
| QGraphicsObject.mousePressEvent(self, event) | |||
| def mouseMoveEvent(self, event): | |||
| if self.m_mouse_down: | |||
| @@ -620,7 +650,7 @@ class CanvasBox(QGraphicsItem): | |||
| self.setCursor(QCursor(Qt.SizeAllCursor)) | |||
| self.m_cursor_moving = True | |||
| self.repaintLines() | |||
| QGraphicsItem.mouseMoveEvent(self, event) | |||
| QGraphicsObject.mouseMoveEvent(self, event) | |||
| def mouseReleaseEvent(self, event): | |||
| if self.m_cursor_moving: | |||
| @@ -628,11 +658,13 @@ class CanvasBox(QGraphicsItem): | |||
| QTimer.singleShot(0, self.fixPos) | |||
| self.m_mouse_down = False | |||
| self.m_cursor_moving = False | |||
| QGraphicsItem.mouseReleaseEvent(self, event) | |||
| QGraphicsObject.mouseReleaseEvent(self, event) | |||
| def fixPos(self): | |||
| self.blockSignals(True) | |||
| self.setX(round(self.x())) | |||
| self.setY(round(self.y())) | |||
| self.blockSignals(False) | |||
| def boundingRect(self): | |||
| return QRectF(0, 0, self.p_width, self.p_height) | |||
| @@ -138,6 +138,36 @@ class CanvasObject(QObject): | |||
| CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "") | |||
| @pyqtSlot(int, bool, int, int) | |||
| def boxPositionChanged(self, groupId, split, x, y): | |||
| x2 = y2 = 0 | |||
| if split: | |||
| for group in canvas.group_list: | |||
| if group.group_id == groupId: | |||
| if group.split: | |||
| pos = group.widgets[1].pos() | |||
| x2 = pos.x() | |||
| y2 = pos.y() | |||
| break | |||
| valueStr = "%i:%i:%i:%i" % (x, y, x2, y2) | |||
| CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr) | |||
| @pyqtSlot(int, bool, int, int) | |||
| def sboxPositionChanged(self, groupId, split, x2, y2): | |||
| x = y = 0 | |||
| for group in canvas.group_list: | |||
| if group.group_id == groupId: | |||
| pos = group.widgets[0].pos() | |||
| x = pos.x() | |||
| y = pos.y() | |||
| break | |||
| valueStr = "%i:%i:%i:%i" % (x, y, x2, y2) | |||
| CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr) | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| def getStoredCanvasPosition(key, fallback_pos): | |||
| @@ -201,11 +231,17 @@ def clear(): | |||
| if canvas.debug: | |||
| print("PatchCanvas::clear()") | |||
| group_pos = {} | |||
| group_list_ids = [] | |||
| port_list_ids = [] | |||
| connection_list_ids = [] | |||
| for group in canvas.group_list: | |||
| group_pos[group.group_name] = ( | |||
| group.split, | |||
| group.widgets[0].pos(), | |||
| group.widgets[1].pos() if group.split else None, | |||
| ) | |||
| group_list_ids.append(group.group_id) | |||
| for port in canvas.port_list: | |||
| @@ -230,6 +266,7 @@ def clear(): | |||
| canvas.port_list = [] | |||
| canvas.connection_list = [] | |||
| canvas.group_plugin_map = {} | |||
| canvas.old_group_pos = group_pos | |||
| canvas.scene.clearSelection() | |||
| @@ -278,6 +315,8 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||
| group_id, group_name.encode(), split2str(split), icon2str(icon))) | |||
| return | |||
| old_matching_group = canvas.old_group_pos.pop(group_name, None) | |||
| if split == SPLIT_UNDEF: | |||
| isHardware = bool(icon == ICON_HARDWARE) | |||
| @@ -285,8 +324,11 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||
| split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split) | |||
| elif isHardware: | |||
| split = SPLIT_YES | |||
| elif old_matching_group is not None and old_matching_group[0]: | |||
| split = SPLIT_YES | |||
| group_box = CanvasBox(group_id, group_name, icon) | |||
| group_box.positionChanged.connect(canvas.qobject.boxPositionChanged) | |||
| group_dict = group_dict_t() | |||
| group_dict.group_id = group_id | |||
| @@ -301,20 +343,29 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||
| if split == SPLIT_YES: | |||
| group_box.setSplit(True, PORT_MODE_OUTPUT) | |||
| group_box.blockSignals(True) | |||
| if features.handle_group_pos: | |||
| group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False))) | |||
| elif old_matching_group is not None: | |||
| group_box.setPos(old_matching_group[1]) | |||
| else: | |||
| group_box.setPos(CanvasGetNewGroupPos(False)) | |||
| group_box.blockSignals(False) | |||
| group_sbox = CanvasBox(group_id, group_name, icon) | |||
| group_sbox.setSplit(True, PORT_MODE_INPUT) | |||
| group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged) | |||
| group_dict.widgets[1] = group_sbox | |||
| group_sbox.blockSignals(True) | |||
| if features.handle_group_pos: | |||
| group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True))) | |||
| elif old_matching_group is not None and old_matching_group[0]: | |||
| group_sbox.setPos(old_matching_group[2]) | |||
| else: | |||
| group_sbox.setPos(CanvasGetNewGroupPos(True)) | |||
| group_sbox.blockSignals(False) | |||
| canvas.last_z_value += 1 | |||
| group_sbox.setZValue(canvas.last_z_value) | |||
| @@ -322,19 +373,21 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): | |||
| if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups: | |||
| CanvasItemFX(group_sbox, True, False) | |||
| group_sbox.checkItemPos() | |||
| group_sbox.checkItemPos(True) | |||
| else: | |||
| group_box.setSplit(False) | |||
| if features.handle_group_pos: | |||
| group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False))) | |||
| elif old_matching_group is not None: | |||
| group_box.setPos(old_matching_group[1]) | |||
| else: | |||
| # Special ladish fake-split groups | |||
| horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM) | |||
| group_box.setPos(CanvasGetNewGroupPos(horizontal)) | |||
| group_box.checkItemPos() | |||
| group_box.checkItemPos(True) | |||
| canvas.last_z_value += 1 | |||
| group_box.setZValue(canvas.last_z_value) | |||
| @@ -630,10 +683,14 @@ def restoreGroupPositions(dataList): | |||
| if group is None: | |||
| continue | |||
| group.widgets[0].blockSignals(True) | |||
| group.widgets[0].setPos(data['pos1x'], data['pos1y']) | |||
| group.widgets[0].blockSignals(False) | |||
| if group.split and group.widgets[1]: | |||
| group.widgets[1].blockSignals(True) | |||
| group.widgets[1].setPos(data['pos2x'], data['pos2y']) | |||
| group.widgets[1].blockSignals(False) | |||
| def setGroupPos(group_id, group_pos_x, group_pos_y): | |||
| setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y) | |||
| @@ -645,10 +702,14 @@ def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group | |||
| for group in canvas.group_list: | |||
| if group.group_id == group_id: | |||
| group.widgets[0].blockSignals(True) | |||
| group.widgets[0].setPos(group_pos_x_o, group_pos_y_o) | |||
| group.widgets[0].blockSignals(False) | |||
| if group.split and group.widgets[1]: | |||
| group.widgets[1].blockSignals(True) | |||
| group.widgets[1].setPos(group_pos_x_i, group_pos_y_i) | |||
| group.widgets[1].blockSignals(False) | |||
| QTimer.singleShot(0, canvas.scene.update) | |||
| return | |||
| @@ -58,7 +58,6 @@ class RubberbandRect(QGraphicsRectItem): | |||
| class PatchScene(QGraphicsScene): | |||
| scaleChanged = pyqtSignal(float) | |||
| sceneGroupMoved = pyqtSignal(int, int, QPointF) | |||
| pluginSelected = pyqtSignal(list) | |||
| def __init__(self, parent, view): | |||
| @@ -367,8 +366,7 @@ class PatchScene(QGraphicsScene): | |||
| items_list = self.selectedItems() | |||
| for item in items_list: | |||
| if item and item.isVisible() and item.type() == CanvasBoxType: | |||
| item.checkItemPos() | |||
| self.sceneGroupMoved.emit(item.getGroupId(), item.getSplittedMode(), item.scenePos()) | |||
| item.checkItemPos(False) | |||
| if len(items_list) > 1: | |||
| canvas.scene.update() | |||
| @@ -315,6 +315,8 @@ const char* EngineCallbackOpcode2Str(const EngineCallbackOpcode opcode) noexcept | |||
| return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED"; | |||
| case ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: | |||
| return "ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED"; | |||
| case ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: | |||
| return "ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED"; | |||
| } | |||
| carla_stderr("CarlaBackend::EngineCallbackOpcode2Str(%i) - invalid opcode", opcode); | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * Carla patchbay utils | |||
| * Copyright (C) 2011-2017 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| @@ -27,7 +27,7 @@ | |||
| struct GroupNameToId { | |||
| uint group; | |||
| char name[STR_MAX+1]; // globally unique | |||
| char name[STR_MAX]; // globally unique | |||
| void clear() noexcept | |||
| { | |||
| @@ -43,15 +43,15 @@ struct GroupNameToId { | |||
| void rename(const char n[]) noexcept | |||
| { | |||
| std::strncpy(name, n, STR_MAX); | |||
| name[STR_MAX] = '\0'; | |||
| std::strncpy(name, n, STR_MAX-1); | |||
| name[STR_MAX-1] = '\0'; | |||
| } | |||
| bool operator==(const GroupNameToId& groupNameToId) const noexcept | |||
| { | |||
| if (groupNameToId.group != group) | |||
| return false; | |||
| if (std::strncmp(groupNameToId.name, name, STR_MAX) != 0) | |||
| if (std::strncmp(groupNameToId.name, name, STR_MAX-1) != 0) | |||
| return false; | |||
| return true; | |||
| } | |||
| @@ -65,10 +65,12 @@ struct GroupNameToId { | |||
| struct PatchbayGroupList { | |||
| uint lastId; | |||
| LinkedList<GroupNameToId> list; | |||
| CarlaMutex mutex; | |||
| PatchbayGroupList() noexcept | |||
| : lastId(0), | |||
| list() {} | |||
| list(), | |||
| mutex() {} | |||
| void clear() noexcept | |||
| { | |||
| @@ -87,8 +89,8 @@ struct PatchbayGroupList { | |||
| struct PortNameToId { | |||
| uint group; | |||
| uint port; | |||
| char name[STR_MAX+1]; // locally unique (within the same group) | |||
| char fullName[STR_MAX+1]; // globally unique | |||
| char name[STR_MAX]; // locally unique (within the same group) | |||
| char fullName[STR_MAX]; // globally unique | |||
| void clear() noexcept | |||
| { | |||
| @@ -107,26 +109,26 @@ struct PortNameToId { | |||
| void setFullName(const char fn[]) noexcept | |||
| { | |||
| std::strncpy(fullName, fn, STR_MAX); | |||
| fullName[STR_MAX] = '\0'; | |||
| std::strncpy(fullName, fn, STR_MAX-1); | |||
| fullName[STR_MAX-1] = '\0'; | |||
| } | |||
| void rename(const char n[], const char fn[]) noexcept | |||
| { | |||
| std::strncpy(name, n, STR_MAX); | |||
| name[STR_MAX] = '\0'; | |||
| std::strncpy(name, n, STR_MAX-1); | |||
| name[STR_MAX-1] = '\0'; | |||
| std::strncpy(fullName, fn, STR_MAX); | |||
| fullName[STR_MAX] = '\0'; | |||
| std::strncpy(fullName, fn, STR_MAX-1); | |||
| fullName[STR_MAX-1] = '\0'; | |||
| } | |||
| bool operator==(const PortNameToId& portNameToId) noexcept | |||
| { | |||
| if (portNameToId.group != group || portNameToId.port != port) | |||
| return false; | |||
| if (std::strncmp(portNameToId.name, name, STR_MAX) != 0) | |||
| if (std::strncmp(portNameToId.name, name, STR_MAX-1) != 0) | |||
| return false; | |||
| if (std::strncmp(portNameToId.fullName, fullName, STR_MAX) != 0) | |||
| if (std::strncmp(portNameToId.fullName, fullName, STR_MAX-1) != 0) | |||
| return false; | |||
| return true; | |||
| } | |||
| @@ -140,10 +142,12 @@ struct PortNameToId { | |||
| struct PatchbayPortList { | |||
| uint lastId; | |||
| LinkedList<PortNameToId> list; | |||
| CarlaMutex mutex; | |||
| PatchbayPortList() noexcept | |||
| : lastId(0), | |||
| list() {} | |||
| list(), | |||
| mutex() {} | |||
| void clear() noexcept | |||
| { | |||