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.

527 lines
16KB

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