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.

700 lines
21KB

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