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.

1067 lines
35KB

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