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.

481 lines
16KB

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