From 0de47b5300bc09371e0102237d0caf0a1e8b193f Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:54:07 +0200 Subject: [PATCH 1/8] Lv2ViewProc: Improve variable names --- include/Lv2ViewBase.h | 2 +- src/gui/Lv2ViewBase.cpp | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/Lv2ViewBase.h b/include/Lv2ViewBase.h index f7d0e9bcb4f..3c8f1bc3faf 100644 --- a/include/Lv2ViewBase.h +++ b/include/Lv2ViewBase.h @@ -56,7 +56,7 @@ class Lv2ViewProc : public LinkedModelGroupView { public: //! @param colNum numbers of columns for the controls - Lv2ViewProc(QWidget *parent, Lv2Proc *ctrlBase, int colNum); + Lv2ViewProc(QWidget *parent, Lv2Proc *proc, int colNum); ~Lv2ViewProc() override = default; private: diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index 3fd1d44b109..12be5e06924 100644 --- a/src/gui/Lv2ViewBase.cpp +++ b/src/gui/Lv2ViewBase.cpp @@ -51,13 +51,13 @@ namespace lmms::gui { -Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) : - LinkedModelGroupView (parent, ctrlBase, colNum) +Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) : + LinkedModelGroupView (parent, proc, colNum) { - class SetupWidget : public Lv2Ports::ConstVisitor + class SetupTheWidget : public Lv2Ports::ConstVisitor { public: - QWidget* m_par; // input + QWidget* m_parent; // input const LilvNode* m_commentUri; // input Control* m_control = nullptr; // output void visit(const Lv2Ports::Control& port) override @@ -69,20 +69,20 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) : switch (port.m_vis) { case PortVis::Generic: - m_control = new KnobControl(m_par); + m_control = new KnobControl(m_parent); break; case PortVis::Integer: { sample_rate_t sr = Engine::audioEngine()->processingSampleRate(); m_control = new LcdControl((port.max(sr) <= 9.0f) ? 1 : 2, - m_par); + m_parent); break; } case PortVis::Enumeration: - m_control = new ComboControl(m_par); + m_control = new ComboControl(m_parent); break; case PortVis::Toggled: - m_control = new CheckControl(m_par); + m_control = new CheckControl(m_parent); break; } m_control->setText(port.name()); @@ -100,14 +100,14 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) : }; AutoLilvNode commentUri = uri(LILV_NS_RDFS "comment"); - ctrlBase->foreach_port( + proc->foreach_port( [this, &commentUri](const Lv2Ports::PortBase* port) { if(!lilv_port_has_property(port->m_plugin, port->m_port, uri(LV2_PORT_PROPS__notOnGUI).get())) { - SetupWidget setup; - setup.m_par = this; + SetupTheWidget setup; + setup.m_parent = this; setup.m_commentUri = commentUri.get(); port->accept(setup); From a1bdf338fe19263adeb880e5ad50256cbb2d0019 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:58:52 +0200 Subject: [PATCH 2/8] Lv2: Improve LED widget's digit calculation This improves the way digits are calculated for display in `Lv2ViewProc`: - More than 2 digits are now recognized - Minus signs are now recognized - Tests have been added --- include/lmms_math.h | 26 ++++++++++++++++++ src/gui/Lv2ViewBase.cpp | 7 +++-- tests/CMakeLists.txt | 1 + tests/src/core/MathTest.cpp | 53 +++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/src/core/MathTest.cpp diff --git a/include/lmms_math.h b/include/lmms_math.h index b62da81c249..ea0a75581e8 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -325,6 +325,32 @@ static inline T absMin( T a, T b ) return std::abs(a) < std::abs(b) ? a : b; } +// @brief Calculate number of digits which LcdSpinBox would show for a given number +// @note Once we upgrade to C++20, we could probably use std::formatted_size +static inline int numDigitsAsInt(float f) +{ + // use rounding: + // LcdSpinBox sometimes uses roundf(), sometimes cast rounding + // we use rounding to be on the "safe side" + const float rounded = roundf(f); + int asInt = static_cast(rounded); + int digits = 1; // always at least 1 + if(asInt < 0) + { + ++digits; + asInt = -asInt; + } + // "asInt" is positive from now + int32_t power = 1; + for(int32_t i = 1; i<10; ++i) + { + power *= 10; + if(static_cast(asInt) >= power) { ++digits; } // 2 digits for >=10, 3 for >=100 + else { break; } + } + return digits; +} + } // namespace lmms diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index 12be5e06924..830a994c8c6 100644 --- a/src/gui/Lv2ViewBase.cpp +++ b/src/gui/Lv2ViewBase.cpp @@ -39,6 +39,7 @@ #include "GuiApplication.h" #include "embed.h" #include "gui_templates.h" +#include "lmms_math.h" #include "Lv2ControlBase.h" #include "Lv2Manager.h" #include "Lv2Proc.h" @@ -74,8 +75,10 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) : case PortVis::Integer: { sample_rate_t sr = Engine::audioEngine()->processingSampleRate(); - m_control = new LcdControl((port.max(sr) <= 9.0f) ? 1 : 2, - m_parent); + auto pMin = port.min(sr); + auto pMax = port.max(sr); + int numDigits = std::max(numDigitsAsInt(pMin), numDigitsAsInt(pMax)); + m_control = new LcdControl(numDigits, m_parent); break; } case PortVis::Enumeration: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 514bf78154e..ddf9e29621b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,7 @@ ADD_EXECUTABLE(tests src/core/ArrayVectorTest.cpp src/core/AutomatableModelTest.cpp + src/core/MathTest.cpp src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp diff --git a/tests/src/core/MathTest.cpp b/tests/src/core/MathTest.cpp new file mode 100644 index 00000000000..2b6404cfd5c --- /dev/null +++ b/tests/src/core/MathTest.cpp @@ -0,0 +1,53 @@ +/* + * MathTest.cpp + * + * Copyright (c) 2023 Johannes Lorenz + * + * 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 "QTestSuite.h" + +#include "lmms_math.h" + +#include + +class MathTest : QTestSuite +{ + Q_OBJECT +private slots: + void NumDigitsTest() + { + using namespace lmms; + QCOMPARE(numDigitsAsInt(1.f), 1); + QCOMPARE(numDigitsAsInt(9.9f), 2); + QCOMPARE(numDigitsAsInt(10.f), 2); + QCOMPARE(numDigitsAsInt(0.f), 1); + QCOMPARE(numDigitsAsInt(-100.f), 4); + QCOMPARE(numDigitsAsInt(-99.f), 3); + QCOMPARE(numDigitsAsInt(-0.4f), 1); // there is no "-0" for LED spinbox + QCOMPARE(numDigitsAsInt(-0.99f), 2); + QCOMPARE(numDigitsAsInt(1000000000), 10); + QCOMPARE(numDigitsAsInt(-1000000000), 11); + QCOMPARE(numDigitsAsInt(900000000), 9); + QCOMPARE(numDigitsAsInt(-900000000), 10); + } +} MathTests; + +#include "MathTest.moc" From ada138d3b416e4d625290a0af0c72c7fbbdcb4e2 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 23 Sep 2023 00:00:02 +0200 Subject: [PATCH 3/8] Support LV2_BUF_SIZE__powerOf2BlockLength --- src/core/lv2/Lv2Manager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index 9c62703e0a4..df6e5a7c19f 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -35,6 +35,7 @@ #include #include +#include "AudioEngine.h" #include "Engine.h" #include "Plugin.h" #include "Lv2ControlBase.h" @@ -156,6 +157,10 @@ Lv2Manager::Lv2Manager() : m_supportedFeatureURIs.insert(LV2_BUF_SIZE__boundedBlockLength); // block length is only changed initially in AudioEngine CTOR m_supportedFeatureURIs.insert(LV2_BUF_SIZE__fixedBlockLength); + if (const auto fpp = Engine::audioEngine()->framesPerPeriod(); (fpp & (fpp - 1)) == 0) // <=> ffp is power of 2 (for ffp > 0) + { + m_supportedFeatureURIs.insert(LV2_BUF_SIZE__powerOf2BlockLength); + } auto supportOpt = [this](Lv2UridCache::Id id) { From 66ca53bb086bcafa8e85673e9b0a7cd211f58744 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:38:57 +0200 Subject: [PATCH 4/8] Add plugin blacklist for buffersize<=32 --- include/Lv2Features.h | 3 ++- include/Lv2Manager.h | 20 ++++++++++---------- src/core/lv2/Lv2Features.cpp | 6 +++--- src/core/lv2/Lv2Manager.cpp | 31 +++++++++++++++++++++---------- src/core/lv2/Lv2Proc.cpp | 16 ++++++++++++---- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/include/Lv2Features.h b/include/Lv2Features.h index b5bc284c87d..69a456bbde3 100644 --- a/include/Lv2Features.h +++ b/include/Lv2Features.h @@ -30,6 +30,7 @@ #ifdef LMMS_HAVE_LV2 #include +#include #include #include "Lv2Manager.h" @@ -78,7 +79,7 @@ class Lv2Features //! pointers to m_features, required for lilv_plugin_instantiate std::vector m_featurePointers; //! features + data, ordered by URI - std::map m_featureByUri; + std::map m_featureByUri; }; diff --git a/include/Lv2Manager.h b/include/Lv2Manager.h index 909dba5607e..58126a0a448 100644 --- a/include/Lv2Manager.h +++ b/include/Lv2Manager.h @@ -31,6 +31,7 @@ #include #include +#include #include #include "Lv2Basics.h" @@ -120,15 +121,9 @@ class Lv2Manager Iterator begin() { return m_lv2InfoMap.begin(); } Iterator end() { return m_lv2InfoMap.end(); } - //! strcmp based key comparator for std::set and std::map - struct CmpStr - { - bool operator()(char const *a, char const *b) const; - }; - UridMap& uridMap() { return m_uridMap; } const Lv2UridCache& uridCache() const { return m_uridCache; } - const std::set& supportedFeatureURIs() const + const std::set& supportedFeatureURIs() const { return m_supportedFeatureURIs; } @@ -136,17 +131,21 @@ class Lv2Manager AutoLilvNodes findNodes(const LilvNode *subject, const LilvNode *predicate, const LilvNode *object); - static const std::set& getPluginBlacklist() + static const std::set& getPluginBlacklist() { return pluginBlacklist; } + static const std::set& getPluginBlacklistBuffersizeLessThan32() + { + return pluginBlacklistBuffersizeLessThan32; + } private: // general data bool m_debug; //!< if set, debug output will be printed LilvWorld* m_world; Lv2InfoMap m_lv2InfoMap; - std::set m_supportedFeatureURIs; + std::set m_supportedFeatureURIs; // feature data that are common for all Lv2Proc UridMap m_uridMap; @@ -155,7 +154,8 @@ class Lv2Manager Lv2UridCache m_uridCache; // static - static const std::set pluginBlacklist; + static const std::set + pluginBlacklist, pluginBlacklistBuffersizeLessThan32; // functions bool isSubclassOf(const LilvPluginClass *clvss, const char *uriStr); diff --git a/src/core/lv2/Lv2Features.cpp b/src/core/lv2/Lv2Features.cpp index 6e74a89360c..c8fc0546517 100644 --- a/src/core/lv2/Lv2Features.cpp +++ b/src/core/lv2/Lv2Features.cpp @@ -48,7 +48,7 @@ Lv2Features::Lv2Features() { const Lv2Manager* man = Engine::getLv2Manager(); // create (yet empty) map feature URI -> feature - for(const char* uri : man->supportedFeatureURIs()) + for(auto uri : man->supportedFeatureURIs()) { m_featureByUri.emplace(uri, nullptr); } @@ -71,7 +71,7 @@ void Lv2Features::initCommon() void Lv2Features::createFeatureVectors() { // create vector of features - for(std::pair& pr : m_featureByUri) + for(const auto& [uri, feature] : m_featureByUri) { /* If pr.second is nullptr here, this means that the LV2_feature @@ -82,7 +82,7 @@ void Lv2Features::createFeatureVectors() vector creation (This can be done in Lv2Proc::initPluginSpecificFeatures or in Lv2Features::initCommon) */ - m_features.push_back(LV2_Feature { pr.first, pr.second }); + m_features.push_back(LV2_Feature{(const char*)uri.data(), (void*)feature}); } // create pointer vector (for lilv_plugin_instantiate) diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index df6e5a7c19f..489e613b705 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -47,7 +46,7 @@ namespace lmms { -const std::set Lv2Manager::pluginBlacklist = +const std::set Lv2Manager::pluginBlacklist = { // github.com/calf-studio-gear/calf, #278 "http://calf.sourceforge.net/plugins/Analyzer", @@ -138,6 +137,26 @@ const std::set Lv2Manager::pluginBlacklist = "urn:juced:DrumSynth" }; +const std::set Lv2Manager::pluginBlacklistBuffersizeLessThan32 = +{ + "http://moddevices.com/plugins/mod-devel/2Voices", + "http://moddevices.com/plugins/mod-devel/Capo", + "http://moddevices.com/plugins/mod-devel/Drop", + "http://moddevices.com/plugins/mod-devel/Harmonizer", + "http://moddevices.com/plugins/mod-devel/Harmonizer2", + "http://moddevices.com/plugins/mod-devel/HarmonizerCS", + "http://moddevices.com/plugins/mod-devel/SuperCapo", + "http://moddevices.com/plugins/mod-devel/SuperWhammy", + "http://moddevices.com/plugins/mod-devel/Gx2Voices", + "http://moddevices.com/plugins/mod-devel/GxCapo", + "http://moddevices.com/plugins/mod-devel/GxDrop", + "http://moddevices.com/plugins/mod-devel/GxHarmonizer", + "http://moddevices.com/plugins/mod-devel/GxHarmonizer2", + "http://moddevices.com/plugins/mod-devel/GxHarmonizerCS", + "http://moddevices.com/plugins/mod-devel/GxSuperCapo", + "http://moddevices.com/plugins/mod-devel/GxSuperWhammy" +}; + @@ -293,14 +312,6 @@ void Lv2Manager::initPlugins() -bool Lv2Manager::CmpStr::operator()(const char *a, const char *b) const -{ - return std::strcmp(a, b) < 0; -} - - - - bool Lv2Manager::isFeatureSupported(const char *featName) const { return m_supportedFeatureURIs.find(featName) != m_supportedFeatureURIs.end(); diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index e0541b948c9..6776a26ed63 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -75,11 +75,19 @@ Plugin::Type Lv2Proc::check(const LilvPlugin *plugin, // TODO: manage a global blacklist outside of the code // for now, this will help // this is only a fix for the meantime - const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist(); - if (!Engine::ignorePluginBlacklist() && - pluginBlacklist.find(pluginUri) != pluginBlacklist.end()) + if (!Engine::ignorePluginBlacklist()) { - issues.emplace_back(PluginIssueType::Blacklisted); + 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) From 243e28b20ccd0578b9b91e4c0800782d03360f44 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:56:45 +0200 Subject: [PATCH 5/8] SetupDialog: Warn of unusual buffersizes This displays a warning dialog if the users requests unusual buffersizes: - buffersizes less than 32 - buffersizes which are not a (natural number) power of 2 This commit also replaces some `setGeometry` stuff by `QBoxLayout`. --- include/SetupDialog.h | 2 ++ src/gui/modals/SetupDialog.cpp | 38 ++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/include/SetupDialog.h b/include/SetupDialog.h index de4cdd9ddbd..fa41325db79 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -102,6 +102,7 @@ private slots: // Audio settings widget. void audioInterfaceChanged(const QString & driver); void toggleHQAudioDev(bool enabled); + void updateBufferSizeWarning(int value); void setBufferSize(int value); void resetBufferSize(); @@ -179,6 +180,7 @@ private slots: int m_bufferSize; QSlider * m_bufferSizeSlider; QLabel * m_bufferSizeLbl; + QLabel * m_bufferSizeWarnLbl; // MIDI settings widgets. QComboBox * m_midiInterfaces; diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 63b84506e46..0266285a7a4 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -579,32 +579,39 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Buffer size tab. auto bufferSize_tw = new TabWidget(tr("Buffer size"), audio_w); - bufferSize_tw->setFixedHeight(76); + auto bufferSize_layout = new QVBoxLayout(bufferSize_tw); + bufferSize_layout->setSpacing(10); + bufferSize_layout->setContentsMargins(10, 18, 10, 10); m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSize_tw); m_bufferSizeSlider->setRange(1, 128); m_bufferSizeSlider->setTickInterval(8); m_bufferSizeSlider->setPageStep(8); m_bufferSizeSlider->setValue(m_bufferSize / BUFFERSIZE_RESOLUTION); - m_bufferSizeSlider->setGeometry(10, 18, 340, 18); m_bufferSizeSlider->setTickPosition(QSlider::TicksBelow); + m_bufferSizeLbl = new QLabel(bufferSize_tw); + + m_bufferSizeWarnLbl = new QLabel(bufferSize_tw); + m_bufferSizeWarnLbl->setWordWrap(true); + connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBufferSize(int))); connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); - - m_bufferSizeLbl = new QLabel(bufferSize_tw); - m_bufferSizeLbl->setGeometry(10, 40, 200, 24); setBufferSize(m_bufferSizeSlider->value()); auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSize_tw); - bufferSize_reset_btn->setGeometry(320, 40, 28, 28); connect(bufferSize_reset_btn, SIGNAL(clicked()), this, SLOT(resetBufferSize())); bufferSize_reset_btn->setToolTip( tr("Reset to default value")); + bufferSize_layout->addWidget(m_bufferSizeSlider); + bufferSize_layout->addWidget(m_bufferSizeLbl); + bufferSize_layout->addWidget(m_bufferSizeWarnLbl); + bufferSize_layout->addWidget(bufferSize_reset_btn); + // Audio layout ordering. audio_layout->addWidget(audioiface_tw); @@ -1172,6 +1179,24 @@ void SetupDialog::audioInterfaceChanged(const QString & iface) } +void SetupDialog::updateBufferSizeWarning(int value) +{ + QString text = "
    "; + if((value & (value - 1)) != 0) // <=> value is not a power of 2 (for value > 0) + { + text += "
  • " + tr("The currently selected value is not a power of 2 " + "(32, 64, 128, 256, 512, 1024, ...). Some plugins may not be available.") + "
  • "; + } + if(value <= 32) + { + text += "
  • " + tr("The currently selected value is less than or equal to 32. " + "Some plugins may not be available.") + "
  • "; + } + text += "
"; + m_bufferSizeWarnLbl->setText(text); +} + + void SetupDialog::setBufferSize(int value) { const int step = DEFAULT_BUFFER_SIZE / BUFFERSIZE_RESOLUTION; @@ -1197,6 +1222,7 @@ void SetupDialog::setBufferSize(int value) m_bufferSize = value * BUFFERSIZE_RESOLUTION; m_bufferSizeLbl->setText(tr("Frames: %1\nLatency: %2 ms").arg(m_bufferSize).arg( 1000.0f * m_bufferSize / Engine::audioEngine()->processingSampleRate(), 0, 'f', 1)); + updateBufferSizeWarning(m_bufferSize); } From a3b033ccc974df9c29e91bd46e59f8fcb1b10681 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:42:45 +0200 Subject: [PATCH 6/8] Lv2Proc: Sort scale point maps (#6859) lilv can return the scale points in randomized order, and so the linked models (of stereo effects wit 2 `Lv2Proc`) may have different scale points. This would mean that the same combo enumeration value would have different float values in linked models. This problem is fixed by sorting the scale values. Also, it is more natural for users if scale points are enumerated in a numerical order. --- include/Lv2Basics.h | 6 ++++++ src/core/lv2/Lv2Proc.cpp | 28 +++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/Lv2Basics.h b/include/Lv2Basics.h index 9a958d9736a..53489e30d10 100644 --- a/include/Lv2Basics.h +++ b/include/Lv2Basics.h @@ -47,8 +47,14 @@ struct LilvNodesDeleter void operator()(LilvNodes* n) { lilv_nodes_free(n); } }; +struct LilvScalePointsDeleter +{ + void operator()(LilvScalePoints* s) { lilv_scale_points_free(s); } +}; + using AutoLilvNode = std::unique_ptr; using AutoLilvNodes = std::unique_ptr; +using AutoLilvScalePoints = std::unique_ptr; /** Return QString from a plugin's node, everything will be freed automatically diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index 6776a26ed63..a5fdf404156 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -574,19 +574,25 @@ void Lv2Proc::createPort(std::size_t portNum) break; case Lv2Ports::Vis::Enumeration: { - auto comboModel = new ComboBoxModel(nullptr, dispName); - LilvScalePoints* sps = - lilv_port_get_scale_points(m_plugin, lilvPort); - LILV_FOREACH(scale_points, i, sps) + ComboBoxModel* comboModel = new ComboBoxModel(nullptr, dispName); + { - 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))); + AutoLilvScalePoints sps (static_cast(lilv_port_get_scale_points(m_plugin, lilvPort))); + // temporary map, since lilv may return scale points in random order + std::map 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); + } } - lilv_scale_points_free(sps); ctrl->m_connectedModel.reset(comboModel); // TODO: use default value on comboModel, too? break; From 665eb1bdd8608d9823e41a8d9212a978f236e276 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:42:45 +0200 Subject: [PATCH 7/8] Lv2Proc: Set def val for ComboModel, too --- src/core/lv2/Lv2Proc.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index a5fdf404156..242f3d92bc2 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -593,8 +593,16 @@ void Lv2Proc::createPort(std::size_t portNum) 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); - // TODO: use default value on comboModel, too? break; } case Lv2Ports::Vis::Toggled: From 6e4ea2240da8cee7f41466b7f157b2ea7c5dc75b Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Fri, 22 Sep 2023 23:27:02 +0200 Subject: [PATCH 8/8] Implement Lv2 Worker (#6484) --- include/AudioEngine.h | 1 + include/LmmsSemaphore.h | 93 ++++++++++++++++ include/LocklessRingBuffer.h | 3 +- include/Lv2Proc.h | 13 ++- include/Lv2Worker.h | 93 ++++++++++++++++ src/core/CMakeLists.txt | 2 + src/core/LmmsSemaphore.cpp | 143 ++++++++++++++++++++++++ src/core/lv2/Lv2Manager.cpp | 2 + src/core/lv2/Lv2Proc.cpp | 31 +++++- src/core/lv2/Lv2Worker.cpp | 203 +++++++++++++++++++++++++++++++++++ 10 files changed, 580 insertions(+), 4 deletions(-) create mode 100644 include/LmmsSemaphore.h create mode 100644 include/Lv2Worker.h create mode 100644 src/core/LmmsSemaphore.cpp create mode 100644 src/core/lv2/Lv2Worker.cpp diff --git a/include/AudioEngine.h b/include/AudioEngine.h index f056c22e108..d3d0d025ffc 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -197,6 +197,7 @@ class LMMS_EXPORT AudioEngine : public QObject // audio-device-stuff + bool renderOnly() const { return m_renderOnly; } // Returns the current audio device's name. This is not necessarily // the user's preferred audio device, in case you were thinking that. inline const QString & audioDevName() const diff --git a/include/LmmsSemaphore.h b/include/LmmsSemaphore.h new file mode 100644 index 00000000000..4170eef6c65 --- /dev/null +++ b/include/LmmsSemaphore.h @@ -0,0 +1,93 @@ +/* + * Semaphore.h - Semaphore declaration + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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. + * + */ + +/* + * This code has been copied and adapted from https://github.com/drobilla/jalv + * File src/zix/sem.h + */ + +#ifndef LMMS_SEMAPHORE_H +#define LMMS_SEMAPHORE_H + +#include "lmmsconfig.h" + +#ifdef LMMS_BUILD_APPLE +# include +#elif defined(LMMS_BUILD_WIN32) +# include +#else +# include +#endif + +#include + +namespace lmms { + +/** + A counting semaphore. + + This is an integer that is always positive, and has two main operations: + increment (post) and decrement (wait). If a decrement can not be performed + (i.e. the value is 0) the caller will be blocked until another thread posts + and the operation can succeed. + + Semaphores can be created with any starting value, but typically this will + be 0 so the semaphore can be used as a simple signal where each post + corresponds to one wait. + + Semaphores are very efficient (much moreso than a mutex/cond pair). In + particular, at least on Linux, post is async-signal-safe, which means it + does not block and will not be interrupted. If you need to signal from + a realtime thread, this is the most appropriate primitive to use. + + @note Likely outdated with C++20's std::counting_semaphore + (though we have to check that this will be RT conforming on all platforms) +*/ +class Semaphore +{ +public: + Semaphore(unsigned initial); + Semaphore(const Semaphore&) = delete; + Semaphore& operator=(const Semaphore&) = delete; + Semaphore(Semaphore&&) = delete; + Semaphore& operator=(Semaphore&&) = delete; + ~Semaphore(); + + void post(); + void wait(); + bool tryWait(); + +private: +#ifdef LMMS_BUILD_APPLE + semaphore_t m_sem; +#elif defined(LMMS_BUILD_WIN32) + HANDLE m_sem; +#else + sem_t m_sem; +#endif +}; + +} // namespace lmms + +#endif // LMMS_SEMAPHORE_H diff --git a/include/LocklessRingBuffer.h b/include/LocklessRingBuffer.h index 99c48cc904a..2d65badfe58 100644 --- a/include/LocklessRingBuffer.h +++ b/include/LocklessRingBuffer.h @@ -51,13 +51,14 @@ class LocklessRingBuffer std::size_t capacity() const {return m_buffer.maximum_eventual_write_space();} std::size_t free() const {return m_buffer.write_space();} void wakeAll() {m_notifier.wakeAll();} - std::size_t write(const sampleFrame *src, std::size_t cnt, bool notify = false) + std::size_t write(const T *src, std::size_t cnt, bool notify = false) { std::size_t written = LocklessRingBuffer::m_buffer.write(src, cnt); // Let all waiting readers know new data are available. if (notify) {LocklessRingBuffer::m_notifier.wakeAll();} return written; } + void mlock() { m_buffer.mlock(); } protected: ringbuffer_t m_buffer; diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index 62070def7f6..76fa5eec25b 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -1,7 +1,7 @@ /* * Lv2Proc.h - Lv2 processor class * - * Copyright (c) 2019-2020 Johannes Lorenz + * Copyright (c) 2019-2022 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -31,11 +31,14 @@ #include #include +#include +#include "LinkedModelGroups.h" +#include "LmmsSemaphore.h" #include "Lv2Basics.h" #include "Lv2Features.h" #include "Lv2Options.h" -#include "LinkedModelGroups.h" +#include "Lv2Worker.h" #include "Plugin.h" #include "../src/3rdparty/ringbuffer/include/ringbuffer/ringbuffer.h" #include "TimePos.h" @@ -174,8 +177,14 @@ class Lv2Proc : public LinkedModelGroup const LilvPlugin* m_plugin; LilvInstance* m_instance; Lv2Features m_features; + + // options Lv2Options m_options; + // worker + std::optional m_worker; + Semaphore m_workLock; // this must be shared by different workers + // full list of ports std::vector> m_ports; // quick reference to specific, unique ports diff --git a/include/Lv2Worker.h b/include/Lv2Worker.h new file mode 100644 index 00000000000..7931f8e7cde --- /dev/null +++ b/include/Lv2Worker.h @@ -0,0 +1,93 @@ +/* + * Lv2Worker.h - Lv2Worker class + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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 LV2WORKER_H +#define LV2WORKER_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_LV2 + +#include +#include +#include +#include + +#include "LocklessRingBuffer.h" +#include "LmmsSemaphore.h" + +namespace lmms +{ + +/** + Worker container +*/ +class Lv2Worker +{ +public: + // CTOR/DTOR/feature access + Lv2Worker(const LV2_Worker_Interface* iface, Semaphore* common_work_lock, bool threaded); + ~Lv2Worker(); + void setHandle(LV2_Handle handle) { m_handle = handle; } + LV2_Worker_Schedule* feature() { return &m_scheduleFeature; } + + // public API + void emitResponses(); + void notifyPluginThatRunFinished() + { + if(m_iface->end_run) { m_iface->end_run(m_scheduleFeature.handle); } + } + + // to be called only by static functions + LV2_Worker_Status scheduleWork(uint32_t size, const void* data); + LV2_Worker_Status respond(uint32_t size, const void* data); + +private: + // functions + void workerFunc(); + std::size_t bufferSize() const; //!< size of internal buffers + + // parameters + const LV2_Worker_Interface* m_iface; + bool m_threaded; + LV2_Handle m_handle; + LV2_Worker_Schedule m_scheduleFeature; + + // threading/synchronization + std::thread m_thread; + std::vector m_response; //!< buffer where single requests from m_requests are unpacked + LocklessRingBuffer m_requests, m_responses; //!< ringbuffer to queue multiple requests + LocklessRingBufferReader m_requestsReader, m_responsesReader; + std::atomic m_exit = false; //!< Whether the worker function should keep looping + Semaphore m_sem; + Semaphore* m_workLock; +}; + + +} // namespace lmms + +#endif // LMMS_HAVE_LV2 + +#endif // LV2WORKER_H + diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 319882af2f9..1155f5e0d22 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -70,6 +70,7 @@ set(LMMS_SRCS core/SamplePlayHandle.cpp core/SampleRecordHandle.cpp core/Scale.cpp + core/LmmsSemaphore.cpp core/SerializingObject.cpp core/Song.cpp core/TempoSyncKnobModel.cpp @@ -112,6 +113,7 @@ set(LMMS_SRCS core/lv2/Lv2SubPluginFeatures.cpp core/lv2/Lv2UridCache.cpp core/lv2/Lv2UridMap.cpp + core/lv2/Lv2Worker.cpp core/midi/MidiAlsaRaw.cpp core/midi/MidiAlsaSeq.cpp diff --git a/src/core/LmmsSemaphore.cpp b/src/core/LmmsSemaphore.cpp new file mode 100644 index 00000000000..daa70a45ba3 --- /dev/null +++ b/src/core/LmmsSemaphore.cpp @@ -0,0 +1,143 @@ +/* + * Semaphore.cpp - Semaphore implementation + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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. + * + */ + +/* + * This code has been copied and adapted from https://github.com/drobilla/jalv + * File src/zix/sem.h + */ + +#include "LmmsSemaphore.h" + +#if defined(LMMS_BUILD_WIN32) +# include +#else +# include +#endif + +#include + +namespace lmms { + +#ifdef LMMS_BUILD_APPLE +Semaphore::Semaphore(unsigned val) +{ + kern_return_t rval = semaphore_create(mach_task_self(), &m_sem, SYNC_POLICY_FIFO, val); + if(rval != 0) { + throw std::system_error(rval, std::system_category(), "Could not create semaphore"); + } +} + +Semaphore::~Semaphore() +{ + semaphore_destroy(mach_task_self(), m_sem); +} + +void Semaphore::post() +{ + semaphore_signal(m_sem); +} + +void Semaphore::wait() +{ + kern_return_t rval = semaphore_wait(m_sem); + if (rval != KERN_SUCCESS) { + throw std::system_error(rval, std::system_category(), "Waiting for semaphore failed"); + } +} + +bool Semaphore::tryWait() +{ + const mach_timespec_t zero = { 0, 0 }; + return semaphore_timedwait(m_sem, zero) == KERN_SUCCESS; +} + +#elif defined(LMMS_BUILD_WIN32) + +Semaphore::Semaphore(unsigned initial) +{ + if(CreateSemaphore(nullptr, initial, LONG_MAX, nullptr) == nullptr) { + throw std::system_error(GetLastError(), std::system_category(), "Could not create semaphore"); + } +} + +Semaphore::~Semaphore() +{ + CloseHandle(m_sem); +} + +void Semaphore::post() +{ + ReleaseSemaphore(m_sem, 1, nullptr); +} + +void Semaphore::wait() +{ + if (WaitForSingleObject(m_sem, INFINITE) != WAIT_OBJECT_0) { + throw std::system_error(GetLastError(), std::system_category(), "Waiting for semaphore failed"); + } +} + +bool Semaphore::tryWait() +{ + return WaitForSingleObject(m_sem, 0) == WAIT_OBJECT_0; +} + +#else /* !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_WIN32) */ + +Semaphore::Semaphore(unsigned initial) +{ + if(sem_init(&m_sem, 0, initial) != 0) { + throw std::system_error(errno, std::generic_category(), "Could not create semaphore"); + } +} + +Semaphore::~Semaphore() +{ + sem_destroy(&m_sem); +} + +void Semaphore::post() +{ + sem_post(&m_sem); +} + +void Semaphore::wait() +{ + while (sem_wait(&m_sem) != 0) { + if (errno != EINTR) { + throw std::system_error(errno, std::generic_category(), "Waiting for semaphore failed"); + } + /* Otherwise, interrupted, so try again. */ + } +} + +bool Semaphore::tryWait() +{ + return (sem_trywait(&m_sem) == 0); +} + +#endif + +} // namespace lmms + diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index 489e613b705..6a1b2a8af20 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -172,6 +173,7 @@ Lv2Manager::Lv2Manager() : m_supportedFeatureURIs.insert(LV2_URID__map); m_supportedFeatureURIs.insert(LV2_URID__unmap); m_supportedFeatureURIs.insert(LV2_OPTIONS__options); + m_supportedFeatureURIs.insert(LV2_WORKER__schedule); // min/max is always passed in the options m_supportedFeatureURIs.insert(LV2_BUF_SIZE__boundedBlockLength); // block length is only changed initially in AudioEngine CTOR diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index 242f3d92bc2..11290013e5b 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -1,7 +1,7 @@ /* * Lv2Proc.cpp - Lv2 processor class * - * Copyright (c) 2019-2020 Johannes Lorenz + * Copyright (c) 2019-2022 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -170,6 +171,7 @@ Plugin::Type Lv2Proc::check(const LilvPlugin *plugin, Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) : LinkedModelGroup(parent), m_plugin(plugin), + m_workLock(1), m_midiInputBuf(m_maxMidiInputEvents), m_midiInputReader(m_midiInputBuf) { @@ -360,7 +362,19 @@ void Lv2Proc::copyBuffersToCore(sampleFrame* buf, void Lv2Proc::run(fpp_t frames) { + if (m_worker) + { + // Process any worker replies + m_worker->emitResponses(); + } + lilv_instance_run(m_instance, static_cast(frames)); + + if (m_worker) + { + // Notify the plugin the run() cycle is finished + m_worker->notifyPluginThatRunFinished(); + } } @@ -428,6 +442,9 @@ void Lv2Proc::initPlugin() if (m_instance) { + if(m_worker) { + m_worker->setHandle(lilv_instance_get_handle(m_instance)); + } for (std::size_t portNum = 0; portNum < m_ports.size(); ++portNum) connectPort(portNum); lilv_instance_activate(m_instance); @@ -504,8 +521,20 @@ void Lv2Proc::initMOptions() void Lv2Proc::initPluginSpecificFeatures() { + // options initMOptions(); m_features[LV2_OPTIONS__options] = const_cast(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())) { + const auto iface = static_cast( + lilv_instance_get_extension_data(m_instance, LV2_WORKER__interface)); + bool threaded = !Engine::audioEngine()->renderOnly(); + m_worker.emplace(iface, &m_workLock, threaded); + m_features[LV2_WORKER__schedule] = m_worker->feature(); + // Note: m_worker::setHandle will still need to be called later + } } diff --git a/src/core/lv2/Lv2Worker.cpp b/src/core/lv2/Lv2Worker.cpp new file mode 100644 index 00000000000..5af955ff766 --- /dev/null +++ b/src/core/lv2/Lv2Worker.cpp @@ -0,0 +1,203 @@ +/* + * Lv2Worker.cpp - Lv2Worker implementation + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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 "Lv2Worker.h" + +#include +#include + +#ifdef LMMS_HAVE_LV2 + +#include "Engine.h" + + +namespace lmms +{ + + +// static wrappers + +static LV2_Worker_Status +staticWorkerRespond(LV2_Worker_Respond_Handle handle, + uint32_t size, const void* data) +{ + Lv2Worker* worker = static_cast(handle); + return worker->respond(size, data); +} + + + + +std::size_t Lv2Worker::bufferSize() const +{ + // ardour uses this fixed size for ALSA: + return 8192 * 4; + // for jack, they use 4 * jack_port_type_get_buffer_size (..., JACK_DEFAULT_MIDI_TYPE) + // (possible extension for AudioDevice) +} + + + + +Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface, + Semaphore* common_work_lock, + bool threaded) : + m_iface(iface), + m_threaded(threaded), + m_response(bufferSize()), + m_requests(bufferSize()), + m_responses(bufferSize()), + m_requestsReader(m_requests), + m_responsesReader(m_responses), + m_sem(0), + m_workLock(common_work_lock) +{ + assert(iface); + m_scheduleFeature.handle = static_cast(this); + m_scheduleFeature.schedule_work = [](LV2_Worker_Schedule_Handle handle, + uint32_t size, const void* data) -> LV2_Worker_Status + { + Lv2Worker* worker = static_cast(handle); + return worker->scheduleWork(size, data); + }; + + if (threaded) { m_thread = std::thread(&Lv2Worker::workerFunc, this); } + + m_requests.mlock(); + m_responses.mlock(); +} + + + + +Lv2Worker::~Lv2Worker() +{ + m_exit = true; + if(m_threaded) { + m_sem.post(); + m_thread.join(); + } +} + + + + +// Let the worker send responses to the audio thread +LV2_Worker_Status Lv2Worker::respond(uint32_t size, const void* data) +{ + if(m_threaded) + { + if(m_responses.free() < sizeof(size) + size) + { + return LV2_WORKER_ERR_NO_SPACE; + } + else + { + m_responses.write((const char*)&size, sizeof(size)); + if(size && data) { m_responses.write((const char*)data, size); } + } + } + else + { + m_iface->work_response(m_handle, size, data); + } + return LV2_WORKER_SUCCESS; +} + + + + +// Let the worker receive work from the audio thread and "work" on it +void Lv2Worker::workerFunc() +{ + std::vector buf; + uint32_t size; + while (true) { + m_sem.wait(); + if (m_exit) { break; } + const std::size_t readSpace = m_requestsReader.read_space(); + if (readSpace <= sizeof(size)) { continue; } // (should not happen) + + m_requestsReader.read(sizeof(size)).copy((char*)&size, sizeof(size)); + assert(size <= readSpace - sizeof(size)); + if(size > buf.size()) { buf.resize(size); } + if(size) { m_requestsReader.read(size).copy(buf.data(), size); } + + m_workLock->wait(); + m_iface->work(m_handle, staticWorkerRespond, this, size, buf.data()); + m_workLock->post(); + } +} + + + + +// Let the audio thread schedule work for the worker +LV2_Worker_Status Lv2Worker::scheduleWork(uint32_t size, const void *data) +{ + if (m_threaded) + { + if(m_requests.free() < sizeof(size) + size) + { + return LV2_WORKER_ERR_NO_SPACE; + } + else + { + // Schedule a request to be executed by the worker thread + m_requests.write((const char*)&size, sizeof(size)); + if(size && data) { m_requests.write((const char*)data, size); } + m_sem.post(); + } + } + else + { + // Execute work immediately in this thread + m_workLock->wait(); + m_iface->work(m_handle, staticWorkerRespond, this, size, data); + m_workLock->post(); + } + + return LV2_WORKER_SUCCESS; +} + + + + +// Let the audio thread read incoming worker responses, and process it +void Lv2Worker::emitResponses() +{ + std::size_t read_space = m_responsesReader.read_space(); + uint32_t size; + while (read_space > sizeof(size)) { + m_responsesReader.read(sizeof(size)).copy((char*)&size, sizeof(size)); + if(size) { m_responsesReader.read(size).copy(m_response.data(), size); } + m_iface->work_response(m_handle, size, m_response.data()); + read_space -= sizeof(size) + size; + } +} + + +} // namespace lmms + +#endif // LMMS_HAVE_LV2