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:
Johannes Lorenz
2020-12-08 00:12:04 +01:00
committed by GitHub
parent cd2366a21c
commit 2f66449092
9 changed files with 307 additions and 5 deletions

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
{

View 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

View File

@@ -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();

View File

@@ -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); }
}