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.

1306 lines
37KB

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