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.

1015 lines
34KB

  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. # include <lo/lo.h>
  35. # include "extra/Thread.hpp"
  36. #endif
  37. #include <list>
  38. #include "DistrhoPluginUtils.hpp"
  39. #include "PluginDriver.hpp"
  40. #include "extra/Base64.hpp"
  41. #include "extra/SharedResourcePointer.hpp"
  42. #ifndef HEADLESS
  43. # include "WindowParameters.hpp"
  44. static const constexpr uint kCardinalStateCount = 2; // patch, windowSize
  45. #else
  46. # define kWindowParameterCount 0
  47. static const constexpr uint kCardinalStateCount = 1; // patch
  48. #endif
  49. #define REMOTE_HOST_PORT "2228"
  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. #ifdef HAVE_LIBLO
  65. : public Thread
  66. #endif
  67. {
  68. #ifdef HAVE_LIBLO
  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 midi driver");
  163. midi::addDriver(0, new CardinalMidiDriver);
  164. INFO("Initializing plugins");
  165. plugin::initStaticPlugins();
  166. INFO("Initializing plugin browser DB");
  167. app::browserInit();
  168. #ifdef HAVE_LIBLO
  169. INFO("Initializing OSC Remote control");
  170. oscServer = lo_server_new_with_proto(REMOTE_HOST_PORT, LO_UDP, osc_error_handler);
  171. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,);
  172. lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this);
  173. lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this);
  174. lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr);
  175. startThread();
  176. #else
  177. INFO("OSC Remote control is not enabled in this build");
  178. #endif
  179. }
  180. ~Initializer()
  181. {
  182. using namespace rack;
  183. #ifdef HAVE_LIBLO
  184. if (oscServer != nullptr)
  185. {
  186. stopThread(5000);
  187. lo_server_del_method(oscServer, nullptr, nullptr);
  188. lo_server_free(oscServer);
  189. oscServer = nullptr;
  190. }
  191. #endif
  192. INFO("Clearing asset paths");
  193. asset::bundlePath.clear();
  194. asset::systemDir.clear();
  195. asset::userDir.clear();
  196. INFO("Destroying plugins");
  197. plugin::destroyStaticPlugins();
  198. INFO("Destroying MIDI devices");
  199. midi::destroy();
  200. INFO("Destroying audio devices");
  201. audio::destroy();
  202. INFO("Destroying logger");
  203. logger::destroy();
  204. }
  205. #ifdef HAVE_LIBLO
  206. void run() override
  207. {
  208. INFO("OSC Thread Listening for remote commands");
  209. while (! shouldThreadExit())
  210. {
  211. d_msleep(200);
  212. while (lo_server_recv_noblock(oscServer, 0) != 0) {}
  213. }
  214. INFO("OSC Thread Closed");
  215. }
  216. static void osc_error_handler(int num, const char* msg, const char* path)
  217. {
  218. d_stderr("Cardinal OSC Error: code: %i, msg: \"%s\", path: \"%s\")", num, msg, path);
  219. }
  220. static int osc_fallback_handler(const char* const path, const char* const types, lo_arg**, int, lo_message, void*)
  221. {
  222. d_stderr("Cardinal OSC unhandled message \"%s\" with types \"%s\"", path, types);
  223. return 0;
  224. }
  225. static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self)
  226. {
  227. d_stdout("osc_hello_handler()");
  228. const lo_address source = lo_message_get_source(m);
  229. lo_send_from(source, static_cast<Initializer*>(self)->oscServer, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok");
  230. return 0;
  231. }
  232. static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  233. {
  234. d_stdout("osc_load_handler()");
  235. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  236. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  237. const int32_t size = argv[0]->blob.size;
  238. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  239. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  240. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  241. bool ok = false;
  242. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->oscPlugin)
  243. {
  244. CardinalPluginContext* const context = plugin->context;
  245. std::vector<uint8_t> data(size);
  246. std::memcpy(data.data(), blob, size);
  247. rack::contextSet(context);
  248. rack::system::removeRecursively(context->patch->autosavePath);
  249. rack::system::createDirectories(context->patch->autosavePath);
  250. try {
  251. rack::system::unarchiveToDirectory(data, context->patch->autosavePath);
  252. context->patch->loadAutosave();
  253. ok = true;
  254. }
  255. catch (rack::Exception& e) {
  256. WARN("%s", e.what());
  257. }
  258. rack::contextSet(nullptr);
  259. }
  260. const lo_address source = lo_message_get_source(m);
  261. lo_send_from(source, static_cast<Initializer*>(self)->oscServer,
  262. LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail");
  263. return 0;
  264. }
  265. #endif
  266. };
  267. // -----------------------------------------------------------------------------------------------------------
  268. struct ScopedContext {
  269. ScopedContext(const CardinalBasePlugin* const plugin)
  270. {
  271. rack::contextSet(plugin->context);
  272. }
  273. ~ScopedContext()
  274. {
  275. rack::contextSet(nullptr);
  276. }
  277. };
  278. // -----------------------------------------------------------------------------------------------------------
  279. class CardinalPlugin : public CardinalBasePlugin
  280. {
  281. SharedResourcePointer<Initializer> fInitializer;
  282. float** fAudioBufferCopy;
  283. std::string fAutosavePath;
  284. String fWindowSize;
  285. // for base/context handling
  286. CardinalMidiInputDevice** fCurrentMidiInputs;
  287. CardinalMidiOutputDevice** fCurrentMidiOutputs;
  288. uint64_t fPreviousFrame;
  289. Mutex fDeviceMutex;
  290. #ifndef HEADLESS
  291. // real values, not VCV interpreted ones
  292. float fWindowParameters[kWindowParameterCount];
  293. #endif
  294. public:
  295. CardinalPlugin()
  296. : CardinalBasePlugin(kModuleParameters + kWindowParameterCount, 0, kCardinalStateCount),
  297. fInitializer(this),
  298. fAudioBufferCopy(nullptr),
  299. fCurrentMidiInputs(nullptr),
  300. fCurrentMidiOutputs(nullptr),
  301. fPreviousFrame(0)
  302. {
  303. #ifndef HEADLESS
  304. fWindowParameters[kWindowParameterShowTooltips] = 1.0f;
  305. fWindowParameters[kWindowParameterCableOpacity] = 50.0f;
  306. fWindowParameters[kWindowParameterCableTension] = 75.0f;
  307. fWindowParameters[kWindowParameterRackBrightness] = 100.0f;
  308. fWindowParameters[kWindowParameterHaloBrightness] = 25.0f;
  309. fWindowParameters[kWindowParameterKnobMode] = 0.0f;
  310. fWindowParameters[kWindowParameterWheelKnobControl] = 0.0f;
  311. fWindowParameters[kWindowParameterWheelSensitivity] = 1.0f;
  312. fWindowParameters[kWindowParameterLockModulePositions] = 0.0f;
  313. fWindowParameters[kWindowParameterUpdateRateLimit] = 0.0f;
  314. #endif
  315. // create unique temporary path for this instance
  316. try {
  317. char uidBuf[24];
  318. const std::string tmp = rack::system::getTempDirectory();
  319. for (int i=1;; ++i)
  320. {
  321. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  322. const std::string trypath = rack::system::join(tmp, uidBuf);
  323. if (! rack::system::exists(trypath))
  324. {
  325. if (rack::system::createDirectories(trypath))
  326. fAutosavePath = trypath;
  327. break;
  328. }
  329. }
  330. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  331. const float sampleRate = getSampleRate();
  332. rack::settings::sampleRate = sampleRate;
  333. context->bufferSize = getBufferSize();
  334. context->sampleRate = sampleRate;
  335. const ScopedContext sc(this);
  336. context->engine = new rack::engine::Engine;
  337. context->engine->setSampleRate(sampleRate);
  338. context->history = new rack::history::State;
  339. context->patch = new rack::patch::Manager;
  340. context->patch->autosavePath = fAutosavePath;
  341. context->patch->templatePath = fInitializer->templatePath;
  342. context->event = new rack::widget::EventState;
  343. context->scene = new rack::app::Scene;
  344. context->event->rootWidget = context->scene;
  345. if (! isDummyInstance())
  346. context->window = new rack::window::Window;
  347. context->patch->loadTemplate();
  348. context->scene->rackScroll->reset();
  349. #ifdef HAVE_LIBLO
  350. fInitializer->oscPlugin = this;
  351. #endif
  352. }
  353. ~CardinalPlugin() override
  354. {
  355. #ifdef HAVE_LIBLO
  356. fInitializer->oscPlugin = nullptr;
  357. #endif
  358. {
  359. const ScopedContext sc(this);
  360. context->patch->clear();
  361. delete context;
  362. }
  363. {
  364. const MutexLocker cml(fDeviceMutex);
  365. delete[] fCurrentMidiInputs;
  366. fCurrentMidiInputs = nullptr;
  367. delete[] fCurrentMidiOutputs;
  368. fCurrentMidiOutputs = nullptr;
  369. }
  370. if (! fAutosavePath.empty())
  371. rack::system::removeRecursively(fAutosavePath);
  372. }
  373. CardinalPluginContext* getRackContext() const noexcept
  374. {
  375. return context;
  376. }
  377. protected:
  378. /* --------------------------------------------------------------------------------------------------------
  379. * Cardinal Base things */
  380. void assignMidiInputDevice(CardinalMidiInputDevice* const dev) noexcept override
  381. {
  382. CardinalMidiInputDevice** const oldDevs = fCurrentMidiInputs;
  383. uint numDevs = 0;
  384. if (oldDevs != nullptr)
  385. {
  386. while (oldDevs[numDevs] != nullptr)
  387. ++numDevs;
  388. }
  389. CardinalMidiInputDevice** const newDevs = new CardinalMidiInputDevice*[numDevs + 2];
  390. for (uint i=0; i<numDevs; ++i)
  391. newDevs[i] = oldDevs[i];
  392. newDevs[numDevs+0] = dev;
  393. newDevs[numDevs+1] = nullptr;
  394. {
  395. const MutexLocker cml(fDeviceMutex);
  396. fCurrentMidiInputs = newDevs;
  397. }
  398. delete[] oldDevs;
  399. }
  400. void assignMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override
  401. {
  402. CardinalMidiOutputDevice** const oldDevs = fCurrentMidiOutputs;
  403. uint numDevs = 0;
  404. if (oldDevs != nullptr)
  405. {
  406. while (oldDevs[numDevs] != nullptr)
  407. ++numDevs;
  408. }
  409. CardinalMidiOutputDevice** const newDevs = new CardinalMidiOutputDevice*[numDevs + 2];
  410. for (uint i=0; i<numDevs; ++i)
  411. newDevs[i] = oldDevs[i];
  412. newDevs[numDevs+0] = dev;
  413. newDevs[numDevs+1] = nullptr;
  414. {
  415. const MutexLocker cml(fDeviceMutex);
  416. fCurrentMidiOutputs = newDevs;
  417. }
  418. delete[] oldDevs;
  419. }
  420. void clearMidiInputDevice(CardinalMidiInputDevice* const dev) noexcept override
  421. {
  422. const MutexLocker cml(fDeviceMutex);
  423. CardinalMidiInputDevice** const inputs = fCurrentMidiInputs;
  424. DISTRHO_SAFE_ASSERT_RETURN(inputs != nullptr,);
  425. for (uint i=0; inputs[i] != nullptr; ++i)
  426. {
  427. CardinalMidiInputDevice* const input = inputs[i];
  428. if (input != dev)
  429. continue;
  430. for (; inputs[i+1] != nullptr; ++i)
  431. inputs[i] = inputs[i+1];
  432. inputs[i] = nullptr;
  433. break;
  434. }
  435. }
  436. void clearMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override
  437. {
  438. const MutexLocker cml(fDeviceMutex);
  439. CardinalMidiOutputDevice** const outputs = fCurrentMidiOutputs;
  440. DISTRHO_SAFE_ASSERT_RETURN(outputs != nullptr,);
  441. for (uint i=0; outputs[i] != nullptr; ++i)
  442. {
  443. CardinalMidiOutputDevice* const output = outputs[i];
  444. if (output != dev)
  445. continue;
  446. for (; outputs[i+1] != nullptr; ++i)
  447. outputs[i] = outputs[i+1];
  448. outputs[i] = nullptr;
  449. break;
  450. }
  451. }
  452. /* --------------------------------------------------------------------------------------------------------
  453. * Information */
  454. const char* getLabel() const override
  455. {
  456. return DISTRHO_PLUGIN_LABEL;
  457. }
  458. const char* getDescription() const override
  459. {
  460. return ""
  461. "Cardinal is an open-source plugin wrapper around VCV Rack.\n"
  462. "It is NOT an official VCV project, and it is not affiliated with it in any way.\n"
  463. "\n"
  464. "Cardinal contains Rack, some 3rd-party modules and a few internal utilities all in a single binary.\n"
  465. "It does not load external modules and does not connect to the official Rack library/store.\n";
  466. }
  467. const char* getMaker() const override
  468. {
  469. return "DISTRHO";
  470. }
  471. const char* getHomePage() const override
  472. {
  473. return "https://github.com/DISTRHO/Cardinal";
  474. }
  475. const char* getLicense() const override
  476. {
  477. return "GPLv3+";
  478. }
  479. uint32_t getVersion() const override
  480. {
  481. return d_version(2, 0, 0);
  482. }
  483. int64_t getUniqueId() const override
  484. {
  485. #if CARDINAL_VARIANT_MAIN
  486. return d_cconst('d', 'C', 'd', 'n');
  487. #elif CARDINAL_VARIANT_FX
  488. return d_cconst('d', 'C', 'n', 'F');
  489. #elif CARDINAL_VARIANT_SYNTH
  490. return d_cconst('d', 'C', 'n', 'S');
  491. #else
  492. #error cardinal variant not set
  493. #endif
  494. }
  495. /* --------------------------------------------------------------------------------------------------------
  496. * Init */
  497. void initAudioPort(const bool input, uint32_t index, AudioPort& port) override
  498. {
  499. if (index >= 8)
  500. {
  501. port.hints = kAudioPortIsCV | kCVPortHasPositiveUnipolarRange | kCVPortHasScaledRange;
  502. index -= 8;
  503. }
  504. CardinalBasePlugin::initAudioPort(input, index, port);
  505. }
  506. void initParameter(const uint32_t index, Parameter& parameter) override
  507. {
  508. if (index < kModuleParameters)
  509. {
  510. parameter.name = "Parameter ";
  511. parameter.name += String(index + 1);
  512. parameter.symbol = "param_";
  513. parameter.symbol += String(index + 1);
  514. parameter.unit = "v";
  515. parameter.hints = kParameterIsAutomatable;
  516. parameter.ranges.def = 0.0f;
  517. parameter.ranges.min = 0.0f;
  518. parameter.ranges.max = 10.0f;
  519. return;
  520. }
  521. #ifndef HEADLESS
  522. switch (index - kModuleParameters)
  523. {
  524. case kWindowParameterShowTooltips:
  525. parameter.name = "Show tooltips";
  526. parameter.symbol = "tooltips";
  527. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  528. parameter.ranges.def = 1.0f;
  529. parameter.ranges.min = 0.0f;
  530. parameter.ranges.max = 1.0f;
  531. break;
  532. case kWindowParameterCableOpacity:
  533. parameter.name = "Cable opacity";
  534. parameter.symbol = "cableOpacity";
  535. parameter.unit = "%";
  536. parameter.hints = kParameterIsAutomatable;
  537. parameter.ranges.def = 50.0f;
  538. parameter.ranges.min = 0.0f;
  539. parameter.ranges.max = 100.0f;
  540. break;
  541. case kWindowParameterCableTension:
  542. parameter.name = "Cable tension";
  543. parameter.symbol = "cableTension";
  544. parameter.unit = "%";
  545. parameter.hints = kParameterIsAutomatable;
  546. parameter.ranges.def = 75.0f;
  547. parameter.ranges.min = 0.0f;
  548. parameter.ranges.max = 100.0f;
  549. break;
  550. case kWindowParameterRackBrightness:
  551. parameter.name = "Room brightness";
  552. parameter.symbol = "rackBrightness";
  553. parameter.unit = "%";
  554. parameter.hints = kParameterIsAutomatable;
  555. parameter.ranges.def = 100.0f;
  556. parameter.ranges.min = 0.0f;
  557. parameter.ranges.max = 100.0f;
  558. break;
  559. case kWindowParameterHaloBrightness:
  560. parameter.name = "Light Bloom";
  561. parameter.symbol = "haloBrightness";
  562. parameter.unit = "%";
  563. parameter.hints = kParameterIsAutomatable;
  564. parameter.ranges.def = 25.0f;
  565. parameter.ranges.min = 0.0f;
  566. parameter.ranges.max = 100.0f;
  567. break;
  568. case kWindowParameterKnobMode:
  569. parameter.name = "Knob mode";
  570. parameter.symbol = "knobMode";
  571. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  572. parameter.ranges.def = 0.0f;
  573. parameter.ranges.min = 0.0f;
  574. parameter.ranges.max = 2.0f;
  575. parameter.enumValues.count = 3;
  576. parameter.enumValues.restrictedMode = true;
  577. parameter.enumValues.values = new ParameterEnumerationValue[3];
  578. parameter.enumValues.values[0].label = "Linear";
  579. parameter.enumValues.values[0].value = 0.0f;
  580. parameter.enumValues.values[1].label = "Absolute rotary";
  581. parameter.enumValues.values[1].value = 1.0f;
  582. parameter.enumValues.values[2].label = "Relative rotary";
  583. parameter.enumValues.values[2].value = 2.0f;
  584. break;
  585. case kWindowParameterWheelKnobControl:
  586. parameter.name = "Scroll wheel knob control";
  587. parameter.symbol = "knobScroll";
  588. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  589. parameter.ranges.def = 0.0f;
  590. parameter.ranges.min = 0.0f;
  591. parameter.ranges.max = 1.0f;
  592. break;
  593. case kWindowParameterWheelSensitivity:
  594. parameter.name = "Scroll wheel knob sensitivity";
  595. parameter.symbol = "knobScrollSensitivity";
  596. parameter.hints = kParameterIsAutomatable|kParameterIsLogarithmic;
  597. parameter.ranges.def = 1.0f;
  598. parameter.ranges.min = 0.1f;
  599. parameter.ranges.max = 10.0f;
  600. break;
  601. case kWindowParameterLockModulePositions:
  602. parameter.name = "Lock module positions";
  603. parameter.symbol = "lockModules";
  604. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  605. parameter.ranges.def = 0.0f;
  606. parameter.ranges.min = 0.0f;
  607. parameter.ranges.max = 1.0f;
  608. break;
  609. case kWindowParameterUpdateRateLimit:
  610. parameter.name = "Update rate limit";
  611. parameter.symbol = "rateLimit";
  612. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  613. parameter.ranges.def = 0.0f;
  614. parameter.ranges.min = 0.0f;
  615. parameter.ranges.max = 2.0f;
  616. parameter.enumValues.count = 3;
  617. parameter.enumValues.restrictedMode = true;
  618. parameter.enumValues.values = new ParameterEnumerationValue[3];
  619. parameter.enumValues.values[0].label = "None";
  620. parameter.enumValues.values[0].value = 0.0f;
  621. parameter.enumValues.values[1].label = "2x";
  622. parameter.enumValues.values[1].value = 1.0f;
  623. parameter.enumValues.values[2].label = "4x";
  624. parameter.enumValues.values[2].value = 2.0f;
  625. break;
  626. }
  627. #endif
  628. }
  629. void initState(const uint32_t index, String& stateKey, String& defaultStateValue) override
  630. {
  631. defaultStateValue = "";
  632. switch (index)
  633. {
  634. case 0:
  635. stateKey = "patch";
  636. break;
  637. #ifndef HEADLESS
  638. case 1:
  639. stateKey = "windowSize";
  640. break;
  641. #endif
  642. }
  643. }
  644. /* --------------------------------------------------------------------------------------------------------
  645. * Internal data */
  646. float getParameterValue(uint32_t index) const override
  647. {
  648. if (index < kModuleParameters)
  649. return context->parameters[index];
  650. #ifndef HEADLESS
  651. index -= kModuleParameters;
  652. if (index < kWindowParameterCount)
  653. return fWindowParameters[index];
  654. #endif
  655. return 0.0f;
  656. }
  657. void setParameterValue(uint32_t index, float value) override
  658. {
  659. if (index < kModuleParameters)
  660. {
  661. context->parameters[index] = value;
  662. return;
  663. }
  664. #ifndef HEADLESS
  665. index -= kModuleParameters;
  666. if (index < kWindowParameterCount)
  667. {
  668. fWindowParameters[index] = value;
  669. return;
  670. }
  671. #endif
  672. }
  673. String getState(const char* const key) const override
  674. {
  675. #ifndef HEADLESS
  676. if (std::strcmp(key, "windowSize") == 0)
  677. return fWindowSize;
  678. #endif
  679. if (std::strcmp(key, "patch") != 0)
  680. return String();
  681. if (fAutosavePath.empty())
  682. return String();
  683. std::vector<uint8_t> data;
  684. {
  685. const ScopedContext sc(this);
  686. context->engine->prepareSave();
  687. context->patch->saveAutosave();
  688. context->patch->cleanAutosave();
  689. // context->history->setSaved();
  690. try {
  691. data = rack::system::archiveDirectory(fAutosavePath, 1);
  692. } DISTRHO_SAFE_EXCEPTION_RETURN("getState archiveDirectory", String());
  693. }
  694. return String::asBase64(data.data(), data.size());
  695. }
  696. void setState(const char* const key, const char* const value) override
  697. {
  698. #ifndef HEADLESS
  699. if (std::strcmp(key, "windowSize") == 0)
  700. {
  701. fWindowSize = value;
  702. return;
  703. }
  704. #endif
  705. if (std::strcmp(key, "patch") != 0)
  706. return;
  707. if (fAutosavePath.empty())
  708. return;
  709. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  710. const ScopedContext sc(this);
  711. rack::system::removeRecursively(fAutosavePath);
  712. rack::system::createDirectories(fAutosavePath);
  713. rack::system::unarchiveToDirectory(data, fAutosavePath);
  714. try {
  715. context->patch->loadAutosave();
  716. } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",);
  717. // context->history->setSaved();
  718. }
  719. /* --------------------------------------------------------------------------------------------------------
  720. * Process */
  721. void activate() override
  722. {
  723. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  724. const uint32_t bufferSize = getBufferSize();
  725. fAudioBufferCopy = new float*[DISTRHO_PLUGIN_NUM_INPUTS];
  726. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  727. fAudioBufferCopy[i] = new float[newBufferSize];
  728. #endif
  729. fPreviousFrame = 0;
  730. }
  731. void deactivate() override
  732. {
  733. if (fAudioBufferCopy != nullptr)
  734. {
  735. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  736. delete[] fAudioBufferCopy[i];
  737. delete[] fAudioBufferCopy;
  738. fAudioBufferCopy = nullptr;
  739. }
  740. }
  741. inline void sendSingleSimpleMidiMessage(const MidiEvent& midiEvent)
  742. {
  743. if (CardinalMidiInputDevice** inputs = fCurrentMidiInputs)
  744. {
  745. for (;*inputs != nullptr; ++inputs)
  746. (*inputs)->handleSingleSimpleMessageFromHost(midiEvent);
  747. }
  748. }
  749. void run(const float** const inputs, float** const outputs, const uint32_t frames,
  750. const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  751. {
  752. const MutexLocker cml(fDeviceMutex);
  753. rack::contextSet(context);
  754. {
  755. const TimePosition& timePos(getTimePosition());
  756. bool reset = false;
  757. MidiEvent singleTimeMidiEvent = { 0, 1, { 0, 0, 0, 0 }, nullptr };
  758. if (timePos.playing)
  759. {
  760. if (timePos.frame == 0 || fPreviousFrame + frames != timePos.frame)
  761. reset = true;
  762. if (! context->playing)
  763. {
  764. if (timePos.frame == 0)
  765. {
  766. singleTimeMidiEvent.data[0] = 0xFA; // start
  767. sendSingleSimpleMidiMessage(singleTimeMidiEvent);
  768. }
  769. singleTimeMidiEvent.data[0] = 0xFB; // continue
  770. sendSingleSimpleMidiMessage(singleTimeMidiEvent);
  771. }
  772. }
  773. else if (context->playing)
  774. {
  775. singleTimeMidiEvent.data[0] = 0xFC; // stop
  776. sendSingleSimpleMidiMessage(singleTimeMidiEvent);
  777. }
  778. context->playing = timePos.playing;
  779. context->bbtValid = timePos.bbt.valid;
  780. context->frame = timePos.frame;
  781. if (timePos.bbt.valid)
  782. {
  783. const double samplesPerTick = 60.0 * getSampleRate()
  784. / timePos.bbt.beatsPerMinute
  785. / timePos.bbt.ticksPerBeat;
  786. context->bar = timePos.bbt.bar;
  787. context->beat = timePos.bbt.beat;
  788. context->beatsPerBar = timePos.bbt.beatsPerBar;
  789. context->beatType = timePos.bbt.beatType;
  790. context->barStartTick = timePos.bbt.barStartTick;
  791. context->beatsPerMinute = timePos.bbt.beatsPerMinute;
  792. context->tick = timePos.bbt.tick;
  793. context->ticksPerBeat = timePos.bbt.ticksPerBeat;
  794. context->ticksPerClock = timePos.bbt.ticksPerBeat / timePos.bbt.beatType;
  795. context->ticksPerFrame = 1.0 / samplesPerTick;
  796. context->tickClock = std::fmod(timePos.bbt.tick, context->ticksPerClock);
  797. }
  798. context->reset = reset;
  799. fPreviousFrame = timePos.frame;
  800. }
  801. if (CardinalMidiInputDevice** inputs = fCurrentMidiInputs)
  802. {
  803. for (;*inputs != nullptr; ++inputs)
  804. (*inputs)->handleMessagesFromHost(midiEvents, midiEventCount);
  805. }
  806. // separate buffers, use them
  807. if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
  808. {
  809. context->dataIns = inputs;
  810. context->dataOuts = outputs;
  811. }
  812. // inline processing, use a safe copy
  813. else
  814. {
  815. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  816. std::memcpy(fAudioBufferCopy[i], inputs[i], sizeof(float)*frames);
  817. context->dataIns = fAudioBufferCopy;
  818. context->dataOuts = outputs;
  819. }
  820. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  821. std::memset(outputs[i], 0, sizeof(float)*frames);
  822. context->engine->stepBlock(frames);
  823. }
  824. void bufferSizeChanged(const uint32_t newBufferSize) override
  825. {
  826. rack::contextSet(context);
  827. context->bufferSize = newBufferSize;
  828. }
  829. void sampleRateChanged(const double newSampleRate) override
  830. {
  831. rack::contextSet(context);
  832. rack::settings::sampleRate = newSampleRate;
  833. context->sampleRate = newSampleRate;
  834. context->engine->setSampleRate(newSampleRate);
  835. }
  836. // -------------------------------------------------------------------------------------------------------
  837. private:
  838. /**
  839. Set our plugin class as non-copyable and add a leak detector just in case.
  840. */
  841. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  842. };
  843. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  844. {
  845. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  846. }
  847. /* ------------------------------------------------------------------------------------------------------------
  848. * Plugin entry point, called by DPF to create a new plugin instance. */
  849. Plugin* createPlugin()
  850. {
  851. return new CardinalPlugin();
  852. }
  853. // -----------------------------------------------------------------------------------------------------------
  854. END_NAMESPACE_DISTRHO