diff --git a/include/Lv2Features.h b/include/Lv2Features.h new file mode 100644 index 00000000000..f036c6d1f67 --- /dev/null +++ b/include/Lv2Features.h @@ -0,0 +1,81 @@ +/* + * Lv2Features.h - Lv2Features class + * + * Copyright (c) 2020-2020 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LV2FEATURES_H +#define LV2FEATURES_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_LV2 + +#include +#include +#include +#include "Lv2Manager.h" + +/** + Feature container + + References all available features for a plugin and maps them to their URIs. + + The public member functions should be called in descending order: + + 1. initCommon: map plugin-common features + 2. operator[]: map plugin-specific features + 3. createFeatureVectors: create the feature vectors required for + lilv_plugin_instantiate + 4. access the latter +*/ +class Lv2Features +{ +public: + //! Return if a feature is supported by LMMS + static bool isFeatureSupported(const char *featName); + + Lv2Features(); + + //! Register only plugin-common features + void initCommon(); + //! Return reference to feature data with given URI featName + void*& operator[](const char* featName); + //! Fill m_features and m_featurePointers with all features + void createFeatureVectors(); + //! Return LV2_Feature pointer vector, suited for lilv_plugin_instantiate + const LV2_Feature* const* featurePointers() const + { + return m_featurePointers.data(); + } + +private: + //! feature storage + std::vector m_features; + //! pointers to m_features, required for lilv_plugin_instantiate + std::vector m_featurePointers; + //! features + data, ordered by URI + std::map m_featureByUri; +}; + +#endif // LMMS_HAVE_LV2 + +#endif // LV2FEATURES_H diff --git a/include/Lv2Manager.h b/include/Lv2Manager.h index 8437715619e..0b5fc692354 100644 --- a/include/Lv2Manager.h +++ b/include/Lv2Manager.h @@ -30,9 +30,11 @@ #ifdef LMMS_HAVE_LV2 #include +#include #include #include "Lv2Basics.h" +#include "Lv2UridMap.h" #include "Plugin.h" @@ -114,10 +116,31 @@ class Lv2Manager Iterator begin() { return m_lv2InfoMap.begin(); } Iterator end() { return m_lv2InfoMap.end(); } + //! strcmp based key comparator for std::set and std::map + struct CmpStr + { + bool operator()(char const *a, char const *b) const; + }; + + UridMap& uridMap() { return m_uridMap; } + //! Return all + const std::set& supportedFeatureURIs() const + { + return m_supportedFeatureURIs; + } + bool isFeatureSupported(const char* featName) const; + private: + // general data bool m_debug; //!< if set, debug output will be printed LilvWorld* m_world; Lv2InfoMap m_lv2InfoMap; + std::set m_supportedFeatureURIs; + + // feature data that are common for all Lv2Proc + UridMap m_uridMap; + + // functions bool isSubclassOf(const LilvPluginClass *clvss, const char *uriStr); }; diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index fb1f3466634..1c1cc11d8ac 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -34,6 +34,7 @@ #include #include "Lv2Basics.h" +#include "Lv2Features.h" #include "LinkedModelGroups.h" #include "Plugin.h" #include "PluginIssue.h" @@ -156,6 +157,7 @@ class Lv2Proc : public LinkedModelGroup const LilvPlugin* m_plugin; LilvInstance* m_instance; + Lv2Features m_features; std::vector> m_ports; StereoPortRef m_inPorts, m_outPorts; @@ -163,6 +165,8 @@ class Lv2Proc : public LinkedModelGroup //! models for the controls, sorted by port symbols std::map m_connectedModels; + void initPluginSpecificFeatures(); + //! load a file in the plugin, but don't do anything in LMMS void loadFileInternal(const QString &file); //! allocate m_ports, fill all with metadata, and assign meaning of ports diff --git a/include/Lv2UridMap.h b/include/Lv2UridMap.h new file mode 100644 index 00000000000..f1ddb6253b7 --- /dev/null +++ b/include/Lv2UridMap.h @@ -0,0 +1,69 @@ +/* + * Lv2UridMap.cpp - Lv2UridMap class + * + * Copyright (c) 2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LV2URIDMAP_H +#define LV2URIDMAP_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_LV2 + +#include +#include // TODO: use semaphore, even though this is not realtime critical +#include +#include + +/** + * Complete implementation of the Lv2 Urid Map extension + */ +class UridMap +{ + std::unordered_map m_map; + std::vector m_unMap; + + //! mutex for both m_map and m_unMap + //! the URID map is global, which is why a mutex is required here + std::mutex m_MapMutex; + + LV2_URID_Map m_mapFeature; + LV2_URID_Unmap m_unmapFeature; + + LV2_URID m_lastUrid = 0; + +public: + //! constructor; will set up the features + UridMap(); + + //! map feature function + LV2_URID map(const char* uri); + //! unmap feature function + const char* unmap(LV2_URID urid); + + // access the features + LV2_URID_Map* mapFeature() { return &m_mapFeature; } + LV2_URID_Unmap* unmapFeature() { return &m_unmapFeature; } +}; + +#endif // LMMS_HAVE_LV2 +#endif // LV2URIDMAP_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 730791bf770..b7685522c24 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -93,10 +93,12 @@ set(LMMS_SRCS core/lv2/Lv2Basics.cpp core/lv2/Lv2ControlBase.cpp + core/lv2/Lv2Features.cpp core/lv2/Lv2Ports.cpp core/lv2/Lv2Proc.cpp core/lv2/Lv2Manager.cpp core/lv2/Lv2SubPluginFeatures.cpp + core/lv2/Lv2UridMap.cpp core/midi/MidiAlsaRaw.cpp core/midi/MidiAlsaSeq.cpp diff --git a/src/core/lv2/Lv2Features.cpp b/src/core/lv2/Lv2Features.cpp new file mode 100644 index 00000000000..fe668807e06 --- /dev/null +++ b/src/core/lv2/Lv2Features.cpp @@ -0,0 +1,97 @@ +/* + * Lv2Features.cpp - Lv2Features implementation + * + * Copyright (c) 2020-2020 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "Lv2Features.h" + +#ifdef LMMS_HAVE_LV2 + +#include + +#include "Engine.h" +#include "Lv2Manager.h" + + +bool Lv2Features::isFeatureSupported(const char* featName) +{ + return Engine::getLv2Manager()->isFeatureSupported(featName); +} + + + + +Lv2Features::Lv2Features() +{ + const Lv2Manager* man = Engine::getLv2Manager(); + // create (yet empty) map feature URI -> feature + for(const char* uri : man->supportedFeatureURIs()) + { + m_featureByUri.emplace(uri, nullptr); + } +} + + + + +void Lv2Features::initCommon() +{ + Lv2Manager* man = Engine::getLv2Manager(); + // init m_featureByUri with the plugin-common features + operator[](LV2_URID__map) = man->uridMap().mapFeature(); + operator[](LV2_URID__unmap) = man->uridMap().unmapFeature(); +} + + + + +void Lv2Features::createFeatureVectors() +{ + // create vector of features + for(std::pair& pr : m_featureByUri) + { + Q_ASSERT(pr.second != nullptr); + m_features.push_back(LV2_Feature { pr.first, pr.second }); + } + + // create pointer vector (for lilv_plugin_instantiate) + m_featurePointers.reserve(m_features.size() + 1); + for(std::size_t i = 0; i < m_features.size(); ++i) + { + m_featurePointers.push_back(&m_features[i]); + } + m_featurePointers.push_back(nullptr); +} + + + + +void *&Lv2Features::operator[](const char *featName) +{ + auto itr = m_featureByUri.find(featName); + Q_ASSERT(itr != m_featureByUri.end()); + return itr->second; +} + + +#endif // LMMS_HAVE_LV2 + diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index bce3bf372ca..69fbd0137c5 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -27,6 +27,7 @@ #ifdef LMMS_HAVE_LV2 #include +#include #include #include #include @@ -50,6 +51,9 @@ Lv2Manager::Lv2Manager() m_world = lilv_world_new(); lilv_world_load_all(m_world); + + m_supportedFeatureURIs.insert(LV2_URID__map); + m_supportedFeatureURIs.insert(LV2_URID__unmap); } @@ -133,6 +137,22 @@ void Lv2Manager::initPlugins() +bool Lv2Manager::CmpStr::operator()(const char *a, const char *b) const +{ + return std::strcmp(a, b) < 0; +} + + + + +bool Lv2Manager::isFeatureSupported(const char *featName) const +{ + return m_supportedFeatureURIs.find(featName) != m_supportedFeatureURIs.end(); +} + + + + // unused + untested yet bool Lv2Manager::isSubclassOf(const LilvPluginClass* clvss, const char* uriStr) { diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index 2d77f3f9835..86235f145b2 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -31,6 +31,7 @@ #include "AutomatableModel.h" #include "ComboBoxModel.h" #include "Engine.h" +#include "Lv2Features.h" #include "Lv2Manager.h" #include "Lv2Ports.h" #include "Mixer.h" @@ -74,8 +75,12 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, AutoLilvNodes reqFeats(lilv_plugin_get_required_features(plugin)); LILV_FOREACH (nodes, itr, reqFeats.get()) { - issues.emplace_back(featureNotSupported, - lilv_node_as_string(lilv_nodes_get(reqFeats.get(), itr))); + const char* reqFeatName = lilv_node_as_string( + lilv_nodes_get(reqFeats.get(), itr)); + if(!Lv2Features::isFeatureSupported(reqFeatName)) + { + issues.emplace_back(featureNotSupported, reqFeatName); + } } if (printIssues && issues.size()) @@ -240,11 +245,15 @@ AutomatableModel *Lv2Proc::modelAtPort(const QString &uri) void Lv2Proc::initPlugin() { + m_features.initCommon(); + initPluginSpecificFeatures(); + m_features.createFeatureVectors(); + createPorts(); m_instance = lilv_plugin_instantiate(m_plugin, Engine::mixer()->processingSampleRate(), - nullptr); + m_features.featurePointers()); if (m_instance) { @@ -276,6 +285,16 @@ void Lv2Proc::shutdownPlugin() +void Lv2Proc::initPluginSpecificFeatures() +{ + // nothing yet + // it would look like this: + // m_features[LV2_URID__map] = m_uridMapFeature +} + + + + void Lv2Proc::loadFileInternal(const QString &file) { (void)file; diff --git a/src/core/lv2/Lv2UridMap.cpp b/src/core/lv2/Lv2UridMap.cpp new file mode 100644 index 00000000000..7e4fa864f1e --- /dev/null +++ b/src/core/lv2/Lv2UridMap.cpp @@ -0,0 +1,99 @@ +/* + * Lv2UridMap.cpp - Lv2UridMap implementation + * + * Copyright (c) 2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#include "Lv2UridMap.h" + +#ifdef LMMS_HAVE_LV2 + +static LV2_URID staticMap(LV2_URID_Map_Handle handle, const char* uri) +{ + UridMap* map = static_cast(handle); + return map->map(uri); +} + +static const char* staticUnmap(LV2_URID_Unmap_Handle handle, LV2_URID urid) +{ + UridMap* map = static_cast(handle); + return map->unmap(urid); +} + +UridMap::UridMap() +{ + m_mapFeature.handle = static_cast(this); + m_mapFeature.map = staticMap; + m_unmapFeature.handle = static_cast(this); + m_unmapFeature.unmap = staticUnmap; +} + +LV2_URID UridMap::map(const char *uri) +{ + LV2_URID result = 0u; + + // the Lv2 docs say that 0 should be returned in any case + // where creating an ID for the given URI fails + try + { + // TODO: + // when using C++14, we can get around any string allocation + // in the case the URI is already inside the map: + // * use `m_map.find(uri)` instead of `m_map.find(uriStr)` + // * to avoid temporary string construction in the `find` call, create + // m_map like this: + // std::unordered_map, std::equal<>> m_map; + // * move the try block inside the case where the URI is not in the map + const std::string uriStr = uri; + + std::lock_guard guard (m_MapMutex); + + auto itr = m_map.find(uriStr); + if (itr == m_map.end()) + { + // 1 is the first free URID + std::size_t index = 1u + m_unMap.size(); + auto pr = m_map.emplace(std::move(uriStr), index); + if (pr.second) + { + m_unMap.emplace_back(pr.first->first.c_str()); + result = static_cast(index); + } + } + else { result = itr->second; } + } + catch(...) { /* result variable is already 0 */ } + + return result; +} + +const char *UridMap::unmap(LV2_URID urid) +{ + std::size_t idx = static_cast(urid) - 1; + + std::lock_guard guard (m_MapMutex); + return (idx < m_unMap.size()) ? m_unMap[idx] : nullptr; +} + +#endif // LMMS_HAVE_LV2 +