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.

959 lines
31KB

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