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.

1065 lines
35KB

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