Lv2 core implementation

Implementation of the Lv2 core, except for CV ports. No features or
extensions are supported yet.

You can now generate sound using Lv2 instruments (restricted to non-piano)
or effects.

For an explenation about the new classes, see Lv2Manager.h
This commit is contained in:
Johannes Lorenz
2020-05-24 12:50:50 +02:00
parent 7aef23d209
commit 2a66e83f53
39 changed files with 3536 additions and 3 deletions

View File

@@ -90,6 +90,17 @@ IF(NOT ("${LAME_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES("${LAME_INCLUDE_DIRS}")
ENDIF()
IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
ENDIF()
IF(NOT ("${LILV_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
ENDIF()
IF(NOT ("${SUIL_INCLUDE_DIRS}" STREQUAL ""))
INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
ENDIF()
LIST(APPEND LMMS_SRCS "${RINGBUFFER_DIR}/src/lib/ringbuffer.cpp")
# Use libraries in non-standard directories (e.g., another version of Qt)
@@ -167,6 +178,9 @@ SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS}
${JACK_LIBRARIES}
${OGGVORBIS_LIBRARIES}
${LAME_LIBRARIES}
${LV2_LIBRARIES}
${SUIL_LIBRARIES}
${LILV_LIBRARIES}
${SAMPLERATE_LIBRARIES}
${SNDFILE_LIBRARIES}
${EXTRA_LIBRARIES}

View File

@@ -90,6 +90,13 @@ set(LMMS_SRCS
core/audio/AudioSampleRecorder.cpp
core/audio/AudioSdl.cpp
core/lv2/Lv2Basics.cpp
core/lv2/Lv2ControlBase.cpp
core/lv2/Lv2Ports.cpp
core/lv2/Lv2Proc.cpp
core/lv2/Lv2Manager.cpp
core/lv2/Lv2SubPluginFeatures.cpp
core/midi/MidiAlsaRaw.cpp
core/midi/MidiAlsaSeq.cpp
core/midi/MidiClient.cpp

View File

@@ -166,6 +166,9 @@ bool DataFile::validate( QString extension )
( extension == "xiz" && ! pluginFactory->pluginSupportingExtension(extension).isNull()) ||
extension == "sf2" || extension == "sf3" || extension == "pat" || extension == "mid" ||
extension == "dll"
#ifdef LMMS_HAVE_LV2
|| extension == "lv2"
#endif
) )
{
return true;

View File

@@ -28,6 +28,7 @@
#include "ConfigManager.h"
#include "FxMixer.h"
#include "Ladspa2LMMS.h"
#include "Lv2Manager.h"
#include "Mixer.h"
#include "Plugin.h"
#include "PresetPreviewPlayHandle.h"
@@ -41,6 +42,9 @@ FxMixer * LmmsCore::s_fxMixer = NULL;
BBTrackContainer * LmmsCore::s_bbTrackContainer = NULL;
Song * LmmsCore::s_song = NULL;
ProjectJournal * LmmsCore::s_projectJournal = NULL;
#ifdef LMMS_HAVE_LV2
Lv2Manager * LmmsCore::s_lv2Manager = nullptr;
#endif
Ladspa2LMMS * LmmsCore::s_ladspaManager = NULL;
void* LmmsCore::s_dndPluginKey = nullptr;
DummyTrackContainer * LmmsCore::s_dummyTC = NULL;
@@ -63,6 +67,10 @@ void LmmsCore::init( bool renderOnly )
s_fxMixer = new FxMixer;
s_bbTrackContainer = new BBTrackContainer;
#ifdef LMMS_HAVE_LV2
s_lv2Manager = new Lv2Manager;
s_lv2Manager->initPlugins();
#endif
s_ladspaManager = new Ladspa2LMMS;
s_projectJournal->setJournalling( true );
@@ -95,6 +103,9 @@ void LmmsCore::destroy()
deleteHelper( &s_fxMixer );
deleteHelper( &s_mixer );
#ifdef LMMS_HAVE_LV2
deleteHelper( &s_lv2Manager );
#endif
deleteHelper( &s_ladspaManager );
//delete ConfigManager::inst();

View File

@@ -0,0 +1,49 @@
/*
* Lv2Basics.cpp - basic Lv2 functions
*
* Copyright (c) 2019-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 "Lv2Basics.h"
#ifdef LMMS_HAVE_LV2
QString qStringFromPluginNode(const LilvPlugin* plug,
LilvNode* (*getFunc)(const LilvPlugin*))
{
return QString::fromUtf8(
lilv_node_as_string(AutoLilvNode((*getFunc)(plug)).get()));
}
QString qStringFromPortName(const LilvPlugin* plug, const LilvPort* port)
{
return QString::fromUtf8(
lilv_node_as_string(AutoLilvNode(lilv_port_get_name(plug, port)).get()));
}
std::string stdStringFromPortName(const LilvPlugin* plug, const LilvPort* port)
{
return std::string(
lilv_node_as_string(AutoLilvNode(lilv_port_get_name(plug, port)).get()));
}
#endif // LMMS_HAVE_LV2

View File

@@ -0,0 +1,191 @@
/*
* Lv2ControlBase.cpp - Lv2 control base class
*
* Copyright (c) 2018-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 "Lv2ControlBase.h"
#ifdef LMMS_HAVE_LV2
#include <QtGlobal>
#include "Engine.h"
#include "Lv2Manager.h"
#include "Lv2Proc.h"
#include "stdshims.h"
Plugin::PluginTypes Lv2ControlBase::check(const LilvPlugin *plugin,
std::vector<PluginIssue> &issues, bool printIssues)
{
// for some reason, all checks can be done by one processor...
return Lv2Proc::check(plugin, issues, printIssues);
}
Lv2ControlBase::Lv2ControlBase(Model* that, const QString &uri) :
m_plugin(Engine::getLv2Manager()->getPlugin(uri))
{
if (m_plugin)
{
int channelsLeft = DEFAULT_CHANNELS; // LMMS plugins are stereo
while (channelsLeft > 0)
{
std::unique_ptr<Lv2Proc> newOne = make_unique<Lv2Proc>(m_plugin, that);
if (newOne->isValid())
{
channelsLeft -= std::max(
1 + static_cast<bool>(newOne->inPorts().m_right),
1 + static_cast<bool>(newOne->outPorts().m_right));
Q_ASSERT(channelsLeft >= 0);
m_procs.push_back(std::move(newOne));
}
else
{
qCritical() << "Failed instantiating LV2 processor";
m_valid = false;
channelsLeft = 0;
}
}
if (m_valid)
{
m_channelsPerProc = DEFAULT_CHANNELS / m_procs.size();
linkAllModels();
}
}
else
{
qCritical() << "No Lv2 plugin found for URI" << uri;
m_valid = false;
}
}
Lv2ControlBase::~Lv2ControlBase() {}
LinkedModelGroup *Lv2ControlBase::getGroup(std::size_t idx)
{
return (m_procs.size() > idx) ? m_procs[idx].get() : nullptr;
}
const LinkedModelGroup *Lv2ControlBase::getGroup(std::size_t idx) const
{
return (m_procs.size() > idx) ? m_procs[idx].get() : nullptr;
}
void Lv2ControlBase::copyModelsFromLmms() {
for (auto& c : m_procs) { c->copyModelsFromCore(); }
}
void Lv2ControlBase::copyBuffersFromLmms(const sampleFrame *buf, fpp_t frames) {
unsigned firstChan = 0; // tell the procs which channels they shall read from
for (auto& c : m_procs) {
c->copyBuffersFromCore(buf, firstChan, m_channelsPerProc, frames);
firstChan += m_channelsPerProc;
}
}
void Lv2ControlBase::copyBuffersToLmms(sampleFrame *buf, fpp_t frames) const {
unsigned firstChan = 0; // tell the procs which channels they shall write to
for (const auto& c : m_procs) {
c->copyBuffersToCore(buf, firstChan, m_channelsPerProc, frames);
firstChan += m_channelsPerProc;
}
}
void Lv2ControlBase::run(fpp_t frames) {
for (auto& c : m_procs) { c->run(frames); }
}
void Lv2ControlBase::saveSettings(QDomDocument &doc, QDomElement &that)
{
LinkedModelGroups::saveSettings(doc, that);
// TODO: save state if supported by plugin
}
void Lv2ControlBase::loadSettings(const QDomElement &that)
{
LinkedModelGroups::loadSettings(that);
// TODO: load state if supported by plugin
}
void Lv2ControlBase::loadFile(const QString &file)
{
(void)file;
}
void Lv2ControlBase::reloadPlugin()
{
// TODO
}
std::size_t Lv2ControlBase::controlCount() const {
std::size_t res = 0;
for (const auto& c : m_procs) { res += c->controlCount(); }
return res;
}
#endif // LMMS_HAVE_LV2

163
src/core/lv2/Lv2Manager.cpp Normal file
View File

@@ -0,0 +1,163 @@
/*
* Lv2Manager.cpp - Implementation of Lv2Manager class
*
* Copyright (c) 2018-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 "Lv2Manager.h"
#ifdef LMMS_HAVE_LV2
#include <cstdlib>
#include <lilv/lilv.h>
#include <lv2.h>
#include <QDebug>
#include <QDir>
#include <QLibrary>
#include <QElapsedTimer>
#include "ConfigManager.h"
#include "Plugin.h"
#include "PluginFactory.h"
#include "Lv2ControlBase.h"
#include "PluginIssue.h"
Lv2Manager::Lv2Manager()
{
const char* dbgStr = getenv("LMMS_LV2_DEBUG");
m_debug = (dbgStr && *dbgStr);
m_world = lilv_world_new();
lilv_world_load_all(m_world);
}
Lv2Manager::~Lv2Manager()
{
lilv_world_free(m_world);
}
AutoLilvNode Lv2Manager::uri(const char *uriStr)
{
return AutoLilvNode(lilv_new_uri(m_world, uriStr));
}
const LilvPlugin *Lv2Manager::getPlugin(const std::string &uri)
{
auto itr = m_lv2InfoMap.find(uri);
return itr == m_lv2InfoMap.end() ? nullptr : itr->second.plugin();
}
const LilvPlugin *Lv2Manager::getPlugin(const QString &uri)
{
return getPlugin(uri.toStdString());
}
void Lv2Manager::initPlugins()
{
const LilvPlugins* plugins = lilv_world_get_all_plugins(m_world);
std::size_t pluginCount = 0, pluginsLoaded = 0;
QElapsedTimer timer;
timer.start();
LILV_FOREACH(plugins, itr, plugins)
{
const LilvPlugin* curPlug = lilv_plugins_get(plugins, itr);
std::vector<PluginIssue> issues;
Plugin::PluginTypes type = Lv2ControlBase::check(curPlug, issues, m_debug);
Lv2Info info(curPlug, type, issues.empty());
m_lv2InfoMap[lilv_node_as_uri(lilv_plugin_get_uri(curPlug))]
= std::move(info);
if(issues.empty()) { ++pluginsLoaded; }
++pluginCount;
}
qDebug() << "Lv2 plugin SUMMARY:"
<< pluginsLoaded << "of" << pluginCount << " loaded in"
<< timer.elapsed() << "msecs.";
if(pluginsLoaded != pluginCount)
{
if (m_debug)
{
qDebug() <<
"If you don't want to see all this debug output, please set\n"
" environment variable \"LMMS_LV2_DEBUG\" to empty or\n"
" do not set it.";
}
else
{
qDebug() <<
"For details about not loaded plugins, please set\n"
" environment variable \"LMMS_LV2_DEBUG\" to nonempty.";
}
}
}
// unused + untested yet
bool Lv2Manager::isSubclassOf(const LilvPluginClass* clvss, const char* uriStr)
{
const LilvPluginClasses* allClasses = lilv_world_get_plugin_classes(m_world);
const LilvPluginClass* root = lilv_world_get_plugin_class(m_world);
const LilvPluginClass* search = lilv_plugin_classes_get_by_uri(allClasses,
uri(uriStr).get());
auto clssEq = [](const LilvPluginClass* pc1,
const LilvPluginClass* pc2) -> bool
{
return lilv_node_equals(
lilv_plugin_class_get_uri(pc1),
lilv_plugin_class_get_uri(pc2));
};
bool isFound = false;
while (!(isFound = clssEq(clvss, search)) && !clssEq(clvss, root))
{
clvss = lilv_plugin_classes_get_by_uri(allClasses,
lilv_plugin_class_get_parent_uri(clvss));
}
return isFound;
}
#endif // LMMS_HAVE_LV2

256
src/core/lv2/Lv2Ports.cpp Normal file
View File

@@ -0,0 +1,256 @@
/*
* Lv2Ports.cpp - Lv2 port classes implementation
*
* Copyright (c) 2019-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 "Lv2Ports.h"
#ifdef LMMS_HAVE_LV2
#include "Engine.h"
#include "Lv2Basics.h"
#include "Lv2Manager.h"
namespace Lv2Ports {
const char *toStr(Flow pf)
{
switch(pf)
{
case Flow::Unknown: return "unknown";
case Flow::Input: return "input";
case Flow::Output: return "output";
}
return "";
}
const char *toStr(Type pt)
{
switch(pt)
{
case Type::Unknown: return "unknown";
case Type::Control: return "control";
case Type::Audio: return "audio";
case Type::Event: return "event";
case Type::Cv: return "cv";
}
return "";
}
const char *toStr(Vis pv)
{
switch(pv)
{
case Vis::Toggled: return "toggled";
case Vis::Enumeration: return "enumeration";
case Vis::Integer: return "integer";
case Vis::None: return "none";
}
return "";
}
std::vector<PluginIssue> Meta::get(const LilvPlugin *plugin,
std::size_t portNum)
{
std::vector<PluginIssue> portIssues;
auto issue = [&portIssues](PluginIssueType i, std::string msg = "") {
portIssues.emplace_back(i, std::move(msg)); };
Lv2Manager* man = Engine::getLv2Manager();
const LilvPort* lilvPort = lilv_plugin_get_port_by_index(
plugin, static_cast<uint32_t>(portNum));
auto portFunc = [&plugin, &lilvPort, &man](
bool (*fptr)(const LilvPlugin*, const LilvPort*, const LilvNode*),
const char* str) {
return fptr(plugin, lilvPort, man->uri(str).get());
};
auto hasProperty = [&portFunc](const char* str) {
return portFunc(lilv_port_has_property, str); };
auto isA = [&portFunc](const char* str) {
return portFunc(lilv_port_is_a, str); };
const std::string portName = stdStringFromPortName(plugin, lilvPort);
m_optional = hasProperty(LV2_CORE__connectionOptional);
m_vis = hasProperty(LV2_CORE__integer)
? Vis::Integer // WARNING: this may still be changed below
: hasProperty(LV2_CORE__enumeration)
? Vis::Enumeration
: hasProperty(LV2_CORE__toggled)
? Vis::Toggled
: Vis::None;
if (isA(LV2_CORE__InputPort)) { m_flow = Flow::Input; }
else if (isA(LV2_CORE__OutputPort)) { m_flow = Flow::Output; }
else {
m_flow = Flow::Unknown;
issue(unknownPortFlow, portName);
}
m_def = .0f; m_min = .0f; m_max = .0f;
if (isA(LV2_CORE__ControlPort))
{
m_type = Type::Control;
if (m_flow == Flow::Input)
{
bool isToggle = m_vis == Vis::Toggled;
LilvNode * defN, * minN = nullptr, * maxN = nullptr;
lilv_port_get_range(plugin, lilvPort, &defN,
isToggle ? nullptr : &minN,
isToggle ? nullptr : &maxN);
AutoLilvNode def(defN), min(minN), max(maxN);
auto takeRangeValue = [&](LilvNode* node,
float& storeHere, PluginIssueType it)
{
if (node) { storeHere = lilv_node_as_float(node); }
else { issue(it, portName); }
};
takeRangeValue(def.get(), m_def, portHasNoDef);
if (!isToggle)
{
takeRangeValue(min.get(), m_min, portHasNoMin);
takeRangeValue(max.get(), m_max, portHasNoMax);
if (m_max - m_min > 15.0f)
{
// range too large for spinbox visualisation, use knobs
// e.g. 0...15 would be OK
m_vis = Vis::None;
}
}
}
}
else if (isA(LV2_CORE__AudioPort)) { m_type = Type::Audio; }
else if (isA(LV2_CORE__CVPort)) {
issue(badPortType, "cvPort");
m_type = Type::Cv;
} else {
if (m_optional) { m_used = false; }
else {
issue(PluginIssueType::unknownPortType, portName);
m_type = Type::Unknown;
}
}
return portIssues;
}
QString PortBase::name() const
{
AutoLilvNode node(lilv_port_get_name(m_plugin, m_port));
QString res = lilv_node_as_string(node.get());
return res;
}
QString PortBase::uri() const
{
return lilv_node_as_string(lilv_port_get_symbol(m_plugin, m_port));
}
Audio::Audio(std::size_t bufferSize, bool isSidechain, bool isOptional)
: m_buffer(bufferSize), m_sidechain(isSidechain), m_optional(isOptional)
{
}
void Audio::copyBuffersFromCore(const sampleFrame *lmmsBuf,
unsigned channel, fpp_t frames)
{
for (std::size_t f = 0; f < static_cast<unsigned>(frames); ++f)
{
m_buffer[f] = lmmsBuf[f][channel];
}
}
void Audio::averageWithBuffersFromCore(const sampleFrame *lmmsBuf,
unsigned channel, fpp_t frames)
{
for (std::size_t f = 0; f < static_cast<unsigned>(frames); ++f)
{
m_buffer[f] = (m_buffer[f] + lmmsBuf[f][channel]) / 2.0f;
}
}
void Audio::copyBuffersToCore(sampleFrame *lmmsBuf,
unsigned channel, fpp_t frames) const
{
for (std::size_t f = 0; f < static_cast<unsigned>(frames); ++f)
{
lmmsBuf[f][channel] = m_buffer[f];
}
}
// make the compiler happy, give each class with virtuals
// a function (the destructor here) which is in a cpp file
PortBase::~PortBase() {}
ConstVisitor::~ConstVisitor() {}
Visitor::~Visitor() {}
} // namespace Lv2Ports
#endif // LMMS_HAVE_LV2

544
src/core/lv2/Lv2Proc.cpp Normal file
View File

@@ -0,0 +1,544 @@
/*
* Lv2Proc.cpp - Lv2 processor class
*
* Copyright (c) 2019-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 "Lv2Proc.h"
#ifdef LMMS_HAVE_LV2
#include <cmath>
#include "AutomatableModel.h"
#include "ComboBoxModel.h"
#include "Engine.h"
#include "Lv2Manager.h"
#include "Lv2Ports.h"
#include "Mixer.h"
Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin,
std::vector<PluginIssue>& issues, bool printIssues)
{
unsigned maxPorts = lilv_plugin_get_num_ports(plugin);
enum { inCount, outCount, maxCount };
unsigned audioChannels[maxCount] = { 0, 0 }; // input and output count
for (unsigned portNum = 0; portNum < maxPorts; ++portNum)
{
Lv2Ports::Meta meta;
// does all port checks:
std::vector<PluginIssue> tmp = meta.get(plugin, portNum);
std::move(tmp.begin(), tmp.end(), std::back_inserter(issues));
bool portMustBeUsed =
!portIsSideChain(plugin,
lilv_plugin_get_port_by_index(plugin, portNum)) &&
!portIsOptional(plugin,
lilv_plugin_get_port_by_index(plugin, portNum));
if (meta.m_type == Lv2Ports::Type::Audio && portMustBeUsed)
++audioChannels[meta.m_flow == Lv2Ports::Flow::Output
? outCount : inCount];
}
if (audioChannels[inCount] > 2)
issues.emplace_back(tooManyInputChannels,
std::to_string(audioChannels[inCount]));
if (audioChannels[outCount] == 0)
issues.emplace_back(noOutputChannel);
else if (audioChannels[outCount] > 2)
issues.emplace_back(tooManyOutputChannels,
std::to_string(audioChannels[outCount]));
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)));
}
if (printIssues && issues.size())
{
qDebug() << "Lv2 plugin"
<< qStringFromPluginNode(plugin, lilv_plugin_get_name)
<< "(URI:"
<< lilv_node_as_uri(lilv_plugin_get_uri(plugin))
<< ") can not be loaded:";
for (const PluginIssue& iss : issues) { qDebug() << " - " << iss; }
}
return (audioChannels[inCount] > 2 || audioChannels[outCount] > 2)
? Plugin::Undefined
: (audioChannels[inCount] > 0)
? Plugin::Effect
: Plugin::Instrument;
}
Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
LinkedModelGroup(parent),
m_plugin(plugin)
{
initPlugin();
}
Lv2Proc::~Lv2Proc() { shutdownPlugin(); }
void Lv2Proc::dumpPorts()
{
std::size_t num = 0;
for (const std::unique_ptr<Lv2Ports::PortBase>& port: m_ports)
{
(void)port;
dumpPort(num++);
}
}
void Lv2Proc::copyModelsFromCore()
{
struct FloatFromModelVisitor : public ConstModelVisitor
{
const std::vector<float>* m_scalePointMap; // in
float m_res; // out
void visit(const FloatModel& m) override { m_res = m.value(); }
void visit(const IntModel& m) override {
m_res = static_cast<float>(m.value()); }
void visit(const BoolModel& m) override {
m_res = static_cast<float>(m.value()); }
void visit(const ComboBoxModel& m) override {
m_res = (*m_scalePointMap)[static_cast<std::size_t>(m.value())]; }
};
struct Copy : public Lv2Ports::Visitor
{
void visit(Lv2Ports::Control& ctrl) override
{
if (ctrl.m_flow == Lv2Ports::Flow::Input)
{
FloatFromModelVisitor ffm;
ffm.m_scalePointMap = &ctrl.m_scalePointMap;
ctrl.m_connectedModel->accept(ffm);
ctrl.m_val = ffm.m_res;
}
}
void visit(Lv2Ports::Cv& cv) override
{
if (cv.m_flow == Lv2Ports::Flow::Input)
{
FloatFromModelVisitor ffm;
ffm.m_scalePointMap = &cv.m_scalePointMap;
cv.m_connectedModel->accept(ffm);
// dirty fix, needs better interpolation
std::fill(cv.m_buffer.begin(), cv.m_buffer.end(), ffm.m_res);
}
}
} copy;
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports) {
port->accept(copy); }
}
void Lv2Proc::copyBuffersFromCore(const sampleFrame *buf,
unsigned firstChan, unsigned num,
fpp_t frames)
{
inPorts().m_left->copyBuffersFromCore(buf, firstChan, frames);
if (num > 1)
{
// if the caller requests to take input from two channels, but we only
// have one input channel... take medium of left and right for
// mono input
// (this happens if we have two outputs and only one input)
if (inPorts().m_right)
{
inPorts().m_right->copyBuffersFromCore(buf, firstChan + 1, frames);
}
else
{
inPorts().m_left->averageWithBuffersFromCore(buf, firstChan + 1, frames);
}
}
}
void Lv2Proc::copyBuffersToCore(sampleFrame* buf,
unsigned firstChan, unsigned num,
fpp_t frames) const
{
outPorts().m_left->copyBuffersToCore(buf, firstChan + 0, frames);
if (num > 1)
{
// if the caller requests to copy into two channels, but we only have
// one output channel, duplicate our output
// (this happens if we have two inputs and only one output)
Lv2Ports::Audio* ap = outPorts().m_right
? outPorts().m_right : outPorts().m_left;
ap->copyBuffersToCore(buf, firstChan + 1, frames);
}
}
void Lv2Proc::run(fpp_t frames)
{
lilv_instance_run(m_instance, static_cast<uint32_t>(frames));
}
AutomatableModel *Lv2Proc::modelAtPort(const QString &uri)
{
// unused currently
AutomatableModel *mod;
auto itr = m_connectedModels.find(uri.toUtf8().data());
if (itr != m_connectedModels.end()) { mod = itr->second; }
else { mod = nullptr; }
return mod;
}
void Lv2Proc::initPlugin()
{
createPorts();
m_instance = lilv_plugin_instantiate(m_plugin,
Engine::mixer()->processingSampleRate(),
nullptr);
if (m_instance)
{
for (std::size_t portNum = 0; portNum < m_ports.size(); ++portNum)
connectPort(portNum);
lilv_instance_activate(m_instance);
}
else
{
qCritical() << "Failed to create an instance of"
<< qStringFromPluginNode(m_plugin, lilv_plugin_get_name)
<< "(URI:"
<< lilv_node_as_uri(lilv_plugin_get_uri(m_plugin))
<< ")";
m_valid = false;
}
}
void Lv2Proc::shutdownPlugin()
{
lilv_instance_deactivate(m_instance);
lilv_instance_free(m_instance);
m_instance = nullptr;
}
void Lv2Proc::loadFileInternal(const QString &file)
{
(void)file;
}
void Lv2Proc::createPort(std::size_t portNum)
{
Lv2Ports::Meta meta;
meta.get(m_plugin, portNum);
const LilvPort* lilvPort = lilv_plugin_get_port_by_index(m_plugin,
static_cast<uint32_t>(portNum));
Lv2Ports::PortBase* port;
if (meta.m_type == Lv2Ports::Type::Control)
{
Lv2Ports::Control* ctrl = new Lv2Ports::Control;
if (meta.m_flow == Lv2Ports::Flow::Input)
{
AutoLilvNode node(lilv_port_get_name(m_plugin, lilvPort));
QString dispName = lilv_node_as_string(node.get());
switch (meta.m_vis)
{
case Lv2Ports::Vis::None:
{
// allow ~1000 steps
float stepSize = (meta.m_max - meta.m_min) / 1000.0f;
// make multiples of 0.01 (or 0.1 for larger values)
float minStep = (stepSize >= 1.0f) ? 0.1f : 0.01f;
stepSize -= fmodf(stepSize, minStep);
stepSize = std::max(stepSize, minStep);
ctrl->m_connectedModel.reset(
new FloatModel(meta.m_def, meta.m_min, meta.m_max,
stepSize, nullptr, dispName));
break;
}
case Lv2Ports::Vis::Integer:
ctrl->m_connectedModel.reset(
new IntModel(static_cast<int>(meta.m_def),
static_cast<int>(meta.m_min),
static_cast<int>(meta.m_max),
nullptr, dispName));
break;
case Lv2Ports::Vis::Enumeration:
{
ComboBoxModel* comboModel
= new ComboBoxModel(
nullptr, dispName);
LilvScalePoints* sps =
lilv_port_get_scale_points(m_plugin, lilvPort);
LILV_FOREACH(scale_points, i, sps)
{
const LilvScalePoint* sp = lilv_scale_points_get(sps, i);
ctrl->m_scalePointMap.push_back(lilv_node_as_float(
lilv_scale_point_get_value(sp)));
comboModel->addItem(
lilv_node_as_string(
lilv_scale_point_get_label(sp)));
}
lilv_scale_points_free(sps);
ctrl->m_connectedModel.reset(comboModel);
break;
}
case Lv2Ports::Vis::Toggled:
ctrl->m_connectedModel.reset(
new BoolModel(static_cast<bool>(meta.m_def),
nullptr, dispName));
break;
}
}
port = ctrl;
}
else if (meta.m_type == Lv2Ports::Type::Audio)
{
Lv2Ports::Audio* audio =
new Lv2Ports::Audio(
static_cast<std::size_t>(
Engine::mixer()->framesPerPeriod()),
portIsSideChain(m_plugin, lilvPort),
portIsOptional(m_plugin, lilvPort)
);
port = audio;
} else {
port = new Lv2Ports::Unknown;
}
// `meta` is of class `Lv2Ports::Meta` and `port` is of a child class
// we can now assign the `Lv2Ports::Meta` part of meta to ports, leaving
// the additional members of `port` unchanged
*static_cast<Lv2Ports::Meta*>(port) = meta;
port->m_port = lilvPort;
port->m_plugin = m_plugin;
m_ports[portNum].reset(port);
}
void Lv2Proc::createPorts()
{
// register ports at the processor after creation,
// i.e. link their data or count them
struct RegisterPort : public Lv2Ports::Visitor
{
Lv2Proc* m_proc;
void visit(Lv2Ports::Control& ctrl) override
{
if (ctrl.m_flow == Lv2Ports::Flow::Input)
{
AutomatableModel* amo = ctrl.m_connectedModel.get();
m_proc->m_connectedModels.emplace(
lilv_node_as_string(lilv_port_get_symbol(
m_proc->m_plugin, ctrl.m_port)),
amo);
m_proc->addModel(amo, ctrl.uri());
}
}
void visit(Lv2Ports::Audio& audio) override
{
if (audio.mustBeUsed())
{
StereoPortRef dummy;
StereoPortRef* portRef = &dummy;
switch (audio.m_flow)
{
case Lv2Ports::Flow::Input:
portRef = &m_proc->m_inPorts;
break;
case Lv2Ports::Flow::Output:
portRef = &m_proc->m_outPorts;
break;
case Lv2Ports::Flow::Unknown:
break;
}
// in Lv2, leftPort is defined to be the first port
if (!portRef->m_left) { portRef->m_left = &audio; }
else if (!portRef->m_right) { portRef->m_right = &audio; }
}
}
};
std::size_t maxPorts = lilv_plugin_get_num_ports(m_plugin);
m_ports.resize(maxPorts);
for (std::size_t portNum = 0; portNum < maxPorts; ++portNum)
{
createPort(portNum);
RegisterPort registerPort;
registerPort.m_proc = this;
m_ports[portNum]->accept(registerPort);
}
// initially assign model values to port values
copyModelsFromCore();
// debugging:
//dumpPorts();
}
struct ConnectPortVisitor : public Lv2Ports::Visitor
{
std::size_t m_num;
LilvInstance* m_instance;
void connectPort(void* location) {
lilv_instance_connect_port(m_instance,
static_cast<uint32_t>(m_num), location);
}
void visit(Lv2Ports::Control& ctrl) override { connectPort(&ctrl.m_val); }
void visit(Lv2Ports::Audio& audio) override
{
connectPort((audio.mustBeUsed()) ? audio.m_buffer.data() : nullptr);
}
void visit(Lv2Ports::Unknown&) override { connectPort(nullptr); }
~ConnectPortVisitor() override;
};
ConnectPortVisitor::~ConnectPortVisitor() {}
void Lv2Proc::connectPort(std::size_t num)
{
ConnectPortVisitor connect;
connect.m_num = num;
connect.m_instance = m_instance;
m_ports[num]->accept(connect);
}
void Lv2Proc::dumpPort(std::size_t num)
{
struct DumpPortDetail : public Lv2Ports::ConstVisitor
{
void visit(const Lv2Ports::Control& ctrl) override {
qDebug() << " control port";
// output ports may be uninitialized yet, only print inputs
if (ctrl.m_flow == Lv2Ports::Flow::Input)
{
qDebug() << " value:" << ctrl.m_val;
}
}
void visit(const Lv2Ports::Audio& audio) override {
qDebug() << (audio.isSideChain() ? " audio port (sidechain)"
: " audio port");
qDebug() << " buffer size:" << audio.bufferSize();
}
};
const Lv2Ports::PortBase& port = *m_ports[num];
qDebug().nospace() << "port " << num << ":";
qDebug() << " name:"
<< qStringFromPortName(m_plugin, port.m_port);
qDebug() << " flow: " << Lv2Ports::toStr(port.m_flow);
qDebug() << " type: " << Lv2Ports::toStr(port.m_type);
qDebug() << " visualization: " << Lv2Ports::toStr(port.m_vis);
if (port.m_type == Lv2Ports::Type::Control || port.m_type == Lv2Ports::Type::Cv)
{
qDebug() << " default:" << port.m_def;
qDebug() << " min:" << port.m_min;
qDebug() << " max:" << port.m_max;
}
qDebug() << " optional: " << port.m_optional;
qDebug() << " => USED: " << port.m_used;
DumpPortDetail dumper;
port.accept(dumper);
}
bool Lv2Proc::portIsSideChain(const LilvPlugin *plugin, const LilvPort *port)
{
return lilv_port_has_property(plugin, port,
uri(LV2_CORE_PREFIX "isSidechain").get());
}
bool Lv2Proc::portIsOptional(const LilvPlugin *plugin, const LilvPort *port)
{
return lilv_port_has_property(plugin, port,
uri(LV2_CORE__connectionOptional).get());
}
AutoLilvNode Lv2Proc::uri(const char *uriStr)
{
return Engine::getLv2Manager()->uri(uriStr);
}
#endif // LMMS_HAVE_LV2

View File

@@ -0,0 +1,184 @@
/*
* Lv2SubPluginFeatures.cpp - derivation from
* Plugin::Descriptor::SubPluginFeatures for
* hosting LV2 plugins
*
* Copyright (c) 2018-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 "Lv2SubPluginFeatures.h"
#ifdef LMMS_HAVE_LV2
#include <QDebug>
#include <QHBoxLayout>
#include <QLabel>
#include "Engine.h"
#include "Lv2Basics.h"
#include "Lv2Manager.h"
const LilvPlugin *Lv2SubPluginFeatures::getPlugin(
const Plugin::Descriptor::SubPluginFeatures::Key &k)
{
const LilvPlugin* result = Engine::getLv2Manager()->
getPlugin(k.attributes["uri"]);
Q_ASSERT(result);
return result;
}
QString Lv2SubPluginFeatures::pluginName(const LilvPlugin *plug)
{
return qStringFromPluginNode(plug, lilv_plugin_get_name);
}
Lv2SubPluginFeatures::Lv2SubPluginFeatures(Plugin::PluginTypes type) :
SubPluginFeatures(type)
{
}
void Lv2SubPluginFeatures::fillDescriptionWidget(QWidget *parent,
const Key *k) const
{
const LilvPlugin *plug = getPlugin(*k);
QLabel *label = new QLabel(parent);
label->setText(QWidget::tr("Name: ") + pluginName(plug));
QLabel *label2 = new QLabel(parent);
label2->setText(QWidget::tr("URI: ") +
lilv_node_as_uri(lilv_plugin_get_uri(plug)));
QWidget *maker = new QWidget(parent);
QHBoxLayout *l = new QHBoxLayout(maker);
l->setMargin(0);
l->setSpacing(0);
QLabel *maker_label = new QLabel(maker);
maker_label->setText(QWidget::tr("Maker: "));
maker_label->setAlignment(Qt::AlignTop);
QLabel *maker_content = new QLabel(maker);
maker_content->setText(
qStringFromPluginNode(plug, lilv_plugin_get_author_name));
maker_content->setWordWrap(true);
l->addWidget(maker_label);
l->addWidget(maker_content, 1);
QWidget *copyright = new QWidget(parent);
l = new QHBoxLayout(copyright);
l->setMargin(0);
l->setSpacing(0);
copyright->setMinimumWidth(parent->minimumWidth());
QLabel *copyright_label = new QLabel(copyright);
copyright_label->setText(QWidget::tr("Copyright: "));
copyright_label->setAlignment(Qt::AlignTop);
QLabel *copyright_content = new QLabel(copyright);
copyright_content->setText("<unknown>");
copyright_content->setWordWrap(true);
l->addWidget(copyright_label);
l->addWidget(copyright_content, 1);
AutoLilvNodes extensions(lilv_plugin_get_extension_data(plug));
(void)extensions;
// possibly TODO: version, project, plugin type, number of channels
}
QString Lv2SubPluginFeatures::additionalFileExtensions(
const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
(void)k;
// lv2 only loads .lv2 files
// maybe add conversions later, e.g. for loading xmz
return QString();
}
QString Lv2SubPluginFeatures::displayName(
const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
return pluginName(getPlugin(k));
}
QString Lv2SubPluginFeatures::description(
const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
(void)k;
return QString::fromUtf8("description not implemented yet"); // TODO
}
const PixmapLoader *Lv2SubPluginFeatures::logo(
const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
(void)k; // TODO
return nullptr;
}
void Lv2SubPluginFeatures::listSubPluginKeys(const Plugin::Descriptor *desc,
KeyList &kl) const
{
Lv2Manager *lv2Mgr = Engine::getLv2Manager();
for (const auto &uriInfoPair : *lv2Mgr)
{
if (uriInfoPair.second.type() == m_type && uriInfoPair.second.isValid())
{
using KeyType =
Plugin::Descriptor::SubPluginFeatures::Key;
KeyType::AttributeMap atm;
atm["uri"] = QString::fromUtf8(uriInfoPair.first.c_str());
const LilvPlugin* plug = uriInfoPair.second.plugin();
kl.push_back(KeyType(desc, pluginName(plug), atm));
//qDebug() << "Found LV2 sub plugin key of type" <<
// m_type << ":" << pr.first.c_str();
}
}
}
#endif // LMMS_HAVE_LV2

View File

@@ -19,6 +19,7 @@ SET(LMMS_SRCS
gui/LfoControllerDialog.cpp
gui/LmmsPalette.cpp
gui/LmmsStyle.cpp
gui/Lv2ViewBase.cpp
gui/MainApplication.cpp
gui/MainWindow.cpp
gui/MidiSetupWidget.cpp

View File

@@ -457,7 +457,11 @@ void FileBrowserTreeWidget::mousePressEvent(QMouseEvent * me )
m_previewPlayHandle = s;
delete tf;
}
else if( ( f->extension ()== "xiz" || f->extension() == "sf2" || f->extension() == "sf3" || f->extension() == "gig" || f->extension() == "pat" ) &&
else if ( ( f->extension ()== "xiz" || f->extension() == "sf2" || f->extension() == "sf3" || f->extension() == "gig" || f->extension() == "pat"
#ifdef LMMS_HAVE_LV2
|| f->extension() == "lv2"
#endif
) &&
! pluginFactory->pluginSupportingExtension(f->extension()).info.isNull() )
{
m_previewPlayHandle = new PresetPreviewPlayHandle( f->fullName(), f->handling() == FileItem::LoadByPlugin );
@@ -1069,6 +1073,11 @@ void FileItem::determineFileType( void )
m_type = VstPluginFile;
m_handling = LoadByPlugin;
}
else if ( ext == "lv2" )
{
m_type = PresetFile;
m_handling = LoadByPlugin;
}
else
{
m_type = UnknownFile;

237
src/gui/Lv2ViewBase.cpp Normal file
View File

@@ -0,0 +1,237 @@
/*
* Lv2ViewBase.cpp - base class for Lv2 plugin views
*
* Copyright (c) 2018-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 "Lv2ViewBase.h"
#ifdef LMMS_HAVE_LV2
#include <QGridLayout>
#include <QGroupBox>
#include <QMdiSubWindow>
#include <QPushButton>
#include <QHBoxLayout>
#include <lilv/lilv.h>
#include "Controls.h"
#include "Engine.h"
#include "GuiApplication.h"
#include "embed.h"
#include "gui_templates.h"
#include "LedCheckbox.h"
#include "Lv2ControlBase.h"
#include "Lv2Manager.h"
#include "Lv2Proc.h"
#include "Lv2Ports.h"
#include "MainWindow.h"
#include "SubWindow.h"
Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) :
LinkedModelGroupView (parent, ctrlBase, colNum)
{
class SetupWidget : public Lv2Ports::ConstVisitor
{
public:
QWidget* m_par; // input
const LilvNode* m_commentUri; // input
Control* m_control = nullptr; // output
void visit(const Lv2Ports::Control& port) override
{
if (port.m_flow == Lv2Ports::Flow::Input)
{
using PortVis = Lv2Ports::Vis;
switch (port.m_vis)
{
case PortVis::None:
m_control = new KnobControl(m_par);
break;
case PortVis::Integer:
m_control = new LcdControl((port.m_max <= 9.0f) ? 1 : 2,
m_par);
break;
case PortVis::Enumeration:
m_control = new ComboControl(m_par);
break;
case PortVis::Toggled:
m_control = new CheckControl(m_par);
break;
}
m_control->setText(port.name());
AutoLilvNodes props(lilv_port_get_value(
port.m_plugin, port.m_port, m_commentUri));
LILV_FOREACH(nodes, itr, props.get())
{
const LilvNode* nod = lilv_nodes_get(props.get(), itr);
m_control->topWidget()->setToolTip(lilv_node_as_string(nod));
break;
}
}
}
};
AutoLilvNode commentUri = uri(LILV_NS_RDFS "comment");
ctrlBase->foreach_port(
[this, &commentUri](const Lv2Ports::PortBase* port)
{
SetupWidget setup;
setup.m_par = this;
setup.m_commentUri = commentUri.get();
port->accept(setup);
if (setup.m_control)
{
addControl(setup.m_control,
lilv_node_as_string(lilv_port_get_symbol(
port->m_plugin, port->m_port)),
port->name().toUtf8().data(),
false);
}
});
}
Lv2ViewProc::~Lv2ViewProc() {}
AutoLilvNode Lv2ViewProc::uri(const char *uriStr)
{
return Engine::getLv2Manager()->uri(uriStr);
}
Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase)
{
QGridLayout* grid = new QGridLayout(meAsWidget);
QHBoxLayout* btnBox = new QHBoxLayout();
if (/* DISABLES CODE */ (false))
{
m_reloadPluginButton = new QPushButton(QObject::tr("Reload Plugin"),
meAsWidget);
btnBox->addWidget(m_reloadPluginButton, 0);
}
if (/* DISABLES CODE */ (false)) // TODO: check if the plugin has the UI extension
{
m_toggleUIButton = new QPushButton(QObject::tr("Show GUI"),
meAsWidget);
m_toggleUIButton->setCheckable(true);
m_toggleUIButton->setChecked(false);
m_toggleUIButton->setIcon(embed::getIconPixmap("zoom"));
m_toggleUIButton->setFont(
pointSize<8>(m_toggleUIButton->font()));
btnBox->addWidget(m_toggleUIButton, 0);
}
btnBox->addStretch(1);
meAsWidget->setAcceptDrops(true);
// note: the lifetime of C++ objects ends after the top expression in the
// expression syntax tree, so the AutoLilvNode gets freed after the function
// has been called
AutoLilvNodes props(lilv_plugin_get_value(ctrlBase->getPlugin(),
uri(LILV_NS_RDFS "comment").get()));
LILV_FOREACH(nodes, itr, props.get())
{
const LilvNode* node = lilv_nodes_get(props.get(), itr);
QLabel* infoLabel = new QLabel(lilv_node_as_string(node));
infoLabel->setWordWrap(true);
infoLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
m_helpButton = new QPushButton(QObject::tr("Help"));
m_helpButton->setCheckable(true);
btnBox->addWidget(m_helpButton);
m_helpWindow = gui->mainWindow()->addWindowedWidget(infoLabel);
m_helpWindow->setSizePolicy(QSizePolicy::Minimum,
QSizePolicy::Expanding);
m_helpWindow->setAttribute(Qt::WA_DeleteOnClose, false);
m_helpWindow->hide();
break;
}
if (m_reloadPluginButton || m_toggleUIButton || m_helpButton)
{
grid->addLayout(btnBox, Rows::ButtonRow, 0, 1, m_colNum);
}
else { delete btnBox; }
m_procView = new Lv2ViewProc(meAsWidget, ctrlBase->control(0), m_colNum);
grid->addWidget(m_procView, Rows::ProcRow, 0);
}
Lv2ViewBase::~Lv2ViewBase() {
// TODO: hide UI if required
}
void Lv2ViewBase::toggleHelp(bool visible)
{
if (m_helpWindow)
{
if (visible) { m_helpWindow->show(); m_helpWindow->raise(); }
else { m_helpWindow->hide(); }
}
}
void Lv2ViewBase::modelChanged(Lv2ControlBase *ctrlBase)
{
// reconnect models
if (m_toggleUIButton)
{
m_toggleUIButton->setChecked(ctrlBase->hasGui());
}
LinkedModelGroupsView::modelChanged(ctrlBase);
}
AutoLilvNode Lv2ViewBase::uri(const char *uriStr)
{
return Engine::getLv2Manager()->uri(uriStr);
}
#endif // LMMS_HAVE_LV2

View File

@@ -138,7 +138,7 @@ MainWindow::MainWindow() :
sideBar->appendTab( new FileBrowser(
confMgr->userPresetsDir() + "*" +
confMgr->factoryPresetsDir(),
"*.xpf *.cs.xml *.xiz",
"*.xpf *.cs.xml *.xiz *.lv2",
tr( "My Presets" ),
embed::getIconPixmap( "preset_file" ).transformed( QTransform().rotate( 90 ) ),
splitter , false, true ) );

View File

@@ -13,6 +13,8 @@
#cmakedefine LMMS_HAVE_FLUIDSYNTH
#cmakedefine LMMS_HAVE_JACK
#cmakedefine LMMS_HAVE_WEAKJACK
#cmakedefine LMMS_HAVE_LV2
#cmakedefine LMMS_HAVE_SUIL
#cmakedefine LMMS_HAVE_MP3LAME
#cmakedefine LMMS_HAVE_OGGVORBIS
#cmakedefine LMMS_HAVE_OSS