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.

1095 lines
38KB

  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 <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 <window/Window.hpp>
  28. #ifdef NDEBUG
  29. # undef DEBUG
  30. #endif
  31. #if defined(HAVE_LIBLO) && defined(HEADLESS)
  32. # include <lo/lo.h>
  33. # include "extra/Thread.hpp"
  34. #endif
  35. #include <list>
  36. #include "CardinalCommon.hpp"
  37. #include "DistrhoPluginUtils.hpp"
  38. #include "PluginContext.hpp"
  39. #include "extra/Base64.hpp"
  40. #ifdef DISTRHO_OS_WASM
  41. # include <emscripten/emscripten.h>
  42. #else
  43. # include "extra/SharedResourcePointer.hpp"
  44. #endif
  45. static const constexpr uint kCardinalStateBaseCount = 3; // patch, screenshot, comment
  46. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  47. # include "extra/ScopedValueSetter.hpp"
  48. # include "WindowParameters.hpp"
  49. static const constexpr uint kCardinalStateCount = kCardinalStateBaseCount + 2; // moduleInfos, windowSize
  50. #else
  51. # define kWindowParameterCount 0
  52. static const constexpr uint kCardinalStateCount = kCardinalStateBaseCount;
  53. #endif
  54. extern const std::string CARDINAL_VERSION;
  55. namespace rack {
  56. namespace engine {
  57. void Engine_setAboutToClose(Engine*);
  58. }
  59. }
  60. START_NAMESPACE_DISTRHO
  61. template<typename T>
  62. static inline
  63. bool d_isDiffHigherThanLimit(const T& v1, const T& v2, const T& limit)
  64. {
  65. return v1 != v2 ? (v1 > v2 ? v1 - v2 : v2 - v1) > limit : false;
  66. }
  67. #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  68. const char* UI::getBundlePath() const noexcept { return nullptr; }
  69. #endif
  70. // -----------------------------------------------------------------------------------------------------------
  71. #ifdef DISTRHO_OS_WASM
  72. static char* getPatchFileEncodedInURL() {
  73. return static_cast<char*>(EM_ASM_PTR({
  74. var searchParams = new URLSearchParams(window.location.search);
  75. var patch = searchParams.get('patch');
  76. if (!patch)
  77. return null;
  78. var length = lengthBytesUTF8(patch) + 1;
  79. var str = _malloc(length);
  80. stringToUTF8(patch, str, length);
  81. return str;
  82. }));
  83. };
  84. static char* getPatchRemoteURL() {
  85. return static_cast<char*>(EM_ASM_PTR({
  86. var searchParams = new URLSearchParams(window.location.search);
  87. var patch = searchParams.get('patchurl');
  88. if (!patch)
  89. return null;
  90. var length = lengthBytesUTF8(patch) + 1;
  91. var str = _malloc(length);
  92. stringToUTF8(patch, str, length);
  93. return str;
  94. }));
  95. };
  96. static char* getPatchStorageSlug() {
  97. return static_cast<char*>(EM_ASM_PTR({
  98. var searchParams = new URLSearchParams(window.location.search);
  99. var patch = searchParams.get('patchstorage');
  100. if (!patch)
  101. return null;
  102. var length = lengthBytesUTF8(patch) + 1;
  103. var str = _malloc(length);
  104. stringToUTF8(patch, str, length);
  105. return str;
  106. }));
  107. };
  108. #endif
  109. // -----------------------------------------------------------------------------------------------------------
  110. struct ScopedContext {
  111. ScopedContext(const CardinalBasePlugin* const plugin)
  112. {
  113. rack::contextSet(plugin->context);
  114. }
  115. ~ScopedContext()
  116. {
  117. rack::contextSet(nullptr);
  118. }
  119. };
  120. // -----------------------------------------------------------------------------------------------------------
  121. class CardinalPlugin : public CardinalBasePlugin
  122. {
  123. #ifdef DISTRHO_OS_WASM
  124. ScopedPointer<Initializer> fInitializer;
  125. #else
  126. SharedResourcePointer<Initializer> fInitializer;
  127. #endif
  128. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  129. /* If host audio ins == outs we can get issues for inplace processing.
  130. * So allocate a float array that will serve as safe copy for those cases.
  131. */
  132. float** fAudioBufferCopy;
  133. #endif
  134. std::string fAutosavePath;
  135. uint64_t fNextExpectedFrame;
  136. struct {
  137. String comment;
  138. String screenshot;
  139. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  140. String windowSize;
  141. #endif
  142. } fState;
  143. // bypass handling
  144. bool fWasBypassed;
  145. MidiEvent bypassMidiEvents[16];
  146. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  147. // real values, not VCV interpreted ones
  148. float fWindowParameters[kWindowParameterCount];
  149. #endif
  150. public:
  151. CardinalPlugin()
  152. : CardinalBasePlugin(kModuleParameters + kWindowParameterCount + 1, 0, kCardinalStateCount),
  153. #ifdef DISTRHO_OS_WASM
  154. fInitializer(new Initializer(this, static_cast<const CardinalBaseUI*>(nullptr))),
  155. #else
  156. fInitializer(this, static_cast<const CardinalBaseUI*>(nullptr)),
  157. #endif
  158. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  159. fAudioBufferCopy(nullptr),
  160. #endif
  161. fNextExpectedFrame(0),
  162. fWasBypassed(false)
  163. {
  164. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  165. fWindowParameters[kWindowParameterShowTooltips] = 1.0f;
  166. fWindowParameters[kWindowParameterCableOpacity] = 50.0f;
  167. fWindowParameters[kWindowParameterCableTension] = 75.0f;
  168. fWindowParameters[kWindowParameterRackBrightness] = 100.0f;
  169. fWindowParameters[kWindowParameterHaloBrightness] = 25.0f;
  170. fWindowParameters[kWindowParameterKnobMode] = 0.0f;
  171. fWindowParameters[kWindowParameterWheelKnobControl] = 0.0f;
  172. fWindowParameters[kWindowParameterWheelSensitivity] = 1.0f;
  173. fWindowParameters[kWindowParameterLockModulePositions] = 0.0f;
  174. fWindowParameters[kWindowParameterUpdateRateLimit] = 0.0f;
  175. fWindowParameters[kWindowParameterBrowserSort] = 3.0f;
  176. fWindowParameters[kWindowParameterBrowserZoom] = 50.0f;
  177. fWindowParameters[kWindowParameterInvertZoom] = 0.0f;
  178. fWindowParameters[kWindowParameterSqueezeModulePositions] = 1.0f;
  179. #endif
  180. // create unique temporary path for this instance
  181. try {
  182. char uidBuf[24];
  183. const std::string tmp = rack::system::getTempDirectory();
  184. for (int i=1;; ++i)
  185. {
  186. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  187. const std::string trypath = rack::system::join(tmp, uidBuf);
  188. if (! rack::system::exists(trypath))
  189. {
  190. if (rack::system::createDirectories(trypath))
  191. fAutosavePath = trypath;
  192. break;
  193. }
  194. }
  195. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  196. // initialize midi events used when entering bypassed state
  197. std::memset(bypassMidiEvents, 0, sizeof(bypassMidiEvents));
  198. for (uint8_t i=0; i<16; ++i)
  199. {
  200. bypassMidiEvents[i].size = 3;
  201. bypassMidiEvents[i].data[0] = 0xB0 + i;
  202. bypassMidiEvents[i].data[1] = 0x7B;
  203. }
  204. const float sampleRate = getSampleRate();
  205. rack::settings::sampleRate = sampleRate;
  206. context->bufferSize = getBufferSize();
  207. context->sampleRate = sampleRate;
  208. const ScopedContext sc(this);
  209. context->engine = new rack::engine::Engine;
  210. context->engine->setSampleRate(sampleRate);
  211. context->history = new rack::history::State;
  212. context->patch = new rack::patch::Manager;
  213. context->patch->autosavePath = fAutosavePath;
  214. context->patch->templatePath = fInitializer->templatePath;
  215. context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath;
  216. context->event = new rack::widget::EventState;
  217. context->scene = new rack::app::Scene;
  218. context->event->rootWidget = context->scene;
  219. if (! isDummyInstance())
  220. context->window = new rack::window::Window;
  221. #ifdef DISTRHO_OS_WASM
  222. if ((rack::patchStorageSlug = getPatchStorageSlug()) == nullptr &&
  223. (rack::patchRemoteURL = getPatchRemoteURL()) == nullptr &&
  224. (rack::patchFromURL = getPatchFileEncodedInURL()) == nullptr)
  225. #endif
  226. {
  227. context->patch->loadTemplate();
  228. context->scene->rackScroll->reset();
  229. // swap to factory template after first load
  230. context->patch->templatePath = context->patch->factoryTemplatePath;
  231. }
  232. #ifdef CARDINAL_INIT_OSC_THREAD
  233. fInitializer->remotePluginInstance = this;
  234. #endif
  235. }
  236. ~CardinalPlugin() override
  237. {
  238. #ifdef CARDINAL_INIT_OSC_THREAD
  239. if (fInitializer->remotePluginInstance == this)
  240. fInitializer->remotePluginInstance = nullptr;
  241. #endif
  242. {
  243. const ScopedContext sc(this);
  244. context->patch->clear();
  245. // do a little dance to prevent context scene deletion from saving to temp dir
  246. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  247. const ScopedValueSetter<bool> svs(rack::settings::headless, true);
  248. #endif
  249. Engine_setAboutToClose(context->engine);
  250. delete context;
  251. }
  252. if (! fAutosavePath.empty())
  253. rack::system::removeRecursively(fAutosavePath);
  254. }
  255. CardinalPluginContext* getRackContext() const noexcept
  256. {
  257. return context;
  258. }
  259. protected:
  260. /* --------------------------------------------------------------------------------------------------------
  261. * Information */
  262. const char* getLabel() const override
  263. {
  264. return DISTRHO_PLUGIN_LABEL;
  265. }
  266. const char* getDescription() const override
  267. {
  268. return ""
  269. "Cardinal is a free and open-source virtual modular synthesizer plugin.\n"
  270. "It is based on the popular VCV Rack but with a focus on being a fully self-contained plugin version.\n"
  271. "It is not an official VCV project, and it is not affiliated with it in any way.\n"
  272. "\n"
  273. "Cardinal contains Rack, some 3rd-party modules and a few internal utilities.\n"
  274. "It does not load external modules and does not connect to the official Rack library/store.\n";
  275. }
  276. const char* getMaker() const override
  277. {
  278. return "DISTRHO";
  279. }
  280. const char* getHomePage() const override
  281. {
  282. return "https://github.com/DISTRHO/Cardinal";
  283. }
  284. const char* getLicense() const override
  285. {
  286. return "GPLv3+";
  287. }
  288. uint32_t getVersion() const override
  289. {
  290. return d_version(0, 22, 12);
  291. }
  292. int64_t getUniqueId() const override
  293. {
  294. #if CARDINAL_VARIANT_MAIN || CARDINAL_VARIANT_NATIVE
  295. return d_cconst('d', 'C', 'd', 'n');
  296. #elif CARDINAL_VARIANT_MINI
  297. return d_cconst('d', 'C', 'd', 'M');
  298. #elif CARDINAL_VARIANT_FX
  299. return d_cconst('d', 'C', 'n', 'F');
  300. #elif CARDINAL_VARIANT_SYNTH
  301. return d_cconst('d', 'C', 'n', 'S');
  302. #else
  303. #error cardinal variant not set
  304. #endif
  305. }
  306. /* --------------------------------------------------------------------------------------------------------
  307. * Init */
  308. void initAudioPort(const bool input, uint32_t index, AudioPort& port) override
  309. {
  310. #if CARDINAL_VARIANT_MAIN
  311. if (index < 8)
  312. {
  313. port.groupId = index / 2;
  314. }
  315. else
  316. {
  317. port.hints = kAudioPortIsCV | kCVPortHasPositiveUnipolarRange | kCVPortHasScaledRange | kCVPortIsOptional;
  318. index -= 8;
  319. }
  320. #elif CARDINAL_VARIANT_MINI || CARDINAL_VARIANT_NATIVE || CARDINAL_VARIANT_FX || CARDINAL_VARIANT_SYNTH
  321. if (index < 2)
  322. port.groupId = kPortGroupStereo;
  323. #endif
  324. CardinalBasePlugin::initAudioPort(input, index, port);
  325. }
  326. #if CARDINAL_VARIANT_MAIN
  327. void initPortGroup(const uint32_t index, PortGroup& portGroup) override
  328. {
  329. switch (index)
  330. {
  331. case 0:
  332. portGroup.name = "Audio 1+2";
  333. portGroup.symbol = "audio_1_and_2";
  334. break;
  335. case 1:
  336. portGroup.name = "Audio 3+4";
  337. portGroup.symbol = "audio_3_and_4";
  338. break;
  339. case 2:
  340. portGroup.name = "Audio 5+6";
  341. portGroup.symbol = "audio_5_and_6";
  342. break;
  343. case 3:
  344. portGroup.name = "Audio 7+8";
  345. portGroup.symbol = "audio_7_and_8";
  346. break;
  347. }
  348. }
  349. #endif
  350. void initParameter(const uint32_t index, Parameter& parameter) override
  351. {
  352. if (index < kModuleParameters)
  353. {
  354. parameter.name = "Parameter ";
  355. parameter.name += String(index + 1);
  356. parameter.symbol = "param_";
  357. parameter.symbol += String(index + 1);
  358. parameter.unit = "v";
  359. parameter.hints = kParameterIsAutomatable;
  360. #if CARDINAL_VARIANT_MINI
  361. // TODO is hidden
  362. // parameter.hints |= kParameterIsAutomatable;
  363. #endif
  364. parameter.ranges.def = 0.0f;
  365. parameter.ranges.min = 0.0f;
  366. parameter.ranges.max = 10.0f;
  367. return;
  368. }
  369. if (index == kModuleParameters)
  370. {
  371. parameter.initDesignation(kParameterDesignationBypass);
  372. return;
  373. }
  374. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  375. switch (index - kModuleParameters - 1)
  376. {
  377. case kWindowParameterShowTooltips:
  378. parameter.name = "Show tooltips";
  379. parameter.symbol = "tooltips";
  380. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  381. parameter.ranges.def = 1.0f;
  382. parameter.ranges.min = 0.0f;
  383. parameter.ranges.max = 1.0f;
  384. break;
  385. case kWindowParameterCableOpacity:
  386. parameter.name = "Cable opacity";
  387. parameter.symbol = "cableOpacity";
  388. parameter.unit = "%";
  389. parameter.hints = kParameterIsAutomatable;
  390. parameter.ranges.def = 50.0f;
  391. parameter.ranges.min = 0.0f;
  392. parameter.ranges.max = 100.0f;
  393. break;
  394. case kWindowParameterCableTension:
  395. parameter.name = "Cable tension";
  396. parameter.symbol = "cableTension";
  397. parameter.unit = "%";
  398. parameter.hints = kParameterIsAutomatable;
  399. parameter.ranges.def = 75.0f;
  400. parameter.ranges.min = 0.0f;
  401. parameter.ranges.max = 100.0f;
  402. break;
  403. case kWindowParameterRackBrightness:
  404. parameter.name = "Room brightness";
  405. parameter.symbol = "rackBrightness";
  406. parameter.unit = "%";
  407. parameter.hints = kParameterIsAutomatable;
  408. parameter.ranges.def = 100.0f;
  409. parameter.ranges.min = 0.0f;
  410. parameter.ranges.max = 100.0f;
  411. break;
  412. case kWindowParameterHaloBrightness:
  413. parameter.name = "Light Bloom";
  414. parameter.symbol = "haloBrightness";
  415. parameter.unit = "%";
  416. parameter.hints = kParameterIsAutomatable;
  417. parameter.ranges.def = 25.0f;
  418. parameter.ranges.min = 0.0f;
  419. parameter.ranges.max = 100.0f;
  420. break;
  421. case kWindowParameterKnobMode:
  422. parameter.name = "Knob mode";
  423. parameter.symbol = "knobMode";
  424. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  425. parameter.ranges.def = 0.0f;
  426. parameter.ranges.min = 0.0f;
  427. parameter.ranges.max = 2.0f;
  428. parameter.enumValues.count = 3;
  429. parameter.enumValues.restrictedMode = true;
  430. parameter.enumValues.values = new ParameterEnumerationValue[3];
  431. parameter.enumValues.values[0].label = "Linear";
  432. parameter.enumValues.values[0].value = 0.0f;
  433. parameter.enumValues.values[1].label = "Absolute rotary";
  434. parameter.enumValues.values[1].value = 1.0f;
  435. parameter.enumValues.values[2].label = "Relative rotary";
  436. parameter.enumValues.values[2].value = 2.0f;
  437. break;
  438. case kWindowParameterWheelKnobControl:
  439. parameter.name = "Scroll wheel knob control";
  440. parameter.symbol = "knobScroll";
  441. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  442. parameter.ranges.def = 0.0f;
  443. parameter.ranges.min = 0.0f;
  444. parameter.ranges.max = 1.0f;
  445. break;
  446. case kWindowParameterWheelSensitivity:
  447. parameter.name = "Scroll wheel knob sensitivity";
  448. parameter.symbol = "knobScrollSensitivity";
  449. parameter.hints = kParameterIsAutomatable|kParameterIsLogarithmic;
  450. parameter.ranges.def = 1.0f;
  451. parameter.ranges.min = 0.1f;
  452. parameter.ranges.max = 10.0f;
  453. break;
  454. case kWindowParameterLockModulePositions:
  455. parameter.name = "Lock module positions";
  456. parameter.symbol = "lockModules";
  457. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  458. parameter.ranges.def = 0.0f;
  459. parameter.ranges.min = 0.0f;
  460. parameter.ranges.max = 1.0f;
  461. break;
  462. case kWindowParameterUpdateRateLimit:
  463. parameter.name = "Update rate limit";
  464. parameter.symbol = "rateLimit";
  465. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  466. parameter.ranges.def = 0.0f;
  467. parameter.ranges.min = 0.0f;
  468. parameter.ranges.max = 2.0f;
  469. parameter.enumValues.count = 3;
  470. parameter.enumValues.restrictedMode = true;
  471. parameter.enumValues.values = new ParameterEnumerationValue[3];
  472. parameter.enumValues.values[0].label = "None";
  473. parameter.enumValues.values[0].value = 0.0f;
  474. parameter.enumValues.values[1].label = "2x";
  475. parameter.enumValues.values[1].value = 1.0f;
  476. parameter.enumValues.values[2].label = "4x";
  477. parameter.enumValues.values[2].value = 2.0f;
  478. break;
  479. case kWindowParameterBrowserSort:
  480. parameter.name = "Browser sort";
  481. parameter.symbol = "browserSort";
  482. parameter.hints = kParameterIsAutomatable|kParameterIsInteger;
  483. parameter.ranges.def = 3.0f;
  484. parameter.ranges.min = 0.0f;
  485. parameter.ranges.max = 5.0f;
  486. parameter.enumValues.count = 6;
  487. parameter.enumValues.restrictedMode = true;
  488. parameter.enumValues.values = new ParameterEnumerationValue[6];
  489. parameter.enumValues.values[0].label = "Updated";
  490. parameter.enumValues.values[0].value = 0.0f;
  491. parameter.enumValues.values[1].label = "Last used";
  492. parameter.enumValues.values[1].value = 1.0f;
  493. parameter.enumValues.values[2].label = "Most used";
  494. parameter.enumValues.values[2].value = 2.0f;
  495. parameter.enumValues.values[3].label = "Brand";
  496. parameter.enumValues.values[3].value = 3.0f;
  497. parameter.enumValues.values[4].label = "Name";
  498. parameter.enumValues.values[4].value = 4.0f;
  499. parameter.enumValues.values[5].label = "Random";
  500. parameter.enumValues.values[5].value = 5.0f;
  501. break;
  502. case kWindowParameterBrowserZoom:
  503. parameter.name = "Browser zoom";
  504. parameter.symbol = "browserZoom";
  505. parameter.hints = kParameterIsAutomatable;
  506. parameter.unit = "%";
  507. parameter.ranges.def = 50.0f;
  508. parameter.ranges.min = 25.0f;
  509. parameter.ranges.max = 200.0f;
  510. parameter.enumValues.count = 7;
  511. parameter.enumValues.restrictedMode = true;
  512. parameter.enumValues.values = new ParameterEnumerationValue[7];
  513. parameter.enumValues.values[0].label = "25";
  514. parameter.enumValues.values[0].value = 25.0f;
  515. parameter.enumValues.values[1].label = "35";
  516. parameter.enumValues.values[1].value = 35.0f;
  517. parameter.enumValues.values[2].label = "50";
  518. parameter.enumValues.values[2].value = 50.0f;
  519. parameter.enumValues.values[3].label = "71";
  520. parameter.enumValues.values[3].value = 71.0f;
  521. parameter.enumValues.values[4].label = "100";
  522. parameter.enumValues.values[4].value = 100.0f;
  523. parameter.enumValues.values[5].label = "141";
  524. parameter.enumValues.values[5].value = 141.0f;
  525. parameter.enumValues.values[6].label = "200";
  526. parameter.enumValues.values[6].value = 200.0f;
  527. break;
  528. case kWindowParameterInvertZoom:
  529. parameter.name = "Invert zoom";
  530. parameter.symbol = "invertZoom";
  531. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  532. parameter.ranges.def = 0.0f;
  533. parameter.ranges.min = 0.0f;
  534. parameter.ranges.max = 1.0f;
  535. break;
  536. case kWindowParameterSqueezeModulePositions:
  537. parameter.name = "Auto-squeeze module positions";
  538. parameter.symbol = "squeezeModules";
  539. parameter.hints = kParameterIsAutomatable|kParameterIsInteger|kParameterIsBoolean;
  540. parameter.ranges.def = 1.0f;
  541. parameter.ranges.min = 0.0f;
  542. parameter.ranges.max = 1.0f;
  543. break;
  544. }
  545. #endif
  546. }
  547. void initState(const uint32_t index, State& state) override
  548. {
  549. switch (index)
  550. {
  551. case 0:
  552. #if CARDINAL_VARIANT_MINI
  553. state.hints = kStateIsHostWritable;
  554. #else
  555. state.hints = kStateIsBase64Blob;
  556. #endif
  557. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  558. state.hints |= kStateIsOnlyForDSP;
  559. #endif
  560. if (FILE* const f = std::fopen(context->patch->factoryTemplatePath.c_str(), "r"))
  561. {
  562. std::fseek(f, 0, SEEK_END);
  563. if (const long fileSize = std::ftell(f))
  564. {
  565. std::fseek(f, 0, SEEK_SET);
  566. char* const fileContent = new char[fileSize+1];
  567. if (std::fread(fileContent, fileSize, 1, f) == 1)
  568. {
  569. fileContent[fileSize] = '\0';
  570. #if CARDINAL_VARIANT_MINI
  571. state.defaultValue = fileContent;
  572. #else
  573. state.defaultValue = String::asBase64(fileContent, fileSize);
  574. #endif
  575. }
  576. delete[] fileContent;
  577. }
  578. std::fclose(f);
  579. }
  580. state.key = "patch";
  581. state.label = "Patch";
  582. break;
  583. case 1:
  584. state.hints = kStateIsHostReadable | kStateIsBase64Blob;
  585. state.key = "screenshot";
  586. state.label = "Screenshot";
  587. break;
  588. case 2:
  589. state.hints = kStateIsHostWritable;
  590. state.key = "comment";
  591. state.label = "Comment";
  592. break;
  593. case 3:
  594. state.hints = 0x0;
  595. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  596. state.hints |= kStateIsOnlyForDSP;
  597. #endif
  598. state.defaultValue = "{}";
  599. state.key = "moduleInfos";
  600. state.label = "moduleInfos";
  601. break;
  602. case 4:
  603. state.hints = kStateIsOnlyForUI;
  604. // state.defaultValue = String("%d:%d", DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT);
  605. state.key = "windowSize";
  606. state.label = "Window size";
  607. break;
  608. }
  609. }
  610. /* --------------------------------------------------------------------------------------------------------
  611. * Internal data */
  612. float getParameterValue(uint32_t index) const override
  613. {
  614. // host mapped parameters
  615. if (index < kModuleParameters)
  616. return context->parameters[index];
  617. // bypass
  618. if (index == kModuleParameters)
  619. return context->bypassed ? 1.0f : 0.0f;
  620. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  621. // window related parameters
  622. index -= kModuleParameters + 1;
  623. if (index < kWindowParameterCount)
  624. return fWindowParameters[index];
  625. #endif
  626. return 0.0f;
  627. }
  628. void setParameterValue(uint32_t index, float value) override
  629. {
  630. // host mapped parameters
  631. if (index < kModuleParameters)
  632. {
  633. context->parameters[index] = value;
  634. return;
  635. }
  636. // bypass
  637. if (index == kModuleParameters)
  638. {
  639. context->bypassed = value > 0.5f;
  640. return;
  641. }
  642. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  643. // window related parameters
  644. index -= kModuleParameters + 1;
  645. if (index < kWindowParameterCount)
  646. {
  647. fWindowParameters[index] = value;
  648. return;
  649. }
  650. #endif
  651. }
  652. String getState(const char* const key) const override
  653. {
  654. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  655. if (std::strcmp(key, "moduleInfos") == 0)
  656. {
  657. json_t* const rootJ = json_object();
  658. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, String());
  659. for (const auto& pluginPair : rack::settings::moduleInfos)
  660. {
  661. json_t* const pluginJ = json_object();
  662. DISTRHO_SAFE_ASSERT_CONTINUE(pluginJ != nullptr);
  663. for (const auto& modulePair : pluginPair.second)
  664. {
  665. json_t* const moduleJ = json_object();
  666. DISTRHO_SAFE_ASSERT_CONTINUE(moduleJ != nullptr);
  667. const rack::settings::ModuleInfo& m(modulePair.second);
  668. // To make setting.json smaller, only set properties if not default values.
  669. if (m.favorite)
  670. json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
  671. if (m.added > 0)
  672. json_object_set_new(moduleJ, "added", json_integer(m.added));
  673. if (std::isfinite(m.lastAdded))
  674. json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
  675. if (json_object_size(moduleJ))
  676. json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
  677. else
  678. json_decref(moduleJ);
  679. }
  680. if (json_object_size(pluginJ))
  681. json_object_set_new(rootJ, pluginPair.first.c_str(), pluginJ);
  682. else
  683. json_decref(pluginJ);
  684. }
  685. const String info(json_dumps(rootJ, JSON_COMPACT), false);
  686. json_decref(rootJ);
  687. return info;
  688. }
  689. if (std::strcmp(key, "windowSize") == 0)
  690. return fState.windowSize;
  691. #endif
  692. if (std::strcmp(key, "comment") == 0)
  693. return fState.comment;
  694. if (std::strcmp(key, "screenshot") == 0)
  695. return fState.screenshot;
  696. if (std::strcmp(key, "patch") != 0)
  697. return String();
  698. if (fAutosavePath.empty())
  699. return String();
  700. std::vector<uint8_t> data;
  701. {
  702. const ScopedContext sc(this);
  703. context->engine->prepareSave();
  704. context->patch->saveAutosave();
  705. context->patch->cleanAutosave();
  706. // context->history->setSaved();
  707. #if CARDINAL_VARIANT_MINI
  708. #else
  709. try {
  710. data = rack::system::archiveDirectory(fAutosavePath, 1);
  711. } DISTRHO_SAFE_EXCEPTION_RETURN("getState archiveDirectory", String());
  712. #endif
  713. }
  714. return String::asBase64(data.data(), data.size());
  715. }
  716. void setState(const char* const key, const char* const value) override
  717. {
  718. #if CARDINAL_VARIANT_MINI
  719. if (std::strcmp(key, "param") == 0)
  720. {
  721. longlong moduleId = 0;
  722. int paramId = 0;
  723. float paramValue = 0.f;
  724. std::sscanf(value, "%lld:%d:%f", &moduleId, &paramId, &paramValue);
  725. rack::engine::Module* const module = context->engine->getModule(moduleId);
  726. DISTRHO_SAFE_ASSERT_RETURN(module != nullptr,);
  727. context->engine->setParamValue(module, paramId, paramValue);
  728. return;
  729. }
  730. #endif
  731. #if CARDINAL_VARIANT_MINI || !defined(HEADLESS)
  732. if (std::strcmp(key, "moduleInfos") == 0)
  733. {
  734. json_error_t error;
  735. json_t* const rootJ = json_loads(value, 0, &error);
  736. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr,);
  737. const char* pluginSlug;
  738. json_t* pluginJ;
  739. json_object_foreach(rootJ, pluginSlug, pluginJ)
  740. {
  741. const char* moduleSlug;
  742. json_t* moduleJ;
  743. json_object_foreach(pluginJ, moduleSlug, moduleJ)
  744. {
  745. rack::settings::ModuleInfo m;
  746. if (json_t* const favoriteJ = json_object_get(moduleJ, "favorite"))
  747. m.favorite = json_boolean_value(favoriteJ);
  748. if (json_t* const addedJ = json_object_get(moduleJ, "added"))
  749. m.added = json_integer_value(addedJ);
  750. if (json_t* const lastAddedJ = json_object_get(moduleJ, "lastAdded"))
  751. m.lastAdded = json_number_value(lastAddedJ);
  752. rack::settings::moduleInfos[pluginSlug][moduleSlug] = m;
  753. }
  754. }
  755. json_decref(rootJ);
  756. return;
  757. }
  758. if (std::strcmp(key, "windowSize") == 0)
  759. {
  760. fState.windowSize = value;
  761. return;
  762. }
  763. #endif
  764. if (std::strcmp(key, "comment") == 0)
  765. {
  766. fState.comment = value;
  767. return;
  768. }
  769. if (std::strcmp(key, "screenshot") == 0)
  770. {
  771. fState.screenshot = value;
  772. return;
  773. }
  774. if (std::strcmp(key, "patch") != 0)
  775. return;
  776. if (fAutosavePath.empty())
  777. return;
  778. #if CARDINAL_VARIANT_MINI
  779. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  780. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  781. rack::system::removeRecursively(fAutosavePath);
  782. rack::system::createDirectories(fAutosavePath);
  783. std::fwrite(value, std::strlen(value)+1, 1, f);
  784. std::fclose(f);
  785. #else
  786. const std::vector<uint8_t> data(d_getChunkFromBase64String(value));
  787. DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,);
  788. rack::system::removeRecursively(fAutosavePath);
  789. rack::system::createDirectories(fAutosavePath);
  790. static constexpr const char zstdMagic[] = "\x28\xb5\x2f\xfd";
  791. if (std::memcmp(data.data(), zstdMagic, sizeof(zstdMagic)) != 0)
  792. {
  793. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  794. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  795. std::fwrite(data.data(), data.size(), 1, f);
  796. std::fclose(f);
  797. }
  798. else
  799. {
  800. try {
  801. rack::system::unarchiveToDirectory(data, fAutosavePath);
  802. } DISTRHO_SAFE_EXCEPTION_RETURN("setState unarchiveToDirectory",);
  803. }
  804. #endif
  805. const ScopedContext sc(this);
  806. try {
  807. context->patch->loadAutosave();
  808. } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",);
  809. // context->history->setSaved();
  810. }
  811. /* --------------------------------------------------------------------------------------------------------
  812. * Process */
  813. void activate() override
  814. {
  815. context->bufferSize = getBufferSize();
  816. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  817. fAudioBufferCopy = new float*[DISTRHO_PLUGIN_NUM_INPUTS];
  818. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  819. fAudioBufferCopy[i] = new float[context->bufferSize];
  820. #endif
  821. fNextExpectedFrame = 0;
  822. }
  823. void deactivate() override
  824. {
  825. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  826. if (fAudioBufferCopy != nullptr)
  827. {
  828. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  829. delete[] fAudioBufferCopy[i];
  830. delete[] fAudioBufferCopy;
  831. fAudioBufferCopy = nullptr;
  832. }
  833. #endif
  834. }
  835. void run(const float** const inputs, float** const outputs, const uint32_t frames,
  836. const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  837. {
  838. rack::contextSet(context);
  839. const bool bypassed = context->bypassed;
  840. {
  841. const TimePosition& timePos(getTimePosition());
  842. bool reset = timePos.playing && (timePos.frame == 0 || d_isDiffHigherThanLimit(fNextExpectedFrame, timePos.frame, (uint64_t)2));
  843. // ignore hosts which cannot supply time frame position
  844. if (context->playing == timePos.playing && timePos.frame == 0 && context->frame == 0)
  845. reset = false;
  846. context->playing = timePos.playing;
  847. context->bbtValid = timePos.bbt.valid;
  848. context->frame = timePos.frame;
  849. if (timePos.bbt.valid)
  850. {
  851. const double samplesPerTick = 60.0 * getSampleRate()
  852. / timePos.bbt.beatsPerMinute
  853. / timePos.bbt.ticksPerBeat;
  854. context->bar = timePos.bbt.bar;
  855. context->beat = timePos.bbt.beat;
  856. context->beatsPerBar = timePos.bbt.beatsPerBar;
  857. context->beatType = timePos.bbt.beatType;
  858. context->barStartTick = timePos.bbt.barStartTick;
  859. context->beatsPerMinute = timePos.bbt.beatsPerMinute;
  860. context->tick = timePos.bbt.tick;
  861. context->ticksPerBeat = timePos.bbt.ticksPerBeat;
  862. context->ticksPerClock = timePos.bbt.ticksPerBeat / timePos.bbt.beatType;
  863. context->ticksPerFrame = 1.0 / samplesPerTick;
  864. context->tickClock = std::fmod(timePos.bbt.tick, context->ticksPerClock);
  865. }
  866. context->reset = reset;
  867. fNextExpectedFrame = timePos.playing ? timePos.frame + frames : 0;
  868. }
  869. // separate buffers, use them
  870. if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
  871. {
  872. context->dataIns = inputs;
  873. context->dataOuts = outputs;
  874. }
  875. // inline processing, use a safe copy
  876. else
  877. {
  878. #if DISTRHO_PLUGIN_NUM_INPUTS != 0
  879. for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
  880. {
  881. #if CARDINAL_VARIANT_MAIN
  882. // can be null on main variant
  883. if (inputs[i] != nullptr)
  884. #endif
  885. std::memcpy(fAudioBufferCopy[i], inputs[i], sizeof(float)*frames);
  886. }
  887. context->dataIns = fAudioBufferCopy;
  888. #else
  889. context->dataIns = nullptr;
  890. #endif
  891. context->dataOuts = outputs;
  892. }
  893. for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
  894. {
  895. #if CARDINAL_VARIANT_MAIN
  896. // can be null on main variant
  897. if (outputs[i] != nullptr)
  898. #endif
  899. std::memset(outputs[i], 0, sizeof(float)*frames);
  900. }
  901. if (bypassed)
  902. {
  903. if (fWasBypassed != bypassed)
  904. {
  905. context->midiEvents = bypassMidiEvents;
  906. context->midiEventCount = 16;
  907. }
  908. else
  909. {
  910. context->midiEvents = nullptr;
  911. context->midiEventCount = 0;
  912. }
  913. }
  914. else
  915. {
  916. context->midiEvents = midiEvents;
  917. context->midiEventCount = midiEventCount;
  918. }
  919. ++context->processCounter;
  920. context->engine->stepBlock(frames);
  921. fWasBypassed = bypassed;
  922. }
  923. void sampleRateChanged(const double newSampleRate) override
  924. {
  925. rack::contextSet(context);
  926. rack::settings::sampleRate = newSampleRate;
  927. context->sampleRate = newSampleRate;
  928. context->engine->setSampleRate(newSampleRate);
  929. }
  930. // -------------------------------------------------------------------------------------------------------
  931. private:
  932. /**
  933. Set our plugin class as non-copyable and add a leak detector just in case.
  934. */
  935. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalPlugin)
  936. };
  937. CardinalPluginContext* getRackContextFromPlugin(void* const ptr)
  938. {
  939. return static_cast<CardinalPlugin*>(ptr)->getRackContext();
  940. }
  941. /* ------------------------------------------------------------------------------------------------------------
  942. * Plugin entry point, called by DPF to create a new plugin instance. */
  943. Plugin* createPlugin()
  944. {
  945. return new CardinalPlugin();
  946. }
  947. // --------------------------------------------------------------------------------------------------------------------
  948. END_NAMESPACE_DISTRHO