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.

876 lines
28KB

  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. #ifdef NDEBUG
  30. # undef DEBUG
  31. #endif
  32. #ifdef HAVE_LIBLO
  33. # include <lo/lo.h>
  34. # include "extra/Thread.hpp"
  35. #endif
  36. #include <list>
  37. #include "DistrhoPluginUtils.hpp"
  38. #include "PluginDriver.hpp"
  39. #include "WindowParameters.hpp"
  40. #include "extra/Base64.hpp"
  41. #include "extra/SharedResourcePointer.hpp"
  42. #define REMOTE_HOST_PORT "2228"
  43. namespace rack {
  44. namespace plugin {
  45. void initStaticPlugins();
  46. void destroyStaticPlugins();
  47. }
  48. #if defined(__MOD_DEVICES__) && !defined(HEADLESS)
  49. namespace window {
  50. void WindowInit(Window* window, DISTRHO_NAMESPACE::Plugin* plugin);
  51. }
  52. #endif
  53. }
  54. START_NAMESPACE_DISTRHO
  55. // -----------------------------------------------------------------------------------------------------------
  56. struct Initializer
  57. #ifdef HAVE_LIBLO
  58. : public Thread
  59. #endif
  60. {
  61. #ifdef HAVE_LIBLO
  62. lo_server oscServer = nullptr;
  63. CardinalBasePlugin* oscPlugin = nullptr;
  64. #endif
  65. std::string templatePath;
  66. Initializer(const CardinalBasePlugin* const plugin)
  67. {
  68. using namespace rack;
  69. settings::allowCursorLock = false;
  70. settings::autoCheckUpdates = false;
  71. settings::autosaveInterval = 0;
  72. settings::devMode = true;
  73. settings::discordUpdateActivity = false;
  74. settings::isPlugin = true;
  75. settings::skipLoadOnLaunch = true;
  76. settings::showTipsOnLaunch = false;
  77. #ifdef HEADLESS
  78. settings::headless = true;
  79. #endif
  80. #ifdef __MOD_DEVICES__
  81. settings::threadCount = 3;
  82. #else
  83. settings::threadCount = 1;
  84. #endif
  85. system::init();
  86. logger::init();
  87. random::init();
  88. ui::init();
  89. if (asset::systemDir.empty())
  90. {
  91. if (const char* const bundlePath = plugin->getBundlePath())
  92. {
  93. if (const char* const resourcePath = getResourcePath(bundlePath))
  94. {
  95. asset::bundlePath = system::join(resourcePath, "PluginManifests");
  96. asset::systemDir = resourcePath;
  97. templatePath = system::join(asset::systemDir, "template.vcv");
  98. }
  99. }
  100. if (asset::systemDir.empty())
  101. {
  102. #ifdef CARDINAL_PLUGIN_SOURCE_DIR
  103. // Make system dir point to source code location as fallback
  104. asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack";
  105. if (system::exists(system::join(asset::systemDir, "res")))
  106. {
  107. templatePath = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "template.vcv";
  108. }
  109. else
  110. // If source code dir does not exist use install target prefix as system dir
  111. #endif
  112. {
  113. asset::bundlePath = CARDINAL_PLUGIN_PREFIX "/share/Cardinal/PluginManifests";
  114. asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/Cardinal";
  115. templatePath = system::join(asset::systemDir, "template.vcv");
  116. }
  117. }
  118. asset::userDir = asset::systemDir;
  119. }
  120. // Log environment
  121. INFO("%s %s v%s", APP_NAME.c_str(), APP_EDITION.c_str(), APP_VERSION.c_str());
  122. INFO("%s", system::getOperatingSystemInfo().c_str());
  123. INFO("Binary filename: %s", getBinaryFilename());
  124. INFO("Bundle path: %s", plugin->getBundlePath());
  125. INFO("System directory: %s", asset::systemDir.c_str());
  126. INFO("User directory: %s", asset::userDir.c_str());
  127. INFO("Template patch: %s", templatePath.c_str());
  128. // Check existence of the system res/ directory
  129. if (! system::exists(asset::systemDir))
  130. {
  131. d_stderr2("System directory \"%s\" does not exist.\n"
  132. "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
  133. }
  134. INFO("Initializing audio driver");
  135. rack::audio::addDriver(0, new CardinalAudioDriver);
  136. INFO("Initializing midi driver");
  137. rack::midi::addDriver(0, new CardinalMidiDriver);
  138. INFO("Initializing plugins");
  139. plugin::initStaticPlugins();
  140. #ifdef HAVE_LIBLO
  141. INFO("Initializing OSC Remote control");
  142. oscServer = lo_server_new_with_proto(REMOTE_HOST_PORT, LO_UDP, osc_error_handler);
  143. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,);
  144. lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this);
  145. lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this);
  146. lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr);
  147. startThread();
  148. #else
  149. INFO("OSC Remote control is not enabled in this build");
  150. #endif
  151. }
  152. ~Initializer()
  153. {
  154. using namespace rack;
  155. #ifdef HAVE_LIBLO
  156. if (oscServer != nullptr)
  157. {
  158. stopThread(5000);
  159. lo_server_del_method(oscServer, nullptr, nullptr);
  160. lo_server_free(oscServer);
  161. oscServer = nullptr;
  162. }
  163. #endif
  164. INFO("Clearing asset paths");
  165. asset::bundlePath.clear();
  166. asset::systemDir.clear();
  167. asset::userDir.clear();
  168. INFO("Destroying plugins");
  169. plugin::destroyStaticPlugins();
  170. INFO("Destroying MIDI devices");
  171. midi::destroy();
  172. INFO("Destroying audio devices");
  173. audio::destroy();
  174. INFO("Destroying logger");
  175. logger::destroy();
  176. }
  177. #ifdef HAVE_LIBLO
  178. void run() override
  179. {
  180. INFO("OSC Thread Listening for remote commands");
  181. while (! shouldThreadExit())
  182. {
  183. d_msleep(200);
  184. while (lo_server_recv_noblock(oscServer, 0) != 0) {}
  185. }
  186. INFO("OSC Thread Closed");
  187. }
  188. static void osc_error_handler(int num, const char* msg, const char* path)
  189. {
  190. d_stderr("Cardinal OSC Error: code: %i, msg: \"%s\", path: \"%s\")", num, msg, path);
  191. }
  192. static int osc_fallback_handler(const char* const path, const char* const types, lo_arg**, int, lo_message, void*)
  193. {
  194. d_stderr("Cardinal OSC unhandled message \"%s\" with types \"%s\"", path, types);
  195. return 0;
  196. }
  197. static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self)
  198. {
  199. d_stdout("osc_hello_handler()");
  200. const lo_address source = lo_message_get_source(m);
  201. lo_send_from(source, static_cast<Initializer*>(self)->oscServer, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok");
  202. return 0;
  203. }
  204. static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  205. {
  206. d_stdout("osc_load_handler()");
  207. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  208. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  209. const int32_t size = argv[0]->blob.size;
  210. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  211. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  212. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  213. bool ok = false;
  214. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->oscPlugin)
  215. {
  216. CardinalPluginContext* const context = plugin->context;
  217. std::vector<uint8_t> data(size);
  218. std::memcpy(data.data(), blob, size);
  219. rack::contextSet(context);
  220. rack::system::removeRecursively(context->patch->autosavePath);
  221. rack::system::createDirectories(context->patch->autosavePath);
  222. try {
  223. rack::system::unarchiveToDirectory(data, context->patch->autosavePath);
  224. context->patch->loadAutosave();
  225. ok = true;
  226. }
  227. catch (rack::Exception& e) {
  228. WARN("%s", e.what());
  229. }
  230. rack::contextSet(nullptr);
  231. }
  232. const lo_address source = lo_message_get_source(m);
  233. lo_send_from(source, static_cast<Initializer*>(self)->oscServer,
  234. LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail");
  235. return 0;
  236. }
  237. #endif
  238. };
  239. // -----------------------------------------------------------------------------------------------------------
  240. struct ScopedContext {
  241. ScopedContext(const CardinalBasePlugin* const plugin)
  242. {
  243. rack::contextSet(plugin->context);
  244. }
  245. ~ScopedContext()
  246. {
  247. rack::contextSet(nullptr);
  248. }
  249. };
  250. // -----------------------------------------------------------------------------------------------------------
  251. class CardinalPlugin : public CardinalBasePlugin
  252. {
  253. SharedResourcePointer<Initializer> fInitializer;
  254. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  255. float* fAudioBufferIn;
  256. #endif
  257. float* fAudioBufferOut;
  258. std::string fAutosavePath;
  259. String fWindowSize;
  260. // for base/context handling
  261. bool fIsActive;
  262. CardinalAudioDevice* fCurrentAudioDevice;
  263. CardinalMidiOutputDevice* fCurrentMidiOutput;
  264. std::list<CardinalMidiInputDevice*> fMidiInputs;
  265. Mutex fDeviceMutex;
  266. // real values, not VCV interpreted ones
  267. float fWindowParameters[kWindowParameterCount];
  268. public:
  269. CardinalPlugin()
  270. : CardinalBasePlugin(kModuleParameters + kWindowParameterCount, 0, 2),
  271. fInitializer(this),
  272. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  273. fAudioBufferIn(nullptr),
  274. #endif
  275. fAudioBufferOut(nullptr),
  276. fIsActive(false),
  277. fCurrentAudioDevice(nullptr),
  278. fCurrentMidiOutput(nullptr)
  279. {
  280. fWindowParameters[kWindowParameterShowTooltips] = 1.0f;
  281. fWindowParameters[kWindowParameterCableOpacity] = 50.0f;
  282. fWindowParameters[kWindowParameterCableTension] = 50.0f;
  283. fWindowParameters[kWindowParameterRackBrightness] = 100.0f;
  284. fWindowParameters[kWindowParameterHaloBrightness] = 25.0f;
  285. fWindowParameters[kWindowParameterKnobMode] = 0.0f;
  286. fWindowParameters[kWindowParameterWheelKnobControl] = 0.0f;
  287. fWindowParameters[kWindowParameterWheelSensitivity] = 1.0f;
  288. fWindowParameters[kWindowParameterLockModulePositions] = 0.0f;
  289. // create unique temporary path for this instance
  290. try {
  291. char uidBuf[24];
  292. const std::string tmp = rack::system::getTempDirectory();
  293. for (int i=1;; ++i)
  294. {
  295. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  296. const std::string trypath = rack::system::join(tmp, uidBuf);
  297. if (! rack::system::exists(trypath))
  298. {
  299. if (rack::system::createDirectories(trypath))
  300. fAutosavePath = trypath;
  301. break;
  302. }
  303. }
  304. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  305. const ScopedContext sc(this);
  306. context->engine = new rack::engine::Engine;
  307. context->history = new rack::history::State;
  308. context->patch = new rack::patch::Manager;
  309. context->patch->autosavePath = fAutosavePath;
  310. context->patch->templatePath = fInitializer->templatePath;
  311. context->event = new rack::widget::EventState;
  312. context->scene = new rack::app::Scene;
  313. context->event->rootWidget = context->scene;
  314. context->patch->loadTemplate();
  315. context->engine->startFallbackThread();
  316. #ifdef HAVE_LIBLO
  317. fInitializer->oscPlugin = this;
  318. #endif
  319. #if defined(__MOD_DEVICES__) && !defined(HEADLESS)
  320. context->window = new rack::window::Window;
  321. rack::window::WindowInit(context->window, this);
  322. /*
  323. context->scene->removeChild(context->scene->menuBar);
  324. context->scene->menuBar = rack::app::createMenuBar(getWindow(), getApp().isStandalone());
  325. context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll);
  326. */
  327. #endif
  328. }
  329. ~CardinalPlugin() override
  330. {
  331. #ifdef HAVE_LIBLO
  332. fInitializer->oscPlugin = nullptr;
  333. #endif
  334. rack::contextSet(context);
  335. #if defined(__MOD_DEVICES__) && !defined(HEADLESS)
  336. delete context->window;
  337. context->window = nullptr;
  338. #endif
  339. delete context;
  340. rack::contextSet(nullptr);
  341. if (! fAutosavePath.empty())
  342. rack::system::removeRecursively(fAutosavePath);
  343. }
  344. CardinalPluginContext* getRackContext() const noexcept
  345. {
  346. return context;
  347. }
  348. protected:
  349. /* --------------------------------------------------------------------------------------------------------
  350. * Cardinal Base things */
  351. bool isActive() const noexcept override
  352. {
  353. return fIsActive;
  354. }
  355. bool canAssignAudioDevice() const noexcept override
  356. {
  357. const MutexLocker cml(fDeviceMutex);
  358. return fCurrentAudioDevice == nullptr;
  359. }
  360. bool canAssignMidiOutputDevice() const noexcept override
  361. {
  362. const MutexLocker cml(fDeviceMutex);
  363. return fCurrentMidiOutput == nullptr;
  364. }
  365. void assignAudioDevice(CardinalAudioDevice* const dev) noexcept override
  366. {
  367. DISTRHO_SAFE_ASSERT_RETURN(fCurrentAudioDevice == nullptr,);
  368. const MutexLocker cml(fDeviceMutex);
  369. fCurrentAudioDevice = dev;
  370. }
  371. void assignMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override
  372. {
  373. DISTRHO_SAFE_ASSERT_RETURN(fCurrentMidiOutput == nullptr,);
  374. const MutexLocker cml(fDeviceMutex);
  375. fCurrentMidiOutput = dev;
  376. }
  377. bool clearAudioDevice(CardinalAudioDevice* const dev) noexcept override
  378. {
  379. const MutexLocker cml(fDeviceMutex);
  380. if (fCurrentAudioDevice != dev)
  381. return false;
  382. fCurrentAudioDevice = nullptr;
  383. return true;
  384. }
  385. bool clearMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override
  386. {
  387. const MutexLocker cml(fDeviceMutex);
  388. if (fCurrentMidiOutput != dev)
  389. return false;
  390. fCurrentMidiOutput = nullptr;
  391. return true;
  392. }
  393. void addMidiInput(CardinalMidiInputDevice* const dev) override
  394. {
  395. // NOTE this will xrun
  396. const MutexLocker cml(fDeviceMutex);
  397. fMidiInputs.push_back(dev);
  398. }
  399. void removeMidiInput(CardinalMidiInputDevice* const dev) override
  400. {
  401. // NOTE this will xrun
  402. const MutexLocker cml(fDeviceMutex);
  403. fMidiInputs.remove(dev);
  404. }
  405. /* --------------------------------------------------------------------------------------------------------
  406. * Information */
  407. const char* getLabel() const override
  408. {
  409. #if DISTRHO_PLUGIN_IS_SYNTH
  410. return "CardinalSynth";
  411. #else
  412. return "Cardinal";
  413. #endif
  414. }
  415. const char* getDescription() const override
  416. {
  417. return ""
  418. "Cardinal is an open-source self-contained special plugin version of VCVRack, using DPF.\n"
  419. "It is NOT an official VCV project, and it is not affiliated with it in any way.\n";
  420. }
  421. const char* getMaker() const override
  422. {
  423. return "DISTRHO";
  424. }
  425. const char* getHomePage() const override
  426. {
  427. return "https://github.com/DISTRHO/Cardinal";
  428. }
  429. const char* getLicense() const override
  430. {
  431. return "GPLv3+";
  432. }
  433. uint32_t getVersion() const override
  434. {
  435. return d_version(2, 0, 0);
  436. }
  437. int64_t getUniqueId() const override
  438. {
  439. #if DISTRHO_PLUGIN_IS_SYNTH
  440. return d_cconst('d', 'C', 'n', 'S');
  441. #else
  442. return d_cconst('d', 'C', 'd', 'n');
  443. #endif
  444. }
  445. /* --------------------------------------------------------------------------------------------------------
  446. * Init */
  447. void initParameter(const uint32_t index, Parameter& parameter) override
  448. {
  449. if (index < kModuleParameters)
  450. {
  451. parameter.name = "Parameter ";
  452. parameter.name += String(index + 1);
  453. parameter.symbol = "param_";
  454. parameter.symbol += String(index + 1);
  455. parameter.unit = "v";
  456. parameter.hints = kParameterIsAutomable;
  457. parameter.ranges.def = 0.0f;
  458. parameter.ranges.min = 0.0f;
  459. parameter.ranges.max = 10.0f;
  460. return;
  461. }
  462. switch (index - kModuleParameters)
  463. {
  464. case kWindowParameterShowTooltips:
  465. parameter.name = "Show tooltips";
  466. parameter.symbol = "tooltips";
  467. parameter.hints = kParameterIsAutomable|kParameterIsInteger|kParameterIsBoolean;
  468. parameter.ranges.def = 1.0f;
  469. parameter.ranges.min = 0.0f;
  470. parameter.ranges.max = 1.0f;
  471. break;
  472. case kWindowParameterCableOpacity:
  473. parameter.name = "Cable opacity";
  474. parameter.symbol = "cableOpacity";
  475. parameter.unit = "%";
  476. parameter.hints = kParameterIsAutomable;
  477. parameter.ranges.def = 50.0f;
  478. parameter.ranges.min = 0.0f;
  479. parameter.ranges.max = 100.0f;
  480. break;
  481. case kWindowParameterCableTension:
  482. parameter.name = "Cable tension";
  483. parameter.symbol = "cableTension";
  484. parameter.unit = "%";
  485. parameter.hints = kParameterIsAutomable;
  486. parameter.ranges.def = 50.0f;
  487. parameter.ranges.min = 0.0f;
  488. parameter.ranges.max = 100.0f;
  489. break;
  490. case kWindowParameterRackBrightness:
  491. parameter.name = "Room brightness";
  492. parameter.symbol = "rackBrightness";
  493. parameter.unit = "%";
  494. parameter.hints = kParameterIsAutomable;
  495. parameter.ranges.def = 100.0f;
  496. parameter.ranges.min = 0.0f;
  497. parameter.ranges.max = 100.0f;
  498. break;
  499. case kWindowParameterHaloBrightness:
  500. parameter.name = "Light Bloom";
  501. parameter.symbol = "haloBrightness";
  502. parameter.unit = "%";
  503. parameter.hints = kParameterIsAutomable;
  504. parameter.ranges.def = 25.0f;
  505. parameter.ranges.min = 0.0f;
  506. parameter.ranges.max = 100.0f;
  507. break;
  508. case kWindowParameterKnobMode:
  509. parameter.name = "Knob mode";
  510. parameter.symbol = "knobMode";
  511. parameter.hints = kParameterIsAutomable|kParameterIsInteger;
  512. parameter.ranges.def = 0.0f;
  513. parameter.ranges.min = 0.0f;
  514. parameter.ranges.max = 2.0f;
  515. parameter.enumValues.count = 3;
  516. parameter.enumValues.restrictedMode = true;
  517. parameter.enumValues.values = new ParameterEnumerationValue[3];
  518. parameter.enumValues.values[0].label = "Linear";
  519. parameter.enumValues.values[0].value = 0.0f;
  520. parameter.enumValues.values[1].label = "Absolute rotary";
  521. parameter.enumValues.values[1].value = 1.0f;
  522. parameter.enumValues.values[2].label = "Relative rotary";
  523. parameter.enumValues.values[2].value = 2.0f;
  524. break;
  525. case kWindowParameterWheelKnobControl:
  526. parameter.name = "Scroll wheel knob control";
  527. parameter.symbol = "knobScroll";
  528. parameter.hints = kParameterIsAutomable|kParameterIsInteger|kParameterIsBoolean;
  529. parameter.ranges.def = 0.0f;
  530. parameter.ranges.min = 0.0f;
  531. parameter.ranges.max = 1.0f;
  532. break;
  533. case kWindowParameterWheelSensitivity:
  534. parameter.name = "Scroll wheel knob sensitivity";
  535. parameter.symbol = "knobScrollSensitivity";
  536. parameter.hints = kParameterIsAutomable|kParameterIsLogarithmic;
  537. parameter.ranges.def = 1.0f;
  538. parameter.ranges.min = 0.1f;
  539. parameter.ranges.max = 10.0f;
  540. break;
  541. case kWindowParameterLockModulePositions:
  542. parameter.name = "Lock module positions";
  543. parameter.symbol = "lockModules";
  544. parameter.hints = kParameterIsAutomable|kParameterIsInteger|kParameterIsBoolean;
  545. parameter.ranges.def = 0.0f;
  546. parameter.ranges.min = 0.0f;
  547. parameter.ranges.max = 1.0f;
  548. break;
  549. }
  550. }
  551. void initState(const uint32_t index, String& stateKey, String& defaultStateValue) override
  552. {
  553. defaultStateValue = "";
  554. switch (index)
  555. {
  556. case 0:
  557. stateKey = "patch";
  558. break;
  559. case 1:
  560. stateKey = "windowSize";
  561. break;
  562. }
  563. }
  564. /* --------------------------------------------------------------------------------------------------------
  565. * Internal data */
  566. float getParameterValue(uint32_t index) const override
  567. {
  568. if (index < kModuleParameters)
  569. return context->parameters[index];
  570. index -= kModuleParameters;
  571. if (index < kWindowParameterCount)
  572. return fWindowParameters[index];
  573. return 0.0f;
  574. }
  575. void setParameterValue(uint32_t index, float value) override
  576. {
  577. if (index < kModuleParameters)
  578. {
  579. context->parameters[index] = value;
  580. return;
  581. }
  582. index -= kModuleParameters;
  583. if (index < kWindowParameterCount)
  584. {
  585. fWindowParameters[index] = value;
  586. return;
  587. }
  588. }
  589. String getState(const char* const key) const override
  590. {
  591. if (std::strcmp(key, "windowSize") == 0)
  592. return fWindowSize;
  593. if (std::strcmp(key, "patch") != 0)
  594. return String();
  595. if (fAutosavePath.empty())
  596. return String();
  597. std::vector<uint8_t> data;
  598. {
  599. const ScopedContext sc(this);
  600. context->engine->prepareSave();
  601. context->patch->saveAutosave();
  602. context->patch->cleanAutosave();
  603. try {
  604. data = rack::system::archiveDirectory(fAutosavePath, 1);
  605. } DISTRHO_SAFE_EXCEPTION_RETURN("getState archiveDirectory", String());
  606. }
  607. return String::asBase64(data.data(), data.size());
  608. }
  609. void setState(const char* const key, const char* const value) override
  610. {
  611. if (std::strcmp(key, "windowSize") == 0)
  612. {
  613. fWindowSize = value;
  614. return;
  615. }
  616. if (std::strcmp(key, "patch") != 0)
  617. return;
  618. if (fAutosavePath.empty())
  619. return;
  620. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  621. const ScopedContext sc(this);
  622. rack::system::removeRecursively(fAutosavePath);
  623. rack::system::createDirectories(fAutosavePath);
  624. rack::system::unarchiveToDirectory(data, fAutosavePath);
  625. context->patch->loadAutosave();
  626. }
  627. /* --------------------------------------------------------------------------------------------------------
  628. * Process */
  629. void activate() override
  630. {
  631. const uint32_t bufferSize = getBufferSize() * DISTRHO_PLUGIN_NUM_OUTPUTS;
  632. fAudioBufferOut = new float[bufferSize];
  633. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  634. fAudioBufferIn = new float[bufferSize];
  635. std::memset(fAudioBufferIn, 0, sizeof(float)*bufferSize);
  636. #endif
  637. {
  638. const MutexLocker cml(fDeviceMutex);
  639. if (fCurrentAudioDevice != nullptr)
  640. fCurrentAudioDevice->onStartStream();
  641. }
  642. }
  643. void deactivate() override
  644. {
  645. {
  646. const MutexLocker cml(fDeviceMutex);
  647. if (fCurrentAudioDevice != nullptr)
  648. fCurrentAudioDevice->onStopStream();
  649. }
  650. delete[] fAudioBufferOut;
  651. fAudioBufferOut = nullptr;
  652. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  653. delete[] fAudioBufferIn;
  654. fAudioBufferIn = nullptr;
  655. #endif
  656. }
  657. void run(const float** const inputs, float** const outputs, const uint32_t frames,
  658. const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  659. {
  660. /*
  661. context->engine->setFrame(getTimePosition().frame);
  662. context->engine->stepBlock(frames);
  663. */
  664. const MutexLocker cml(fDeviceMutex);
  665. if (fCurrentAudioDevice != nullptr)
  666. {
  667. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  668. for (uint32_t i=0, j=0; i<frames; ++i)
  669. {
  670. fAudioBufferIn[j++] = inputs[0][i];
  671. fAudioBufferIn[j++] = inputs[1][i];
  672. }
  673. #endif
  674. }
  675. else
  676. {
  677. std::memset(outputs[0], 0, sizeof(float)*frames);
  678. std::memset(outputs[1], 0, sizeof(float)*frames);
  679. }
  680. {
  681. const TimePosition& timePos(getTimePosition());
  682. context->playing = timePos.playing;
  683. context->frameZero = timePos.frame == 0;
  684. if (timePos.bbt.valid)
  685. {
  686. context->bar = timePos.bbt.bar;
  687. context->beat = timePos.bbt.beat;
  688. context->beatsPerBar = timePos.bbt.beatsPerBar;
  689. context->tick = timePos.bbt.tick;
  690. context->ticksPerBeat = timePos.bbt.ticksPerBeat;
  691. context->ticksPerFrame = 1.0 / (60.0 * getSampleRate() / timePos.bbt.beatsPerMinute / timePos.bbt.ticksPerBeat);
  692. }
  693. }
  694. std::memset(fAudioBufferOut, 0, sizeof(float)*frames*DISTRHO_PLUGIN_NUM_OUTPUTS);
  695. for (CardinalMidiInputDevice* dev : fMidiInputs)
  696. dev->handleMessagesFromHost(midiEvents, midiEventCount);
  697. if (fCurrentAudioDevice != nullptr)
  698. {
  699. #if DISTRHO_PLUGIN_NUM_INPUTS == 0
  700. const float* const insPtr = nullptr;
  701. #else
  702. const float* const insPtr = fAudioBufferIn;
  703. #endif
  704. fCurrentAudioDevice->processBuffer(insPtr, DISTRHO_PLUGIN_NUM_INPUTS,
  705. fAudioBufferOut, DISTRHO_PLUGIN_NUM_OUTPUTS, frames);
  706. }
  707. if (fCurrentMidiOutput != nullptr)
  708. fCurrentMidiOutput->processMessages();
  709. for (uint32_t i=0, j=0; i<frames; ++i)
  710. {
  711. outputs[0][i] = fAudioBufferOut[j++];
  712. outputs[1][i] = fAudioBufferOut[j++];
  713. }
  714. }
  715. // -------------------------------------------------------------------------------------------------------
  716. private:
  717. /**
  718. Set our plugin class as non-copyable and add a leak detector just in case.
  719. */
  720. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  721. };
  722. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  723. {
  724. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  725. }
  726. /* ------------------------------------------------------------------------------------------------------------
  727. * Plugin entry point, called by DPF to create a new plugin instance. */
  728. Plugin* createPlugin()
  729. {
  730. return new CardinalPlugin();
  731. }
  732. // -----------------------------------------------------------------------------------------------------------
  733. END_NAMESPACE_DISTRHO