Implement Lv2 Options (#5761)
Implement `LV2_OPTIONS__options` feature and some buf-size properties. The code currently assumes that the LMMS buffersize never changes, which is currently true in the LMMS code base.
This commit is contained in:
@@ -130,6 +130,8 @@ public:
|
||||
return m_supportedFeatureURIs;
|
||||
}
|
||||
bool isFeatureSupported(const char* featName) const;
|
||||
AutoLilvNodes findNodes(const LilvNode *subject,
|
||||
const LilvNode *predicate, const LilvNode *object);
|
||||
|
||||
static const std::set<const char*, Lv2Manager::CmpStr>& getPluginBlacklist()
|
||||
{
|
||||
|
||||
104
include/Lv2Options.h
Normal file
104
include/Lv2Options.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Lv2Options.h - Lv2Options class
|
||||
*
|
||||
* Copyright (c) 2020-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
|
||||
*
|
||||
* 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 <cstdint>
|
||||
#include <lv2/lv2plug.in/ns/ext/options/options.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#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<typename Opt, typename Arg>
|
||||
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<Opt>::value],
|
||||
std::make_shared<Opt>(std::forward<Arg>(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<void> value,
|
||||
LV2_Options_Context context = LV2_OPTIONS_INSTANCE,
|
||||
uint32_t subject = 0);
|
||||
//! options that are supported by every processor
|
||||
static std::set<LV2_URID> s_supportedOptions;
|
||||
//! options + data, ordered by URID
|
||||
std::map<LV2_URID, LV2_Options_Option> m_optionByUrid;
|
||||
//! option storage
|
||||
std::vector<LV2_Options_Option> m_options;
|
||||
//! option value storage
|
||||
std::map<LV2_URID, std::shared_ptr<void>> m_optionValues;
|
||||
};
|
||||
|
||||
#endif // LMMS_HAVE_LV2
|
||||
|
||||
#endif // LV2OPTIONS_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 @@ private:
|
||||
const LilvPlugin* m_plugin;
|
||||
LilvInstance* m_instance;
|
||||
Lv2Features m_features;
|
||||
Lv2Options m_options;
|
||||
|
||||
// full list of ports
|
||||
std::vector<std::unique_ptr<Lv2Ports::PortBase>> m_ports;
|
||||
@@ -187,11 +189,12 @@ private:
|
||||
ringbuffer_reader_t<struct MidiInputEvent> 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<std::string, AutomatableModel *> m_connectedModels;
|
||||
|
||||
void initMOptions(); //!< initialize m_options
|
||||
void initPluginSpecificFeatures();
|
||||
|
||||
//! load a file in the plugin, but don't do anything in LMMS
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <cstdint>
|
||||
|
||||
//! 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<typename T>
|
||||
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<int>(Id::size)];
|
||||
};
|
||||
|
||||
template<> struct Lv2UridCache::IdForType<float> { static constexpr auto value = Id::atom_Float; };
|
||||
template<> struct Lv2UridCache::IdForType<std::int32_t> { static constexpr auto value = Id::atom_Int; };
|
||||
|
||||
#endif // LMMS_HAVE_LV2
|
||||
#endif // LV2URIDCACHE_H
|
||||
|
||||
@@ -100,6 +100,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
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <cstring>
|
||||
#include <lilv/lilv.h>
|
||||
#include <lv2.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/options/options.h>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QLibrary>
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
93
src/core/lv2/Lv2Options.cpp
Normal file
93
src/core/lv2/Lv2Options.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Lv2Options.cpp - Lv2Options implementation
|
||||
*
|
||||
* Copyright (c) 2020-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
|
||||
*
|
||||
* 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 <QtGlobal>
|
||||
|
||||
|
||||
std::set<LV2_URID> 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<void> 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
|
||||
@@ -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<float>(Id::param_sampleRate, sampleRate);
|
||||
m_options.initOption<int32_t>(Id::bufsz_maxBlockLength, blockLength);
|
||||
m_options.initOption<int32_t>(Id::bufsz_minBlockLength, blockLength);
|
||||
m_options.initOption<int32_t>(Id::bufsz_nominalBlockLength, blockLength);
|
||||
m_options.initOption<int32_t>(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<LV2_Options_Option*>(m_options.feature());
|
||||
}
|
||||
|
||||
|
||||
@@ -558,7 +602,7 @@ void Lv2Proc::createPort(std::size_t portNum)
|
||||
}
|
||||
}
|
||||
|
||||
int minimumSize = minimumEvbufSize();
|
||||
int minimumSize = defaultEvbufSize();
|
||||
|
||||
Lv2Manager* mgr = Engine::getLv2Manager();
|
||||
|
||||
|
||||
@@ -26,11 +26,19 @@
|
||||
|
||||
#ifdef LMMS_HAVE_LV2
|
||||
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h>
|
||||
#include <QtGlobal>
|
||||
|
||||
#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<std::size_t>(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); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user