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.

1393 lines
55KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2023 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 <library.hpp>
  18. #include <midi.hpp>
  19. #include <patch.hpp>
  20. #include <plugin.hpp>
  21. #include <random.hpp>
  22. #include <settings.hpp>
  23. #include <system.hpp>
  24. #include <app/Scene.hpp>
  25. #include <engine/Engine.hpp>
  26. #include <ui/common.hpp>
  27. #include <widget/Widget.hpp>
  28. #include <window/Window.hpp>
  29. #ifdef NDEBUG
  30. # undef DEBUG
  31. #endif
  32. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  33. # include <lo/lo.h>
  34. # include "extra/Thread.hpp"
  35. #endif
  36. #include <cfloat>
  37. #include <list>
  38. #include "CardinalCommon.hpp"
  39. #include "DistrhoPluginUtils.hpp"
  40. #include "PluginContext.hpp"
  41. #include "extra/Base64.hpp"
  42. #include "extra/ScopedDenormalDisable.hpp"
  43. #include "extra/ScopedSafeLocale.hpp"
  44. #ifdef DISTRHO_OS_WASM
  45. # include <emscripten/emscripten.h>
  46. #else
  47. # include "extra/SharedResourcePointer.hpp"
  48. #endif
  49. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  50. # include "extra/ScopedValueSetter.hpp"
  51. #endif
  52. extern const std::string CARDINAL_VERSION;
  53. namespace rack {
  54. #if (CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS) || defined(HEADLESS)
  55. namespace app {
  56. rack::widget::Widget* createMenuBar() { return new rack::widget::Widget; }
  57. }
  58. #endif
  59. #ifdef DISTRHO_OS_WASM
  60. namespace asset {
  61. std::string patchesPath();
  62. }
  63. #endif
  64. namespace engine {
  65. void Engine_setAboutToClose(Engine*);
  66. }
  67. }
  68. START_NAMESPACE_DISTRHO
  69. template<typename T>
  70. static inline
  71. bool d_isDiffHigherThanLimit(const T& v1, const T& v2, const T& limit)
  72. {
  73. return v1 != v2 ? (v1 > v2 ? v1 - v2 : v2 - v1) > limit : false;
  74. }
  75. #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  76. const char* UI::getBundlePath() const noexcept { return nullptr; }
  77. void UI::setState(const char*, const char*) {}
  78. #endif
  79. // -----------------------------------------------------------------------------------------------------------
  80. #ifdef DISTRHO_OS_WASM
  81. static char* getPatchFileEncodedInURL() {
  82. return static_cast<char*>(EM_ASM_PTR({
  83. var searchParams = new URLSearchParams(window.location.search);
  84. var patch = searchParams.get('patch');
  85. if (!patch)
  86. return null;
  87. var length = lengthBytesUTF8(patch) + 1;
  88. var str = _malloc(length);
  89. stringToUTF8(patch, str, length);
  90. return str;
  91. }));
  92. };
  93. static char* getPatchRemoteURL() {
  94. return static_cast<char*>(EM_ASM_PTR({
  95. var searchParams = new URLSearchParams(window.location.search);
  96. var patch = searchParams.get('patchurl');
  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* getPatchStorageSlug() {
  106. return static_cast<char*>(EM_ASM_PTR({
  107. var searchParams = new URLSearchParams(window.location.search);
  108. var patch = searchParams.get('patchstorage');
  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. #endif
  118. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  119. float RackKnobModeToFloat(const rack::settings::KnobMode knobMode) noexcept
  120. {
  121. switch (knobMode)
  122. {
  123. case rack::settings::KNOB_MODE_LINEAR:
  124. return 0.f;
  125. case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
  126. return 1.f;
  127. case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
  128. return 2.f;
  129. // unused in Rack
  130. case rack::settings::KNOB_MODE_SCALED_LINEAR:
  131. break;
  132. }
  133. return 0.f;
  134. }
  135. #endif
  136. // -----------------------------------------------------------------------------------------------------------
  137. struct ScopedContext {
  138. ScopedContext(const CardinalBasePlugin* const plugin)
  139. {
  140. rack::contextSet(plugin->context);
  141. }
  142. ~ScopedContext()
  143. {
  144. rack::contextSet(nullptr);
  145. }
  146. };
  147. // -----------------------------------------------------------------------------------------------------------
  148. class CardinalPlugin : public CardinalBasePlugin
  149. {
  150. #ifdef DISTRHO_OS_WASM
  151. ScopedPointer<Initializer> fInitializer;
  152. #else
  153. SharedResourcePointer<Initializer> fInitializer;
  154. #endif
  155. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  156. /* If host audio ins == outs we can get issues for inplace processing.
  157. * So allocate a float array that will serve as safe copy for those cases.
  158. */
  159. float** fAudioBufferCopy;
  160. #endif
  161. std::string fAutosavePath;
  162. uint64_t fNextExpectedFrame;
  163. struct {
  164. String comment;
  165. String screenshot;
  166. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  167. String windowSize;
  168. #endif
  169. } fState;
  170. // bypass handling
  171. bool fWasBypassed;
  172. MidiEvent bypassMidiEvents[16];
  173. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  174. // real values, not VCV interpreted ones
  175. float fWindowParameters[kWindowParameterCount];
  176. #endif
  177. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  178. float fMiniReportValues[kCardinalParameterCountAtMini - kCardinalParameterStartMini];
  179. #endif
  180. public:
  181. CardinalPlugin()
  182. : CardinalBasePlugin(kCardinalParameterCount, 0, kCardinalStateCount),
  183. #ifdef DISTRHO_OS_WASM
  184. fInitializer(new Initializer(this, static_cast<const CardinalBaseUI*>(nullptr))),
  185. #else
  186. fInitializer(this, static_cast<const CardinalBaseUI*>(nullptr)),
  187. #endif
  188. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  189. fAudioBufferCopy(nullptr),
  190. #endif
  191. fNextExpectedFrame(0),
  192. fWasBypassed(false)
  193. {
  194. // check if first time loading a real instance
  195. if (!fInitializer->shouldSaveSettings && !isDummyInstance())
  196. fInitializer->loadSettings(true);
  197. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  198. fWindowParameters[kWindowParameterShowTooltips] = rack::settings::tooltips ? 1.f : 0.f;
  199. fWindowParameters[kWindowParameterCableOpacity] = std::min(100.f, std::max(0.f, rack::settings::cableOpacity * 100));
  200. fWindowParameters[kWindowParameterCableTension] = std::min(100.f, std::max(0.f, rack::settings::cableTension * 100));
  201. fWindowParameters[kWindowParameterRackBrightness] = std::min(100.f, std::max(0.f, rack::settings::rackBrightness * 100));
  202. fWindowParameters[kWindowParameterHaloBrightness] = std::min(100.f, std::max(0.f, rack::settings::haloBrightness * 100));
  203. fWindowParameters[kWindowParameterKnobMode] = RackKnobModeToFloat(rack::settings::knobMode);
  204. fWindowParameters[kWindowParameterWheelKnobControl] = rack::settings::knobScroll ? 1.f : 0.f;
  205. fWindowParameters[kWindowParameterWheelSensitivity] = std::min(10.f, std::max(0.1f, rack::settings::knobScrollSensitivity * 1000));
  206. fWindowParameters[kWindowParameterLockModulePositions] = rack::settings::lockModules ? 1.f : 0.f;
  207. fWindowParameters[kWindowParameterBrowserSort] = std::min(rack::settings::BROWSER_SORT_RANDOM,
  208. std::max(rack::settings::BROWSER_SORT_UPDATED,
  209. rack::settings::browserSort));
  210. fWindowParameters[kWindowParameterBrowserZoom] = std::min(200.f, std::max(25.f, std::pow(2.f, rack::settings::browserZoom) * 100.0f));
  211. fWindowParameters[kWindowParameterInvertZoom] = rack::settings::invertZoom ? 1.f : 0.f;
  212. fWindowParameters[kWindowParameterSqueezeModulePositions] = rack::settings::squeezeModules ? 1.f : 0.f;
  213. // not saved
  214. fWindowParameters[kWindowParameterUpdateRateLimit] = 0.0f;
  215. #endif
  216. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  217. std::memset(fMiniReportValues, 0, sizeof(fMiniReportValues));
  218. fMiniReportValues[kCardinalParameterMiniTimeBar - kCardinalParameterStartMini] = 1;
  219. fMiniReportValues[kCardinalParameterMiniTimeBeat - kCardinalParameterStartMini] = 1;
  220. fMiniReportValues[kCardinalParameterMiniTimeBeatsPerBar - kCardinalParameterStartMini] = 4;
  221. fMiniReportValues[kCardinalParameterMiniTimeBeatType - kCardinalParameterStartMini] = 4;
  222. fMiniReportValues[kCardinalParameterMiniTimeBeatsPerMinute - kCardinalParameterStartMini] = 120;
  223. #endif
  224. // create unique temporary path for this instance
  225. try {
  226. char uidBuf[24];
  227. const std::string tmp = rack::system::getTempDirectory();
  228. for (int i=1;; ++i)
  229. {
  230. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  231. const std::string trypath = rack::system::join(tmp, uidBuf);
  232. if (! rack::system::exists(trypath))
  233. {
  234. if (rack::system::createDirectories(trypath))
  235. fAutosavePath = trypath;
  236. break;
  237. }
  238. }
  239. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  240. // initialize midi events used when entering bypassed state
  241. std::memset(bypassMidiEvents, 0, sizeof(bypassMidiEvents));
  242. for (uint8_t i=0; i<16; ++i)
  243. {
  244. bypassMidiEvents[i].size = 3;
  245. bypassMidiEvents[i].data[0] = 0xB0 + i;
  246. bypassMidiEvents[i].data[1] = 0x7B;
  247. }
  248. const float sampleRate = getSampleRate();
  249. rack::settings::sampleRate = sampleRate;
  250. context->bufferSize = getBufferSize();
  251. context->sampleRate = sampleRate;
  252. const ScopedContext sc(this);
  253. context->engine = new rack::engine::Engine;
  254. context->engine->setSampleRate(sampleRate);
  255. context->history = new rack::history::State;
  256. context->patch = new rack::patch::Manager;
  257. context->patch->autosavePath = fAutosavePath;
  258. context->patch->templatePath = fInitializer->templatePath;
  259. context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath;
  260. context->event = new rack::widget::EventState;
  261. context->scene = new rack::app::Scene;
  262. context->event->rootWidget = context->scene;
  263. if (! isDummyInstance())
  264. context->window = new rack::window::Window;
  265. #ifdef DISTRHO_OS_WASM
  266. if ((rack::patchStorageSlug = getPatchStorageSlug()) == nullptr &&
  267. (rack::patchRemoteURL = getPatchRemoteURL()) == nullptr &&
  268. (rack::patchFromURL = getPatchFileEncodedInURL()) == nullptr)
  269. #endif
  270. {
  271. context->patch->loadTemplate();
  272. context->scene->rackScroll->reset();
  273. }
  274. #ifdef DISTRHO_OS_WASM
  275. // switch factory template to regular one after first load
  276. #if CARDINAL_VARIANT_MINI
  277. context->patch->factoryTemplatePath = rack::system::join(rack::asset::patchesPath(), "templates/mini.vcv");
  278. #else
  279. context->patch->factoryTemplatePath = rack::system::join(rack::asset::patchesPath(), "templates/main.vcv");
  280. #endif
  281. #endif
  282. #ifdef CARDINAL_INIT_OSC_THREAD
  283. fInitializer->remotePluginInstance = this;
  284. #endif
  285. }
  286. ~CardinalPlugin() override
  287. {
  288. #ifdef CARDINAL_INIT_OSC_THREAD
  289. if (fInitializer->remotePluginInstance == this)
  290. fInitializer->remotePluginInstance = nullptr;
  291. #endif
  292. {
  293. const ScopedContext sc(this);
  294. context->patch->clear();
  295. // do a little dance to prevent context scene deletion from saving to temp dir
  296. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  297. const ScopedValueSetter<bool> svs(rack::settings::headless, true);
  298. #endif
  299. Engine_setAboutToClose(context->engine);
  300. delete context;
  301. }
  302. if (! fAutosavePath.empty())
  303. rack::system::removeRecursively(fAutosavePath);
  304. }
  305. CardinalPluginContext* getRackContext() const noexcept
  306. {
  307. return context;
  308. }
  309. protected:
  310. /* --------------------------------------------------------------------------------------------------------
  311. * Information */
  312. const char* getLabel() const override
  313. {
  314. return DISTRHO_PLUGIN_LABEL;
  315. }
  316. const char* getDescription() const override
  317. {
  318. return ""
  319. "Cardinal is a free and open-source virtual modular synthesizer plugin.\n"
  320. "It is based on the popular VCV Rack but with a focus on being a fully self-contained plugin version.\n"
  321. "It is not an official VCV project, and it is not affiliated with it in any way.\n"
  322. "\n"
  323. "Cardinal contains Rack, some 3rd-party modules and a few internal utilities.\n"
  324. "It does not load external modules and does not connect to the official Rack library/store.\n";
  325. }
  326. const char* getMaker() const override
  327. {
  328. return "DISTRHO";
  329. }
  330. const char* getHomePage() const override
  331. {
  332. return "https://github.com/DISTRHO/Cardinal";
  333. }
  334. const char* getLicense() const override
  335. {
  336. return "GPLv3+";
  337. }
  338. uint32_t getVersion() const override
  339. {
  340. return d_version(0, 23, 5);
  341. }
  342. int64_t getUniqueId() const override
  343. {
  344. #if CARDINAL_VARIANT_MAIN || CARDINAL_VARIANT_NATIVE
  345. return d_cconst('d', 'C', 'd', 'n');
  346. #elif CARDINAL_VARIANT_MINI
  347. return d_cconst('d', 'C', 'd', 'M');
  348. #elif CARDINAL_VARIANT_FX
  349. return d_cconst('d', 'C', 'n', 'F');
  350. #elif CARDINAL_VARIANT_SYNTH
  351. return d_cconst('d', 'C', 'n', 'S');
  352. #else
  353. #error cardinal variant not set
  354. #endif
  355. }
  356. /* --------------------------------------------------------------------------------------------------------
  357. * Init */
  358. void initAudioPort(const bool input, uint32_t index, AudioPort& port) override
  359. {
  360. #if CARDINAL_VARIANT_MAIN || CARDINAL_VARIANT_MINI
  361. static_assert(CARDINAL_NUM_AUDIO_INPUTS == CARDINAL_NUM_AUDIO_OUTPUTS, "inputs == outputs");
  362. if (index < CARDINAL_NUM_AUDIO_INPUTS)
  363. {
  364. #if CARDINAL_VARIANT_MINI
  365. port.groupId = kPortGroupStereo;
  366. #else
  367. port.groupId = index / 2;
  368. #endif
  369. }
  370. else
  371. {
  372. port.hints = kAudioPortIsCV | kCVPortHasPositiveUnipolarRange | kCVPortHasScaledRange | kCVPortIsOptional;
  373. index -= CARDINAL_NUM_AUDIO_INPUTS;
  374. }
  375. #elif CARDINAL_VARIANT_NATIVE || CARDINAL_VARIANT_FX || CARDINAL_VARIANT_SYNTH
  376. if (index < 2)
  377. port.groupId = kPortGroupStereo;
  378. #endif
  379. CardinalBasePlugin::initAudioPort(input, index, port);
  380. }
  381. #if CARDINAL_VARIANT_MAIN
  382. void initPortGroup(const uint32_t index, PortGroup& portGroup) override
  383. {
  384. switch (index)
  385. {
  386. case 0:
  387. portGroup.name = "Audio 1+2";
  388. portGroup.symbol = "audio_1_and_2";
  389. break;
  390. case 1:
  391. portGroup.name = "Audio 3+4";
  392. portGroup.symbol = "audio_3_and_4";
  393. break;
  394. case 2:
  395. portGroup.name = "Audio 5+6";
  396. portGroup.symbol = "audio_5_and_6";
  397. break;
  398. case 3:
  399. portGroup.name = "Audio 7+8";
  400. portGroup.symbol = "audio_7_and_8";
  401. break;
  402. }
  403. }
  404. #endif
  405. void initParameter(const uint32_t index, Parameter& parameter) override
  406. {
  407. if (index < kCardinalParameterCountAtModules)
  408. {
  409. parameter.name = "Parameter ";
  410. parameter.name += String(index + 1);
  411. parameter.symbol = "param_";
  412. parameter.symbol += String(index + 1);
  413. parameter.unit = "v";
  414. parameter.hints = kParameterIsAutomatable;
  415. parameter.ranges.def = 0.0f;
  416. parameter.ranges.min = 0.0f;
  417. parameter.ranges.max = 10.0f;
  418. return;
  419. }
  420. if (index == kCardinalParameterBypass)
  421. {
  422. parameter.initDesignation(kParameterDesignationBypass);
  423. return;
  424. }
  425. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  426. if (index < kCardinalParameterCountAtWindow)
  427. {
  428. switch (index - kCardinalParameterStartWindow)
  429. {
  430. case kWindowParameterShowTooltips:
  431. parameter.name = "Show tooltips";
  432. parameter.symbol = "tooltips";
  433. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  434. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  435. parameter.hints |= kParameterIsHidden;
  436. #endif
  437. parameter.ranges.def = rack::settings::tooltips ? 1.f : 0.f;
  438. parameter.ranges.min = 0.0f;
  439. parameter.ranges.max = 1.0f;
  440. break;
  441. case kWindowParameterCableOpacity:
  442. parameter.name = "Cable opacity";
  443. parameter.symbol = "cableOpacity";
  444. parameter.unit = "%";
  445. parameter.hints = kParameterIsAutomatable;
  446. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  447. parameter.hints |= kParameterIsHidden;
  448. #endif
  449. parameter.ranges.def = std::min(100.f, std::max(0.f, rack::settings::cableOpacity * 100));
  450. parameter.ranges.min = 0.0f;
  451. parameter.ranges.max = 100.0f;
  452. break;
  453. case kWindowParameterCableTension:
  454. parameter.name = "Cable tension";
  455. parameter.symbol = "cableTension";
  456. parameter.unit = "%";
  457. parameter.hints = kParameterIsAutomatable;
  458. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  459. parameter.hints |= kParameterIsHidden;
  460. #endif
  461. parameter.ranges.def = std::min(100.f, std::max(0.f, rack::settings::cableTension * 100));
  462. parameter.ranges.min = 0.0f;
  463. parameter.ranges.max = 100.0f;
  464. break;
  465. case kWindowParameterRackBrightness:
  466. parameter.name = "Room brightness";
  467. parameter.symbol = "rackBrightness";
  468. parameter.unit = "%";
  469. parameter.hints = kParameterIsAutomatable;
  470. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  471. parameter.hints |= kParameterIsHidden;
  472. #endif
  473. parameter.ranges.def = std::min(100.f, std::max(0.f, rack::settings::rackBrightness * 100));
  474. parameter.ranges.min = 0.0f;
  475. parameter.ranges.max = 100.0f;
  476. break;
  477. case kWindowParameterHaloBrightness:
  478. parameter.name = "Light Bloom";
  479. parameter.symbol = "haloBrightness";
  480. parameter.unit = "%";
  481. parameter.hints = kParameterIsAutomatable;
  482. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  483. parameter.hints |= kParameterIsHidden;
  484. #endif
  485. parameter.ranges.def = std::min(100.f, std::max(0.f, rack::settings::haloBrightness * 100));
  486. parameter.ranges.min = 0.0f;
  487. parameter.ranges.max = 100.0f;
  488. break;
  489. case kWindowParameterKnobMode:
  490. parameter.name = "Knob mode";
  491. parameter.symbol = "knobMode";
  492. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  493. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  494. parameter.hints |= kParameterIsHidden;
  495. #endif
  496. parameter.ranges.def = RackKnobModeToFloat(rack::settings::knobMode);
  497. parameter.ranges.min = 0.0f;
  498. parameter.ranges.max = 2.0f;
  499. parameter.enumValues.count = 3;
  500. parameter.enumValues.restrictedMode = true;
  501. parameter.enumValues.values = new ParameterEnumerationValue[3];
  502. parameter.enumValues.values[0].label = "Linear";
  503. parameter.enumValues.values[0].value = 0.0f;
  504. parameter.enumValues.values[1].label = "Absolute rotary";
  505. parameter.enumValues.values[1].value = 1.0f;
  506. parameter.enumValues.values[2].label = "Relative rotary";
  507. parameter.enumValues.values[2].value = 2.0f;
  508. break;
  509. case kWindowParameterWheelKnobControl:
  510. parameter.name = "Scroll wheel knob control";
  511. parameter.symbol = "knobScroll";
  512. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  513. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  514. parameter.hints |= kParameterIsHidden;
  515. #endif
  516. parameter.ranges.def = rack::settings::knobScroll ? 1.f : 0.f;
  517. parameter.ranges.min = 0.0f;
  518. parameter.ranges.max = 1.0f;
  519. break;
  520. case kWindowParameterWheelSensitivity:
  521. parameter.name = "Scroll wheel knob sensitivity";
  522. parameter.symbol = "knobScrollSensitivity";
  523. parameter.hints = kParameterIsAutomatable|kParameterIsLogarithmic;
  524. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  525. parameter.hints |= kParameterIsHidden;
  526. #endif
  527. parameter.ranges.def = std::min(10.f, std::max(0.1f, rack::settings::knobScrollSensitivity * 1000));
  528. parameter.ranges.min = 0.1f;
  529. parameter.ranges.max = 10.0f;
  530. break;
  531. case kWindowParameterLockModulePositions:
  532. parameter.name = "Lock module positions";
  533. parameter.symbol = "lockModules";
  534. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  535. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  536. parameter.hints |= kParameterIsHidden;
  537. #endif
  538. parameter.ranges.def = rack::settings::lockModules ? 1.f : 0.f;
  539. parameter.ranges.min = 0.0f;
  540. parameter.ranges.max = 1.0f;
  541. break;
  542. case kWindowParameterUpdateRateLimit:
  543. parameter.name = "Update rate limit";
  544. parameter.symbol = "rateLimit";
  545. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  546. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  547. parameter.hints |= kParameterIsHidden;
  548. #endif
  549. parameter.ranges.def = 0.0f;
  550. parameter.ranges.min = 0.0f;
  551. parameter.ranges.max = 2.0f;
  552. parameter.enumValues.count = 3;
  553. parameter.enumValues.restrictedMode = true;
  554. parameter.enumValues.values = new ParameterEnumerationValue[3];
  555. parameter.enumValues.values[0].label = "None";
  556. parameter.enumValues.values[0].value = 0.0f;
  557. parameter.enumValues.values[1].label = "2x";
  558. parameter.enumValues.values[1].value = 1.0f;
  559. parameter.enumValues.values[2].label = "4x";
  560. parameter.enumValues.values[2].value = 2.0f;
  561. break;
  562. case kWindowParameterBrowserSort:
  563. parameter.name = "Browser sort";
  564. parameter.symbol = "browserSort";
  565. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  566. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  567. parameter.hints |= kParameterIsHidden;
  568. #endif
  569. parameter.ranges.def = std::min(rack::settings::BROWSER_SORT_RANDOM,
  570. std::max(rack::settings::BROWSER_SORT_UPDATED,
  571. rack::settings::browserSort));
  572. parameter.ranges.min = rack::settings::BROWSER_SORT_UPDATED;
  573. parameter.ranges.max = rack::settings::BROWSER_SORT_RANDOM;
  574. parameter.enumValues.count = 6;
  575. parameter.enumValues.restrictedMode = true;
  576. parameter.enumValues.values = new ParameterEnumerationValue[6];
  577. parameter.enumValues.values[0].label = "Updated";
  578. parameter.enumValues.values[0].value = rack::settings::BROWSER_SORT_UPDATED;
  579. parameter.enumValues.values[1].label = "Last used";
  580. parameter.enumValues.values[1].value = rack::settings::BROWSER_SORT_LAST_USED;
  581. parameter.enumValues.values[2].label = "Most used";
  582. parameter.enumValues.values[2].value = rack::settings::BROWSER_SORT_MOST_USED;
  583. parameter.enumValues.values[3].label = "Brand";
  584. parameter.enumValues.values[3].value = rack::settings::BROWSER_SORT_BRAND;
  585. parameter.enumValues.values[4].label = "Name";
  586. parameter.enumValues.values[4].value = rack::settings::BROWSER_SORT_NAME;
  587. parameter.enumValues.values[5].label = "Random";
  588. parameter.enumValues.values[5].value = rack::settings::BROWSER_SORT_RANDOM;
  589. break;
  590. case kWindowParameterBrowserZoom:
  591. parameter.name = "Browser zoom";
  592. parameter.symbol = "browserZoom";
  593. parameter.hints = kParameterIsAutomatable;
  594. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  595. parameter.hints |= kParameterIsHidden;
  596. #endif
  597. parameter.unit = "%";
  598. parameter.ranges.def = std::min(200.f, std::max(25.f, std::pow(2.f, rack::settings::browserZoom) * 100.0f));
  599. parameter.ranges.min = 25.0f;
  600. parameter.ranges.max = 200.0f;
  601. parameter.enumValues.count = 7;
  602. parameter.enumValues.restrictedMode = true;
  603. parameter.enumValues.values = new ParameterEnumerationValue[7];
  604. parameter.enumValues.values[0].label = "25";
  605. parameter.enumValues.values[0].value = 25.0f;
  606. parameter.enumValues.values[1].label = "35";
  607. parameter.enumValues.values[1].value = 35.0f;
  608. parameter.enumValues.values[2].label = "50";
  609. parameter.enumValues.values[2].value = 50.0f;
  610. parameter.enumValues.values[3].label = "71";
  611. parameter.enumValues.values[3].value = 71.0f;
  612. parameter.enumValues.values[4].label = "100";
  613. parameter.enumValues.values[4].value = 100.0f;
  614. parameter.enumValues.values[5].label = "141";
  615. parameter.enumValues.values[5].value = 141.0f;
  616. parameter.enumValues.values[6].label = "200";
  617. parameter.enumValues.values[6].value = 200.0f;
  618. break;
  619. case kWindowParameterInvertZoom:
  620. parameter.name = "Invert zoom";
  621. parameter.symbol = "invertZoom";
  622. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  623. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  624. parameter.hints |= kParameterIsHidden;
  625. #endif
  626. parameter.ranges.def = rack::settings::invertZoom ? 1.f : 0.f;
  627. parameter.ranges.min = 0.0f;
  628. parameter.ranges.max = 1.0f;
  629. break;
  630. case kWindowParameterSqueezeModulePositions:
  631. parameter.name = "Auto-squeeze module positions";
  632. parameter.symbol = "squeezeModules";
  633. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  634. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  635. parameter.hints |= kParameterIsHidden;
  636. #endif
  637. parameter.ranges.def = rack::settings::squeezeModules ? 1.f : 0.f;
  638. parameter.ranges.min = 0.0f;
  639. parameter.ranges.max = 1.0f;
  640. break;
  641. }
  642. }
  643. #endif
  644. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  645. switch (index)
  646. {
  647. case kCardinalParameterMiniAudioIn1:
  648. parameter.name = "Report Audio Input 1";
  649. parameter.symbol = "r_audio_in_1";
  650. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  651. parameter.ranges.def = 0.0f;
  652. parameter.ranges.min = 0.0f;
  653. parameter.ranges.max = 1.0f;
  654. break;
  655. case kCardinalParameterMiniAudioIn2:
  656. parameter.name = "Report Audio Input 2";
  657. parameter.symbol = "r_audio_in_2";
  658. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  659. parameter.ranges.def = 0.0f;
  660. parameter.ranges.min = 0.0f;
  661. parameter.ranges.max = 1.0f;
  662. break;
  663. case kCardinalParameterMiniCVIn1:
  664. parameter.name = "Report CV Input 1";
  665. parameter.symbol = "r_cv_in_1";
  666. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  667. parameter.ranges.def = -10.0f;
  668. parameter.ranges.min = 0.0f;
  669. parameter.ranges.max = 10.0f;
  670. break;
  671. case kCardinalParameterMiniCVIn2:
  672. parameter.name = "Report CV Input 2";
  673. parameter.symbol = "r_cv_in_2";
  674. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  675. parameter.ranges.def = -10.0f;
  676. parameter.ranges.min = 0.0f;
  677. parameter.ranges.max = 10.0f;
  678. break;
  679. case kCardinalParameterMiniCVIn3:
  680. parameter.name = "Report CV Input 3";
  681. parameter.symbol = "r_cv_in_3";
  682. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  683. parameter.ranges.def = -10.0f;
  684. parameter.ranges.min = 0.0f;
  685. parameter.ranges.max = 10.0f;
  686. break;
  687. case kCardinalParameterMiniCVIn4:
  688. parameter.name = "Report CV Input 4";
  689. parameter.symbol = "r_cv_in_4";
  690. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  691. parameter.ranges.def = -10.0f;
  692. parameter.ranges.min = 0.0f;
  693. parameter.ranges.max = 10.0f;
  694. break;
  695. case kCardinalParameterMiniCVIn5:
  696. parameter.name = "Report CV Input 5";
  697. parameter.symbol = "r_cv_in_5";
  698. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  699. parameter.ranges.def = -10.0f;
  700. parameter.ranges.min = 0.0f;
  701. parameter.ranges.max = 10.0f;
  702. break;
  703. case kCardinalParameterMiniTimeFlags:
  704. parameter.name = "Report Time Flags";
  705. parameter.symbol = "r_time_flags";
  706. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  707. parameter.ranges.def = 0x0;
  708. parameter.ranges.min = 0x0;
  709. parameter.ranges.max = 0x7;
  710. break;
  711. case kCardinalParameterMiniTimeBar:
  712. parameter.name = "Report Time Bar";
  713. parameter.symbol = "r_time_bar";
  714. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  715. parameter.ranges.def = 1.0f;
  716. parameter.ranges.min = 1.0f;
  717. parameter.ranges.max = FLT_MAX;
  718. break;
  719. case kCardinalParameterMiniTimeBeat:
  720. parameter.name = "Report Time Beat";
  721. parameter.symbol = "r_time_beat";
  722. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  723. parameter.ranges.def = 1.0f;
  724. parameter.ranges.min = 1.0f;
  725. parameter.ranges.max = 128.0f;
  726. break;
  727. case kCardinalParameterMiniTimeBeatsPerBar:
  728. parameter.name = "Report Time Beats Per Bar";
  729. parameter.symbol = "r_time_beatsPerBar";
  730. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  731. parameter.ranges.def = 4.0f;
  732. parameter.ranges.min = 0.0f;
  733. parameter.ranges.max = 128.0f;
  734. break;
  735. case kCardinalParameterMiniTimeBeatType:
  736. parameter.name = "Report Time Beat Type";
  737. parameter.symbol = "r_time_beatType";
  738. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  739. parameter.ranges.def = 4.0f;
  740. parameter.ranges.min = 0.0f;
  741. parameter.ranges.max = 128.0f;
  742. break;
  743. case kCardinalParameterMiniTimeFrame:
  744. parameter.name = "Report Time Frame";
  745. parameter.symbol = "r_time_frame";
  746. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  747. parameter.ranges.def = 0.0f;
  748. parameter.ranges.min = 0.0f;
  749. parameter.ranges.max = FLT_MAX;
  750. break;
  751. case kCardinalParameterMiniTimeBarStartTick:
  752. parameter.name = "Report Time BarStartTick";
  753. parameter.symbol = "r_time_barStartTick";
  754. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  755. parameter.ranges.def = 0.0f;
  756. parameter.ranges.min = 0.0f;
  757. parameter.ranges.max = FLT_MAX;
  758. break;
  759. case kCardinalParameterMiniTimeBeatsPerMinute:
  760. parameter.name = "Report Time Beats Per Minute";
  761. parameter.symbol = "r_time_bpm";
  762. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  763. parameter.ranges.def = 20.0f;
  764. parameter.ranges.min = 120.0f;
  765. parameter.ranges.max = 999.0f;
  766. break;
  767. case kCardinalParameterMiniTimeTick:
  768. parameter.name = "Report Time Tick";
  769. parameter.symbol = "r_time_tick";
  770. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  771. parameter.ranges.def = 0.0f;
  772. parameter.ranges.min = 0.0f;
  773. parameter.ranges.max = 8192.0f;
  774. break;
  775. case kCardinalParameterMiniTimeTicksPerBeat:
  776. parameter.name = "Report Time Ticks Per Beat";
  777. parameter.symbol = "r_time_ticksPerBeat";
  778. parameter.hints = kParameterIsAutomatable|kParameterIsOutput;
  779. parameter.ranges.def = 0.0f;
  780. parameter.ranges.min = 0.0f;
  781. parameter.ranges.max = 8192.0f;
  782. break;
  783. }
  784. #endif
  785. }
  786. void initState(const uint32_t index, State& state) override
  787. {
  788. switch (index)
  789. {
  790. case kCardinalStatePatch:
  791. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  792. state.hints = kStateIsHostReadable;
  793. #else
  794. state.hints = kStateIsOnlyForDSP | kStateIsBase64Blob;
  795. #endif
  796. if (FILE* const f = std::fopen(context->patch->factoryTemplatePath.c_str(), "r"))
  797. {
  798. std::fseek(f, 0, SEEK_END);
  799. if (const long fileSize = std::ftell(f))
  800. {
  801. std::fseek(f, 0, SEEK_SET);
  802. char* const fileContent = new char[fileSize+1];
  803. if (std::fread(fileContent, fileSize, 1, f) == 1)
  804. {
  805. fileContent[fileSize] = '\0';
  806. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  807. state.defaultValue = fileContent;
  808. #else
  809. state.defaultValue = String::asBase64(fileContent, fileSize);
  810. #endif
  811. }
  812. delete[] fileContent;
  813. }
  814. std::fclose(f);
  815. }
  816. state.key = "patch";
  817. state.label = "Patch";
  818. break;
  819. case kCardinalStateScreenshot:
  820. state.hints = kStateIsHostReadable | kStateIsBase64Blob;
  821. state.key = "screenshot";
  822. state.label = "Screenshot";
  823. break;
  824. case kCardinalStateComment:
  825. state.hints = kStateIsHostWritable;
  826. state.key = "comment";
  827. state.label = "Comment";
  828. break;
  829. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  830. case kCardinalStateModuleInfos:
  831. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  832. state.hints = kStateIsHostReadable;
  833. #else
  834. state.hints = kStateIsOnlyForDSP;
  835. #endif
  836. state.defaultValue = "{}";
  837. state.key = "moduleInfos";
  838. state.label = "moduleInfos";
  839. break;
  840. case kCardinalStateWindowSize:
  841. state.hints = kStateIsOnlyForUI;
  842. // state.defaultValue = String("%d:%d", DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT);
  843. state.key = "windowSize";
  844. state.label = "Window size";
  845. break;
  846. #endif
  847. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  848. case kCardinalStateParamChange:
  849. state.hints = kStateIsHostReadable | kStateIsOnlyForDSP;
  850. state.key = "param";
  851. state.label = "ParamChange";
  852. break;
  853. #endif
  854. }
  855. }
  856. /* --------------------------------------------------------------------------------------------------------
  857. * Internal data */
  858. float getParameterValue(uint32_t index) const override
  859. {
  860. // host mapped parameters
  861. if (index < kCardinalParameterCountAtModules)
  862. return context->parameters[index];
  863. // bypass
  864. if (index == kCardinalParameterBypass)
  865. return context->bypassed ? 1.0f : 0.0f;
  866. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  867. if (index < kCardinalParameterCountAtWindow)
  868. return fWindowParameters[index - kCardinalParameterStartWindow];
  869. #endif
  870. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  871. if (index < kCardinalParameterCountAtMini)
  872. return fMiniReportValues[index - kCardinalParameterStartMini];
  873. #endif
  874. return 0.0f;
  875. }
  876. void setParameterValue(uint32_t index, float value) override
  877. {
  878. // host mapped parameters
  879. if (index < kCardinalParameterCountAtModules)
  880. {
  881. context->parameters[index] = value;
  882. return;
  883. }
  884. // bypass
  885. if (index == kCardinalParameterBypass)
  886. {
  887. context->bypassed = value > 0.5f;
  888. return;
  889. }
  890. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  891. if (index < kCardinalParameterCountAtWindow)
  892. {
  893. fWindowParameters[index - kCardinalParameterStartWindow] = value;
  894. return;
  895. }
  896. #endif
  897. }
  898. String getState(const char* const key) const override
  899. {
  900. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  901. if (std::strcmp(key, "moduleInfos") == 0)
  902. {
  903. json_t* const rootJ = json_object();
  904. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, String());
  905. for (const auto& pluginPair : rack::settings::moduleInfos)
  906. {
  907. json_t* const pluginJ = json_object();
  908. DISTRHO_SAFE_ASSERT_CONTINUE(pluginJ != nullptr);
  909. for (const auto& modulePair : pluginPair.second)
  910. {
  911. json_t* const moduleJ = json_object();
  912. DISTRHO_SAFE_ASSERT_CONTINUE(moduleJ != nullptr);
  913. const rack::settings::ModuleInfo& m(modulePair.second);
  914. // To make setting.json smaller, only set properties if not default values.
  915. if (m.favorite)
  916. json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
  917. if (m.added > 0)
  918. json_object_set_new(moduleJ, "added", json_integer(m.added));
  919. if (std::isfinite(m.lastAdded) && d_isNotZero(m.lastAdded))
  920. json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
  921. if (json_object_size(moduleJ))
  922. json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
  923. else
  924. json_decref(moduleJ);
  925. }
  926. if (json_object_size(pluginJ))
  927. json_object_set_new(rootJ, pluginPair.first.c_str(), pluginJ);
  928. else
  929. json_decref(pluginJ);
  930. }
  931. const String info(json_dumps(rootJ, JSON_COMPACT), false);
  932. json_decref(rootJ);
  933. return info;
  934. }
  935. if (std::strcmp(key, "windowSize") == 0)
  936. return fState.windowSize;
  937. #endif
  938. if (std::strcmp(key, "comment") == 0)
  939. return fState.comment;
  940. if (std::strcmp(key, "screenshot") == 0)
  941. return fState.screenshot;
  942. if (std::strcmp(key, "patch") != 0)
  943. return String();
  944. if (fAutosavePath.empty())
  945. return String();
  946. std::vector<uint8_t> data;
  947. {
  948. const ScopedContext sc(this);
  949. context->engine->prepareSave();
  950. context->patch->saveAutosave();
  951. context->patch->cleanAutosave();
  952. // context->history->setSaved();
  953. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  954. FILE* const f = std::fopen(rack::system::join(context->patch->autosavePath, "patch.json").c_str(), "r");
  955. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr, String());
  956. DEFER({
  957. std::fclose(f);
  958. });
  959. std::fseek(f, 0, SEEK_END);
  960. const long fileSize = std::ftell(f);
  961. DISTRHO_SAFE_ASSERT_RETURN(fileSize > 0, String());
  962. std::fseek(f, 0, SEEK_SET);
  963. char* const fileContent = static_cast<char*>(std::malloc(fileSize+1));
  964. DISTRHO_SAFE_ASSERT_RETURN(std::fread(fileContent, fileSize, 1, f) == 1, String());
  965. fileContent[fileSize] = '\0';
  966. return String(fileContent, false);
  967. #else
  968. try {
  969. data = rack::system::archiveDirectory(fAutosavePath, 1);
  970. } DISTRHO_SAFE_EXCEPTION_RETURN("getState archiveDirectory", String());
  971. #endif
  972. }
  973. return String::asBase64(data.data(), data.size());
  974. }
  975. void setState(const char* const key, const char* const value) override
  976. {
  977. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  978. if (std::strcmp(key, "param") == 0)
  979. {
  980. long long moduleId = 0;
  981. int paramId = 0;
  982. float paramValue = 0.f;
  983. {
  984. const ScopedSafeLocale cssl;
  985. std::sscanf(value, "%lld:%d:%f", &moduleId, &paramId, &paramValue);
  986. }
  987. rack::engine::Module* const module = context->engine->getModule(moduleId);
  988. DISTRHO_SAFE_ASSERT_RETURN(module != nullptr,);
  989. context->engine->setParamValue(module, paramId, paramValue);
  990. return;
  991. }
  992. #endif
  993. #if CARDINAL_VARIANT_MINI || !defined(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. return;
  1035. }
  1036. if (std::strcmp(key, "patch") != 0)
  1037. return;
  1038. if (fAutosavePath.empty())
  1039. return;
  1040. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  1041. rack::system::removeRecursively(fAutosavePath);
  1042. rack::system::createDirectories(fAutosavePath);
  1043. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  1044. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  1045. std::fwrite(value, std::strlen(value), 1, f);
  1046. std::fclose(f);
  1047. #else
  1048. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  1049. DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,);
  1050. rack::system::removeRecursively(fAutosavePath);
  1051. rack::system::createDirectories(fAutosavePath);
  1052. static constexpr const char zstdMagic[] = "\x28\xb5\x2f\xfd";
  1053. if (std::memcmp(data.data(), zstdMagic, sizeof(zstdMagic)) != 0)
  1054. {
  1055. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  1056. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  1057. std::fwrite(data.data(), data.size(), 1, f);
  1058. std::fclose(f);
  1059. }
  1060. else
  1061. {
  1062. try {
  1063. rack::system::unarchiveToDirectory(data, fAutosavePath);
  1064. } DISTRHO_SAFE_EXCEPTION_RETURN("setState unarchiveToDirectory",);
  1065. }
  1066. #endif
  1067. const ScopedContext sc(this);
  1068. try {
  1069. context->patch->loadAutosave();
  1070. } catch(const rack::Exception& e) {
  1071. d_stderr(e.what());
  1072. } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",);
  1073. // context->history->setSaved();
  1074. }
  1075. /* --------------------------------------------------------------------------------------------------------
  1076. * Process */
  1077. void activate() override
  1078. {
  1079. context->bufferSize = getBufferSize();
  1080. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1081. fAudioBufferCopy = new float*[DISTRHO_PLUGIN_NUM_INPUTS];
  1082. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1083. fAudioBufferCopy[i] = new float[context->bufferSize];
  1084. #endif
  1085. fNextExpectedFrame = 0;
  1086. }
  1087. void deactivate() override
  1088. {
  1089. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1090. if (fAudioBufferCopy != nullptr)
  1091. {
  1092. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1093. delete[] fAudioBufferCopy[i];
  1094. delete[] fAudioBufferCopy;
  1095. fAudioBufferCopy = nullptr;
  1096. }
  1097. #endif
  1098. }
  1099. void run(const float** const inputs, float** const outputs, const uint32_t frames,
  1100. const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  1101. {
  1102. const ScopedDenormalDisable sdd;
  1103. rack::contextSet(context);
  1104. const bool bypassed = context->bypassed;
  1105. {
  1106. const TimePosition& timePos(getTimePosition());
  1107. bool reset = timePos.playing && (timePos.frame == 0 || d_isDiffHigherThanLimit(fNextExpectedFrame, timePos.frame, (uint64_t)2));
  1108. // ignore hosts which cannot supply time frame position
  1109. if (context->playing == timePos.playing && timePos.frame == 0 && context->frame == 0)
  1110. reset = false;
  1111. context->playing = timePos.playing;
  1112. context->bbtValid = timePos.bbt.valid;
  1113. context->frame = timePos.frame;
  1114. if (timePos.bbt.valid)
  1115. {
  1116. const double samplesPerTick = 60.0 * getSampleRate()
  1117. / timePos.bbt.beatsPerMinute
  1118. / timePos.bbt.ticksPerBeat;
  1119. context->bar = timePos.bbt.bar;
  1120. context->beat = timePos.bbt.beat;
  1121. context->beatsPerBar = timePos.bbt.beatsPerBar;
  1122. context->beatType = timePos.bbt.beatType;
  1123. context->barStartTick = timePos.bbt.barStartTick;
  1124. context->beatsPerMinute = timePos.bbt.beatsPerMinute;
  1125. context->tick = timePos.bbt.tick;
  1126. context->ticksPerBeat = timePos.bbt.ticksPerBeat;
  1127. context->ticksPerClock = timePos.bbt.ticksPerBeat / timePos.bbt.beatType;
  1128. context->ticksPerFrame = 1.0 / samplesPerTick;
  1129. context->tickClock = std::fmod(timePos.bbt.tick, context->ticksPerClock);
  1130. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  1131. fMiniReportValues[kCardinalParameterMiniTimeBar - kCardinalParameterStartMini] = timePos.bbt.bar;
  1132. fMiniReportValues[kCardinalParameterMiniTimeBeat - kCardinalParameterStartMini] = timePos.bbt.beat;
  1133. fMiniReportValues[kCardinalParameterMiniTimeBeatsPerBar - kCardinalParameterStartMini] = timePos.bbt.beatsPerBar;
  1134. fMiniReportValues[kCardinalParameterMiniTimeBeatType - kCardinalParameterStartMini] = timePos.bbt.beatType;
  1135. fMiniReportValues[kCardinalParameterMiniTimeBarStartTick - kCardinalParameterStartMini] = timePos.bbt.barStartTick;
  1136. fMiniReportValues[kCardinalParameterMiniTimeBeatsPerMinute - kCardinalParameterStartMini] = timePos.bbt.beatsPerMinute;
  1137. fMiniReportValues[kCardinalParameterMiniTimeTick - kCardinalParameterStartMini] = timePos.bbt.tick;
  1138. fMiniReportValues[kCardinalParameterMiniTimeTicksPerBeat - kCardinalParameterStartMini] = timePos.bbt.ticksPerBeat;
  1139. #endif
  1140. }
  1141. context->reset = reset;
  1142. fNextExpectedFrame = timePos.playing ? timePos.frame + frames : 0;
  1143. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  1144. const int flags = (timePos.playing ? 0x1 : 0x0)
  1145. | (timePos.bbt.valid ? 0x2 : 0x0)
  1146. | (reset ? 0x4 : 0x0);
  1147. fMiniReportValues[kCardinalParameterMiniTimeFlags - kCardinalParameterStartMini] = flags;
  1148. fMiniReportValues[kCardinalParameterMiniTimeFrame - kCardinalParameterStartMini] = timePos.frame / getSampleRate();
  1149. #endif
  1150. }
  1151. // separate buffers, use them
  1152. if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
  1153. {
  1154. context->dataIns = inputs;
  1155. context->dataOuts = outputs;
  1156. }
  1157. // inline processing, use a safe copy
  1158. else
  1159. {
  1160. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  1161. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1162. {
  1163. #if CARDINAL_VARIANT_MAIN || CARDINAL_VARIANT_MINI
  1164. // can be null on main and mini variants
  1165. if (inputs[i] != nullptr)
  1166. #endif
  1167. std::memcpy(fAudioBufferCopy[i], inputs[i], sizeof(float)*frames);
  1168. }
  1169. context->dataIns = fAudioBufferCopy;
  1170. #else
  1171. context->dataIns = nullptr;
  1172. #endif
  1173. context->dataOuts = outputs;
  1174. }
  1175. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  1176. {
  1177. #if CARDINAL_VARIANT_MAIN || CARDINAL_VARIANT_MINI
  1178. // can be null on main and mini variants
  1179. if (outputs[i] != nullptr)
  1180. #endif
  1181. std::memset(outputs[i], 0, sizeof(float)*frames);
  1182. }
  1183. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  1184. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  1185. fMiniReportValues[i] = context->dataIns[i][0];
  1186. #endif
  1187. if (bypassed)
  1188. {
  1189. if (fWasBypassed != bypassed)
  1190. {
  1191. context->midiEvents = bypassMidiEvents;
  1192. context->midiEventCount = 16;
  1193. }
  1194. else
  1195. {
  1196. context->midiEvents = nullptr;
  1197. context->midiEventCount = 0;
  1198. }
  1199. }
  1200. else
  1201. {
  1202. context->midiEvents = midiEvents;
  1203. context->midiEventCount = midiEventCount;
  1204. }
  1205. ++context->processCounter;
  1206. context->engine->stepBlock(frames);
  1207. fWasBypassed = bypassed;
  1208. }
  1209. void sampleRateChanged(const double newSampleRate) override
  1210. {
  1211. rack::contextSet(context);
  1212. rack::settings::sampleRate = newSampleRate;
  1213. context->sampleRate = newSampleRate;
  1214. context->engine->setSampleRate(newSampleRate);
  1215. }
  1216. // -------------------------------------------------------------------------------------------------------
  1217. private:
  1218. /**
  1219. Set our plugin class as non-copyable and add a leak detector just in case.
  1220. */
  1221. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  1222. };
  1223. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  1224. {
  1225. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  1226. }
  1227. /* ------------------------------------------------------------------------------------------------------------
  1228. * Plugin entry point, called by DPF to create a new plugin instance. */
  1229. Plugin* createPlugin()
  1230. {
  1231. return new CardinalPlugin();
  1232. }
  1233. // --------------------------------------------------------------------------------------------------------------------
  1234. END_NAMESPACE_DISTRHO