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.

561 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(static_cast<uint>(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 bool findDirectories(std::vector<water::File>& files, const char* const pluginPath, const char* const wildcard)
  296. {
  297. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  298. if (pluginPath[0] == '\0')
  299. return true;
  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 true;
  306. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  307. {
  308. const File dir(*it);
  309. std::vector<File> results;
  310. if (dir.findChildFiles(results, File::findDirectories|File::ignoreHiddenFiles, true, wildcard) > 0)
  311. {
  312. files.reserve(files.size() + results.size());
  313. files.insert(files.end(), results.begin(), results.end());
  314. }
  315. }
  316. return files.empty();
  317. }
  318. static bool findFiles(std::vector<water::File>& files, const char* const pluginPath, const char* const wildcard)
  319. {
  320. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  321. if (pluginPath[0] == '\0')
  322. return true;
  323. using water::File;
  324. using water::String;
  325. using water::StringArray;
  326. const StringArray splitPaths(StringArray::fromTokens(pluginPath, CARLA_OS_SPLIT_STR, ""));
  327. if (splitPaths.size() == 0)
  328. return true;
  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. files.reserve(files.size() + results.size());
  336. files.insert(files.end(), results.begin(), results.end());
  337. }
  338. }
  339. return files.empty();
  340. }
  341. static bool findVST3s(std::vector<water::File>& files, const char* const pluginPath)
  342. {
  343. CARLA_SAFE_ASSERT_RETURN(pluginPath != nullptr, true);
  344. if (pluginPath[0] == '\0')
  345. return true;
  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 true;
  352. #if defined(CARLA_OS_WIN)
  353. static constexpr const uint flags = File::findDirectories|File::findFiles;
  354. #else
  355. static constexpr const uint flags = File::findDirectories;
  356. #endif
  357. for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it)
  358. {
  359. const File dir(*it);
  360. std::vector<File> results;
  361. if (dir.findChildFiles(results, flags|File::ignoreHiddenFiles, true, "*.vst3") > 0)
  362. {
  363. files.reserve(files.size() + results.size());
  364. files.insert(files.end(), results.begin(), results.end());
  365. }
  366. }
  367. return files.empty();
  368. }
  369. CarlaPluginDiscoveryHandle carla_plugin_discovery_start(const char* const discoveryTool,
  370. const PluginType ptype,
  371. const char* const pluginPath,
  372. const CarlaPluginDiscoveryCallback callback,
  373. void* const callbackPtr)
  374. {
  375. CARLA_SAFE_ASSERT_RETURN(discoveryTool != nullptr && discoveryTool[0] != '\0', nullptr);
  376. CARLA_SAFE_ASSERT_RETURN(callback != nullptr, nullptr);
  377. bool directories = false;
  378. const char* wildcard = nullptr;
  379. switch (ptype)
  380. {
  381. case CB::PLUGIN_NONE:
  382. case CB::PLUGIN_JACK:
  383. case CB::PLUGIN_TYPE_COUNT:
  384. return nullptr;
  385. case CB::PLUGIN_SFZ:
  386. case CB::PLUGIN_JSFX:
  387. {
  388. const CarlaScopedEnvVar csev("CARLA_DISCOVERY_PATH", pluginPath);
  389. return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
  390. }
  391. case CB::PLUGIN_INTERNAL:
  392. case CB::PLUGIN_LV2:
  393. case CB::PLUGIN_AU:
  394. return new CarlaPluginDiscovery(discoveryTool, ptype, callback, callbackPtr);
  395. case CB::PLUGIN_LADSPA:
  396. case CB::PLUGIN_DSSI:
  397. #if defined(CARLA_OS_MAC)
  398. wildcard = "*.dylib";
  399. #elif defined(CARLA_OS_WIN)
  400. wildcard = "*.dll";
  401. #else
  402. wildcard = "*.so";
  403. #endif
  404. break;
  405. case CB::PLUGIN_VST2:
  406. #if defined(CARLA_OS_MAC)
  407. directories = true;
  408. wildcard = "*.vst";
  409. #elif defined(CARLA_OS_WIN)
  410. wildcard = "*.dll";
  411. #else
  412. wildcard = "*.so";
  413. #endif
  414. break;
  415. case CB::PLUGIN_VST3:
  416. directories = true;
  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. std::vector<water::File> files;
  437. if (ptype == CB::PLUGIN_VST3)
  438. {
  439. if (findVST3s(files, pluginPath))
  440. return nullptr;
  441. }
  442. else if (directories)
  443. {
  444. if (findDirectories(files, pluginPath, wildcard))
  445. return nullptr;
  446. }
  447. else
  448. {
  449. if (findFiles(files, pluginPath, wildcard))
  450. return nullptr;
  451. }
  452. return new CarlaPluginDiscovery(discoveryTool, ptype, std::move(files), callback, callbackPtr);
  453. }
  454. bool carla_plugin_discovery_idle(CarlaPluginDiscoveryHandle handle)
  455. {
  456. return static_cast<CarlaPluginDiscovery*>(handle)->idle();
  457. }
  458. void carla_plugin_discovery_stop(CarlaPluginDiscoveryHandle handle)
  459. {
  460. delete static_cast<CarlaPluginDiscovery*>(handle);
  461. }
  462. // --------------------------------------------------------------------------------------------------------------------