diff --git a/include/Lv2ControlBase.h b/include/Lv2ControlBase.h index 353941f3d..b1482afcb 100644 --- a/include/Lv2ControlBase.h +++ b/include/Lv2ControlBase.h @@ -77,6 +77,9 @@ public: static Plugin::PluginTypes check(const LilvPlugin* m_plugin, std::vector &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 diff --git a/include/Lv2Features.h b/include/Lv2Features.h index b4fd6c06d..b5bc284c8 100644 --- a/include/Lv2Features.h +++ b/include/Lv2Features.h @@ -69,6 +69,8 @@ public: { return m_featurePointers.data(); } + //! Clear everything + void clear(); private: //! feature storage diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index 7e6342ec4..8e6d2ad44 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -68,10 +68,12 @@ public: std::vector &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; } diff --git a/plugins/Lv2Effect/Lv2FxControlDialog.cpp b/plugins/Lv2Effect/Lv2FxControlDialog.cpp index d10c01cd4..1b0c05bb8 100644 --- a/plugins/Lv2Effect/Lv2FxControlDialog.cpp +++ b/plugins/Lv2Effect/Lv2FxControlDialog.cpp @@ -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 \ No newline at end of file +} // namespace lmms::gui diff --git a/plugins/Lv2Effect/Lv2FxControlDialog.h b/plugins/Lv2Effect/Lv2FxControlDialog.h index c749dc124..208a4c86a 100644 --- a/plugins/Lv2Effect/Lv2FxControlDialog.h +++ b/plugins/Lv2Effect/Lv2FxControlDialog.h @@ -45,7 +45,7 @@ public: private: Lv2FxControls *lv2Controls(); - void modelChanged() override; + void modelChanged() final; }; diff --git a/plugins/Lv2Effect/Lv2FxControls.cpp b/plugins/Lv2Effect/Lv2FxControls.cpp index a7fbfc782..2fde949d4 100644 --- a/plugins/Lv2Effect/Lv2FxControls.cpp +++ b/plugins/Lv2Effect/Lv2FxControls.cpp @@ -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); diff --git a/plugins/Lv2Effect/Lv2FxControls.h b/plugins/Lv2Effect/Lv2FxControls.h index 1d7553a34..0ae57de94 100644 --- a/plugins/Lv2Effect/Lv2FxControls.h +++ b/plugins/Lv2Effect/Lv2FxControls.h @@ -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; }; diff --git a/plugins/Lv2Instrument/Lv2Instrument.cpp b/plugins/Lv2Instrument/Lv2Instrument.cpp index 77130bcc2..e766924ec 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.cpp +++ b/plugins/Lv2Instrument/Lv2Instrument.cpp @@ -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()->reloadPlugin();} ); + this, [this](){ this->castModel()->reload();} ); } if (m_toggleUIButton) { connect(m_toggleUIButton, &QPushButton::toggled, @@ -267,6 +300,8 @@ void Lv2InsView::dropEvent(QDropEvent *_de) void Lv2InsView::modelChanged() { Lv2ViewBase::modelChanged(castModel()); + connect(castModel(), &Lv2Instrument::modelChanged, + this, [this](){ this->modelChanged();} ); } diff --git a/plugins/Lv2Instrument/Lv2Instrument.h b/plugins/Lv2Instrument/Lv2Instrument.h index 38ffdd680..8eaf9b1d4 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.h +++ b/plugins/Lv2Instrument/Lv2Instrument.h @@ -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 m_runningNotes = {}; #endif + void clearRunningNotes(); friend class gui::Lv2InsView; }; diff --git a/src/core/lv2/Lv2ControlBase.cpp b/src/core/lv2/Lv2ControlBase.cpp index 3ab8b278c..7764313fc 100644 --- a/src/core/lv2/Lv2ControlBase.cpp +++ b/src/core/lv2/Lv2ControlBase.cpp @@ -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 newOne = std::make_unique(m_plugin, that); - if (newOne->isValid()) - { - channelsLeft -= std::max( - 1 + static_cast(newOne->inPorts().m_right), - 1 + static_cast(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 newOne = std::make_unique(m_plugin, meAsModel); + if (newOne->isValid()) + { + channelsLeft -= std::max( + 1 + static_cast(newOne->inPorts().m_right), + 1 + static_cast(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(); } diff --git a/src/core/lv2/Lv2Features.cpp b/src/core/lv2/Lv2Features.cpp index be7fb6e28..6e74a8936 100644 --- a/src/core/lv2/Lv2Features.cpp +++ b/src/core/lv2/Lv2Features.cpp @@ -105,6 +105,14 @@ void *&Lv2Features::operator[](const char *featName) } + + +void Lv2Features::clear() +{ + m_featureByUri.clear(); +} + + } // namespace lmms #endif // LMMS_HAVE_LV2 diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index b1391b065..d60bf6c49 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -60,6 +60,12 @@ const std::set 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", diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index cbb4be2d2..31af47a63 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #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; }