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.

483 lines
16KB

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