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.

1260 lines
35KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2026 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 = "26.01";
  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. asset::userDir = system::join(getSpecialDir(kSpecialDirDocuments), "Cardinal");
  529. if (isRealInstance)
  530. {
  531. system::createDirectory(asset::userDir);
  532. #if defined(DISTRHO_OS_WASM) && !defined(CARDINAL_COMMON_UI_ONLY)
  533. EM_ASM({
  534. Module.FS.mount(Module.IDBFS, {}, '/userfiles');
  535. Module.FS.syncfs(true, function(err) { if (!err) { dynCall('vi', $0, [$1]) } });
  536. }, WebBrowserDataLoaded, this);
  537. #endif
  538. }
  539. }
  540. #ifndef CARDINAL_COMMON_DSP_ONLY
  541. if (asset::configDir.empty())
  542. {
  543. asset::configDir = system::join(getSpecialDir(kSpecialDirConfig), "Cardinal");
  544. if (isRealInstance)
  545. system::createDirectory(asset::configDir);
  546. }
  547. #endif
  548. if (settings::settingsPath.empty())
  549. settings::settingsPath = asset::config(CARDINAL_VARIANT_NAME ".json");
  550. templatePath = asset::user("templates/" CARDINAL_VARIANT_NAME ".vcv");
  551. #ifdef DISTRHO_OS_WASM
  552. factoryTemplatePath = system::join(asset::patchesPath(), CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME ".vcv");
  553. #else
  554. factoryTemplatePath = system::join(asset::patchesPath(), "templates/" CARDINAL_VARIANT_NAME ".vcv");
  555. #endif
  556. // Log environment
  557. 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());
  558. INFO("%s", system::getOperatingSystemInfo().c_str());
  559. INFO("Binary filename: %s", getBinaryFilename());
  560. if (plugin != nullptr) {
  561. INFO("Bundle path: %s", plugin->getBundlePath());
  562. #if DISTRHO_PLUGIN_HAS_UI
  563. } else if (ui != nullptr) {
  564. INFO("Bundle path: %s", ui->getBundlePath());
  565. #endif
  566. }
  567. INFO("System directory: %s", asset::systemDir.c_str());
  568. INFO("User directory: %s", asset::userDir.c_str());
  569. INFO("Template patch: %s", templatePath.c_str());
  570. INFO("System template patch: %s", factoryTemplatePath.c_str());
  571. // Report to user if something is wrong with the installation
  572. if (asset::systemDir.empty())
  573. {
  574. d_stderr2("Failed to locate Cardinal plugin bundle.\n"
  575. "Install Cardinal with its bundle folder intact and try again.");
  576. }
  577. else if (! system::exists(asset::systemDir))
  578. {
  579. d_stderr2("System directory \"%s\" does not exist.\n"
  580. "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
  581. }
  582. INFO("Initializing plugins");
  583. plugin::initStaticPlugins();
  584. INFO("Initializing plugin browser DB");
  585. app::browserInit();
  586. loadSettings(isRealInstance);
  587. #if defined(CARDINAL_INIT_OSC_THREAD)
  588. INFO("Initializing OSC Remote control");
  589. const char* port;
  590. if (const char* const portEnv = std::getenv("CARDINAL_REMOTE_HOST_PORT"))
  591. port = portEnv;
  592. else
  593. port = CARDINAL_DEFAULT_REMOTE_PORT;
  594. startRemoteServer(port);
  595. #elif defined(HAVE_LIBLO)
  596. if (isStandalone()) {
  597. INFO("OSC Remote control is available on request");
  598. } else {
  599. INFO("OSC Remote control is not available on plugin variants");
  600. }
  601. #else
  602. INFO("OSC Remote control is not enabled in this build");
  603. #endif
  604. }
  605. Initializer::~Initializer()
  606. {
  607. using namespace rack;
  608. #ifdef HAVE_LIBLO
  609. stopRemoteServer();
  610. #endif
  611. if (shouldSaveSettings)
  612. {
  613. INFO("Save settings");
  614. settings::save();
  615. }
  616. INFO("Clearing asset paths");
  617. asset::bundlePath.clear();
  618. asset::systemDir.clear();
  619. asset::userDir.clear();
  620. INFO("Destroying plugins");
  621. plugin::destroyStaticPlugins();
  622. INFO("Destroying colourized assets");
  623. asset::destroy();
  624. INFO("Destroying settings");
  625. settings::destroy();
  626. INFO("Destroying logger");
  627. logger::destroy();
  628. }
  629. void Initializer::loadSettings(const bool isRealInstance)
  630. {
  631. using namespace rack;
  632. if (isRealInstance)
  633. {
  634. INFO("Loading settings");
  635. settings::load();
  636. shouldSaveSettings = true;
  637. }
  638. // enforce settings that do not make sense as anything else
  639. settings::safeMode = false;
  640. settings::token.clear();
  641. settings::windowMaximized = false;
  642. settings::windowPos = math::Vec(0, 0);
  643. settings::pixelRatio = 0.0;
  644. settings::sampleRate = 0;
  645. settings::threadCount = 1;
  646. settings::autosaveInterval = 0;
  647. settings::skipLoadOnLaunch = true;
  648. settings::autoCheckUpdates = false;
  649. settings::showTipsOnLaunch = false;
  650. settings::tipIndex = -1;
  651. if (settings::uiTheme != "dark" && settings::uiTheme != "light")
  652. {
  653. settings::uiTheme = "dark";
  654. rack::ui::refreshTheme();
  655. }
  656. // reload dark/light mode as necessary
  657. switchDarkMode(settings::uiTheme == "dark");
  658. }
  659. #ifdef HAVE_LIBLO
  660. bool Initializer::startRemoteServer(const char* const port)
  661. {
  662. #ifdef CARDINAL_INIT_OSC_THREAD
  663. if (oscServerThread != nullptr)
  664. return true;
  665. if ((oscServerThread = lo_server_thread_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr)
  666. return false;
  667. oscServer = lo_server_thread_get_server(oscServerThread);
  668. lo_server_thread_add_method(oscServerThread, "/hello", "", osc_hello_handler, this);
  669. lo_server_thread_add_method(oscServerThread, "/host-param", "if", osc_host_param_handler, this);
  670. lo_server_thread_add_method(oscServerThread, "/load", "b", osc_load_handler, this);
  671. lo_server_thread_add_method(oscServerThread, "/param", "hif", osc_param_handler, this);
  672. lo_server_thread_add_method(oscServerThread, "/screenshot", "b", osc_screenshot_handler, this);
  673. lo_server_thread_add_method(oscServerThread, nullptr, nullptr, osc_fallback_handler, nullptr);
  674. lo_server_thread_start(oscServerThread);
  675. #else
  676. if (oscServer != nullptr)
  677. return true;
  678. if ((oscServer = lo_server_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr)
  679. return false;
  680. lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this);
  681. lo_server_add_method(oscServer, "/host-param", "if", osc_host_param_handler, this);
  682. lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this);
  683. lo_server_add_method(oscServer, "/param", "hif", osc_param_handler, this);
  684. lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr);
  685. #endif
  686. return true;
  687. }
  688. void Initializer::stopRemoteServer()
  689. {
  690. DISTRHO_SAFE_ASSERT(remotePluginInstance == nullptr);
  691. #ifdef CARDINAL_INIT_OSC_THREAD
  692. if (oscServerThread != nullptr)
  693. {
  694. lo_server_thread_stop(oscServerThread);
  695. lo_server_thread_del_method(oscServerThread, nullptr, nullptr);
  696. lo_server_thread_free(oscServerThread);
  697. oscServerThread = nullptr;
  698. oscServer = nullptr;
  699. }
  700. #else
  701. if (oscServer != nullptr)
  702. {
  703. lo_server_del_method(oscServer, nullptr, nullptr);
  704. lo_server_free(oscServer);
  705. oscServer = nullptr;
  706. }
  707. #endif
  708. }
  709. void Initializer::stepRemoteServer()
  710. {
  711. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,);
  712. DISTRHO_SAFE_ASSERT_RETURN(remotePluginInstance != nullptr,);
  713. #ifndef CARDINAL_INIT_OSC_THREAD
  714. for (;;)
  715. {
  716. try {
  717. if (lo_server_recv_noblock(oscServer, 0) == 0)
  718. break;
  719. } DISTRHO_SAFE_EXCEPTION_CONTINUE("stepRemoteServer")
  720. }
  721. #endif
  722. }
  723. #endif // HAVE_LIBLO
  724. // --------------------------------------------------------------------------------------------------------------------
  725. END_NAMESPACE_DISTRHO
  726. // --------------------------------------------------------------------------------------------------------------------
  727. namespace rack {
  728. bool isMini()
  729. {
  730. #if CARDINAL_VARIANT_MINI
  731. return true;
  732. #else
  733. return false;
  734. #endif
  735. }
  736. bool isStandalone()
  737. {
  738. static const bool standalone = std::strstr(getPluginFormatName(), "Standalone") != nullptr;
  739. return standalone;
  740. }
  741. #ifdef ARCH_WIN
  742. std::string getSpecialPath(const SpecialPath type)
  743. {
  744. int csidl;
  745. switch (type)
  746. {
  747. case kSpecialPathUserProfile:
  748. csidl = CSIDL_PROFILE;
  749. break;
  750. case kSpecialPathCommonProgramFiles:
  751. csidl = CSIDL_PROGRAM_FILES_COMMON;
  752. break;
  753. case kSpecialPathProgramFiles:
  754. csidl = CSIDL_PROGRAM_FILES;
  755. break;
  756. case kSpecialPathAppData:
  757. csidl = CSIDL_APPDATA;
  758. break;
  759. case kSpecialPathMyDocuments:
  760. csidl = CSIDL_MYDOCUMENTS;
  761. break;
  762. default:
  763. return {};
  764. }
  765. WCHAR path[MAX_PATH] = {};
  766. if (SHGetFolderPathW(nullptr, csidl, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK)
  767. return string::UTF16toUTF8(path);
  768. return {};
  769. }
  770. #endif
  771. #ifdef DISTRHO_OS_WASM
  772. char* patchFromURL = nullptr;
  773. char* patchRemoteURL = nullptr;
  774. char* patchStorageSlug = nullptr;
  775. void syncfs()
  776. {
  777. settings::save();
  778. #ifndef CARDINAL_COMMON_UI_ONLY
  779. EM_ASM({
  780. Module.FS.syncfs(false, function(){} );
  781. });
  782. #endif
  783. }
  784. #endif
  785. std::string homeDir()
  786. {
  787. #ifdef ARCH_WIN
  788. return getSpecialPath(kSpecialPathUserProfile);
  789. #else
  790. if (const char* const home = getenv("HOME"))
  791. return home;
  792. if (struct passwd* const pwd = getpwuid(getuid()))
  793. return pwd->pw_dir;
  794. #endif
  795. return {};
  796. }
  797. } // namespace rack
  798. // --------------------------------------------------------------------------------------------------------------------
  799. namespace patchUtils
  800. {
  801. using namespace rack;
  802. #ifndef HEADLESS_BEHAVIOUR
  803. static void promptClear(const char* const message, const std::function<void()> action)
  804. {
  805. if (APP->history->isSaved() || APP->scene->rack->hasModules())
  806. return action();
  807. asyncDialog::create(message, action);
  808. }
  809. #endif
  810. void loadDialog()
  811. {
  812. #ifndef HEADLESS_BEHAVIOUR
  813. promptClear("The current patch is unsaved. Clear it and open a new patch?", []() {
  814. std::string dir;
  815. if (! APP->patch->path.empty())
  816. dir = system::getDirectory(APP->patch->path);
  817. else
  818. dir = homeDir();
  819. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  820. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  821. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  822. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  823. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  824. opts.saving = ui->saving = false;
  825. opts.startDir = dir.c_str();
  826. opts.title = "Open patch";
  827. ui->openFileBrowser(opts);
  828. });
  829. #endif
  830. }
  831. void loadPathDialog(const std::string& path, const bool asTemplate)
  832. {
  833. #ifndef HEADLESS_BEHAVIOUR
  834. promptClear("The current patch is unsaved. Clear it and open the new patch?", [path, asTemplate]() {
  835. APP->patch->loadAction(path);
  836. if (asTemplate)
  837. {
  838. APP->patch->path = "";
  839. APP->history->setSaved();
  840. }
  841. #ifdef DISTRHO_OS_WASM
  842. syncfs();
  843. #endif
  844. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  845. if (remoteDetails->autoDeploy)
  846. remoteUtils::sendFullPatchToRemote(remoteDetails);
  847. });
  848. #endif
  849. }
  850. void loadSelectionDialog()
  851. {
  852. app::RackWidget* const w = APP->scene->rack;
  853. std::string selectionDir = asset::user("selections");
  854. system::createDirectories(selectionDir);
  855. async_dialog_filebrowser(false, nullptr, selectionDir.c_str(), "Import selection", [w](char* pathC) {
  856. if (!pathC) {
  857. // No path selected
  858. return;
  859. }
  860. try {
  861. w->loadSelection(pathC);
  862. }
  863. catch (Exception& e) {
  864. async_dialog_message(e.what());
  865. }
  866. std::free(pathC);
  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. }
  875. void loadTemplate(const bool factory)
  876. {
  877. try {
  878. APP->patch->load(factory ? APP->patch->factoryTemplatePath : APP->patch->templatePath);
  879. }
  880. catch (Exception& e) {
  881. // if user template failed, try the factory one
  882. if (!factory)
  883. return loadTemplate(true);
  884. const std::string message = string::f("Could not load template patch, clearing rack: %s", e.what());
  885. asyncDialog::create(message.c_str());
  886. APP->patch->clear();
  887. APP->patch->clearAutosave();
  888. }
  889. // load() sets the patch's original patch, but we don't want to use that.
  890. APP->patch->path.clear();
  891. APP->history->setSaved();
  892. #ifdef DISTRHO_OS_WASM
  893. syncfs();
  894. #endif
  895. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  896. if (remoteDetails->autoDeploy)
  897. remoteUtils::sendFullPatchToRemote(remoteDetails);
  898. }
  899. void loadTemplateDialog(const bool factory)
  900. {
  901. #ifndef HEADLESS_BEHAVIOUR
  902. promptClear("The current patch is unsaved. Clear it and start a new patch?", [factory]() {
  903. loadTemplate(factory);
  904. });
  905. #endif
  906. }
  907. void revertDialog()
  908. {
  909. #ifndef HEADLESS_BEHAVIOUR
  910. if (APP->patch->path.empty())
  911. return;
  912. promptClear("Revert patch to the last saved state?", []{
  913. APP->patch->loadAction(APP->patch->path);
  914. #ifdef DISTRHO_OS_WASM
  915. syncfs();
  916. #endif
  917. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  918. if (remoteDetails->autoDeploy)
  919. remoteUtils::sendFullPatchToRemote(remoteDetails);
  920. });
  921. #endif
  922. }
  923. void saveDialog(const std::string& path)
  924. {
  925. #ifndef HEADLESS_BEHAVIOUR
  926. if (path.empty()) {
  927. return;
  928. }
  929. // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
  930. APP->history->setSaved();
  931. try {
  932. APP->patch->save(path);
  933. }
  934. catch (Exception& e) {
  935. asyncDialog::create(string::f("Could not save patch: %s", e.what()).c_str());
  936. return;
  937. }
  938. APP->patch->pushRecentPath(path);
  939. #ifdef DISTRHO_OS_WASM
  940. syncfs();
  941. #else
  942. rack::settings::save();
  943. #endif
  944. #endif
  945. }
  946. #ifndef HEADLESS_BEHAVIOUR
  947. static void saveAsDialog(const bool uncompressed)
  948. {
  949. std::string dir;
  950. if (! APP->patch->path.empty())
  951. {
  952. dir = system::getDirectory(APP->patch->path);
  953. }
  954. else
  955. {
  956. dir = asset::user("patches");
  957. system::createDirectories(dir);
  958. }
  959. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  960. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  961. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  962. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  963. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  964. opts.saving = ui->saving = true;
  965. opts.defaultName = "patch.vcv";
  966. opts.startDir = dir.c_str();
  967. opts.title = "Save patch";
  968. ui->savingUncompressed = uncompressed;
  969. ui->openFileBrowser(opts);
  970. }
  971. #endif
  972. void saveAsDialog()
  973. {
  974. #ifndef HEADLESS_BEHAVIOUR
  975. saveAsDialog(false);
  976. #endif
  977. }
  978. void saveAsDialogUncompressed()
  979. {
  980. #ifndef HEADLESS_BEHAVIOUR
  981. saveAsDialog(true);
  982. #endif
  983. }
  984. void saveTemplateDialog()
  985. {
  986. asyncDialog::create("Overwrite template patch?", []{
  987. rack::system::createDirectories(system::getDirectory(APP->patch->templatePath));
  988. try {
  989. APP->patch->save(APP->patch->templatePath);
  990. }
  991. catch (Exception& e) {
  992. asyncDialog::create(string::f("Could not save template patch: %s", e.what()).c_str());
  993. return;
  994. }
  995. #ifdef DISTRHO_OS_WASM
  996. syncfs();
  997. #endif
  998. });
  999. }
  1000. void openBrowser(const std::string& url)
  1001. {
  1002. #ifdef DISTRHO_OS_WASM
  1003. EM_ASM({
  1004. window.open(UTF8ToString($0), '_blank');
  1005. }, url.c_str());
  1006. #else
  1007. system::openBrowser(url);
  1008. #endif
  1009. }
  1010. }
  1011. // --------------------------------------------------------------------------------------------------------------------
  1012. void async_dialog_filebrowser(const bool saving,
  1013. const char* const defaultName,
  1014. const char* const startDir,
  1015. const char* const title,
  1016. const std::function<void(char* path)> action)
  1017. {
  1018. #ifndef HEADLESS_BEHAVIOUR
  1019. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  1020. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  1021. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  1022. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1023. // only 1 dialog possible at a time
  1024. DISTRHO_SAFE_ASSERT_RETURN(ui->filebrowserhandle == nullptr,);
  1025. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  1026. opts.saving = saving;
  1027. opts.defaultName = defaultName;
  1028. opts.startDir = startDir;
  1029. opts.title = title;
  1030. ui->filebrowseraction = action;
  1031. ui->filebrowserhandle = fileBrowserCreate(true, pcontext->nativeWindowId, pcontext->window->pixelRatio, opts);
  1032. #endif
  1033. }
  1034. void async_dialog_message(const char* const message)
  1035. {
  1036. #ifndef HEADLESS_BEHAVIOUR
  1037. asyncDialog::create(message);
  1038. #endif
  1039. }
  1040. void async_dialog_message(const char* const message, const std::function<void()> action)
  1041. {
  1042. #ifndef HEADLESS_BEHAVIOUR
  1043. asyncDialog::create(message, action);
  1044. #endif
  1045. }
  1046. void async_dialog_text_input(const char* const message, const char* const text,
  1047. const std::function<void(char* newText)> action)
  1048. {
  1049. #ifndef HEADLESS_BEHAVIOUR
  1050. asyncDialog::textInput(message, text, action);
  1051. #endif
  1052. }