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.

558 lines
17KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021 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 3 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 LICENSE file.
  16. */
  17. #include <asset.hpp>
  18. #include <library.hpp>
  19. #include <midi.hpp>
  20. #include <patch.hpp>
  21. #include <plugin.hpp>
  22. #include <random.hpp>
  23. #include <settings.hpp>
  24. #include <system.hpp>
  25. #include <app/Scene.hpp>
  26. #include <engine/Engine.hpp>
  27. #include <ui/common.hpp>
  28. #include <window/Window.hpp>
  29. #include <osdialog.h>
  30. #ifdef NDEBUG
  31. # undef DEBUG
  32. #endif
  33. #include "DistrhoPluginUtils.hpp"
  34. #include "PluginContext.hpp"
  35. #include "WindowParameters.hpp"
  36. #include "extra/Base64.hpp"
  37. #include "extra/SharedResourcePointer.hpp"
  38. namespace rack {
  39. namespace plugin {
  40. void initStaticPlugins();
  41. void destroyStaticPlugins();
  42. }
  43. }
  44. START_NAMESPACE_DISTRHO
  45. // -----------------------------------------------------------------------------------------------------------
  46. struct Initializer {
  47. Initializer(const CardinalBasePlugin* const plugin)
  48. {
  49. using namespace rack;
  50. settings::allowCursorLock = false;
  51. settings::autoCheckUpdates = false;
  52. settings::autosaveInterval = 0;
  53. settings::devMode = true;
  54. settings::discordUpdateActivity = false;
  55. settings::isPlugin = true;
  56. settings::skipLoadOnLaunch = true;
  57. settings::showTipsOnLaunch = false;
  58. settings::threadCount = 1;
  59. system::init();
  60. logger::init();
  61. random::init();
  62. ui::init();
  63. if (asset::systemDir.empty())
  64. {
  65. if (const char* const bundlePath = plugin->getBundlePath())
  66. {
  67. if (const char* const resourcePath = getResourcePath(bundlePath))
  68. {
  69. asset::bundlePath = system::join(resourcePath, "PluginManifests");
  70. asset::systemDir = resourcePath;
  71. }
  72. }
  73. if (asset::systemDir.empty())
  74. {
  75. #ifdef CARDINAL_PLUGIN_SOURCE_DIR
  76. // Make system dir point to source code location as fallback
  77. asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack";
  78. // And if that fails, use install target prefix
  79. if (! system::isDirectory(system::join(asset::systemDir, "res")))
  80. #endif
  81. {
  82. asset::bundlePath = CARDINAL_PLUGIN_PREFIX "/share/Cardinal/PluginManifests";
  83. asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/Cardinal";
  84. }
  85. }
  86. asset::userDir = asset::systemDir;
  87. }
  88. // Log environment
  89. INFO("%s %s v%s", APP_NAME.c_str(), APP_EDITION.c_str(), APP_VERSION.c_str());
  90. INFO("%s", system::getOperatingSystemInfo().c_str());
  91. INFO("Binary filename: %s", getBinaryFilename());
  92. INFO("Bundle path: %s", plugin->getBundlePath());
  93. INFO("System directory: %s", asset::systemDir.c_str());
  94. INFO("User directory: %s", asset::userDir.c_str());
  95. // Check existence of the system res/ directory
  96. if (! system::isDirectory(asset::systemDir))
  97. {
  98. d_stderr2("System directory \"%s\" does not exist.\n"
  99. "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
  100. }
  101. INFO("Initializing audio driver");
  102. rack::audio::addDriver(0, new CardinalAudioDriver);
  103. INFO("Initializing plugins");
  104. plugin::initStaticPlugins();
  105. }
  106. ~Initializer()
  107. {
  108. using namespace rack;
  109. INFO("Destroying plugins");
  110. plugin::destroyStaticPlugins();
  111. INFO("Destroying MIDI devices");
  112. midi::destroy();
  113. INFO("Destroying audio devices");
  114. audio::destroy();
  115. INFO("Destroying logger");
  116. logger::destroy();
  117. }
  118. };
  119. // -----------------------------------------------------------------------------------------------------------
  120. struct ScopedContext {
  121. const MutexLocker cml;
  122. ScopedContext(const CardinalBasePlugin* const plugin)
  123. : cml(plugin->context->mutex)
  124. {
  125. rack::contextSet(plugin->context);
  126. }
  127. ~ScopedContext()
  128. {
  129. rack::contextSet(nullptr);
  130. }
  131. };
  132. // -----------------------------------------------------------------------------------------------------------
  133. class CardinalPlugin : public CardinalBasePlugin
  134. {
  135. SharedResourcePointer<Initializer> fInitializer;
  136. float* fAudioBufferIn;
  137. float* fAudioBufferOut;
  138. std::string fAutosavePath;
  139. // for base/context handling
  140. bool fIsActive;
  141. rack::audio::Device* fCurrentDevice;
  142. Mutex fDeviceMutex;
  143. float fWindowParameters[kWindowParameterCount];
  144. public:
  145. CardinalPlugin()
  146. : CardinalBasePlugin(kModuleParameters + kWindowParameterCount, 0, 1),
  147. fInitializer(this),
  148. fAudioBufferIn(nullptr),
  149. fAudioBufferOut(nullptr),
  150. fIsActive(false),
  151. fCurrentDevice(nullptr)
  152. {
  153. fWindowParameters[kWindowParameterCableOpacity] = 50.0f;
  154. fWindowParameters[kWindowParameterCableTension] = 50.0f;
  155. fWindowParameters[kWindowParameterRackBrightness] = 100.0f;
  156. fWindowParameters[kWindowParameterHaloBrightness] = 25.0f;
  157. // create unique temporary path for this instance
  158. try {
  159. char uidBuf[24];
  160. const std::string tmp = rack::system::getTempDirectory();
  161. for (int i=1;; ++i)
  162. {
  163. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  164. const std::string trypath = rack::system::join(tmp, uidBuf);
  165. if (! rack::system::exists(trypath))
  166. {
  167. if (rack::system::createDirectories(trypath))
  168. fAutosavePath = trypath;
  169. break;
  170. }
  171. }
  172. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  173. const ScopedContext sc(this);
  174. context->engine = new rack::engine::Engine;
  175. context->history = new rack::history::State;
  176. context->patch = new rack::patch::Manager;
  177. context->patch->autosavePath = fAutosavePath;
  178. context->patch->templatePath = rack::system::join(rack::asset::systemDir, "template.vcv");
  179. // context->patch->templatePath = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "template.vcv";
  180. context->event = new rack::widget::EventState;
  181. context->scene = new rack::app::Scene;
  182. context->event->rootWidget = context->scene;
  183. context->patch->loadTemplate();
  184. context->engine->startFallbackThread();
  185. }
  186. ~CardinalPlugin() override
  187. {
  188. {
  189. const ScopedContext sc(this);
  190. /*
  191. delete context->scene;
  192. context->scene = nullptr;
  193. delete context->event;
  194. context->event = nullptr;
  195. */
  196. delete context;
  197. }
  198. if (! fAutosavePath.empty())
  199. rack::system::removeRecursively(fAutosavePath);
  200. }
  201. CardinalPluginContext* getRackContext() const noexcept
  202. {
  203. return context;
  204. }
  205. protected:
  206. /* --------------------------------------------------------------------------------------------------------
  207. * Cardinal Base things */
  208. bool isActive() const noexcept override
  209. {
  210. return fIsActive;
  211. }
  212. bool canAssignDevice() const noexcept override
  213. {
  214. const MutexLocker cml(fDeviceMutex);
  215. return fCurrentDevice == nullptr;
  216. }
  217. void assignDevice(rack::audio::Device* const dev) noexcept override
  218. {
  219. DISTRHO_SAFE_ASSERT_RETURN(fCurrentDevice == nullptr,);
  220. const MutexLocker cml(fDeviceMutex);
  221. fCurrentDevice = dev;
  222. }
  223. bool clearDevice(rack::audio::Device* const dev) noexcept override
  224. {
  225. const MutexLocker cml(fDeviceMutex);
  226. if (fCurrentDevice != dev)
  227. return false;
  228. fCurrentDevice = nullptr;
  229. return true;
  230. }
  231. /* --------------------------------------------------------------------------------------------------------
  232. * Information */
  233. const char* getLabel() const override
  234. {
  235. return "Cardinal";
  236. }
  237. const char* getDescription() const override
  238. {
  239. return ""
  240. "Cardinal is an open-source self-contained special plugin version of VCVRack, using DPF.\n"
  241. "It is NOT an official VCV project, and it is not affiliated with it in any way.\n";
  242. }
  243. const char* getMaker() const override
  244. {
  245. return "DISTRHO";
  246. }
  247. const char* getHomePage() const override
  248. {
  249. return "https://github.com/DISTRHO/Cardinal";
  250. }
  251. const char* getLicense() const override
  252. {
  253. return "GPLv3+";
  254. }
  255. uint32_t getVersion() const override
  256. {
  257. return d_version(2, 0, 0);
  258. }
  259. int64_t getUniqueId() const override
  260. {
  261. return d_cconst('d', 'C', 'd', 'n');
  262. }
  263. /* --------------------------------------------------------------------------------------------------------
  264. * Init */
  265. void initParameter(const uint32_t index, Parameter& parameter) override
  266. {
  267. if (index < kModuleParameters)
  268. {
  269. parameter.name = "Parameter ";
  270. parameter.name += String(index + 1);
  271. parameter.symbol = "param_";
  272. parameter.symbol += String(index + 1);
  273. parameter.unit = "v";
  274. parameter.hints = kParameterIsAutomable;
  275. parameter.ranges.def = 0.0f;
  276. parameter.ranges.min = 0.0f;
  277. parameter.ranges.max = 10.0f;
  278. return;
  279. }
  280. switch (index - kModuleParameters)
  281. {
  282. case kWindowParameterCableOpacity:
  283. parameter.name = "Cable Opacity";
  284. parameter.symbol = "cableOpacity";
  285. parameter.unit = "%";
  286. parameter.hints = kParameterIsAutomable;
  287. parameter.ranges.def = 50.0f;
  288. parameter.ranges.min = 0.0f;
  289. parameter.ranges.max = 100.0f;
  290. break;
  291. case kWindowParameterCableTension:
  292. parameter.name = "Cable Tension";
  293. parameter.symbol = "cableTension";
  294. parameter.unit = "%";
  295. parameter.hints = kParameterIsAutomable;
  296. parameter.ranges.def = 50.0f;
  297. parameter.ranges.min = 0.0f;
  298. parameter.ranges.max = 100.0f;
  299. break;
  300. case kWindowParameterRackBrightness:
  301. parameter.name = "Rack Brightness";
  302. parameter.symbol = "rackBrightness";
  303. parameter.unit = "%";
  304. parameter.hints = kParameterIsAutomable;
  305. parameter.ranges.def = 100.0f;
  306. parameter.ranges.min = 0.0f;
  307. parameter.ranges.max = 100.0f;
  308. break;
  309. case kWindowParameterHaloBrightness:
  310. parameter.name = "Halo Brightness";
  311. parameter.symbol = "haloBrightness";
  312. parameter.unit = "%";
  313. parameter.hints = kParameterIsAutomable;
  314. parameter.ranges.def = 25.0f;
  315. parameter.ranges.min = 0.0f;
  316. parameter.ranges.max = 100.0f;
  317. break;
  318. }
  319. }
  320. void initState(const uint32_t index, String& stateKey, String& defaultStateValue) override
  321. {
  322. DISTRHO_SAFE_ASSERT_RETURN(index == 0,);
  323. stateKey = "patch";
  324. defaultStateValue = "";
  325. }
  326. /* --------------------------------------------------------------------------------------------------------
  327. * Internal data */
  328. float getParameterValue(uint32_t index) const override
  329. {
  330. if (index < kModuleParameters)
  331. return context->parameters[index];
  332. index -= kModuleParameters;
  333. if (index < kWindowParameterCount)
  334. return fWindowParameters[index];
  335. return 0.0f;
  336. }
  337. void setParameterValue(uint32_t index, float value) override
  338. {
  339. if (index < kModuleParameters)
  340. {
  341. context->parameters[index] = value;
  342. return;
  343. }
  344. index -= kModuleParameters;
  345. if (index < kWindowParameterCount)
  346. {
  347. fWindowParameters[index] = value;
  348. return;
  349. }
  350. }
  351. String getState(const char* const key) const override
  352. {
  353. if (std::strcmp(key, "patch") != 0)
  354. return String();
  355. if (fAutosavePath.empty())
  356. return String();
  357. std::vector<uint8_t> data;
  358. {
  359. const ScopedContext sc(this);
  360. context->engine->prepareSave();
  361. context->patch->saveAutosave();
  362. context->patch->cleanAutosave();
  363. data = rack::system::archiveDirectory(fAutosavePath, 1);
  364. }
  365. return String::asBase64(data.data(), data.size());
  366. }
  367. void setState(const char* const key, const char* const value) override
  368. {
  369. if (std::strcmp(key, "patch") != 0)
  370. return;
  371. if (fAutosavePath.empty())
  372. return;
  373. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  374. const ScopedContext sc(this);
  375. rack::system::removeRecursively(fAutosavePath);
  376. rack::system::createDirectories(fAutosavePath);
  377. rack::system::unarchiveToDirectory(data, fAutosavePath);
  378. context->patch->loadAutosave();
  379. }
  380. /* --------------------------------------------------------------------------------------------------------
  381. * Process */
  382. void activate() override
  383. {
  384. const uint32_t bufferSize = getBufferSize() * DISTRHO_PLUGIN_NUM_OUTPUTS;
  385. fAudioBufferIn = new float[bufferSize];
  386. fAudioBufferOut = new float[bufferSize];
  387. std::memset(fAudioBufferIn, 0, sizeof(float)*bufferSize);
  388. {
  389. const MutexLocker cml(fDeviceMutex);
  390. if (fCurrentDevice != nullptr)
  391. fCurrentDevice->onStartStream();
  392. }
  393. }
  394. void deactivate() override
  395. {
  396. {
  397. const MutexLocker cml(fDeviceMutex);
  398. if (fCurrentDevice != nullptr)
  399. fCurrentDevice->onStopStream();
  400. }
  401. delete[] fAudioBufferIn;
  402. delete[] fAudioBufferOut;
  403. fAudioBufferIn = fAudioBufferOut = nullptr;
  404. }
  405. void run(const float** const inputs, float** const outputs, const uint32_t frames) override
  406. {
  407. /*
  408. context->engine->setFrame(getTimePosition().frame);
  409. context->engine->stepBlock(frames);
  410. */
  411. const MutexLocker cml(fDeviceMutex);
  412. // const MutexTryLocker cmtl(fPatchMutex);
  413. if (fCurrentDevice == nullptr /*|| cmtl.wasNotLocked()*/)
  414. {
  415. std::memset(outputs[0], 0, sizeof(float)*frames);
  416. std::memset(outputs[1], 0, sizeof(float)*frames);
  417. return;
  418. }
  419. for (uint32_t i=0, j=0; i<frames; ++i)
  420. {
  421. fAudioBufferIn[j++] = inputs[0][i];
  422. fAudioBufferIn[j++] = inputs[1][i];
  423. }
  424. std::memset(fAudioBufferOut, 0, sizeof(float)*frames*DISTRHO_PLUGIN_NUM_OUTPUTS);
  425. fCurrentDevice->processBuffer(fAudioBufferIn, DISTRHO_PLUGIN_NUM_INPUTS,
  426. fAudioBufferOut, DISTRHO_PLUGIN_NUM_OUTPUTS, frames);
  427. for (uint32_t i=0, j=0; i<frames; ++i)
  428. {
  429. outputs[0][i] = fAudioBufferOut[j++];
  430. outputs[1][i] = fAudioBufferOut[j++];
  431. }
  432. }
  433. // -------------------------------------------------------------------------------------------------------
  434. private:
  435. /**
  436. Set our plugin class as non-copyable and add a leak detector just in case.
  437. */
  438. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  439. };
  440. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  441. {
  442. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  443. }
  444. /* ------------------------------------------------------------------------------------------------------------
  445. * Plugin entry point, called by DPF to create a new plugin instance. */
  446. Plugin* createPlugin()
  447. {
  448. return new CardinalPlugin();
  449. }
  450. // -----------------------------------------------------------------------------------------------------------
  451. END_NAMESPACE_DISTRHO