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.

477 lines
16KB

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