diff --git a/include/Lv2Manager.h b/include/Lv2Manager.h index 585fdafc57d..dbea510ce39 100644 --- a/include/Lv2Manager.h +++ b/include/Lv2Manager.h @@ -130,6 +130,8 @@ class Lv2Manager return m_supportedFeatureURIs; } bool isFeatureSupported(const char* featName) const; + AutoLilvNodes findNodes(const LilvNode *subject, + const LilvNode *predicate, const LilvNode *object); static const std::set& getPluginBlacklist() { diff --git a/include/Lv2Options.h b/include/Lv2Options.h new file mode 100644 index 00000000000..1453de2ea10 --- /dev/null +++ b/include/Lv2Options.h @@ -0,0 +1,104 @@ +/* + * Lv2Options.h - Lv2Options 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 LV2OPTIONS_H +#define LV2OPTIONS_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_LV2 + +#include +#include +#include +#include +#include +#include +#include + +#include "Engine.h" +#include "Lv2Manager.h" +#include "Lv2UridCache.h" + +/** + Option container + + References all available options for a plugin and maps them to their URIDs. + This class is used per Lv2 processor (justification in Lv2Proc::initMOptions()) + + The public member functions should be called in descending order: + + 1. supportOption: set all supported option URIDs + 2. initOption: initialize options with values + 3. createOptionVectors: create the option vectors required for + the feature + 4. access the latter using feature() +*/ +class Lv2Options +{ +public: + //! Return if an option is supported by LMMS + static bool isOptionSupported(LV2_URID key); + //! Mark option as supported + static void supportOption(LV2_URID key); + + //! Initialize an option + template + void initOption(Lv2UridCache::Id key, Arg&& value, + LV2_Options_Context context = LV2_OPTIONS_INSTANCE, + std::uint32_t subject = 0) + { + const Lv2UridCache& cache = Engine::getLv2Manager()->uridCache(); + initOption(cache[key], sizeof(Opt), cache[Lv2UridCache::IdForType::value], + std::make_shared(std::forward(value)), context, subject); + } + //! Fill m_options and m_optionPointers with all options + void createOptionVectors(); + //! Return the feature + const LV2_Options_Option* feature() const + { + return m_options.data(); + } + +private: + //! Initialize an option internally + void initOption(LV2_URID key, + uint32_t size, + LV2_URID type, + std::shared_ptr value, + LV2_Options_Context context = LV2_OPTIONS_INSTANCE, + uint32_t subject = 0); + //! options that are supported by every processor + static std::set s_supportedOptions; + //! options + data, ordered by URID + std::map m_optionByUrid; + //! option storage + std::vector m_options; + //! option value storage + std::map> m_optionValues; +}; + +#endif // LMMS_HAVE_LV2 + +#endif // LV2OPTIONS_H diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index 312f9cf34f2..a80c10e3f18 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -35,6 +35,7 @@ #include "Lv2Basics.h" #include "Lv2Features.h" +#include "Lv2Options.h" #include "LinkedModelGroups.h" #include "MidiEvent.h" #include "Plugin.h" @@ -168,6 +169,7 @@ class Lv2Proc : public LinkedModelGroup const LilvPlugin* m_plugin; LilvInstance* m_instance; Lv2Features m_features; + Lv2Options m_options; // full list of ports std::vector> m_ports; @@ -187,11 +189,12 @@ class Lv2Proc : public LinkedModelGroup ringbuffer_reader_t m_midiInputReader; // other - static std::size_t minimumEvbufSize() { return 1 << 15; /* ardour uses this*/ } + static int32_t defaultEvbufSize() { return 1 << 15; /* ardour uses this*/ } //! models for the controls, sorted by port symbols std::map m_connectedModels; + void initMOptions(); //!< initialize m_options void initPluginSpecificFeatures(); //! load a file in the plugin, but don't do anything in LMMS diff --git a/include/Lv2UridCache.h b/include/Lv2UridCache.h index 81dd1346cf7..1921bdfd70b 100644 --- a/include/Lv2UridCache.h +++ b/include/Lv2UridCache.h @@ -29,6 +29,7 @@ #ifdef LMMS_HAVE_LV2 +#include #include //! Cached URIDs for fast access (for use in real-time code) @@ -37,16 +38,33 @@ class Lv2UridCache public: enum class Id //!< ID for m_uridCache array { + // keep it alphabetically (except "size" at the end) + atom_Float, + atom_Int, + bufsz_minBlockLength, + bufsz_maxBlockLength, + bufsz_nominalBlockLength, + bufsz_sequenceSize, midi_MidiEvent, + param_sampleRate, + // exception to alphabetic ordering - keep at the end: size }; + + template + struct IdForType; + //! Return URID for a cache ID uint32_t operator[](Id id) const; Lv2UridCache(class UridMap& mapper); + private: uint32_t m_cache[static_cast(Id::size)]; }; +template<> struct Lv2UridCache::IdForType { static constexpr auto value = Id::atom_Float; }; +template<> struct Lv2UridCache::IdForType { static constexpr auto value = Id::atom_Int; }; + #endif // LMMS_HAVE_LV2 #endif // LV2URIDCACHE_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7b38c6c39da..cad6e8608df 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -99,6 +99,7 @@ set(LMMS_SRCS core/lv2/Lv2Ports.cpp core/lv2/Lv2Proc.cpp core/lv2/Lv2Manager.cpp + core/lv2/Lv2Options.cpp core/lv2/Lv2SubPluginFeatures.cpp core/lv2/Lv2UridCache.cpp core/lv2/Lv2UridMap.cpp diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index 635b5462c44..57f04f6808c 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include "Plugin.h" #include "PluginFactory.h" #include "Lv2ControlBase.h" +#include "Lv2Options.h" #include "PluginIssue.h" @@ -67,6 +69,17 @@ Lv2Manager::Lv2Manager() : m_supportedFeatureURIs.insert(LV2_URID__map); m_supportedFeatureURIs.insert(LV2_URID__unmap); + m_supportedFeatureURIs.insert(LV2_OPTIONS__options); + + auto supportOpt = [this](Lv2UridCache::Id id) + { + Lv2Options::supportOption(uridCache()[id]); + }; + supportOpt(Lv2UridCache::Id::param_sampleRate); + supportOpt(Lv2UridCache::Id::bufsz_maxBlockLength); + supportOpt(Lv2UridCache::Id::bufsz_minBlockLength); + supportOpt(Lv2UridCache::Id::bufsz_nominalBlockLength); + supportOpt(Lv2UridCache::Id::bufsz_sequenceSize); } @@ -205,6 +218,15 @@ bool Lv2Manager::isFeatureSupported(const char *featName) const +AutoLilvNodes Lv2Manager::findNodes(const LilvNode *subject, + const LilvNode *predicate, const LilvNode *object) +{ + return AutoLilvNodes(lilv_world_find_nodes (m_world, subject, predicate, object)); +} + + + + // unused + untested yet bool Lv2Manager::isSubclassOf(const LilvPluginClass* clvss, const char* uriStr) { diff --git a/src/core/lv2/Lv2Options.cpp b/src/core/lv2/Lv2Options.cpp new file mode 100644 index 00000000000..e941165f090 --- /dev/null +++ b/src/core/lv2/Lv2Options.cpp @@ -0,0 +1,93 @@ +/* + * Lv2Options.cpp - Lv2Options 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 "Lv2Options.h" + +#ifdef LMMS_HAVE_LV2 + +#include + + +std::set Lv2Options::s_supportedOptions; + + + + +bool Lv2Options::isOptionSupported(LV2_URID key) +{ + return s_supportedOptions.find(key) != s_supportedOptions.end(); +} + + + + +void Lv2Options::supportOption(LV2_URID key) +{ + const auto result = s_supportedOptions.insert(key); + Q_ASSERT(result.second); +} + + + + +void Lv2Options::createOptionVectors() +{ + // create vector of options + for(LV2_URID urid : s_supportedOptions) + { + auto itr = m_optionByUrid.find(urid); + Q_ASSERT(itr != m_optionByUrid.end()); + m_options.push_back(itr->second); + } + LV2_Options_Option nullOption; + nullOption.key = 0; + nullOption.value = nullptr; + m_options.push_back(nullOption); +} + + + + +void Lv2Options::initOption(LV2_URID key, uint32_t size, LV2_URID type, + std::shared_ptr value, + LV2_Options_Context context, uint32_t subject) +{ + Q_ASSERT(isOptionSupported(key)); + + LV2_Options_Option opt; + opt.key = key; + opt.context = context; + opt.subject = subject; + opt.size = size; + opt.type = type; + opt.value = value.get(); + + const auto optResult = m_optionByUrid.emplace(key, opt); + const auto valResult = m_optionValues.emplace(key, std::move(value)); + Q_ASSERT(optResult.second); + Q_ASSERT(valResult.second); +} + + +#endif // LMMS_HAVE_LV2 diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index bbfc7065ca0..1ae5221507e 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -128,6 +128,23 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, } } + Lv2Manager* mgr = Engine::getLv2Manager(); + AutoLilvNode requiredOptionNode(mgr->uri(LV2_OPTIONS__requiredOption)); + AutoLilvNodes requiredOptions = mgr->findNodes(lilv_plugin_get_uri (plugin), requiredOptionNode.get(), nullptr); + if (requiredOptions) + { + LILV_FOREACH(nodes, i, requiredOptions.get()) + { + const char* ro = lilv_node_as_uri (lilv_nodes_get (requiredOptions.get(), i)); + if (!Lv2Options::isOptionSupported(mgr->uridMap().map(ro))) + { + // yes, this is not a Lv2 feature, + // but it's a feature in abstract sense + issues.emplace_back(featureNotSupported, ro); + } + } + } + return (audioChannels[inCount] > 2 || audioChannels[outCount] > 2) ? Plugin::Undefined : (audioChannels[inCount] > 0) @@ -422,11 +439,38 @@ bool Lv2Proc::hasNoteInput() const +void Lv2Proc::initMOptions() +{ + /* + sampleRate: + LMMS can in theory inform plugins of a new sample rate. + However, Lv2 plugins seem to not allow sample rate changes + (not even through LV2_Options_Interface) - it's assumed to be + fixed after being passed via LV2_Descriptor::instantiate. + So, if the sampleRate would change, the plugin will need to + re-initialize, and this code section will be + executed again, creating a new option vector. + */ + float sampleRate = Engine::mixer()->processingSampleRate(); + int32_t blockLength = Engine::mixer()->framesPerPeriod(); + int32_t sequenceSize = defaultEvbufSize(); + + using Id = Lv2UridCache::Id; + m_options.initOption(Id::param_sampleRate, sampleRate); + m_options.initOption(Id::bufsz_maxBlockLength, blockLength); + m_options.initOption(Id::bufsz_minBlockLength, blockLength); + m_options.initOption(Id::bufsz_nominalBlockLength, blockLength); + m_options.initOption(Id::bufsz_sequenceSize, sequenceSize); + m_options.createOptionVectors(); +} + + + + void Lv2Proc::initPluginSpecificFeatures() { - // nothing yet - // it would look like this: - // m_features[LV2_URID__map] = m_uridMapFeature + initMOptions(); + m_features[LV2_OPTIONS__options] = const_cast(m_options.feature()); } @@ -558,7 +602,7 @@ void Lv2Proc::createPort(std::size_t portNum) } } - int minimumSize = minimumEvbufSize(); + int minimumSize = defaultEvbufSize(); Lv2Manager* mgr = Engine::getLv2Manager(); diff --git a/src/core/lv2/Lv2UridCache.cpp b/src/core/lv2/Lv2UridCache.cpp index 5887a8e3906..3392f9bd2f7 100644 --- a/src/core/lv2/Lv2UridCache.cpp +++ b/src/core/lv2/Lv2UridCache.cpp @@ -26,11 +26,19 @@ #ifdef LMMS_HAVE_LV2 +#include +#include #include +#include #include #include "Lv2UridMap.h" +// support newer URIs on old systems +#ifndef LV2_BUF_SIZE__nominalBlockLength +#define LV2_BUF_SIZE__nominalBlockLength LV2_BUF_SIZE_PREFIX "nominalBlockLength" +#endif + uint32_t Lv2UridCache::operator[](Lv2UridCache::Id id) const { Q_ASSERT(id != Id::size); @@ -47,7 +55,14 @@ Lv2UridCache::Lv2UridCache(UridMap &mapper) m_cache[static_cast(id)] = mapper.map(uridStr); }; + init(Id::atom_Float, LV2_ATOM__Float); + init(Id::atom_Int, LV2_ATOM__Int); + init(Id::bufsz_minBlockLength, LV2_BUF_SIZE__minBlockLength); + init(Id::bufsz_maxBlockLength, LV2_BUF_SIZE__maxBlockLength); + init(Id::bufsz_nominalBlockLength, LV2_BUF_SIZE__nominalBlockLength); + init(Id::bufsz_sequenceSize, LV2_BUF_SIZE__sequenceSize); init(Id::midi_MidiEvent, LV2_MIDI__MidiEvent); + init(Id::param_sampleRate, LV2_PARAMETERS__sampleRate); for(uint32_t urid : m_cache) { Q_ASSERT(urid != noIdYet); } }