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.

947 lines
30KB

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