Files
lmms/src/core/lv2/Lv2Proc.cpp
2024-05-23 00:34:00 +02:00

921 lines
24 KiB
C++

/*
* Lv2Proc.cpp - Lv2 processor class
*
* Copyright (c) 2019-2022 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 <lv2/midi/midi.h>
#include <lv2/atom/atom.h>
#include <lv2/resize-port/resize-port.h>
#include <lv2/worker/worker.h>
#include <QDebug>
#include <QDomDocument>
#include <QtGlobal>
#include "AudioEngine.h"
#include "AutomatableModel.h"
#include "ComboBoxModel.h"
#include "Engine.h"
#include "Lv2Features.h"
#include "Lv2Manager.h"
#include "Lv2Ports.h"
#include "Lv2Evbuf.h"
#include "MidiEvent.h"
#include "MidiEventToByteSeq.h"
#include "NoCopyNoMove.h"
namespace lmms
{
// container for everything required to store MIDI events going to the plugin
struct MidiInputEvent
{
MidiEvent ev;
TimePos time;
f_cnt_t offset;
};
Plugin::Type Lv2Proc::check(const LilvPlugin *plugin,
std::vector<PluginIssue>& issues)
{
unsigned maxPorts = lilv_plugin_get_num_ports(plugin);
enum { inCount, outCount, maxCount };
auto audioChannels = std::array<unsigned, maxCount>{}; // audio input and output count
auto midiChannels = std::array<unsigned, maxCount>{}; // MIDI input and output count
const char* pluginUri = lilv_node_as_uri(lilv_plugin_get_uri(plugin));
//qDebug() << "Checking plugin" << pluginUri << "...";
// TODO: manage a global blacklist outside of the code
// for now, this will help
// this is only a fix for the meantime
if (!Engine::ignorePluginBlacklist())
{
const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist();
const auto& pluginBlacklist32 = Lv2Manager::getPluginBlacklistBuffersizeLessThan32();
if(pluginBlacklist.find(pluginUri) != pluginBlacklist.end())
{
issues.emplace_back(PluginIssueType::Blacklisted);
}
else if(Engine::audioEngine()->framesPerPeriod() <= 32 &&
pluginBlacklist32.find(pluginUri) != pluginBlacklist32.end())
{
issues.emplace_back(PluginIssueType::Blacklisted); // currently no special blacklist category
}
}
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)) &&
!meta.m_optional;
if (meta.m_type == Lv2Ports::Type::Audio && portMustBeUsed)
{
++audioChannels[meta.m_flow == Lv2Ports::Flow::Output
? outCount : inCount];
}
else if(meta.m_type == Lv2Ports::Type::AtomSeq && portMustBeUsed)
{
++midiChannels[meta.m_flow == Lv2Ports::Flow::Output
? outCount : inCount];
}
}
if (audioChannels[inCount] > 2)
issues.emplace_back(PluginIssueType::TooManyInputChannels,
std::to_string(audioChannels[inCount]));
if (audioChannels[outCount] == 0)
issues.emplace_back(PluginIssueType::NoOutputChannel);
else if (audioChannels[outCount] > 2)
issues.emplace_back(PluginIssueType::TooManyOutputChannels,
std::to_string(audioChannels[outCount]));
if (midiChannels[inCount] > 1)
issues.emplace_back(PluginIssueType::TooManyMidiInputChannels,
std::to_string(midiChannels[inCount]));
if (midiChannels[outCount] > 1)
issues.emplace_back(PluginIssueType::TooManyMidiOutputChannels,
std::to_string(midiChannels[outCount]));
AutoLilvNodes reqFeats(lilv_plugin_get_required_features(plugin));
LILV_FOREACH (nodes, itr, reqFeats.get())
{
const char* reqFeatName = lilv_node_as_string(
lilv_nodes_get(reqFeats.get(), itr));
if(!Lv2Features::isFeatureSupported(reqFeatName))
{
issues.emplace_back(PluginIssueType::FeatureNotSupported, reqFeatName);
}
}
Lv2Manager* mgr = Engine::getLv2Manager();
AutoLilvNode requiredOptionNode(mgr->uri(LV2_OPTIONS__requiredOption));
AutoLilvNodes requiredOptions = mgr->findNodes(lilv_plugin_get_uri (plugin), requiredOptionNode.get(), nullptr);
if (requiredOptions)
{
LILV_FOREACH(nodes, i, requiredOptions.get())
{
const char* ro = lilv_node_as_uri (lilv_nodes_get (requiredOptions.get(), i));
if (!Lv2Options::isOptionSupported(mgr->uridMap().map(ro)))
{
// yes, this is not a Lv2 feature,
// but it's a feature in abstract sense
issues.emplace_back(PluginIssueType::FeatureNotSupported, ro);
}
}
}
return (audioChannels[inCount] > 2 || audioChannels[outCount] > 2)
? Plugin::Type::Undefined
: (audioChannels[inCount] > 0)
? Plugin::Type::Effect
: Plugin::Type::Instrument;
}
class Lv2ProcSuspender : NoCopyNoMove
{
public:
Lv2ProcSuspender(Lv2Proc* proc)
: m_proc(proc)
, m_wasActive(proc->m_instance)
{
if (m_wasActive) { m_proc->shutdownPlugin(); }
}
~Lv2ProcSuspender()
{
if (m_wasActive) { m_proc->initPlugin(); }
}
private:
Lv2Proc* const m_proc;
const bool m_wasActive;
};
Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
LinkedModelGroup(parent),
m_plugin(plugin),
m_workLock(1),
m_midiInputBuf(m_maxMidiInputEvents),
m_midiInputReader(m_midiInputBuf)
{
createPorts();
initPlugin();
}
Lv2Proc::~Lv2Proc() { shutdownPlugin(); }
void Lv2Proc::reload() { Lv2ProcSuspender(this); }
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
{
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
{
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);
}
void visit(Lv2Ports::AtomSeq& atomPort) override
{
lv2_evbuf_reset(atomPort.m_buf.get(), true);
}
} copy;
// feed each input port with the respective data from the LMMS core
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
{
if (port->m_flow == Lv2Ports::Flow::Input)
{
port->accept(copy);
}
}
// send pending MIDI events to atom port
if(m_midiIn)
{
LV2_Evbuf_Iterator iter = lv2_evbuf_begin(m_midiIn->m_buf.get());
// MIDI events waiting to go to the plugin?
while(m_midiInputReader.read_space() > 0)
{
const MidiInputEvent ev = m_midiInputReader.read(1)[0];
uint32_t atomStamp =
ev.time.frames(Engine::framesPerTick()) + ev.offset;
uint32_t type = Engine::getLv2Manager()->
uridCache()[Lv2UridCache::Id::midi_MidiEvent];
auto buf = std::array<uint8_t, 4>{};
std::size_t bufsize = writeToByteSeq(ev.ev, buf.data(), buf.size());
if(bufsize)
{
lv2_evbuf_write(&iter, atomStamp, type, bufsize, buf.data());
}
}
}
}
void Lv2Proc::copyModelsToCore()
{
struct Copy : public Lv2Ports::Visitor
{
void visit(Lv2Ports::AtomSeq& atomPort) override
{
// we currently don't copy anything, but we need to clear the buffer
// for the plugin to write again
lv2_evbuf_reset(atomPort.m_buf.get(), false);
}
} copy;
// fetch data from each output port and bring it to the LMMS core
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
{
if (port->m_flow == Lv2Ports::Flow::Output)
{
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)
{
if (m_worker)
{
// Process any worker replies
m_worker->emitResponses();
}
lilv_instance_run(m_instance, static_cast<uint32_t>(frames));
if (m_worker)
{
// Notify the plugin the run() cycle is finished
m_worker->notifyPluginThatRunFinished();
}
}
// in case there will be a PR which removes this callback and instead adds a
// `ringbuffer_t<MidiEvent + time info>` to `class Instrument`, this
// function (and the ringbuffer and its reader in `Lv2Proc`) will simply vanish
void Lv2Proc::handleMidiInputEvent(const MidiEvent &event, const TimePos &time, f_cnt_t offset)
{
if(m_midiIn)
{
// ringbuffer allows only one writer at a time
// however, this function can be called by multiple threads
// (different RT and non-RT!) at the same time
// for now, a spinlock looks like the most safe/easy compromise
// source: https://en.cppreference.com/w/cpp/atomic/atomic_flag
while (m_ringLock.test_and_set(std::memory_order_acquire)) // acquire lock
; // spin
MidiInputEvent ev { event, time, offset };
std::size_t written = m_midiInputBuf.write(&ev, 1);
if(written != 1)
{
qWarning("MIDI ringbuffer is too small! Discarding MIDI event.");
}
m_ringLock.clear(std::memory_order_release);
}
else
{
qWarning() << "Warning: Caught MIDI event for an Lv2 instrument"
<< "that can not handle MIDI... Ignoring";
}
}
AutomatableModel *Lv2Proc::modelAtPort(const QString &uri)
{
const auto itr = m_connectedModels.find(uri.toUtf8().data());
return itr != m_connectedModels.end() ? itr->second : nullptr;
}
void Lv2Proc::initPlugin()
{
m_features.initCommon();
initPluginSpecificFeatures();
m_features.createFeatureVectors();
m_instance = lilv_plugin_instantiate(m_plugin,
Engine::audioEngine()->outputSampleRate(),
m_features.featurePointers());
if (m_instance)
{
const auto iface = static_cast<const LV2_Worker_Interface*>(
lilv_instance_get_extension_data(m_instance, LV2_WORKER__interface));
if (iface)
{
m_worker->setHandle(lilv_instance_get_handle(m_instance));
m_worker->setInterface(iface);
}
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))
<< ")";
throw std::runtime_error("Failed to create Lv2 processor");
}
}
void Lv2Proc::shutdownPlugin()
{
lilv_instance_deactivate(m_instance);
lilv_instance_free(m_instance);
m_instance = nullptr;
m_features.clear();
m_options.clear();
}
bool Lv2Proc::hasNoteInput() const
{
return m_midiIn;
// we could additionally check for
// http://lv2plug.in/ns/lv2core#InstrumentPlugin
// however, jalv does not do that, too
// so, if there's any MIDI input, we just assume we can send notes there
}
void Lv2Proc::initMOptions()
{
/*
sampleRate:
LMMS can in theory inform plugins of a new sample rate.
However, Lv2 plugins seem to not allow sample rate changes
(not even through LV2_Options_Interface) - it's assumed to be
fixed after being passed via LV2_Descriptor::instantiate.
So, if the sampleRate would change, the plugin will need to
re-initialize, and this code section will be
executed again, creating a new option vector.
*/
float sampleRate = Engine::audioEngine()->outputSampleRate();
int32_t blockLength = Engine::audioEngine()->framesPerPeriod();
int32_t sequenceSize = defaultEvbufSize();
using Id = Lv2UridCache::Id;
m_options.initOption<float>(Id::param_sampleRate, sampleRate);
m_options.initOption<int32_t>(Id::bufsz_maxBlockLength, blockLength);
m_options.initOption<int32_t>(Id::bufsz_minBlockLength, blockLength);
m_options.initOption<int32_t>(Id::bufsz_nominalBlockLength, blockLength);
m_options.initOption<int32_t>(Id::bufsz_sequenceSize, sequenceSize);
m_options.createOptionVectors();
}
void Lv2Proc::initPluginSpecificFeatures()
{
// options
initMOptions();
m_features[LV2_OPTIONS__options] = const_cast<LV2_Options_Option*>(m_options.feature());
// worker (if plugin has worker extension)
Lv2Manager* mgr = Engine::getLv2Manager();
if (lilv_plugin_has_extension_data(m_plugin, mgr->uri(LV2_WORKER__interface).get()))
{
bool threaded = !Engine::audioEngine()->renderOnly();
m_worker.emplace(&m_workLock, threaded);
m_features[LV2_WORKER__schedule] = m_worker->feature();
// note: the worker interface can not be instantiated yet - it requires m_instance. see initPlugin()
}
}
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;
switch (meta.m_type)
{
case Lv2Ports::Type::Control:
{
auto 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());
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
if(meta.def() < meta.min(sr) || meta.def() > meta.max(sr))
{
qWarning() << "Warning: Plugin"
<< qStringFromPluginNode(m_plugin, lilv_plugin_get_name)
<< "(URI:"
<< lilv_node_as_uri(lilv_plugin_get_uri(m_plugin))
<< ") has a default value for port"
<< dispName
<< "which is not in range [min, max].";
}
switch (meta.m_vis)
{
case Lv2Ports::Vis::Generic:
{
// allow ~1000 steps
float stepSize = (meta.max(sr) - meta.min(sr)) / 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.def(), meta.min(sr), meta.max(sr),
stepSize, nullptr, dispName));
break;
}
case Lv2Ports::Vis::Integer:
ctrl->m_connectedModel.reset(
new IntModel(static_cast<int>(meta.def()),
static_cast<int>(meta.min(sr)),
static_cast<int>(meta.max(sr)),
nullptr, dispName));
break;
case Lv2Ports::Vis::Enumeration:
{
ComboBoxModel* comboModel = new ComboBoxModel(nullptr, dispName);
{
AutoLilvScalePoints sps (static_cast<LilvScalePoints*>(lilv_port_get_scale_points(m_plugin, lilvPort)));
// temporary map, since lilv may return scale points in random order
std::map<float, const char*> scalePointMap;
LILV_FOREACH(scale_points, i, sps.get())
{
const LilvScalePoint* sp = lilv_scale_points_get(sps.get(), i);
const float f = lilv_node_as_float(lilv_scale_point_get_value(sp));
const char* s = lilv_node_as_string(lilv_scale_point_get_label(sp));
scalePointMap[f] = s;
}
for (const auto& [f,s] : scalePointMap)
{
ctrl->m_scalePointMap.push_back(f);
comboModel->addItem(s);
}
}
for(std::size_t i = 0; i < ctrl->m_scalePointMap.size(); ++i)
{
if(meta.def() == ctrl->m_scalePointMap[i])
{
comboModel->setValue(i);
comboModel->setInitValue(i);
break;
}
}
ctrl->m_connectedModel.reset(comboModel);
break;
}
case Lv2Ports::Vis::Toggled:
ctrl->m_connectedModel.reset(
new BoolModel(static_cast<bool>(meta.def()),
nullptr, dispName));
break;
}
if(meta.m_logarithmic)
{
ctrl->m_connectedModel->setScaleLogarithmic();
}
} // if m_flow == Input
port = ctrl;
break;
}
case Lv2Ports::Type::Audio:
{
auto audio = new Lv2Ports::Audio(static_cast<std::size_t>(Engine::audioEngine()->framesPerPeriod()),
portIsSideChain(m_plugin, lilvPort));
port = audio;
break;
}
case Lv2Ports::Type::AtomSeq:
{
auto atomPort = new Lv2Ports::AtomSeq;
{
AutoLilvNode uriAtomSupports(Engine::getLv2Manager()->uri(LV2_ATOM__supports));
AutoLilvNodes atomSupports(lilv_port_get_value(m_plugin, lilvPort, uriAtomSupports.get()));
AutoLilvNode uriMidiEvent(Engine::getLv2Manager()->uri(LV2_MIDI__MidiEvent));
LILV_FOREACH (nodes, itr, atomSupports.get())
{
if(lilv_node_equals(lilv_nodes_get(atomSupports.get(), itr), uriMidiEvent.get()))
{
atomPort->flags |= Lv2Ports::AtomSeq::FlagType::Midi;
}
}
}
int minimumSize = defaultEvbufSize();
Lv2Manager* mgr = Engine::getLv2Manager();
// check for alternative minimum size
{
AutoLilvNode rszMinimumSize = mgr->uri(LV2_RESIZE_PORT__minimumSize);
AutoLilvNodes minSizeV(lilv_port_get_value(m_plugin, lilvPort, rszMinimumSize.get()));
LilvNode* minSize = minSizeV ? lilv_nodes_get_first(minSizeV.get()) : nullptr;
if (minSize && lilv_node_is_int(minSize))
{
minimumSize = std::max(minimumSize, lilv_node_as_int(minSize));
}
}
atomPort->m_buf.reset(
lv2_evbuf_new(static_cast<uint32_t>(minimumSize),
mgr->uridMap().map(LV2_ATOM__Chunk),
mgr->uridMap().map(LV2_ATOM__Sequence)));
port = atomPort;
break;
}
default:
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; }
}
}
void visit(Lv2Ports::AtomSeq& atomPort) override
{
if(atomPort.m_flow == Lv2Ports::Flow::Input)
{
if(atomPort.flags & Lv2Ports::AtomSeq::FlagType::Midi)
{
// take any MIDI input, prefer mandatory MIDI input
// (Lv2Proc::check() assures there are <=1 mandatory MIDI
// input ports)
if(!m_proc->m_midiIn || !atomPort.m_optional)
m_proc->m_midiIn = &atomPort;
}
}
else if(atomPort.m_flow == Lv2Ports::Flow::Output)
{
if(atomPort.flags & Lv2Ports::AtomSeq::FlagType::Midi)
{
// take any MIDI output, prefer mandatory MIDI output
// (Lv2Proc::check() assures there are <=1 mandatory MIDI
// output ports)
if(!m_proc->m_midiOut || !atomPort.m_optional)
m_proc->m_midiOut = &atomPort;
}
}
else { Q_ASSERT(false); }
}
};
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::AtomSeq& atomSeq) override
{
connectPort(lv2_evbuf_get_buffer(atomSeq.m_buf.get()));
}
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 = default;
};
// !This function must be realtime safe!
// use createPort to create any port before connecting
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)
{
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
qDebug() << " default:" << port.def();
qDebug() << " min:" << port.min(sr);
qDebug() << " max:" << port.max(sr);
}
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);
}
} // namespace lmms
#endif // LMMS_HAVE_LV2