The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

394 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include <JuceHeader.h>
  19. #include "UI/MainHostWindow.h"
  20. #include "Plugins/InternalPlugins.h"
  21. #if ! (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU)
  22. #error "If you're building the audio plugin host, you probably want to enable VST and/or AU support"
  23. #endif
  24. class PluginScannerSubprocess : private ChildProcessWorker,
  25. private AsyncUpdater
  26. {
  27. public:
  28. PluginScannerSubprocess()
  29. {
  30. formatManager.addDefaultFormats();
  31. }
  32. using ChildProcessWorker::initialiseFromCommandLine;
  33. private:
  34. void handleMessageFromCoordinator (const MemoryBlock& mb) override
  35. {
  36. if (mb.isEmpty())
  37. return;
  38. if (! doScan (mb))
  39. {
  40. {
  41. const std::lock_guard<std::mutex> lock (mutex);
  42. pendingBlocks.emplace (mb);
  43. }
  44. triggerAsyncUpdate();
  45. }
  46. }
  47. void handleConnectionLost() override
  48. {
  49. JUCEApplicationBase::quit();
  50. }
  51. void handleAsyncUpdate() override
  52. {
  53. for (;;)
  54. {
  55. const auto block = [&]() -> MemoryBlock
  56. {
  57. const std::lock_guard<std::mutex> lock (mutex);
  58. if (pendingBlocks.empty())
  59. return {};
  60. auto out = std::move (pendingBlocks.front());
  61. pendingBlocks.pop();
  62. return out;
  63. }();
  64. if (block.isEmpty())
  65. return;
  66. doScan (block);
  67. }
  68. }
  69. bool doScan (const MemoryBlock& block)
  70. {
  71. MemoryInputStream stream { block, false };
  72. const auto formatName = stream.readString();
  73. const auto identifier = stream.readString();
  74. PluginDescription pd;
  75. pd.fileOrIdentifier = identifier;
  76. pd.uniqueId = pd.deprecatedUid = 0;
  77. const auto matchingFormat = [&]() -> AudioPluginFormat*
  78. {
  79. for (auto* format : formatManager.getFormats())
  80. if (format->getName() == formatName)
  81. return format;
  82. return nullptr;
  83. }();
  84. if (matchingFormat == nullptr
  85. || (! MessageManager::getInstance()->isThisTheMessageThread()
  86. && ! matchingFormat->requiresUnblockedMessageThreadDuringCreation (pd)))
  87. {
  88. return false;
  89. }
  90. OwnedArray<PluginDescription> results;
  91. matchingFormat->findAllTypesForFile (results, identifier);
  92. sendPluginDescriptions (results);
  93. return true;
  94. }
  95. void sendPluginDescriptions (const OwnedArray<PluginDescription>& results)
  96. {
  97. XmlElement xml ("LIST");
  98. for (const auto& desc : results)
  99. xml.addChildElement (desc->createXml().release());
  100. const auto str = xml.toString();
  101. sendMessageToCoordinator ({ str.toRawUTF8(), str.getNumBytesAsUTF8() });
  102. }
  103. std::mutex mutex;
  104. std::queue<MemoryBlock> pendingBlocks;
  105. // After construction, this will only be accessed by doScan so there's no need
  106. // to worry about synchronisation.
  107. AudioPluginFormatManager formatManager;
  108. };
  109. //==============================================================================
  110. class PluginHostApp : public JUCEApplication,
  111. private AsyncUpdater
  112. {
  113. public:
  114. PluginHostApp() = default;
  115. void initialise (const String& commandLine) override
  116. {
  117. auto scannerSubprocess = std::make_unique<PluginScannerSubprocess>();
  118. if (scannerSubprocess->initialiseFromCommandLine (commandLine, processUID))
  119. {
  120. storedScannerSubprocess = std::move (scannerSubprocess);
  121. return;
  122. }
  123. // initialise our settings file..
  124. PropertiesFile::Options options;
  125. options.applicationName = "Juce Audio Plugin Host";
  126. options.filenameSuffix = "settings";
  127. options.osxLibrarySubFolder = "Preferences";
  128. appProperties.reset (new ApplicationProperties());
  129. appProperties->setStorageParameters (options);
  130. mainWindow.reset (new MainHostWindow());
  131. commandManager.registerAllCommandsForTarget (this);
  132. commandManager.registerAllCommandsForTarget (mainWindow.get());
  133. mainWindow->menuItemsChanged();
  134. // Important note! We're going to use an async update here so that if we need
  135. // to re-open a file and instantiate some plugins, it will happen AFTER this
  136. // initialisation method has returned.
  137. // On Windows this probably won't make a difference, but on OSX there's a subtle event loop
  138. // issue that can happen if a plugin runs one of those irritating modal dialogs while it's
  139. // being loaded. If that happens inside this method, the OSX event loop seems to be in some
  140. // kind of special "initialisation" mode and things get confused. But if we load the plugin
  141. // later when the normal event loop is running, everything's fine.
  142. triggerAsyncUpdate();
  143. }
  144. void handleAsyncUpdate() override
  145. {
  146. File fileToOpen;
  147. #if JUCE_ANDROID || JUCE_IOS
  148. fileToOpen = PluginGraph::getDefaultGraphDocumentOnMobile();
  149. #else
  150. for (int i = 0; i < getCommandLineParameterArray().size(); ++i)
  151. {
  152. fileToOpen = File::getCurrentWorkingDirectory().getChildFile (getCommandLineParameterArray()[i]);
  153. if (fileToOpen.existsAsFile())
  154. break;
  155. }
  156. #endif
  157. if (! fileToOpen.existsAsFile())
  158. {
  159. RecentlyOpenedFilesList recentFiles;
  160. recentFiles.restoreFromString (getAppProperties().getUserSettings()->getValue ("recentFilterGraphFiles"));
  161. if (recentFiles.getNumFiles() > 0)
  162. fileToOpen = recentFiles.getFile (0);
  163. }
  164. if (fileToOpen.existsAsFile())
  165. if (auto* graph = mainWindow->graphHolder.get())
  166. if (auto* ioGraph = graph->graph.get())
  167. ioGraph->loadFrom (fileToOpen, true);
  168. }
  169. void shutdown() override
  170. {
  171. mainWindow = nullptr;
  172. appProperties = nullptr;
  173. LookAndFeel::setDefaultLookAndFeel (nullptr);
  174. }
  175. void suspended() override
  176. {
  177. #if JUCE_ANDROID || JUCE_IOS
  178. if (auto graph = mainWindow->graphHolder.get())
  179. if (auto ioGraph = graph->graph.get())
  180. ioGraph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile());
  181. #endif
  182. }
  183. void systemRequestedQuit() override
  184. {
  185. if (mainWindow != nullptr)
  186. mainWindow->tryToQuitApplication();
  187. else
  188. JUCEApplicationBase::quit();
  189. }
  190. bool backButtonPressed() override
  191. {
  192. if (mainWindow->graphHolder != nullptr)
  193. mainWindow->graphHolder->hideLastSidePanel();
  194. return true;
  195. }
  196. const String getApplicationName() override { return "Juce Plug-In Host"; }
  197. const String getApplicationVersion() override { return ProjectInfo::versionString; }
  198. bool moreThanOneInstanceAllowed() override { return true; }
  199. ApplicationCommandManager commandManager;
  200. std::unique_ptr<ApplicationProperties> appProperties;
  201. private:
  202. std::unique_ptr<MainHostWindow> mainWindow;
  203. std::unique_ptr<PluginScannerSubprocess> storedScannerSubprocess;
  204. };
  205. static PluginHostApp& getApp() { return *dynamic_cast<PluginHostApp*>(JUCEApplication::getInstance()); }
  206. ApplicationProperties& getAppProperties() { return *getApp().appProperties; }
  207. ApplicationCommandManager& getCommandManager() { return getApp().commandManager; }
  208. bool isOnTouchDevice()
  209. {
  210. static bool isTouch = Desktop::getInstance().getMainMouseSource().isTouch();
  211. return isTouch;
  212. }
  213. //==============================================================================
  214. static AutoScale autoScaleFromString (StringRef str)
  215. {
  216. if (str.isEmpty()) return AutoScale::useDefault;
  217. if (str == CharPointer_ASCII { "0" }) return AutoScale::scaled;
  218. if (str == CharPointer_ASCII { "1" }) return AutoScale::unscaled;
  219. jassertfalse;
  220. return AutoScale::useDefault;
  221. }
  222. static const char* autoScaleToString (AutoScale autoScale)
  223. {
  224. if (autoScale == AutoScale::scaled) return "0";
  225. if (autoScale == AutoScale::unscaled) return "1";
  226. return {};
  227. }
  228. AutoScale getAutoScaleValueForPlugin (const String& identifier)
  229. {
  230. if (identifier.isNotEmpty())
  231. {
  232. auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
  233. plugins.removeEmptyStrings();
  234. for (auto& plugin : plugins)
  235. {
  236. auto fromIdentifier = plugin.fromFirstOccurrenceOf (identifier, false, false);
  237. if (fromIdentifier.isNotEmpty())
  238. return autoScaleFromString (fromIdentifier.fromFirstOccurrenceOf (":", false, false));
  239. }
  240. }
  241. return AutoScale::useDefault;
  242. }
  243. void setAutoScaleValueForPlugin (const String& identifier, AutoScale s)
  244. {
  245. auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
  246. plugins.removeEmptyStrings();
  247. auto index = [identifier, plugins]
  248. {
  249. auto it = std::find_if (plugins.begin(), plugins.end(),
  250. [&] (const String& str) { return str.startsWith (identifier); });
  251. return (int) std::distance (plugins.begin(), it);
  252. }();
  253. if (s == AutoScale::useDefault && index != plugins.size())
  254. {
  255. plugins.remove (index);
  256. }
  257. else
  258. {
  259. auto str = identifier + ":" + autoScaleToString (s);
  260. if (index != plugins.size())
  261. plugins.getReference (index) = str;
  262. else
  263. plugins.add (str);
  264. }
  265. getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n"));
  266. }
  267. static bool isAutoScaleAvailableForPlugin (const PluginDescription& description)
  268. {
  269. return autoScaleOptionAvailable
  270. && (description.pluginFormatName.containsIgnoreCase ("VST")
  271. || description.pluginFormatName.containsIgnoreCase ("LV2"));
  272. }
  273. bool shouldAutoScalePlugin (const PluginDescription& description)
  274. {
  275. if (! isAutoScaleAvailableForPlugin (description))
  276. return false;
  277. const auto scaleValue = getAutoScaleValueForPlugin (description.fileOrIdentifier);
  278. return (scaleValue == AutoScale::scaled
  279. || (scaleValue == AutoScale::useDefault
  280. && getAppProperties().getUserSettings()->getBoolValue ("autoScalePluginWindows")));
  281. }
  282. void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance,
  283. PopupMenu& menu)
  284. {
  285. if (pluginInstance == nullptr)
  286. return;
  287. auto description = pluginInstance->getPluginDescription();
  288. if (! isAutoScaleAvailableForPlugin (description))
  289. return;
  290. auto identifier = description.fileOrIdentifier;
  291. PopupMenu autoScaleMenu;
  292. autoScaleMenu.addItem ("Default",
  293. true,
  294. getAutoScaleValueForPlugin (identifier) == AutoScale::useDefault,
  295. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::useDefault); });
  296. autoScaleMenu.addItem ("Enabled",
  297. true,
  298. getAutoScaleValueForPlugin (identifier) == AutoScale::scaled,
  299. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::scaled); });
  300. autoScaleMenu.addItem ("Disabled",
  301. true,
  302. getAutoScaleValueForPlugin (identifier) == AutoScale::unscaled,
  303. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::unscaled); });
  304. menu.addSubMenu ("Auto-scale window", autoScaleMenu);
  305. }
  306. // This kicks the whole thing off..
  307. START_JUCE_APPLICATION (PluginHostApp)