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.

638 lines
19KB

  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. void skip()
  131. {
  132. if (isPipeRunning())
  133. stopPipeServer(1000);
  134. }
  135. protected:
  136. bool msgReceived(const char* const msg) noexcept
  137. {
  138. fLastMessageTime = carla_gettime_ms();
  139. if (std::strcmp(msg, "warning") == 0 || std::strcmp(msg, "error") == 0)
  140. {
  141. const char* text = nullptr;
  142. readNextLineAsString(text, false);
  143. carla_stdout("discovery: %s", text);
  144. return true;
  145. }
  146. if (std::strcmp(msg, "init") == 0)
  147. {
  148. const char* _;
  149. readNextLineAsString(_, false);
  150. new (&fNextInfo) _CarlaPluginDiscoveryInfo();
  151. return true;
  152. }
  153. if (std::strcmp(msg, "end") == 0)
  154. {
  155. const char* _;
  156. readNextLineAsString(_, false);
  157. if (fNextInfo.label == nullptr)
  158. fNextInfo.label = gPluginsDiscoveryNullCharPtr;
  159. if (fNextInfo.metadata.maker == nullptr)
  160. fNextInfo.metadata.maker = gPluginsDiscoveryNullCharPtr;
  161. if (fNextInfo.metadata.name == nullptr)
  162. fNextInfo.metadata.name = gPluginsDiscoveryNullCharPtr;
  163. if (fBinaries.empty())
  164. {
  165. char* filename = nullptr;
  166. if (fPluginType == CB::PLUGIN_LV2)
  167. {
  168. do {
  169. const char* const slash = std::strchr(fNextLabel, CARLA_OS_SEP);
  170. CARLA_SAFE_ASSERT_BREAK(slash != nullptr);
  171. filename = strdup(fNextLabel);
  172. filename[slash - fNextLabel] = '\0';
  173. fNextInfo.filename = filename;
  174. fNextInfo.label = slash + 1;
  175. } while (false);
  176. }
  177. fNextInfo.ptype = fPluginType;
  178. fDiscoveryCallback(fCallbackPtr, &fNextInfo, nullptr);
  179. std::free(filename);
  180. }
  181. else
  182. {
  183. CARLA_SAFE_ASSERT(fNextSha1Sum.isNotEmpty());
  184. const water::String filename(fBinaries[fBinaryIndex].getFullPathName());
  185. fNextInfo.filename = filename.toRawUTF8();
  186. fNextInfo.ptype = fPluginType;
  187. fPluginsFoundInBinary = true;
  188. carla_stdout("Found %s from %s", fNextInfo.metadata.name, fNextInfo.filename);
  189. fDiscoveryCallback(fCallbackPtr, &fNextInfo, fNextSha1Sum);
  190. }
  191. std::free(fNextLabel);
  192. fNextLabel = nullptr;
  193. std::free(fNextMaker);
  194. fNextMaker = nullptr;
  195. std::free(fNextName);
  196. fNextName = nullptr;
  197. return true;
  198. }
  199. if (std::strcmp(msg, "build") == 0)
  200. {
  201. uint8_t btype = 0;
  202. readNextLineAsByte(btype);
  203. fNextInfo.btype = static_cast<BinaryType>(btype);
  204. return true;
  205. }
  206. if (std::strcmp(msg, "hints") == 0)
  207. {
  208. readNextLineAsUInt(fNextInfo.metadata.hints);
  209. return true;
  210. }
  211. if (std::strcmp(msg, "category") == 0)
  212. {
  213. const char* category = nullptr;
  214. readNextLineAsString(category, false);
  215. fNextInfo.metadata.category = CB::getPluginCategoryFromString(category);
  216. return true;
  217. }
  218. if (std::strcmp(msg, "name") == 0)
  219. {
  220. fNextInfo.metadata.name = fNextName = readNextLineAsString();
  221. return true;
  222. }
  223. if (std::strcmp(msg, "label") == 0)
  224. {
  225. fNextInfo.label = fNextLabel = readNextLineAsString();
  226. return true;
  227. }
  228. if (std::strcmp(msg, "maker") == 0)
  229. {
  230. fNextInfo.metadata.maker = fNextMaker = readNextLineAsString();
  231. return true;
  232. }
  233. if (std::strcmp(msg, "uniqueId") == 0)
  234. {
  235. readNextLineAsULong(fNextInfo.uniqueId);
  236. return true;
  237. }
  238. if (std::strcmp(msg, "audio.ins") == 0)
  239. {
  240. readNextLineAsUInt(fNextInfo.io.audioIns);
  241. return true;
  242. }
  243. if (std::strcmp(msg, "audio.outs") == 0)
  244. {
  245. readNextLineAsUInt(fNextInfo.io.audioOuts);
  246. return true;
  247. }
  248. if (std::strcmp(msg, "cv.ins") == 0)
  249. {
  250. readNextLineAsUInt(fNextInfo.io.cvIns);
  251. return true;
  252. }
  253. if (std::strcmp(msg, "cv.outs") == 0)
  254. {
  255. readNextLineAsUInt(fNextInfo.io.cvOuts);
  256. return true;
  257. }
  258. if (std::strcmp(msg, "midi.ins") == 0)
  259. {
  260. readNextLineAsUInt(fNextInfo.io.midiIns);
  261. return true;
  262. }
  263. if (std::strcmp(msg, "midi.outs") == 0)
  264. {
  265. readNextLineAsUInt(fNextInfo.io.midiOuts);
  266. return true;
  267. }
  268. if (std::strcmp(msg, "parameters.ins") == 0)
  269. {
  270. readNextLineAsUInt(fNextInfo.io.parameterIns);
  271. return true;
  272. }
  273. if (std::strcmp(msg, "parameters.outs") == 0)
  274. {
  275. readNextLineAsUInt(fNextInfo.io.parameterOuts);
  276. return true;
  277. }
  278. if (std::strcmp(msg, "exiting") == 0)
  279. {
  280. stopPipeServer(1000);
  281. return true;
  282. }
  283. carla_stdout("discovery: unknown message '%s' received", msg);
  284. return true;
  285. }
  286. private:
  287. const PluginType fPluginType;
  288. const CarlaPluginDiscoveryCallback fDiscoveryCallback;
  289. const CarlaPluginCheckCacheCallback fCheckCacheCallback;
  290. void* const fCallbackPtr;
  291. bool fPluginsFoundInBinary;
  292. uint fBinaryIndex;
  293. const uint fBinaryCount;
  294. const std::vector<water::File> fBinaries;
  295. const CarlaString fDiscoveryTool;
  296. uint32_t fLastMessageTime;
  297. CarlaPluginDiscoveryInfo fNextInfo;
  298. CarlaString fNextSha1Sum;
  299. char* fNextLabel;
  300. char* fNextMaker;
  301. char* fNextName;
  302. void start()
  303. {
  304. fLastMessageTime = carla_gettime_ms();
  305. fPluginsFoundInBinary = false;
  306. fNextSha1Sum.clear();
  307. if (fBinaries.empty())
  308. {
  309. startPipeServer(fDiscoveryTool,
  310. getPluginTypeAsString(fPluginType),
  311. ":all");
  312. }
  313. else
  314. {
  315. const water::File file(fBinaries[fBinaryIndex]);
  316. const water::String filename(file.getFullPathName());
  317. if (fCheckCacheCallback != nullptr)
  318. {
  319. makeHash(file, filename);
  320. if (fCheckCacheCallback(fCallbackPtr, filename.toRawUTF8(), fNextSha1Sum))
  321. {
  322. fPluginsFoundInBinary = true;
  323. carla_debug("Skipping \"%s\", using cache", filename.toRawUTF8());
  324. return;
  325. }
  326. }
  327. carla_stdout("Scanning \"%s\"...", filename.toRawUTF8());
  328. startPipeServer(fDiscoveryTool, getPluginTypeAsString(fPluginType), filename.toRawUTF8());
  329. }
  330. }
  331. void makeHash(const water::File& file, const water::String& filename)
  332. {
  333. CarlaSha1 sha1;
  334. /* do we want this? it is not exactly needed and makes discovery slow..
  335. if (file.existsAsFile() && file.getSize() < 20*1024*1024) // dont bother hashing > 20Mb files
  336. {
  337. water::FileInputStream stream(file);
  338. if (stream.openedOk())
  339. {
  340. uint8_t block[8192];
  341. for (int r; r = stream.read(block, sizeof(block)), r > 0;)
  342. sha1.write(block, r);
  343. }
  344. }
  345. */
  346. sha1.write(filename.toRawUTF8(), filename.length());
  347. const int64_t mtime = file.getLastModificationTime();
  348. sha1.write(&mtime, sizeof(mtime));
  349. fNextSha1Sum = sha1.resultAsString();
  350. }
  351. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginDiscovery)
  352. };
  353. // --------------------------------------------------------------------------------------------------------------------
  354. static bool findDirectories(std::vector<water::File>& files, const char* const pluginPath, const char* const wildcard)
  355. {
  356. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  357. if (pluginPath[0] == '\0')
  358. return true;
  359. using water::File;
  360. using water::String;
  361. using water::StringArray;
  362. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  363. if (splitPaths.size() == 0)
  364. return true;
  365. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  366. {
  367. const File dir(*it);
  368. std::vector<File> results;
  369. if (dir.findChildFiles(results, File::findDirectories|File::ignoreHiddenFiles, true, wildcard) > 0)
  370. {
  371. files.reserve(files.size() + results.size());
  372. files.insert(files.end(), results.begin(), results.end());
  373. }
  374. }
  375. return files.empty();
  376. }
  377. static bool findFiles(std::vector<water::File>& files, const char* const pluginPath, const char* const wildcard)
  378. {
  379. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  380. if (pluginPath[0] == '\0')
  381. return true;
  382. using water::File;
  383. using water::String;
  384. using water::StringArray;
  385. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  386. if (splitPaths.size() == 0)
  387. return true;
  388. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  389. {
  390. const File dir(*it);
  391. std::vector<File> results;
  392. if (dir.findChildFiles(results, File::findFiles|File::ignoreHiddenFiles, true, wildcard) > 0)
  393. {
  394. files.reserve(files.size() + results.size());
  395. files.insert(files.end(), results.begin(), results.end());
  396. }
  397. }
  398. return files.empty();
  399. }
  400. static bool findVST3s(std::vector<water::File>& files, const char* const pluginPath)
  401. {
  402. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  403. if (pluginPath[0] == '\0')
  404. return true;
  405. using water::File;
  406. using water::String;
  407. using water::StringArray;
  408. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  409. if (splitPaths.size() == 0)
  410. return true;
  411. #if defined(CARLA_OS_WIN)
  412. static constexpr const uint flags = File::findDirectories|File::findFiles;
  413. #else
  414. static constexpr const uint flags = File::findDirectories;
  415. #endif
  416. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  417. {
  418. const File dir(*it);
  419. std::vector<File> results;
  420. if (dir.findChildFiles(results, flags|File::ignoreHiddenFiles, true, "*.vst3") > 0)
  421. {
  422. files.reserve(files.size() + results.size());
  423. files.insert(files.end(), results.begin(), results.end());
  424. }
  425. }
  426. return files.empty();
  427. }
  428. CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* const discoveryTool,
  429. const PluginType ptype,
  430. const char* const pluginPath,
  431. const CarlaPluginDiscoveryCallback discoveryCb,
  432. const CarlaPluginCheckCacheCallback checkCacheCb,
  433. void* const callbackPtr)
  434. {
  435. CARLA_SAFE_ASSERT_RETURN(discoveryTool != nullptr && discoveryTool[0] != '\0', nullptr);
  436. CARLA_SAFE_ASSERT_RETURN(discoveryCb != nullptr, nullptr);
  437. bool directories = false;
  438. const char* wildcard = nullptr;
  439. switch (ptype)
  440. {
  441. case CB::PLUGIN_NONE:
  442. case CB::PLUGIN_JACK:
  443. case CB::PLUGIN_TYPE_COUNT:
  444. return nullptr;
  445. case CB::PLUGIN_SFZ:
  446. case CB::PLUGIN_JSFX:
  447. {
  448. const CarlaScopedEnvVar csev("CARLA_DISCOVERY_PATH", pluginPath);
  449. return new CarlaPluginDiscovery(discoveryTool, ptype, discoveryCb, checkCacheCb, callbackPtr);
  450. }
  451. case CB::PLUGIN_INTERNAL:
  452. case CB::PLUGIN_LV2:
  453. case CB::PLUGIN_AU:
  454. return new CarlaPluginDiscovery(discoveryTool, ptype, discoveryCb, checkCacheCb, callbackPtr);
  455. case CB::PLUGIN_LADSPA:
  456. case CB::PLUGIN_DSSI:
  457. #if defined(CARLA_OS_MAC)
  458. wildcard = "*.dylib";
  459. #elif defined(CARLA_OS_WIN)
  460. wildcard = "*.dll";
  461. #else
  462. wildcard = "*.so";
  463. #endif
  464. break;
  465. case CB::PLUGIN_VST2:
  466. #if defined(CARLA_OS_MAC)
  467. directories = true;
  468. wildcard = "*.vst";
  469. #elif defined(CARLA_OS_WIN)
  470. wildcard = "*.dll";
  471. #else
  472. wildcard = "*.so";
  473. #endif
  474. break;
  475. case CB::PLUGIN_VST3:
  476. directories = true;
  477. wildcard = "*.vst3";
  478. break;
  479. case CB::PLUGIN_CLAP:
  480. wildcard = "*.clap";
  481. #ifdef CARLA_OS_MAC
  482. directories = true;
  483. #endif
  484. break;
  485. case CB::PLUGIN_DLS:
  486. wildcard = "*.dls";
  487. break;
  488. case CB::PLUGIN_GIG:
  489. wildcard = "*.gig";
  490. break;
  491. case CB::PLUGIN_SF2:
  492. wildcard = "*.sf2";
  493. break;
  494. }
  495. CARLA_SAFE_ASSERT_RETURN(wildcard != nullptr, nullptr);
  496. std::vector<water::File> files;
  497. if (ptype == CB::PLUGIN_VST3)
  498. {
  499. if (findVST3s(files, pluginPath))
  500. return nullptr;
  501. }
  502. else if (directories)
  503. {
  504. if (findDirectories(files, pluginPath, wildcard))
  505. return nullptr;
  506. }
  507. else
  508. {
  509. if (findFiles(files, pluginPath, wildcard))
  510. return nullptr;
  511. }
  512. return new CarlaPluginDiscovery(discoveryTool, ptype, std::move(files), discoveryCb, checkCacheCb, callbackPtr);
  513. }
  514. bool carla_plugin_discovery_idle(const CarlaPluginDiscoveryHandle handle)
  515. {
  516. return static_cast<CarlaPluginDiscovery*>(handle)->idle();
  517. }
  518. void carla_plugin_discovery_skip(const CarlaPluginDiscoveryHandle handle)
  519. {
  520. static_cast<CarlaPluginDiscovery*>(handle)->skip();
  521. }
  522. void carla_plugin_discovery_stop(const CarlaPluginDiscoveryHandle handle)
  523. {
  524. delete static_cast<CarlaPluginDiscovery*>(handle);
  525. }
  526. // --------------------------------------------------------------------------------------------------------------------