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.

1397 lines
48KB

  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, 10);
  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_MAIN
  625. if (index < 8)
  626. {
  627. port.groupId = index / 2;
  628. }
  629. else
  630. {
  631. port.hints = kAudioPortIsCV | kCVPortHasPositiveUnipolarRange | kCVPortHasScaledRange;
  632. index -= 8;
  633. }
  634. #elif CARDINAL_VARIANT_FX || CARDINAL_VARIANT_NATIVE || CARDINAL_VARIANT_SYNTH
  635. if (index < 2)
  636. port.groupId = kPortGroupStereo;
  637. #endif
  638. CardinalBasePlugin::initAudioPort(input, index, port);
  639. }
  640. #if CARDINAL_VARIANT_MAIN
  641. void initPortGroup(const uint32_t index, PortGroup& portGroup) override
  642. {
  643. switch (index)
  644. {
  645. case 0:
  646. portGroup.name = "Audio 1+2";
  647. portGroup.symbol = "audio_1_and_2";
  648. break;
  649. case 1:
  650. portGroup.name = "Audio 3+4";
  651. portGroup.symbol = "audio_3_and_4";
  652. break;
  653. case 2:
  654. portGroup.name = "Audio 5+6";
  655. portGroup.symbol = "audio_5_and_6";
  656. break;
  657. case 3:
  658. portGroup.name = "Audio 7+8";
  659. portGroup.symbol = "audio_7_and_8";
  660. break;
  661. }
  662. }
  663. #endif
  664. void initParameter(const uint32_t index, Parameter& parameter) override
  665. {
  666. if (index < kModuleParameters)
  667. {
  668. parameter.name = "Parameter ";
  669. parameter.name += String(index + 1);
  670. parameter.symbol = "param_";
  671. parameter.symbol += String(index + 1);
  672. parameter.unit = "v";
  673. parameter.hints = kParameterIsAutomatable;
  674. parameter.ranges.def = 0.0f;
  675. parameter.ranges.min = 0.0f;
  676. parameter.ranges.max = 10.0f;
  677. return;
  678. }
  679. if (index == kModuleParameters)
  680. {
  681. parameter.initDesignation(kParameterDesignationBypass);
  682. return;
  683. }
  684. #ifndef HEADLESS
  685. switch (index - kModuleParameters - 1)
  686. {
  687. case kWindowParameterShowTooltips:
  688. parameter.name = "Show tooltips";
  689. parameter.symbol = "tooltips";
  690. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  691. parameter.ranges.def = 1.0f;
  692. parameter.ranges.min = 0.0f;
  693. parameter.ranges.max = 1.0f;
  694. break;
  695. case kWindowParameterCableOpacity:
  696. parameter.name = "Cable opacity";
  697. parameter.symbol = "cableOpacity";
  698. parameter.unit = "%";
  699. parameter.hints = kParameterIsAutomatable;
  700. parameter.ranges.def = 50.0f;
  701. parameter.ranges.min = 0.0f;
  702. parameter.ranges.max = 100.0f;
  703. break;
  704. case kWindowParameterCableTension:
  705. parameter.name = "Cable tension";
  706. parameter.symbol = "cableTension";
  707. parameter.unit = "%";
  708. parameter.hints = kParameterIsAutomatable;
  709. parameter.ranges.def = 75.0f;
  710. parameter.ranges.min = 0.0f;
  711. parameter.ranges.max = 100.0f;
  712. break;
  713. case kWindowParameterRackBrightness:
  714. parameter.name = "Room brightness";
  715. parameter.symbol = "rackBrightness";
  716. parameter.unit = "%";
  717. parameter.hints = kParameterIsAutomatable;
  718. parameter.ranges.def = 100.0f;
  719. parameter.ranges.min = 0.0f;
  720. parameter.ranges.max = 100.0f;
  721. break;
  722. case kWindowParameterHaloBrightness:
  723. parameter.name = "Light Bloom";
  724. parameter.symbol = "haloBrightness";
  725. parameter.unit = "%";
  726. parameter.hints = kParameterIsAutomatable;
  727. parameter.ranges.def = 25.0f;
  728. parameter.ranges.min = 0.0f;
  729. parameter.ranges.max = 100.0f;
  730. break;
  731. case kWindowParameterKnobMode:
  732. parameter.name = "Knob mode";
  733. parameter.symbol = "knobMode";
  734. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  735. parameter.ranges.def = 0.0f;
  736. parameter.ranges.min = 0.0f;
  737. parameter.ranges.max = 2.0f;
  738. parameter.enumValues.count = 3;
  739. parameter.enumValues.restrictedMode = true;
  740. parameter.enumValues.values = new ParameterEnumerationValue[3];
  741. parameter.enumValues.values[0].label = "Linear";
  742. parameter.enumValues.values[0].value = 0.0f;
  743. parameter.enumValues.values[1].label = "Absolute rotary";
  744. parameter.enumValues.values[1].value = 1.0f;
  745. parameter.enumValues.values[2].label = "Relative rotary";
  746. parameter.enumValues.values[2].value = 2.0f;
  747. break;
  748. case kWindowParameterWheelKnobControl:
  749. parameter.name = "Scroll wheel knob control";
  750. parameter.symbol = "knobScroll";
  751. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  752. parameter.ranges.def = 0.0f;
  753. parameter.ranges.min = 0.0f;
  754. parameter.ranges.max = 1.0f;
  755. break;
  756. case kWindowParameterWheelSensitivity:
  757. parameter.name = "Scroll wheel knob sensitivity";
  758. parameter.symbol = "knobScrollSensitivity";
  759. parameter.hints = kParameterIsAutomatable|kParameterIsLogarithmic;
  760. parameter.ranges.def = 1.0f;
  761. parameter.ranges.min = 0.1f;
  762. parameter.ranges.max = 10.0f;
  763. break;
  764. case kWindowParameterLockModulePositions:
  765. parameter.name = "Lock module positions";
  766. parameter.symbol = "lockModules";
  767. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  768. parameter.ranges.def = 0.0f;
  769. parameter.ranges.min = 0.0f;
  770. parameter.ranges.max = 1.0f;
  771. break;
  772. case kWindowParameterUpdateRateLimit:
  773. parameter.name = "Update rate limit";
  774. parameter.symbol = "rateLimit";
  775. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  776. parameter.ranges.def = 0.0f;
  777. parameter.ranges.min = 0.0f;
  778. parameter.ranges.max = 2.0f;
  779. parameter.enumValues.count = 3;
  780. parameter.enumValues.restrictedMode = true;
  781. parameter.enumValues.values = new ParameterEnumerationValue[3];
  782. parameter.enumValues.values[0].label = "None";
  783. parameter.enumValues.values[0].value = 0.0f;
  784. parameter.enumValues.values[1].label = "2x";
  785. parameter.enumValues.values[1].value = 1.0f;
  786. parameter.enumValues.values[2].label = "4x";
  787. parameter.enumValues.values[2].value = 2.0f;
  788. break;
  789. case kWindowParameterBrowserSort:
  790. parameter.name = "Browser sort";
  791. parameter.symbol = "browserSort";
  792. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  793. parameter.ranges.def = 3.0f;
  794. parameter.ranges.min = 0.0f;
  795. parameter.ranges.max = 5.0f;
  796. parameter.enumValues.count = 6;
  797. parameter.enumValues.restrictedMode = true;
  798. parameter.enumValues.values = new ParameterEnumerationValue[6];
  799. parameter.enumValues.values[0].label = "Updated";
  800. parameter.enumValues.values[0].value = 0.0f;
  801. parameter.enumValues.values[1].label = "Last used";
  802. parameter.enumValues.values[1].value = 1.0f;
  803. parameter.enumValues.values[2].label = "Most used";
  804. parameter.enumValues.values[2].value = 2.0f;
  805. parameter.enumValues.values[3].label = "Brand";
  806. parameter.enumValues.values[3].value = 3.0f;
  807. parameter.enumValues.values[4].label = "Name";
  808. parameter.enumValues.values[4].value = 4.0f;
  809. parameter.enumValues.values[5].label = "Random";
  810. parameter.enumValues.values[5].value = 5.0f;
  811. break;
  812. case kWindowParameterBrowserZoom:
  813. parameter.name = "Browser zoom";
  814. parameter.symbol = "browserZoom";
  815. parameter.hints = kParameterIsAutomatable;
  816. parameter.unit = "%";
  817. parameter.ranges.def = 50.0f;
  818. parameter.ranges.min = 25.0f;
  819. parameter.ranges.max = 200.0f;
  820. parameter.enumValues.count = 7;
  821. parameter.enumValues.restrictedMode = true;
  822. parameter.enumValues.values = new ParameterEnumerationValue[7];
  823. parameter.enumValues.values[0].label = "25";
  824. parameter.enumValues.values[0].value = 25.0f;
  825. parameter.enumValues.values[1].label = "35";
  826. parameter.enumValues.values[1].value = 35.0f;
  827. parameter.enumValues.values[2].label = "50";
  828. parameter.enumValues.values[2].value = 50.0f;
  829. parameter.enumValues.values[3].label = "71";
  830. parameter.enumValues.values[3].value = 71.0f;
  831. parameter.enumValues.values[4].label = "100";
  832. parameter.enumValues.values[4].value = 100.0f;
  833. parameter.enumValues.values[5].label = "141";
  834. parameter.enumValues.values[5].value = 141.0f;
  835. parameter.enumValues.values[6].label = "200";
  836. parameter.enumValues.values[6].value = 200.0f;
  837. break;
  838. case kWindowParameterInvertZoom:
  839. parameter.name = "Invert zoom";
  840. parameter.symbol = "invertZoom";
  841. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  842. parameter.ranges.def = 0.0f;
  843. parameter.ranges.min = 0.0f;
  844. parameter.ranges.max = 1.0f;
  845. break;
  846. case kWindowParameterSqueezeModulePositions:
  847. parameter.name = "Auto-squeeze module positions";
  848. parameter.symbol = "squeezeModules";
  849. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  850. parameter.ranges.def = 1.0f;
  851. parameter.ranges.min = 0.0f;
  852. parameter.ranges.max = 1.0f;
  853. break;
  854. }
  855. #endif
  856. }
  857. void initState(const uint32_t index, State& state) override
  858. {
  859. switch (index)
  860. {
  861. case 0:
  862. state.hints = kStateIsBase64Blob | kStateIsOnlyForDSP;
  863. state.key = "patch";
  864. state.label = "Patch";
  865. break;
  866. case 1:
  867. state.hints = kStateIsHostReadable | kStateIsBase64Blob;
  868. state.key = "screenshot";
  869. state.label = "Screenshot";
  870. break;
  871. case 2:
  872. state.hints = kStateIsHostWritable;
  873. state.key = "comment";
  874. state.label = "Comment";
  875. break;
  876. case 3:
  877. state.hints = kStateIsOnlyForUI;
  878. state.key = "moduleInfos";
  879. state.label = "moduleInfos";
  880. break;
  881. case 4:
  882. state.hints = kStateIsOnlyForUI;
  883. state.key = "windowSize";
  884. state.label = "Window size";
  885. break;
  886. }
  887. }
  888. /* --------------------------------------------------------------------------------------------------------
  889. * Internal data */
  890. float getParameterValue(uint32_t index) const override
  891. {
  892. // host mapped parameters
  893. if (index < kModuleParameters)
  894. return context->parameters[index];
  895. // bypass
  896. if (index == kModuleParameters)
  897. return context->bypassed ? 1.0f : 0.0f;
  898. #ifndef HEADLESS
  899. // window related parameters
  900. index -= kModuleParameters + 1;
  901. if (index < kWindowParameterCount)
  902. return fWindowParameters[index];
  903. #endif
  904. return 0.0f;
  905. }
  906. void setParameterValue(uint32_t index, float value) override
  907. {
  908. // host mapped parameters
  909. if (index < kModuleParameters)
  910. {
  911. context->parameters[index] = value;
  912. return;
  913. }
  914. // bypass
  915. if (index == kModuleParameters)
  916. {
  917. context->bypassed = value > 0.5f;
  918. return;
  919. }
  920. #ifndef HEADLESS
  921. // window related parameters
  922. index -= kModuleParameters + 1;
  923. if (index < kWindowParameterCount)
  924. {
  925. fWindowParameters[index] = value;
  926. return;
  927. }
  928. #endif
  929. }
  930. String getState(const char* const key) const override
  931. {
  932. #ifndef HEADLESS
  933. if (std::strcmp(key, "moduleInfos") == 0)
  934. {
  935. json_t* const rootJ = json_object();
  936. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, String());
  937. for (const auto& pluginPair : rack::settings::moduleInfos)
  938. {
  939. json_t* const pluginJ = json_object();
  940. DISTRHO_SAFE_ASSERT_CONTINUE(pluginJ != nullptr);
  941. for (const auto& modulePair : pluginPair.second)
  942. {
  943. json_t* const moduleJ = json_object();
  944. DISTRHO_SAFE_ASSERT_CONTINUE(moduleJ != nullptr);
  945. const rack::settings::ModuleInfo& m(modulePair.second);
  946. // To make setting.json smaller, only set properties if not default values.
  947. if (m.favorite)
  948. json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
  949. if (m.added > 0)
  950. json_object_set_new(moduleJ, "added", json_integer(m.added));
  951. if (std::isfinite(m.lastAdded))
  952. json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
  953. if (json_object_size(moduleJ))
  954. json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
  955. else
  956. json_decref(moduleJ);
  957. }
  958. if (json_object_size(pluginJ))
  959. json_object_set_new(rootJ, pluginPair.first.c_str(), pluginJ);
  960. else
  961. json_decref(pluginJ);
  962. }
  963. const String info(json_dumps(rootJ, JSON_COMPACT), false);
  964. json_decref(rootJ);
  965. return info;
  966. }
  967. if (std::strcmp(key, "windowSize") == 0)
  968. return fState.windowSize;
  969. #endif
  970. if (std::strcmp(key, "comment") == 0)
  971. return fState.comment;
  972. if (std::strcmp(key, "screenshot") == 0)
  973. return fState.screenshot;
  974. if (std::strcmp(key, "patch") != 0)
  975. return String();
  976. if (fAutosavePath.empty())
  977. return String();
  978. std::vector<uint8_t> data;
  979. {
  980. const ScopedContext sc(this);
  981. context->engine->prepareSave();
  982. context->patch->saveAutosave();
  983. context->patch->cleanAutosave();
  984. // context->history->setSaved();
  985. try {
  986. data = rack::system::archiveDirectory(fAutosavePath, 1);
  987. } DISTRHO_SAFE_EXCEPTION_RETURN("getState archiveDirectory", String());
  988. }
  989. return String::asBase64(data.data(), data.size());
  990. }
  991. void setState(const char* const key, const char* const value) override
  992. {
  993. #ifndef HEADLESS
  994. if (std::strcmp(key, "moduleInfos") == 0)
  995. {
  996. json_error_t error;
  997. json_t* const rootJ = json_loads(value, 0, &error);
  998. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr,);
  999. const char* pluginSlug;
  1000. json_t* pluginJ;
  1001. json_object_foreach(rootJ, pluginSlug, pluginJ)
  1002. {
  1003. const char* moduleSlug;
  1004. json_t* moduleJ;
  1005. json_object_foreach(pluginJ, moduleSlug, moduleJ)
  1006. {
  1007. rack::settings::ModuleInfo m;
  1008. if (json_t* const favoriteJ = json_object_get(moduleJ, "favorite"))
  1009. m.favorite = json_boolean_value(favoriteJ);
  1010. if (json_t* const addedJ = json_object_get(moduleJ, "added"))
  1011. m.added = json_integer_value(addedJ);
  1012. if (json_t* const lastAddedJ = json_object_get(moduleJ, "lastAdded"))
  1013. m.lastAdded = json_number_value(lastAddedJ);
  1014. rack::settings::moduleInfos[pluginSlug][moduleSlug] = m;
  1015. }
  1016. }
  1017. json_decref(rootJ);
  1018. return;
  1019. }
  1020. if (std::strcmp(key, "windowSize") == 0)
  1021. {
  1022. fState.windowSize = value;
  1023. return;
  1024. }
  1025. #endif
  1026. if (std::strcmp(key, "comment") == 0)
  1027. {
  1028. fState.comment = value;
  1029. return;
  1030. }
  1031. if (std::strcmp(key, "screenshot") == 0)
  1032. {
  1033. fState.screenshot = value;
  1034. #if defined(HAVE_LIBLO) && !defined(HEADLESS)
  1035. patchUtils::sendScreenshotToRemote(value);
  1036. #endif
  1037. return;
  1038. }
  1039. if (std::strcmp(key, "patch") != 0)
  1040. return;
  1041. if (fAutosavePath.empty())
  1042. return;
  1043. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  1044. DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,);
  1045. rack::system::removeRecursively(fAutosavePath);
  1046. rack::system::createDirectories(fAutosavePath);
  1047. static constexpr const char zstdMagic[] = "\x28\xb5\x2f\xfd";
  1048. if (std::memcmp(data.data(), zstdMagic, sizeof(zstdMagic)) != 0)
  1049. {
  1050. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  1051. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  1052. std::fwrite(data.data(), data.size(), 1, f);
  1053. std::fclose(f);
  1054. }
  1055. else
  1056. {
  1057. try {
  1058. rack::system::unarchiveToDirectory(data, fAutosavePath);
  1059. } DISTRHO_SAFE_EXCEPTION_RETURN("setState unarchiveToDirectory",);
  1060. }
  1061. const ScopedContext sc(this);
  1062. try {
  1063. context->patch->loadAutosave();
  1064. } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",);
  1065. // context->history->setSaved();
  1066. }
  1067. /* --------------------------------------------------------------------------------------------------------
  1068. * Process */
  1069. void activate() override
  1070. {
  1071. context->bufferSize = getBufferSize();
  1072. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1073. fAudioBufferCopy = new float*[DISTRHO_PLUGIN_NUM_INPUTS];
  1074. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1075. fAudioBufferCopy[i] = new float[context->bufferSize];
  1076. #endif
  1077. fNextExpectedFrame = 0;
  1078. }
  1079. void deactivate() override
  1080. {
  1081. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1082. if (fAudioBufferCopy != nullptr)
  1083. {
  1084. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1085. delete[] fAudioBufferCopy[i];
  1086. delete[] fAudioBufferCopy;
  1087. fAudioBufferCopy = nullptr;
  1088. }
  1089. #endif
  1090. }
  1091. void run(const float** const inputs, float** const outputs, const uint32_t frames,
  1092. const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  1093. {
  1094. rack::contextSet(context);
  1095. const bool bypassed = context->bypassed;
  1096. {
  1097. const TimePosition& timePos(getTimePosition());
  1098. bool reset = timePos.playing && (timePos.frame == 0 || d_isDiffHigherThanLimit(fNextExpectedFrame, timePos.frame, (uint64_t)2));
  1099. // ignore hosts which cannot supply time frame position
  1100. if (context->playing == timePos.playing && timePos.frame == 0 && context->frame == 0)
  1101. reset = false;
  1102. context->playing = timePos.playing;
  1103. context->bbtValid = timePos.bbt.valid;
  1104. context->frame = timePos.frame;
  1105. if (timePos.bbt.valid)
  1106. {
  1107. const double samplesPerTick = 60.0 * getSampleRate()
  1108. / timePos.bbt.beatsPerMinute
  1109. / timePos.bbt.ticksPerBeat;
  1110. context->bar = timePos.bbt.bar;
  1111. context->beat = timePos.bbt.beat;
  1112. context->beatsPerBar = timePos.bbt.beatsPerBar;
  1113. context->beatType = timePos.bbt.beatType;
  1114. context->barStartTick = timePos.bbt.barStartTick;
  1115. context->beatsPerMinute = timePos.bbt.beatsPerMinute;
  1116. context->tick = timePos.bbt.tick;
  1117. context->ticksPerBeat = timePos.bbt.ticksPerBeat;
  1118. context->ticksPerClock = timePos.bbt.ticksPerBeat / timePos.bbt.beatType;
  1119. context->ticksPerFrame = 1.0 / samplesPerTick;
  1120. context->tickClock = std::fmod(timePos.bbt.tick, context->ticksPerClock);
  1121. }
  1122. context->reset = reset;
  1123. fNextExpectedFrame = timePos.playing ? timePos.frame + frames : 0;
  1124. }
  1125. // separate buffers, use them
  1126. if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
  1127. {
  1128. context->dataIns = inputs;
  1129. context->dataOuts = outputs;
  1130. }
  1131. // inline processing, use a safe copy
  1132. else
  1133. {
  1134. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1135. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1136. {
  1137. #if CARDINAL_VARIANT_MAIN
  1138. // can be null on main variant
  1139. if (inputs[i] != nullptr)
  1140. #endif
  1141. std::memcpy(fAudioBufferCopy[i], inputs[i], sizeof(float)*frames);
  1142. }
  1143. context->dataIns = fAudioBufferCopy;
  1144. #else
  1145. context->dataIns = nullptr;
  1146. #endif
  1147. context->dataOuts = outputs;
  1148. }
  1149. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  1150. {
  1151. #if CARDINAL_VARIANT_MAIN
  1152. // can be null on main variant
  1153. if (outputs[i] != nullptr)
  1154. #endif
  1155. std::memset(outputs[i], 0, sizeof(float)*frames);
  1156. }
  1157. if (bypassed)
  1158. {
  1159. if (fWasBypassed != bypassed)
  1160. {
  1161. context->midiEvents = bypassMidiEvents;
  1162. context->midiEventCount = 16;
  1163. }
  1164. else
  1165. {
  1166. context->midiEvents = nullptr;
  1167. context->midiEventCount = 0;
  1168. }
  1169. }
  1170. else
  1171. {
  1172. context->midiEvents = midiEvents;
  1173. context->midiEventCount = midiEventCount;
  1174. }
  1175. ++context->processCounter;
  1176. context->engine->stepBlock(frames);
  1177. fWasBypassed = bypassed;
  1178. }
  1179. void sampleRateChanged(const double newSampleRate) override
  1180. {
  1181. rack::contextSet(context);
  1182. rack::settings::sampleRate = newSampleRate;
  1183. context->sampleRate = newSampleRate;
  1184. context->engine->setSampleRate(newSampleRate);
  1185. }
  1186. // -------------------------------------------------------------------------------------------------------
  1187. private:
  1188. /**
  1189. Set our plugin class as non-copyable and add a leak detector just in case.
  1190. */
  1191. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  1192. };
  1193. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  1194. {
  1195. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  1196. }
  1197. /* ------------------------------------------------------------------------------------------------------------
  1198. * Plugin entry point, called by DPF to create a new plugin instance. */
  1199. Plugin* createPlugin()
  1200. {
  1201. return new CardinalPlugin();
  1202. }
  1203. // -----------------------------------------------------------------------------------------------------------
  1204. END_NAMESPACE_DISTRHO