Fixes #6401: Reload Lv2 plugin on SR change

This also includes banning blop's wavedata plugins, because they crash
on reloading. Reference: https://gitlab.com/drobilla/blop-lv2/-/issues/3
This commit is contained in:
Johannes Lorenz
2022-06-04 23:11:04 +02:00
committed by Johannes Lorenz
parent 7649f5ed24
commit f48dd0fb1f
13 changed files with 168 additions and 41 deletions

View File

@@ -77,6 +77,9 @@ public:
static Plugin::PluginTypes check(const LilvPlugin* m_plugin,
std::vector<PluginIssue> &issues);
void shutdown();
void init(Model* meAsModel);
const LilvPlugin* getPlugin() const { return m_plugin; }
Lv2Proc *control(std::size_t idx) { return m_procs[idx].get(); }
@@ -95,6 +98,7 @@ protected:
Lv2ControlBase(class Model *that, const QString& uri);
Lv2ControlBase(const Lv2ControlBase&) = delete;
~Lv2ControlBase() override;
void reload();
Lv2ControlBase& operator=(const Lv2ControlBase&) = delete;
@@ -129,8 +133,6 @@ protected:
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

View File

@@ -69,6 +69,8 @@ public:
{
return m_featurePointers.data();
}
//! Clear everything
void clear();
private:
//! feature storage

View File

@@ -68,10 +68,12 @@ public:
std::vector<PluginIssue> &issues);
/*
ctor/dtor
ctor/dtor/reload
*/
Lv2Proc(const LilvPlugin* plugin, Model *parent);
~Lv2Proc() override;
void reload();
void onSampleRateChanged();
//! Must be checked after ctor or reload
bool isValid() const { return m_valid; }

View File

@@ -38,7 +38,7 @@ Lv2FxControlDialog::Lv2FxControlDialog(Lv2FxControls *controls) :
{
if (m_reloadPluginButton) {
connect(m_reloadPluginButton, &QPushButton::clicked,
this, [this](){ lv2Controls()->reloadPlugin(); });
this, [this](){ lv2Controls()->reload(); });
}
if (m_toggleUIButton) {
connect(m_toggleUIButton, &QPushButton::toggled,
@@ -67,7 +67,9 @@ Lv2FxControls *Lv2FxControlDialog::lv2Controls()
void Lv2FxControlDialog::modelChanged()
{
Lv2ViewBase::modelChanged(lv2Controls());
connect(lv2Controls(), &Lv2FxControls::modelChanged,
this, [this](){ this->modelChanged();} );
}
} // namespace lmms::gui
} // namespace lmms::gui

View File

@@ -45,7 +45,7 @@ public:
private:
Lv2FxControls *lv2Controls();
void modelChanged() override;
void modelChanged() final;
};

View File

@@ -41,13 +41,33 @@ Lv2FxControls::Lv2FxControls(class Lv2Effect *effect, const QString& uri) :
if (isValid())
{
connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged,
this, [this](){Lv2ControlBase::reloadPlugin();});
this, [this](){onSampleRateChanged();});
}
}
void Lv2FxControls::reload()
{
Lv2ControlBase::reload();
emit modelChanged();
}
void Lv2FxControls::onSampleRateChanged()
{
// TODO: once lv2 options are implemented,
// plugins that support it might allow changing their samplerate
// through it instead of reloading
reload();
}
void Lv2FxControls::saveSettings(QDomDocument &doc, QDomElement &that)
{
Lv2ControlBase::saveSettings(doc, that);

View File

@@ -43,8 +43,11 @@ class Lv2FxControlDialog;
class Lv2FxControls : public EffectControls, public Lv2ControlBase
{
Q_OBJECT
signals:
void modelChanged();
public:
Lv2FxControls(Lv2Effect *effect, const QString &uri);
void reload();
void saveSettings(QDomDocument &_doc, QDomElement &_parent) override;
void loadSettings(const QDomElement &that) override;
@@ -60,6 +63,8 @@ private slots:
void changeControl();
private:
void onSampleRateChanged();
friend class gui::Lv2FxControlDialog;
friend class Lv2Effect;
};

View File

@@ -78,10 +78,12 @@ Lv2Instrument::Lv2Instrument(InstrumentTrack *instrumentTrackArg,
{
if (Lv2ControlBase::isValid())
{
clearRunningNotes();
connect(instrumentTrack()->pitchRangeModel(), SIGNAL(dataChanged()),
this, SLOT(updatePitchRange()), Qt::DirectConnection);
connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged,
this, [this](){Lv2ControlBase::reloadPlugin();});
this, [this](){onSampleRateChanged();});
// now we need a play-handle which cares for calling play()
auto iph = new InstrumentPlayHandle(this, instrumentTrackArg);
@@ -101,6 +103,37 @@ Lv2Instrument::~Lv2Instrument()
void Lv2Instrument::reload()
{
Lv2ControlBase::reload();
clearRunningNotes();
emit modelChanged();
}
void Lv2Instrument::clearRunningNotes()
{
#ifdef LV2_INSTRUMENT_USE_MIDI
for (int i = 0; i < NumKeys; ++i) { m_runningNotes[i] = 0; }
#endif
}
void Lv2Instrument::onSampleRateChanged()
{
// TODO: once lv2 options are implemented,
// plugins that support it might allow changing their samplerate
// through it instead of reloading
reload();
}
bool Lv2Instrument::isValid() const { return Lv2ControlBase::isValid(); }
@@ -211,7 +244,7 @@ Lv2InsView::Lv2InsView(Lv2Instrument *_instrument, QWidget *_parent) :
setAutoFillBackground(true);
if (m_reloadPluginButton) {
connect(m_reloadPluginButton, &QPushButton::clicked,
this, [this](){ this->castModel<Lv2Instrument>()->reloadPlugin();} );
this, [this](){ this->castModel<Lv2Instrument>()->reload();} );
}
if (m_toggleUIButton) {
connect(m_toggleUIButton, &QPushButton::toggled,
@@ -267,6 +300,8 @@ void Lv2InsView::dropEvent(QDropEvent *_de)
void Lv2InsView::modelChanged()
{
Lv2ViewBase::modelChanged(castModel<Lv2Instrument>());
connect(castModel<Lv2Instrument>(), &Lv2Instrument::modelChanged,
this, [this](){ this->modelChanged();} );
}

View File

@@ -50,6 +50,8 @@ class Lv2InsView;
class Lv2Instrument : public Instrument, public Lv2ControlBase
{
Q_OBJECT
signals:
void modelChanged();
public:
/*
initialization
@@ -57,6 +59,8 @@ public:
Lv2Instrument(InstrumentTrack *instrumentTrackArg,
Descriptor::SubPluginFeatures::Key* key);
~Lv2Instrument() override;
void reload();
void onSampleRateChanged();
//! Must be checked after ctor or reload
bool isValid() const;
@@ -101,6 +105,7 @@ private:
#ifdef LV2_INSTRUMENT_USE_MIDI
std::array<int, NumKeys> m_runningNotes = {};
#endif
void clearRunningNotes();
friend class gui::Lv2InsView;
};

View File

@@ -54,30 +54,7 @@ Lv2ControlBase::Lv2ControlBase(Model* that, const QString &uri) :
{
if (m_plugin)
{
int channelsLeft = DEFAULT_CHANNELS; // LMMS plugins are stereo
while (channelsLeft > 0)
{
std::unique_ptr<Lv2Proc> newOne = std::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();
}
init(that);
}
else
{
@@ -94,6 +71,53 @@ Lv2ControlBase::~Lv2ControlBase() = default;
void Lv2ControlBase::init(Model* meAsModel)
{
int channelsLeft = DEFAULT_CHANNELS; // LMMS plugins are stereo
while (channelsLeft > 0)
{
std::unique_ptr<Lv2Proc> newOne = std::make_unique<Lv2Proc>(m_plugin, meAsModel);
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();
}
}
void Lv2ControlBase::shutdown()
{
// currently nothing to do here
}
void Lv2ControlBase::reload()
{
for (const auto& c : m_procs) { c->reload(); }
}
LinkedModelGroup *Lv2ControlBase::getGroup(std::size_t idx)
{
return (m_procs.size() > idx) ? m_procs[idx].get() : nullptr;
@@ -183,14 +207,6 @@ void Lv2ControlBase::loadFile(const QString &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(); }

View File

@@ -105,6 +105,14 @@ void *&Lv2Features::operator[](const char *featName)
}
void Lv2Features::clear()
{
m_featureByUri.clear();
}
} // namespace lmms
#endif // LMMS_HAVE_LV2

View File

@@ -60,6 +60,12 @@ const std::set<const char*, Lv2Manager::CmpStr> Lv2Manager::pluginBlacklist =
"http://calf.sourceforge.net/plugins/TransientDesigner",
"http://calf.sourceforge.net/plugins/Vinyl",
// https://gitlab.com/drobilla/blop-lv2/-/issues/3
"http://drobilla.net/plugins/blop/pulse",
"http://drobilla.net/plugins/blop/sawtooth",
"http://drobilla.net/plugins/blop/square",
"http://drobilla.net/plugins/blop/triangle",
// Visualization, meters, and scopes etc., won't work until we have gui support
"http://distrho.sf.net/plugins/ProM",
"http://distrho.sf.net/plugins/glBars",

View File

@@ -31,6 +31,7 @@
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/resize-port/resize-port.h>
#include <QDebug>
#include <QDomDocument>
#include <QtGlobal>
#include "AudioEngine.h"
@@ -175,6 +176,26 @@ Lv2Proc::~Lv2Proc() { shutdownPlugin(); }
void Lv2Proc::reload()
{
// save controls, which we want to keep
QDomDocument doc;
QDomElement controls = doc.createElement("controls");
saveValues(doc, controls);
// backup construction variables
const LilvPlugin* plugin = m_plugin;
Model* parent = Model::parentModel();
// destroy everything using RAII ...
this->~Lv2Proc();
// ... and reuse it ("placement new")
new (this) Lv2Proc(plugin, parent);
// reload the controls
loadValues(controls);
}
void Lv2Proc::dumpPorts()
{
std::size_t num = 0;
@@ -424,7 +445,10 @@ void Lv2Proc::shutdownPlugin()
lilv_instance_deactivate(m_instance);
lilv_instance_free(m_instance);
m_instance = nullptr;
m_features.clear();
}
m_valid = true;
}