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.

555 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. fCallback(fCallbackPtr, &nextInfo);
  156. std::free(filename);
  157. }
  158. else
  159. {
  160. const water::String filename(fBinaries[fBinaryIndex].getFullPathName());
  161. nextInfo.filename = filename.toRawUTF8();
  162. carla_stdout("Found %s from %s", nextInfo.metadata.name, nextInfo.filename);
  163. fCallback(fCallbackPtr, &nextInfo);
  164. }
  165. std::free(nextLabel);
  166. nextLabel = nullptr;
  167. std::free(nextMaker);
  168. nextMaker = nullptr;
  169. std::free(nextName);
  170. nextName = nullptr;
  171. return true;
  172. }
  173. if (std::strcmp(msg, "build") == 0)
  174. {
  175. uint8_t btype = 0;
  176. readNextLineAsByte(btype);
  177. nextInfo.btype = static_cast<BinaryType>(btype);
  178. return true;
  179. }
  180. if (std::strcmp(msg, "hints") == 0)
  181. {
  182. readNextLineAsUInt(nextInfo.metadata.hints);
  183. return true;
  184. }
  185. if (std::strcmp(msg, "category") == 0)
  186. {
  187. const char* category = nullptr;
  188. readNextLineAsString(category, false);
  189. nextInfo.metadata.category = CB::getPluginCategoryFromString(category);
  190. return true;
  191. }
  192. if (std::strcmp(msg, "name") == 0)
  193. {
  194. nextInfo.metadata.name = nextName = readNextLineAsString();
  195. return true;
  196. }
  197. if (std::strcmp(msg, "label") == 0)
  198. {
  199. nextInfo.label = nextLabel = readNextLineAsString();
  200. return true;
  201. }
  202. if (std::strcmp(msg, "maker") == 0)
  203. {
  204. nextInfo.metadata.maker = nextMaker = readNextLineAsString();
  205. return true;
  206. }
  207. if (std::strcmp(msg, "uniqueId") == 0)
  208. {
  209. readNextLineAsULong(nextInfo.uniqueId);
  210. return true;
  211. }
  212. if (std::strcmp(msg, "audio.ins") == 0)
  213. {
  214. readNextLineAsUInt(nextInfo.io.audioIns);
  215. return true;
  216. }
  217. if (std::strcmp(msg, "audio.outs") == 0)
  218. {
  219. readNextLineAsUInt(nextInfo.io.audioOuts);
  220. return true;
  221. }
  222. if (std::strcmp(msg, "cv.ins") == 0)
  223. {
  224. readNextLineAsUInt(nextInfo.io.cvIns);
  225. return true;
  226. }
  227. if (std::strcmp(msg, "cv.outs") == 0)
  228. {
  229. readNextLineAsUInt(nextInfo.io.cvOuts);
  230. return true;
  231. }
  232. if (std::strcmp(msg, "midi.ins") == 0)
  233. {
  234. readNextLineAsUInt(nextInfo.io.midiIns);
  235. return true;
  236. }
  237. if (std::strcmp(msg, "midi.outs") == 0)
  238. {
  239. readNextLineAsUInt(nextInfo.io.midiOuts);
  240. return true;
  241. }
  242. if (std::strcmp(msg, "parameters.ins") == 0)
  243. {
  244. readNextLineAsUInt(nextInfo.io.parameterIns);
  245. return true;
  246. }
  247. if (std::strcmp(msg, "parameters.outs") == 0)
  248. {
  249. readNextLineAsUInt(nextInfo.io.parameterOuts);
  250. return true;
  251. }
  252. if (std::strcmp(msg, "exiting") == 0)
  253. {
  254. stopPipeServer(1000);
  255. return true;
  256. }
  257. carla_stdout("discovery: unknown message '%s' received", msg);
  258. return true;
  259. }
  260. private:
  261. const PluginType fPluginType;
  262. const CarlaPluginDiscoveryCallback fCallback;
  263. void* const fCallbackPtr;
  264. uint fBinaryIndex;
  265. const uint fBinaryCount;
  266. const std::vector<water::File> fBinaries;
  267. const CarlaString fDiscoveryTool;
  268. uint32_t fLastMessageTime;
  269. CarlaPluginDiscoveryInfo nextInfo;
  270. char* nextLabel;
  271. char* nextMaker;
  272. char* nextName;
  273. void start()
  274. {
  275. fLastMessageTime = water::Time::getMillisecondCounter();
  276. if (fBinaries.empty())
  277. {
  278. startPipeServer(fDiscoveryTool,
  279. getPluginTypeAsString(fPluginType),
  280. ":all");
  281. }
  282. else
  283. {
  284. carla_stdout("Scanning %s...", fBinaries[fBinaryIndex].getFullPathName().toRawUTF8());
  285. startPipeServer(fDiscoveryTool,
  286. getPluginTypeAsString(fPluginType),
  287. fBinaries[fBinaryIndex].getFullPathName().toRawUTF8());
  288. }
  289. }
  290. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginDiscovery)
  291. };
  292. // --------------------------------------------------------------------------------------------------------------------
  293. static std::vector<water::File> findDirectories(const char* const pluginPath, const char* const wildcard)
  294. {
  295. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
  296. if (pluginPath[0] == '\0')
  297. return {};
  298. using water::File;
  299. using water::String;
  300. using water::StringArray;
  301. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  302. if (splitPaths.size() == 0)
  303. return {};
  304. std::vector<water::File> ret;
  305. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  306. {
  307. const File dir(*it);
  308. std::vector<File> results;
  309. if (dir.findChildFiles(results, File::findDirectories|File::ignoreHiddenFiles, true, wildcard) > 0)
  310. {
  311. ret.reserve(ret.size() + results.size());
  312. ret.insert(ret.end(), results.begin(), results.end());
  313. }
  314. }
  315. return ret;
  316. }
  317. static std::vector<water::File> findFiles(const char* const pluginPath, const char* const wildcard)
  318. {
  319. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
  320. if (pluginPath[0] == '\0')
  321. return {};
  322. using water::File;
  323. using water::String;
  324. using water::StringArray;
  325. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  326. if (splitPaths.size() == 0)
  327. return {};
  328. std::vector<water::File> ret;
  329. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  330. {
  331. const File dir(*it);
  332. std::vector<File> results;
  333. if (dir.findChildFiles(results, File::findFiles|File::ignoreHiddenFiles, true, wildcard) > 0)
  334. {
  335. ret.reserve(ret.size() + results.size());
  336. ret.insert(ret.end(), results.begin(), results.end());
  337. }
  338. }
  339. return ret;
  340. }
  341. static std::vector<water::File> findVST3s(const char* const pluginPath)
  342. {
  343. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, {});
  344. if (pluginPath[0] == '\0')
  345. return {};
  346. using water::File;
  347. using water::String;
  348. using water::StringArray;
  349. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  350. if (splitPaths.size() == 0)
  351. return {};
  352. std::vector<water::File> ret;
  353. #if defined(CARLA_OS_WIN)
  354. static constexpr const uint flags = File::findDirectories|File::findFiles;
  355. #else
  356. static constexpr const uint flags = File::findDirectories;
  357. #endif
  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, flags|File::ignoreHiddenFiles, true, "*.vst3") > 0)
  363. {
  364. ret.reserve(ret.size() + results.size());
  365. ret.insert(ret.end(), results.begin(), results.end());
  366. }
  367. }
  368. return ret;
  369. }
  370. CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* const discoveryTool,
  371. const PluginType ptype,
  372. const char* const pluginPath,
  373. const CarlaPluginDiscoveryCallback callback,
  374. void* const callbackPtr)
  375. {
  376. CARLA_SAFE_ASSERT_RETURN(discoveryTool != nullptr && discoveryTool[0] != '\0', nullptr);
  377. CARLA_SAFE_ASSERT_RETURN(callback != nullptr, nullptr);
  378. bool directories = false;
  379. const char* wildcard = nullptr;
  380. switch (ptype)
  381. {
  382. case CB::PLUGIN_NONE:
  383. case CB::PLUGIN_JACK:
  384. case CB::PLUGIN_TYPE_COUNT:
  385. return nullptr;
  386. case CB::PLUGIN_SFZ:
  387. case CB::PLUGIN_JSFX:
  388. {
  389. const CarlaScopedEnvVar csev("CARLA_DISCOVERY_PATH", pluginPath);
  390. return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
  391. }
  392. case CB::PLUGIN_INTERNAL:
  393. case CB::PLUGIN_LV2:
  394. case CB::PLUGIN_AU:
  395. return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
  396. case CB::PLUGIN_LADSPA:
  397. case CB::PLUGIN_DSSI:
  398. #if defined(CARLA_OS_MAC)
  399. wildcard = "*.dylib";
  400. #elif defined(CARLA_OS_WIN)
  401. wildcard = "*.dll";
  402. #else
  403. wildcard = "*.so";
  404. #endif
  405. break;
  406. case CB::PLUGIN_VST2:
  407. #if defined(CARLA_OS_MAC)
  408. directories = true;
  409. wildcard = "*.vst";
  410. #elif defined(CARLA_OS_WIN)
  411. wildcard = "*.dll";
  412. #else
  413. wildcard = "*.so";
  414. #endif
  415. break;
  416. case CB::PLUGIN_VST3:
  417. wildcard = "*.vst3";
  418. break;
  419. case CB::PLUGIN_CLAP:
  420. wildcard = "*.clap";
  421. #ifdef CARLA_OS_MAC
  422. directories = true;
  423. #endif
  424. break;
  425. case CB::PLUGIN_DLS:
  426. wildcard = "*.dls";
  427. break;
  428. case CB::PLUGIN_GIG:
  429. wildcard = "*.gig";
  430. break;
  431. case CB::PLUGIN_SF2:
  432. wildcard = "*.sf2";
  433. break;
  434. }
  435. CARLA_SAFE_ASSERT_RETURN(wildcard != nullptr, nullptr);
  436. const std::vector<water::File> binaries(ptype == CB::PLUGIN_VST3
  437. ? findVST3s(pluginPath)
  438. : directories
  439. ? findDirectories(pluginPath, wildcard)
  440. : findFiles(pluginPath, wildcard));
  441. if (binaries.size() == 0)
  442. return nullptr;
  443. return new CarlaPluginDiscovery(discoveryTool, ptype, std::move(binaries), callback, callbackPtr);
  444. }
  445. bool carla_plugin_discovery_idle(CarlaPluginDiscoveryHandle handle)
  446. {
  447. return static_cast<CarlaPluginDiscovery*>(handle)->idle();
  448. }
  449. void carla_plugin_discovery_stop(CarlaPluginDiscoveryHandle handle)
  450. {
  451. delete static_cast<CarlaPluginDiscovery*>(handle);
  452. }
  453. // --------------------------------------------------------------------------------------------------------------------