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.

1364 lines
47KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. #include <asset.hpp>
  18. #include <library.hpp>
  19. #include <midi.hpp>
  20. #include <patch.hpp>
  21. #include <plugin.hpp>
  22. #include <random.hpp>
  23. #include <settings.hpp>
  24. #include <system.hpp>
  25. #include <app/Browser.hpp>
  26. #include <app/Scene.hpp>
  27. #include <engine/Engine.hpp>
  28. #include <ui/common.hpp>
  29. #include <window/Window.hpp>
  30. #ifdef NDEBUG
  31. # undef DEBUG
  32. #endif
  33. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  34. # include <lo/lo.h>
  35. # include "extra/Thread.hpp"
  36. #endif
  37. #include <list>
  38. #include "CardinalCommon.hpp"
  39. #include "DistrhoPluginUtils.hpp"
  40. #include "PluginContext.hpp"
  41. #include "extra/Base64.hpp"
  42. #ifdef DISTRHO_OS_WASM
  43. # include <emscripten/emscripten.h>
  44. #else
  45. # include "extra/SharedResourcePointer.hpp"
  46. #endif
  47. #if CARDINAL_VARIANT_FX
  48. # define CARDINAL_TEMPLATE_NAME "init/fx.vcv"
  49. #elif CARDINAL_VARIANT_NATIVE
  50. # define CARDINAL_TEMPLATE_NAME "init/native.vcv"
  51. #elif CARDINAL_VARIANT_SYNTH
  52. # define CARDINAL_TEMPLATE_NAME "init/synth.vcv"
  53. #else
  54. # define CARDINAL_TEMPLATE_NAME "init/main.vcv"
  55. #endif
  56. static const constexpr uint kCardinalStateBaseCount = 3; // patch, screenshot, comment
  57. #ifndef HEADLESS
  58. # include "extra/ScopedValueSetter.hpp"
  59. # include "WindowParameters.hpp"
  60. static const constexpr uint kCardinalStateCount = kCardinalStateBaseCount + 2; // moduleInfos, windowSize
  61. #else
  62. # define kWindowParameterCount 0
  63. static const constexpr uint kCardinalStateCount = kCardinalStateBaseCount;
  64. #endif
  65. namespace rack {
  66. namespace asset {
  67. std::string patchesPath();
  68. void destroy();
  69. }
  70. namespace engine {
  71. void Engine_setAboutToClose(Engine*);
  72. }
  73. namespace plugin {
  74. void initStaticPlugins();
  75. void destroyStaticPlugins();
  76. }
  77. #ifndef HEADLESS
  78. namespace window {
  79. void WindowInit(Window* window, DISTRHO_NAMESPACE::Plugin* plugin);
  80. }
  81. #endif
  82. }
  83. START_NAMESPACE_DISTRHO
  84. template<typename T>
  85. static inline
  86. bool d_isDiffHigherThanLimit(const T& v1, const T& v2, const T& limit)
  87. {
  88. return v1 != v2 ? (v1 > v2 ? v1 - v2 : v2 - v1) > limit : false;
  89. }
  90. // -----------------------------------------------------------------------------------------------------------
  91. #ifdef DISTRHO_OS_WASM
  92. static char* getPatchFileEncodedInURL() {
  93. return static_cast<char*>(EM_ASM_PTR({
  94. var searchParams = new URLSearchParams(window.location.search);
  95. var patch = searchParams.get('patch');
  96. if (!patch)
  97. return null;
  98. var length = lengthBytesUTF8(patch) + 1;
  99. var str = _malloc(length);
  100. stringToUTF8(patch, str, length);
  101. return str;
  102. }));
  103. };
  104. static char* getPatchRemoteURL() {
  105. return static_cast<char*>(EM_ASM_PTR({
  106. var searchParams = new URLSearchParams(window.location.search);
  107. var patch = searchParams.get('patchurl');
  108. if (!patch)
  109. return null;
  110. var length = lengthBytesUTF8(patch) + 1;
  111. var str = _malloc(length);
  112. stringToUTF8(patch, str, length);
  113. return str;
  114. }));
  115. };
  116. static char* getPatchStorageSlug() {
  117. return static_cast<char*>(EM_ASM_PTR({
  118. var searchParams = new URLSearchParams(window.location.search);
  119. var patch = searchParams.get('patchstorage');
  120. if (!patch)
  121. return null;
  122. var length = lengthBytesUTF8(patch) + 1;
  123. var str = _malloc(length);
  124. stringToUTF8(patch, str, length);
  125. return str;
  126. }));
  127. };
  128. #endif
  129. // -----------------------------------------------------------------------------------------------------------
  130. struct Initializer
  131. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  132. : public Thread
  133. #endif
  134. {
  135. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  136. lo_server oscServer = nullptr;
  137. CardinalBasePlugin* oscPlugin = nullptr;
  138. #endif
  139. std::string templatePath;
  140. std::string factoryTemplatePath;
  141. Initializer(const CardinalBasePlugin* const plugin)
  142. {
  143. using namespace rack;
  144. #ifdef DISTRHO_OS_WASM
  145. settings::allowCursorLock = true;
  146. #else
  147. settings::allowCursorLock = false;
  148. #endif
  149. settings::autoCheckUpdates = false;
  150. settings::autosaveInterval = 0;
  151. settings::devMode = true;
  152. settings::isPlugin = true;
  153. settings::skipLoadOnLaunch = true;
  154. settings::showTipsOnLaunch = false;
  155. settings::windowPos = math::Vec(0, 0);
  156. #ifdef HEADLESS
  157. settings::headless = true;
  158. #endif
  159. // copied from https://community.vcvrack.com/t/16-colour-cable-palette/15951
  160. settings::cableColors = {
  161. color::fromHexString("#ff5252"),
  162. color::fromHexString("#ff9352"),
  163. color::fromHexString("#ffd452"),
  164. color::fromHexString("#e8ff52"),
  165. color::fromHexString("#a8ff52"),
  166. color::fromHexString("#67ff52"),
  167. color::fromHexString("#52ff7d"),
  168. color::fromHexString("#52ffbe"),
  169. color::fromHexString("#52ffff"),
  170. color::fromHexString("#52beff"),
  171. color::fromHexString("#527dff"),
  172. color::fromHexString("#6752ff"),
  173. color::fromHexString("#a852ff"),
  174. color::fromHexString("#e952ff"),
  175. color::fromHexString("#ff52d4"),
  176. color::fromHexString("#ff5293"),
  177. };
  178. system::init();
  179. logger::init();
  180. random::init();
  181. ui::init();
  182. if (asset::systemDir.empty())
  183. {
  184. if (const char* const bundlePath = plugin->getBundlePath())
  185. {
  186. if (const char* const resourcePath = getResourcePath(bundlePath))
  187. {
  188. asset::systemDir = resourcePath;
  189. asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
  190. }
  191. }
  192. if (asset::systemDir.empty() || ! system::exists(asset::systemDir) || ! system::exists(asset::bundlePath))
  193. {
  194. #ifdef CARDINAL_PLUGIN_SOURCE_DIR
  195. // Make system dir point to source code location as fallback
  196. asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack";
  197. asset::bundlePath.clear();
  198. // If source code dir does not exist use install target prefix as system dir
  199. if (!system::exists(system::join(asset::systemDir, "res")))
  200. #endif
  201. {
  202. #if defined(DISTRHO_OS_WASM)
  203. asset::systemDir = "/resources";
  204. #elif defined(ARCH_MAC)
  205. asset::systemDir = "/Library/Application Support/Cardinal";
  206. #elif defined(ARCH_WIN)
  207. const std::string commonprogfiles = getSpecialPath(kSpecialPathCommonProgramFiles);
  208. if (! commonprogfiles.empty())
  209. asset::systemDir = system::join(commonprogfiles, "Cardinal");
  210. #else
  211. asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal";
  212. #endif
  213. asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
  214. }
  215. }
  216. asset::userDir = asset::systemDir;
  217. }
  218. const std::string patchesPath = asset::patchesPath();
  219. #ifdef DISTRHO_OS_WASM
  220. templatePath = system::join(patchesPath, CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME);
  221. #else
  222. templatePath = system::join(patchesPath, CARDINAL_TEMPLATE_NAME);
  223. #endif
  224. factoryTemplatePath = system::join(patchesPath, CARDINAL_TEMPLATE_NAME);
  225. // Log environment
  226. INFO("%s %s v%s", APP_NAME.c_str(), APP_EDITION.c_str(), APP_VERSION.c_str());
  227. INFO("%s", system::getOperatingSystemInfo().c_str());
  228. INFO("Binary filename: %s", getBinaryFilename());
  229. INFO("Bundle path: %s", plugin->getBundlePath());
  230. INFO("System directory: %s", asset::systemDir.c_str());
  231. INFO("User directory: %s", asset::userDir.c_str());
  232. INFO("Template patch: %s", templatePath.c_str());
  233. INFO("System template patch: %s", factoryTemplatePath.c_str());
  234. // Report to user if something is wrong with the installation
  235. if (asset::systemDir.empty())
  236. {
  237. d_stderr2("Failed to locate Cardinal plugin bundle.\n"
  238. "Install Cardinal with its bundle folder intact and try again.");
  239. }
  240. else if (! system::exists(asset::systemDir))
  241. {
  242. d_stderr2("System directory \"%s\" does not exist.\n"
  243. "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
  244. }
  245. INFO("Initializing plugins");
  246. plugin::initStaticPlugins();
  247. INFO("Initializing plugin browser DB");
  248. app::browserInit();
  249. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  250. INFO("Initializing OSC Remote control");
  251. oscServer = lo_server_new_with_proto(REMOTE_HOST_PORT, LO_UDP, osc_error_handler);
  252. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,);
  253. lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this);
  254. lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this);
  255. lo_server_add_method(oscServer, "/screenshot", "b", osc_screenshot_handler, this);
  256. lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr);
  257. startThread();
  258. #elif defined(HEADLESS)
  259. INFO("OSC Remote control is not enabled in this build");
  260. #endif
  261. }
  262. ~Initializer()
  263. {
  264. using namespace rack;
  265. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  266. if (oscServer != nullptr)
  267. {
  268. stopThread(5000);
  269. lo_server_del_method(oscServer, nullptr, nullptr);
  270. lo_server_free(oscServer);
  271. oscServer = nullptr;
  272. }
  273. #endif
  274. INFO("Clearing asset paths");
  275. asset::bundlePath.clear();
  276. asset::systemDir.clear();
  277. asset::userDir.clear();
  278. INFO("Destroying plugins");
  279. plugin::destroyStaticPlugins();
  280. INFO("Destroying colourized assets");
  281. asset::destroy();
  282. INFO("Destroying settings");
  283. settings::destroy();
  284. INFO("Destroying logger");
  285. logger::destroy();
  286. }
  287. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  288. void run() override
  289. {
  290. INFO("OSC Thread Listening for remote commands");
  291. while (! shouldThreadExit())
  292. {
  293. d_msleep(200);
  294. while (lo_server_recv_noblock(oscServer, 0) != 0) {}
  295. }
  296. INFO("OSC Thread Closed");
  297. }
  298. static void osc_error_handler(int num, const char* msg, const char* path)
  299. {
  300. d_stderr("Cardinal OSC Error: code: %i, msg: \"%s\", path: \"%s\")", num, msg, path);
  301. }
  302. static int osc_fallback_handler(const char* const path, const char* const types, lo_arg**, int, lo_message, void*)
  303. {
  304. d_stderr("Cardinal OSC unhandled message \"%s\" with types \"%s\"", path, types);
  305. return 0;
  306. }
  307. static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self)
  308. {
  309. d_stdout("osc_hello_handler()");
  310. const lo_address source = lo_message_get_source(m);
  311. lo_send_from(source, static_cast<Initializer*>(self)->oscServer, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok");
  312. return 0;
  313. }
  314. static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  315. {
  316. d_stdout("osc_load_handler()");
  317. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  318. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  319. const int32_t size = argv[0]->blob.size;
  320. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  321. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  322. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  323. bool ok = false;
  324. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->oscPlugin)
  325. {
  326. CardinalPluginContext* const context = plugin->context;
  327. std::vector<uint8_t> data(size);
  328. std::memcpy(data.data(), blob, size);
  329. rack::contextSet(context);
  330. rack::system::removeRecursively(context->patch->autosavePath);
  331. rack::system::createDirectories(context->patch->autosavePath);
  332. try {
  333. rack::system::unarchiveToDirectory(data, context->patch->autosavePath);
  334. context->patch->loadAutosave();
  335. ok = true;
  336. }
  337. catch (rack::Exception& e) {
  338. WARN("%s", e.what());
  339. }
  340. rack::contextSet(nullptr);
  341. }
  342. const lo_address source = lo_message_get_source(m);
  343. lo_send_from(source, static_cast<Initializer*>(self)->oscServer,
  344. LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail");
  345. return 0;
  346. }
  347. static int osc_screenshot_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  348. {
  349. d_stdout("osc_screenshot_handler()");
  350. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  351. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  352. const int32_t size = argv[0]->blob.size;
  353. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  354. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  355. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  356. bool ok = false;
  357. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->oscPlugin)
  358. ok = plugin->updateStateValue("screenshot", String::asBase64(blob, size).buffer());
  359. const lo_address source = lo_message_get_source(m);
  360. lo_send_from(source, static_cast<Initializer*>(self)->oscServer,
  361. LO_TT_IMMEDIATE, "/resp", "ss", "screenshot", ok ? "ok" : "fail");
  362. return 0;
  363. }
  364. #endif
  365. };
  366. // -----------------------------------------------------------------------------------------------------------
  367. void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message, const uint8_t channel)
  368. {
  369. if (bypassed)
  370. return;
  371. const size_t size = message.bytes.size();
  372. DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
  373. DISTRHO_SAFE_ASSERT_RETURN(message.frame >= 0,);
  374. MidiEvent event;
  375. event.frame = message.frame;
  376. switch (message.bytes[0] & 0xF0)
  377. {
  378. case 0x80:
  379. case 0x90:
  380. case 0xA0:
  381. case 0xB0:
  382. case 0xE0:
  383. event.size = 3;
  384. break;
  385. case 0xC0:
  386. case 0xD0:
  387. event.size = 2;
  388. break;
  389. case 0xF0:
  390. switch (message.bytes[0] & 0x0F)
  391. {
  392. case 0x0:
  393. case 0x4:
  394. case 0x5:
  395. case 0x7:
  396. case 0x9:
  397. case 0xD:
  398. // unsupported
  399. return;
  400. case 0x1:
  401. case 0x2:
  402. case 0x3:
  403. case 0xE:
  404. event.size = 3;
  405. break;
  406. case 0x6:
  407. case 0x8:
  408. case 0xA:
  409. case 0xB:
  410. case 0xC:
  411. case 0xF:
  412. event.size = 1;
  413. break;
  414. }
  415. break;
  416. default:
  417. // invalid
  418. return;
  419. }
  420. DISTRHO_SAFE_ASSERT_RETURN(size >= event.size,);
  421. std::memcpy(event.data, message.bytes.data(), event.size);
  422. if (channel != 0 && event.data[0] < 0xF0)
  423. event.data[0] |= channel & 0x0F;
  424. plugin->writeMidiEvent(event);
  425. }
  426. // -----------------------------------------------------------------------------------------------------------
  427. struct ScopedContext {
  428. ScopedContext(const CardinalBasePlugin* const plugin)
  429. {
  430. rack::contextSet(plugin->context);
  431. }
  432. ~ScopedContext()
  433. {
  434. rack::contextSet(nullptr);
  435. }
  436. };
  437. // -----------------------------------------------------------------------------------------------------------
  438. class CardinalPlugin : public CardinalBasePlugin
  439. {
  440. #ifdef DISTRHO_OS_WASM
  441. ScopedPointer<Initializer> fInitializer;
  442. #else
  443. SharedResourcePointer<Initializer> fInitializer;
  444. #endif
  445. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  446. /* If host audio ins == outs we can get issues for inplace processing.
  447. * So allocate a float array that will serve as safe copy for those cases.
  448. */
  449. float** fAudioBufferCopy;
  450. #endif
  451. std::string fAutosavePath;
  452. uint64_t fNextExpectedFrame;
  453. struct {
  454. String comment;
  455. String screenshot;
  456. #ifndef HEADLESS
  457. String windowSize;
  458. #endif
  459. } fState;
  460. // bypass handling
  461. bool fWasBypassed;
  462. MidiEvent bypassMidiEvents[16];
  463. #ifndef HEADLESS
  464. // real values, not VCV interpreted ones
  465. float fWindowParameters[kWindowParameterCount];
  466. #endif
  467. public:
  468. CardinalPlugin()
  469. : CardinalBasePlugin(kModuleParameters + kWindowParameterCount + 1, 0, kCardinalStateCount),
  470. #ifdef DISTRHO_OS_WASM
  471. fInitializer(new Initializer(this)),
  472. #else
  473. fInitializer(this),
  474. #endif
  475. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  476. fAudioBufferCopy(nullptr),
  477. #endif
  478. fNextExpectedFrame(0),
  479. fWasBypassed(false)
  480. {
  481. #ifndef HEADLESS
  482. fWindowParameters[kWindowParameterShowTooltips] = 1.0f;
  483. fWindowParameters[kWindowParameterCableOpacity] = 50.0f;
  484. fWindowParameters[kWindowParameterCableTension] = 75.0f;
  485. fWindowParameters[kWindowParameterRackBrightness] = 100.0f;
  486. fWindowParameters[kWindowParameterHaloBrightness] = 25.0f;
  487. fWindowParameters[kWindowParameterKnobMode] = 0.0f;
  488. fWindowParameters[kWindowParameterWheelKnobControl] = 0.0f;
  489. fWindowParameters[kWindowParameterWheelSensitivity] = 1.0f;
  490. fWindowParameters[kWindowParameterLockModulePositions] = 0.0f;
  491. fWindowParameters[kWindowParameterUpdateRateLimit] = 0.0f;
  492. fWindowParameters[kWindowParameterBrowserSort] = 3.0f;
  493. fWindowParameters[kWindowParameterBrowserZoom] = 50.0f;
  494. fWindowParameters[kWindowParameterInvertZoom] = 0.0f;
  495. fWindowParameters[kWindowParameterSqueezeModulePositions] = 1.0f;
  496. #endif
  497. // create unique temporary path for this instance
  498. try {
  499. char uidBuf[24];
  500. const std::string tmp = rack::system::getTempDirectory();
  501. for (int i=1;; ++i)
  502. {
  503. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  504. const std::string trypath = rack::system::join(tmp, uidBuf);
  505. if (! rack::system::exists(trypath))
  506. {
  507. if (rack::system::createDirectories(trypath))
  508. fAutosavePath = trypath;
  509. break;
  510. }
  511. }
  512. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  513. // initialize midi events used when entering bypassed state
  514. std::memset(bypassMidiEvents, 0, sizeof(bypassMidiEvents));
  515. for (uint8_t i=0; i<16; ++i)
  516. {
  517. bypassMidiEvents[i].size = 3;
  518. bypassMidiEvents[i].data[0] = 0xB0 + i;
  519. bypassMidiEvents[i].data[1] = 0x7B;
  520. }
  521. const float sampleRate = getSampleRate();
  522. rack::settings::sampleRate = sampleRate;
  523. context->bufferSize = getBufferSize();
  524. context->sampleRate = sampleRate;
  525. const ScopedContext sc(this);
  526. context->engine = new rack::engine::Engine;
  527. context->engine->setSampleRate(sampleRate);
  528. context->history = new rack::history::State;
  529. context->patch = new rack::patch::Manager;
  530. context->patch->autosavePath = fAutosavePath;
  531. context->patch->templatePath = fInitializer->templatePath;
  532. context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath;
  533. context->event = new rack::widget::EventState;
  534. context->scene = new rack::app::Scene;
  535. context->event->rootWidget = context->scene;
  536. if (! isDummyInstance())
  537. context->window = new rack::window::Window;
  538. #ifdef DISTRHO_OS_WASM
  539. if ((rack::patchStorageSlug = getPatchStorageSlug()) == nullptr &&
  540. (rack::patchRemoteURL = getPatchRemoteURL()) == nullptr &&
  541. (rack::patchFromURL = getPatchFileEncodedInURL()) == nullptr)
  542. #endif
  543. {
  544. context->patch->loadTemplate();
  545. context->scene->rackScroll->reset();
  546. // swap to factory template after first load
  547. context->patch->templatePath = context->patch->factoryTemplatePath;
  548. }
  549. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  550. fInitializer->oscPlugin = this;
  551. #endif
  552. }
  553. ~CardinalPlugin() override
  554. {
  555. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  556. fInitializer->oscPlugin = nullptr;
  557. #endif
  558. {
  559. const ScopedContext sc(this);
  560. context->patch->clear();
  561. // do a little dance to prevent context scene deletion from saving to temp dir
  562. #ifndef HEADLESS
  563. const ScopedValueSetter<bool> svs(rack::settings::headless, true);
  564. #endif
  565. Engine_setAboutToClose(context->engine);
  566. delete context;
  567. }
  568. if (! fAutosavePath.empty())
  569. rack::system::removeRecursively(fAutosavePath);
  570. }
  571. CardinalPluginContext* getRackContext() const noexcept
  572. {
  573. return context;
  574. }
  575. protected:
  576. /* --------------------------------------------------------------------------------------------------------
  577. * Information */
  578. const char* getLabel() const override
  579. {
  580. return DISTRHO_PLUGIN_LABEL;
  581. }
  582. const char* getDescription() const override
  583. {
  584. return ""
  585. "Cardinal is a free and open-source virtual modular synthesizer plugin.\n"
  586. "It is based on the popular VCV Rack but with a focus on being a fully self-contained plugin version.\n"
  587. "It is not an official VCV project, and it is not affiliated with it in any way.\n"
  588. "\n"
  589. "Cardinal contains Rack, some 3rd-party modules and a few internal utilities.\n"
  590. "It does not load external modules and does not connect to the official Rack library/store.\n";
  591. }
  592. const char* getMaker() const override
  593. {
  594. return "DISTRHO";
  595. }
  596. const char* getHomePage() const override
  597. {
  598. return "https://github.com/DISTRHO/Cardinal";
  599. }
  600. const char* getLicense() const override
  601. {
  602. return "GPLv3+";
  603. }
  604. uint32_t getVersion() const override
  605. {
  606. return d_version(0, 22, 8);
  607. }
  608. int64_t getUniqueId() const override
  609. {
  610. #if CARDINAL_VARIANT_MAIN || CARDINAL_VARIANT_NATIVE
  611. return d_cconst('d', 'C', 'd', 'n');
  612. #elif CARDINAL_VARIANT_FX
  613. return d_cconst('d', 'C', 'n', 'F');
  614. #elif CARDINAL_VARIANT_SYNTH
  615. return d_cconst('d', 'C', 'n', 'S');
  616. #else
  617. #error cardinal variant not set
  618. #endif
  619. }
  620. /* --------------------------------------------------------------------------------------------------------
  621. * Init */
  622. void initAudioPort(const bool input, uint32_t index, AudioPort& port) override
  623. {
  624. #if CARDINAL_VARIANT_FX || CARDINAL_VARIANT_NATIVE || CARDINAL_VARIANT_SYNTH
  625. if (index < 2)
  626. port.groupId = kPortGroupStereo;
  627. #endif
  628. if (index >= 8)
  629. {
  630. port.hints = kAudioPortIsCV | kCVPortHasPositiveUnipolarRange | kCVPortHasScaledRange;
  631. index -= 8;
  632. }
  633. CardinalBasePlugin::initAudioPort(input, index, port);
  634. }
  635. void initParameter(const uint32_t index, Parameter& parameter) override
  636. {
  637. if (index < kModuleParameters)
  638. {
  639. parameter.name = "Parameter ";
  640. parameter.name += String(index + 1);
  641. parameter.symbol = "param_";
  642. parameter.symbol += String(index + 1);
  643. parameter.unit = "v";
  644. parameter.hints = kParameterIsAutomatable;
  645. parameter.ranges.def = 0.0f;
  646. parameter.ranges.min = 0.0f;
  647. parameter.ranges.max = 10.0f;
  648. return;
  649. }
  650. if (index == kModuleParameters)
  651. {
  652. parameter.initDesignation(kParameterDesignationBypass);
  653. return;
  654. }
  655. #ifndef HEADLESS
  656. switch (index - kModuleParameters - 1)
  657. {
  658. case kWindowParameterShowTooltips:
  659. parameter.name = "Show tooltips";
  660. parameter.symbol = "tooltips";
  661. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  662. parameter.ranges.def = 1.0f;
  663. parameter.ranges.min = 0.0f;
  664. parameter.ranges.max = 1.0f;
  665. break;
  666. case kWindowParameterCableOpacity:
  667. parameter.name = "Cable opacity";
  668. parameter.symbol = "cableOpacity";
  669. parameter.unit = "%";
  670. parameter.hints = kParameterIsAutomatable;
  671. parameter.ranges.def = 50.0f;
  672. parameter.ranges.min = 0.0f;
  673. parameter.ranges.max = 100.0f;
  674. break;
  675. case kWindowParameterCableTension:
  676. parameter.name = "Cable tension";
  677. parameter.symbol = "cableTension";
  678. parameter.unit = "%";
  679. parameter.hints = kParameterIsAutomatable;
  680. parameter.ranges.def = 75.0f;
  681. parameter.ranges.min = 0.0f;
  682. parameter.ranges.max = 100.0f;
  683. break;
  684. case kWindowParameterRackBrightness:
  685. parameter.name = "Room brightness";
  686. parameter.symbol = "rackBrightness";
  687. parameter.unit = "%";
  688. parameter.hints = kParameterIsAutomatable;
  689. parameter.ranges.def = 100.0f;
  690. parameter.ranges.min = 0.0f;
  691. parameter.ranges.max = 100.0f;
  692. break;
  693. case kWindowParameterHaloBrightness:
  694. parameter.name = "Light Bloom";
  695. parameter.symbol = "haloBrightness";
  696. parameter.unit = "%";
  697. parameter.hints = kParameterIsAutomatable;
  698. parameter.ranges.def = 25.0f;
  699. parameter.ranges.min = 0.0f;
  700. parameter.ranges.max = 100.0f;
  701. break;
  702. case kWindowParameterKnobMode:
  703. parameter.name = "Knob mode";
  704. parameter.symbol = "knobMode";
  705. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  706. parameter.ranges.def = 0.0f;
  707. parameter.ranges.min = 0.0f;
  708. parameter.ranges.max = 2.0f;
  709. parameter.enumValues.count = 3;
  710. parameter.enumValues.restrictedMode = true;
  711. parameter.enumValues.values = new ParameterEnumerationValue[3];
  712. parameter.enumValues.values[0].label = "Linear";
  713. parameter.enumValues.values[0].value = 0.0f;
  714. parameter.enumValues.values[1].label = "Absolute rotary";
  715. parameter.enumValues.values[1].value = 1.0f;
  716. parameter.enumValues.values[2].label = "Relative rotary";
  717. parameter.enumValues.values[2].value = 2.0f;
  718. break;
  719. case kWindowParameterWheelKnobControl:
  720. parameter.name = "Scroll wheel knob control";
  721. parameter.symbol = "knobScroll";
  722. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  723. parameter.ranges.def = 0.0f;
  724. parameter.ranges.min = 0.0f;
  725. parameter.ranges.max = 1.0f;
  726. break;
  727. case kWindowParameterWheelSensitivity:
  728. parameter.name = "Scroll wheel knob sensitivity";
  729. parameter.symbol = "knobScrollSensitivity";
  730. parameter.hints = kParameterIsAutomatable|kParameterIsLogarithmic;
  731. parameter.ranges.def = 1.0f;
  732. parameter.ranges.min = 0.1f;
  733. parameter.ranges.max = 10.0f;
  734. break;
  735. case kWindowParameterLockModulePositions:
  736. parameter.name = "Lock module positions";
  737. parameter.symbol = "lockModules";
  738. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  739. parameter.ranges.def = 0.0f;
  740. parameter.ranges.min = 0.0f;
  741. parameter.ranges.max = 1.0f;
  742. break;
  743. case kWindowParameterUpdateRateLimit:
  744. parameter.name = "Update rate limit";
  745. parameter.symbol = "rateLimit";
  746. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  747. parameter.ranges.def = 0.0f;
  748. parameter.ranges.min = 0.0f;
  749. parameter.ranges.max = 2.0f;
  750. parameter.enumValues.count = 3;
  751. parameter.enumValues.restrictedMode = true;
  752. parameter.enumValues.values = new ParameterEnumerationValue[3];
  753. parameter.enumValues.values[0].label = "None";
  754. parameter.enumValues.values[0].value = 0.0f;
  755. parameter.enumValues.values[1].label = "2x";
  756. parameter.enumValues.values[1].value = 1.0f;
  757. parameter.enumValues.values[2].label = "4x";
  758. parameter.enumValues.values[2].value = 2.0f;
  759. break;
  760. case kWindowParameterBrowserSort:
  761. parameter.name = "Browser sort";
  762. parameter.symbol = "browserSort";
  763. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  764. parameter.ranges.def = 3.0f;
  765. parameter.ranges.min = 0.0f;
  766. parameter.ranges.max = 5.0f;
  767. parameter.enumValues.count = 6;
  768. parameter.enumValues.restrictedMode = true;
  769. parameter.enumValues.values = new ParameterEnumerationValue[6];
  770. parameter.enumValues.values[0].label = "Updated";
  771. parameter.enumValues.values[0].value = 0.0f;
  772. parameter.enumValues.values[1].label = "Last used";
  773. parameter.enumValues.values[1].value = 1.0f;
  774. parameter.enumValues.values[2].label = "Most used";
  775. parameter.enumValues.values[2].value = 2.0f;
  776. parameter.enumValues.values[3].label = "Brand";
  777. parameter.enumValues.values[3].value = 3.0f;
  778. parameter.enumValues.values[4].label = "Name";
  779. parameter.enumValues.values[4].value = 4.0f;
  780. parameter.enumValues.values[5].label = "Random";
  781. parameter.enumValues.values[5].value = 5.0f;
  782. break;
  783. case kWindowParameterBrowserZoom:
  784. parameter.name = "Browser zoom";
  785. parameter.symbol = "browserZoom";
  786. parameter.hints = kParameterIsAutomatable;
  787. parameter.unit = "%";
  788. parameter.ranges.def = 50.0f;
  789. parameter.ranges.min = 25.0f;
  790. parameter.ranges.max = 200.0f;
  791. parameter.enumValues.count = 7;
  792. parameter.enumValues.restrictedMode = true;
  793. parameter.enumValues.values = new ParameterEnumerationValue[7];
  794. parameter.enumValues.values[0].label = "25";
  795. parameter.enumValues.values[0].value = 25.0f;
  796. parameter.enumValues.values[1].label = "35";
  797. parameter.enumValues.values[1].value = 35.0f;
  798. parameter.enumValues.values[2].label = "50";
  799. parameter.enumValues.values[2].value = 50.0f;
  800. parameter.enumValues.values[3].label = "71";
  801. parameter.enumValues.values[3].value = 71.0f;
  802. parameter.enumValues.values[4].label = "100";
  803. parameter.enumValues.values[4].value = 100.0f;
  804. parameter.enumValues.values[5].label = "141";
  805. parameter.enumValues.values[5].value = 141.0f;
  806. parameter.enumValues.values[6].label = "200";
  807. parameter.enumValues.values[6].value = 200.0f;
  808. break;
  809. case kWindowParameterInvertZoom:
  810. parameter.name = "Invert zoom";
  811. parameter.symbol = "invertZoom";
  812. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  813. parameter.ranges.def = 0.0f;
  814. parameter.ranges.min = 0.0f;
  815. parameter.ranges.max = 1.0f;
  816. break;
  817. case kWindowParameterSqueezeModulePositions:
  818. parameter.name = "Auto-squeeze module positions";
  819. parameter.symbol = "squeezeModules";
  820. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  821. parameter.ranges.def = 1.0f;
  822. parameter.ranges.min = 0.0f;
  823. parameter.ranges.max = 1.0f;
  824. break;
  825. }
  826. #endif
  827. }
  828. void initState(const uint32_t index, State& state) override
  829. {
  830. switch (index)
  831. {
  832. case 0:
  833. state.hints = kStateIsBase64Blob | kStateIsOnlyForDSP;
  834. state.key = "patch";
  835. state.label = "Patch";
  836. break;
  837. case 1:
  838. state.hints = kStateIsHostReadable | kStateIsBase64Blob;
  839. state.key = "screenshot";
  840. state.label = "Screenshot";
  841. break;
  842. case 2:
  843. state.hints = kStateIsHostWritable;
  844. state.key = "comment";
  845. state.label = "Comment";
  846. break;
  847. case 3:
  848. state.hints = kStateIsOnlyForUI;
  849. state.key = "moduleInfos";
  850. state.label = "moduleInfos";
  851. break;
  852. case 4:
  853. state.hints = kStateIsOnlyForUI;
  854. state.key = "windowSize";
  855. state.label = "Window size";
  856. break;
  857. }
  858. }
  859. /* --------------------------------------------------------------------------------------------------------
  860. * Internal data */
  861. float getParameterValue(uint32_t index) const override
  862. {
  863. // host mapped parameters
  864. if (index < kModuleParameters)
  865. return context->parameters[index];
  866. // bypass
  867. if (index == kModuleParameters)
  868. return context->bypassed ? 1.0f : 0.0f;
  869. #ifndef HEADLESS
  870. // window related parameters
  871. index -= kModuleParameters + 1;
  872. if (index < kWindowParameterCount)
  873. return fWindowParameters[index];
  874. #endif
  875. return 0.0f;
  876. }
  877. void setParameterValue(uint32_t index, float value) override
  878. {
  879. // host mapped parameters
  880. if (index < kModuleParameters)
  881. {
  882. context->parameters[index] = value;
  883. return;
  884. }
  885. // bypass
  886. if (index == kModuleParameters)
  887. {
  888. context->bypassed = value > 0.5f;
  889. return;
  890. }
  891. #ifndef HEADLESS
  892. // window related parameters
  893. index -= kModuleParameters + 1;
  894. if (index < kWindowParameterCount)
  895. {
  896. fWindowParameters[index] = value;
  897. return;
  898. }
  899. #endif
  900. }
  901. String getState(const char* const key) const override
  902. {
  903. #ifndef HEADLESS
  904. if (std::strcmp(key, "moduleInfos") == 0)
  905. {
  906. json_t* const rootJ = json_object();
  907. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, String());
  908. for (const auto& pluginPair : rack::settings::moduleInfos)
  909. {
  910. json_t* const pluginJ = json_object();
  911. DISTRHO_SAFE_ASSERT_CONTINUE(pluginJ != nullptr);
  912. for (const auto& modulePair : pluginPair.second)
  913. {
  914. json_t* const moduleJ = json_object();
  915. DISTRHO_SAFE_ASSERT_CONTINUE(moduleJ != nullptr);
  916. const rack::settings::ModuleInfo& m(modulePair.second);
  917. // To make setting.json smaller, only set properties if not default values.
  918. if (m.favorite)
  919. json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
  920. if (m.added > 0)
  921. json_object_set_new(moduleJ, "added", json_integer(m.added));
  922. if (std::isfinite(m.lastAdded))
  923. json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
  924. if (json_object_size(moduleJ))
  925. json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
  926. else
  927. json_decref(moduleJ);
  928. }
  929. if (json_object_size(pluginJ))
  930. json_object_set_new(rootJ, pluginPair.first.c_str(), pluginJ);
  931. else
  932. json_decref(pluginJ);
  933. }
  934. const String info(json_dumps(rootJ, JSON_COMPACT), false);
  935. json_decref(rootJ);
  936. return info;
  937. }
  938. if (std::strcmp(key, "windowSize") == 0)
  939. return fState.windowSize;
  940. #endif
  941. if (std::strcmp(key, "comment") == 0)
  942. return fState.comment;
  943. if (std::strcmp(key, "screenshot") == 0)
  944. return fState.screenshot;
  945. if (std::strcmp(key, "patch") != 0)
  946. return String();
  947. if (fAutosavePath.empty())
  948. return String();
  949. std::vector<uint8_t> data;
  950. {
  951. const ScopedContext sc(this);
  952. context->engine->prepareSave();
  953. context->patch->saveAutosave();
  954. context->patch->cleanAutosave();
  955. // context->history->setSaved();
  956. try {
  957. data = rack::system::archiveDirectory(fAutosavePath, 1);
  958. } DISTRHO_SAFE_EXCEPTION_RETURN("getState archiveDirectory", String());
  959. }
  960. return String::asBase64(data.data(), data.size());
  961. }
  962. void setState(const char* const key, const char* const value) override
  963. {
  964. #ifndef HEADLESS
  965. if (std::strcmp(key, "moduleInfos") == 0)
  966. {
  967. json_error_t error;
  968. json_t* const rootJ = json_loads(value, 0, &error);
  969. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr,);
  970. const char* pluginSlug;
  971. json_t* pluginJ;
  972. json_object_foreach(rootJ, pluginSlug, pluginJ)
  973. {
  974. const char* moduleSlug;
  975. json_t* moduleJ;
  976. json_object_foreach(pluginJ, moduleSlug, moduleJ)
  977. {
  978. rack::settings::ModuleInfo m;
  979. if (json_t* const favoriteJ = json_object_get(moduleJ, "favorite"))
  980. m.favorite = json_boolean_value(favoriteJ);
  981. if (json_t* const addedJ = json_object_get(moduleJ, "added"))
  982. m.added = json_integer_value(addedJ);
  983. if (json_t* const lastAddedJ = json_object_get(moduleJ, "lastAdded"))
  984. m.lastAdded = json_number_value(lastAddedJ);
  985. rack::settings::moduleInfos[pluginSlug][moduleSlug] = m;
  986. }
  987. }
  988. json_decref(rootJ);
  989. return;
  990. }
  991. if (std::strcmp(key, "windowSize") == 0)
  992. {
  993. fState.windowSize = value;
  994. return;
  995. }
  996. #endif
  997. if (std::strcmp(key, "comment") == 0)
  998. {
  999. fState.comment = value;
  1000. return;
  1001. }
  1002. if (std::strcmp(key, "screenshot") == 0)
  1003. {
  1004. fState.screenshot = value;
  1005. #if defined(HAVE_LIBLO) && !defined(HEADLESS)
  1006. patchUtils::sendScreenshotToRemote(value);
  1007. #endif
  1008. return;
  1009. }
  1010. if (std::strcmp(key, "patch") != 0)
  1011. return;
  1012. if (fAutosavePath.empty())
  1013. return;
  1014. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  1015. DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,);
  1016. const ScopedContext sc(this);
  1017. rack::system::removeRecursively(fAutosavePath);
  1018. rack::system::createDirectories(fAutosavePath);
  1019. static constexpr const char zstdMagic[] = "\x28\xb5\x2f\xfd";
  1020. if (std::memcmp(data.data(), zstdMagic, sizeof(zstdMagic)) != 0)
  1021. {
  1022. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  1023. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  1024. std::fwrite(data.data(), data.size(), 1, f);
  1025. std::fclose(f);
  1026. }
  1027. else
  1028. {
  1029. try {
  1030. rack::system::unarchiveToDirectory(data, fAutosavePath);
  1031. } DISTRHO_SAFE_EXCEPTION_RETURN("setState unarchiveToDirectory",);
  1032. }
  1033. try {
  1034. context->patch->loadAutosave();
  1035. } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",);
  1036. // context->history->setSaved();
  1037. }
  1038. /* --------------------------------------------------------------------------------------------------------
  1039. * Process */
  1040. void activate() override
  1041. {
  1042. context->bufferSize = getBufferSize();
  1043. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1044. fAudioBufferCopy = new float*[DISTRHO_PLUGIN_NUM_INPUTS];
  1045. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1046. fAudioBufferCopy[i] = new float[context->bufferSize];
  1047. #endif
  1048. fNextExpectedFrame = 0;
  1049. }
  1050. void deactivate() override
  1051. {
  1052. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1053. if (fAudioBufferCopy != nullptr)
  1054. {
  1055. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1056. delete[] fAudioBufferCopy[i];
  1057. delete[] fAudioBufferCopy;
  1058. fAudioBufferCopy = nullptr;
  1059. }
  1060. #endif
  1061. }
  1062. void run(const float** const inputs, float** const outputs, const uint32_t frames,
  1063. const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  1064. {
  1065. rack::contextSet(context);
  1066. const bool bypassed = context->bypassed;
  1067. {
  1068. const TimePosition& timePos(getTimePosition());
  1069. const bool reset = timePos.playing && (timePos.frame == 0 || d_isDiffHigherThanLimit(fNextExpectedFrame, timePos.frame, (uint64_t)2));
  1070. context->playing = timePos.playing;
  1071. context->bbtValid = timePos.bbt.valid;
  1072. context->frame = timePos.frame;
  1073. if (timePos.bbt.valid)
  1074. {
  1075. const double samplesPerTick = 60.0 * getSampleRate()
  1076. / timePos.bbt.beatsPerMinute
  1077. / timePos.bbt.ticksPerBeat;
  1078. context->bar = timePos.bbt.bar;
  1079. context->beat = timePos.bbt.beat;
  1080. context->beatsPerBar = timePos.bbt.beatsPerBar;
  1081. context->beatType = timePos.bbt.beatType;
  1082. context->barStartTick = timePos.bbt.barStartTick;
  1083. context->beatsPerMinute = timePos.bbt.beatsPerMinute;
  1084. context->tick = timePos.bbt.tick;
  1085. context->ticksPerBeat = timePos.bbt.ticksPerBeat;
  1086. context->ticksPerClock = timePos.bbt.ticksPerBeat / timePos.bbt.beatType;
  1087. context->ticksPerFrame = 1.0 / samplesPerTick;
  1088. context->tickClock = std::fmod(timePos.bbt.tick, context->ticksPerClock);
  1089. }
  1090. context->reset = reset;
  1091. fNextExpectedFrame = timePos.playing ? timePos.frame + frames : 0;
  1092. }
  1093. // separate buffers, use them
  1094. if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
  1095. {
  1096. context->dataIns = inputs;
  1097. context->dataOuts = outputs;
  1098. }
  1099. // inline processing, use a safe copy
  1100. else
  1101. {
  1102. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1103. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1104. {
  1105. #if CARDINAL_VARIANT_MAIN
  1106. // can be null on main variant
  1107. if (inputs[i] != nullptr)
  1108. #endif
  1109. std::memcpy(fAudioBufferCopy[i], inputs[i], sizeof(float)*frames);
  1110. }
  1111. context->dataIns = fAudioBufferCopy;
  1112. #else
  1113. context->dataIns = nullptr;
  1114. #endif
  1115. context->dataOuts = outputs;
  1116. }
  1117. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  1118. {
  1119. #if CARDINAL_VARIANT_MAIN
  1120. // can be null on main variant
  1121. if (outputs[i] != nullptr)
  1122. #endif
  1123. std::memset(outputs[i], 0, sizeof(float)*frames);
  1124. }
  1125. if (bypassed)
  1126. {
  1127. if (fWasBypassed != bypassed)
  1128. {
  1129. context->midiEvents = bypassMidiEvents;
  1130. context->midiEventCount = 16;
  1131. }
  1132. else
  1133. {
  1134. context->midiEvents = nullptr;
  1135. context->midiEventCount = 0;
  1136. }
  1137. }
  1138. else
  1139. {
  1140. context->midiEvents = midiEvents;
  1141. context->midiEventCount = midiEventCount;
  1142. }
  1143. ++context->processCounter;
  1144. context->engine->stepBlock(frames);
  1145. fWasBypassed = bypassed;
  1146. }
  1147. void sampleRateChanged(const double newSampleRate) override
  1148. {
  1149. rack::contextSet(context);
  1150. rack::settings::sampleRate = newSampleRate;
  1151. context->sampleRate = newSampleRate;
  1152. context->engine->setSampleRate(newSampleRate);
  1153. }
  1154. // -------------------------------------------------------------------------------------------------------
  1155. private:
  1156. /**
  1157. Set our plugin class as non-copyable and add a leak detector just in case.
  1158. */
  1159. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  1160. };
  1161. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  1162. {
  1163. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  1164. }
  1165. /* ------------------------------------------------------------------------------------------------------------
  1166. * Plugin entry point, called by DPF to create a new plugin instance. */
  1167. Plugin* createPlugin()
  1168. {
  1169. return new CardinalPlugin();
  1170. }
  1171. // -----------------------------------------------------------------------------------------------------------
  1172. END_NAMESPACE_DISTRHO