diff --git a/include/Plugin.h b/include/Plugin.h index 100e9f658eb..2583c5acae2 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -88,7 +88,7 @@ class LMMS_EXPORT Plugin : public Model, public JournallingObject //! Descriptor holds information about a plugin - every external plugin //! has to instantiate such a Descriptor in an extern "C"-section so that //! the plugin-loader is able to access information about the plugin - struct Descriptor + struct LMMS_EXPORT Descriptor { const char * name; const char * displayName; @@ -132,7 +132,7 @@ class LMMS_EXPORT Plugin : public Model, public JournallingObject SubPluginFeatures, which are like values in a key-value map). */ - struct Key + struct LMMS_EXPORT Key { using AttributeMap = QMap; diff --git a/include/SetupDialog.h b/include/SetupDialog.h index 63418bd8ddb..82dc276be80 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -98,6 +98,7 @@ private slots: void toggleAnimateAFP(bool enabled); void vstEmbedMethodChanged(); void toggleVSTAlwaysOnTop(bool en); + void toggleVstScanExec(bool enabled) { m_vstScanExec = enabled; }; void toggleDisableAutoQuit(bool enabled); void toggleMixSanitization(bool enabled); @@ -173,6 +174,7 @@ private slots: QString m_vstEmbedMethod; QCheckBox * m_vstAlwaysOnTopCheckBox; bool m_vstAlwaysOnTop; + bool m_vstScanExec; bool m_disableAutoQuit; using AswMap = QMap; diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index ce69b2eb569..9ed9aebe2a1 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -54,6 +54,8 @@ #include "SubWindow.h" #include "TextFloat.h" #include "VstPlugin.h" +#include "VstSubPluginFeatures.h" +#include "VstList.h" namespace lmms { @@ -81,7 +83,9 @@ Plugin::Descriptor Q_DECL_EXPORT vestige_plugin_descriptor = "so", # endif #endif - nullptr, + (!ConfigManager::inst()->value("app", "vstscanexec", "0").toInt() || VstList::inst()->plugins().empty()) + ? nullptr // makes Vestige appear as a plugin by itself + : new VstSubPluginFeatures(Plugin::Type::Instrument), } ; } @@ -149,8 +153,8 @@ class VstInstrumentPlugin : public VstPlugin -VestigeInstrument::VestigeInstrument( InstrumentTrack * _instrument_track ) : - Instrument(_instrument_track, &vestige_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsMidiBased), +VestigeInstrument::VestigeInstrument(InstrumentTrack* instrumentTrack, Plugin::Descriptor::SubPluginFeatures::Key* key) : + Instrument(instrumentTrack, &vestige_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsMidiBased), m_plugin( nullptr ), m_pluginMutex(), m_subWindow( nullptr ), @@ -159,12 +163,17 @@ VestigeInstrument::VestigeInstrument( InstrumentTrack * _instrument_track ) : p_subWindow( nullptr ) { // now we need a play-handle which cares for calling play() - auto iph = new InstrumentPlayHandle(this, _instrument_track); + auto iph = new InstrumentPlayHandle(this, instrumentTrack); Engine::audioEngine()->addPlayHandle( iph ); connect( ConfigManager::inst(), SIGNAL( valueChanged(QString,QString,QString) ), this, SLOT( handleConfigChange(QString, QString, QString) ), Qt::QueuedConnection ); + if (auto& path = key->attributes["file"]; !path.isEmpty()) + { + loadFile(path); + setDisplayName(key->displayName()); + } } @@ -1182,9 +1191,12 @@ extern "C" { // necessary for getting instance out of shared lib -Q_DECL_EXPORT Plugin * lmms_plugin_main( Model *m, void * ) +Q_DECL_EXPORT Plugin * lmms_plugin_main(Model* m, void* data) { - return new VestigeInstrument( static_cast( m ) ); + return new VestigeInstrument{ + static_cast(m), + static_cast(data) + }; } diff --git a/plugins/Vestige/Vestige.h b/plugins/Vestige/Vestige.h index 8dd955c770a..f2bf86a9aee 100644 --- a/plugins/Vestige/Vestige.h +++ b/plugins/Vestige/Vestige.h @@ -58,7 +58,7 @@ class VestigeInstrument : public Instrument { Q_OBJECT public: - VestigeInstrument( InstrumentTrack * _instrument_track ); + VestigeInstrument(InstrumentTrack* instrumentTrack, Plugin::Descriptor::SubPluginFeatures::Key* data); virtual ~VestigeInstrument(); virtual void play( SampleFrame* _working_buffer ); diff --git a/plugins/VstBase/RemoteVstPlugin.cpp b/plugins/VstBase/RemoteVstPlugin.cpp index 7343078ea89..eccf7be1f99 100644 --- a/plugins/VstBase/RemoteVstPlugin.cpp +++ b/plugins/VstBase/RemoteVstPlugin.cpp @@ -171,9 +171,9 @@ class RemoteVstPlugin : public RemotePluginClient { public: #ifdef SYNC_WITH_SHM_FIFO - RemoteVstPlugin( const std::string& _shm_in, const std::string& _shm_out ); + RemoteVstPlugin(const std::string& _shm_in, const std::string& _shm_out, bool skipInit = false); #else - RemoteVstPlugin( const char * socketPath ); + RemoteVstPlugin(const char* socketPath, bool skipInit = false); #endif virtual ~RemoteVstPlugin(); @@ -491,16 +491,17 @@ class RemoteVstPlugin : public RemotePluginClient }; Sync m_sync; + bool m_skipInit; }; #ifdef SYNC_WITH_SHM_FIFO -RemoteVstPlugin::RemoteVstPlugin( const std::string& _shm_in, const std::string& _shm_out ) : +RemoteVstPlugin::RemoteVstPlugin(const std::string& _shm_in, const std::string& _shm_out, bool skipInit) : RemotePluginClient( _shm_in, _shm_out ), #else -RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : +RemoteVstPlugin::RemoteVstPlugin(const char * socketPath, bool skipInit) : RemotePluginClient( socketPath ), #endif m_libInst( nullptr ), @@ -519,7 +520,8 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_midiEvents(), m_bpm( 0 ), m_currentSamplePos( 0 ), - m_currentProgram(-1) + m_currentProgram(-1), + m_skipInit{skipInit} { __plugin = this; @@ -749,26 +751,25 @@ void RemoteVstPlugin::init( const std::string & _plugin_file ) setResumed( true ); - debugMessage( "creating editor\n" ); - initEditor(); - debugMessage( "editor successfully created\n" ); + if (!m_skipInit) { + debugMessage( "creating editor\n" ); + initEditor(); + debugMessage( "editor successfully created\n" ); + } // now post some information about our plugin - sendMessage( message( IdVstPluginWindowID ).addInt( m_windowID ) ); - - sendMessage( message( IdVstPluginEditorGeometry ). - addInt( m_windowWidth ). - addInt( m_windowHeight ) ); - sendMessage( message( IdVstPluginName ).addString( pluginName() ) ); - debugMessage( std::string("plugin name: ") + pluginName() + "\n" ); - sendMessage( message( IdVstPluginVersion ).addInt( pluginVersion() ) ); - sendMessage( message( IdVstPluginVendorString ). - addString( pluginVendorString() ) ); - sendMessage( message( IdVstPluginProductString ). - addString( pluginProductString() ) ); - sendMessage( message( IdVstParameterCount ). - addInt( m_plugin->numParams ) ); + sendMessage(message(IdVstPluginWindowID).addInt(m_windowID)); + + sendMessage(message(IdVstPluginEditorGeometry).addInt(m_windowWidth).addInt(m_windowHeight)); + sendMessage(message(IdVstPluginName).addString(pluginName())); + debugMessage(std::string("plugin name: ") + pluginName() + "\n"); + sendMessage(message(IdVstPluginVersion).addInt(pluginVersion())); + sendMessage(message(IdVstPluginVendorString).addString(pluginVendorString())); + sendMessage(message(IdVstPluginProductString).addString(pluginProductString())); + sendMessage(message(IdVstParameterCount).addInt(m_plugin->numParams)); + + if (m_plugin->flags & effFlagsIsSynth) { sendMessage(message(IdVstIsSynth)); } sendMessage( IdInitDone ); @@ -1034,7 +1035,7 @@ bool RemoteVstPlugin::load( const std::string & _plugin_file ) sendMessage( message( IdVstPluginUniqueID ).addString( id ) ); - pluginDispatch( effOpen ); + if (!m_skipInit){ pluginDispatch(effOpen); } return true; } @@ -2480,9 +2481,9 @@ int main( int _argc, char * * _argv ) using lmms::RemoteVstPlugin; #ifdef SYNC_WITH_SHM_FIFO - if( _argc < 4 ) + if( _argc < 5 ) #else - if( _argc < 3 ) + if( _argc < 4 ) #endif { fprintf( stderr, "not enough arguments\n" ); @@ -2540,6 +2541,7 @@ int main( int _argc, char * * _argv ) } #endif + bool skipInit; { #ifdef SYNC_WITH_SHM_FIFO int embedMethodIndex = 3; @@ -2547,6 +2549,7 @@ int main( int _argc, char * * _argv ) int embedMethodIndex = 2; #endif std::string embedMethod = _argv[embedMethodIndex]; + skipInit = _argv[embedMethodIndex+1][0] != '0'; if ( embedMethod == "none" ) { @@ -2590,9 +2593,9 @@ int main( int _argc, char * * _argv ) // constructor automatically will process messages until it receives // a IdVstLoadPlugin message and processes it #ifdef SYNC_WITH_SHM_FIFO - __plugin = new RemoteVstPlugin( _argv[1], _argv[2] ); + __plugin = new RemoteVstPlugin{_argv[1], _argv[2], skipInit}; #else - __plugin = new RemoteVstPlugin( _argv[1] ); + __plugin = new RemoteVstPlugin{_argv[1], skipInit}; #endif if( __plugin->isInitialized() ) diff --git a/plugins/VstBase/VstList.cpp b/plugins/VstBase/VstList.cpp new file mode 100644 index 00000000000..8116d24de9d --- /dev/null +++ b/plugins/VstBase/VstList.cpp @@ -0,0 +1,322 @@ +#include "VstList.h" + +#include "VstPlugin.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ConfigManager.h" +#include "GuiApplication.h" +#include "lmmsconfig.h" + +namespace fs = std::filesystem; + +namespace +{ + +lmms::VstList::Metadata::Checksum checksum(fs::path filePath) +{ +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) + QFile file{QString::fromStdString(filePath.string())}; +#else + QFile file{filePath}; +#endif + if (!file.open(QFile::ReadOnly)) { return 0; } + QCryptographicHash hash{QCryptographicHash::Sha256}; + if (!hash.addData(&file)) { return 0; } + + lmms::VstList::Metadata::Checksum out; + memcpy(&out, hash.result().constData(), sizeof out); + + return out; +} + +fs::path cacheFilePath() +{ + auto cacheDir = fs::path{QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).toStdString()} / "lmms"; + fs::create_directories(cacheDir); + return cacheDir / "vst-list.bin"; +} + +fs::path toWinePath(const fs::path& winPath) +{ + if (const char* wineprefix_str = std::getenv("WINEPREFIX")) + { + return fs::path{wineprefix_str} / "drive_c" / winPath.relative_path(); + } + else if (const char* home_str = std::getenv("HOME")) + { + return fs::path{home_str} / ".wine" / "drive_c" / winPath.relative_path(); + } + return {}; +} + +namespace VstPaths +{ + const fs::path Win64 = "C:/Program Files/Steinberg/VstPlugins"; + const fs::path Linux64 = "/usr/lib64/vst"; + const fs::path Linux32 = "/usr/lib/vst"; + const fs::path Wine64 = toWinePath(Win64); +} + +// helpers for cache I/O + +template +inline void writeBin(std::ostream& out, const T& x) +{ + out.write(reinterpret_cast(&x), sizeof x); +} + +template +inline void readBin(std::istream& out, T& x) +{ + out.read(reinterpret_cast(&x), sizeof x); +} + +} // namespace + + +namespace lmms +{ + + +VstList* VstList::s_inst = nullptr; + + +VstList::VstList(bool initDefaultDir, bool loadNewlyFound) +{ + if (initDefaultDir) + { + loadCache(cacheFilePath()); + scanDefaultDirs(loadNewlyFound); + saveCache(cacheFilePath()); + } +} + + +void VstList::scanDefaultDirs(bool loadNewlyFound) +{ + scanDirRecursive(ConfigManager::inst()->vstDir().toStdString(), loadNewlyFound); + + #if defined(LMMS_BUILD_WIN32) + + scanDirRecursive(VstPaths::Win64, loadNewlyFound); + + #elif defined(LMMS_BUILD_LINUX) + + scanDirRecursive(VstPaths::Linux64, loadNewlyFound); + scanDirRecursive(VstPaths::Linux32, loadNewlyFound); + + #if defined(LMMS_HAVE_VST_32) || defined(LMMS_HAVE_VST_64) + + if (!VstPaths::Wine64.empty()) { scanDirRecursive(VstPaths::Wine64, loadNewlyFound); } + #endif + #endif +} + + +void VstList::scanDirRecursive(fs::path dirPath, bool loadNewlyFound) +{ + if (!fs::exists(dirPath)) { return; } + for (const auto& entry : fs::directory_iterator{dirPath}) + { + const fs::path& path = entry.path(); + + // skip loading already analyzed executables + if (m_pluginsCache.contains(path) && m_pluginsCache[path].file_checksum == checksum(path)) + { + addPlugin(m_pluginsCache[path]); + continue; + } + + // ignore "hidden" files and folders. Dot and dot-dot are skipped by C++ STL, not here. + // this doesn't go out of bounds unless the filesystem somehow has an empty string as a file name. + if (path.filename().string()[0] == '.') { continue; } + + // resolve symlinks and fetch data from the other end + const fs::file_status& stat = entry.status(); + + if (fs::is_directory(stat)) { scanDirRecursive(path, loadNewlyFound); } + else if (fs::is_regular_file(stat)) + { + if (!loadNewlyFound) { continue; } + if (const auto& ext = path.extension(); +#if defined(LMMS_BUILD_WIN32) + ext != ".dll") +#elif defined(LMMS_BUILD_LINUX) +# if defined(LMMS_HAVE_VST_32) || defined(LMMS_HAVE_VST_64) + ext != ".dll" && +# endif + ext != ".so") +#endif + { + continue; + } + +#if QT_VERSION < QT_VERSION_CHECK(6,9,0) + gui::getGUI()->displayInitProgress(QCoreApplication::translate("lmms::VstList", "Scanning VSTs: %1").arg(QString::fromStdString(path.string()))); +#else + gui::getGUI()->displayInitProgress(QCoreApplication::translate("lmms::VstList", "Scanning VSTs: %1").arg(path.string())); +#endif + + if (ConfigManager::inst()->value("app", "vstscanexec", "0").toInt()) { + // This is very slow and should be optimized more. + // TODO: There's also room for async here but i don't have experience to implement it. + VstPlugin plug{QString::fromStdString(path.string()), true}; + if (plug.name().isEmpty()) // TODO: figure out a better way to check load fail + { + m_pluginsCache.emplace(path, Metadata { + path, + checksum(path), + Metadata::PluginType::NotVst}); + continue; + } + Metadata metadata = { + path, + checksum(path), + plug.isSynth() + ? Metadata::PluginType::Instrument + : Metadata::PluginType::Effect, + plug.uniqueID().toStdString(), + plug.name().toStdString(), + plug.productString().toStdString(), + plug.vendorString().toStdString() + }; + + m_pluginsCache.insert({path, metadata}); + addPlugin(metadata); + } else { + // Ideally this branch should never be reached. When handled properly, the consumer + // plugin should instead place a nullptr to turn itself into a standalone plugin, + // and then have a file picker dialog allowing to manually select a file. + + // In future if the config value is still wanted it may be split into two - + // one that fully disables the scan and requires use of the file chooser, + // and one that disables *loading* the VSTs, but still populating the list. + // In that case this fallback would be wanted. + + // This metadata is cheap and should not be stored in cache. + + auto chksum = checksum(path); + auto name = path.stem().string(); + + // We don't know if the plugin is an instrument or an effect, so we'll add it to both. + // This is yucky, at least in LMMS vst dir it ould be better to have an explicit way to + // categorize them. We don't get such luxury when dealing with system dirs though, but + // maybe they should be ignored in this mode anyway. + addPlugin({ + path, + chksum, + Metadata::PluginType::Unknown, + "", + name, + }); + } + } + } +} + + +void VstList::addPlugin(Metadata data) +{ + if (data.type != Metadata::PluginType::NotVst) + { + m_plugins.insert_or_assign(data.file_checksum, data); + } +} + + +std::vector VstList::instrumentPlugins() +{ + std::vector out; + for (const auto& [_, data] : m_plugins + | std::views::filter([](const auto& elem){ + return elem.second.type == Metadata::PluginType::Instrument + || elem.second.type == Metadata::PluginType::Unknown; + })) + { + out.push_back(data); + } + return out; +} + +std::vector VstList::effectPlugins() +{ + std::vector out; + for (const auto& [_, data] : m_plugins + | std::views::filter([](const auto& elem){ + return elem.second.type == Metadata::PluginType::Effect + || elem.second.type == Metadata::PluginType::Unknown; + })) + { + out.push_back(data); + } + return out; +} + + +constexpr uint32_t CACHE_VER = 3; // increment this when changing the functions' output + +// `saveCache` and `loadCache` implementations should be kept in sync. +// All values should be dumped because cache existing for a file implies +// the dll shouldn't be queried for new information. That also means the cache has to be rejeced +// if it's from an old version entirely to fill new information. +void VstList::saveCache(fs::path cacheFilePath) +{ + std::ofstream cache{cacheFilePath, std::ios::binary}; + writeBin(cache, CACHE_VER); + for (const auto& [_, data] : m_pluginsCache) + { + cache << data.path.string() << '\0'; + writeBin(cache, data.file_checksum); + writeBin(cache, data.type); + cache << data.ID << '\0'; + cache << data.name << '\0'; + cache << data.product << '\0'; + cache << data.vendor << '\0'; + } +} + +void VstList::loadCache(fs::path cacheFilePath) +{ + std::ifstream cache{cacheFilePath, std::ios::binary}; + uint32_t version; + readBin(cache, version); + if (version != CACHE_VER) { return; } + while (cache.peek() != EOF) + { + VstList::Metadata data; + char buf[4096]; + + cache.getline(buf, 4096, '\0'); + data.path = fs::path{buf}; + + readBin(cache, data.file_checksum); + readBin(cache, data.type); + + cache.getline(buf, 4096, '\0'); + data.ID = buf; + + cache.getline(buf, 4096, '\0'); + data.name = buf; + + cache.getline(buf, 4096, '\0'); + data.product = buf; + + cache.getline(buf, 4096, '\0'); + data.vendor = buf; + + m_pluginsCache.insert({data.path, data}); + } +} + +} // namespace lmms diff --git a/plugins/VstBase/VstList.h b/plugins/VstBase/VstList.h new file mode 100644 index 00000000000..b99f0532018 --- /dev/null +++ b/plugins/VstBase/VstList.h @@ -0,0 +1,61 @@ +#ifndef _VST_LISTING_H +#define _VST_LISTING_H + +#include +#include +#include +#include + +#include "vstbase_export.h" + +namespace lmms +{ + + +class VSTBASE_EXPORT VstList +{ +public: + struct Metadata + { + using Checksum = std::bitset<256>; // sha256 + + std::filesystem::path path; + Checksum file_checksum; + // TODO add file hash to detect changes + enum class PluginType: uint8_t { NotVst, Instrument, Effect, Unknown } type = PluginType::NotVst; + std::string ID = ""; + std::string name = ""; + std::string product = ""; + std::string vendor = ""; + }; + + static VstList* inst() + { + if (!s_inst) { s_inst = new VstList; } + return s_inst; + } + + VstList(bool initDefaultDir = true, bool loadNewlyFound = true); + void scanDefaultDirs(bool loadNewlyFound); + void scanDirRecursive(std::filesystem::path dirPath, bool loadNewlyFound); + + void loadCache(std::filesystem::path cacheFilePath); + void saveCache(std::filesystem::path cacheFilePath); + + std::vector instrumentPlugins(); + std::vector effectPlugins(); + std::unordered_map& plugins() {return m_plugins;} + +private: + void addPlugin(Metadata data); + + static VstList* s_inst; + std::unordered_map m_pluginsCache; + std::unordered_map m_plugins; + bool m_pluginsByIdNeedsRebuilding = true; +}; + + +} // namespace lmms + +#endif diff --git a/plugins/VstBase/VstPlugin.cpp b/plugins/VstBase/VstPlugin.cpp index dcd03fbbac5..f83e6b926d7 100644 --- a/plugins/VstBase/VstPlugin.cpp +++ b/plugins/VstBase/VstPlugin.cpp @@ -124,13 +124,14 @@ enum class ExecutableType Unknown, Win32, Win64, Linux64, }; -VstPlugin::VstPlugin( const QString & _plugin ) : +VstPlugin::VstPlugin(const QString & _plugin, bool skipInit) : m_plugin( PathUtil::toAbsolute(_plugin) ), m_pluginWindowID( 0 ), m_embedMethod( (gui::getGUI() != nullptr) ? ConfigManager::inst()->vstEmbedMethod() : "headless" ), m_version( 0 ), + m_isSynth{false}, m_currentProgram() { setSplittedChannels( true ); @@ -168,14 +169,14 @@ VstPlugin::VstPlugin( const QString & _plugin ) : switch(pluginType) { case ExecutableType::Win64: - tryLoad( REMOTE_VST_PLUGIN_FILEPATH_64 ); // Default: RemoteVstPlugin64 + tryLoad(REMOTE_VST_PLUGIN_FILEPATH_64, skipInit); // Default: RemoteVstPlugin64 break; case ExecutableType::Win32: - tryLoad( REMOTE_VST_PLUGIN_FILEPATH_32 ); // Default: 32/RemoteVstPlugin32 + tryLoad(REMOTE_VST_PLUGIN_FILEPATH_32, skipInit); // Default: 32/RemoteVstPlugin32 break; #ifdef LMMS_BUILD_LINUX case ExecutableType::Linux64: - tryLoad( NATIVE_LINUX_REMOTE_VST_PLUGIN_FILEPATH_64 ); // Default: NativeLinuxRemoteVstPlugin32 + tryLoad(NATIVE_LINUX_REMOTE_VST_PLUGIN_FILEPATH_64, skipInit); // Default: NativeLinuxRemoteVstPlugin32 break; #endif default: @@ -207,9 +208,9 @@ VstPlugin::~VstPlugin() -void VstPlugin::tryLoad( const QString &remoteVstPluginExecutable ) +void VstPlugin::tryLoad(const QString &remoteVstPluginExecutable, bool skipInit) { - init( remoteVstPluginExecutable, false, {m_embedMethod} ); + init( remoteVstPluginExecutable, false, {m_embedMethod, QString::number(skipInit)} ); waitForHostInfoGotten(); if( failed() ) @@ -431,6 +432,10 @@ bool VstPlugin::processMessage( const message & _m ) _m.getInt( 1 ) ); break; + case IdVstIsSynth: + m_isSynth = true; + break; + case IdVstPluginName: m_name = _m.getQString(); break; @@ -495,6 +500,7 @@ bool VstPlugin::processMessage( const message & _m ) case IdVstPluginUniqueID: // TODO: display graphically in case of failure + m_uniqueID = _m.getQString(); printf("unique ID: %s\n", _m.getString().c_str() ); break; diff --git a/plugins/VstBase/VstPlugin.h b/plugins/VstBase/VstPlugin.h index dabd651e113..ae17c829a80 100644 --- a/plugins/VstBase/VstPlugin.h +++ b/plugins/VstBase/VstPlugin.h @@ -45,10 +45,10 @@ class VSTBASE_EXPORT VstPlugin : public RemotePlugin, public JournallingObject { Q_OBJECT public: - VstPlugin( const QString & _plugin ); + VstPlugin(const QString & _plugin, bool skipInit = false); ~VstPlugin() override; - void tryLoad( const QString &remoteVstPluginExecutable ); + void tryLoad(const QString &remoteVstPluginExecutable, bool skipInit = false); bool processMessage( const message & _m ) override; @@ -102,6 +102,16 @@ class VSTBASE_EXPORT VstPlugin : public RemotePlugin, public JournallingObject return m_allParameterDisplays; } + inline const QString& uniqueID() const + { + return m_uniqueID; + } + + inline bool isSynth() const + { + return m_isSynth; + } + int currentProgram(); const QMap & parameterDump(); @@ -165,6 +175,8 @@ public slots: QString m_allProgramNames; std::vector m_allParameterLabels; std::vector m_allParameterDisplays; + QString m_uniqueID; + bool m_isSynth; QString p_name; diff --git a/plugins/VstBase/VstSubPluginFeatures.cpp b/plugins/VstBase/VstSubPluginFeatures.cpp new file mode 100644 index 00000000000..04145950f44 --- /dev/null +++ b/plugins/VstBase/VstSubPluginFeatures.cpp @@ -0,0 +1,101 @@ +/* + * VstSubPluginFeatures.cpp - derivation from + * Plugin::Descriptor::SubPluginFeatures for + * hosting VST-plugins + * + * Copyright (c) 2006-2009 Tobias Doerffel + * + * 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 "VstSubPluginFeatures.h" + +#include +#include +#include + +#include "VstList.h" +#include "Plugin.h" +#include "lmmsconfig.h" + +using AttributeMap = lmms::Plugin::Descriptor::SubPluginFeatures::Key::AttributeMap; + +namespace +{ + +// converts a bitset to base64 pretty much +QString checksumToB64(lmms::VstList::Metadata::Checksum checksum) +{ + return QByteArray::fromRawData(reinterpret_cast(&checksum), sizeof checksum).toBase64(); +} + +lmms::VstList::Metadata::Checksum checksumFromB64(QString b64) +{ + return *reinterpret_cast(QByteArray::fromBase64(b64.toUtf8()).data()); +} + +inline lmms::VstList::Metadata& getMetadata(const lmms::Plugin::Descriptor::SubPluginFeatures::Key& key) +{ + return lmms::VstList::inst()->plugins()[checksumFromB64(key.attributes["checksum"])]; +} + +} // namespace + + +namespace lmms +{ + +VstSubPluginFeatures::VstSubPluginFeatures(Plugin::Type pluginType) + : SubPluginFeatures{pluginType} + , m_pluginType{pluginType} +{ +} + +void VstSubPluginFeatures::fillDescriptionWidget(QWidget* parent, const Key* key) const +{ + new QLabel(QWidget::tr("Name: ") + key->name, parent); + new QLabel(QWidget::tr("File: ") + key->attributes["file"], parent); +} + +void VstSubPluginFeatures::listSubPluginKeys(const Plugin::Descriptor* desc, KeyList& keylist) const +{ + // TODO: eval m_type + for (const auto& data : m_pluginType == Plugin::Type::Instrument + ? VstList::inst()->instrumentPlugins() + : VstList::inst()->effectPlugins()) + { + AttributeMap am; + am["file"] = QString::fromStdString(data.path.string()); + am["checksum"] = checksumToB64(data.file_checksum); + keylist.push_back(Key(desc, QString::fromStdString(data.name), am)); + } +} + +QString VstSubPluginFeatures::displayName(const Key& key) const +{ + return QString::fromStdString(getMetadata(key).name); +} + +QString VstSubPluginFeatures::description(const Key& key) const +{ + const auto& metadata = getMetadata(key); + return QString::fromStdString(metadata.product + " (" + metadata.path.string() + ")"); +} + +} // namespace lmms diff --git a/plugins/VstEffect/VstSubPluginFeatures.h b/plugins/VstBase/VstSubPluginFeatures.h similarity index 72% rename from plugins/VstEffect/VstSubPluginFeatures.h rename to plugins/VstBase/VstSubPluginFeatures.h index f6b4d7141d3..2e372bb9420 100644 --- a/plugins/VstEffect/VstSubPluginFeatures.h +++ b/plugins/VstBase/VstSubPluginFeatures.h @@ -29,23 +29,26 @@ #include "Plugin.h" +#include "vstbase_export.h" namespace lmms { -class VstSubPluginFeatures : public Plugin::Descriptor::SubPluginFeatures +class VSTBASE_EXPORT VstSubPluginFeatures : public Plugin::Descriptor::SubPluginFeatures { public: - VstSubPluginFeatures( Plugin::Type _type ); + VstSubPluginFeatures(Plugin::Type type); - void fillDescriptionWidget( QWidget * _parent, - const Key * _key ) const override; + void fillDescriptionWidget(QWidget* parent, const Key* key) const override; + + void listSubPluginKeys(const Plugin::Descriptor* desc, KeyList& key) const override; + + QString displayName(const Key& key) const override; + QString description(const Key& key) const override; - void listSubPluginKeys( const Plugin::Descriptor * _desc, - KeyList & _kl ) const override; private: - void addPluginsFromDir(QStringList* filenames, QString path) const; + Plugin::Type m_pluginType; } ; diff --git a/plugins/VstBase/communication.h b/plugins/VstBase/communication.h index d3f5647740d..68c9163b639 100644 --- a/plugins/VstBase/communication.h +++ b/plugins/VstBase/communication.h @@ -86,7 +86,8 @@ enum VstRemoteMessageIDs IdVstPluginUniqueID, IdVstSetParameter, IdVstParameterCount, - IdVstParameterDump + IdVstParameterDump, + IdVstIsSynth } ; diff --git a/plugins/VstBase/vstbase/CMakeLists.txt b/plugins/VstBase/vstbase/CMakeLists.txt index 3a69d20fc09..b4b8d621c5b 100644 --- a/plugins/VstBase/vstbase/CMakeLists.txt +++ b/plugins/VstBase/vstbase/CMakeLists.txt @@ -5,8 +5,9 @@ IF(LMMS_BUILD_LINUX) ENDIF() BUILD_PLUGIN(vstbase - ../vst_base.cpp ../VstPlugin.cpp ../VstPlugin.h ../communication.h - MOCFILES ../VstPlugin.h + ../vst_base.cpp ../VstPlugin.cpp ../VstPlugin.h ../communication.h ../VstList.cpp ../VstList.h + ../VstSubPluginFeatures.cpp ../VstSubPluginFeatures.h + MOCFILES ../VstPlugin.h ../VstList.h ../VstSubPluginFeatures.h EXPORT_BASE_NAME vstbase LINK SHARED ) diff --git a/plugins/VstEffect/CMakeLists.txt b/plugins/VstEffect/CMakeLists.txt index 7bb42dc24b8..e0153ed6e5d 100644 --- a/plugins/VstEffect/CMakeLists.txt +++ b/plugins/VstEffect/CMakeLists.txt @@ -13,5 +13,5 @@ ELSE() SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${PLUGIN_DIR}") ENDIF() -BUILD_PLUGIN(vsteffect VstEffect.cpp VstEffectControls.cpp VstEffectControlDialog.cpp VstSubPluginFeatures.cpp VstEffect.h VstEffectControls.h VstEffectControlDialog.h VstSubPluginFeatures.h MOCFILES VstEffectControlDialog.h VstEffectControls.h EMBEDDED_RESOURCES *.png *.svg) +BUILD_PLUGIN(vsteffect VstEffect.cpp VstEffectControls.cpp VstEffectControlDialog.cpp VstEffect.h VstEffectControls.h VstEffectControlDialog.h MOCFILES VstEffectControlDialog.h VstEffectControls.h EMBEDDED_RESOURCES *.png *.svg) TARGET_LINK_LIBRARIES(vsteffect vstbase) diff --git a/plugins/VstEffect/VstEffect.cpp b/plugins/VstEffect/VstEffect.cpp index dfc20a57d33..71fa39b5b84 100644 --- a/plugins/VstEffect/VstEffect.cpp +++ b/plugins/VstEffect/VstEffect.cpp @@ -52,7 +52,7 @@ Plugin::Descriptor PLUGIN_EXPORT vsteffect_plugin_descriptor = Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, - new VstSubPluginFeatures( Plugin::Type::Effect ) + new VstSubPluginFeatures(Plugin::Type::Effect) } ; } @@ -69,9 +69,8 @@ VstEffect::VstEffect( Model * _parent, if( !m_key.attributes["file"].isEmpty() ) { loaded = openPlugin(m_key.attributes["file"]); + setDisplayName(m_key.displayName()); } - setDisplayName( m_key.attributes["file"].section( ".dll", 0, 0 ).isEmpty() - ? m_key.name : m_key.attributes["file"].section( ".dll", 0, 0 ) ); setDontRun(!loaded); } diff --git a/plugins/VstEffect/VstSubPluginFeatures.cpp b/plugins/VstEffect/VstSubPluginFeatures.cpp deleted file mode 100644 index b30adf95e56..00000000000 --- a/plugins/VstEffect/VstSubPluginFeatures.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * VstSubPluginFeatures.cpp - derivation from - * Plugin::Descriptor::SubPluginFeatures for - * hosting VST-plugins - * - * Copyright (c) 2006-2009 Tobias Doerffel - * - * 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 -#include - -#include "VstSubPluginFeatures.h" -#include "ConfigManager.h" -#include "Effect.h" - -namespace lmms -{ - - -VstSubPluginFeatures::VstSubPluginFeatures( Plugin::Type _type ) : - SubPluginFeatures( _type ) -{ -} - - - - -void VstSubPluginFeatures::fillDescriptionWidget( QWidget * _parent, - const Key * _key ) const -{ - new QLabel( QWidget::tr( "Name: " ) + _key->name, _parent ); - new QLabel( QWidget::tr( "File: " ) + _key->attributes["file"], _parent ); -} - - - - -void VstSubPluginFeatures::listSubPluginKeys( const Plugin::Descriptor * _desc, - KeyList & _kl ) const -{ - auto dlls = new QStringList(); - const QString path = QString(""); - addPluginsFromDir(dlls, path ); - // TODO: eval m_type - for( QStringList::ConstIterator it = dlls->begin(); - it != dlls->end(); ++it ) - { - EffectKey::AttributeMap am; - am["file"] = *it; - _kl.push_back( Key( _desc, QFileInfo( *it ).baseName(), am ) ); - } - delete dlls; -} - -void VstSubPluginFeatures::addPluginsFromDir( QStringList* filenames, QString path ) const -{ - QStringList dirs = QDir ( ConfigManager::inst()->vstDir() + path ). - entryList( QStringList() << "*" , - QDir::Dirs, QDir::Name ); - for( int i = 0; i < dirs.size(); i++ ) - { - if( dirs.at( i )[0] != '.' ) - { - addPluginsFromDir( filenames, path+QDir::separator() + dirs.at( i ) ); - } - } - QStringList dlls = QDir( ConfigManager::inst()->vstDir() + path ). - entryList( QStringList() << "*.dll" -#ifdef LMMS_BUILD_LINUX - << "*.so" -#endif - , - QDir::Files, QDir::Name ); - for( int i = 0; i < dlls.size(); i++ ) - { - QString fName = path + QDir::separator() + dlls.at( i ); - fName.remove( 0, 1 ); - filenames->append( fName ); - } -} - - -} // namespace lmms diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index a6ba585c7de..2193191f5e8 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -140,6 +140,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_vstEmbedMethod(ConfigManager::inst()->vstEmbedMethod()), m_vstAlwaysOnTop(ConfigManager::inst()->value( "ui", "vstalwaysontop").toInt()), + m_vstScanExec(ConfigManager::inst()->value("app", "vstscanexec", "0").toInt()), m_disableAutoQuit(ConfigManager::inst()->value( "ui", "disableautoquit", "1").toInt()), m_bufferSize(ConfigManager::inst()->value( @@ -470,6 +471,9 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : this, SLOT(vstEmbedMethodChanged())); pluginsLayout->addWidget(m_vstEmbedComboBox); + addCheckBox(tr("Scan for VST plugins (experimental, first startup is slow)"), pluginsBox, pluginsLayout, + m_vstScanExec, SLOT(toggleVstScanExec(bool)), true); + m_vstAlwaysOnTopCheckBox = addCheckBox(tr("Keep plugin windows on top when not embedded"), pluginsBox, pluginsLayout, m_vstAlwaysOnTop, SLOT(toggleVSTAlwaysOnTop(bool)), false); @@ -1023,6 +1027,8 @@ void SetupDialog::accept() QString::number(m_animateAFP)); ConfigManager::inst()->setValue("ui", "vstembedmethod", m_vstEmbedComboBox->currentData().toString()); + ConfigManager::inst()->setValue("app", "vstscanexec", + QString::number(m_vstScanExec)); ConfigManager::inst()->setValue("ui", "vstalwaysontop", QString::number(m_vstAlwaysOnTop)); ConfigManager::inst()->setValue("ui", "disableautoquit",