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

@@ -13,8 +13,11 @@ SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl"
# VST dependencies
VST_PACKAGES="wine-dev qt59x11extras qtbase5-private-dev libxcb-util0-dev libxcb-keysyms1-dev"
# LV2 dependencies; libsuil-dev is not required
LV2_PACKAGES="lv2-dev liblilv-dev"
# Help with unmet dependencies
PACKAGES="$PACKAGES $SWH_PACKAGES $VST_PACKAGES libjack-jackd2-0"
PACKAGES="$PACKAGES $SWH_PACKAGES $VST_PACKAGES $LV2_PACKAGES libjack-jackd2-0"
# shellcheck disable=SC2086
sudo apt-get install -y $PACKAGES

View File

@@ -58,6 +58,8 @@ OPTION(WANT_CARLA "Include Carla plugin" ON)
OPTION(WANT_CMT "Include Computer Music Toolkit LADSPA plugins" ON)
OPTION(WANT_JACK "Include JACK (Jack Audio Connection Kit) support" ON)
OPTION(WANT_WEAKJACK "Loosely link JACK libraries" ON)
OPTION(WANT_LV2 "Include Lv2 plugins" ON)
OPTION(WANT_SUIL "Include SUIL for LV2 plugin UIs" ON)
OPTION(WANT_MP3LAME "Include MP3/Lame support" ON)
OPTION(WANT_OGGVORBIS "Include OGG/Vorbis support" ON)
OPTION(WANT_PULSEAUDIO "Include PulseAudio support" ON)
@@ -182,6 +184,39 @@ IF(NOT SNDFILE_VERSION VERSION_LESS 1.0.26)
SET(LMMS_HAVE_SF_COMPLEVEL TRUE)
ENDIF()
IF(WANT_LV2)
IF(PKG_CONFIG_FOUND)
PKG_CHECK_MODULES(LV2 lv2)
PKG_CHECK_MODULES(LILV lilv-0)
IF(LV2_FOUND AND LILV_FOUND)
SET(LMMS_HAVE_LV2 TRUE)
SET(STATUS_LV2 "OK")
ELSE()
SET(STATUS_LV2 "not found, install it or set PKG_CONFIG_PATH appropriately")
ENDIF()
ELSE()
SET(STATUS_LV2 "not found, requires pkg-config")
ENDIF()
ELSE(WANT_LV2)
SET(STATUS_LV2 "not built as requested")
ENDIF(WANT_LV2)
IF(WANT_SUIL)
IF(PKG_CONFIG_FOUND)
PKG_CHECK_MODULES(SUIL suil-0)
IF(SUIL_FOUND)
SET(LMMS_HAVE_SUIL TRUE)
SET(STATUS_SUIL "OK")
ELSE()
SET(STATUS_SUIL "not found, install it or set PKG_CONFIG_PATH appropriately")
ENDIF()
ELSE()
SET(STATUS_SUIL "not found, requires pkg-config")
ENDIF()
ELSE(WANT_SUIL)
SET(STATUS_SUIL "not built as requested")
ENDIF(WANT_SUIL)
IF(WANT_CALF)
SET(LMMS_HAVE_CALF TRUE)
SET(STATUS_CALF "OK")
@@ -686,6 +721,8 @@ MESSAGE(
MESSAGE(
"Optional plugins\n"
"----------------\n"
"* Lv2 plugins : ${STATUS_LV2}\n"
"* SUIL for plugin UIs : ${STATUS_SUIL}\n"
"* ZynAddSubFX instrument : ${STATUS_ZYN}\n"
"* Carla Patchbay & Rack : ${STATUS_CARLA}\n"
"* SoundFont2 player : ${STATUS_FLUIDSYNTH}\n"

View File

@@ -39,6 +39,8 @@ SET(LMMS_PLUGIN_LIST
HydrogenImport
ladspa_browser
LadspaEffect
Lv2Effect
Lv2Instrument
lb302
MidiImport
MidiExport

View File

@@ -30,6 +30,7 @@
#include <QtCore/QObject>
#include "lmmsconfig.h"
#include "lmms_export.h"
#include "lmms_basics.h"
@@ -87,6 +88,13 @@ public:
return s_projectJournal;
}
#ifdef LMMS_HAVE_LV2
static class Lv2Manager * getLv2Manager()
{
return s_lv2Manager;
}
#endif
static Ladspa2LMMS * getLADSPAManager()
{
return s_ladspaManager;
@@ -143,6 +151,9 @@ private:
static ProjectJournal * s_projectJournal;
static DummyTrackContainer * s_dummyTC;
#ifdef LMMS_HAVE_LV2
static class Lv2Manager* s_lv2Manager;
#endif
static Ladspa2LMMS * s_ladspaManager;
static void* s_dndPluginKey;

67
include/Lv2Basics.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* Lv2Basics.h - basic Lv2 utils
*
* 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.
*
*/
#ifndef LV2BASICS_H
#define LV2BASICS_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <lilv/lilv.h>
#include <memory>
#include <QString>
#include <string>
struct LilvNodeDeleter
{
void operator()(LilvNode* n) { lilv_node_free(n); }
};
struct LilvNodesDeleter
{
void operator()(LilvNodes* n) { lilv_nodes_free(n); }
};
using AutoLilvNode = std::unique_ptr<LilvNode, LilvNodeDeleter>;
using AutoLilvNodes = std::unique_ptr<LilvNodes, LilvNodesDeleter>;
/**
Return QString from a plugin's node, everything will be freed automatically
@param plug The plugin where the node is
@param getFunc The function to return the node from the plugin
*/
QString qStringFromPluginNode(const LilvPlugin* plug,
LilvNode * (*getFunc)(const LilvPlugin*));
//! Return port name as QString, everything will be freed automatically
QString qStringFromPortName(const LilvPlugin* plug, const LilvPort* port);
//! Return port name as std::string, everything will be freed automatically
std::string stdStringFromPortName(const LilvPlugin* plug, const LilvPort* port);
#endif // LMMS_HAVE_LV2
#endif // LV2BASICS_H

146
include/Lv2ControlBase.h Normal file
View File

@@ -0,0 +1,146 @@
/*
* Lv2ControlBase.h - 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.
*
*/
#ifndef LV2_CONTROL_BASE_H
#define LV2_CONTROL_BASE_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <lilv/lilv.h>
#include "DataFile.h"
#include "LinkedModelGroups.h"
#include "Plugin.h"
class Lv2Proc;
class PluginIssue;
/**
Common base class for Lv2 plugins
This class contains a vector of Lv2Proc, usually 1 (for stereo plugins) or
2 (for mono plugins). Most of the logic is done there, this class primarily
forwards work to the Lv2Proc and collects the results.
This class provides everything Lv2 plugins have in common. It's not
named Lv2Plugin, because
* it does not inherit Instrument
* the Plugin subclass Effect does not inherit this class
This class would usually be a Model subclass. However, Qt doesn't allow
this:
* inhertiting only from Model will cause diamond inheritance for QObject,
which will cause errors with Q_OBJECT
* making this a direct subclass of Instrument resp. EffectControls would
require CRTP, which would make this class a template class, which would
conflict with Q_OBJECT
The consequence is that this class can neither inherit QObject or Model, nor
Instrument or EffectControls, which means in fact:
* this class contains no signals or slots, but it offers stubs for slots
that shall be called by child classes
* this class can not override virtuals of Instrument or EffectControls, so
it will offer functions that must be called by virtuals in its child class
*/
class Lv2ControlBase : public LinkedModelGroups
{
public:
static Plugin::PluginTypes check(const LilvPlugin* m_plugin,
std::vector<PluginIssue> &issues, bool printIssues = false);
const LilvPlugin* getPlugin() const { return m_plugin; }
Lv2Proc *control(std::size_t idx) { return m_procs[idx].get(); }
const Lv2Proc *control(std::size_t idx) const { return m_procs[idx].get(); }
bool hasGui() const { return m_hasGUI; }
void setHasGui(bool val) { m_hasGUI = val; }
protected:
/*
ctor/dtor
*/
//! @param that the class inheriting this class and inheriting Model;
//! this is the same pointer as this, but a different type
//! @param uri the Lv2 URI telling this class what plugin to construct
Lv2ControlBase(class Model *that, const QString& uri);
~Lv2ControlBase() override;
//! Must be checked after ctor or reload
bool isValid() const { return m_valid; }
/*
overrides
*/
LinkedModelGroup* getGroup(std::size_t idx) override;
const LinkedModelGroup* getGroup(std::size_t idx) const override;
/*
utils for the run thread
*/
//! Copy values from all connected models into the respective ports
void copyModelsFromLmms();
//! Copy buffer passed by LMMS into our ports
void copyBuffersFromLmms(const sampleFrame *buf, fpp_t frames);
//! Copy our ports into buffers passed by LMMS
void copyBuffersToLmms(sampleFrame *buf, fpp_t frames) const;
//! Run the Lv2 plugin instance for @param frames frames
void run(fpp_t frames);
/*
load/save, must be called from virtuals
*/
void saveSettings(QDomDocument &doc, QDomElement &that);
void loadSettings(const QDomElement &that);
void loadFile(const QString &file);
//! TODO: not implemented
void reloadPlugin();
/*
more functions that must be called from virtuals
*/
std::size_t controlCount() const;
QString nodeName() const { return "lv2controls"; }
private:
//! Return the DataFile settings type
virtual DataFile::Types settingsType() = 0;
//! Inform the plugin about a file name change
virtual void setNameFromFile(const QString &fname) = 0;
//! Independent processors
//! If this is a mono effect, the vector will have size 2 in order to
//! fulfill LMMS' requirement of having stereo input and output
std::vector<std::unique_ptr<Lv2Proc>> m_procs;
bool m_valid = true;
bool m_hasGUI = false;
unsigned m_channelsPerProc;
const LilvPlugin* m_plugin;
};
#endif // LMMS_HAVE_LV2
#endif // LV2_CONTROL_BASE_H

126
include/Lv2Manager.h Normal file
View File

@@ -0,0 +1,126 @@
/*
* Lv2Manager.h - 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.
*
*/
#ifndef LV2MANAGER_H
#define LV2MANAGER_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <map>
#include <lilv/lilv.h>
#include "Lv2Basics.h"
#include "Plugin.h"
/*
all Lv2 classes in relation (use our "4 spaces per tab rule" to view):
explanation:
"x = {y z}" means class "x" consists of classes "y" and "z"
(and probably other classes not mentioned)
"x = {y*}" means class "x" references/uses class "y"
core:
Lv2Proc = {LilvInstance}
Lv2ControlBase = {Lv2Proc, Lv2Proc... (2 for mono, 1 for stereo)}
Lv2Manager = {LilvPlugin*, LilvPlugin* ...}
(creates Lv2ControlBase, Lv2ControlBase...)
Lv2FxControls = {Lv2ControlBase}
Lv2Effect = {Effect + Lv2FxControls}
(takes Lv2SubPluginFeatures in ctor)
Lv2Instrument = {Instrument + Lv2ControlBase}
(takes Lv2SubPluginFeatures in ctor)
gui:
Lv2ViewProc = {Lv2Proc*}
Lv2ViewBase = {Lv2ViewProc, Lv2ViewProc...
(2 for mono, 1 for stereo)}
Lv2FxControlDialog = {EffectControlDialog + Lv2ViewBase}
Lv2InsView = {InstrumentView + Lv2ViewBase}
Lv2SubPluginFeatures:
Lv2SubPluginFeatures = {Lv2Manager*}
Lv2Effect::Descriptor = {Lv2SubPluginFeatures}
Lv2Instrument::Descriptor = {Lv2SubPluginFeatures}
*/
//! Class to keep track of all LV2 plugins
class Lv2Manager
{
public:
void initPlugins();
Lv2Manager();
~Lv2Manager();
AutoLilvNode uri(const char* uriStr);
//! Class representing info for one plugin
struct Lv2Info
{
public:
//! use only for std::map internals
Lv2Info() : m_plugin(nullptr) {}
//! ctor used inside Lv2Manager
Lv2Info(const LilvPlugin* plug, Plugin::PluginTypes type, bool valid) :
m_plugin(plug), m_type(type), m_valid(valid) {}
Lv2Info(Lv2Info&& other) = default;
Lv2Info& operator=(Lv2Info&& other) = default;
const LilvPlugin* plugin() const { return m_plugin; }
Plugin::PluginTypes type() const { return m_type; }
bool isValid() const { return m_valid; }
private:
const LilvPlugin* m_plugin;
Plugin::PluginTypes m_type;
bool m_valid = false;
};
//! Return descriptor with URI @p uri or nullptr if none exists
const LilvPlugin *getPlugin(const std::string &uri);
//! Return descriptor with URI @p uri or nullptr if none exists
const LilvPlugin *getPlugin(const QString& uri);
using Lv2InfoMap = std::map<std::string, Lv2Info>;
using Iterator = Lv2InfoMap::iterator;
Iterator begin() { return m_lv2InfoMap.begin(); }
Iterator end() { return m_lv2InfoMap.end(); }
private:
bool m_debug; //!< if set, debug output will be printed
LilvWorld* m_world;
Lv2InfoMap m_lv2InfoMap;
bool isSubclassOf(const LilvPluginClass *clvss, const char *uriStr);
};
#endif // LMMS_HAVE_LV2
#endif // LV2MANAGER_H

237
include/Lv2Ports.h Normal file
View File

@@ -0,0 +1,237 @@
/*
* Lv2Ports.h - Lv2 port classes definitions
*
* 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.
*
*/
#ifndef LV2PORTS_H
#define LV2PORTS_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <lilv/lilv.h>
#include <memory>
#include <vector>
#include "lmms_basics.h"
#include "PluginIssue.h"
struct ConnectPortVisitor;
namespace Lv2Ports {
/*
port structs
*/
enum class Flow {
Unknown,
Input,
Output
};
enum class Type {
Unknown,
Control,
Audio,
Event, //!< TODO: unused, describe
Cv //!< TODO: unused, describe
};
//! Port visualization
//! @note All Lv2 audio ports are float, this is only the visualisation
enum class Vis {
None,
Integer,
Enumeration,
Toggled
};
const char* toStr(Lv2Ports::Flow pf);
const char* toStr(Lv2Ports::Type pt);
const char* toStr(Lv2Ports::Vis pv);
struct ControlPortBase;
struct Control;
struct Audio;
struct Cv;
struct Unknown;
struct ConstVisitor
{
virtual void visit(const Lv2Ports::ControlPortBase& ) {}
virtual void visit(const Lv2Ports::Control& ) {}
virtual void visit(const Lv2Ports::Audio& ) {}
virtual void visit(const Lv2Ports::Cv& ) {}
virtual void visit(const Lv2Ports::Unknown& ) {}
virtual ~ConstVisitor();
};
struct Visitor
{
virtual void visit(Lv2Ports::ControlPortBase& ) {}
virtual void visit(Lv2Ports::Control& ) {}
virtual void visit(Lv2Ports::Audio& ) {}
virtual void visit(Lv2Ports::Cv& ) {}
virtual void visit(Lv2Ports::Unknown& ) {}
virtual ~Visitor();
};
struct Meta
{
Type m_type = Type::Unknown;
Flow m_flow = Flow::Unknown;
Vis m_vis = Vis::None;
float m_def = .0f, m_min = .0f, m_max = .0f;
bool m_optional = false;
bool m_used = true;
std::vector<PluginIssue> get(const LilvPlugin* plugin, std::size_t portNum);
};
struct PortBase : public Meta
{
const LilvPort* m_port = nullptr;
const LilvPlugin* m_plugin = nullptr;
virtual void accept(Visitor& v) = 0;
virtual void accept(ConstVisitor& v) const = 0;
QString name() const;
QString uri() const;
virtual ~PortBase();
};
template<typename Derived, typename Base>
struct VisitablePort : public Base
{
void accept(Visitor& v) override { v.visit(static_cast<Derived&>(*this)); }
void accept(ConstVisitor& v) const override { v.visit(static_cast<const Derived&>(*this)); }
};
struct ControlPortBase : public VisitablePort<ControlPortBase, PortBase>
{
//! LMMS models
//! Always up-to-date, except during runs
std::unique_ptr<class AutomatableModel> m_connectedModel;
//! Enumerate float values
//! lv2 defines scale points as
//! "single float Points (for control inputs)"
std::vector<float> m_scalePointMap;
};
struct Control : public VisitablePort<Control, ControlPortBase>
{
//! Data location which Lv2 plugins see
//! Model values are being copied here every run
//! Between runs, this data is not up-to-date
float m_val;
};
struct Cv : public VisitablePort<Cv, ControlPortBase>
{
//! Data location which Lv2 plugins see
//! Model values are being copied here every run
//! Between runs, this data is not up-to-date
std::vector<float> m_buffer;
};
struct Audio : public VisitablePort<Audio, PortBase>
{
Audio(std::size_t bufferSize, bool isSidechain, bool isOptional);
//! Copy buffer passed by LMMS into our ports
//! @param channel channel index into each sample frame
void copyBuffersFromCore(const sampleFrame *lmmsBuf,
unsigned channel, fpp_t frames);
//! Add buffer passed by LMMS into our ports, and halve the result
//! @param channel channel index into each sample frame
void averageWithBuffersFromCore(const sampleFrame *lmmsBuf,
unsigned channel, fpp_t frames);
//! Copy our ports into buffers passed by LMMS
//! @param channel channel index into each sample frame
void copyBuffersToCore(sampleFrame *lmmsBuf,
unsigned channel, fpp_t frames) const;
bool isSideChain() const { return m_sidechain; }
bool isOptional() const { return m_optional; }
bool mustBeUsed() const { return !isSideChain() && !isOptional(); }
std::size_t bufferSize() const { return m_buffer.size(); }
private:
//! the buffer where Lv2 reads/writes the data from/to
std::vector<float> m_buffer;
bool m_sidechain;
bool m_optional;
// the only case when data of m_buffer may be referenced:
friend struct ::ConnectPortVisitor;
};
struct Unknown : public VisitablePort<Unknown, PortBase>
{
};
/*
port casts
*/
template<class Target>
struct DCastVisitor : public Visitor
{
Target* m_result = nullptr;
void visit(Target& tar) { m_result = &tar; }
};
template<class Target>
struct ConstDCastVisitor : public ConstVisitor
{
const Target* m_result = nullptr;
void visit(const Target& tar) { m_result = &tar; }
};
//! If you don't want to use a whole visitor, you can use dcast
template<class Target>
Target* dcast(PortBase* base)
{
DCastVisitor<Target> vis;
base->accept(vis);
return vis.m_result;
}
//! const overload
template<class Target>
const Target* dcast(const PortBase* base)
{
ConstDCastVisitor<Target> vis;
base->accept(vis);
return vis.m_result;
}
} // namespace Lv2Ports
#endif // LMMS_HAVE_LV2
#endif // LV2PORTS_H

183
include/Lv2Proc.h Normal file
View File

@@ -0,0 +1,183 @@
/*
* Lv2Proc.h - 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.
*
*/
#ifndef LV2PROC_H
#define LV2PROC_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <lilv/lilv.h>
#include <memory>
#include <QObject>
#include "Lv2Basics.h"
#include "LinkedModelGroups.h"
#include "Plugin.h"
#include "PluginIssue.h"
// forward declare port structs/enums
namespace Lv2Ports
{
struct Audio;
struct PortBase;
enum class Type;
enum class Flow;
enum class Vis;
}
//! Class representing one Lv2 processor, i.e. one Lv2 handle
//! For Mono effects, 1 Lv2ControlBase references 2 Lv2Proc
class Lv2Proc : public LinkedModelGroup
{
public:
static Plugin::PluginTypes check(const LilvPlugin* plugin,
std::vector<PluginIssue> &issues, bool printIssues = false);
/*
ctor/dtor
*/
Lv2Proc(const LilvPlugin* plugin, Model *parent);
~Lv2Proc() override;
//! Must be checked after ctor or reload
bool isValid() const { return m_valid; }
/*
port access
*/
struct StereoPortRef
{
//! mono port or left port in case of stereo
Lv2Ports::Audio* m_left = nullptr;
//! unused, or right port in case of stereo
Lv2Ports::Audio* m_right = nullptr;
};
StereoPortRef& inPorts() { return m_inPorts; }
const StereoPortRef& inPorts() const { return m_inPorts; }
StereoPortRef& outPorts() { return m_outPorts; }
const StereoPortRef& outPorts() const { return m_outPorts; }
template<class Functor>
void foreach_port(const Functor& ftor)
{
for (std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
{
ftor(port.get());
}
}
template<class Functor>
void foreach_port(const Functor& ftor) const
{
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
{
ftor(port.get());
}
}
//! Debug function to print ports to stdout
void dumpPorts();
/*
utils for the run thread
*/
//! Copy values from all connected models into the respective ports
void copyModelsFromCore();
/**
* Copy buffer passed by the core into our ports
* @param buf buffer of sample frames, each sample frame is something like
* a `float[<number-of-procs> * <channels per proc>]` array.
* @param firstChan The offset for @p buf where we have to read our
* first channel.
* This marks the first sample in each sample frame where we read from.
* If we are the 2nd of 2 mono procs, this can be greater than 0.
* @param num Number of channels we must read from @param buf (starting at
* @p offset)
*/
void copyBuffersFromCore(const sampleFrame *buf,
unsigned firstChan, unsigned num, fpp_t frames);
/**
* Copy our ports into buffers passed by the core
* @param buf buffer of sample frames, each sample frame is something like
* a `float[<number-of-procs> * <channels per proc>]` array.
* @param firstChan The offset for @p buf where we have to write our
* first channel.
* This marks the first sample in each sample frame where we write to.
* If we are the 2nd of 2 mono procs, this can be greater than 0.
* @param num Number of channels we must write to @param buf (starting at
* @p offset)
*/
void copyBuffersToCore(sampleFrame *buf, unsigned firstChan, unsigned num,
fpp_t frames) const;
//! Run the Lv2 plugin instance for @param frames frames
void run(fpp_t frames);
/*
misc
*/
class AutomatableModel *modelAtPort(const QString &uri); // unused currently
std::size_t controlCount() const { return LinkedModelGroup::modelNum(); }
protected:
/*
load and save
*/
//! Create ports and instance, connect ports, activate plugin
void initPlugin();
//! Deactivate instance
void shutdownPlugin();
private:
bool m_valid = true;
const LilvPlugin* m_plugin;
LilvInstance* m_instance;
std::vector<std::unique_ptr<Lv2Ports::PortBase>> m_ports;
StereoPortRef m_inPorts, m_outPorts;
//! models for the controls, sorted by port symbols
std::map<std::string, AutomatableModel *> m_connectedModels;
//! load a file in the plugin, but don't do anything in LMMS
void loadFileInternal(const QString &file);
//! allocate m_ports, fill all with metadata, and assign meaning of ports
void createPorts();
//! fill m_ports[portNum] with metadata
void createPort(std::size_t portNum);
//! connect m_ports[portNum] with Lv2
void connectPort(std::size_t num);
void dumpPort(std::size_t num);
static bool portIsSideChain(const LilvPlugin* plugin, const LilvPort *port);
static bool portIsOptional(const LilvPlugin* plugin, const LilvPort *port);
static AutoLilvNode uri(const char* uriStr);
};
#endif // LMMS_HAVE_LV2
#endif // LV2PROC_H

View File

@@ -0,0 +1,61 @@
/*
* Lv2SubPluginFeatures.h - 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.
*
*/
#ifndef LV2_SUBPLUGIN_FEATURES_H
#define LV2_SUBPLUGIN_FEATURES_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <lilv/lilv.h>
#include "Plugin.h"
class Lv2SubPluginFeatures : public Plugin::Descriptor::SubPluginFeatures
{
private:
static const LilvPlugin *getPlugin(const Key &k);
static QString pluginName(const LilvPlugin *plug);
public:
Lv2SubPluginFeatures(Plugin::PluginTypes type);
void fillDescriptionWidget(
QWidget *parent, const Key *k) const override;
QString additionalFileExtensions(const Key &k) const override;
QString displayName(const Key &k) const override;
QString description(const Key &k) const override;
const PixmapLoader *logo(const Key &k) const override;
void listSubPluginKeys(
const Plugin::Descriptor *desc, KeyList &kl) const override;
};
#endif // LMMS_HAVE_LV2
#endif

97
include/Lv2ViewBase.h Normal file
View File

@@ -0,0 +1,97 @@
/*
* Lv2ViewBase.h - 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.
*
*/
#ifndef LV2VIEWBASE_H
#define LV2VIEWBASE_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <QString>
#include <QVector>
#include "LinkedModelGroupViews.h"
#include "Lv2Basics.h"
class Lv2Proc;
class Lv2ControlBase;
//! View for one processor, Lv2ViewBase contains 2 of those for mono plugins
class Lv2ViewProc : public LinkedModelGroupView
{
public:
//! @param colNum numbers of columns for the controls
Lv2ViewProc(QWidget *parent, Lv2Proc *ctrlBase, int colNum);
~Lv2ViewProc();
private:
static AutoLilvNode uri(const char *uriStr);
};
//! Base class for view for one Lv2 plugin
class Lv2ViewBase : public LinkedModelGroupsView
{
protected:
//! @param pluginWidget A child class which inherits QWidget
Lv2ViewBase(class QWidget *pluginWidget, Lv2ControlBase *ctrlBase);
~Lv2ViewBase();
// these widgets must be connected by child widgets
class QPushButton *m_reloadPluginButton = nullptr;
class QPushButton *m_toggleUIButton = nullptr;
class QPushButton *m_helpButton = nullptr;
void toggleUI();
void toggleHelp(bool visible);
// to be called by child virtuals
//! Reconnect models if model changed
void modelChanged(Lv2ControlBase* ctrlBase);
private:
enum Rows
{
ButtonRow,
ProcRow,
LinkChannelsRow
};
static AutoLilvNode uri(const char *uriStr);
LinkedModelGroupView* getGroupView() override { return m_procView; }
Lv2ViewProc* m_procView;
//! Numbers of controls per row; must be multiple of 2 for mono effects
const int m_colNum = 6;
class QMdiSubWindow* m_helpWindow = nullptr;
class LedCheckBox *m_multiChannelLink;
};
#endif // LMMS_HAVE_LV2
#endif // LV2VIEWBASE_H

View File

@@ -71,6 +71,7 @@ private:
ch_cnt_t m_controlCount;
bool m_noLink;
BoolModel m_stereoLinkModel;
//! control vector for each processor
QVector<control_list_t> m_controls;

View File

@@ -0,0 +1,9 @@
IF(LMMS_HAVE_LV2)
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
INCLUDE(BuildPlugin)
BUILD_PLUGIN(lv2effect Lv2Effect.cpp Lv2FxControls.cpp Lv2FxControlDialog.cpp Lv2Effect.h Lv2FxControls.h Lv2FxControlDialog.h
MOCFILES Lv2Effect.h Lv2FxControls.h Lv2FxControlDialog.h
EMBEDDED_RESOURCES logo.png)
ENDIF(LMMS_HAVE_LV2)

View File

@@ -0,0 +1,111 @@
/*
* Lv2Effect.cpp - implementation of LV2 effect
*
* 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 "Lv2Effect.h"
#include <QDebug>
#include <lv2.h>
#include "Lv2SubPluginFeatures.h"
#include "embed.h"
#include "plugin_export.h"
Plugin::Descriptor PLUGIN_EXPORT lv2effect_plugin_descriptor =
{
STRINGIFY(PLUGIN_NAME),
"LV2",
QT_TRANSLATE_NOOP("pluginBrowser",
"plugin for using arbitrary LV2-effects inside LMMS."),
"Johannes Lorenz <jlsf2013$$$users.sourceforge.net, $$$=@>",
0x0100,
Plugin::Effect,
new PluginPixmapLoader("logo"),
nullptr,
new Lv2SubPluginFeatures(Plugin::Effect)
};
Lv2Effect::Lv2Effect(Model* parent, const Descriptor::SubPluginFeatures::Key *key) :
Effect(&lv2effect_plugin_descriptor, parent, key),
m_controls(this, key->attributes["uri"]),
m_tmpOutputSmps(Engine::mixer()->framesPerPeriod())
{
}
bool Lv2Effect::processAudioBuffer(sampleFrame *buf, const fpp_t frames)
{
if (!isEnabled() || !isRunning()) { return false; }
Q_ASSERT(frames <= static_cast<fpp_t>(m_tmpOutputSmps.size()));
m_controls.copyBuffersFromLmms(buf, frames);
m_controls.copyModelsFromLmms();
// m_pluginMutex.lock();
m_controls.run(frames);
// m_pluginMutex.unlock();
m_controls.copyBuffersToLmms(m_tmpOutputSmps.data(), frames);
double outSum = .0;
bool corrupt = wetLevel() < 0; // #3261 - if w < 0, bash w := 0, d := 1
const float d = corrupt ? 1 : dryLevel();
const float w = corrupt ? 0 : wetLevel();
for(fpp_t f = 0; f < frames; ++f)
{
buf[f][0] = d * buf[f][0] + w * m_tmpOutputSmps[f][0];
buf[f][1] = d * buf[f][1] + w * m_tmpOutputSmps[f][1];
double l = static_cast<double>(buf[f][0]);
double r = static_cast<double>(buf[f][1]);
outSum += l*l + r*r;
}
checkGate(outSum / frames);
return isRunning();
}
extern "C"
{
// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin *lmms_plugin_main(Model *_parent, void *_data)
{
using KeyType = Plugin::Descriptor::SubPluginFeatures::Key;
Lv2Effect* eff = new Lv2Effect(_parent, static_cast<const KeyType*>(_data));
if (!eff->isValid()) { delete eff; eff = nullptr; }
return eff;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Lv2Effect.h - implementation of LV2 effect
*
* 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.
*
*/
#ifndef LV2_EFFECT_H
#define LV2_EFFECT_H
#include "Effect.h"
#include "Lv2FxControls.h"
class Lv2Effect : public Effect
{
Q_OBJECT
public:
/*
initialization
*/
Lv2Effect(Model* parent, const Descriptor::SubPluginFeatures::Key* _key);
//! Must be checked after ctor or reload
bool isValid() const { return m_controls.isValid(); }
bool processAudioBuffer( sampleFrame* buf, const fpp_t frames ) override;
EffectControls* controls() override { return &m_controls; }
Lv2FxControls* lv2Controls() { return &m_controls; }
const Lv2FxControls* lv2Controls() const { return &m_controls; }
private:
Lv2FxControls m_controls;
std::vector<sampleFrame> m_tmpOutputSmps;
};
#endif // LMMS_HAVE_LV2

View File

@@ -0,0 +1,72 @@
/*
* Lv2FxControlDialog.cpp - Lv2FxControlDialog implementation
*
* 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 "Lv2FxControlDialog.h"
#include <QDebug>
#include <QPushButton>
#include <lv2.h>
#include "Lv2Effect.h"
#include "Lv2FxControls.h"
Lv2FxControlDialog::Lv2FxControlDialog(Lv2FxControls *controls) :
EffectControlDialog(controls),
Lv2ViewBase(this, controls)
{
if (m_reloadPluginButton) {
connect(m_reloadPluginButton, &QPushButton::clicked,
this, [this](){ lv2Controls()->reloadPlugin(); });
}
if (m_toggleUIButton) {
connect(m_toggleUIButton, &QPushButton::toggled,
this, [this](){ toggleUI(); });
}
if (m_helpButton) {
connect(m_helpButton, &QPushButton::toggled,
this, [this](bool visible){ toggleHelp(visible); });
}
// for Effects, modelChanged only goes to the top EffectView
// we need to call it manually
modelChanged();
}
Lv2FxControls *Lv2FxControlDialog::lv2Controls()
{
return static_cast<Lv2FxControls *>(m_effectControls);
}
void Lv2FxControlDialog::modelChanged()
{
Lv2ViewBase::modelChanged(lv2Controls());
}

View File

@@ -0,0 +1,47 @@
/*
* Lv2FxControlDialog.h - Lv2FxControlDialog implementation
*
* 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.
*
*/
#ifndef LV2_FX_CONTROL_DIALOG_H
#define LV2_FX_CONTROL_DIALOG_H
#include "EffectControlDialog.h"
#include "Lv2ViewBase.h"
class Lv2FxControls;
class Lv2FxControlDialog : public EffectControlDialog, public Lv2ViewBase
{
Q_OBJECT
public:
Lv2FxControlDialog(Lv2FxControls *controls);
private:
Lv2FxControls *lv2Controls();
void modelChanged() override;
};
#endif

View File

@@ -0,0 +1,104 @@
/*
* Lv2FxControls.cpp - Lv2FxControls implementation
*
* 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 "Lv2FxControls.h"
#include <QDomElement>
#include "Engine.h"
#include "Lv2Effect.h"
#include "Lv2FxControlDialog.h"
#include "Lv2Proc.h"
Lv2FxControls::Lv2FxControls(class Lv2Effect *effect, const QString& uri) :
EffectControls(effect),
Lv2ControlBase(this, uri)
{
if (isValid())
{
connect(Engine::mixer(), &Mixer::sampleRateChanged,
this, [this](){Lv2ControlBase::reloadPlugin();});
}
}
void Lv2FxControls::saveSettings(QDomDocument &doc, QDomElement &that)
{
Lv2ControlBase::saveSettings(doc, that);
}
void Lv2FxControls::loadSettings(const QDomElement &that)
{
Lv2ControlBase::loadSettings(that);
}
int Lv2FxControls::controlCount()
{
return static_cast<int>(Lv2ControlBase::controlCount());
}
EffectControlDialog *Lv2FxControls::createView()
{
return new Lv2FxControlDialog(this);
}
void Lv2FxControls::changeControl() // TODO: what is that?
{
// engine::getSong()->setModified();
}
DataFile::Types Lv2FxControls::settingsType()
{
return DataFile::EffectSettings;
}
void Lv2FxControls::setNameFromFile(const QString &name)
{
effect()->setDisplayName(name);
}

View File

@@ -0,0 +1,61 @@
/*
* Lv2FxControls.h - Lv2FxControls implementation
*
* 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.
*
*/
#ifndef LV2_FX_CONTROLS_H
#define LV2_FX_CONTROLS_H
#include "EffectControls.h"
#include "Lv2ControlBase.h"
class Lv2Effect;
class Lv2FxControls : public EffectControls, public Lv2ControlBase
{
Q_OBJECT
public:
Lv2FxControls(Lv2Effect *effect, const QString &uri);
void saveSettings(QDomDocument &_doc, QDomElement &_parent) override;
void loadSettings(const QDomElement &that) override;
inline QString nodeName() const override
{
return Lv2ControlBase::nodeName();
}
int controlCount() override;
EffectControlDialog *createView() override;
private slots:
void changeControl();
private:
DataFile::Types settingsType() override;
void setNameFromFile(const QString &name) override;
friend class Lv2FxControlDialog;
friend class Lv2Effect;
};
#endif

BIN
plugins/Lv2Effect/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View File

@@ -0,0 +1,7 @@
IF(LMMS_HAVE_LV2)
INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${LILV_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${SUIL_INCLUDE_DIRS})
INCLUDE(BuildPlugin)
BUILD_PLUGIN(lv2instrument Lv2Instrument.cpp Lv2Instrument.h MOCFILES Lv2Instrument.h EMBEDDED_RESOURCES logo.png)
ENDIF(LMMS_HAVE_LV2)

View File

@@ -0,0 +1,303 @@
/*
* Lv2Instrument.cpp - implementation of LV2 instrument
*
* 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 "Lv2Instrument.h"
#include <QDebug>
#include <QDragEnterEvent>
#include "Engine.h"
#include "InstrumentPlayHandle.h"
#include "InstrumentTrack.h"
#include "Lv2SubPluginFeatures.h"
#include "Mixer.h"
#include "StringPairDrag.h"
#include "embed.h"
#include "plugin_export.h"
Plugin::Descriptor PLUGIN_EXPORT lv2instrument_plugin_descriptor =
{
STRINGIFY(PLUGIN_NAME),
"LV2",
QT_TRANSLATE_NOOP("pluginBrowser",
"plugin for using arbitrary LV2 instruments inside LMMS."),
"Johannes Lorenz <jlsf2013$$$users.sourceforge.net, $$$=@>",
0x0100,
Plugin::Instrument,
new PluginPixmapLoader("logo"),
nullptr,
new Lv2SubPluginFeatures(Plugin::Instrument)
};
/*
Lv2Instrument
*/
Lv2Instrument::Lv2Instrument(InstrumentTrack *instrumentTrackArg,
Descriptor::SubPluginFeatures::Key *key) :
Instrument(instrumentTrackArg, &lv2instrument_plugin_descriptor, key),
Lv2ControlBase(this, key->attributes["uri"])
{
if (Lv2ControlBase::isValid())
{
#ifdef LV2_INSTRUMENT_USE_MIDI
for (int i = 0; i < NumKeys; ++i) { m_runningNotes[i] = 0; }
#endif
connect(instrumentTrack()->pitchRangeModel(), SIGNAL(dataChanged()),
this, SLOT(updatePitchRange()), Qt::DirectConnection);
connect(Engine::mixer(), &Mixer::sampleRateChanged,
this, [this](){Lv2ControlBase::reloadPlugin();});
// now we need a play-handle which cares for calling play()
InstrumentPlayHandle *iph =
new InstrumentPlayHandle(this, instrumentTrackArg);
Engine::mixer()->addPlayHandle(iph);
}
}
Lv2Instrument::~Lv2Instrument()
{
Engine::mixer()->removePlayHandlesOfTypes(instrumentTrack(),
PlayHandle::TypeNotePlayHandle |
PlayHandle::TypeInstrumentPlayHandle);
}
bool Lv2Instrument::isValid() const { return Lv2ControlBase::isValid(); }
void Lv2Instrument::saveSettings(QDomDocument &doc, QDomElement &that)
{
Lv2ControlBase::saveSettings(doc, that);
}
void Lv2Instrument::loadSettings(const QDomElement &that)
{
Lv2ControlBase::loadSettings(that);
}
void Lv2Instrument::loadFile(const QString &file)
{
Lv2ControlBase::loadFile(file);
}
#ifdef LV2_INSTRUMENT_USE_MIDI
bool Lv2Instrument::handleMidiEvent(
const MidiEvent &event, const MidiTime &time, f_cnt_t offset)
{
// this function can be called from GUI threads while the plugin is running,
// so this requires caching, e.g. in ringbuffers
(void)time;
(void)offset;
(void)event;
return true;
}
#endif
// not yet working
#ifndef LV2_INSTRUMENT_USE_MIDI
void Lv2Instrument::playNote(NotePlayHandle *nph, sampleFrame *)
{
}
#endif
void Lv2Instrument::play(sampleFrame *buf)
{
copyModelsFromLmms();
fpp_t fpp = Engine::mixer()->framesPerPeriod();
run(fpp);
copyBuffersToLmms(buf, fpp);
instrumentTrack()->processAudioBuffer(buf, fpp, nullptr);
}
PluginView *Lv2Instrument::instantiateView(QWidget *parent)
{
return new Lv2InsView(this, parent);
}
void Lv2Instrument::updatePitchRange()
{
qDebug() << "Lmms: Cannot update pitch range for lv2 plugin:"
"not implemented yet";
}
QString Lv2Instrument::nodeName() const
{
return Lv2ControlBase::nodeName();
}
DataFile::Types Lv2Instrument::settingsType()
{
return DataFile::InstrumentTrackSettings;
}
void Lv2Instrument::setNameFromFile(const QString &name)
{
instrumentTrack()->setName(name);
}
/*
Lv2InsView
*/
Lv2InsView::Lv2InsView(Lv2Instrument *_instrument, QWidget *_parent) :
InstrumentView(_instrument, _parent),
Lv2ViewBase(this, _instrument)
{
setAutoFillBackground(true);
if (m_reloadPluginButton) {
connect(m_reloadPluginButton, &QPushButton::clicked,
this, [this](){ castModel<Lv2Instrument>()->reloadPlugin();} );
}
if (m_toggleUIButton) {
connect(m_toggleUIButton, &QPushButton::toggled,
this, [this](){ toggleUI(); });
}
if (m_helpButton) {
connect(m_helpButton, &QPushButton::toggled,
this, [this](bool visible){ toggleHelp(visible); });
}
}
void Lv2InsView::dragEnterEvent(QDragEnterEvent *_dee)
{
void (QDragEnterEvent::*reaction)(void) = &QDragEnterEvent::ignore;
if (_dee->mimeData()->hasFormat(StringPairDrag::mimeType()))
{
const QString txt =
_dee->mimeData()->data(StringPairDrag::mimeType());
if (txt.section(':', 0, 0) == "pluginpresetfile") {
reaction = &QDragEnterEvent::acceptProposedAction;
}
}
(_dee->*reaction)();
}
void Lv2InsView::dropEvent(QDropEvent *_de)
{
const QString type = StringPairDrag::decodeKey(_de);
const QString value = StringPairDrag::decodeValue(_de);
if (type == "pluginpresetfile")
{
castModel<Lv2Instrument>()->loadFile(value);
_de->accept();
return;
}
_de->ignore();
}
void Lv2InsView::toggleUI()
{
}
void Lv2InsView::modelChanged()
{
Lv2ViewBase::modelChanged(castModel<Lv2Instrument>());
}
extern "C"
{
// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin *lmms_plugin_main(Model *_parent, void *_data)
{
using KeyType = Plugin::Descriptor::SubPluginFeatures::Key;
Lv2Instrument* ins = new Lv2Instrument(
static_cast<InstrumentTrack*>(_parent),
static_cast<KeyType*>(_data ));
if (!ins->isValid()) { delete ins; ins = nullptr; }
return ins;
}
}

View File

@@ -0,0 +1,123 @@
/*
* Lv2Instrument.h - implementation of LV2 instrument
*
* 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.
*
*/
#ifndef LV2_INSTRUMENT_H
#define LV2_INSTRUMENT_H
#include <QString>
#include "Instrument.h"
#include "InstrumentView.h"
#include "Note.h"
#include "Lv2ControlBase.h"
#include "Lv2ViewBase.h"
// whether to use MIDI vs playHandle
// currently only MIDI works
#define LV2_INSTRUMENT_USE_MIDI
class QPushButton;
class Lv2Instrument : public Instrument, public Lv2ControlBase
{
Q_OBJECT
public:
/*
initialization
*/
Lv2Instrument(InstrumentTrack *instrumentTrackArg,
Descriptor::SubPluginFeatures::Key* key);
~Lv2Instrument() override;
//! Must be checked after ctor or reload
bool isValid() const;
/*
load/save
*/
void saveSettings(QDomDocument &doc, QDomElement &that) override;
void loadSettings(const QDomElement &that) override;
void loadFile(const QString &file) override;
/*
realtime funcs
*/
bool hasNoteInput() const override { return false; /* not supported yet */ }
#ifdef LV2_INSTRUMENT_USE_MIDI
bool handleMidiEvent(const MidiEvent &event,
const MidiTime &time = MidiTime(), f_cnt_t offset = 0) override;
#else
void playNote(NotePlayHandle *nph, sampleFrame *) override;
#endif
void play(sampleFrame *buf) override;
/*
misc
*/
Flags flags() const override
{
#ifdef LV2_INSTRUMENT_USE_MIDI
return IsSingleStreamed | IsMidiBased;
#else
return IsSingleStreamed;
#endif
}
PluginView *instantiateView(QWidget *parent) override;
private slots:
void updatePitchRange();
private:
QString nodeName() const override;
DataFile::Types settingsType() override;
void setNameFromFile(const QString &name) override;
#ifdef LV2_INSTRUMENT_USE_MIDI
int m_runningNotes[NumKeys];
#endif
friend class Lv2InsView;
};
class Lv2InsView : public InstrumentView, public Lv2ViewBase
{
Q_OBJECT
public:
Lv2InsView(Lv2Instrument *_instrument, QWidget *_parent);
protected:
void dragEnterEvent(QDragEnterEvent *_dee) override;
void dropEvent(QDropEvent *_de) override;
private slots:
void reloadPlugin();
void toggleUI();
private:
void modelChanged() override;
};
#endif // LV2_INSTRUMENT_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

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