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.

1287 lines
36KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2024 Filipe Coelho <falktx@falktx.com>
  4. * SPDX-License-Identifier: GPL-3.0-or-later
  5. */
  6. /**
  7. * This file is partially based on VCVRack's patch.cpp
  8. * Copyright (C) 2016-2021 VCV.
  9. *
  10. * This program is free software: you can redistribute it and/or
  11. * modify it under the terms of the GNU General Public License as
  12. * published by the Free Software Foundation; either version 3 of
  13. * the License, or (at your option) any later version.
  14. */
  15. #include "CardinalCommon.hpp"
  16. #include "AsyncDialog.hpp"
  17. #include "CardinalPluginContext.hpp"
  18. #include "DistrhoPluginUtils.hpp"
  19. #include <asset.hpp>
  20. #include <context.hpp>
  21. #include <history.hpp>
  22. #include <patch.hpp>
  23. #include <settings.hpp>
  24. #include <string.hpp>
  25. #include <system.hpp>
  26. #include <app/Browser.hpp>
  27. #include <app/Scene.hpp>
  28. #include <engine/Engine.hpp>
  29. #include <window/Window.hpp>
  30. #ifndef DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  31. # error wrong build
  32. #endif
  33. #if (defined(STATIC_BUILD) && !defined(__MOD_DEVICES__)) || CARDINAL_VARIANT_MINI
  34. # undef CARDINAL_INIT_OSC_THREAD
  35. #endif
  36. #ifdef NDEBUG
  37. # undef DEBUG
  38. #endif
  39. // for finding special paths
  40. #ifdef ARCH_WIN
  41. # include <shlobj.h>
  42. #else
  43. # include <pwd.h>
  44. # include <unistd.h>
  45. #endif
  46. #ifdef ARCH_LIN
  47. # include <fstream>
  48. #endif
  49. #ifdef HAVE_LIBLO
  50. # include <lo/lo.h>
  51. #endif
  52. #ifdef DISTRHO_OS_WASM
  53. # include <emscripten/emscripten.h>
  54. #endif
  55. #if defined(CARDINAL_COMMON_DSP_ONLY) || defined(HEADLESS)
  56. # define HEADLESS_BEHAVIOUR
  57. #endif
  58. #if CARDINAL_VARIANT_FX
  59. # define CARDINAL_VARIANT_NAME "fx"
  60. #elif CARDINAL_VARIANT_MINI
  61. # define CARDINAL_VARIANT_NAME "mini"
  62. #elif CARDINAL_VARIANT_NATIVE
  63. # define CARDINAL_VARIANT_NAME "native"
  64. #elif CARDINAL_VARIANT_SYNTH
  65. # define CARDINAL_VARIANT_NAME "synth"
  66. #else
  67. # define CARDINAL_VARIANT_NAME "main"
  68. #endif
  69. #ifdef DISTRHO_OS_WASM
  70. # if CARDINAL_VARIANT_MINI
  71. # define CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME "welcome-wasm-mini"
  72. # else
  73. # define CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME "welcome-wasm"
  74. # endif
  75. #endif
  76. namespace rack {
  77. namespace asset {
  78. std::string patchesPath();
  79. void destroy();
  80. }
  81. namespace plugin {
  82. void initStaticPlugins();
  83. void destroyStaticPlugins();
  84. }
  85. }
  86. const std::string CARDINAL_VERSION = "24.04";
  87. // -----------------------------------------------------------------------------------------------------------
  88. #ifndef HEADLESS
  89. void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started)
  90. {
  91. DISTRHO_SAFE_ASSERT_RETURN(pcontext->ui != nullptr,);
  92. #ifndef CARDINAL_COMMON_DSP_ONLY
  93. if (started)
  94. {
  95. pcontext->ui->editParameter(index, true);
  96. pcontext->ui->setParameterValue(index, pcontext->parameters[index]);
  97. }
  98. else
  99. {
  100. pcontext->ui->editParameter(index, false);
  101. }
  102. #endif
  103. }
  104. #endif
  105. // --------------------------------------------------------------------------------------------------------------------
  106. CardinalPluginContext::CardinalPluginContext(Plugin* const p)
  107. : bufferSize(p != nullptr ? p->getBufferSize() : 0),
  108. processCounter(0),
  109. sampleRate(p != nullptr ? p->getSampleRate() : 0.0),
  110. #if CARDINAL_VARIANT_MAIN
  111. variant(kCardinalVariantMain),
  112. #elif CARDINAL_VARIANT_MINI
  113. variant(kCardinalVariantMini),
  114. #elif CARDINAL_VARIANT_FX
  115. variant(kCardinalVariantFX),
  116. #elif CARDINAL_VARIANT_NATIVE
  117. variant(kCardinalVariantNative),
  118. #elif CARDINAL_VARIANT_SYNTH
  119. variant(kCardinalVariantSynth),
  120. #else
  121. #error cardinal variant not set
  122. #endif
  123. bypassed(false),
  124. playing(false),
  125. reset(false),
  126. bbtValid(false),
  127. bar(1),
  128. beat(1),
  129. beatsPerBar(4),
  130. beatType(4),
  131. frame(0),
  132. barStartTick(0.0),
  133. beatsPerMinute(120.0),
  134. tick(0.0),
  135. tickClock(0.0),
  136. ticksPerBeat(0.0),
  137. ticksPerClock(0.0),
  138. ticksPerFrame(0.0),
  139. nativeWindowId(0),
  140. dataIns(nullptr),
  141. dataOuts(nullptr),
  142. midiEvents(nullptr),
  143. midiEventCount(0),
  144. plugin(p)
  145. #ifndef HEADLESS
  146. , tlw(nullptr)
  147. , ui(nullptr)
  148. #endif
  149. {
  150. std::memset(parameters, 0, sizeof(parameters));
  151. }
  152. #ifndef HEADLESS
  153. bool CardinalPluginContext::addIdleCallback(IdleCallback* const cb) const
  154. {
  155. #ifndef CARDINAL_COMMON_DSP_ONLY
  156. if (ui != nullptr)
  157. {
  158. ui->addIdleCallback(cb);
  159. return true;
  160. }
  161. #else
  162. // unused
  163. (void)cb;
  164. #endif
  165. return false;
  166. }
  167. void CardinalPluginContext::removeIdleCallback(IdleCallback* const cb) const
  168. {
  169. #ifndef CARDINAL_COMMON_DSP_ONLY
  170. if (ui != nullptr)
  171. ui->removeIdleCallback(cb);
  172. #else
  173. // unused
  174. (void)cb;
  175. #endif
  176. }
  177. #endif
  178. void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message, const uint8_t channel)
  179. {
  180. if (bypassed)
  181. return;
  182. const size_t size = message.bytes.size();
  183. DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
  184. DISTRHO_SAFE_ASSERT_RETURN(message.frame >= 0,);
  185. MidiEvent event;
  186. event.frame = message.frame;
  187. switch (message.bytes[0] & 0xF0)
  188. {
  189. case 0x80:
  190. case 0x90:
  191. case 0xA0:
  192. case 0xB0:
  193. case 0xE0:
  194. event.size = 3;
  195. break;
  196. case 0xC0:
  197. case 0xD0:
  198. event.size = 2;
  199. break;
  200. case 0xF0:
  201. switch (message.bytes[0] & 0x0F)
  202. {
  203. case 0x0:
  204. case 0x4:
  205. case 0x5:
  206. case 0x7:
  207. case 0x9:
  208. case 0xD:
  209. // unsupported
  210. return;
  211. case 0x1:
  212. case 0x2:
  213. case 0x3:
  214. case 0xE:
  215. event.size = 3;
  216. break;
  217. case 0x6:
  218. case 0x8:
  219. case 0xA:
  220. case 0xB:
  221. case 0xC:
  222. case 0xF:
  223. event.size = 1;
  224. break;
  225. }
  226. break;
  227. default:
  228. // invalid
  229. return;
  230. }
  231. DISTRHO_SAFE_ASSERT_RETURN(size >= event.size,);
  232. std::memcpy(event.data, message.bytes.data(), event.size);
  233. if (channel != 0 && event.data[0] < 0xF0)
  234. event.data[0] |= channel & 0x0F;
  235. plugin->writeMidiEvent(event);
  236. }
  237. // -----------------------------------------------------------------------------------------------------------
  238. namespace rack {
  239. namespace midi {
  240. struct InputQueue::Internal {
  241. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  242. const CardinalDISTRHO::MidiEvent* midiEvents = nullptr;
  243. uint32_t midiEventsLeft = 0;
  244. uint32_t lastProcessCounter = 0;
  245. int64_t lastBlockFrame = 0;
  246. };
  247. InputQueue::InputQueue() {
  248. internal = new Internal;
  249. }
  250. InputQueue::~InputQueue() {
  251. delete internal;
  252. }
  253. bool InputQueue::tryPop(Message* const messageOut, int64_t maxFrame)
  254. {
  255. const uint32_t processCounter = internal->pcontext->processCounter;
  256. const bool processCounterChanged = internal->lastProcessCounter != processCounter;
  257. if (processCounterChanged)
  258. {
  259. internal->lastBlockFrame = internal->pcontext->engine->getBlockFrame();
  260. internal->lastProcessCounter = processCounter;
  261. internal->midiEvents = internal->pcontext->midiEvents;
  262. internal->midiEventsLeft = internal->pcontext->midiEventCount;
  263. }
  264. if (internal->midiEventsLeft == 0 || maxFrame < internal->lastBlockFrame)
  265. return false;
  266. const uint32_t frame = maxFrame - internal->lastBlockFrame;
  267. if (frame > internal->midiEvents->frame)
  268. return false;
  269. const CardinalDISTRHO::MidiEvent& midiEvent(*internal->midiEvents);
  270. const uint8_t* data;
  271. if (midiEvent.size > CardinalDISTRHO::MidiEvent::kDataSize)
  272. {
  273. data = midiEvent.dataExt;
  274. messageOut->bytes.resize(midiEvent.size);
  275. }
  276. else
  277. {
  278. data = midiEvent.data;
  279. }
  280. messageOut->frame = frame;
  281. std::memcpy(messageOut->bytes.data(), data, midiEvent.size);
  282. ++internal->midiEvents;
  283. --internal->midiEventsLeft;
  284. return true;
  285. }
  286. json_t* InputQueue::toJson() const
  287. {
  288. return nullptr;
  289. }
  290. void InputQueue::fromJson(json_t* rootJ)
  291. {
  292. }
  293. }
  294. }
  295. // -----------------------------------------------------------------------------------------------------------
  296. START_NAMESPACE_DISTRHO
  297. // -----------------------------------------------------------------------------------------------------------
  298. #ifdef HAVE_LIBLO
  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("Hello received from OSC, saying hello back to them o/");
  311. const lo_address source = lo_message_get_source(m);
  312. const lo_server server = static_cast<Initializer*>(self)->oscServer;
  313. lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok");
  314. return 0;
  315. }
  316. static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  317. {
  318. d_debug("osc_load_handler()");
  319. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  320. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  321. const int32_t size = argv[0]->blob.size;
  322. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  323. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  324. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  325. bool ok = false;
  326. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  327. {
  328. CardinalPluginContext* const context = plugin->context;
  329. std::vector<uint8_t> data(size);
  330. std::memcpy(data.data(), blob, size);
  331. rack::contextSet(context);
  332. rack::system::removeRecursively(context->patch->autosavePath);
  333. rack::system::createDirectories(context->patch->autosavePath);
  334. try {
  335. rack::system::unarchiveToDirectory(data, context->patch->autosavePath);
  336. context->patch->loadAutosave();
  337. ok = true;
  338. }
  339. catch (rack::Exception& e) {
  340. WARN("%s", e.what());
  341. }
  342. rack::contextSet(nullptr);
  343. }
  344. const lo_address source = lo_message_get_source(m);
  345. const lo_server server = static_cast<Initializer*>(self)->oscServer;
  346. lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail");
  347. return 0;
  348. }
  349. static int osc_param_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  350. {
  351. d_debug("osc_param_handler()");
  352. DISTRHO_SAFE_ASSERT_RETURN(argc == 3, 0);
  353. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr, 0);
  354. DISTRHO_SAFE_ASSERT_RETURN(types[0] == 'h', 0);
  355. DISTRHO_SAFE_ASSERT_RETURN(types[1] == 'i', 0);
  356. DISTRHO_SAFE_ASSERT_RETURN(types[2] == 'f', 0);
  357. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  358. {
  359. CardinalPluginContext* const context = plugin->context;
  360. const int64_t moduleId = argv[0]->h;
  361. const int paramId = argv[1]->i;
  362. const float paramValue = argv[2]->f;
  363. rack::engine::Module* const module = context->engine->getModule(moduleId);
  364. DISTRHO_SAFE_ASSERT_RETURN(module != nullptr, 0);
  365. context->engine->setParamValue(module, paramId, paramValue);
  366. }
  367. return 0;
  368. }
  369. static int osc_host_param_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  370. {
  371. d_debug("osc_host_param_handler()");
  372. DISTRHO_SAFE_ASSERT_RETURN(argc == 2, 0);
  373. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr, 0);
  374. DISTRHO_SAFE_ASSERT_RETURN(types[0] == 'i', 0);
  375. DISTRHO_SAFE_ASSERT_RETURN(types[1] == 'f', 0);
  376. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  377. {
  378. CardinalPluginContext* const context = plugin->context;
  379. const int paramId = argv[0]->i;
  380. DISTRHO_SAFE_ASSERT_RETURN(paramId >= 0, 0);
  381. const uint uparamId = static_cast<uint>(paramId);
  382. DISTRHO_SAFE_ASSERT_UINT2_RETURN(uparamId < kModuleParameterCount, uparamId, kModuleParameterCount, 0);
  383. const float paramValue = argv[1]->f;
  384. context->parameters[uparamId] = paramValue;
  385. }
  386. return 0;
  387. }
  388. # ifdef CARDINAL_INIT_OSC_THREAD
  389. static int osc_screenshot_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  390. {
  391. d_debug("osc_screenshot_handler()");
  392. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  393. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  394. const int32_t size = argv[0]->blob.size;
  395. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  396. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  397. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  398. bool ok = false;
  399. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  400. {
  401. if (char* const screenshot = String::asBase64(blob, size).getAndReleaseBuffer())
  402. {
  403. ok = plugin->updateStateValue("screenshot", screenshot);
  404. std::free(screenshot);
  405. }
  406. }
  407. const lo_address source = lo_message_get_source(m);
  408. const lo_server server = static_cast<Initializer*>(self)->oscServer;
  409. lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "screenshot", ok ? "ok" : "fail");
  410. return 0;
  411. }
  412. # endif
  413. #endif
  414. // -----------------------------------------------------------------------------------------------------------
  415. #if defined(DISTRHO_OS_WASM) && !defined(CARDINAL_COMMON_UI_ONLY)
  416. static void WebBrowserDataLoaded(void* const data)
  417. {
  418. static_cast<Initializer*>(data)->loadSettings(true);
  419. }
  420. #endif
  421. // -----------------------------------------------------------------------------------------------------------
  422. Initializer::Initializer(const CardinalBasePlugin* const plugin, const CardinalBaseUI* const ui)
  423. {
  424. using namespace rack;
  425. // Cardinal default settings, potentially overriding VCV Rack ones
  426. settings::allowCursorLock = false;
  427. settings::tooltips = true;
  428. settings::cableOpacity = 0.5f;
  429. settings::cableTension = 0.75f;
  430. settings::rackBrightness = 1.0f;
  431. settings::haloBrightness = 0.25f;
  432. settings::knobMode = settings::KNOB_MODE_LINEAR;
  433. settings::knobScroll = false;
  434. settings::knobScrollSensitivity = 0.001f;
  435. settings::lockModules = false;
  436. settings::browserSort = settings::BROWSER_SORT_UPDATED;
  437. settings::browserZoom = -1.f;
  438. settings::invertZoom = false;
  439. settings::squeezeModules = true;
  440. settings::preferDarkPanels = true;
  441. settings::uiTheme = "dark";
  442. // runtime behaviour
  443. settings::devMode = true;
  444. settings::isPlugin = true;
  445. #ifdef HEADLESS_BEHAVIOUR
  446. settings::headless = true;
  447. #endif
  448. // copied from https://community.vcvrack.com/t/16-colour-cable-palette/15951
  449. settings::cableColors = {
  450. color::fromHexString("#ff5252"),
  451. color::fromHexString("#ff9352"),
  452. color::fromHexString("#ffd452"),
  453. color::fromHexString("#e8ff52"),
  454. color::fromHexString("#a8ff52"),
  455. color::fromHexString("#67ff52"),
  456. color::fromHexString("#52ff7d"),
  457. color::fromHexString("#52ffbe"),
  458. color::fromHexString("#52ffff"),
  459. color::fromHexString("#52beff"),
  460. color::fromHexString("#527dff"),
  461. color::fromHexString("#6752ff"),
  462. color::fromHexString("#a852ff"),
  463. color::fromHexString("#e952ff"),
  464. color::fromHexString("#ff52d4"),
  465. color::fromHexString("#ff5293"),
  466. };
  467. system::init();
  468. logger::init();
  469. random::init();
  470. ui::init();
  471. #ifdef CARDINAL_COMMON_UI_ONLY
  472. constexpr const bool isRealInstance = true;
  473. #else
  474. const bool isRealInstance = !plugin->isDummyInstance();
  475. #endif
  476. if (asset::systemDir.empty())
  477. {
  478. if (const char* const bundlePath = (plugin != nullptr ? plugin->getBundlePath() :
  479. #if DISTRHO_PLUGIN_HAS_UI
  480. ui != nullptr ? ui->getBundlePath() :
  481. #endif
  482. nullptr))
  483. {
  484. if (const char* const resourcePath = getResourcePath(bundlePath))
  485. {
  486. asset::systemDir = resourcePath;
  487. asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
  488. }
  489. }
  490. if (asset::systemDir.empty() || ! system::exists(asset::systemDir) || ! system::exists(asset::bundlePath))
  491. {
  492. #ifdef CARDINAL_PLUGIN_SOURCE_DIR
  493. // Make system dir point to source code location as fallback
  494. asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack";
  495. asset::bundlePath.clear();
  496. // If source code dir does not exist use install target prefix as system dir
  497. if (!system::exists(system::join(asset::systemDir, "res")))
  498. #endif
  499. {
  500. #if defined(DISTRHO_OS_WASM)
  501. asset::systemDir = "/resources";
  502. #elif defined(ARCH_MAC)
  503. asset::systemDir = "/Library/Application Support/Cardinal";
  504. #elif defined(ARCH_WIN)
  505. asset::systemDir = system::join(getSpecialPath(kSpecialPathCommonProgramFiles), "Cardinal");
  506. #else
  507. asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal";
  508. #endif
  509. asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
  510. }
  511. }
  512. }
  513. if (asset::userDir.empty())
  514. {
  515. #if defined(DISTRHO_OS_WASM)
  516. asset::userDir = "/userfiles";
  517. #elif defined(ARCH_MAC)
  518. asset::userDir = system::join(homeDir(), "Documents", "Cardinal");
  519. #elif defined(ARCH_WIN)
  520. asset::userDir = system::join(getSpecialPath(kSpecialPathMyDocuments), "Cardinal");
  521. #else
  522. std::string xdgConfigDir;
  523. if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME"))
  524. xdgConfigDir = xdgEnv;
  525. if (xdgConfigDir.empty())
  526. xdgConfigDir = system::join(homeDir(), ".config");
  527. const std::string xdgDirsConfigPath(system::join(xdgConfigDir, "user-dirs.dirs"));
  528. if (system::exists(xdgDirsConfigPath))
  529. {
  530. std::ifstream xdgDirsConfigFile(xdgDirsConfigPath, std::ios::in|std::ios::ate);
  531. std::string xdgDirsConfig(xdgDirsConfigFile.tellg(), 0);
  532. xdgDirsConfigFile.seekg(0);
  533. xdgDirsConfigFile.read(&xdgDirsConfig[0], xdgDirsConfig.size());
  534. if (const char* const xdgDocsDir = std::strstr(xdgDirsConfig.c_str(), "XDG_DOCUMENTS_DIR=\""))
  535. {
  536. if (const char* const xdgDocsDirNL = std::strstr(xdgDocsDir, "\"\n"))
  537. {
  538. asset::userDir = std::string(xdgDocsDir + 19, xdgDocsDirNL - xdgDocsDir - 19);
  539. if (string::startsWith(asset::userDir, "$HOME"))
  540. asset::userDir.replace(asset::userDir.begin(), asset::userDir.begin() + 5, homeDir());
  541. if (! system::exists(asset::userDir))
  542. asset::userDir.clear();
  543. }
  544. }
  545. }
  546. if (asset::userDir.empty())
  547. asset::userDir = system::join(homeDir(), "Documents", "Cardinal");
  548. #endif
  549. if (isRealInstance)
  550. {
  551. system::createDirectory(asset::userDir);
  552. #if defined(DISTRHO_OS_WASM) && !defined(CARDINAL_COMMON_UI_ONLY)
  553. EM_ASM({
  554. Module.FS.mount(Module.IDBFS, {}, '/userfiles');
  555. Module.FS.syncfs(true, function(err) { if (!err) { dynCall('vi', $0, [$1]) } });
  556. }, WebBrowserDataLoaded, this);
  557. #endif
  558. }
  559. }
  560. #ifndef CARDINAL_COMMON_DSP_ONLY
  561. if (asset::configDir.empty())
  562. {
  563. #if defined(ARCH_MAC) || defined(ARCH_WIN) || defined(DISTRHO_OS_WASM)
  564. asset::configDir = asset::userDir;
  565. #else
  566. if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME"))
  567. asset::configDir = system::join(xdgEnv, "Cardinal");
  568. else
  569. asset::configDir = system::join(homeDir(), ".config", "Cardinal");
  570. if (isRealInstance)
  571. system::createDirectory(asset::configDir);
  572. #endif
  573. }
  574. #endif
  575. if (settings::settingsPath.empty())
  576. settings::settingsPath = asset::config(CARDINAL_VARIANT_NAME ".json");
  577. templatePath = asset::user("templates/" CARDINAL_VARIANT_NAME ".vcv");
  578. #ifdef DISTRHO_OS_WASM
  579. factoryTemplatePath = system::join(asset::patchesPath(), CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME ".vcv");
  580. #else
  581. factoryTemplatePath = system::join(asset::patchesPath(), "templates/" CARDINAL_VARIANT_NAME ".vcv");
  582. #endif
  583. // Log environment
  584. INFO("%s %s %s, compatible with Rack version %s", APP_NAME.c_str(), APP_EDITION.c_str(), CARDINAL_VERSION.c_str(), APP_VERSION.c_str());
  585. INFO("%s", system::getOperatingSystemInfo().c_str());
  586. INFO("Binary filename: %s", getBinaryFilename());
  587. if (plugin != nullptr) {
  588. INFO("Bundle path: %s", plugin->getBundlePath());
  589. #if DISTRHO_PLUGIN_HAS_UI
  590. } else if (ui != nullptr) {
  591. INFO("Bundle path: %s", ui->getBundlePath());
  592. #endif
  593. }
  594. INFO("System directory: %s", asset::systemDir.c_str());
  595. INFO("User directory: %s", asset::userDir.c_str());
  596. INFO("Template patch: %s", templatePath.c_str());
  597. INFO("System template patch: %s", factoryTemplatePath.c_str());
  598. // Report to user if something is wrong with the installation
  599. if (asset::systemDir.empty())
  600. {
  601. d_stderr2("Failed to locate Cardinal plugin bundle.\n"
  602. "Install Cardinal with its bundle folder intact and try again.");
  603. }
  604. else if (! system::exists(asset::systemDir))
  605. {
  606. d_stderr2("System directory \"%s\" does not exist.\n"
  607. "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
  608. }
  609. INFO("Initializing plugins");
  610. plugin::initStaticPlugins();
  611. INFO("Initializing plugin browser DB");
  612. app::browserInit();
  613. loadSettings(isRealInstance);
  614. #if defined(CARDINAL_INIT_OSC_THREAD)
  615. INFO("Initializing OSC Remote control");
  616. const char* port;
  617. if (const char* const portEnv = std::getenv("CARDINAL_REMOTE_HOST_PORT"))
  618. port = portEnv;
  619. else
  620. port = CARDINAL_DEFAULT_REMOTE_PORT;
  621. startRemoteServer(port);
  622. #elif defined(HAVE_LIBLO)
  623. if (isStandalone()) {
  624. INFO("OSC Remote control is available on request");
  625. } else {
  626. INFO("OSC Remote control is not available on plugin variants");
  627. }
  628. #else
  629. INFO("OSC Remote control is not enabled in this build");
  630. #endif
  631. }
  632. Initializer::~Initializer()
  633. {
  634. using namespace rack;
  635. #ifdef HAVE_LIBLO
  636. stopRemoteServer();
  637. #endif
  638. if (shouldSaveSettings)
  639. {
  640. INFO("Save settings");
  641. settings::save();
  642. }
  643. INFO("Clearing asset paths");
  644. asset::bundlePath.clear();
  645. asset::systemDir.clear();
  646. asset::userDir.clear();
  647. INFO("Destroying plugins");
  648. plugin::destroyStaticPlugins();
  649. INFO("Destroying colourized assets");
  650. asset::destroy();
  651. INFO("Destroying settings");
  652. settings::destroy();
  653. INFO("Destroying logger");
  654. logger::destroy();
  655. }
  656. void Initializer::loadSettings(const bool isRealInstance)
  657. {
  658. using namespace rack;
  659. if (isRealInstance)
  660. {
  661. INFO("Loading settings");
  662. settings::load();
  663. shouldSaveSettings = true;
  664. }
  665. // enforce settings that do not make sense as anything else
  666. settings::safeMode = false;
  667. settings::token.clear();
  668. settings::windowMaximized = false;
  669. settings::windowPos = math::Vec(0, 0);
  670. settings::pixelRatio = 0.0;
  671. settings::sampleRate = 0;
  672. settings::threadCount = 1;
  673. settings::autosaveInterval = 0;
  674. settings::skipLoadOnLaunch = true;
  675. settings::autoCheckUpdates = false;
  676. settings::showTipsOnLaunch = false;
  677. settings::tipIndex = -1;
  678. if (settings::uiTheme != "dark" && settings::uiTheme != "light")
  679. {
  680. settings::uiTheme = "dark";
  681. rack::ui::refreshTheme();
  682. }
  683. // reload dark/light mode as necessary
  684. switchDarkMode(settings::uiTheme == "dark");
  685. }
  686. #ifdef HAVE_LIBLO
  687. bool Initializer::startRemoteServer(const char* const port)
  688. {
  689. #ifdef CARDINAL_INIT_OSC_THREAD
  690. if (oscServerThread != nullptr)
  691. return true;
  692. if ((oscServerThread = lo_server_thread_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr)
  693. return false;
  694. oscServer = lo_server_thread_get_server(oscServerThread);
  695. lo_server_thread_add_method(oscServerThread, "/hello", "", osc_hello_handler, this);
  696. lo_server_thread_add_method(oscServerThread, "/host-param", "if", osc_host_param_handler, this);
  697. lo_server_thread_add_method(oscServerThread, "/load", "b", osc_load_handler, this);
  698. lo_server_thread_add_method(oscServerThread, "/param", "hif", osc_param_handler, this);
  699. lo_server_thread_add_method(oscServerThread, "/screenshot", "b", osc_screenshot_handler, this);
  700. lo_server_thread_add_method(oscServerThread, nullptr, nullptr, osc_fallback_handler, nullptr);
  701. lo_server_thread_start(oscServerThread);
  702. #else
  703. if (oscServer != nullptr)
  704. return true;
  705. if ((oscServer = lo_server_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr)
  706. return false;
  707. lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this);
  708. lo_server_add_method(oscServer, "/host-param", "if", osc_host_param_handler, this);
  709. lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this);
  710. lo_server_add_method(oscServer, "/param", "hif", osc_param_handler, this);
  711. lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr);
  712. #endif
  713. return true;
  714. }
  715. void Initializer::stopRemoteServer()
  716. {
  717. DISTRHO_SAFE_ASSERT(remotePluginInstance == nullptr);
  718. #ifdef CARDINAL_INIT_OSC_THREAD
  719. if (oscServerThread != nullptr)
  720. {
  721. lo_server_thread_stop(oscServerThread);
  722. lo_server_thread_del_method(oscServerThread, nullptr, nullptr);
  723. lo_server_thread_free(oscServerThread);
  724. oscServerThread = oscServer = nullptr;
  725. }
  726. #else
  727. if (oscServer != nullptr)
  728. {
  729. lo_server_del_method(oscServer, nullptr, nullptr);
  730. lo_server_free(oscServer);
  731. oscServer = nullptr;
  732. }
  733. #endif
  734. }
  735. void Initializer::stepRemoteServer()
  736. {
  737. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,);
  738. DISTRHO_SAFE_ASSERT_RETURN(remotePluginInstance != nullptr,);
  739. #ifndef CARDINAL_INIT_OSC_THREAD
  740. for (;;)
  741. {
  742. try {
  743. if (lo_server_recv_noblock(oscServer, 0) == 0)
  744. break;
  745. } DISTRHO_SAFE_EXCEPTION_CONTINUE("stepRemoteServer")
  746. }
  747. #endif
  748. }
  749. #endif // HAVE_LIBLO
  750. // --------------------------------------------------------------------------------------------------------------------
  751. END_NAMESPACE_DISTRHO
  752. // --------------------------------------------------------------------------------------------------------------------
  753. namespace rack {
  754. bool isMini()
  755. {
  756. #if CARDINAL_VARIANT_MINI
  757. return true;
  758. #else
  759. return false;
  760. #endif
  761. }
  762. bool isStandalone()
  763. {
  764. static const bool standalone = std::strstr(getPluginFormatName(), "Standalone") != nullptr;
  765. return standalone;
  766. }
  767. #ifdef ARCH_WIN
  768. std::string getSpecialPath(const SpecialPath type)
  769. {
  770. int csidl;
  771. switch (type)
  772. {
  773. case kSpecialPathUserProfile:
  774. csidl = CSIDL_PROFILE;
  775. break;
  776. case kSpecialPathCommonProgramFiles:
  777. csidl = CSIDL_PROGRAM_FILES_COMMON;
  778. break;
  779. case kSpecialPathProgramFiles:
  780. csidl = CSIDL_PROGRAM_FILES;
  781. break;
  782. case kSpecialPathAppData:
  783. csidl = CSIDL_APPDATA;
  784. break;
  785. case kSpecialPathMyDocuments:
  786. csidl = CSIDL_MYDOCUMENTS;
  787. break;
  788. default:
  789. return {};
  790. }
  791. WCHAR path[MAX_PATH] = {};
  792. if (SHGetFolderPathW(nullptr, csidl, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK)
  793. return string::UTF16toUTF8(path);
  794. return {};
  795. }
  796. #endif
  797. #ifdef DISTRHO_OS_WASM
  798. char* patchFromURL = nullptr;
  799. char* patchRemoteURL = nullptr;
  800. char* patchStorageSlug = nullptr;
  801. void syncfs()
  802. {
  803. settings::save();
  804. #ifndef CARDINAL_COMMON_UI_ONLY
  805. EM_ASM({
  806. Module.FS.syncfs(false, function(){} );
  807. });
  808. #endif
  809. }
  810. #endif
  811. std::string homeDir()
  812. {
  813. #ifdef ARCH_WIN
  814. return getSpecialPath(kSpecialPathUserProfile);
  815. #else
  816. if (const char* const home = getenv("HOME"))
  817. return home;
  818. if (struct passwd* const pwd = getpwuid(getuid()))
  819. return pwd->pw_dir;
  820. #endif
  821. return {};
  822. }
  823. } // namespace rack
  824. // --------------------------------------------------------------------------------------------------------------------
  825. namespace patchUtils
  826. {
  827. using namespace rack;
  828. #ifndef HEADLESS_BEHAVIOUR
  829. static void promptClear(const char* const message, const std::function<void()> action)
  830. {
  831. if (APP->history->isSaved() || APP->scene->rack->hasModules())
  832. return action();
  833. asyncDialog::create(message, action);
  834. }
  835. #endif
  836. void loadDialog()
  837. {
  838. #ifndef HEADLESS_BEHAVIOUR
  839. promptClear("The current patch is unsaved. Clear it and open a new patch?", []() {
  840. std::string dir;
  841. if (! APP->patch->path.empty())
  842. dir = system::getDirectory(APP->patch->path);
  843. else
  844. dir = homeDir();
  845. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  846. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  847. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  848. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  849. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  850. opts.saving = ui->saving = false;
  851. opts.startDir = dir.c_str();
  852. opts.title = "Open patch";
  853. ui->openFileBrowser(opts);
  854. });
  855. #endif
  856. }
  857. void loadPathDialog(const std::string& path, const bool asTemplate)
  858. {
  859. #ifndef HEADLESS_BEHAVIOUR
  860. promptClear("The current patch is unsaved. Clear it and open the new patch?", [path, asTemplate]() {
  861. APP->patch->loadAction(path);
  862. if (asTemplate)
  863. {
  864. APP->patch->path = "";
  865. APP->history->setSaved();
  866. }
  867. #ifdef DISTRHO_OS_WASM
  868. syncfs();
  869. #endif
  870. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  871. if (remoteDetails->autoDeploy)
  872. remoteUtils::sendFullPatchToRemote(remoteDetails);
  873. });
  874. #endif
  875. }
  876. void loadSelectionDialog()
  877. {
  878. app::RackWidget* const w = APP->scene->rack;
  879. std::string selectionDir = asset::user("selections");
  880. system::createDirectories(selectionDir);
  881. async_dialog_filebrowser(false, nullptr, selectionDir.c_str(), "Import selection", [w](char* pathC) {
  882. if (!pathC) {
  883. // No path selected
  884. return;
  885. }
  886. try {
  887. w->loadSelection(pathC);
  888. }
  889. catch (Exception& e) {
  890. async_dialog_message(e.what());
  891. }
  892. std::free(pathC);
  893. #ifdef DISTRHO_OS_WASM
  894. syncfs();
  895. #endif
  896. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  897. if (remoteDetails->autoDeploy)
  898. remoteUtils::sendFullPatchToRemote(remoteDetails);
  899. });
  900. }
  901. void loadTemplate(const bool factory)
  902. {
  903. try {
  904. APP->patch->load(factory ? APP->patch->factoryTemplatePath : APP->patch->templatePath);
  905. }
  906. catch (Exception& e) {
  907. // if user template failed, try the factory one
  908. if (!factory)
  909. return loadTemplate(true);
  910. const std::string message = string::f("Could not load template patch, clearing rack: %s", e.what());
  911. asyncDialog::create(message.c_str());
  912. APP->patch->clear();
  913. APP->patch->clearAutosave();
  914. }
  915. // load() sets the patch's original patch, but we don't want to use that.
  916. APP->patch->path.clear();
  917. APP->history->setSaved();
  918. #ifdef DISTRHO_OS_WASM
  919. syncfs();
  920. #endif
  921. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  922. if (remoteDetails->autoDeploy)
  923. remoteUtils::sendFullPatchToRemote(remoteDetails);
  924. }
  925. void loadTemplateDialog(const bool factory)
  926. {
  927. #ifndef HEADLESS_BEHAVIOUR
  928. promptClear("The current patch is unsaved. Clear it and start a new patch?", [factory]() {
  929. loadTemplate(factory);
  930. });
  931. #endif
  932. }
  933. void revertDialog()
  934. {
  935. #ifndef HEADLESS_BEHAVIOUR
  936. if (APP->patch->path.empty())
  937. return;
  938. promptClear("Revert patch to the last saved state?", []{
  939. APP->patch->loadAction(APP->patch->path);
  940. #ifdef DISTRHO_OS_WASM
  941. syncfs();
  942. #endif
  943. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  944. if (remoteDetails->autoDeploy)
  945. remoteUtils::sendFullPatchToRemote(remoteDetails);
  946. });
  947. #endif
  948. }
  949. void saveDialog(const std::string& path)
  950. {
  951. #ifndef HEADLESS_BEHAVIOUR
  952. if (path.empty()) {
  953. return;
  954. }
  955. // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
  956. APP->history->setSaved();
  957. try {
  958. APP->patch->save(path);
  959. }
  960. catch (Exception& e) {
  961. asyncDialog::create(string::f("Could not save patch: %s", e.what()).c_str());
  962. return;
  963. }
  964. APP->patch->pushRecentPath(path);
  965. #ifdef DISTRHO_OS_WASM
  966. syncfs();
  967. #else
  968. rack::settings::save();
  969. #endif
  970. #endif
  971. }
  972. #ifndef HEADLESS_BEHAVIOUR
  973. static void saveAsDialog(const bool uncompressed)
  974. {
  975. std::string dir;
  976. if (! APP->patch->path.empty())
  977. {
  978. dir = system::getDirectory(APP->patch->path);
  979. }
  980. else
  981. {
  982. dir = asset::user("patches");
  983. system::createDirectories(dir);
  984. }
  985. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  986. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  987. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  988. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  989. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  990. opts.saving = ui->saving = true;
  991. opts.defaultName = "patch.vcv";
  992. opts.startDir = dir.c_str();
  993. opts.title = "Save patch";
  994. ui->savingUncompressed = uncompressed;
  995. ui->openFileBrowser(opts);
  996. }
  997. #endif
  998. void saveAsDialog()
  999. {
  1000. #ifndef HEADLESS_BEHAVIOUR
  1001. saveAsDialog(false);
  1002. #endif
  1003. }
  1004. void saveAsDialogUncompressed()
  1005. {
  1006. #ifndef HEADLESS_BEHAVIOUR
  1007. saveAsDialog(true);
  1008. #endif
  1009. }
  1010. void saveTemplateDialog()
  1011. {
  1012. asyncDialog::create("Overwrite template patch?", []{
  1013. rack::system::createDirectories(system::getDirectory(APP->patch->templatePath));
  1014. try {
  1015. APP->patch->save(APP->patch->templatePath);
  1016. }
  1017. catch (Exception& e) {
  1018. asyncDialog::create(string::f("Could not save template patch: %s", e.what()).c_str());
  1019. return;
  1020. }
  1021. #ifdef DISTRHO_OS_WASM
  1022. syncfs();
  1023. #endif
  1024. });
  1025. }
  1026. void openBrowser(const std::string& url)
  1027. {
  1028. #ifdef DISTRHO_OS_WASM
  1029. EM_ASM({
  1030. window.open(UTF8ToString($0), '_blank');
  1031. }, url.c_str());
  1032. #else
  1033. system::openBrowser(url);
  1034. #endif
  1035. }
  1036. }
  1037. // --------------------------------------------------------------------------------------------------------------------
  1038. void async_dialog_filebrowser(const bool saving,
  1039. const char* const defaultName,
  1040. const char* const startDir,
  1041. const char* const title,
  1042. const std::function<void(char* path)> action)
  1043. {
  1044. #ifndef HEADLESS_BEHAVIOUR
  1045. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  1046. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  1047. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  1048. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1049. // only 1 dialog possible at a time
  1050. DISTRHO_SAFE_ASSERT_RETURN(ui->filebrowserhandle == nullptr,);
  1051. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  1052. opts.saving = saving;
  1053. opts.defaultName = defaultName;
  1054. opts.startDir = startDir;
  1055. opts.title = title;
  1056. ui->filebrowseraction = action;
  1057. ui->filebrowserhandle = fileBrowserCreate(true, pcontext->nativeWindowId, pcontext->window->pixelRatio, opts);
  1058. #endif
  1059. }
  1060. void async_dialog_message(const char* const message)
  1061. {
  1062. #ifndef HEADLESS_BEHAVIOUR
  1063. asyncDialog::create(message);
  1064. #endif
  1065. }
  1066. void async_dialog_message(const char* const message, const std::function<void()> action)
  1067. {
  1068. #ifndef HEADLESS_BEHAVIOUR
  1069. asyncDialog::create(message, action);
  1070. #endif
  1071. }
  1072. void async_dialog_text_input(const char* const message, const char* const text,
  1073. const std::function<void(char* newText)> action)
  1074. {
  1075. #ifndef HEADLESS_BEHAVIOUR
  1076. asyncDialog::textInput(message, text, action);
  1077. #endif
  1078. }