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.

509 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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 "PluginGraph.h"
  21. #include "InternalPlugins.h"
  22. #include "../UI/GraphEditorPanel.h"
  23. static std::unique_ptr<ScopedDPIAwarenessDisabler> makeDPIAwarenessDisablerForPlugin (const PluginDescription& desc)
  24. {
  25. return shouldAutoScalePlugin (desc) ? std::make_unique<ScopedDPIAwarenessDisabler>()
  26. : nullptr;
  27. }
  28. //==============================================================================
  29. PluginGraph::PluginGraph (AudioPluginFormatManager& fm, KnownPluginList& kpl)
  30. : FileBasedDocument (getFilenameSuffix(),
  31. getFilenameWildcard(),
  32. "Load a graph",
  33. "Save a graph"),
  34. formatManager (fm),
  35. knownPlugins (kpl)
  36. {
  37. newDocument();
  38. graph.addListener (this);
  39. }
  40. PluginGraph::~PluginGraph()
  41. {
  42. graph.removeListener (this);
  43. graph.removeChangeListener (this);
  44. graph.clear();
  45. }
  46. PluginGraph::NodeID PluginGraph::getNextUID() noexcept
  47. {
  48. return PluginGraph::NodeID (++(lastUID.uid));
  49. }
  50. //==============================================================================
  51. void PluginGraph::changeListenerCallback (ChangeBroadcaster*)
  52. {
  53. changed();
  54. for (int i = activePluginWindows.size(); --i >= 0;)
  55. if (! graph.getNodes().contains (activePluginWindows.getUnchecked(i)->node))
  56. activePluginWindows.remove (i);
  57. }
  58. AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name) const
  59. {
  60. for (auto* node : graph.getNodes())
  61. if (auto p = node->getProcessor())
  62. if (p->getName().equalsIgnoreCase (name))
  63. return node;
  64. return nullptr;
  65. }
  66. void PluginGraph::addPlugin (const PluginDescription& desc, Point<double> pos)
  67. {
  68. std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc);
  69. formatManager.createPluginInstanceAsync (desc,
  70. graph.getSampleRate(),
  71. graph.getBlockSize(),
  72. [this, pos, dpiDisabler] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
  73. {
  74. addPluginCallback (std::move (instance), error, pos);
  75. });
  76. }
  77. void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instance,
  78. const String& error, Point<double> pos)
  79. {
  80. if (instance == nullptr)
  81. {
  82. AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
  83. TRANS("Couldn't create plugin"),
  84. error);
  85. }
  86. else
  87. {
  88. instance->enableAllBuses();
  89. if (auto node = graph.addNode (std::move (instance)))
  90. {
  91. node->properties.set ("x", pos.x);
  92. node->properties.set ("y", pos.y);
  93. changed();
  94. }
  95. }
  96. }
  97. void PluginGraph::setNodePosition (NodeID nodeID, Point<double> pos)
  98. {
  99. if (auto* n = graph.getNodeForId (nodeID))
  100. {
  101. n->properties.set ("x", jlimit (0.0, 1.0, pos.x));
  102. n->properties.set ("y", jlimit (0.0, 1.0, pos.y));
  103. }
  104. }
  105. Point<double> PluginGraph::getNodePosition (NodeID nodeID) const
  106. {
  107. if (auto* n = graph.getNodeForId (nodeID))
  108. return { static_cast<double> (n->properties ["x"]),
  109. static_cast<double> (n->properties ["y"]) };
  110. return {};
  111. }
  112. //==============================================================================
  113. void PluginGraph::clear()
  114. {
  115. closeAnyOpenPluginWindows();
  116. graph.clear();
  117. changed();
  118. }
  119. PluginWindow* PluginGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node, PluginWindow::Type type)
  120. {
  121. jassert (node != nullptr);
  122. #if JUCE_IOS || JUCE_ANDROID
  123. closeAnyOpenPluginWindows();
  124. #else
  125. for (auto* w : activePluginWindows)
  126. if (w->node.get() == node && w->type == type)
  127. return w;
  128. #endif
  129. if (auto* processor = node->getProcessor())
  130. {
  131. if (auto* plugin = dynamic_cast<AudioPluginInstance*> (processor))
  132. {
  133. auto description = plugin->getPluginDescription();
  134. if (! plugin->hasEditor() && description.pluginFormatName == "Internal")
  135. {
  136. getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false);
  137. return nullptr;
  138. }
  139. auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description);
  140. return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows));
  141. }
  142. }
  143. return nullptr;
  144. }
  145. bool PluginGraph::closeAnyOpenPluginWindows()
  146. {
  147. bool wasEmpty = activePluginWindows.isEmpty();
  148. activePluginWindows.clear();
  149. return ! wasEmpty;
  150. }
  151. //==============================================================================
  152. String PluginGraph::getDocumentTitle()
  153. {
  154. if (! getFile().exists())
  155. return "Unnamed";
  156. return getFile().getFileNameWithoutExtension();
  157. }
  158. void PluginGraph::newDocument()
  159. {
  160. clear();
  161. setFile ({});
  162. graph.removeChangeListener (this);
  163. InternalPluginFormat internalFormat;
  164. jassert (internalFormat.getAllTypes().size() > 3);
  165. addPlugin (internalFormat.getAllTypes()[0], { 0.5, 0.1 });
  166. addPlugin (internalFormat.getAllTypes()[1], { 0.25, 0.1 });
  167. addPlugin (internalFormat.getAllTypes()[2], { 0.5, 0.9 });
  168. addPlugin (internalFormat.getAllTypes()[3], { 0.25, 0.9 });
  169. MessageManager::callAsync ([this]
  170. {
  171. setChangedFlag (false);
  172. graph.addChangeListener (this);
  173. });
  174. }
  175. Result PluginGraph::loadDocument (const File& file)
  176. {
  177. if (auto xml = parseXMLIfTagMatches (file, "FILTERGRAPH"))
  178. {
  179. graph.removeChangeListener (this);
  180. restoreFromXml (*xml);
  181. MessageManager::callAsync ([this]
  182. {
  183. setChangedFlag (false);
  184. graph.addChangeListener (this);
  185. });
  186. return Result::ok();
  187. }
  188. return Result::fail ("Not a valid graph file");
  189. }
  190. Result PluginGraph::saveDocument (const File& file)
  191. {
  192. auto xml = createXml();
  193. if (! xml->writeTo (file, {}))
  194. return Result::fail ("Couldn't write to the file");
  195. return Result::ok();
  196. }
  197. File PluginGraph::getLastDocumentOpened()
  198. {
  199. RecentlyOpenedFilesList recentFiles;
  200. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  201. ->getValue ("recentFilterGraphFiles"));
  202. return recentFiles.getFile (0);
  203. }
  204. void PluginGraph::setLastDocumentOpened (const File& file)
  205. {
  206. RecentlyOpenedFilesList recentFiles;
  207. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  208. ->getValue ("recentFilterGraphFiles"));
  209. recentFiles.addFile (file);
  210. getAppProperties().getUserSettings()
  211. ->setValue ("recentFilterGraphFiles", recentFiles.toString());
  212. }
  213. //==============================================================================
  214. static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, AudioProcessor& plugin,
  215. const XmlElement& xml, bool isInput)
  216. {
  217. auto& targetBuses = (isInput ? busesLayout.inputBuses
  218. : busesLayout.outputBuses);
  219. int maxNumBuses = 0;
  220. if (auto* buses = xml.getChildByName (isInput ? "INPUTS" : "OUTPUTS"))
  221. {
  222. for (auto* e : buses->getChildWithTagNameIterator ("BUS"))
  223. {
  224. const int busIdx = e->getIntAttribute ("index");
  225. maxNumBuses = jmax (maxNumBuses, busIdx + 1);
  226. // the number of buses on busesLayout may not be in sync with the plugin after adding buses
  227. // because adding an input bus could also add an output bus
  228. for (int actualIdx = plugin.getBusCount (isInput) - 1; actualIdx < busIdx; ++actualIdx)
  229. if (! plugin.addBus (isInput))
  230. return;
  231. for (int actualIdx = targetBuses.size() - 1; actualIdx < busIdx; ++actualIdx)
  232. targetBuses.add (plugin.getChannelLayoutOfBus (isInput, busIdx));
  233. auto layout = e->getStringAttribute ("layout");
  234. if (layout.isNotEmpty())
  235. targetBuses.getReference (busIdx) = AudioChannelSet::fromAbbreviatedString (layout);
  236. }
  237. }
  238. // if the plugin has more buses than specified in the xml, then try to remove them!
  239. while (maxNumBuses < targetBuses.size())
  240. {
  241. if (! plugin.removeBus (isInput))
  242. return;
  243. targetBuses.removeLast();
  244. }
  245. }
  246. //==============================================================================
  247. static XmlElement* createBusLayoutXml (const AudioProcessor::BusesLayout& layout, const bool isInput)
  248. {
  249. auto& buses = isInput ? layout.inputBuses
  250. : layout.outputBuses;
  251. auto* xml = new XmlElement (isInput ? "INPUTS" : "OUTPUTS");
  252. for (int busIdx = 0; busIdx < buses.size(); ++busIdx)
  253. {
  254. auto& set = buses.getReference (busIdx);
  255. auto* bus = xml->createNewChildElement ("BUS");
  256. bus->setAttribute ("index", busIdx);
  257. bus->setAttribute ("layout", set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString());
  258. }
  259. return xml;
  260. }
  261. static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcept
  262. {
  263. if (auto* plugin = dynamic_cast<AudioPluginInstance*> (node->getProcessor()))
  264. {
  265. auto e = new XmlElement ("FILTER");
  266. e->setAttribute ("uid", (int) node->nodeID.uid);
  267. e->setAttribute ("x", node->properties ["x"].toString());
  268. e->setAttribute ("y", node->properties ["y"].toString());
  269. for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
  270. {
  271. auto type = (PluginWindow::Type) i;
  272. if (node->properties.contains (PluginWindow::getOpenProp (type)))
  273. {
  274. e->setAttribute (PluginWindow::getLastXProp (type), node->properties[PluginWindow::getLastXProp (type)].toString());
  275. e->setAttribute (PluginWindow::getLastYProp (type), node->properties[PluginWindow::getLastYProp (type)].toString());
  276. e->setAttribute (PluginWindow::getOpenProp (type), node->properties[PluginWindow::getOpenProp (type)].toString());
  277. }
  278. }
  279. {
  280. PluginDescription pd;
  281. plugin->fillInPluginDescription (pd);
  282. e->addChildElement (pd.createXml().release());
  283. }
  284. {
  285. MemoryBlock m;
  286. node->getProcessor()->getStateInformation (m);
  287. e->createNewChildElement ("STATE")->addTextElement (m.toBase64Encoding());
  288. }
  289. auto layout = plugin->getBusesLayout();
  290. auto layouts = e->createNewChildElement ("LAYOUT");
  291. layouts->addChildElement (createBusLayoutXml (layout, true));
  292. layouts->addChildElement (createBusLayoutXml (layout, false));
  293. return e;
  294. }
  295. jassertfalse;
  296. return nullptr;
  297. }
  298. void PluginGraph::createNodeFromXml (const XmlElement& xml)
  299. {
  300. PluginDescription pd;
  301. for (auto* e : xml.getChildIterator())
  302. {
  303. if (pd.loadFromXml (*e))
  304. break;
  305. }
  306. auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance>
  307. {
  308. auto createInstance = [this] (const PluginDescription& description)
  309. {
  310. String errorMessage;
  311. auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description);
  312. return formatManager.createPluginInstance (description,
  313. graph.getSampleRate(),
  314. graph.getBlockSize(),
  315. errorMessage);
  316. };
  317. if (auto instance = createInstance (pd))
  318. return instance;
  319. const auto allFormats = formatManager.getFormats();
  320. const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(),
  321. [&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginFormatName; });
  322. if (matchingFormat == allFormats.end())
  323. return nullptr;
  324. const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat);
  325. const auto matchingPlugin = std::find_if (plugins.begin(), plugins.end(),
  326. [&] (const PluginDescription& desc) { return pd.uniqueId == desc.uniqueId; });
  327. if (matchingPlugin == plugins.end())
  328. return nullptr;
  329. return createInstance (*matchingPlugin);
  330. };
  331. if (auto instance = createInstanceWithFallback())
  332. {
  333. if (auto* layoutEntity = xml.getChildByName ("LAYOUT"))
  334. {
  335. auto layout = instance->getBusesLayout();
  336. readBusLayoutFromXml (layout, *instance, *layoutEntity, true);
  337. readBusLayoutFromXml (layout, *instance, *layoutEntity, false);
  338. instance->setBusesLayout (layout);
  339. }
  340. if (auto node = graph.addNode (std::move (instance), NodeID ((uint32) xml.getIntAttribute ("uid"))))
  341. {
  342. if (auto* state = xml.getChildByName ("STATE"))
  343. {
  344. MemoryBlock m;
  345. m.fromBase64Encoding (state->getAllSubText());
  346. node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize());
  347. }
  348. node->properties.set ("x", xml.getDoubleAttribute ("x"));
  349. node->properties.set ("y", xml.getDoubleAttribute ("y"));
  350. for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
  351. {
  352. auto type = (PluginWindow::Type) i;
  353. if (xml.hasAttribute (PluginWindow::getOpenProp (type)))
  354. {
  355. node->properties.set (PluginWindow::getLastXProp (type), xml.getIntAttribute (PluginWindow::getLastXProp (type)));
  356. node->properties.set (PluginWindow::getLastYProp (type), xml.getIntAttribute (PluginWindow::getLastYProp (type)));
  357. node->properties.set (PluginWindow::getOpenProp (type), xml.getIntAttribute (PluginWindow::getOpenProp (type)));
  358. if (node->properties[PluginWindow::getOpenProp (type)])
  359. {
  360. jassert (node->getProcessor() != nullptr);
  361. if (auto w = getOrCreateWindowFor (node, type))
  362. w->toFront (true);
  363. }
  364. }
  365. }
  366. }
  367. }
  368. }
  369. std::unique_ptr<XmlElement> PluginGraph::createXml() const
  370. {
  371. auto xml = std::make_unique<XmlElement> ("FILTERGRAPH");
  372. for (auto* node : graph.getNodes())
  373. xml->addChildElement (createNodeXml (node));
  374. for (auto& connection : graph.getConnections())
  375. {
  376. auto e = xml->createNewChildElement ("CONNECTION");
  377. e->setAttribute ("srcFilter", (int) connection.source.nodeID.uid);
  378. e->setAttribute ("srcChannel", connection.source.channelIndex);
  379. e->setAttribute ("dstFilter", (int) connection.destination.nodeID.uid);
  380. e->setAttribute ("dstChannel", connection.destination.channelIndex);
  381. }
  382. return xml;
  383. }
  384. void PluginGraph::restoreFromXml (const XmlElement& xml)
  385. {
  386. clear();
  387. for (auto* e : xml.getChildWithTagNameIterator ("FILTER"))
  388. {
  389. createNodeFromXml (*e);
  390. changed();
  391. }
  392. for (auto* e : xml.getChildWithTagNameIterator ("CONNECTION"))
  393. {
  394. graph.addConnection ({ { NodeID ((uint32) e->getIntAttribute ("srcFilter")), e->getIntAttribute ("srcChannel") },
  395. { NodeID ((uint32) e->getIntAttribute ("dstFilter")), e->getIntAttribute ("dstChannel") } });
  396. }
  397. graph.removeIllegalConnections();
  398. }
  399. File PluginGraph::getDefaultGraphDocumentOnMobile()
  400. {
  401. auto persistantStorageLocation = File::getSpecialLocation (File::userApplicationDataDirectory);
  402. return persistantStorageLocation.getChildFile ("state.filtergraph");
  403. }