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:
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
49
src/core/lv2/Lv2Basics.cpp
Normal file
49
src/core/lv2/Lv2Basics.cpp
Normal 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
|
||||
|
||||
191
src/core/lv2/Lv2ControlBase.cpp
Normal file
191
src/core/lv2/Lv2ControlBase.cpp
Normal 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
163
src/core/lv2/Lv2Manager.cpp
Normal 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
256
src/core/lv2/Lv2Ports.cpp
Normal 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
544
src/core/lv2/Lv2Proc.cpp
Normal 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
|
||||
184
src/core/lv2/Lv2SubPluginFeatures.cpp
Normal file
184
src/core/lv2/Lv2SubPluginFeatures.cpp
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
237
src/gui/Lv2ViewBase.cpp
Normal 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
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user