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.

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