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:
committed by
Johannes Lorenz
parent
7649f5ed24
commit
f48dd0fb1f
@@ -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
|
||||
|
||||
@@ -69,6 +69,8 @@ public:
|
||||
{
|
||||
return m_featurePointers.data();
|
||||
}
|
||||
//! Clear everything
|
||||
void clear();
|
||||
|
||||
private:
|
||||
//! feature storage
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
|
||||
private:
|
||||
Lv2FxControls *lv2Controls();
|
||||
void modelChanged() override;
|
||||
void modelChanged() final;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();} );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -105,6 +105,14 @@ void *&Lv2Features::operator[](const char *featName)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Lv2Features::clear()
|
||||
{
|
||||
m_featureByUri.clear();
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_HAVE_LV2
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user