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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -39,6 +39,8 @@ SET(LMMS_PLUGIN_LIST
|
||||
HydrogenImport
|
||||
ladspa_browser
|
||||
LadspaEffect
|
||||
Lv2Effect
|
||||
Lv2Instrument
|
||||
lb302
|
||||
MidiImport
|
||||
MidiExport
|
||||
|
||||
@@ -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
67
include/Lv2Basics.h
Normal 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
146
include/Lv2ControlBase.h
Normal 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
126
include/Lv2Manager.h
Normal 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
237
include/Lv2Ports.h
Normal 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
183
include/Lv2Proc.h
Normal 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
|
||||
61
include/Lv2SubPluginFeatures.h
Normal file
61
include/Lv2SubPluginFeatures.h
Normal 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
97
include/Lv2ViewBase.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
9
plugins/Lv2Effect/CMakeLists.txt
Normal file
9
plugins/Lv2Effect/CMakeLists.txt
Normal 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)
|
||||
111
plugins/Lv2Effect/Lv2Effect.cpp
Normal file
111
plugins/Lv2Effect/Lv2Effect.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
54
plugins/Lv2Effect/Lv2Effect.h
Normal file
54
plugins/Lv2Effect/Lv2Effect.h
Normal 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
|
||||
72
plugins/Lv2Effect/Lv2FxControlDialog.cpp
Normal file
72
plugins/Lv2Effect/Lv2FxControlDialog.cpp
Normal 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());
|
||||
}
|
||||
|
||||
|
||||
47
plugins/Lv2Effect/Lv2FxControlDialog.h
Normal file
47
plugins/Lv2Effect/Lv2FxControlDialog.h
Normal 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
|
||||
104
plugins/Lv2Effect/Lv2FxControls.cpp
Normal file
104
plugins/Lv2Effect/Lv2FxControls.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
61
plugins/Lv2Effect/Lv2FxControls.h
Normal file
61
plugins/Lv2Effect/Lv2FxControls.h
Normal 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
BIN
plugins/Lv2Effect/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 967 B |
7
plugins/Lv2Instrument/CMakeLists.txt
Normal file
7
plugins/Lv2Instrument/CMakeLists.txt
Normal 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)
|
||||
303
plugins/Lv2Instrument/Lv2Instrument.cpp
Normal file
303
plugins/Lv2Instrument/Lv2Instrument.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
123
plugins/Lv2Instrument/Lv2Instrument.h
Normal file
123
plugins/Lv2Instrument/Lv2Instrument.h
Normal 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
|
||||
BIN
plugins/Lv2Instrument/logo.png
Normal file
BIN
plugins/Lv2Instrument/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 967 B |
@@ -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