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.

557 lines
16KB

  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 "water/files/File.h"
  22. #include "water/misc/Time.h"
  23. #include "water/threads/ChildProcess.h"
  24. #include "water/text/StringArray.h"
  25. namespace CB = CARLA_BACKEND_NAMESPACE;
  26. // --------------------------------------------------------------------------------------------------------------------
  27. static const char* const gPluginsDiscoveryNullCharPtr = "";
  28. _CarlaPluginDiscoveryMetadata::_CarlaPluginDiscoveryMetadata() noexcept
  29. : name(gPluginsDiscoveryNullCharPtr),
  30. maker(gPluginsDiscoveryNullCharPtr),
  31. category(CB::PLUGIN_CATEGORY_NONE),
  32. hints(0x0) {}
  33. _CarlaPluginDiscoveryIO::_CarlaPluginDiscoveryIO() noexcept
  34. : audioIns(0),
  35. audioOuts(0),
  36. cvIns(0),
  37. cvOuts(0),
  38. midiIns(0),
  39. midiOuts(0),
  40. parameterIns(0),
  41. parameterOuts(0) {}
  42. _CarlaPluginDiscoveryInfo::_CarlaPluginDiscoveryInfo() noexcept
  43. : btype(CB::BINARY_NONE),
  44. ptype(CB::PLUGIN_NONE),
  45. filename(gPluginsDiscoveryNullCharPtr),
  46. label(gPluginsDiscoveryNullCharPtr),
  47. uniqueId(0),
  48. metadata() {}
  49. // --------------------------------------------------------------------------------------------------------------------
  50. class CarlaPluginDiscovery : private CarlaPipeServer
  51. {
  52. public:
  53. CarlaPluginDiscovery(const char* const discoveryTool,
  54. const PluginType ptype,
  55. const std::vector<water::File>&& binaries,
  56. const CarlaPluginDiscoveryCallback callback,
  57. void* const callbackPtr)
  58. : fPluginType(ptype),
  59. fCallback(callback),
  60. fCallbackPtr(callbackPtr),
  61. fBinaryIndex(0),
  62. fBinaryCount(binaries.size()),
  63. fBinaries(binaries),
  64. fDiscoveryTool(discoveryTool),
  65. fLastMessageTime(0),
  66. nextLabel(nullptr),
  67. nextMaker(nullptr),
  68. nextName(nullptr)
  69. {
  70. start();
  71. }
  72. CarlaPluginDiscovery(const char* const discoveryTool,
  73. const PluginType ptype,
  74. const CarlaPluginDiscoveryCallback callback,
  75. void* const callbackPtr)
  76. : fPluginType(ptype),
  77. fCallback(callback),
  78. fCallbackPtr(callbackPtr),
  79. fBinaryIndex(0),
  80. fBinaryCount(1),
  81. fDiscoveryTool(discoveryTool),
  82. fLastMessageTime(0),
  83. nextLabel(nullptr),
  84. nextMaker(nullptr),
  85. nextName(nullptr)
  86. {
  87. start();
  88. }
  89. ~CarlaPluginDiscovery()
  90. {
  91. std::free(nextLabel);
  92. std::free(nextMaker);
  93. std::free(nextName);
  94. }
  95. // closePipeServer()
  96. bool idle()
  97. {
  98. if (isPipeRunning())
  99. {
  100. idlePipe();
  101. // automatically skip a plugin if 30s passes without a reply
  102. const uint32_t timeNow = water::Time::getMillisecondCounter();
  103. if (timeNow - fLastMessageTime < 30000)
  104. return true;
  105. carla_stdout("Plugin took too long to respond, skipping...");
  106. stopPipeServer(1000);
  107. }
  108. if (++fBinaryIndex == fBinaryCount)
  109. return false;
  110. start();
  111. return true;
  112. }
  113. protected:
  114. bool msgReceived(const char* const msg) noexcept
  115. {
  116. fLastMessageTime = water::Time::getMillisecondCounter();
  117. if (std::strcmp(msg, "warning") == 0 || std::strcmp(msg, "error") == 0)
  118. {
  119. const char* text = nullptr;
  120. readNextLineAsString(text, false);
  121. carla_stdout("discovery: %s", text);
  122. return true;
  123. }
  124. if (std::strcmp(msg, "init") == 0)
  125. {
  126. const char* _;
  127. readNextLineAsString(_, false);
  128. new (&nextInfo) _CarlaPluginDiscoveryInfo();
  129. return true;
  130. }
  131. if (std::strcmp(msg, "end") == 0)
  132. {
  133. const char* _;
  134. readNextLineAsString(_, false);
  135. if (nextInfo.label == nullptr)
  136. nextInfo.label = gPluginsDiscoveryNullCharPtr;
  137. if (nextInfo.metadata.maker == nullptr)
  138. nextInfo.metadata.maker = gPluginsDiscoveryNullCharPtr;
  139. if (nextInfo.metadata.name == nullptr)
  140. nextInfo.metadata.name = gPluginsDiscoveryNullCharPtr;
  141. if (fBinaries.empty())
  142. {
  143. char* filename = nullptr;
  144. if (fPluginType == CB::PLUGIN_LV2)
  145. {
  146. do {
  147. const char* const slash = std::strchr(nextLabel, CARLA_OS_SEP);
  148. CARLA_SAFE_ASSERT_BREAK(slash != nullptr);
  149. filename = strdup(nextLabel);
  150. filename[slash - nextLabel] = '\0';
  151. nextInfo.filename = filename;
  152. nextInfo.label = slash + 1;
  153. } while (false);
  154. }
  155. nextInfo.ptype = fPluginType;
  156. fCallback(fCallbackPtr, &nextInfo);
  157. std::free(filename);
  158. }
  159. else
  160. {
  161. const water::String filename(fBinaries[fBinaryIndex].getFullPathName());
  162. nextInfo.filename = filename.toRawUTF8();
  163. nextInfo.ptype = fPluginType;
  164. carla_stdout("Found %s from %s", nextInfo.metadata.name, nextInfo.filename);
  165. fCallback(fCallbackPtr, &nextInfo);
  166. }
  167. std::free(nextLabel);
  168. nextLabel = nullptr;
  169. std::free(nextMaker);
  170. nextMaker = nullptr;
  171. std::free(nextName);
  172. nextName = nullptr;
  173. return true;
  174. }
  175. if (std::strcmp(msg, "build") == 0)
  176. {
  177. uint8_t btype = 0;
  178. readNextLineAsByte(btype);
  179. nextInfo.btype = static_cast<BinaryType>(btype);
  180. return true;
  181. }
  182. if (std::strcmp(msg, "hints") == 0)
  183. {
  184. readNextLineAsUInt(nextInfo.metadata.hints);
  185. return true;
  186. }
  187. if (std::strcmp(msg, "category") == 0)
  188. {
  189. const char* category = nullptr;
  190. readNextLineAsString(category, false);
  191. nextInfo.metadata.category = CB::getPluginCategoryFromString(category);
  192. return true;
  193. }
  194. if (std::strcmp(msg, "name") == 0)
  195. {
  196. nextInfo.metadata.name = nextName = readNextLineAsString();
  197. return true;
  198. }
  199. if (std::strcmp(msg, "label") == 0)
  200. {
  201. nextInfo.label = nextLabel = readNextLineAsString();
  202. return true;
  203. }
  204. if (std::strcmp(msg, "maker") == 0)
  205. {
  206. nextInfo.metadata.maker = nextMaker = readNextLineAsString();
  207. return true;
  208. }
  209. if (std::strcmp(msg, "uniqueId") == 0)
  210. {
  211. readNextLineAsULong(nextInfo.uniqueId);
  212. return true;
  213. }
  214. if (std::strcmp(msg, "audio.ins") == 0)
  215. {
  216. readNextLineAsUInt(nextInfo.io.audioIns);
  217. return true;
  218. }
  219. if (std::strcmp(msg, "audio.outs") == 0)
  220. {
  221. readNextLineAsUInt(nextInfo.io.audioOuts);
  222. return true;
  223. }
  224. if (std::strcmp(msg, "cv.ins") == 0)
  225. {
  226. readNextLineAsUInt(nextInfo.io.cvIns);
  227. return true;
  228. }
  229. if (std::strcmp(msg, "cv.outs") == 0)
  230. {
  231. readNextLineAsUInt(nextInfo.io.cvOuts);
  232. return true;
  233. }
  234. if (std::strcmp(msg, "midi.ins") == 0)
  235. {
  236. readNextLineAsUInt(nextInfo.io.midiIns);
  237. return true;
  238. }
  239. if (std::strcmp(msg, "midi.outs") == 0)
  240. {
  241. readNextLineAsUInt(nextInfo.io.midiOuts);
  242. return true;
  243. }
  244. if (std::strcmp(msg, "parameters.ins") == 0)
  245. {
  246. readNextLineAsUInt(nextInfo.io.parameterIns);
  247. return true;
  248. }
  249. if (std::strcmp(msg, "parameters.outs") == 0)
  250. {
  251. readNextLineAsUInt(nextInfo.io.parameterOuts);
  252. return true;
  253. }
  254. if (std::strcmp(msg, "exiting") == 0)
  255. {
  256. stopPipeServer(1000);
  257. return true;
  258. }
  259. carla_stdout("discovery: unknown message '%s' received", msg);
  260. return true;
  261. }
  262. private:
  263. const PluginType fPluginType;
  264. const CarlaPluginDiscoveryCallback fCallback;
  265. void* const fCallbackPtr;
  266. uint fBinaryIndex;
  267. const uint fBinaryCount;
  268. const std::vector<water::File> fBinaries;
  269. const CarlaString fDiscoveryTool;
  270. uint32_t fLastMessageTime;
  271. CarlaPluginDiscoveryInfo nextInfo;
  272. char* nextLabel;
  273. char* nextMaker;
  274. char* nextName;
  275. void start()
  276. {
  277. fLastMessageTime = water::Time::getMillisecondCounter();
  278. if (fBinaries.empty())
  279. {
  280. startPipeServer(fDiscoveryTool,
  281. getPluginTypeAsString(fPluginType),
  282. ":all");
  283. }
  284. else
  285. {
  286. carla_stdout("Scanning %s...", fBinaries[fBinaryIndex].getFullPathName().toRawUTF8());
  287. startPipeServer(fDiscoveryTool,
  288. getPluginTypeAsString(fPluginType),
  289. fBinaries[fBinaryIndex].getFullPathName().toRawUTF8());
  290. }
  291. }
  292. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginDiscovery)
  293. };
  294. // --------------------------------------------------------------------------------------------------------------------
  295. static std::vector<water::File> findDirectories(const char* const pluginPath, const char* const wildcard)
  296. {
  297. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
  298. if (pluginPath[0] == '\0')
  299. return {};
  300. using water::File;
  301. using water::String;
  302. using water::StringArray;
  303. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  304. if (splitPaths.size() == 0)
  305. return {};
  306. std::vector<water::File> ret;
  307. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  308. {
  309. const File dir(*it);
  310. std::vector<File> results;
  311. if (dir.findChildFiles(results, File::findDirectories|File::ignoreHiddenFiles, true, wildcard) > 0)
  312. {
  313. ret.reserve(ret.size() + results.size());
  314. ret.insert(ret.end(), results.begin(), results.end());
  315. }
  316. }
  317. return ret;
  318. }
  319. static std::vector<water::File> findFiles(const char* const pluginPath, const char* const wildcard)
  320. {
  321. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
  322. if (pluginPath[0] == '\0')
  323. return {};
  324. using water::File;
  325. using water::String;
  326. using water::StringArray;
  327. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  328. if (splitPaths.size() == 0)
  329. return {};
  330. std::vector<water::File> ret;
  331. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  332. {
  333. const File dir(*it);
  334. std::vector<File> results;
  335. if (dir.findChildFiles(results, File::findFiles|File::ignoreHiddenFiles, true, wildcard) > 0)
  336. {
  337. ret.reserve(ret.size() + results.size());
  338. ret.insert(ret.end(), results.begin(), results.end());
  339. }
  340. }
  341. return ret;
  342. }
  343. static std::vector<water::File> findVST3s(const char* const pluginPath)
  344. {
  345. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
  346. if (pluginPath[0] == '\0')
  347. return {};
  348. using water::File;
  349. using water::String;
  350. using water::StringArray;
  351. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  352. if (splitPaths.size() == 0)
  353. return {};
  354. std::vector<water::File> ret;
  355. #if defined(CARLA_OS_WIN)
  356. static constexpr const uint flags = File::findDirectories|File::findFiles;
  357. #else
  358. static constexpr const uint flags = File::findDirectories;
  359. #endif
  360. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  361. {
  362. const File dir(*it);
  363. std::vector<File> results;
  364. if (dir.findChildFiles(results, flags|File::ignoreHiddenFiles, true, "*.vst3") > 0)
  365. {
  366. ret.reserve(ret.size() + results.size());
  367. ret.insert(ret.end(), results.begin(), results.end());
  368. }
  369. }
  370. return ret;
  371. }
  372. CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* const discoveryTool,
  373. const PluginType ptype,
  374. const char* const pluginPath,
  375. const CarlaPluginDiscoveryCallback callback,
  376. void* const callbackPtr)
  377. {
  378. CARLA_SAFE_ASSERT_RETURN(discoveryTool != nullptr && discoveryTool[0] != '\0', nullptr);
  379. CARLA_SAFE_ASSERT_RETURN(callback != nullptr, nullptr);
  380. bool directories = false;
  381. const char* wildcard = nullptr;
  382. switch (ptype)
  383. {
  384. case CB::PLUGIN_NONE:
  385. case CB::PLUGIN_JACK:
  386. case CB::PLUGIN_TYPE_COUNT:
  387. return nullptr;
  388. case CB::PLUGIN_SFZ:
  389. case CB::PLUGIN_JSFX:
  390. {
  391. const CarlaScopedEnvVar csev("CARLA_DISCOVERY_PATH", pluginPath);
  392. return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
  393. }
  394. case CB::PLUGIN_INTERNAL:
  395. case CB::PLUGIN_LV2:
  396. case CB::PLUGIN_AU:
  397. return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
  398. case CB::PLUGIN_LADSPA:
  399. case CB::PLUGIN_DSSI:
  400. #if defined(CARLA_OS_MAC)
  401. wildcard = "*.dylib";
  402. #elif defined(CARLA_OS_WIN)
  403. wildcard = "*.dll";
  404. #else
  405. wildcard = "*.so";
  406. #endif
  407. break;
  408. case CB::PLUGIN_VST2:
  409. #if defined(CARLA_OS_MAC)
  410. directories = true;
  411. wildcard = "*.vst";
  412. #elif defined(CARLA_OS_WIN)
  413. wildcard = "*.dll";
  414. #else
  415. wildcard = "*.so";
  416. #endif
  417. break;
  418. case CB::PLUGIN_VST3:
  419. wildcard = "*.vst3";
  420. break;
  421. case CB::PLUGIN_CLAP:
  422. wildcard = "*.clap";
  423. #ifdef CARLA_OS_MAC
  424. directories = true;
  425. #endif
  426. break;
  427. case CB::PLUGIN_DLS:
  428. wildcard = "*.dls";
  429. break;
  430. case CB::PLUGIN_GIG:
  431. wildcard = "*.gig";
  432. break;
  433. case CB::PLUGIN_SF2:
  434. wildcard = "*.sf2";
  435. break;
  436. }
  437. CARLA_SAFE_ASSERT_RETURN(wildcard != nullptr, nullptr);
  438. const std::vector<water::File> binaries(ptype == CB::PLUGIN_VST3
  439. ? findVST3s(pluginPath)
  440. : directories
  441. ? findDirectories(pluginPath, wildcard)
  442. : findFiles(pluginPath, wildcard));
  443. if (binaries.size() == 0)
  444. return nullptr;
  445. return new CarlaPluginDiscovery(discoveryTool, ptype, std::move(binaries), callback, callbackPtr);
  446. }
  447. bool carla_plugin_discovery_idle(CarlaPluginDiscoveryHandle handle)
  448. {
  449. return static_cast<CarlaPluginDiscovery*>(handle)->idle();
  450. }
  451. void carla_plugin_discovery_stop(CarlaPluginDiscoveryHandle handle)
  452. {
  453. delete static_cast<CarlaPluginDiscovery*>(handle);
  454. }
  455. // --------------------------------------------------------------------------------------------------------------------