Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PluginDiscovery.cpp 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /*
  2. * Carla Plugin Host
  3. * Copyright (C) 2011-2023 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. #include "CarlaUtils.h"
  18. #include "CarlaBackendUtils.hpp"
  19. #include "CarlaJuceUtils.hpp"
  20. #include "CarlaPipeUtils.hpp"
  21. #include "CarlaSha1Utils.hpp"
  22. #include "CarlaTimeUtils.hpp"
  23. #include "water/files/File.h"
  24. #include "water/files/FileInputStream.h"
  25. #include "water/threads/ChildProcess.h"
  26. #include "water/text/StringArray.h"
  27. namespace CB = CARLA_BACKEND_NAMESPACE;
  28. // --------------------------------------------------------------------------------------------------------------------
  29. static const char* const gPluginsDiscoveryNullCharPtr = "";
  30. _CarlaPluginDiscoveryMetadata::_CarlaPluginDiscoveryMetadata() noexcept
  31. : name(gPluginsDiscoveryNullCharPtr),
  32. maker(gPluginsDiscoveryNullCharPtr),
  33. category(CB::PLUGIN_CATEGORY_NONE),
  34. hints(0x0) {}
  35. _CarlaPluginDiscoveryIO::_CarlaPluginDiscoveryIO() noexcept
  36. : audioIns(0),
  37. audioOuts(0),
  38. cvIns(0),
  39. cvOuts(0),
  40. midiIns(0),
  41. midiOuts(0),
  42. parameterIns(0),
  43. parameterOuts(0) {}
  44. _CarlaPluginDiscoveryInfo::_CarlaPluginDiscoveryInfo() noexcept
  45. : btype(CB::BINARY_NONE),
  46. ptype(CB::PLUGIN_NONE),
  47. filename(gPluginsDiscoveryNullCharPtr),
  48. label(gPluginsDiscoveryNullCharPtr),
  49. uniqueId(0),
  50. metadata() {}
  51. // --------------------------------------------------------------------------------------------------------------------
  52. class CarlaPluginDiscovery : private CarlaPipeServer
  53. {
  54. public:
  55. CarlaPluginDiscovery(const char* const discoveryTool,
  56. const PluginType ptype,
  57. const std::vector<water::File>&& binaries,
  58. const CarlaPluginDiscoveryCallback discoveryCb,
  59. const CarlaPluginCheckCacheCallback checkCacheCb,
  60. void* const callbackPtr)
  61. : fPluginType(ptype),
  62. fDiscoveryCallback(discoveryCb),
  63. fCheckCacheCallback(checkCacheCb),
  64. fCallbackPtr(callbackPtr),
  65. fPluginsFoundInBinary(false),
  66. fBinaryIndex(0),
  67. fBinaryCount(static_cast<uint>(binaries.size())),
  68. fBinaries(binaries),
  69. fDiscoveryTool(discoveryTool),
  70. fLastMessageTime(0),
  71. fNextLabel(nullptr),
  72. fNextMaker(nullptr),
  73. fNextName(nullptr)
  74. {
  75. start();
  76. }
  77. CarlaPluginDiscovery(const char* const discoveryTool,
  78. const PluginType ptype,
  79. const CarlaPluginDiscoveryCallback discoveryCb,
  80. const CarlaPluginCheckCacheCallback checkCacheCb,
  81. void* const callbackPtr)
  82. : fPluginType(ptype),
  83. fDiscoveryCallback(discoveryCb),
  84. fCheckCacheCallback(checkCacheCb),
  85. fCallbackPtr(callbackPtr),
  86. fPluginsFoundInBinary(false),
  87. fBinaryIndex(0),
  88. fBinaryCount(1),
  89. fDiscoveryTool(discoveryTool),
  90. fLastMessageTime(0),
  91. fNextLabel(nullptr),
  92. fNextMaker(nullptr),
  93. fNextName(nullptr)
  94. {
  95. start();
  96. }
  97. ~CarlaPluginDiscovery()
  98. {
  99. stopPipeServer(5000);
  100. std::free(fNextLabel);
  101. std::free(fNextMaker);
  102. std::free(fNextName);
  103. }
  104. bool idle()
  105. {
  106. if (isPipeRunning())
  107. {
  108. idlePipe();
  109. // automatically skip a plugin if 30s passes without a reply
  110. const uint32_t timeNow = carla_gettime_ms();
  111. if (timeNow - fLastMessageTime < 30000)
  112. return true;
  113. carla_stdout("Plugin took too long to respond, skipping...");
  114. stopPipeServer(1000);
  115. }
  116. // report binary as having no plugins
  117. if (fCheckCacheCallback != nullptr && !fPluginsFoundInBinary && !fBinaries.empty())
  118. {
  119. const water::File file(fBinaries[fBinaryIndex]);
  120. const water::String filename(file.getFullPathName());
  121. makeHash(file, filename);
  122. if (! fCheckCacheCallback(fCallbackPtr, filename.toRawUTF8(), fNextSha1Sum))
  123. fDiscoveryCallback(fCallbackPtr, nullptr, fNextSha1Sum);
  124. }
  125. if (++fBinaryIndex == fBinaryCount)
  126. return false;
  127. start();
  128. return true;
  129. }
  130. protected:
  131. bool msgReceived(const char* const msg) noexcept
  132. {
  133. fLastMessageTime = carla_gettime_ms();
  134. if (std::strcmp(msg, "warning") == 0 || std::strcmp(msg, "error") == 0)
  135. {
  136. const char* text = nullptr;
  137. readNextLineAsString(text, false);
  138. carla_stdout("discovery: %s", text);
  139. return true;
  140. }
  141. if (std::strcmp(msg, "init") == 0)
  142. {
  143. const char* _;
  144. readNextLineAsString(_, false);
  145. new (&fNextInfo) _CarlaPluginDiscoveryInfo();
  146. return true;
  147. }
  148. if (std::strcmp(msg, "end") == 0)
  149. {
  150. const char* _;
  151. readNextLineAsString(_, false);
  152. if (fNextInfo.label == nullptr)
  153. fNextInfo.label = gPluginsDiscoveryNullCharPtr;
  154. if (fNextInfo.metadata.maker == nullptr)
  155. fNextInfo.metadata.maker = gPluginsDiscoveryNullCharPtr;
  156. if (fNextInfo.metadata.name == nullptr)
  157. fNextInfo.metadata.name = gPluginsDiscoveryNullCharPtr;
  158. if (fBinaries.empty())
  159. {
  160. char* filename = nullptr;
  161. if (fPluginType == CB::PLUGIN_LV2)
  162. {
  163. do {
  164. const char* const slash = std::strchr(fNextLabel, CARLA_OS_SEP);
  165. CARLA_SAFE_ASSERT_BREAK(slash != nullptr);
  166. filename = strdup(fNextLabel);
  167. filename[slash - fNextLabel] = '\0';
  168. fNextInfo.filename = filename;
  169. fNextInfo.label = slash + 1;
  170. } while (false);
  171. }
  172. fNextInfo.ptype = fPluginType;
  173. fDiscoveryCallback(fCallbackPtr, &fNextInfo, nullptr);
  174. std::free(filename);
  175. }
  176. else
  177. {
  178. CARLA_SAFE_ASSERT(fNextSha1Sum.isNotEmpty());
  179. const water::String filename(fBinaries[fBinaryIndex].getFullPathName());
  180. fNextInfo.filename = filename.toRawUTF8();
  181. fNextInfo.ptype = fPluginType;
  182. fPluginsFoundInBinary = true;
  183. carla_stdout("Found %s from %s", fNextInfo.metadata.name, fNextInfo.filename);
  184. fDiscoveryCallback(fCallbackPtr, &fNextInfo, fNextSha1Sum);
  185. }
  186. std::free(fNextLabel);
  187. fNextLabel = nullptr;
  188. std::free(fNextMaker);
  189. fNextMaker = nullptr;
  190. std::free(fNextName);
  191. fNextName = nullptr;
  192. return true;
  193. }
  194. if (std::strcmp(msg, "build") == 0)
  195. {
  196. uint8_t btype = 0;
  197. readNextLineAsByte(btype);
  198. fNextInfo.btype = static_cast<BinaryType>(btype);
  199. return true;
  200. }
  201. if (std::strcmp(msg, "hints") == 0)
  202. {
  203. readNextLineAsUInt(fNextInfo.metadata.hints);
  204. return true;
  205. }
  206. if (std::strcmp(msg, "category") == 0)
  207. {
  208. const char* category = nullptr;
  209. readNextLineAsString(category, false);
  210. fNextInfo.metadata.category = CB::getPluginCategoryFromString(category);
  211. return true;
  212. }
  213. if (std::strcmp(msg, "name") == 0)
  214. {
  215. fNextInfo.metadata.name = fNextName = readNextLineAsString();
  216. return true;
  217. }
  218. if (std::strcmp(msg, "label") == 0)
  219. {
  220. fNextInfo.label = fNextLabel = readNextLineAsString();
  221. return true;
  222. }
  223. if (std::strcmp(msg, "maker") == 0)
  224. {
  225. fNextInfo.metadata.maker = fNextMaker = readNextLineAsString();
  226. return true;
  227. }
  228. if (std::strcmp(msg, "uniqueId") == 0)
  229. {
  230. readNextLineAsULong(fNextInfo.uniqueId);
  231. return true;
  232. }
  233. if (std::strcmp(msg, "audio.ins") == 0)
  234. {
  235. readNextLineAsUInt(fNextInfo.io.audioIns);
  236. return true;
  237. }
  238. if (std::strcmp(msg, "audio.outs") == 0)
  239. {
  240. readNextLineAsUInt(fNextInfo.io.audioOuts);
  241. return true;
  242. }
  243. if (std::strcmp(msg, "cv.ins") == 0)
  244. {
  245. readNextLineAsUInt(fNextInfo.io.cvIns);
  246. return true;
  247. }
  248. if (std::strcmp(msg, "cv.outs") == 0)
  249. {
  250. readNextLineAsUInt(fNextInfo.io.cvOuts);
  251. return true;
  252. }
  253. if (std::strcmp(msg, "midi.ins") == 0)
  254. {
  255. readNextLineAsUInt(fNextInfo.io.midiIns);
  256. return true;
  257. }
  258. if (std::strcmp(msg, "midi.outs") == 0)
  259. {
  260. readNextLineAsUInt(fNextInfo.io.midiOuts);
  261. return true;
  262. }
  263. if (std::strcmp(msg, "parameters.ins") == 0)
  264. {
  265. readNextLineAsUInt(fNextInfo.io.parameterIns);
  266. return true;
  267. }
  268. if (std::strcmp(msg, "parameters.outs") == 0)
  269. {
  270. readNextLineAsUInt(fNextInfo.io.parameterOuts);
  271. return true;
  272. }
  273. if (std::strcmp(msg, "exiting") == 0)
  274. {
  275. stopPipeServer(1000);
  276. return true;
  277. }
  278. carla_stdout("discovery: unknown message '%s' received", msg);
  279. return true;
  280. }
  281. private:
  282. const PluginType fPluginType;
  283. const CarlaPluginDiscoveryCallback fDiscoveryCallback;
  284. const CarlaPluginCheckCacheCallback fCheckCacheCallback;
  285. void* const fCallbackPtr;
  286. bool fPluginsFoundInBinary;
  287. uint fBinaryIndex;
  288. const uint fBinaryCount;
  289. const std::vector<water::File> fBinaries;
  290. const CarlaString fDiscoveryTool;
  291. uint32_t fLastMessageTime;
  292. CarlaPluginDiscoveryInfo fNextInfo;
  293. CarlaString fNextSha1Sum;
  294. char* fNextLabel;
  295. char* fNextMaker;
  296. char* fNextName;
  297. void start()
  298. {
  299. fLastMessageTime = carla_gettime_ms();
  300. fPluginsFoundInBinary = false;
  301. fNextSha1Sum.clear();
  302. if (fBinaries.empty())
  303. {
  304. startPipeServer(fDiscoveryTool,
  305. getPluginTypeAsString(fPluginType),
  306. ":all");
  307. }
  308. else
  309. {
  310. const water::File file(fBinaries[fBinaryIndex]);
  311. const water::String filename(file.getFullPathName());
  312. if (fCheckCacheCallback != nullptr)
  313. {
  314. makeHash(file, filename);
  315. if (fCheckCacheCallback(fCallbackPtr, filename.toRawUTF8(), fNextSha1Sum))
  316. {
  317. fPluginsFoundInBinary = true;
  318. carla_stdout("Skipping \"%s\", using cache", filename.toRawUTF8());
  319. return;
  320. }
  321. }
  322. carla_stdout("Scanning \"%s\"...", filename.toRawUTF8());
  323. startPipeServer(fDiscoveryTool, getPluginTypeAsString(fPluginType), filename.toRawUTF8());
  324. }
  325. }
  326. void makeHash(const water::File& file, const water::String& filename)
  327. {
  328. CarlaSha1 sha1;
  329. if (file.existsAsFile() && file.getSize() < 20*1024*1024) // dont bother hashing > 20Mb files
  330. {
  331. water::FileInputStream stream(file);
  332. if (stream.openedOk())
  333. {
  334. uint8_t block[8192];
  335. for (int r; r = stream.read(block, sizeof(block)), r > 0;)
  336. sha1.write(block, r);
  337. }
  338. }
  339. sha1.write(filename.toRawUTF8(), filename.length());
  340. const int64_t mtime = file.getLastModificationTime();
  341. sha1.write(&mtime, sizeof(mtime));
  342. fNextSha1Sum = sha1.resultAsString();
  343. }
  344. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginDiscovery)
  345. };
  346. // --------------------------------------------------------------------------------------------------------------------
  347. static bool findDirectories(std::vector<water::File>& files, const char* const pluginPath, const char* const wildcard)
  348. {
  349. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  350. if (pluginPath[0] == '\0')
  351. return true;
  352. using water::File;
  353. using water::String;
  354. using water::StringArray;
  355. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  356. if (splitPaths.size() == 0)
  357. return true;
  358. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  359. {
  360. const File dir(*it);
  361. std::vector<File> results;
  362. if (dir.findChildFiles(results, File::findDirectories|File::ignoreHiddenFiles, true, wildcard) > 0)
  363. {
  364. files.reserve(files.size() + results.size());
  365. files.insert(files.end(), results.begin(), results.end());
  366. }
  367. }
  368. return files.empty();
  369. }
  370. static bool findFiles(std::vector<water::File>& files, const char* const pluginPath, const char* const wildcard)
  371. {
  372. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  373. if (pluginPath[0] == '\0')
  374. return true;
  375. using water::File;
  376. using water::String;
  377. using water::StringArray;
  378. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  379. if (splitPaths.size() == 0)
  380. return true;
  381. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  382. {
  383. const File dir(*it);
  384. std::vector<File> results;
  385. if (dir.findChildFiles(results, File::findFiles|File::ignoreHiddenFiles, true, wildcard) > 0)
  386. {
  387. files.reserve(files.size() + results.size());
  388. files.insert(files.end(), results.begin(), results.end());
  389. }
  390. }
  391. return files.empty();
  392. }
  393. static bool findVST3s(std::vector<water::File>& files, const char* const pluginPath)
  394. {
  395. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  396. if (pluginPath[0] == '\0')
  397. return true;
  398. using water::File;
  399. using water::String;
  400. using water::StringArray;
  401. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  402. if (splitPaths.size() == 0)
  403. return true;
  404. #if defined(CARLA_OS_WIN)
  405. static constexpr const uint flags = File::findDirectories|File::findFiles;
  406. #else
  407. static constexpr const uint flags = File::findDirectories;
  408. #endif
  409. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  410. {
  411. const File dir(*it);
  412. std::vector<File> results;
  413. if (dir.findChildFiles(results, flags|File::ignoreHiddenFiles, true, "*.vst3") > 0)
  414. {
  415. files.reserve(files.size() + results.size());
  416. files.insert(files.end(), results.begin(), results.end());
  417. }
  418. }
  419. return files.empty();
  420. }
  421. CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* const discoveryTool,
  422. const PluginType ptype,
  423. const char* const pluginPath,
  424. const CarlaPluginDiscoveryCallback discoveryCb,
  425. const CarlaPluginCheckCacheCallback checkCacheCb,
  426. void* const callbackPtr)
  427. {
  428. CARLA_SAFE_ASSERT_RETURN(discoveryTool != nullptr && discoveryTool[0] != '\0', nullptr);
  429. CARLA_SAFE_ASSERT_RETURN(discoveryCb != nullptr, nullptr);
  430. bool directories = false;
  431. const char* wildcard = nullptr;
  432. switch (ptype)
  433. {
  434. case CB::PLUGIN_NONE:
  435. case CB::PLUGIN_JACK:
  436. case CB::PLUGIN_TYPE_COUNT:
  437. return nullptr;
  438. case CB::PLUGIN_SFZ:
  439. case CB::PLUGIN_JSFX:
  440. {
  441. const CarlaScopedEnvVar csev("CARLA_DISCOVERY_PATH", pluginPath);
  442. return new CarlaPluginDiscovery(discoveryTool, ptype, discoveryCb, checkCacheCb, callbackPtr);
  443. }
  444. case CB::PLUGIN_INTERNAL:
  445. case CB::PLUGIN_LV2:
  446. case CB::PLUGIN_AU:
  447. return new CarlaPluginDiscovery(discoveryTool, ptype, discoveryCb, checkCacheCb, callbackPtr);
  448. case CB::PLUGIN_LADSPA:
  449. case CB::PLUGIN_DSSI:
  450. #if defined(CARLA_OS_MAC)
  451. wildcard = "*.dylib";
  452. #elif defined(CARLA_OS_WIN)
  453. wildcard = "*.dll";
  454. #else
  455. wildcard = "*.so";
  456. #endif
  457. break;
  458. case CB::PLUGIN_VST2:
  459. #if defined(CARLA_OS_MAC)
  460. directories = true;
  461. wildcard = "*.vst";
  462. #elif defined(CARLA_OS_WIN)
  463. wildcard = "*.dll";
  464. #else
  465. wildcard = "*.so";
  466. #endif
  467. break;
  468. case CB::PLUGIN_VST3:
  469. directories = true;
  470. wildcard = "*.vst3";
  471. break;
  472. case CB::PLUGIN_CLAP:
  473. wildcard = "*.clap";
  474. #ifdef CARLA_OS_MAC
  475. directories = true;
  476. #endif
  477. break;
  478. case CB::PLUGIN_DLS:
  479. wildcard = "*.dls";
  480. break;
  481. case CB::PLUGIN_GIG:
  482. wildcard = "*.gig";
  483. break;
  484. case CB::PLUGIN_SF2:
  485. wildcard = "*.sf2";
  486. break;
  487. }
  488. CARLA_SAFE_ASSERT_RETURN(wildcard != nullptr, nullptr);
  489. std::vector<water::File> files;
  490. if (ptype == CB::PLUGIN_VST3)
  491. {
  492. if (findVST3s(files, pluginPath))
  493. return nullptr;
  494. }
  495. else if (directories)
  496. {
  497. if (findDirectories(files, pluginPath, wildcard))
  498. return nullptr;
  499. }
  500. else
  501. {
  502. if (findFiles(files, pluginPath, wildcard))
  503. return nullptr;
  504. }
  505. return new CarlaPluginDiscovery(discoveryTool, ptype, std::move(files), discoveryCb, checkCacheCb, callbackPtr);
  506. }
  507. bool carla_plugin_discovery_idle(CarlaPluginDiscoveryHandle handle)
  508. {
  509. return static_cast<CarlaPluginDiscovery*>(handle)->idle();
  510. }
  511. void carla_plugin_discovery_stop(CarlaPluginDiscoveryHandle handle)
  512. {
  513. delete static_cast<CarlaPluginDiscovery*>(handle);
  514. }
  515. // --------------------------------------------------------------------------------------------------------------------