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.

1139 lines
32KB

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