From d0514c7924d1fa44d7e76dfefb7d6ea903adae48 Mon Sep 17 00:00:00 2001 From: jules Date: Thu, 26 Oct 2017 17:26:24 +0100 Subject: [PATCH] Cleanup and refactoring work on the AudioProcessorGraph and the audio plugin host demo --- BREAKING-CHANGES.txt | 26 + .../Builds/LinuxMakefile/Makefile | 2 + .../Plugin Host.xcodeproj/project.pbxproj | 4 +- .../VisualStudio2013/Plugin Host_App.vcxproj | 1 + .../Plugin Host_App.vcxproj.filters | 3 + .../VisualStudio2015/Plugin Host_App.vcxproj | 1 + .../Plugin Host_App.vcxproj.filters | 3 + .../VisualStudio2017/Plugin Host_App.vcxproj | 1 + .../Plugin Host_App.vcxproj.filters | 3 + examples/audio plugin host/Plugin Host.jucer | 25 +- .../audio plugin host/Source/FilterGraph.cpp | 359 ++-- .../audio plugin host/Source/FilterGraph.h | 67 +- .../Source/FilterIOConfiguration.cpp | 118 +- .../Source/FilterIOConfiguration.h | 33 +- .../Source/GraphEditorPanel.cpp | 667 ++---- .../Source/GraphEditorPanel.h | 109 +- .../audio plugin host/Source/HostStartup.cpp | 4 +- .../Source/InternalFilters.cpp | 15 +- .../Source/InternalFilters.h | 1 + .../Source/MainHostWindow.cpp | 113 +- .../audio plugin host/Source/MainHostWindow.h | 36 +- .../audio plugin host/Source/PluginWindow.h | 201 ++ .../processors/juce_AudioProcessorGraph.cpp | 1831 +++++++---------- .../processors/juce_AudioProcessorGraph.h | 191 +- 24 files changed, 1699 insertions(+), 2115 deletions(-) create mode 100644 examples/audio plugin host/Source/PluginWindow.h diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index d637971071..52f11541e8 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -1,6 +1,32 @@ JUCE breaking changes ===================== + + +Develop +======= +Change +------ +AudioProcessorGraph interface has changed in a number of ways - Node objects +are now reference counted, there are different accessor methods to iterate them, +and misc other small improvements to the API + +Possible Issues +--------------- +The changes won't cause any silent errors in user code, but will require some +manual refactoring + +Workaround +---------- +Just find equivalent new methods to replace existing code. + +Rationale +--------- +The graph class was extremely old and creaky, and these changes is the start of +an improvement process that should eventually result in it being broken down +into fundamental graph building block classes for use in other contexts. + + Version 5.2.0 ============= diff --git a/examples/audio plugin host/Builds/LinuxMakefile/Makefile b/examples/audio plugin host/Builds/LinuxMakefile/Makefile index 5cbf59a412..7da9f64e4a 100644 --- a/examples/audio plugin host/Builds/LinuxMakefile/Makefile +++ b/examples/audio plugin host/Builds/LinuxMakefile/Makefile @@ -23,6 +23,8 @@ ifndef CONFIG CONFIG=Debug endif +JUCE_ARCH_LABEL := $(shell uname -m) + ifeq ($(CONFIG),Debug) JUCE_BINDIR := build JUCE_LIBDIR := build diff --git a/examples/audio plugin host/Builds/MacOSX/Plugin Host.xcodeproj/project.pbxproj b/examples/audio plugin host/Builds/MacOSX/Plugin Host.xcodeproj/project.pbxproj index 5239e4fee9..67b2b125e6 100644 --- a/examples/audio plugin host/Builds/MacOSX/Plugin Host.xcodeproj/project.pbxproj +++ b/examples/audio plugin host/Builds/MacOSX/Plugin Host.xcodeproj/project.pbxproj @@ -70,6 +70,7 @@ 6692043E22BB181F01767845 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainHostWindow.h; path = ../../Source/MainHostWindow.h; sourceTree = "SOURCE_ROOT"; }; 683CEE986A2467C850FE99E6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_core.mm"; path = "../../JuceLibraryCode/include_juce_core.mm"; sourceTree = "SOURCE_ROOT"; }; 6A71B2BCAC4239072BC2BD7E = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_basics"; path = "../../../../modules/juce_audio_basics"; sourceTree = "SOURCE_ROOT"; }; + 714C53257417E615916687E5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginWindow.h; path = ../../Source/PluginWindow.h; sourceTree = "SOURCE_ROOT"; }; 7DA35787B5F6F7440D667CC8 = {isa = PBXFileReference; lastKnownFileType = file.nib; name = RecentFilesMenuTemplate.nib; path = RecentFilesMenuTemplate.nib; sourceTree = "SOURCE_ROOT"; }; 81C1A7770E082F56FE5A90A7 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_opengl"; path = "../../../../modules/juce_opengl"; sourceTree = "SOURCE_ROOT"; }; 82800DBA287EF4BAB13B42FB = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_graphics.mm"; path = "../../JuceLibraryCode/include_juce_graphics.mm"; sourceTree = "SOURCE_ROOT"; }; @@ -115,7 +116,8 @@ 362BB539489999164C3A3D5B, EE1BEF4055936CD0C543687C, 1EC0F33A3BABE58138317375, - 6692043E22BB181F01767845, ); name = "Plugin Host"; sourceTree = ""; }; + 6692043E22BB181F01767845, + 714C53257417E615916687E5, ); name = "Plugin Host"; sourceTree = ""; }; 9D8FE1F65CAD416AA606C47A = {isa = PBXGroup; children = ( 6A71B2BCAC4239072BC2BD7E, 5313EB852E41EE58B199B9A2, diff --git a/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj b/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj index 2e69a4d238..159ac25ee1 100644 --- a/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj +++ b/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj @@ -1779,6 +1779,7 @@ + diff --git a/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj.filters b/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj.filters index 546b36edd3..6d07606e09 100644 --- a/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj.filters +++ b/examples/audio plugin host/Builds/VisualStudio2013/Plugin Host_App.vcxproj.filters @@ -2169,6 +2169,9 @@ Plugin Host + + Plugin Host + Juce Modules\juce_audio_basics\audio_play_head diff --git a/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj b/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj index 73b1c6855e..6270d9164e 100644 --- a/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj +++ b/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj @@ -1779,6 +1779,7 @@ + diff --git a/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj.filters b/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj.filters index 4c998d421f..6609efc85b 100644 --- a/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj.filters +++ b/examples/audio plugin host/Builds/VisualStudio2015/Plugin Host_App.vcxproj.filters @@ -2169,6 +2169,9 @@ Plugin Host + + Plugin Host + Juce Modules\juce_audio_basics\audio_play_head diff --git a/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj b/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj index 4ffd566b21..2cd44a7d60 100644 --- a/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj +++ b/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj @@ -1779,6 +1779,7 @@ + diff --git a/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj.filters b/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj.filters index 2457c535ab..98c8d6c0dc 100644 --- a/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj.filters +++ b/examples/audio plugin host/Builds/VisualStudio2017/Plugin Host_App.vcxproj.filters @@ -2169,6 +2169,9 @@ Plugin Host + + Plugin Host + Juce Modules\juce_audio_basics\audio_play_head diff --git a/examples/audio plugin host/Plugin Host.jucer b/examples/audio plugin host/Plugin Host.jucer index 1c379e0c9f..1ce6af150d 100644 --- a/examples/audio plugin host/Plugin Host.jucer +++ b/examples/audio plugin host/Plugin Host.jucer @@ -10,9 +10,11 @@ objCExtraSuffix="M73TRi" vst3Folder="" extraCompilerFlags="-Wall -Wshadow -Wstrict-aliasing -Wconversion -Wsign-compare -Woverloaded-virtual -Wextra-semi"> + osxSDK="default" osxCompatibility="default" osxArchitecture="default" + enablePluginBinaryCopyStep="1"/> + osxSDK="default" osxCompatibility="default" osxArchitecture="default" + enablePluginBinaryCopyStep="1"/> @@ -58,9 +60,11 @@ + isDebug="1" optimisation="1" targetName="Plugin Host" debugInformationFormat="ProgramDatabase" + enablePluginBinaryCopyStep="0"/> + isDebug="0" optimisation="3" targetName="Plugin Host" debugInformationFormat="ProgramDatabase" + enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/> @@ -82,9 +86,11 @@ + isDebug="1" optimisation="1" targetName="Plugin Host" debugInformationFormat="ProgramDatabase" + enablePluginBinaryCopyStep="0"/> + isDebug="0" optimisation="3" targetName="Plugin Host" debugInformationFormat="ProgramDatabase" + enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/> @@ -106,9 +112,11 @@ + isDebug="1" optimisation="1" targetName="Plugin Host" debugInformationFormat="ProgramDatabase" + enablePluginBinaryCopyStep="0"/> + isDebug="0" optimisation="3" targetName="Plugin Host" debugInformationFormat="ProgramDatabase" + enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/> @@ -151,6 +159,7 @@ file="Source/MainHostWindow.cpp"/> + = 0;) + if (! graph.getNodes().contains (activePluginWindows.getUnchecked(i)->node)) + activePluginWindows.remove (i); } AudioProcessorGraph::Node::Ptr FilterGraph::getNodeForName (const String& name) const { - for (int i = 0; i < graph.getNumNodes(); i++) - if (auto node = graph.getNode (i)) - if (auto p = node->getProcessor()) - if (p->getName().equalsIgnoreCase (name)) - return node; + for (auto* node : graph.getNodes()) + if (auto p = node->getProcessor()) + if (p->getName().equalsIgnoreCase (name)) + return node; return nullptr; } -void FilterGraph::addFilter (const PluginDescription& desc, Point p) +void FilterGraph::addPlugin (const PluginDescription& desc, Point p) { struct AsyncCallback : public AudioPluginFormat::InstantiationCompletionCallback { @@ -106,7 +95,9 @@ void FilterGraph::addFilter (const PluginDescription& desc, Point p) Point position; }; - formatManager.createPluginInstanceAsync (desc, graph.getSampleRate(), graph.getBlockSize(), + formatManager.createPluginInstanceAsync (desc, + graph.getSampleRate(), + graph.getBlockSize(), new AsyncCallback (*this, p)); } @@ -122,7 +113,7 @@ void FilterGraph::addFilterCallback (AudioPluginInstance* instance, const String { instance->enableAllBuses(); - if (auto* node = graph.addNode (instance)) + if (auto node = graph.addNode (instance)) { node->properties.set ("x", pos.x); node->properties.set ("y", pos.y); @@ -131,38 +122,18 @@ void FilterGraph::addFilterCallback (AudioPluginInstance* instance, const String } } -void FilterGraph::removeFilter (const uint32 id) +void FilterGraph::setNodePosition (NodeID nodeID, Point pos) { - PluginWindow::closeCurrentlyOpenWindowsFor (id); - - if (graph.removeNode (id)) - changed(); -} - -void FilterGraph::disconnectFilter (const uint32 id) -{ - if (graph.disconnectNode (id)) - changed(); -} - -void FilterGraph::removeIllegalConnections() -{ - if (graph.removeIllegalConnections()) - changed(); -} - -void FilterGraph::setNodePosition (const uint32 nodeId, double x, double y) -{ - if (AudioProcessorGraph::Node::Ptr n = graph.getNodeForId (nodeId)) + if (auto* n = graph.getNodeForId (nodeID)) { - n->properties.set ("x", jlimit (0.0, 1.0, x)); - n->properties.set ("y", jlimit (0.0, 1.0, y)); + n->properties.set ("x", jlimit (0.0, 1.0, pos.x)); + n->properties.set ("y", jlimit (0.0, 1.0, pos.y)); } } -Point FilterGraph::getNodePosition (const uint32 nodeId) const +Point FilterGraph::getNodePosition (NodeID nodeID) const { - if (auto n = graph.getNodeForId (nodeId)) + if (auto* n = graph.getNodeForId (nodeID)) return { static_cast (n->properties ["x"]), static_cast (n->properties ["y"]) }; @@ -170,62 +141,45 @@ Point FilterGraph::getNodePosition (const uint32 nodeId) const } //============================================================================== -int FilterGraph::getNumConnections() const noexcept -{ - return graph.getNumConnections(); -} - -const AudioProcessorGraph::Connection* FilterGraph::getConnection (const int index) const noexcept -{ - return graph.getConnection (index); -} - -const AudioProcessorGraph::Connection* FilterGraph::getConnectionBetween (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel) const noexcept +void FilterGraph::clear() { - return graph.getConnectionBetween (sourceFilterUID, sourceFilterChannel, - destFilterUID, destFilterChannel); + closeAnyOpenPluginWindows(); + graph.clear(); + changed(); } -bool FilterGraph::canConnect (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel) const noexcept +PluginWindow* FilterGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node, PluginWindow::Type type) { - return graph.canConnect (sourceFilterUID, sourceFilterChannel, - destFilterUID, destFilterChannel); -} + jassert (node != nullptr); -bool FilterGraph::addConnection (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel) -{ - const bool result = graph.addConnection (sourceFilterUID, sourceFilterChannel, - destFilterUID, destFilterChannel); + for (auto* w : activePluginWindows) + if (w->node == node && w->type == type) + return w; - if (result) - changed(); + if (auto* processor = node->getProcessor()) + { + if (auto* plugin = dynamic_cast (processor)) + { + auto description = plugin->getPluginDescription(); - return result; -} + if (description.pluginFormatName == "Internal") + { + getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false); + return nullptr; + } + } -void FilterGraph::removeConnection (const int index) -{ - graph.removeConnection (index); - changed(); -} + return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows)); + } -void FilterGraph::removeConnection (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel) -{ - if (graph.removeConnection (sourceFilterUID, sourceFilterChannel, - destFilterUID, destFilterChannel)) - changed(); + return nullptr; } -void FilterGraph::clear() +bool FilterGraph::closeAnyOpenPluginWindows() { - PluginWindow::closeAllCurrentlyOpenWindows(); - - graph.clear(); - changed(); + bool wasEmpty = activePluginWindows.isEmpty(); + activePluginWindows.clear(); + return ! wasEmpty; } //============================================================================== @@ -244,9 +198,9 @@ void FilterGraph::newDocument() InternalPluginFormat internalFormat; - addFilter (internalFormat.audioInDesc, { 0.5, 0.1 }); - addFilter (internalFormat.midiInDesc, { 0.25, 0.1 }); - addFilter (internalFormat.audioOutDesc, { 0.5, 0.9 }); + addPlugin (internalFormat.audioInDesc, { 0.5, 0.1 }); + addPlugin (internalFormat.midiInDesc, { 0.25, 0.1 }); + addPlugin (internalFormat.audioOutDesc, { 0.5, 0.9 }); setChangedFlag (false); } @@ -267,7 +221,7 @@ Result FilterGraph::saveDocument (const File& file) { ScopedPointer xml (createXml()); - if (! xml->writeToFile (file, String())) + if (! xml->writeToFile (file, {})) return Result::fail ("Couldn't write to the file"); return Result::ok(); @@ -295,9 +249,11 @@ void FilterGraph::setLastDocumentOpened (const File& file) } //============================================================================== -static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, AudioProcessor* plugin, const XmlElement& xml, const bool isInput) +static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, AudioProcessor* plugin, + const XmlElement& xml, const bool isInput) { - Array& targetBuses = (isInput ? busesLayout.inputBuses : busesLayout.outputBuses); + auto& targetBuses = (isInput ? busesLayout.inputBuses + : busesLayout.outputBuses); int maxNumBuses = 0; if (auto* buses = xml.getChildByName (isInput ? "INPUTS" : "OUTPUTS")) @@ -310,12 +266,13 @@ static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, Audi // the number of buses on busesLayout may not be in sync with the plugin after adding buses // because adding an input bus could also add an output bus for (int actualIdx = plugin->getBusCount (isInput) - 1; actualIdx < busIdx; ++actualIdx) - if (! plugin->addBus (isInput)) return; + if (! plugin->addBus (isInput)) + return; for (int actualIdx = targetBuses.size() - 1; actualIdx < busIdx; ++actualIdx) targetBuses.add (plugin->getChannelLayoutOfBus (isInput, busIdx)); - const String& layout = e->getStringAttribute("layout"); + auto layout = e->getStringAttribute ("layout"); if (layout.isNotEmpty()) targetBuses.getReference (busIdx) = AudioChannelSet::fromAbbreviatedString (layout); @@ -335,22 +292,18 @@ static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, Audi //============================================================================== static XmlElement* createBusLayoutXml (const AudioProcessor::BusesLayout& layout, const bool isInput) { - const Array& buses = (isInput ? layout.inputBuses : layout.outputBuses); + auto& buses = isInput ? layout.inputBuses + : layout.outputBuses; - XmlElement* xml = new XmlElement (isInput ? "INPUTS" : "OUTPUTS"); + auto* xml = new XmlElement (isInput ? "INPUTS" : "OUTPUTS"); - const int n = buses.size(); - for (int busIdx = 0; busIdx < n; ++busIdx) + for (int busIdx = 0; busIdx < buses.size(); ++busIdx) { - XmlElement* bus = new XmlElement ("BUS"); - bus->setAttribute ("index", busIdx); - - const AudioChannelSet& set = buses.getReference (busIdx); - const String layoutName = set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString(); - - bus->setAttribute ("layout", layoutName); + auto& set = buses.getReference (busIdx); - xml->addChildElement (bus); + auto* bus = xml->createNewChildElement ("BUS"); + bus->setAttribute ("index", busIdx); + bus->setAttribute ("layout", set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString()); } return xml; @@ -358,53 +311,48 @@ static XmlElement* createBusLayoutXml (const AudioProcessor::BusesLayout& layout static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcept { - AudioPluginInstance* plugin = dynamic_cast (node->getProcessor()); - - if (plugin == nullptr) + if (auto* plugin = dynamic_cast (node->getProcessor())) { - jassertfalse; - return nullptr; - } + auto e = new XmlElement ("FILTER"); + e->setAttribute ("uid", (int) node->nodeID); + e->setAttribute ("x", node->properties ["x"].toString()); + e->setAttribute ("y", node->properties ["y"].toString()); - XmlElement* e = new XmlElement ("FILTER"); - e->setAttribute ("uid", (int) node->nodeId); - e->setAttribute ("x", node->properties ["x"].toString()); - e->setAttribute ("y", node->properties ["y"].toString()); + for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) + { + auto type = (PluginWindow::Type) i; - for (int i = 0; i < PluginWindow::NumTypes; ++i) - { - PluginWindow::WindowFormatType type = (PluginWindow::WindowFormatType) i; + if (node->properties.contains (PluginWindow::getOpenProp (type))) + { + e->setAttribute (PluginWindow::getLastXProp (type), node->properties[PluginWindow::getLastXProp (type)].toString()); + e->setAttribute (PluginWindow::getLastYProp (type), node->properties[PluginWindow::getLastYProp (type)].toString()); + e->setAttribute (PluginWindow::getOpenProp (type), node->properties[PluginWindow::getOpenProp (type)].toString()); + } + } - if (node->properties.contains (getOpenProp (type))) { - e->setAttribute (getLastXProp (type), node->properties[getLastXProp (type)].toString()); - e->setAttribute (getLastYProp (type), node->properties[getLastYProp (type)].toString()); - e->setAttribute (getOpenProp (type), node->properties[getOpenProp (type)].toString()); + PluginDescription pd; + plugin->fillInPluginDescription (pd); + e->addChildElement (pd.createXml()); } - } - PluginDescription pd; - plugin->fillInPluginDescription (pd); - - e->addChildElement (pd.createXml()); - - XmlElement* state = new XmlElement ("STATE"); - - MemoryBlock m; - node->getProcessor()->getStateInformation (m); - state->addTextElement (m.toBase64Encoding()); - e->addChildElement (state); + { + MemoryBlock m; + node->getProcessor()->getStateInformation (m); + e->createNewChildElement ("STATE")->addTextElement (m.toBase64Encoding()); + } - XmlElement* layouts = new XmlElement ("LAYOUT"); - const AudioProcessor::BusesLayout layout = plugin->getBusesLayout(); + auto layout = plugin->getBusesLayout(); - const bool isInputChoices[] = { true, false }; - for (bool isInput : isInputChoices) - layouts->addChildElement (createBusLayoutXml (layout, isInput)); + auto layouts = e->createNewChildElement ("LAYOUT"); + layouts->addChildElement (createBusLayoutXml (layout, true)); + layouts->addChildElement (createBusLayoutXml (layout, false)); - e->addChildElement (layouts); + return e; + } - return e; + jassertfalse; + return nullptr; } void FilterGraph::createNodeFromXml (const XmlElement& xml) @@ -419,51 +367,50 @@ void FilterGraph::createNodeFromXml (const XmlElement& xml) String errorMessage; - AudioPluginInstance* instance = formatManager.createPluginInstance (pd, graph.getSampleRate(), graph.getBlockSize(), errorMessage); - - if (instance == nullptr) - return; - - if (const XmlElement* const layoutEntity = xml.getChildByName ("LAYOUT")) + if (auto* instance = formatManager.createPluginInstance (pd, graph.getSampleRate(), + graph.getBlockSize(), errorMessage)) { - AudioProcessor::BusesLayout layout = instance->getBusesLayout(); - - const bool isInputChoices[] = { true, false }; - for (bool isInput : isInputChoices) - readBusLayoutFromXml (layout, instance, *layoutEntity, isInput); - - instance->setBusesLayout (layout); - } - - AudioProcessorGraph::Node::Ptr node (graph.addNode (instance, (uint32) xml.getIntAttribute ("uid"))); + if (auto* layoutEntity = xml.getChildByName ("LAYOUT")) + { + auto layout = instance->getBusesLayout(); - if (const XmlElement* const state = xml.getChildByName ("STATE")) - { - MemoryBlock m; - m.fromBase64Encoding (state->getAllSubText()); + readBusLayoutFromXml (layout, instance, *layoutEntity, true); + readBusLayoutFromXml (layout, instance, *layoutEntity, false); - node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize()); - } + instance->setBusesLayout (layout); + } - node->properties.set ("x", xml.getDoubleAttribute ("x")); - node->properties.set ("y", xml.getDoubleAttribute ("y")); + if (auto node = graph.addNode (instance, (NodeID) xml.getIntAttribute ("uid"))) + { + if (auto* state = xml.getChildByName ("STATE")) + { + MemoryBlock m; + m.fromBase64Encoding (state->getAllSubText()); - for (int i = 0; i < PluginWindow::NumTypes; ++i) - { - PluginWindow::WindowFormatType type = (PluginWindow::WindowFormatType) i; + node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize()); + } - if (xml.hasAttribute (getOpenProp (type))) - { - node->properties.set (getLastXProp (type), xml.getIntAttribute (getLastXProp (type))); - node->properties.set (getLastYProp (type), xml.getIntAttribute (getLastYProp (type))); - node->properties.set (getOpenProp (type), xml.getIntAttribute (getOpenProp (type))); + node->properties.set ("x", xml.getDoubleAttribute ("x")); + node->properties.set ("y", xml.getDoubleAttribute ("y")); - if (node->properties[getOpenProp (type)]) + for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) { - jassert (node->getProcessor() != nullptr); - - if (PluginWindow* const w = PluginWindow::getWindowFor (node, type)) - w->toFront (true); + auto type = (PluginWindow::Type) i; + + if (xml.hasAttribute (PluginWindow::getOpenProp (type))) + { + node->properties.set (PluginWindow::getLastXProp (type), xml.getIntAttribute (PluginWindow::getLastXProp (type))); + node->properties.set (PluginWindow::getLastYProp (type), xml.getIntAttribute (PluginWindow::getLastYProp (type))); + node->properties.set (PluginWindow::getOpenProp (type), xml.getIntAttribute (PluginWindow::getOpenProp (type))); + + if (node->properties[PluginWindow::getOpenProp (type)]) + { + jassert (node->getProcessor() != nullptr); + + if (auto w = getOrCreateWindowFor (node, type)) + w->toFront (true); + } + } } } } @@ -471,23 +418,19 @@ void FilterGraph::createNodeFromXml (const XmlElement& xml) XmlElement* FilterGraph::createXml() const { - XmlElement* xml = new XmlElement ("FILTERGRAPH"); + auto* xml = new XmlElement ("FILTERGRAPH"); - for (int i = 0; i < graph.getNumNodes(); ++i) - xml->addChildElement (createNodeXml (graph.getNode (i))); + for (auto* node : graph.getNodes()) + xml->addChildElement (createNodeXml (node)); - for (int i = 0; i < graph.getNumConnections(); ++i) + for (auto& connection : graph.getConnections()) { - const AudioProcessorGraph::Connection* const fc = graph.getConnection(i); - - XmlElement* e = new XmlElement ("CONNECTION"); - - e->setAttribute ("srcFilter", (int) fc->sourceNodeId); - e->setAttribute ("srcChannel", fc->sourceChannelIndex); - e->setAttribute ("dstFilter", (int) fc->destNodeId); - e->setAttribute ("dstChannel", fc->destChannelIndex); + auto e = xml->createNewChildElement ("CONNECTION"); - xml->addChildElement (e); + e->setAttribute ("srcFilter", (int) connection.source.nodeID); + e->setAttribute ("srcChannel", connection.source.channelIndex); + e->setAttribute ("dstFilter", (int) connection.destination.nodeID); + e->setAttribute ("dstChannel", connection.destination.channelIndex); } return xml; @@ -505,10 +448,8 @@ void FilterGraph::restoreFromXml (const XmlElement& xml) forEachXmlChildElementWithTagName (xml, e, "CONNECTION") { - addConnection ((uint32) e->getIntAttribute ("srcFilter"), - e->getIntAttribute ("srcChannel"), - (uint32) e->getIntAttribute ("dstFilter"), - e->getIntAttribute ("dstChannel")); + graph.addConnection ({ { (NodeID) e->getIntAttribute ("srcFilter"), e->getIntAttribute ("srcChannel") }, + { (NodeID) e->getIntAttribute ("dstFilter"), e->getIntAttribute ("dstChannel") } }); } graph.removeIllegalConnections(); diff --git a/examples/audio plugin host/Source/FilterGraph.h b/examples/audio plugin host/Source/FilterGraph.h index ba61d844be..5a260931fb 100644 --- a/examples/audio plugin host/Source/FilterGraph.h +++ b/examples/audio plugin host/Source/FilterGraph.h @@ -26,64 +26,38 @@ #pragma once -class FilterInGraph; -class FilterGraph; +#include "PluginWindow.h" -const char* const filenameSuffix = ".filtergraph"; -const char* const filenameWildcard = "*.filtergraph"; //============================================================================== /** A collection of filters and some connections between them. */ -class FilterGraph : public FileBasedDocument, public AudioProcessorListener +class FilterGraph : public FileBasedDocument, + public AudioProcessorListener, + private ChangeListener { public: //============================================================================== - FilterGraph (AudioPluginFormatManager& formatManager); + FilterGraph (AudioPluginFormatManager&); ~FilterGraph(); //============================================================================== - AudioProcessorGraph& getGraph() noexcept { return graph; } + typedef AudioProcessorGraph::NodeID NodeID; - int getNumFilters() const noexcept; - AudioProcessorGraph::Node::Ptr getNode (int index) const noexcept; + void addPlugin (const PluginDescription&, Point); - AudioProcessorGraph::Node::Ptr getNodeForId (uint32 uid) const; AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const; - void addFilter (const PluginDescription&, Point); - - void addFilterCallback (AudioPluginInstance*, const String& error, Point pos); - - void removeFilter (const uint32 filterUID); - void disconnectFilter (const uint32 filterUID); - - void removeIllegalConnections(); - - void setNodePosition (uint32 nodeId, double x, double y); - Point getNodePosition (uint32 nodeId) const; + void setNodePosition (NodeID, Point); + Point getNodePosition (NodeID) const; //============================================================================== - int getNumConnections() const noexcept; - const AudioProcessorGraph::Connection* getConnection (const int index) const noexcept; - - const AudioProcessorGraph::Connection* getConnectionBetween (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel) const noexcept; - - bool canConnect (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel) const noexcept; - - bool addConnection (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel); - - void removeConnection (const int index); - - void removeConnection (uint32 sourceFilterUID, int sourceFilterChannel, - uint32 destFilterUID, int destFilterChannel); - void clear(); + PluginWindow* getOrCreateWindowFor (AudioProcessorGraph::Node*, PluginWindow::Type); + void closeCurrentlyOpenWindowsFor (AudioProcessorGraph::NodeID); + bool closeAnyOpenPluginWindows(); //============================================================================== void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} @@ -93,6 +67,9 @@ public: XmlElement* createXml() const; void restoreFromXml (const XmlElement& xml); + static const char* getFilenameSuffix() { return ".filtergraph"; } + static const char* getFilenameWildcard() { return "*.filtergraph"; } + //============================================================================== void newDocument(); String getDocumentTitle() override; @@ -102,21 +79,19 @@ public: void setLastDocumentOpened (const File& file) override; //============================================================================== - - - /** The special channel index used to refer to a filter's midi channel. - */ - static const int midiChannelNumber; + AudioProcessorGraph graph; private: //============================================================================== AudioPluginFormatManager& formatManager; - AudioProcessorGraph graph; + OwnedArray activePluginWindows; - uint32 lastUID = 0; - uint32 getNextUID() noexcept; + NodeID lastUID = 0; + NodeID getNextUID() noexcept; void createNodeFromXml (const XmlElement& xml); + void addFilterCallback (AudioPluginInstance*, const String& error, Point); + void changeListenerCallback (ChangeBroadcaster*) override; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilterGraph) }; diff --git a/examples/audio plugin host/Source/FilterIOConfiguration.cpp b/examples/audio plugin host/Source/FilterIOConfiguration.cpp index d7274977ac..646e8dc5aa 100644 --- a/examples/audio plugin host/Source/FilterIOConfiguration.cpp +++ b/examples/audio plugin host/Source/FilterIOConfiguration.cpp @@ -32,11 +32,10 @@ //============================================================================== -class NumberedBoxes : public TableListBox, +struct NumberedBoxes : public TableListBox, private TableListBoxModel, private Button::Listener { -public: struct Listener { virtual ~Listener() {} @@ -98,6 +97,10 @@ public: } private: + //============================================================================== + Listener& listener; + bool canAddColumn, canRemoveColumn; + //============================================================================== int getNumRows() override { return 1; } void paintCell (Graphics&, int, int, int, int, bool) override {} @@ -164,9 +167,7 @@ private: listener.columnSelected (text.getIntValue()); } - //============================================================================== - Listener& listener; - bool canAddColumn, canRemoveColumn; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NumberedBoxes) }; //============================================================================== @@ -179,12 +180,8 @@ public: InputOutputConfig (FilterIOConfigurationWindow& parent, bool direction) : owner (parent), ioTitle ("ioLabel", direction ? "Input Configuration" : "Output Configuration"), - nameLabel ("nameLabel", "Bus Name:"), - layoutLabel ("layoutLabel", "Channel Layout:"), - enabledToggle ("Enabled"), ioBuses (*this, false, false), - isInput (direction), - currentBus (0) + isInput (direction) { ioTitle.setFont (ioTitle.getFont().withStyle (Font::bold)); nameLabel.setFont (nameLabel.getFont().withStyle (Font::bold)); @@ -223,7 +220,6 @@ public: { auto label = r.removeFromTop (24); - nameLabel.setBounds (label.removeFromLeft (100)); enabledToggle.setBounds (label.removeFromRight (80)); name.setBounds (label); @@ -231,7 +227,6 @@ public: { auto label = r.removeFromTop (24); - layoutLabel.setBounds (label.removeFromLeft (100)); layouts.setBounds (label); } @@ -304,9 +299,9 @@ private: { if (combo == &layouts) { - if (auto* audioProcessor = owner.getAudioProcessor()) + if (auto* p = owner.getAudioProcessor()) { - if (auto* bus = audioProcessor->getBus (isInput, currentBus)) + if (auto* bus = p->getBus (isInput, currentBus)) { auto selectedNumChannels = layouts.getSelectedId(); @@ -332,24 +327,20 @@ private: { if (btn == &enabledToggle && enabledToggle.isEnabled()) { - if (auto* audioProcessor = owner.getAudioProcessor()) + if (auto* p = owner.getAudioProcessor()) { - if (auto* bus = audioProcessor->getBus (isInput, currentBus)) + if (auto* bus = p->getBus (isInput, currentBus)) { if (bus->isEnabled() != enabledToggle.getToggleState()) { - bool success; - - if (enabledToggle.getToggleState()) - success = bus->enable(); - else - success = bus->setCurrentLayout (AudioChannelSet::disabled()); + bool success = enabledToggle.getToggleState() ? bus->enable() + : bus->setCurrentLayout (AudioChannelSet::disabled()); if (success) { updateBusLayout(); - if (InputOutputConfig* config = owner.getConfig (! isInput)) + if (auto* config = owner.getConfig (! isInput)) config->updateBusLayout(); owner.update(); @@ -368,11 +359,11 @@ private: //============================================================================== void addColumn() override { - if (auto* audioProcessor = owner.getAudioProcessor()) + if (auto* p = owner.getAudioProcessor()) { - if (audioProcessor->canAddBus (isInput)) + if (p->canAddBus (isInput)) { - if (audioProcessor->addBus (isInput)) + if (p->addBus (isInput)) { updateBusButtons(); updateBusLayout(); @@ -382,22 +373,22 @@ private: config->updateBusButtons(); config->updateBusLayout(); } - - owner.update(); } + + owner.update(); } } } void removeColumn() override { - if (auto* audioProcessor = owner.getAudioProcessor()) + if (auto* p = owner.getAudioProcessor()) { - if (audioProcessor->getBusCount (isInput) > 1 && audioProcessor->canRemoveBus (isInput)) + if (p->getBusCount (isInput) > 1 && p->canRemoveBus (isInput)) { - if (audioProcessor->removeBus (isInput)) + if (p->removeBus (isInput)) { - currentBus = jmin (audioProcessor->getBusCount (isInput) - 1, currentBus); + currentBus = jmin (p->getBusCount (isInput) - 1, currentBus); updateBusButtons(); updateBusLayout(); @@ -428,40 +419,41 @@ private: //============================================================================== FilterIOConfigurationWindow& owner; - Label ioTitle, nameLabel, name, layoutLabel; - ToggleButton enabledToggle; + Label ioTitle, name; + Label nameLabel { "nameLabel", "Bus Name:" }; + Label layoutLabel { "layoutLabel", "Channel Layout:" }; + ToggleButton enabledToggle { "Enabled" }; ComboBox layouts; NumberedBoxes ioBuses; bool isInput; - int currentBus; + int currentBus = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputOutputConfig) }; -FilterIOConfigurationWindow::FilterIOConfigurationWindow (AudioProcessor* const p) - : AudioProcessorEditor (p), - title ("title", p->getName()) +FilterIOConfigurationWindow::FilterIOConfigurationWindow (AudioProcessor& p) + : AudioProcessorEditor (&p), + title ("title", p.getName()) { - jassert (p != nullptr); setOpaque (true); title.setFont (title.getFont().withStyle (Font::bold)); addAndMakeVisible (title); { - ScopedLock renderLock (p->getCallbackLock()); - p->suspendProcessing (true); - p->releaseResources(); + ScopedLock renderLock (p.getCallbackLock()); + p.suspendProcessing (true); + p.releaseResources(); } - if (p->getBusCount (true) > 0 || p->canAddBus (true)) + if (p.getBusCount (true) > 0 || p.canAddBus (true)) addAndMakeVisible (inConfig = new InputOutputConfig (*this, true)); - if (p->getBusCount (false) > 0 || p->canAddBus (false)) + if (p.getBusCount (false) > 0 || p.canAddBus (false)) addAndMakeVisible (outConfig = new InputOutputConfig (*this, false)); - currentLayout = p->getBusesLayout(); + currentLayout = p.getBusesLayout(); setSize (400, (inConfig != nullptr && outConfig != nullptr ? 160 : 0) + 200); } @@ -506,38 +498,33 @@ void FilterIOConfigurationWindow::resized() void FilterIOConfigurationWindow::update() { - auto nodeId = getNodeId(); + auto nodeID = getNodeID(); if (auto* graph = getGraph()) - if (nodeId != -1) - graph->disconnectNode (static_cast (nodeId)); + if (nodeID != 0) + graph->disconnectNode (nodeID); if (auto* graphEditor = getGraphEditor()) - if (auto* panel = graphEditor->graphPanel) + if (auto* panel = graphEditor->graphPanel.get()) panel->updateComponents(); } -int32 FilterIOConfigurationWindow::getNodeId() const +AudioProcessorGraph::NodeID FilterIOConfigurationWindow::getNodeID() const { if (auto* graph = getGraph()) - { - const int n = graph->getNumNodes(); - - for (int i = 0; i < n; ++i) - if (auto* node = graph->getNode (i)) - if (node->getProcessor() == getAudioProcessor()) - return static_cast (node->nodeId); - } + for (auto* node : graph->getNodes()) + if (node->getProcessor() == getAudioProcessor()) + return node->nodeID; - return -1; + return 0; } MainHostWindow* FilterIOConfigurationWindow::getMainWindow() const { - Component* comp; + auto& desktop = Desktop::getInstance(); - for (int idx = 0; (comp = Desktop::getInstance().getComponent(idx)) != nullptr; ++idx) - if (auto* mainWindow = dynamic_cast (comp)) + for (int i = desktop.getNumComponents(); --i >= 0;) + if (auto* mainWindow = dynamic_cast (desktop.getComponent(i))) return mainWindow; return nullptr; @@ -546,8 +533,7 @@ MainHostWindow* FilterIOConfigurationWindow::getMainWindow() const GraphDocumentComponent* FilterIOConfigurationWindow::getGraphEditor() const { if (auto* mainWindow = getMainWindow()) - if (auto* graphEditor = mainWindow->getGraphEditor()) - return graphEditor; + return mainWindow->graphHolder.get(); return nullptr; } @@ -555,8 +541,8 @@ GraphDocumentComponent* FilterIOConfigurationWindow::getGraphEditor() const AudioProcessorGraph* FilterIOConfigurationWindow::getGraph() const { if (auto* graphEditor = getGraphEditor()) - if (auto* graph = graphEditor->graph.get()) - return &graph->getGraph(); + if (auto* panel = graphEditor->graph.get()) + return &panel->graph; return nullptr; } diff --git a/examples/audio plugin host/Source/FilterIOConfiguration.h b/examples/audio plugin host/Source/FilterIOConfiguration.h index 64c94a4099..4666ddc73f 100644 --- a/examples/audio plugin host/Source/FilterIOConfiguration.h +++ b/examples/audio plugin host/Source/FilterIOConfiguration.h @@ -26,38 +26,35 @@ #pragma once -#include "FilterGraph.h" +class MainHostWindow; +class GraphDocumentComponent; -class FilterIOConfigurationWindow : public AudioProcessorEditor + +//============================================================================== +class FilterIOConfigurationWindow : public AudioProcessorEditor { public: - class InputOutputConfig; - - //============================================================================== - FilterIOConfigurationWindow (AudioProcessor* const p); + FilterIOConfigurationWindow (AudioProcessor&); ~FilterIOConfigurationWindow(); //============================================================================== void paint (Graphics& g) override; void resized() override; - //============================================================================== - InputOutputConfig* getConfig (bool isInput) noexcept { return isInput ? inConfig : outConfig; } - void update(); private: - - //============================================================================== - MainHostWindow* getMainWindow() const; - GraphDocumentComponent* getGraphEditor() const; - AudioProcessorGraph* getGraph() const; - int32 getNodeId() const; - - //============================================================================== - friend class InputOutputConfig; + class InputOutputConfig; AudioProcessor::BusesLayout currentLayout; Label title; ScopedPointer inConfig, outConfig; + InputOutputConfig* getConfig (bool isInput) noexcept { return isInput ? inConfig : outConfig; } + void update(); + + MainHostWindow* getMainWindow() const; + GraphDocumentComponent* getGraphEditor() const; + AudioProcessorGraph* getGraph() const; + AudioProcessorGraph::NodeID getNodeID() const; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilterIOConfigurationWindow) }; diff --git a/examples/audio plugin host/Source/GraphEditorPanel.cpp b/examples/audio plugin host/Source/GraphEditorPanel.cpp index 9d92fd3c7c..7ed59e5526 100644 --- a/examples/audio plugin host/Source/GraphEditorPanel.cpp +++ b/examples/audio plugin host/Source/GraphEditorPanel.cpp @@ -28,203 +28,20 @@ #include "GraphEditorPanel.h" #include "InternalFilters.h" #include "MainHostWindow.h" -#include "FilterIOConfiguration.h" //============================================================================== -static Array activePluginWindows; - -PluginWindow::PluginWindow (AudioProcessorEditor* pluginEditor, AudioProcessorGraph::Node* o, WindowFormatType t) - : DocumentWindow (pluginEditor->getName(), - LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), - DocumentWindow::minimiseButton | DocumentWindow::closeButton), - owner (o), - type (t) +struct GraphEditorPanel::PinComponent : public Component, + public SettableTooltipClient { - setSize (400, 300); - - setContentOwned (pluginEditor, true); - - setTopLeftPosition (owner->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)), - owner->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500))); - - owner->properties.set (getOpenProp (type), true); - - setVisible (true); - - activePluginWindows.add (this); -} - -void PluginWindow::closeCurrentlyOpenWindowsFor (const uint32 nodeId) -{ - for (int i = activePluginWindows.size(); --i >= 0;) - if (activePluginWindows.getUnchecked(i)->owner->nodeId == nodeId) - delete activePluginWindows.getUnchecked (i); -} - -void PluginWindow::closeAllCurrentlyOpenWindows() -{ - if (activePluginWindows.size() > 0) - { - for (int i = activePluginWindows.size(); --i >= 0;) - delete activePluginWindows.getUnchecked (i); - - Component dummyModalComp; - dummyModalComp.enterModalState (false); - MessageManager::getInstance()->runDispatchLoopUntil (50); - } -} - -//============================================================================== -struct ProcessorProgramPropertyComp : public PropertyComponent, - private AudioProcessorListener -{ - ProcessorProgramPropertyComp (const String& name, AudioProcessor& p) - : PropertyComponent (name), owner (p) - { - owner.addListener (this); - } - - ~ProcessorProgramPropertyComp() - { - owner.removeListener (this); - } - - void refresh() override {} - void audioProcessorChanged (AudioProcessor*) override {} - void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} - - AudioProcessor& owner; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorProgramPropertyComp) -}; - -struct ProgramAudioProcessorEditor : public AudioProcessorEditor -{ - ProgramAudioProcessorEditor (AudioProcessor* p) : AudioProcessorEditor (p) - { - jassert (p != nullptr); - setOpaque (true); - - addAndMakeVisible (panel); - - Array programs; - - auto numPrograms = p->getNumPrograms(); - int totalHeight = 0; - - for (int i = 0; i < numPrograms; ++i) - { - auto name = p->getProgramName (i).trim(); - - if (name.isEmpty()) - name = "Unnamed"; - - auto pc = new ProcessorProgramPropertyComp (name, *p); - programs.add (pc); - totalHeight += pc->getPreferredHeight(); - } - - panel.addProperties (programs); - - setSize (400, jlimit (25, 400, totalHeight)); - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::grey); - } - - void resized() override - { - panel.setBounds (getLocalBounds()); - } - - PropertyPanel panel; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor) -}; - -//============================================================================== -PluginWindow* PluginWindow::getWindowFor (AudioProcessorGraph::Node* node, WindowFormatType type) -{ - jassert (node != nullptr); - - for (auto* w : activePluginWindows) - if (w->owner == node && w->type == type) - return w; - - auto* processor = node->getProcessor(); - AudioProcessorEditor* ui = nullptr; - - if (auto* pluginInstance = dynamic_cast (processor)) - { - auto description = pluginInstance->getPluginDescription(); - - if (description.pluginFormatName == "Internal") - { - getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false); - - return nullptr; - } - } - - if (type == Normal) - { - ui = processor->createEditorIfNeeded(); - - if (ui == nullptr) - type = Generic; - } - - if (ui == nullptr) - { - if (type == Generic || type == Parameters) ui = new GenericAudioProcessorEditor (processor); - else if (type == Programs) ui = new ProgramAudioProcessorEditor (processor); - else if (type == AudioIO) ui = new FilterIOConfigurationWindow (processor); - } - - if (ui != nullptr) - { - if (auto* plugin = dynamic_cast (processor)) - ui->setName (plugin->getName()); - - return new PluginWindow (ui, node, type); - } - - return nullptr; -} - -PluginWindow::~PluginWindow() -{ - activePluginWindows.removeFirstMatchingValue (this); - clearContentComponent(); -} - -void PluginWindow::moved() -{ - owner->properties.set (getLastXProp (type), getX()); - owner->properties.set (getLastYProp (type), getY()); -} - -void PluginWindow::closeButtonPressed() -{ - owner->properties.set (getOpenProp (type), false); - delete this; -} - -//============================================================================== -struct PinComponent : public Component, - public SettableTooltipClient -{ - PinComponent (FilterGraph& g, uint32 id, int i, bool isIn) - : graph (g), pluginID (id), index (i), isInput (isIn) + PinComponent (GraphEditorPanel& p, AudioProcessorGraph::NodeAndChannel pinToUse, bool isIn) + : panel (p), graph (p.graph), pin (pinToUse), isInput (isIn) { - if (auto node = graph.getNodeForId (pluginID)) + if (auto node = graph.graph.getNodeForId (pin.nodeID)) { String tip; - if (index == FilterGraph::midiChannelNumber) + if (pin.isMIDI()) { tip = isInput ? "MIDI Input" : "MIDI Output"; @@ -232,13 +49,13 @@ struct PinComponent : public Component, else { auto& processor = *node->getProcessor(); - auto channel = processor.getOffsetInBusBufferForAbsoluteChannelIndex (isInput, index, busIdx); + auto channel = processor.getOffsetInBusBufferForAbsoluteChannelIndex (isInput, pin.channelIndex, busIdx); if (auto* bus = processor.getBus (isInput, busIdx)) tip = bus->getName() + ": " + AudioChannelSet::getAbbreviatedChannelTypeName (bus->getCurrentLayout().getTypeOfChannel (channel)); else tip = (isInput ? "Main Input: " - : "Main Output: ") + String (index + 1); + : "Main Output: ") + String (pin.channelIndex + 1); } @@ -250,44 +67,41 @@ struct PinComponent : public Component, void paint (Graphics& g) override { - const float w = (float) getWidth(); - const float h = (float) getHeight(); + auto w = (float) getWidth(); + auto h = (float) getHeight(); Path p; p.addEllipse (w * 0.25f, h * 0.25f, w * 0.5f, h * 0.5f); p.addRectangle (w * 0.4f, isInput ? (0.5f * h) : 0.0f, w * 0.2f, h * 0.5f); - auto colour = (index == FilterGraph::midiChannelNumber ? Colours::red : Colours::green); + auto colour = (pin.isMIDI() ? Colours::red : Colours::green); - g.setColour (colour.withRotatedHue (static_cast (busIdx) / 5.0f)); + g.setColour (colour.withRotatedHue (busIdx / 5.0f)); g.fillPath (p); } void mouseDown (const MouseEvent& e) override { - getGraphPanel()->beginConnectorDrag (isInput ? 0 : pluginID, index, - isInput ? pluginID : 0, index, - e); + AudioProcessorGraph::NodeAndChannel dummy { 0, 0 }; + + panel.beginConnectorDrag (isInput ? dummy : pin, + isInput ? pin : dummy, + e); } void mouseDrag (const MouseEvent& e) override { - getGraphPanel()->dragConnector (e); + panel.dragConnector (e); } void mouseUp (const MouseEvent& e) override { - getGraphPanel()->endDraggingConnector (e); - } - - GraphEditorPanel* getGraphPanel() const noexcept - { - return findParentComponentOfClass(); + panel.endDraggingConnector (e); } + GraphEditorPanel& panel; FilterGraph& graph; - const uint32 pluginID; - const int index; + AudioProcessorGraph::NodeAndChannel pin; const bool isInput; int busIdx = 0; @@ -295,9 +109,9 @@ struct PinComponent : public Component, }; //============================================================================== -struct FilterComponent : public Component +struct GraphEditorPanel::FilterComponent : public Component { - FilterComponent (FilterGraph& g, uint32 id) : graph (g), pluginID (id) + FilterComponent (GraphEditorPanel& p, uint32 id) : panel (p), graph (p.graph), pluginID (id) { shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, { 0, 1 })); setComponentEffect (&shadow); @@ -305,11 +119,6 @@ struct FilterComponent : public Component setSize (150, 60); } - ~FilterComponent() - { - deleteAllChildren(); - } - FilterComponent (const FilterComponent&) = delete; FilterComponent& operator= (const FilterComponent&) = delete; @@ -320,62 +129,7 @@ struct FilterComponent : public Component toFront (true); if (e.mods.isPopupMenu()) - { - PopupMenu m; - m.addItem (1, "Delete this filter"); - m.addItem (2, "Disconnect all pins"); - m.addSeparator(); - m.addItem (3, "Show plugin UI"); - m.addItem (4, "Show all programs"); - m.addItem (5, "Show all parameters"); - m.addSeparator(); - m.addItem (6, "Configure Audio I/O"); - m.addItem (7, "Test state save/load"); - - auto r = m.show(); - - if (r == 1) - { - graph.removeFilter (pluginID); - return; - } - else if (r == 2) - { - graph.disconnectFilter (pluginID); - } - else - { - if (auto f = graph.getNodeForId (pluginID)) - { - auto* processor = f->getProcessor(); - jassert (processor != nullptr); - - if (r == 7) - { - MemoryBlock state; - processor->getStateInformation (state); - processor->setStateInformation (state.getData(), (int) state.getSize()); - } - else - { - PluginWindow::WindowFormatType type = processor->hasEditor() ? PluginWindow::Normal - : PluginWindow::Generic; - - switch (r) - { - case 4: type = PluginWindow::Programs; break; - case 5: type = PluginWindow::Parameters; break; - case 6: type = PluginWindow::AudioIO; break; - - default: break; - }; - - if (auto* w = PluginWindow::getWindowFor (f, type)) - w->toFront (true); - } - } - } - } + showPopupMenu(); } void mouseDrag (const MouseEvent& e) override @@ -387,11 +141,13 @@ struct FilterComponent : public Component if (getParentComponent() != nullptr) pos = getParentComponent()->getLocalPoint (nullptr, pos); + pos += getLocalBounds().getCentre(); + graph.setNodePosition (pluginID, - (pos.getX() + getWidth() / 2) / (double) getParentWidth(), - (pos.getY() + getHeight() / 2) / (double) getParentHeight()); + { pos.x / (double) getParentWidth(), + pos.y / (double) getParentHeight() }); - getGraphPanel()->updateComponents(); + panel.updateComponents(); } } @@ -403,8 +159,8 @@ struct FilterComponent : public Component } else if (e.getNumberOfClicks() == 2) { - if (auto f = graph.getNodeForId (pluginID)) - if (auto* w = PluginWindow::getWindowFor (f, PluginWindow::Normal)) + if (auto f = graph.graph.getNodeForId (pluginID)) + if (auto* w = graph.getOrCreateWindowFor (f, PluginWindow::Type::normal)) w->toFront (true); } } @@ -420,44 +176,38 @@ struct FilterComponent : public Component void paint (Graphics& g) override { - g.setColour (findColour (TextEditor::backgroundColourId)); + auto boxArea = getLocalBounds().reduced (4, pinSize); - const int x = 4; - const int y = pinSize; - const int w = getWidth() - x * 2; - const int h = getHeight() - pinSize * 2; - - g.fillRect (x, y, w, h); + g.setColour (findColour (TextEditor::backgroundColourId)); + g.fillRect (boxArea.toFloat()); g.setColour (findColour (TextEditor::textColourId)); g.setFont (font); - g.drawFittedText (getName(), getLocalBounds().reduced (4, 2), Justification::centred, 2); + g.drawFittedText (getName(), boxArea, Justification::centred, 2); } void resized() override { - if (auto f = graph.getNodeForId (pluginID)) + if (auto f = graph.graph.getNodeForId (pluginID)) { if (auto* processor = f->getProcessor()) { - for (auto* child : getChildren()) + for (auto* pin : pins) { - if (auto* pin = dynamic_cast (child)) - { - const bool isInput = pin->isInput; - int busIdx = 0; - processor->getOffsetInBusBufferForAbsoluteChannelIndex (isInput, pin->index, busIdx); - - const int total = isInput ? numIns : numOuts; - const int index = pin->index == FilterGraph::midiChannelNumber ? (total - 1) : pin->index; - - auto totalSpaces = static_cast (total) + (static_cast (jmax (0, processor->getBusCount (isInput) - 1)) * 0.5f); - auto indexPos = static_cast (index) + (static_cast (busIdx) * 0.5f); - - pin->setBounds (proportionOfWidth ((1.0f + indexPos) / (totalSpaces + 1.0f)) - pinSize / 2, - pin->isInput ? 0 : (getHeight() - pinSize), - pinSize, pinSize); - } + const bool isInput = pin->isInput; + auto channelIndex = pin->pin.channelIndex; + int busIdx = 0; + processor->getOffsetInBusBufferForAbsoluteChannelIndex (isInput, channelIndex, busIdx); + + const int total = isInput ? numIns : numOuts; + const int index = pin->pin.isMIDI() ? (total - 1) : channelIndex; + + auto totalSpaces = static_cast (total) + (static_cast (jmax (0, processor->getBusCount (isInput) - 1)) * 0.5f); + auto indexPos = static_cast (index) + (static_cast (busIdx) * 0.5f); + + pin->setBounds (proportionOfWidth ((1.0f + indexPos) / (totalSpaces + 1.0f)) - pinSize / 2, + pin->isInput ? 0 : (getHeight() - pinSize), + pinSize, pinSize); } } } @@ -465,23 +215,17 @@ struct FilterComponent : public Component Point getPinPos (int index, bool isInput) const { - for (auto* child : getChildren()) - if (auto* pin = dynamic_cast (child)) - if (pin->index == index && isInput == pin->isInput) - return getPosition().toFloat() + pin->getBounds().getCentre().toFloat(); + for (auto* pin : pins) + if (pin->pin.channelIndex == index && isInput == pin->isInput) + return getPosition().toFloat() + pin->getBounds().getCentre().toFloat(); return {}; } void update() { - const AudioProcessorGraph::Node::Ptr f (graph.getNodeForId (pluginID)); - - if (f == nullptr) - { - delete this; - return; - } + const AudioProcessorGraph::Node::Ptr f (graph.graph.getNodeForId (pluginID)); + jassert (f != nullptr); numIns = f->getProcessor()->getTotalNumInputChannels(); if (f->getProcessor()->acceptsMidi()) @@ -506,7 +250,7 @@ struct FilterComponent : public Component setName (f->getProcessor()->getName()); { - Point p = graph.getNodePosition (pluginID); + auto p = graph.getNodePosition (pluginID); setCentreRelative ((float) p.x, (float) p.y); } @@ -515,32 +259,79 @@ struct FilterComponent : public Component numInputs = numIns; numOutputs = numOuts; - deleteAllChildren(); + pins.clear(); - int i; - for (i = 0; i < f->getProcessor()->getTotalNumInputChannels(); ++i) - addAndMakeVisible (new PinComponent (graph, pluginID, i, true)); + for (int i = 0; i < f->getProcessor()->getTotalNumInputChannels(); ++i) + addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, i }, true))); if (f->getProcessor()->acceptsMidi()) - addAndMakeVisible (new PinComponent (graph, pluginID, FilterGraph::midiChannelNumber, true)); + addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, AudioProcessorGraph::midiChannelIndex }, true))); - for (i = 0; i < f->getProcessor()->getTotalNumOutputChannels(); ++i) - addAndMakeVisible (new PinComponent (graph, pluginID, i, false)); + for (int i = 0; i < f->getProcessor()->getTotalNumOutputChannels(); ++i) + addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, i }, false))); if (f->getProcessor()->producesMidi()) - addAndMakeVisible (new PinComponent (graph, pluginID, FilterGraph::midiChannelNumber, false)); + addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, AudioProcessorGraph::midiChannelIndex }, false))); resized(); } } - GraphEditorPanel* getGraphPanel() const noexcept + AudioProcessor* getProcessor() const + { + if (auto node = graph.graph.getNodeForId (pluginID)) + return node->getProcessor(); + + return {}; + } + + void showPopupMenu() + { + PopupMenu m; + m.addItem (1, "Delete this filter"); + m.addItem (2, "Disconnect all pins"); + m.addSeparator(); + m.addItem (10, "Show plugin GUI"); + m.addItem (11, "Show all programs"); + m.addItem (12, "Show all parameters"); + m.addSeparator(); + m.addItem (20, "Configure Audio I/O"); + m.addItem (21, "Test state save/load"); + + switch (m.show()) + { + case 1: graph.graph.removeNode (pluginID); break; + case 2: graph.graph.disconnectNode (pluginID); break; + case 10: showWindow (PluginWindow::Type::normal); break; + case 11: showWindow (PluginWindow::Type::programs); break; + case 12: showWindow (PluginWindow::Type::generic); break; + case 20: showWindow (PluginWindow::Type::audioIO); break; + case 21: testStateSaveLoad(); break; + default: break; + } + } + + void testStateSaveLoad() { - return findParentComponentOfClass(); + if (auto* processor = getProcessor()) + { + MemoryBlock state; + processor->getStateInformation (state); + processor->setStateInformation (state.getData(), (int) state.getSize()); + } } + void showWindow (PluginWindow::Type type) + { + if (auto node = graph.graph.getNodeForId (pluginID)) + if (auto* w = graph.getOrCreateWindowFor (node, type)) + w->toFront (true); + } + + GraphEditorPanel& panel; FilterGraph& graph; - const uint32 pluginID; + const AudioProcessorGraph::NodeID pluginID; + OwnedArray pins; int numInputs = 0, numOutputs = 0; int pinSize = 16; Point originalPos; @@ -551,30 +342,28 @@ struct FilterComponent : public Component //============================================================================== -struct ConnectorComponent : public Component, - public SettableTooltipClient +struct GraphEditorPanel::ConnectorComponent : public Component, + public SettableTooltipClient { - ConnectorComponent (FilterGraph& g) : graph (g) + ConnectorComponent (GraphEditorPanel& p) : panel (p), graph (p.graph) { setAlwaysOnTop (true); } - void setInput (uint32 newSourceID, int newSourceChannel) + void setInput (AudioProcessorGraph::NodeAndChannel newSource) { - if (sourceFilterID != newSourceID || sourceFilterChannel != newSourceChannel) + if (connection.source != newSource) { - sourceFilterID = newSourceID; - sourceFilterChannel = newSourceChannel; + connection.source = newSource; update(); } } - void setOutput (uint32 newDestID, int newDestChannel) + void setOutput (AudioProcessorGraph::NodeAndChannel newDest) { - if (destFilterID != newDestID || destFilterChannel != newDestChannel) + if (connection.destination != newDest) { - destFilterID = newDestID; - destFilterChannel = newDestChannel; + connection.destination = newDest; update(); } } @@ -620,27 +409,19 @@ struct ConnectorComponent : public Component, p1 = lastInputPos; p2 = lastOutputPos; - if (auto* hostPanel = getGraphPanel()) - { - if (auto* src = hostPanel->getComponentForFilter (sourceFilterID)) - p1 = src->getPinPos (sourceFilterChannel, false); + if (auto* src = panel.getComponentForFilter (connection.source.nodeID)) + p1 = src->getPinPos (connection.source.channelIndex, false); - if (auto* dest = hostPanel->getComponentForFilter (destFilterID)) - p2 = dest->getPinPos (destFilterChannel, true); - } + if (auto* dest = panel.getComponentForFilter (connection.destination.nodeID)) + p2 = dest->getPinPos (connection.destination.channelIndex, true); } void paint (Graphics& g) override { - if (sourceFilterChannel == FilterGraph::midiChannelNumber - || destFilterChannel == FilterGraph::midiChannelNumber) - { + if (connection.source.isMIDI() || connection.destination.isMIDI()) g.setColour (Colours::red); - } else - { g.setColour (Colours::green); - } g.fillPath (linePath); } @@ -670,30 +451,30 @@ struct ConnectorComponent : public Component, { if (dragging) { - getGraphPanel()->dragConnector (e); + panel.dragConnector (e); } else if (e.mouseWasDraggedSinceMouseDown()) { dragging = true; - graph.removeConnection (sourceFilterID, sourceFilterChannel, destFilterID, destFilterChannel); + graph.graph.removeConnection (connection); double distanceFromStart, distanceFromEnd; - getDistancesFromEnds (e.position, distanceFromStart, distanceFromEnd); + getDistancesFromEnds (getPosition().toFloat() + e.position, distanceFromStart, distanceFromEnd); const bool isNearerSource = (distanceFromStart < distanceFromEnd); - getGraphPanel()->beginConnectorDrag (isNearerSource ? 0 : sourceFilterID, - sourceFilterChannel, - isNearerSource ? destFilterID : 0, - destFilterChannel, - e); + AudioProcessorGraph::NodeAndChannel dummy { 0, 0 }; + + panel.beginConnectorDrag (isNearerSource ? dummy : connection.source, + isNearerSource ? connection.destination : dummy, + e); } } void mouseUp (const MouseEvent& e) override { if (dragging) - getGraphPanel()->endDraggingConnector (e); + panel.endDraggingConnector (e); } void resized() override @@ -735,11 +516,6 @@ struct ConnectorComponent : public Component, linePath.setUsingNonZeroWinding (true); } - GraphEditorPanel* getGraphPanel() const noexcept - { - return findParentComponentOfClass(); - } - void getDistancesFromEnds (Point p, double& distanceFromStart, double& distanceFromEnd) const { Point p1, p2; @@ -749,9 +525,9 @@ struct ConnectorComponent : public Component, distanceFromEnd = p2.getDistanceFrom (p); } + GraphEditorPanel& panel; FilterGraph& graph; - uint32 sourceFilterID = 0, destFilterID = 0; - int sourceFilterChannel = 0, destFilterChannel = 0; + AudioProcessorGraph::Connection connection { { 0, 0 }, { 0, 0 } }; Point lastInputPos, lastOutputPos; Path linePath, hitPath; bool dragging = false; @@ -771,7 +547,8 @@ GraphEditorPanel::~GraphEditorPanel() { graph.removeChangeListener (this); draggingConnector = nullptr; - deleteAllChildren(); + nodes.clear(); + connectors.clear(); } void GraphEditorPanel::paint (Graphics& g) @@ -799,40 +576,32 @@ void GraphEditorPanel::mouseDown (const MouseEvent& e) void GraphEditorPanel::createNewPlugin (const PluginDescription& desc, Point position) { - graph.addFilter (desc, position.toDouble() / Point ((double) getWidth(), (double) getHeight())); + graph.addPlugin (desc, position.toDouble() / Point ((double) getWidth(), (double) getHeight())); } -FilterComponent* GraphEditorPanel::getComponentForFilter (const uint32 filterID) const +GraphEditorPanel::FilterComponent* GraphEditorPanel::getComponentForFilter (const uint32 filterID) const { - for (auto* child : getChildren()) - if (auto* fc = dynamic_cast (child)) - if (fc->pluginID == filterID) - return fc; + for (auto* fc : nodes) + if (fc->pluginID == filterID) + return fc; return nullptr; } -ConnectorComponent* GraphEditorPanel::getComponentForConnection (const AudioProcessorGraph::Connection& conn) const +GraphEditorPanel::ConnectorComponent* GraphEditorPanel::getComponentForConnection (const AudioProcessorGraph::Connection& conn) const { - for (auto* child : getChildren()) - { - if (auto* c = dynamic_cast (child)) - if (c->sourceFilterID == conn.sourceNodeId - && c->destFilterID == conn.destNodeId - && c->sourceFilterChannel == conn.sourceChannelIndex - && c->destFilterChannel == conn.destChannelIndex) - return c; - } + for (auto* cc : connectors) + if (cc->connection == conn) + return cc; return nullptr; } -PinComponent* GraphEditorPanel::findPinAt (Point pos) const +GraphEditorPanel::PinComponent* GraphEditorPanel::findPinAt (Point pos) const { - for (auto* child : getChildren()) - if (auto* fc = dynamic_cast (child)) - if (auto* pin = dynamic_cast (fc->getComponentAt (pos.toInt() - fc->getPosition()))) - return pin; + for (auto* fc : nodes) + if (auto* pin = dynamic_cast (fc->getComponentAt (pos.toInt() - fc->getPosition()))) + return pin; return nullptr; } @@ -849,67 +618,56 @@ void GraphEditorPanel::changeListenerCallback (ChangeBroadcaster*) void GraphEditorPanel::updateComponents() { - auto children = getChildren(); - for (auto child : children) - if (auto* fc = dynamic_cast (static_cast (child))) - fc->update(); + for (int i = nodes.size(); --i >= 0;) + if (graph.graph.getNodeForId (nodes.getUnchecked(i)->pluginID) == nullptr) + nodes.remove (i); - for (int i = getNumChildComponents(); --i >= 0;) - { - auto* cc = dynamic_cast (getChildComponent (i)); + for (int i = connectors.size(); --i >= 0;) + if (! graph.graph.isConnected (connectors.getUnchecked(i)->connection)) + connectors.remove (i); - if (cc != nullptr && cc != draggingConnector) - { - if (graph.getConnectionBetween (cc->sourceFilterID, cc->sourceFilterChannel, - cc->destFilterID, cc->destFilterChannel) == nullptr) - { - delete cc; - } - else - { - cc->update(); - } - } - } + for (auto* fc : nodes) + fc->update(); - for (int i = graph.getNumFilters(); --i >= 0;) - { - auto f = graph.getNode (i); + for (auto* cc : connectors) + cc->update(); - if (getComponentForFilter (f->nodeId) == 0) + for (auto* f : graph.graph.getNodes()) + { + if (getComponentForFilter (f->nodeID) == 0) { - auto* comp = new FilterComponent (graph, f->nodeId); + auto* comp = nodes.add (new FilterComponent (*this, f->nodeID)); addAndMakeVisible (comp); comp->update(); } } - for (int i = graph.getNumConnections(); --i >= 0;) + for (auto& c : graph.graph.getConnections()) { - auto* c = graph.getConnection (i); - - if (getComponentForConnection (*c) == 0) + if (getComponentForConnection (c) == 0) { - auto* comp = new ConnectorComponent (graph); + auto* comp = connectors.add (new ConnectorComponent (*this)); addAndMakeVisible (comp); - comp->setInput (c->sourceNodeId, c->sourceChannelIndex); - comp->setOutput (c->destNodeId, c->destChannelIndex); + comp->setInput (c.source); + comp->setOutput (c.destination); } } } -void GraphEditorPanel::beginConnectorDrag (const uint32 sourceFilterID, const int sourceFilterChannel, - const uint32 destFilterID, const int destFilterChannel, +void GraphEditorPanel::beginConnectorDrag (AudioProcessorGraph::NodeAndChannel source, + AudioProcessorGraph::NodeAndChannel dest, const MouseEvent& e) { - draggingConnector = dynamic_cast (e.originalComponent); + auto* c = dynamic_cast (e.originalComponent); + connectors.removeObject (c, false); + draggingConnector = c; if (draggingConnector == nullptr) - draggingConnector = new ConnectorComponent (graph); + draggingConnector = new ConnectorComponent (*this); - draggingConnector->setInput (sourceFilterID, sourceFilterChannel); - draggingConnector->setOutput (destFilterID, destFilterChannel); + draggingConnector->setInput (source); + draggingConnector->setOutput (dest); addAndMakeVisible (draggingConnector); draggingConnector->toFront (false); @@ -929,30 +687,25 @@ void GraphEditorPanel::dragConnector (const MouseEvent& e) if (auto* pin = findPinAt (pos)) { - auto srcFilter = draggingConnector->sourceFilterID; - auto srcChannel = draggingConnector->sourceFilterChannel; - auto dstFilter = draggingConnector->destFilterID; - auto dstChannel = draggingConnector->destFilterChannel; + auto connection = draggingConnector->connection; - if (srcFilter == 0 && ! pin->isInput) + if (connection.source.nodeID == 0 && ! pin->isInput) { - srcFilter = pin->pluginID; - srcChannel = pin->index; + connection.source = pin->pin; } - else if (dstFilter == 0 && pin->isInput) + else if (connection.destination.nodeID == 0 && pin->isInput) { - dstFilter = pin->pluginID; - dstChannel = pin->index; + connection.destination = pin->pin; } - if (graph.canConnect (srcFilter, srcChannel, dstFilter, dstChannel)) + if (graph.graph.canConnect (connection)) { pos = (pin->getParentComponent()->getPosition() + pin->getBounds().getCentre()).toFloat(); draggingConnector->setTooltip (pin->getTooltip()); } } - if (draggingConnector->sourceFilterID == 0) + if (draggingConnector->connection.source.nodeID == 0) draggingConnector->dragStart (pos); else draggingConnector->dragEnd (pos); @@ -967,41 +720,34 @@ void GraphEditorPanel::endDraggingConnector (const MouseEvent& e) draggingConnector->setTooltip ({}); auto e2 = e.getEventRelativeTo (this); - - auto srcFilter = draggingConnector->sourceFilterID; - auto srcChannel = draggingConnector->sourceFilterChannel; - auto dstFilter = draggingConnector->destFilterID; - auto dstChannel = draggingConnector->destFilterChannel; + auto connection = draggingConnector->connection; draggingConnector = nullptr; if (auto* pin = findPinAt (e2.position)) { - if (srcFilter == 0) + if (connection.source.nodeID == 0) { if (pin->isInput) return; - srcFilter = pin->pluginID; - srcChannel = pin->index; + connection.source = pin->pin; } else { if (! pin->isInput) return; - dstFilter = pin->pluginID; - dstChannel = pin->index; + connection.destination = pin->pin; } - graph.addConnection (srcFilter, srcChannel, dstFilter, dstChannel); + graph.graph.addConnection (connection); } } - //============================================================================== -struct TooltipBar : public Component, - private Timer +struct GraphDocumentComponent::TooltipBar : public Component, + private Timer { TooltipBar() { @@ -1045,13 +791,11 @@ GraphDocumentComponent::GraphDocumentComponent (AudioPluginFormatManager& fm, Au deviceManager.addChangeListener (graphPanel); - graphPlayer.setProcessor (&graph->getGraph()); + graphPlayer.setProcessor (&graph->graph); keyState.addListener (&graphPlayer.getMidiMessageCollector()); - addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (keyState, - MidiKeyboardComponent::horizontalKeyboard)); - + addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (keyState, MidiKeyboardComponent::horizontalKeyboard)); addAndMakeVisible (statusBar = new TooltipBar()); deviceManager.addAudioCallback (&graphPlayer); @@ -1091,9 +835,15 @@ void GraphDocumentComponent::releaseGraph() { deviceManager.removeAudioCallback (&graphPlayer); deviceManager.removeMidiInputCallback (String(), &graphPlayer.getMidiMessageCollector()); - deviceManager.removeChangeListener (graphPanel); - deleteAllChildren(); + if (graphPanel != nullptr) + { + deviceManager.removeChangeListener (graphPanel); + graphPanel = nullptr; + } + + keyboardComp = nullptr; + statusBar = nullptr; graphPlayer.setProcessor (nullptr); graph = nullptr; @@ -1103,3 +853,8 @@ void GraphDocumentComponent::setDoublePrecision (bool doublePrecision) { graphPlayer.setDoublePrecisionProcessing (doublePrecision); } + +bool GraphDocumentComponent::closeAnyOpenPluginWindows() +{ + return graphPanel->graph.closeAnyOpenPluginWindows(); +} diff --git a/examples/audio plugin host/Source/GraphEditorPanel.h b/examples/audio plugin host/Source/GraphEditorPanel.h index 147820cb06..246b875208 100644 --- a/examples/audio plugin host/Source/GraphEditorPanel.h +++ b/examples/audio plugin host/Source/GraphEditorPanel.h @@ -28,10 +28,6 @@ #include "FilterGraph.h" -struct FilterComponent; -struct ConnectorComponent; -struct PinComponent; - //============================================================================== /** @@ -44,31 +40,37 @@ public: GraphEditorPanel (FilterGraph& graph); ~GraphEditorPanel(); - void paint (Graphics& g); - void mouseDown (const MouseEvent& e); - void createNewPlugin (const PluginDescription&, Point position); - FilterComponent* getComponentForFilter (uint32 filterID) const; - ConnectorComponent* getComponentForConnection (const AudioProcessorGraph::Connection& conn) const; - PinComponent* findPinAt (Point) const; - - void resized(); - void changeListenerCallback (ChangeBroadcaster*); + void paint (Graphics&) override; + void mouseDown (const MouseEvent&) override; + void resized() override; + void changeListenerCallback (ChangeBroadcaster*) override; void updateComponents(); //============================================================================== - void beginConnectorDrag (uint32 sourceFilterID, int sourceFilterChannel, - uint32 destFilterID, int destFilterChannel, - const MouseEvent& e); - void dragConnector (const MouseEvent& e); - void endDraggingConnector (const MouseEvent& e); + void beginConnectorDrag (AudioProcessorGraph::NodeAndChannel source, + AudioProcessorGraph::NodeAndChannel dest, + const MouseEvent&); + void dragConnector (const MouseEvent&); + void endDraggingConnector (const MouseEvent&); //============================================================================== -private: FilterGraph& graph; + +private: + struct FilterComponent; + struct ConnectorComponent; + struct PinComponent; + + OwnedArray nodes; + OwnedArray connectors; ScopedPointer draggingConnector; + FilterComponent* getComponentForFilter (AudioProcessorGraph::NodeID) const; + ConnectorComponent* getComponentForConnection (const AudioProcessorGraph::Connection&) const; + PinComponent* findPinAt (Point) const; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphEditorPanel) }; @@ -82,7 +84,6 @@ private: class GraphDocumentComponent : public Component { public: - //============================================================================== GraphDocumentComponent (AudioPluginFormatManager& formatManager, AudioDeviceManager& deviceManager); ~GraphDocumentComponent(); @@ -90,82 +91,26 @@ public: //============================================================================== void createNewPlugin (const PluginDescription&, Point position); void setDoublePrecision (bool doublePrecision); + bool closeAnyOpenPluginWindows(); //============================================================================== ScopedPointer graph; - //============================================================================== void resized(); - - //============================================================================== void unfocusKeyboardComponent(); - - //============================================================================== void releaseGraph(); + ScopedPointer graphPanel; + ScopedPointer keyboardComp; + private: //============================================================================== AudioDeviceManager& deviceManager; AudioProcessorPlayer graphPlayer; MidiKeyboardState keyState; -public: - GraphEditorPanel* graphPanel; - -private: - Component* keyboardComp; - Component* statusBar; + struct TooltipBar; + ScopedPointer statusBar; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphDocumentComponent) }; - -//============================================================================== -/** A desktop window containing a plugin's UI. */ -class PluginWindow : public DocumentWindow -{ -public: - enum WindowFormatType - { - Normal = 0, - Generic, - Programs, - Parameters, - AudioIO, - NumTypes - }; - - PluginWindow (AudioProcessorEditor*, AudioProcessorGraph::Node*, WindowFormatType); - ~PluginWindow(); - - static PluginWindow* getWindowFor (AudioProcessorGraph::Node*, WindowFormatType); - - static void closeCurrentlyOpenWindowsFor (const uint32 nodeId); - static void closeAllCurrentlyOpenWindows(); - - void moved() override; - void closeButtonPressed() override; - -private: - AudioProcessorGraph::Node* owner; - WindowFormatType type; - - float getDesktopScaleFactor() const override { return 1.0f; } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginWindow) -}; - -inline String toString (PluginWindow::WindowFormatType type) -{ - switch (type) - { - case PluginWindow::Normal: return "Normal"; - case PluginWindow::Generic: return "Generic"; - case PluginWindow::Programs: return "Programs"; - case PluginWindow::Parameters: return "Parameters"; - default: return String(); - } -} - -inline String getLastXProp (PluginWindow::WindowFormatType type) { return "uiLastX_" + toString (type); } -inline String getLastYProp (PluginWindow::WindowFormatType type) { return "uiLastY_" + toString (type); } -inline String getOpenProp (PluginWindow::WindowFormatType type) { return "uiopen_" + toString (type); } diff --git a/examples/audio plugin host/Source/HostStartup.cpp b/examples/audio plugin host/Source/HostStartup.cpp index 4168785651..9cfe5b465f 100644 --- a/examples/audio plugin host/Source/HostStartup.cpp +++ b/examples/audio plugin host/Source/HostStartup.cpp @@ -93,8 +93,8 @@ public: } if (fileToOpen.existsAsFile()) - if (GraphDocumentComponent* graph = mainWindow->getGraphEditor()) - if (FilterGraph* ioGraph = graph->graph.get()) + if (auto* graph = mainWindow->graphHolder.get()) + if (auto* ioGraph = graph->graph.get()) ioGraph->loadFrom (fileToOpen, true); } diff --git a/examples/audio plugin host/Source/InternalFilters.cpp b/examples/audio plugin host/Source/InternalFilters.cpp index 4a9e52dbac..497c82827e 100644 --- a/examples/audio plugin host/Source/InternalFilters.cpp +++ b/examples/audio plugin host/Source/InternalFilters.cpp @@ -48,17 +48,22 @@ InternalPluginFormat::InternalPluginFormat() } } +AudioPluginInstance* InternalPluginFormat::createInstance (const String& name) +{ + if (name == audioOutDesc.name) return new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); + if (name == audioInDesc.name) return new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); + if (name == midiInDesc.name) return new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); + + return nullptr; +} + void InternalPluginFormat::createPluginInstance (const PluginDescription& desc, double /*initialSampleRate*/, int /*initialBufferSize*/, void* userData, void (*callback) (void*, AudioPluginInstance*, const String&)) { - AudioPluginInstance* p = nullptr; - - if (desc.name == audioOutDesc.name) p = new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); - if (desc.name == audioInDesc.name) p = new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); - if (desc.name == midiInDesc.name) p = new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); + auto* p = createInstance (desc.name); callback (userData, p, p == nullptr ? NEEDS_TRANS ("Invalid internal filter name") : String()); } diff --git a/examples/audio plugin host/Source/InternalFilters.h b/examples/audio plugin host/Source/InternalFilters.h index e24aafd3e7..cdcde6dc65 100644 --- a/examples/audio plugin host/Source/InternalFilters.h +++ b/examples/audio plugin host/Source/InternalFilters.h @@ -60,6 +60,7 @@ private: //============================================================================== void createPluginInstance (const PluginDescription&, double initialSampleRate, int initialBufferSize, void* userData, void (*callback) (void*, AudioPluginInstance*, const String&)) override; + AudioPluginInstance* createInstance (const String& name); bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; }; diff --git a/examples/audio plugin host/Source/MainHostWindow.cpp b/examples/audio plugin host/Source/MainHostWindow.cpp index 906564544d..7d7c7ba2af 100644 --- a/examples/audio plugin host/Source/MainHostWindow.cpp +++ b/examples/audio plugin host/Source/MainHostWindow.cpp @@ -33,14 +33,14 @@ class MainHostWindow::PluginListWindow : public DocumentWindow { public: - PluginListWindow (MainHostWindow& owner_, AudioPluginFormatManager& pluginFormatManager) + PluginListWindow (MainHostWindow& mw, AudioPluginFormatManager& pluginFormatManager) : DocumentWindow ("Available Plugins", LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), DocumentWindow::minimiseButton | DocumentWindow::closeButton), - owner (owner_) + owner (mw) { - const File deadMansPedalFile (getAppProperties().getUserSettings() - ->getFile().getSiblingFile ("RecentlyCrashedPluginsList")); + auto deadMansPedalFile = getAppProperties().getUserSettings() + ->getFile().getSiblingFile ("RecentlyCrashedPluginsList"); setContentOwned (new PluginListComponent (pluginFormatManager, owner.knownPluginList, @@ -58,11 +58,10 @@ public: ~PluginListWindow() { getAppProperties().getUserSettings()->setValue ("listWindowPos", getWindowStateAsString()); - clearContentComponent(); } - void closeButtonPressed() + void closeButtonPressed() override { owner.pluginListWindow = nullptr; } @@ -91,7 +90,9 @@ MainHostWindow::MainHostWindow() setResizeLimits (500, 400, 10000, 10000); centreWithSize (800, 600); - setContentOwned (new GraphDocumentComponent (formatManager, deviceManager), false); + graphHolder = new GraphDocumentComponent (formatManager, deviceManager); + + setContentNonOwned (graphHolder, false); restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("mainWindowPos")); @@ -110,7 +111,7 @@ MainHostWindow::MainHostWindow() knownPluginList.addChangeListener (this); - if (auto* filterGraph = getGraphEditor()->graph.get()) + if (auto* filterGraph = graphHolder->graph.get()) filterGraph->addChangeListener (this); addKeyListener (getCommandManager().getKeyMappings()); @@ -131,7 +132,7 @@ MainHostWindow::~MainHostWindow() pluginListWindow = nullptr; knownPluginList.removeChangeListener (this); - if (auto* filterGraph = getGraphEditor()->graph.get()) + if (auto* filterGraph = graphHolder->graph.get()) filterGraph->removeChangeListener (this); getAppProperties().getUserSettings()->setValue ("mainWindowPos", getWindowStateAsString()); @@ -142,6 +143,8 @@ MainHostWindow::~MainHostWindow() #else setMenuBar (nullptr); #endif + + graphHolder = nullptr; } void MainHostWindow::closeButtonPressed() @@ -165,18 +168,24 @@ struct AsyncQuitRetrier : private Timer void MainHostWindow::tryToQuitApplication() { - PluginWindow::closeAllCurrentlyOpenWindows(); - - if (ModalComponentManager::getInstance()->cancelAllModalComponents()) + if (graphHolder->closeAnyOpenPluginWindows()) + { + // Really important thing to note here: if the last call just deleted any plugin windows, + // we won't exit immediately - instead we'll use our AsyncQuitRetrier to let the message + // loop run for another brief moment, then try again. This will give any plugins a chance + // to flush any GUI events that may have been in transit before the app forces them to + // be unloaded + new AsyncQuitRetrier(); + } + else if (ModalComponentManager::getInstance()->cancelAllModalComponents()) { new AsyncQuitRetrier(); } - else if (getGraphEditor() == nullptr - || getGraphEditor()->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) + else if (graphHolder == nullptr || graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) { // Some plug-ins do not want [NSApp stop] to be called // before the plug-ins are not deallocated. - getGraphEditor()->releaseGraph(); + graphHolder->releaseGraph(); JUCEApplication::quit(); } @@ -198,11 +207,10 @@ void MainHostWindow::changeListenerCallback (ChangeBroadcaster* changed) getAppProperties().saveIfNeeded(); } } - else if (changed == getGraphEditor()->graph) + else if (graphHolder != nullptr && changed == graphHolder->graph) { - String title = JUCEApplication::getInstance()->getApplicationName(); - - File f = getGraphEditor()->graph->getFile(); + auto title = JUCEApplication::getInstance()->getApplicationName(); + auto f = graphHolder->graph->getFile(); if (f.existsAsFile()) title = f.getFileName() + " - " + title; @@ -286,9 +294,9 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/ { if (menuItemID == 250) { - if (auto* graphEditor = getGraphEditor()) - if (auto* filterGraph = graphEditor->graph.get()) - filterGraph->clear(); + if (graphHolder != nullptr) + if (auto* graph = graphHolder->graph.get()) + graph->clear(); } else if (menuItemID >= 100 && menuItemID < 200) { @@ -296,9 +304,10 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/ recentFiles.restoreFromString (getAppProperties().getUserSettings() ->getValue ("recentFilterGraphFiles")); - if (auto* graphEditor = getGraphEditor()) - if (graphEditor->graph != nullptr && graphEditor->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - graphEditor->graph->loadFrom (recentFiles.getFile (menuItemID - 100), true); + if (graphHolder != nullptr) + if (auto* graph = graphHolder->graph.get()) + if (graph != nullptr && graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) + graph->loadFrom (recentFiles.getFile (menuItemID - 100), true); } else if (menuItemID >= 200 && menuItemID < 210) { @@ -323,26 +332,25 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/ void MainHostWindow::menuBarActivated (bool isActivated) { - if (auto* graphEditor = getGraphEditor()) - if (isActivated) - graphEditor->unfocusKeyboardComponent(); + if (isActivated && graphHolder != nullptr) + graphHolder->unfocusKeyboardComponent(); } void MainHostWindow::createPlugin (const PluginDescription& desc, Point pos) { - if (auto* graphEditor = getGraphEditor()) - graphEditor->createNewPlugin (desc, pos); + if (graphHolder != nullptr) + graphHolder->createNewPlugin (desc, pos); } void MainHostWindow::addPluginsToMenu (PopupMenu& m) const { - if (auto* graphEditor = getGraphEditor()) + if (graphHolder != nullptr) { int i = 0; for (auto* t : internalTypes) m.addItem (++i, t->name + " (" + t->pluginFormatName + ")", - graphEditor->graph->getNodeForName (t->name) == nullptr); + graphHolder->graph->getNodeForName (t->name) == nullptr); } m.addSeparator(); @@ -439,28 +447,26 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma bool MainHostWindow::perform (const InvocationInfo& info) { - auto* graphEditor = getGraphEditor(); - switch (info.commandID) { case CommandIDs::newFile: - if (graphEditor != nullptr && graphEditor->graph != nullptr && graphEditor->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - graphEditor->graph->newDocument(); + if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) + graphHolder->graph->newDocument(); break; case CommandIDs::open: - if (graphEditor != nullptr && graphEditor->graph != nullptr && graphEditor->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - graphEditor->graph->loadFromUserSpecifiedFile (true); + if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) + graphHolder->graph->loadFromUserSpecifiedFile (true); break; case CommandIDs::save: - if (graphEditor != nullptr && graphEditor->graph != nullptr) - graphEditor->graph->save (true, true); + if (graphHolder != nullptr && graphHolder->graph != nullptr) + graphHolder->graph->save (true, true); break; case CommandIDs::saveAs: - if (graphEditor != nullptr && graphEditor->graph != nullptr) - graphEditor->graph->saveAs (File(), true, true, true); + if (graphHolder != nullptr && graphHolder->graph != nullptr) + graphHolder->graph->saveAs (File(), true, true, true); break; case CommandIDs::showPluginListEditor: @@ -486,8 +492,8 @@ bool MainHostWindow::perform (const InvocationInfo& info) menuItemsChanged(); } - if (graphEditor != nullptr) - graphEditor->setDoublePrecision (newIsDoublePrecision); + if (graphHolder != nullptr) + graphHolder->setDoublePrecision (newIsDoublePrecision); } break; @@ -537,9 +543,9 @@ void MainHostWindow::showAudioSettings() getAppProperties().getUserSettings()->setValue ("audioDeviceState", audioState); getAppProperties().getUserSettings()->saveIfNeeded(); - if (auto* graphEditor = getGraphEditor()) - if (graphEditor->graph != nullptr) - graphEditor->graph->removeIllegalConnections(); + if (graphHolder != nullptr) + if (graphHolder->graph != nullptr) + graphHolder->graph->graph.removeIllegalConnections(); } bool MainHostWindow::isInterestedInFileDrag (const StringArray&) @@ -561,11 +567,11 @@ void MainHostWindow::fileDragExit (const StringArray&) void MainHostWindow::filesDropped (const StringArray& files, int x, int y) { - if (auto* graphEditor = getGraphEditor()) + if (graphHolder != nullptr) { - if (files.size() == 1 && File (files[0]).hasFileExtension (filenameSuffix)) + if (files.size() == 1 && File (files[0]).hasFileExtension (FilterGraph::getFilenameSuffix())) { - if (auto* filterGraph = graphEditor->graph.get()) + if (auto* filterGraph = graphHolder->graph.get()) if (filterGraph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) filterGraph->loadFrom (File (files[0]), true); } @@ -574,7 +580,7 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y) OwnedArray typesFound; knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound); - auto pos = graphEditor->getLocalPoint (this, Point (x, y)); + auto pos = graphHolder->getLocalPoint (this, Point (x, y)); for (int i = 0; i < jmin (5, typesFound.size()); ++i) if (auto* desc = typesFound.getUnchecked(i)) @@ -583,11 +589,6 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y) } } -GraphDocumentComponent* MainHostWindow::getGraphEditor() const -{ - return dynamic_cast (getContentComponent()); -} - bool MainHostWindow::isDoublePrecisionProcessing() { if (auto* props = getAppProperties().getUserSettings()) diff --git a/examples/audio plugin host/Source/MainHostWindow.h b/examples/audio plugin host/Source/MainHostWindow.h index fd16dc6ab0..f254d7c464 100644 --- a/examples/audio plugin host/Source/MainHostWindow.h +++ b/examples/audio plugin host/Source/MainHostWindow.h @@ -48,8 +48,6 @@ ApplicationCommandManager& getCommandManager(); ApplicationProperties& getAppProperties(); //============================================================================== -/** -*/ class MainHostWindow : public DocumentWindow, public MenuBarModel, public ApplicationCommandTarget, @@ -62,24 +60,24 @@ public: ~MainHostWindow(); //============================================================================== - void closeButtonPressed(); - void changeListenerCallback (ChangeBroadcaster*); + void closeButtonPressed() override; + void changeListenerCallback (ChangeBroadcaster*) override; - bool isInterestedInFileDrag (const StringArray& files); - void fileDragEnter (const StringArray& files, int, int); - void fileDragMove (const StringArray& files, int, int); - void fileDragExit (const StringArray& files); - void filesDropped (const StringArray& files, int, int); + bool isInterestedInFileDrag (const StringArray& files) override; + void fileDragEnter (const StringArray& files, int, int) override; + void fileDragMove (const StringArray& files, int, int) override; + void fileDragExit (const StringArray& files) override; + void filesDropped (const StringArray& files, int, int) override; - void menuBarActivated (bool isActive); + void menuBarActivated (bool isActive) override; - StringArray getMenuBarNames(); - PopupMenu getMenuForIndex (int topLevelMenuIndex, const String& menuName); - void menuItemSelected (int menuItemID, int topLevelMenuIndex); - ApplicationCommandTarget* getNextCommandTarget(); - void getAllCommands (Array& commands); - void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result); - bool perform (const InvocationInfo& info); + StringArray getMenuBarNames() override; + PopupMenu getMenuForIndex (int topLevelMenuIndex, const String& menuName) override; + void menuItemSelected (int menuItemID, int topLevelMenuIndex) override; + ApplicationCommandTarget* getNextCommandTarget() override; + void getAllCommands (Array&) override; + void getCommandInfo (CommandID, ApplicationCommandInfo&) override; + bool perform (const InvocationInfo&) override; void tryToQuitApplication(); @@ -88,11 +86,11 @@ public: void addPluginsToMenu (PopupMenu&) const; const PluginDescription* getChosenType (int menuID) const; - GraphDocumentComponent* getGraphEditor() const; - bool isDoublePrecisionProcessing(); void updatePrecisionMenuItem (ApplicationCommandInfo& info); + ScopedPointer graphHolder; + private: //============================================================================== AudioDeviceManager deviceManager; diff --git a/examples/audio plugin host/Source/PluginWindow.h b/examples/audio plugin host/Source/PluginWindow.h new file mode 100644 index 0000000000..88b7174e16 --- /dev/null +++ b/examples/audio plugin host/Source/PluginWindow.h @@ -0,0 +1,201 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#include "FilterIOConfiguration.h" +class FilterGraph; + +//============================================================================== +/** + A desktop window containing a plugin's GUI. +*/ +class PluginWindow : public DocumentWindow +{ +public: + enum class Type + { + normal = 0, + generic, + programs, + audioIO, + numTypes + }; + + PluginWindow (AudioProcessorGraph::Node* n, Type t, OwnedArray& windowList) + : DocumentWindow (n->getProcessor()->getName(), + LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), + DocumentWindow::minimiseButton | DocumentWindow::closeButton), + activeWindowList (windowList), + node (n), type (t) + { + setSize (400, 300); + + if (auto* ui = createProcessorEditor (*node->getProcessor(), type)) + setContentOwned (ui, true); + + setTopLeftPosition (node->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)), + node->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500))); + + node->properties.set (getOpenProp (type), true); + + setVisible (true); + } + + ~PluginWindow() + { + clearContentComponent(); + } + + void moved() override + { + node->properties.set (getLastXProp (type), getX()); + node->properties.set (getLastYProp (type), getY()); + } + + void closeButtonPressed() override + { + node->properties.set (getOpenProp (type), false); + activeWindowList.removeObject (this); + } + + static String getLastXProp (Type type) { return "uiLastX_" + getTypeName (type); } + static String getLastYProp (Type type) { return "uiLastY_" + getTypeName (type); } + static String getOpenProp (Type type) { return "uiopen_" + getTypeName (type); } + + OwnedArray& activeWindowList; + const AudioProcessorGraph::Node::Ptr node; + const Type type; + +private: + float getDesktopScaleFactor() const override { return 1.0f; } + + static AudioProcessorEditor* createProcessorEditor (AudioProcessor& processor, PluginWindow::Type type) + { + if (type == PluginWindow::Type::normal) + { + if (auto* ui = processor.createEditorIfNeeded()) + return ui; + + type = PluginWindow::Type::generic; + } + + if (type == PluginWindow::Type::generic) + return new GenericAudioProcessorEditor (&processor); + + if (type == PluginWindow::Type::programs) + return new ProgramAudioProcessorEditor (processor); + + if (type == PluginWindow::Type::audioIO) + return new FilterIOConfigurationWindow (processor); + + jassertfalse; + return {}; + } + + static String getTypeName (Type type) + { + switch (type) + { + case Type::normal: return "Normal"; + case Type::generic: return "Generic"; + case Type::programs: return "Programs"; + case Type::audioIO: return "IO"; + default: return {}; + } + } + + //============================================================================== + struct ProgramAudioProcessorEditor : public AudioProcessorEditor + { + ProgramAudioProcessorEditor (AudioProcessor& p) : AudioProcessorEditor (p) + { + setOpaque (true); + + addAndMakeVisible (panel); + + Array programs; + + auto numPrograms = p.getNumPrograms(); + int totalHeight = 0; + + for (int i = 0; i < numPrograms; ++i) + { + auto name = p.getProgramName (i).trim(); + + if (name.isEmpty()) + name = "Unnamed"; + + auto pc = new PropertyComp (name, p); + programs.add (pc); + totalHeight += pc->getPreferredHeight(); + } + + panel.addProperties (programs); + + setSize (400, jlimit (25, 400, totalHeight)); + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::grey); + } + + void resized() override + { + panel.setBounds (getLocalBounds()); + } + + private: + struct PropertyComp : public PropertyComponent, + private AudioProcessorListener + { + PropertyComp (const String& name, AudioProcessor& p) : PropertyComponent (name), owner (p) + { + owner.addListener (this); + } + + ~PropertyComp() + { + owner.removeListener (this); + } + + void refresh() override {} + void audioProcessorChanged (AudioProcessor*) override {} + void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} + + AudioProcessor& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyComp) + }; + + PropertyPanel panel; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor) + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginWindow) +}; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 2388c9930b..2053ab9d16 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -27,945 +27,765 @@ namespace juce { -const int AudioProcessorGraph::midiChannelIndex = 0x1000; - -//============================================================================== -template struct FloatDoubleUtil {}; -template struct FloatDoubleType {}; - -template -struct FloatAndDoubleComposition +template +struct GraphRenderSequence { - typedef typename FloatDoubleType::Type FloatType; - typedef typename FloatDoubleType::Type DoubleType; + GraphRenderSequence() {} - template - inline typename FloatDoubleType::Type& get() noexcept + struct Context { - return FloatDoubleUtil >::get (*this); - } + FloatType** audioBuffers; + MidiBuffer* midiBuffers; + int numSamples; + }; - FloatType floatVersion; - DoubleType doubleVersion; -}; + void perform (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + auto numSamples = buffer.getNumSamples(); + auto maxSamples = renderingBuffer.getNumSamples(); -template struct FloatDoubleUtil { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } }; -template struct FloatDoubleUtil { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } }; + if (numSamples > maxSamples) + { + // being asked to render more samples than our buffers have, so slice things up... + tempMIDI.clear(); + tempMIDI.addEvents (midiMessages, maxSamples, numSamples, -maxSamples); -struct FloatPlaceholder; + { + AudioBuffer startAudio (buffer.getArrayOfWritePointers(), buffer.getNumChannels(), maxSamples); + midiMessages.clear (maxSamples, numSamples); + perform (startAudio, midiMessages); + } -template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; -template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; -template struct FloatDoubleType, FloatingType> { typedef AudioBuffer Type; }; -template struct FloatDoubleType*, FloatingType> { typedef AudioBuffer* Type; }; + AudioBuffer endAudio (buffer.getArrayOfWritePointers(), buffer.getNumChannels(), maxSamples, numSamples - maxSamples); + perform (endAudio, tempMIDI); + return; + } -//============================================================================== -namespace GraphRenderingOps -{ + currentAudioInputBuffer = &buffer; + currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); + currentAudioOutputBuffer.clear(); + currentMidiInputBuffer = &midiMessages; + currentMidiOutputBuffer.clear(); -struct AudioGraphRenderingOpBase -{ - AudioGraphRenderingOpBase() noexcept {} - virtual ~AudioGraphRenderingOpBase() {} + { + const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), numSamples }; - virtual void perform (AudioBuffer& sharedBufferChans, - const OwnedArray& sharedMidiBuffers, - const int numSamples) = 0; + for (auto* op : renderOps) + op->perform (context); + } - virtual void perform (AudioBuffer& sharedBufferChans, - const OwnedArray& sharedMidiBuffers, - const int numSamples) = 0; + for (int i = 0; i < buffer.getNumChannels(); ++i) + buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); - JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase) -}; + midiMessages.clear(); + midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); + currentAudioInputBuffer = nullptr; + } -// use CRTP -template -struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase -{ - void perform (AudioBuffer& sharedBufferChans, - const OwnedArray& sharedMidiBuffers, - const int numSamples) override + void addClearChannelOp (int index) { - static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + createOp ([=] (const Context& c) { FloatVectorOperations::clear (c.audioBuffers[index], c.numSamples); }); } - void perform (AudioBuffer& sharedBufferChans, - const OwnedArray& sharedMidiBuffers, - const int numSamples) override + void addCopyChannelOp (int srcIndex, int dstIndex) { - static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + createOp ([=] (const Context& c) { FloatVectorOperations::copy (c.audioBuffers[dstIndex], + c.audioBuffers[srcIndex], + c.numSamples); }); } -}; -//============================================================================== -struct ClearChannelOp : public AudioGraphRenderingOp -{ - ClearChannelOp (const int channel) noexcept : channelNum (channel) {} - - template - void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + void addAddChannelOp (int srcIndex, int dstIndex) { - sharedBufferChans.clear (channelNum, 0, numSamples); + createOp ([=] (const Context& c) { FloatVectorOperations::add (c.audioBuffers[dstIndex], + c.audioBuffers[srcIndex], + c.numSamples); }); } - const int channelNum; - - JUCE_DECLARE_NON_COPYABLE (ClearChannelOp) -}; - -//============================================================================== -struct CopyChannelOp : public AudioGraphRenderingOp -{ - CopyChannelOp (const int srcChan, const int dstChan) noexcept - : srcChannelNum (srcChan), dstChannelNum (dstChan) - {} - - template - void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + void addClearMidiBufferOp (int index) { - sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); + createOp ([=] (const Context& c) { c.midiBuffers[index].clear(); }); } - const int srcChannelNum, dstChannelNum; - - JUCE_DECLARE_NON_COPYABLE (CopyChannelOp) -}; + void addCopyMidiBufferOp (int srcIndex, int dstIndex) + { + createOp ([=] (const Context& c) { c.midiBuffers[dstIndex] = c.midiBuffers[srcIndex]; }); + } -//============================================================================== -struct AddChannelOp : public AudioGraphRenderingOp -{ - AddChannelOp (const int srcChan, const int dstChan) noexcept - : srcChannelNum (srcChan), dstChannelNum (dstChan) - {} + void addAddMidiBufferOp (int srcIndex, int dstIndex) + { + createOp ([=] (const Context& c) { c.midiBuffers[dstIndex].addEvents (c.midiBuffers[srcIndex], + 0, c.numSamples, 0); }); + } - template - void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + void addDelayChannelOp (int chan, int delaySize) { - sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); + renderOps.add (new DelayChannelOp (chan, delaySize)); } - const int srcChannelNum, dstChannelNum; + void addProcessOp (const AudioProcessorGraph::Node::Ptr& node, + const Array& audioChannelsUsed, int totalNumChans, int midiBuffer) + { + renderOps.add (new ProcessOp (node, audioChannelsUsed, totalNumChans, midiBuffer)); + } - JUCE_DECLARE_NON_COPYABLE (AddChannelOp) -}; + void prepareBuffers (int blockSize) + { + renderingBuffer.setSize (numBuffersNeeded + 1, blockSize); + renderingBuffer.clear(); + currentAudioOutputBuffer.setSize (numBuffersNeeded + 1, blockSize); + currentAudioOutputBuffer.clear(); -//============================================================================== -struct ClearMidiBufferOp : public AudioGraphRenderingOp -{ - ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} + currentAudioInputBuffer = nullptr; + currentMidiInputBuffer = nullptr; + currentMidiOutputBuffer.clear(); - template - void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) - { - sharedMidiBuffers.getUnchecked (bufferNum)->clear(); - } + midiBuffers.clearQuick(); + midiBuffers.resize (numMidiBuffersNeeded); - const int bufferNum; + const int defaultMIDIBufferSize = 512; - JUCE_DECLARE_NON_COPYABLE (ClearMidiBufferOp) -}; + tempMIDI.ensureSize (defaultMIDIBufferSize); -//============================================================================== -struct CopyMidiBufferOp : public AudioGraphRenderingOp -{ - CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept - : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) - {} + for (auto&& m : midiBuffers) + m.ensureSize (defaultMIDIBufferSize); + } - template - void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) + void releaseBuffers() { - *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); + renderingBuffer.setSize (1, 1); + currentAudioOutputBuffer.setSize (1, 1); + currentAudioInputBuffer = nullptr; + currentMidiInputBuffer = nullptr; + currentMidiOutputBuffer.clear(); + midiBuffers.clear(); } - const int srcBufferNum, dstBufferNum; + int numBuffersNeeded = 0, numMidiBuffersNeeded = 0; - JUCE_DECLARE_NON_COPYABLE (CopyMidiBufferOp) -}; + AudioBuffer renderingBuffer, currentAudioOutputBuffer; + AudioBuffer* currentAudioInputBuffer = nullptr; -//============================================================================== -struct AddMidiBufferOp : public AudioGraphRenderingOp -{ - AddMidiBufferOp (const int srcBuffer, const int dstBuffer) - : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) - {} + MidiBuffer* currentMidiInputBuffer = nullptr; + MidiBuffer currentMidiOutputBuffer; - template - void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) + Array midiBuffers; + MidiBuffer tempMIDI; + +private: + //============================================================================== + struct RenderingOp { - sharedMidiBuffers.getUnchecked (dstBufferNum) - ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); - } + RenderingOp() noexcept {} + virtual ~RenderingOp() {} + virtual void perform (const Context&) = 0; - const int srcBufferNum, dstBufferNum; + JUCE_LEAK_DETECTOR (RenderingOp) + }; - JUCE_DECLARE_NON_COPYABLE (AddMidiBufferOp) -}; + OwnedArray renderOps; -//============================================================================== -struct DelayChannelOp : public AudioGraphRenderingOp -{ - DelayChannelOp (const int chan, const int delaySize) - : channel (chan), - bufferSize (delaySize + 1), - readIndex (0), writeIndex (delaySize) + //============================================================================== + template + void createOp (LambdaType&& fn) { - buffer.floatVersion. calloc ((size_t) bufferSize); - buffer.doubleVersion.calloc ((size_t) bufferSize); + struct LambdaOp : public RenderingOp + { + LambdaOp (LambdaType&& f) : function (static_cast (f)) {} + void perform (const Context& c) override { function (c); } + + LambdaType function; + }; + + renderOps.add (new LambdaOp (static_cast (fn))); } - template - void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + //============================================================================== + struct DelayChannelOp : public RenderingOp { - FloatType* data = sharedBufferChans.getWritePointer (channel, 0); - HeapBlock& block = buffer.get(); - - for (int i = numSamples; --i >= 0;) + DelayChannelOp (int chan, int delaySize) + : channel (chan), + bufferSize (delaySize + 1), + writeIndex (delaySize) { - block [writeIndex] = *data; - *data++ = block [readIndex]; - - if (++readIndex >= bufferSize) readIndex = 0; - if (++writeIndex >= bufferSize) writeIndex = 0; + buffer.calloc ((size_t) bufferSize); } - } -private: - FloatAndDoubleComposition > buffer; - const int channel, bufferSize; - int readIndex, writeIndex; - - JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) -}; - -//============================================================================== -struct ProcessBufferOp : public AudioGraphRenderingOp -{ - ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, - const Array& audioChannelsUsed, - const int totalNumChans, - const int midiBuffer) - : node (n), - processor (n->getProcessor()), - audioChannelsToUse (audioChannelsUsed), - totalChans (jmax (1, totalNumChans)), - midiBufferToUse (midiBuffer) - { - audioChannels.floatVersion. calloc ((size_t) totalChans); - audioChannels.doubleVersion.calloc ((size_t) totalChans); + void perform (const Context& c) + { + auto* data = c.audioBuffers[channel]; - while (audioChannelsToUse.size() < totalChans) - audioChannelsToUse.add (0); - } + for (int i = c.numSamples; --i >= 0;) + { + buffer[writeIndex] = *data; + *data++ = buffer[readIndex]; - template - void perform (AudioBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) - { - HeapBlock& channels = audioChannels.get(); + if (++readIndex >= bufferSize) readIndex = 0; + if (++writeIndex >= bufferSize) writeIndex = 0; + } + } - for (int i = totalChans; --i >= 0;) - channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); + HeapBlock buffer; + const int channel, bufferSize; + int readIndex = 0, writeIndex; - AudioBuffer buffer (channels, totalChans, numSamples); + JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) + }; - if (processor->isSuspended()) + //============================================================================== + struct ProcessOp : public RenderingOp + { + ProcessOp (const AudioProcessorGraph::Node::Ptr& n, + const Array& audioChannelsUsed, + int totalNumChans, int midiBuffer) + : node (n), + processor (*n->getProcessor()), + audioChannelsToUse (audioChannelsUsed), + totalChans (jmax (1, totalNumChans)), + midiBufferToUse (midiBuffer) { - buffer.clear(); + audioChannels.calloc ((size_t) totalChans); + + while (audioChannelsToUse.size() < totalChans) + audioChannelsToUse.add (0); } - else + + void perform (const Context& c) { - ScopedLock lock (processor->getCallbackLock()); + for (int i = 0; i < totalChans; ++i) + audioChannels[i] = c.audioBuffers[audioChannelsToUse.getUnchecked (i)]; - callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); - } - } + AudioBuffer buffer (audioChannels, totalChans, c.numSamples); - void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) - { - processor->processBlock (buffer, midiMessages); - } + if (processor.isSuspended()) + buffer.clear(); + else + callProcess (buffer, c.midiBuffers[midiBufferToUse]); + } - void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) - { - if (processor->isUsingDoublePrecision()) + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) { - processor->processBlock (buffer, midiMessages); + if (processor.isUsingDoublePrecision()) + { + tempBufferDouble.makeCopyOf (buffer, true); + processor.processBlock (tempBufferDouble, midiMessages); + buffer.makeCopyOf (tempBufferDouble, true); + } + else + { + processor.processBlock (buffer, midiMessages); + } } - else + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) { - // if the processor is in single precision mode but the graph in double - // precision then we need to convert between buffer formats. Note, that - // this will only happen if the processor does not support double - // precision processing. - tempBuffer.makeCopyOf (buffer, true); - processor->processBlock (tempBuffer, midiMessages); - buffer.makeCopyOf (tempBuffer, true); + if (processor.isUsingDoublePrecision()) + { + processor.processBlock (buffer, midiMessages); + } + else + { + tempBufferFloat.makeCopyOf (buffer, true); + processor.processBlock (tempBufferFloat, midiMessages); + buffer.makeCopyOf (tempBufferFloat, true); + } } - } - const AudioProcessorGraph::Node::Ptr node; - AudioProcessor* const processor; + const AudioProcessorGraph::Node::Ptr node; + AudioProcessor& processor; -private: - Array audioChannelsToUse; - FloatAndDoubleComposition > audioChannels; - AudioBuffer tempBuffer; - const int totalChans; - const int midiBufferToUse; + Array audioChannelsToUse; + HeapBlock audioChannels; + AudioBuffer tempBufferFloat, tempBufferDouble; + const int totalChans, midiBufferToUse; - JUCE_DECLARE_NON_COPYABLE (ProcessBufferOp) + JUCE_DECLARE_NON_COPYABLE (ProcessOp) + }; }; //============================================================================== -/** Used to calculate the correct sequence of rendering ops needed, based on - the best re-use of shared buffers at each stage. -*/ -struct RenderingOpSequenceCalculator +//============================================================================== +template +struct RenderSequenceBuilder { - RenderingOpSequenceCalculator (AudioProcessorGraph& g, - const Array& nodes, - Array& renderingOps) - : graph (g), - orderedNodes (nodes), - totalLatency (0) + RenderSequenceBuilder (AudioProcessorGraph& g, RenderSequence& s) + : graph (g), sequence (s) { - nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros - channels.add (0); + createOrderedNodeList(); - midiNodeIds.add ((uint32) zeroNodeID); + audioBuffers.add (AssignedBuffer::createReadOnlyEmpty()); // first buffer is read-only zeros + midiBuffers .add (AssignedBuffer::createReadOnlyEmpty()); for (int i = 0; i < orderedNodes.size(); ++i) { - createRenderingOpsForNode (*orderedNodes.getUnchecked(i), renderingOps, i); - markAnyUnusedBuffersAsFree (i); + createRenderingOpsForNode (*orderedNodes.getUnchecked(i), i); + markAnyUnusedBuffersAsFree (audioBuffers, i); + markAnyUnusedBuffersAsFree (midiBuffers, i); } graph.setLatencySamples (totalLatency); - } - int getNumBuffersNeeded() const noexcept { return nodeIds.size(); } - int getNumMidiBuffersNeeded() const noexcept { return midiNodeIds.size(); } + s.numBuffersNeeded = audioBuffers.size(); + s.numMidiBuffersNeeded = midiBuffers.size(); + } -private: //============================================================================== + typedef AudioProcessorGraph::NodeID NodeID; + AudioProcessorGraph& graph; - const Array& orderedNodes; - Array channels; - Array nodeIds, midiNodeIds; + RenderSequence& sequence; - enum { freeNodeID = 0xffffffff, zeroNodeID = 0xfffffffe, anonymousNodeID = 0xfffffffd }; + Array orderedNodes; - static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID && nodeID != zeroNodeID; } + struct AssignedBuffer + { + AudioProcessorGraph::NodeAndChannel channel; - Array nodeDelayIDs; - Array nodeDelays; - int totalLatency; + static AssignedBuffer createReadOnlyEmpty() noexcept { return { { (NodeID) zeroNodeID, 0 } }; } + static AssignedBuffer createFree() noexcept { return { { (NodeID) freeNodeID, 0 } }; } - int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } + bool isReadOnlyEmpty() const noexcept { return channel.nodeID == (NodeID) zeroNodeID; } + bool isFree() const noexcept { return channel.nodeID == (NodeID) freeNodeID; } + bool isAssigned() const noexcept { return ! (isReadOnlyEmpty() || isFree()); } - void setNodeDelay (const uint32 nodeID, const int latency) - { - const int index = nodeDelayIDs.indexOf (nodeID); + void setFree() noexcept { channel = { (NodeID) freeNodeID, 0 }; } + void setAssignedToNonExistentNode() noexcept { channel = { (NodeID) anonNodeID, 0 }; } - if (index >= 0) - { - nodeDelays.set (index, latency); - } - else + private: + enum { - nodeDelayIDs.add (nodeID); - nodeDelays.add (latency); - } + anonNodeID = 0x7ffffffd, + zeroNodeID = 0x7ffffffe, + freeNodeID = 0x7fffffff + }; + }; + + Array audioBuffers, midiBuffers; + + enum { readOnlyEmptyBufferIndex = 0 }; + + struct Delay + { + NodeID nodeID; + int delay; + }; + + HashMap delays; + int totalLatency = 0; + + int getNodeDelay (NodeID nodeID) const noexcept + { + return delays[nodeID]; } - int getInputLatencyForNode (const uint32 nodeID) const + int getInputLatencyForNode (NodeID nodeID) const { int maxLatency = 0; - for (int i = graph.getNumConnections(); --i >= 0;) - { - const AudioProcessorGraph::Connection* const c = graph.getConnection (i); - - if (c->destNodeId == nodeID) - maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); - } + for (auto&& c : graph.getConnections()) + if (c.destination.nodeID == nodeID) + maxLatency = jmax (maxLatency, getNodeDelay (c.source.nodeID)); return maxLatency; } //============================================================================== - void createRenderingOpsForNode (AudioProcessorGraph::Node& node, - Array& renderingOps, - const int ourRenderingIndex) + void createOrderedNodeList() { - AudioProcessor& processor = *node.getProcessor(); - const int numIns = processor.getTotalNumInputChannels(); - const int numOuts = processor.getTotalNumOutputChannels(); - const int totalChans = jmax (numIns, numOuts); + for (auto* node : graph.getNodes()) + { + int j = 0; - Array audioChannelsToUse; - int midiBufferToUse = -1; + for (; j < orderedNodes.size(); ++j) + if (graph.isAnInputTo (*node, *orderedNodes.getUnchecked(j))) + break; - int maxLatency = getInputLatencyForNode (node.nodeId); + orderedNodes.insert (j, node); + } + } - for (int inputChan = 0; inputChan < numIns; ++inputChan) + int findBufferForInputAudioChannel (AudioProcessorGraph::Node& node, const int inputChan, + const int ourRenderingIndex, const int maxLatency) + { + auto& processor = *node.getProcessor(); + auto numOuts = processor.getTotalNumOutputChannels(); + + auto sources = getSourcesForChannel (node, inputChan); + + // Handle an unconnected input channel... + if (sources.isEmpty()) { - // get a list of all the inputs to this node - Array sourceNodes; - Array sourceOutputChans; + if (inputChan >= numOuts) + return readOnlyEmptyBufferIndex; - for (int i = graph.getNumConnections(); --i >= 0;) - { - const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + auto index = getFreeBuffer (audioBuffers); + sequence.addClearChannelOp (index); + return index; + } - if (c->destNodeId == node.nodeId && c->destChannelIndex == inputChan) - { - sourceNodes.add (c->sourceNodeId); - sourceOutputChans.add (c->sourceChannelIndex); - } - } + // Handle an input from a single source.. + if (sources.size() == 1) + { + // channel with a straightforward single input.. + auto src = sources.getUnchecked(0); - int bufIndex = -1; + int bufIndex = getBufferContaining (src); - if (sourceNodes.size() == 0) + if (bufIndex < 0) { - // unconnected input channel - - if (inputChan >= numOuts) - { - bufIndex = getReadOnlyEmptyBuffer(); - jassert (bufIndex >= 0); - } - else - { - bufIndex = getFreeBuffer (false); - renderingOps.add (new ClearChannelOp (bufIndex)); - } + // if not found, this is probably a feedback loop + bufIndex = readOnlyEmptyBufferIndex; + jassert (bufIndex >= 0); } - else if (sourceNodes.size() == 1) + + if (inputChan < numOuts + && isBufferNeededLater (ourRenderingIndex, inputChan, src)) { - // channel with a straightforward single input.. - const uint32 srcNode = sourceNodes.getUnchecked(0); - const int srcChan = sourceOutputChans.getUnchecked(0); + // can't mess up this channel because it's needed later by another node, + // so we need to use a copy of it.. + auto newFreeBuffer = getFreeBuffer (audioBuffers); + sequence.addCopyChannelOp (bufIndex, newFreeBuffer); + bufIndex = newFreeBuffer; + } - bufIndex = getBufferContaining (srcNode, srcChan); + auto nodeDelay = getNodeDelay (src.nodeID); - if (bufIndex < 0) - { - // if not found, this is probably a feedback loop - bufIndex = getReadOnlyEmptyBuffer(); - jassert (bufIndex >= 0); - } + if (nodeDelay < maxLatency) + sequence.addDelayChannelOp (bufIndex, maxLatency - nodeDelay); - if (inputChan < numOuts - && isBufferNeededLater (ourRenderingIndex, - inputChan, - srcNode, srcChan)) - { - // can't mess up this channel because it's needed later by another node, so we - // need to use a copy of it.. - const int newFreeBuffer = getFreeBuffer (false); + return bufIndex; + } - renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer)); + // Handle a mix of several outputs coming into this input.. + int reusableInputIndex = -1; + int bufIndex = -1; - bufIndex = newFreeBuffer; - } + for (int i = 0; i < sources.size(); ++i) + { + auto src = sources.getReference(i); + auto sourceBufIndex = getBufferContaining (src); + + if (sourceBufIndex >= 0 && ! isBufferNeededLater (ourRenderingIndex, inputChan, src)) + { + // we've found one of our input chans that can be re-used.. + reusableInputIndex = i; + bufIndex = sourceBufIndex; - const int nodeDelay = getNodeDelay (srcNode); + auto nodeDelay = getNodeDelay (src.nodeID); if (nodeDelay < maxLatency) - renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); + sequence.addDelayChannelOp (bufIndex, maxLatency - nodeDelay); + + break; } - else - { - // channel with a mix of several inputs.. + } - // try to find a re-usable channel from our inputs.. - int reusableInputIndex = -1; + if (reusableInputIndex < 0) + { + // can't re-use any of our input chans, so get a new one and copy everything into it.. + bufIndex = getFreeBuffer (audioBuffers); + jassert (bufIndex != 0); - for (int i = 0; i < sourceNodes.size(); ++i) - { - const int sourceBufIndex = getBufferContaining (sourceNodes.getUnchecked(i), - sourceOutputChans.getUnchecked(i)); - - if (sourceBufIndex >= 0 - && ! isBufferNeededLater (ourRenderingIndex, - inputChan, - sourceNodes.getUnchecked(i), - sourceOutputChans.getUnchecked(i))) - { - // we've found one of our input chans that can be re-used.. - reusableInputIndex = i; - bufIndex = sourceBufIndex; + audioBuffers.getReference (bufIndex).setAssignedToNonExistentNode(); - const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); - if (nodeDelay < maxLatency) - renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay)); + auto srcIndex = getBufferContaining (sources.getFirst()); - break; - } - } + if (srcIndex < 0) + sequence.addClearChannelOp (bufIndex); // if not found, this is probably a feedback loop + else + sequence.addCopyChannelOp (srcIndex, bufIndex); - if (reusableInputIndex < 0) - { - // can't re-use any of our input chans, so get a new one and copy everything into it.. - bufIndex = getFreeBuffer (false); - jassert (bufIndex != 0); + reusableInputIndex = 0; + auto nodeDelay = getNodeDelay (sources.getFirst().nodeID); - markBufferAsContaining (bufIndex, static_cast (anonymousNodeID), 0); + if (nodeDelay < maxLatency) + sequence.addDelayChannelOp (bufIndex, maxLatency - nodeDelay); + } - const int srcIndex = getBufferContaining (sourceNodes.getUnchecked (0), - sourceOutputChans.getUnchecked (0)); - if (srcIndex < 0) - { - // if not found, this is probably a feedback loop - renderingOps.add (new ClearChannelOp (bufIndex)); - } - else - { - renderingOps.add (new CopyChannelOp (srcIndex, bufIndex)); - } + for (int i = 0; i < sources.size(); ++i) + { + if (i != reusableInputIndex) + { + auto src = sources.getReference(i); + int srcIndex = getBufferContaining (src); - reusableInputIndex = 0; - const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); + if (srcIndex >= 0) + { + auto nodeDelay = getNodeDelay (src.nodeID); if (nodeDelay < maxLatency) - renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); - } - - for (int j = 0; j < sourceNodes.size(); ++j) - { - if (j != reusableInputIndex) { - int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), - sourceOutputChans.getUnchecked(j)); - if (srcIndex >= 0) + if (! isBufferNeededLater (ourRenderingIndex, inputChan, src)) + { + sequence.addDelayChannelOp (srcIndex, maxLatency - nodeDelay); + } + else // buffer is reused elsewhere, can't be delayed { - const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); - - if (nodeDelay < maxLatency) - { - if (! isBufferNeededLater (ourRenderingIndex, inputChan, - sourceNodes.getUnchecked(j), - sourceOutputChans.getUnchecked(j))) - { - renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay)); - } - else // buffer is reused elsewhere, can't be delayed - { - const int bufferToDelay = getFreeBuffer (false); - renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay)); - renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay)); - srcIndex = bufferToDelay; - } - } - - renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + auto bufferToDelay = getFreeBuffer (audioBuffers); + sequence.addCopyChannelOp (srcIndex, bufferToDelay); + sequence.addDelayChannelOp (bufferToDelay, maxLatency - nodeDelay); + srcIndex = bufferToDelay; } } + + sequence.addAddChannelOp (srcIndex, bufIndex); } } - - jassert (bufIndex >= 0); - audioChannelsToUse.add (bufIndex); - - if (inputChan < numOuts) - markBufferAsContaining (bufIndex, node.nodeId, inputChan); } - for (int outputChan = numIns; outputChan < numOuts; ++outputChan) - { - const int bufIndex = getFreeBuffer (false); - jassert (bufIndex != 0); - audioChannelsToUse.add (bufIndex); - - markBufferAsContaining (bufIndex, node.nodeId, outputChan); - } - - // Now the same thing for midi.. - Array midiSourceNodes; - - for (int i = graph.getNumConnections(); --i >= 0;) - { - const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + return bufIndex; + } - if (c->destNodeId == node.nodeId && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex) - midiSourceNodes.add (c->sourceNodeId); - } + int findBufferForInputMidiChannel (AudioProcessorGraph::Node& node, int ourRenderingIndex) + { + auto& processor = *node.getProcessor(); + auto sources = getSourcesForChannel (node, AudioProcessorGraph::midiChannelIndex); - if (midiSourceNodes.size() == 0) + // No midi inputs.. + if (sources.isEmpty()) { - // No midi inputs.. - midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + auto midiBufferToUse = getFreeBuffer (midiBuffers); // need to pick a buffer even if the processor doesn't use midi if (processor.acceptsMidi() || processor.producesMidi()) - renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); + sequence.addClearMidiBufferOp (midiBufferToUse); + + return midiBufferToUse; } - else if (midiSourceNodes.size() == 1) + + // One midi input.. + if (sources.size() == 1) { - // One midi input.. - midiBufferToUse = getBufferContaining (midiSourceNodes.getUnchecked(0), - AudioProcessorGraph::midiChannelIndex); + auto src = sources.getReference (0); + auto midiBufferToUse = getBufferContaining (src); if (midiBufferToUse >= 0) { - if (isBufferNeededLater (ourRenderingIndex, - AudioProcessorGraph::midiChannelIndex, - midiSourceNodes.getUnchecked(0), - AudioProcessorGraph::midiChannelIndex)) + if (isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, src)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. - const int newFreeBuffer = getFreeBuffer (true); - renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer)); + auto newFreeBuffer = getFreeBuffer (midiBuffers); + sequence.addCopyMidiBufferOp (midiBufferToUse, newFreeBuffer); midiBufferToUse = newFreeBuffer; } } else { // probably a feedback loop, so just use an empty one.. - midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + midiBufferToUse = getFreeBuffer (midiBuffers); // need to pick a buffer even if the processor doesn't use midi } - } - else - { - // More than one midi input being mixed.. - int reusableInputIndex = -1; - for (int i = 0; i < midiSourceNodes.size(); ++i) - { - const int sourceBufIndex = getBufferContaining (midiSourceNodes.getUnchecked(i), - AudioProcessorGraph::midiChannelIndex); - - if (sourceBufIndex >= 0 - && ! isBufferNeededLater (ourRenderingIndex, - AudioProcessorGraph::midiChannelIndex, - midiSourceNodes.getUnchecked(i), - AudioProcessorGraph::midiChannelIndex)) - { - // we've found one of our input buffers that can be re-used.. - reusableInputIndex = i; - midiBufferToUse = sourceBufIndex; - break; - } - } - - if (reusableInputIndex < 0) - { - // can't re-use any of our input buffers, so get a new one and copy everything into it.. - midiBufferToUse = getFreeBuffer (true); - jassert (midiBufferToUse >= 0); + return midiBufferToUse; + } - const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(0), - AudioProcessorGraph::midiChannelIndex); - if (srcIndex >= 0) - renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse)); - else - renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); + // Multiple midi inputs.. + int midiBufferToUse = -1; + int reusableInputIndex = -1; - reusableInputIndex = 0; - } + for (int i = 0; i < sources.size(); ++i) + { + auto src = sources.getReference (i); + auto sourceBufIndex = getBufferContaining (src); - for (int j = 0; j < midiSourceNodes.size(); ++j) + if (sourceBufIndex >= 0 + && ! isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, src)) { - if (j != reusableInputIndex) - { - const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(j), - AudioProcessorGraph::midiChannelIndex); - if (srcIndex >= 0) - renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse)); - } + // we've found one of our input buffers that can be re-used.. + reusableInputIndex = i; + midiBufferToUse = sourceBufIndex; + break; } } - if (processor.producesMidi()) - markBufferAsContaining (midiBufferToUse, node.nodeId, - AudioProcessorGraph::midiChannelIndex); - - setNodeDelay (node.nodeId, maxLatency + processor.getLatencySamples()); - - if (numOuts == 0) - totalLatency = maxLatency; - - renderingOps.add (new ProcessBufferOp (&node, audioChannelsToUse, - totalChans, midiBufferToUse)); - } - - //============================================================================== - int getFreeBuffer (const bool forMidi) - { - if (forMidi) + if (reusableInputIndex < 0) { - for (int i = 1; i < midiNodeIds.size(); ++i) - if (midiNodeIds.getUnchecked(i) == freeNodeID) - return i; + // can't re-use any of our input buffers, so get a new one and copy everything into it.. + midiBufferToUse = getFreeBuffer (midiBuffers); + jassert (midiBufferToUse >= 0); - midiNodeIds.add ((uint32) freeNodeID); - return midiNodeIds.size() - 1; - } - else - { - for (int i = 1; i < nodeIds.size(); ++i) - if (nodeIds.getUnchecked(i) == freeNodeID) - return i; - - nodeIds.add ((uint32) freeNodeID); - channels.add (0); - return nodeIds.size() - 1; - } - } + auto srcIndex = getBufferContaining (sources.getUnchecked(0)); - int getReadOnlyEmptyBuffer() const noexcept - { - return 0; - } + if (srcIndex >= 0) + sequence.addCopyMidiBufferOp (srcIndex, midiBufferToUse); + else + sequence.addClearMidiBufferOp (midiBufferToUse); - int getBufferContaining (const uint32 nodeId, const int outputChannel) const noexcept - { - if (outputChannel == AudioProcessorGraph::midiChannelIndex) - { - for (int i = midiNodeIds.size(); --i >= 0;) - if (midiNodeIds.getUnchecked(i) == nodeId) - return i; - } - else - { - for (int i = nodeIds.size(); --i >= 0;) - if (nodeIds.getUnchecked(i) == nodeId - && channels.getUnchecked(i) == outputChannel) - return i; + reusableInputIndex = 0; } - return -1; - } - - void markAnyUnusedBuffersAsFree (const int stepIndex) - { - for (int i = 0; i < nodeIds.size(); ++i) + for (int i = 0; i < sources.size(); ++i) { - if (isNodeBusy (nodeIds.getUnchecked(i)) - && ! isBufferNeededLater (stepIndex, -1, - nodeIds.getUnchecked(i), - channels.getUnchecked(i))) + if (i != reusableInputIndex) { - nodeIds.set (i, (uint32) freeNodeID); - } - } + auto srcIndex = getBufferContaining (sources.getUnchecked(i)); - for (int i = 0; i < midiNodeIds.size(); ++i) - { - if (isNodeBusy (midiNodeIds.getUnchecked(i)) - && ! isBufferNeededLater (stepIndex, -1, - midiNodeIds.getUnchecked(i), - AudioProcessorGraph::midiChannelIndex)) - { - midiNodeIds.set (i, (uint32) freeNodeID); + if (srcIndex >= 0) + sequence.addAddMidiBufferOp (srcIndex, midiBufferToUse); } } + + return midiBufferToUse; } - bool isBufferNeededLater (int stepIndexToSearchFrom, - int inputChannelOfIndexToIgnore, - const uint32 nodeId, - const int outputChanIndex) const + void createRenderingOpsForNode (AudioProcessorGraph::Node& node, const int ourRenderingIndex) { - while (stepIndexToSearchFrom < orderedNodes.size()) - { - const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom); + auto& processor = *node.getProcessor(); + auto numIns = processor.getTotalNumInputChannels(); + auto numOuts = processor.getTotalNumOutputChannels(); + auto totalChans = jmax (numIns, numOuts); - if (outputChanIndex == AudioProcessorGraph::midiChannelIndex) - { - if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex - && graph.getConnectionBetween (nodeId, AudioProcessorGraph::midiChannelIndex, - node->nodeId, AudioProcessorGraph::midiChannelIndex) != nullptr) - return true; - } - else - { - for (int i = 0; i < node->getProcessor()->getTotalNumInputChannels(); ++i) - if (i != inputChannelOfIndexToIgnore - && graph.getConnectionBetween (nodeId, outputChanIndex, - node->nodeId, i) != nullptr) - return true; - } - - inputChannelOfIndexToIgnore = -1; - ++stepIndexToSearchFrom; - } - - return false; - } + Array audioChannelsToUse; + auto maxLatency = getInputLatencyForNode (node.nodeID); - void markBufferAsContaining (int bufferNum, uint32 nodeId, int outputIndex) - { - if (outputIndex == AudioProcessorGraph::midiChannelIndex) + for (int inputChan = 0; inputChan < numIns; ++inputChan) { - jassert (bufferNum > 0 && bufferNum < midiNodeIds.size()); + // get a list of all the inputs to this node + auto index = findBufferForInputAudioChannel (node, inputChan, ourRenderingIndex, maxLatency); + jassert (index >= 0); - midiNodeIds.set (bufferNum, nodeId); + audioChannelsToUse.add (index); + + if (inputChan < numOuts) + audioBuffers.getReference (index).channel = { node.nodeID, inputChan }; } - else + + for (int outputChan = numIns; outputChan < numOuts; ++outputChan) { - jassert (bufferNum >= 0 && bufferNum < nodeIds.size()); + auto index = getFreeBuffer (audioBuffers); + jassert (index != 0); + audioChannelsToUse.add (index); - nodeIds.set (bufferNum, nodeId); - channels.set (bufferNum, outputIndex); + audioBuffers.getReference (index).channel = { node.nodeID, outputChan }; } - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderingOpSequenceCalculator) -}; -//============================================================================== -// Holds a fast lookup table for checking which nodes are inputs to others. -class ConnectionLookupTable -{ -public: - explicit ConnectionLookupTable (const OwnedArray& connections) - { - for (int i = 0; i < connections.size(); ++i) - { - const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i); + auto midiBufferToUse = findBufferForInputMidiChannel (node, ourRenderingIndex); - int index; - Entry* entry = findEntry (c->destNodeId, index); + if (processor.producesMidi()) + midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, AudioProcessorGraph::midiChannelIndex }; - if (entry == nullptr) - { - entry = new Entry (c->destNodeId); - entries.insert (index, entry); - } + delays.set (node.nodeID, maxLatency + processor.getLatencySamples()); - entry->srcNodes.add (c->sourceNodeId); - } - } + if (numOuts == 0) + totalLatency = maxLatency; - bool isAnInputTo (const uint32 possibleInputId, - const uint32 possibleDestinationId) const noexcept - { - return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size()); + sequence.addProcessOp (&node, audioChannelsToUse, totalChans, midiBufferToUse); } -private: //============================================================================== - struct Entry + Array getSourcesForChannel (AudioProcessorGraph::Node& node, int inputChannelIndex) { - explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {} + Array results; + AudioProcessorGraph::NodeAndChannel nc { node.nodeID, inputChannelIndex }; - const uint32 destNodeId; - SortedSet srcNodes; + for (auto&& c : graph.getConnections()) + if (c.destination == nc) + results.add (c.source); - JUCE_DECLARE_NON_COPYABLE (Entry) - }; + return results; + } + + static int getFreeBuffer (Array& buffers) + { + for (int i = 1; i < buffers.size(); ++i) + if (buffers.getReference(i).isFree()) + return i; - OwnedArray entries; + buffers.add (AssignedBuffer::createFree()); + return buffers.size() - 1; + } - bool isAnInputToRecursive (const uint32 possibleInputId, - const uint32 possibleDestinationId, - int recursionCheck) const noexcept + int getBufferContaining (AudioProcessorGraph::NodeAndChannel output) const noexcept { - int index; + int i = 0; - if (const Entry* const entry = findEntry (possibleDestinationId, index)) + for (auto& b : output.isMIDI() ? midiBuffers : audioBuffers) { - const SortedSet& srcNodes = entry->srcNodes; - - if (srcNodes.contains (possibleInputId)) - return true; + if (b.channel == output) + return i; - if (--recursionCheck >= 0) - { - for (int i = 0; i < srcNodes.size(); ++i) - if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck)) - return true; - } + ++i; } - return false; + return -1; } - Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept + void markAnyUnusedBuffersAsFree (Array& buffers, const int stepIndex) { - Entry* result = nullptr; - - int start = 0; - int end = entries.size(); + for (auto& b : buffers) + if (b.isAssigned() && ! isBufferNeededLater (stepIndex, -1, b.channel)) + b.setFree(); + } - for (;;) + bool isBufferNeededLater (int stepIndexToSearchFrom, + int inputChannelOfIndexToIgnore, + AudioProcessorGraph::NodeAndChannel output) const + { + while (stepIndexToSearchFrom < orderedNodes.size()) { - if (start >= end) - { - break; - } - else if (destNodeId == entries.getUnchecked (start)->destNodeId) + auto* node = orderedNodes.getUnchecked (stepIndexToSearchFrom); + + if (output.isMIDI()) { - result = entries.getUnchecked (start); - break; + if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex + && graph.isConnected ({ { output.nodeID, AudioProcessorGraph::midiChannelIndex }, + { node->nodeID, AudioProcessorGraph::midiChannelIndex } })) + return true; } else { - const int halfway = (start + end) / 2; - - if (halfway == start) - { - if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) - ++start; - - break; - } - else if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) - start = halfway; - else - end = halfway; + for (int i = 0; i < node->getProcessor()->getTotalNumInputChannels(); ++i) + if (i != inputChannelOfIndexToIgnore && graph.isConnected ({ output, { node->nodeID, i } })) + return true; } + + inputChannelOfIndexToIgnore = -1; + ++stepIndexToSearchFrom; } - insertIndex = start; - return result; + return false; } - JUCE_DECLARE_NON_COPYABLE (ConnectionLookupTable) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderSequenceBuilder) }; //============================================================================== -struct ConnectionSorter +AudioProcessorGraph::Connection::Connection (NodeAndChannel src, NodeAndChannel dst) noexcept + : source (src), destination (dst) { - static int compareElements (const AudioProcessorGraph::Connection* const first, - const AudioProcessorGraph::Connection* const second) noexcept - { - if (first->sourceNodeId < second->sourceNodeId) return -1; - if (first->sourceNodeId > second->sourceNodeId) return 1; - if (first->destNodeId < second->destNodeId) return -1; - if (first->destNodeId > second->destNodeId) return 1; - if (first->sourceChannelIndex < second->sourceChannelIndex) return -1; - if (first->sourceChannelIndex > second->sourceChannelIndex) return 1; - if (first->destChannelIndex < second->destChannelIndex) return -1; - if (first->destChannelIndex > second->destChannelIndex) return 1; - - return 0; - } -}; +} + +bool AudioProcessorGraph::Connection::operator== (const Connection& other) const noexcept +{ + return source == other.source && destination == other.destination; +} +bool AudioProcessorGraph::Connection::operator!= (const Connection& c) const noexcept +{ + return ! operator== (c); } -//============================================================================== -AudioProcessorGraph::Connection::Connection (const uint32 sourceID, const int sourceChannel, - const uint32 destID, const int destChannel) noexcept - : sourceNodeId (sourceID), sourceChannelIndex (sourceChannel), - destNodeId (destID), destChannelIndex (destChannel) +bool AudioProcessorGraph::Connection::operator< (const Connection& other) const noexcept { + if (source.nodeID != other.source.nodeID) + return source.nodeID < other.source.nodeID; + + if (destination.nodeID != other.destination.nodeID) + return destination.nodeID < other.destination.nodeID; + + if (source.channelIndex != other.source.channelIndex) + return source.channelIndex < other.source.channelIndex; + + return destination.channelIndex < other.destination.channelIndex; } //============================================================================== -AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) noexcept - : nodeId (nodeID), processor (p), isPrepared (false) +AudioProcessorGraph::Node::Node (NodeID n, AudioProcessor* p) noexcept + : nodeID (n), processor (p) { jassert (processor != nullptr); } -void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, - AudioProcessorGraph* const graph, ProcessingPrecision precision) +void AudioProcessorGraph::Node::prepare (double newSampleRate, int newBlockSize, + AudioProcessorGraph* graph, ProcessingPrecision precision) { if (! isPrepared) { @@ -992,59 +812,23 @@ void AudioProcessorGraph::Node::unprepare() void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph) const { - if (AudioProcessorGraph::AudioGraphIOProcessor* const ioProc - = dynamic_cast (processor.get())) + if (auto* ioProc = dynamic_cast (processor.get())) ioProc->setParentGraph (graph); } -//============================================================================== -struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers +bool AudioProcessorGraph::Node::Connection::operator== (const Connection& other) const noexcept { - AudioProcessorGraphBufferHelpers() - { - currentAudioInputBuffer.floatVersion = nullptr; - currentAudioInputBuffer.doubleVersion = nullptr; - } - - void setRenderingBufferSize (int newNumChannels, int newNumSamples) - { - renderingBuffers.floatVersion. setSize (newNumChannels, newNumSamples); - renderingBuffers.doubleVersion.setSize (newNumChannels, newNumSamples); - - renderingBuffers.floatVersion. clear(); - renderingBuffers.doubleVersion.clear(); - } - - void release() - { - renderingBuffers.floatVersion. setSize (1, 1); - renderingBuffers.doubleVersion.setSize (1, 1); - - currentAudioInputBuffer.floatVersion = nullptr; - currentAudioInputBuffer.doubleVersion = nullptr; - - currentAudioOutputBuffer.floatVersion. setSize (1, 1); - currentAudioOutputBuffer.doubleVersion.setSize (1, 1); - } - - void prepareInOutBuffers(int newNumChannels, int newNumSamples) - { - currentAudioInputBuffer.floatVersion = nullptr; - currentAudioInputBuffer.doubleVersion = nullptr; - - currentAudioOutputBuffer.floatVersion. setSize (newNumChannels, newNumSamples); - currentAudioOutputBuffer.doubleVersion.setSize (newNumChannels, newNumSamples); - } + return otherNode == other.otherNode + && thisChannel == other.thisChannel + && otherChannel == other.otherChannel; +} - FloatAndDoubleComposition > renderingBuffers; - FloatAndDoubleComposition*> currentAudioInputBuffer; - FloatAndDoubleComposition > currentAudioOutputBuffer; -}; +//============================================================================== +struct AudioProcessorGraph::RenderSequenceFloat : public GraphRenderSequence {}; +struct AudioProcessorGraph::RenderSequenceDouble : public GraphRenderSequence {}; //============================================================================== AudioProcessorGraph::AudioProcessorGraph() - : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), - currentMidiInputBuffer (nullptr), isPrepared (false) { } @@ -1060,78 +844,73 @@ const String AudioProcessorGraph::getName() const } //============================================================================== +void AudioProcessorGraph::topologyChanged() +{ + sendChangeMessage(); + + if (isPrepared) + triggerAsyncUpdate(); +} + void AudioProcessorGraph::clear() { + if (nodes.isEmpty()) + return; + nodes.clear(); - connections.clear(); - triggerAsyncUpdate(); + topologyChanged(); } -AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const +AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID nodeID) const { - for (int i = nodes.size(); --i >= 0;) - if (nodes.getUnchecked(i)->nodeId == nodeId) - return nodes.getUnchecked(i); + for (auto* n : nodes) + if (n->nodeID == nodeID) + return n; - return nullptr; + return {}; } -AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) +AudioProcessorGraph::Node::Ptr AudioProcessorGraph::addNode (AudioProcessor* newProcessor, NodeID nodeID) { if (newProcessor == nullptr || newProcessor == this) { jassertfalse; - return nullptr; + return {}; } - for (int i = nodes.size(); --i >= 0;) + if (nodeID == 0) + nodeID = ++lastNodeID; + + for (auto* n : nodes) { - if (nodes.getUnchecked(i)->getProcessor() == newProcessor) + if (n->getProcessor() == newProcessor || n->nodeID == nodeID) { - jassertfalse; // Cannot add the same object to the graph twice! - return nullptr; + jassertfalse; // Cannot add two copies of the same processor, or duplicate node IDs! + return {}; } } - if (nodeId == 0) - { - nodeId = ++lastNodeId; - } - else - { - // you can't add a node with an id that already exists in the graph.. - jassert (getNodeForId (nodeId) == nullptr); - removeNode (nodeId); - - if (nodeId > lastNodeId) - lastNodeId = nodeId; - } + if (nodeID > lastNodeID) + lastNodeID = nodeID; newProcessor->setPlayHead (getPlayHead()); - Node* const n = new Node (nodeId, newProcessor); + Node::Ptr n (new Node (nodeID, newProcessor)); nodes.add (n); - - if (isPrepared) - triggerAsyncUpdate(); - n->setParentGraph (this); + topologyChanged(); return n; } -bool AudioProcessorGraph::removeNode (const uint32 nodeId) +bool AudioProcessorGraph::removeNode (NodeID nodeId) { - disconnectNode (nodeId); - for (int i = nodes.size(); --i >= 0;) { - if (nodes.getUnchecked(i)->nodeId == nodeId) + if (nodes.getUnchecked(i)->nodeID == nodeId) { + disconnectNode (nodeId); nodes.remove (i); - - if (isPrepared) - triggerAsyncUpdate(); - + topologyChanged(); return true; } } @@ -1142,257 +921,274 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) bool AudioProcessorGraph::removeNode (Node* node) { if (node != nullptr) - return removeNode (node->nodeId); + return removeNode (node->nodeID); jassertfalse; return false; } //============================================================================== -const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, - const int sourceChannelIndex, - const uint32 destNodeId, - const int destChannelIndex) const +void AudioProcessorGraph::getNodeConnections (Node& node, std::vector& connections) { - const Connection c (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex); - GraphRenderingOps::ConnectionSorter sorter; - return connections [connections.indexOfSorted (sorter, &c)]; + for (auto& i : node.inputs) + connections.push_back ({ { i.otherNode->nodeID, i.otherChannel }, { node.nodeID, i.thisChannel } }); + + for (auto& o : node.outputs) + connections.push_back ({ { node.nodeID, o.thisChannel }, { o.otherNode->nodeID, o.otherChannel } }); } -bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId, - const uint32 possibleDestNodeId) const +std::vector AudioProcessorGraph::getConnections() const { - for (int i = connections.size(); --i >= 0;) - { - const Connection* const c = connections.getUnchecked(i); + std::vector connections; - if (c->sourceNodeId == possibleSourceNodeId - && c->destNodeId == possibleDestNodeId) - { + for (auto& n : nodes) + getNodeConnections (*n, connections); + + std::sort (connections.begin(), connections.end()); + auto last = std::unique (connections.begin(), connections.end()); + connections.erase (last, connections.end()); + + return connections; +} + +bool AudioProcessorGraph::isConnected (Node* source, int sourceChannel, Node* dest, int destChannel) const noexcept +{ + for (auto& o : source->outputs) + if (o.otherNode == dest && o.thisChannel == sourceChannel && o.otherChannel == destChannel) return true; - } - } return false; } -bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, - const int sourceChannelIndex, - const uint32 destNodeId, - const int destChannelIndex) const +bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept { - if (sourceChannelIndex < 0 - || destChannelIndex < 0 - || sourceNodeId == destNodeId - || (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex)) - return false; + if (auto* source = getNodeForId (c.source.nodeID)) + if (auto* dest = getNodeForId (c.destination.nodeID)) + return isConnected (source, c.source.channelIndex, + dest, c.destination.channelIndex); - const Node* const source = getNodeForId (sourceNodeId); + return false; +} - if (source == nullptr - || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) - || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) - return false; +bool AudioProcessorGraph::isConnected (NodeID srcID, NodeID destID) const noexcept +{ + if (auto* source = getNodeForId (srcID)) + if (auto* dest = getNodeForId (destID)) + for (auto& out : source->outputs) + if (out.otherNode == dest) + return true; + + return false; +} - const Node* const dest = getNodeForId (destNodeId); +bool AudioProcessorGraph::isAnInputTo (Node& src, Node& dst) const noexcept +{ + jassert (nodes.contains (&src)); + jassert (nodes.contains (&dst)); - if (dest == nullptr - || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) - || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) - return false; + return isAnInputTo (src, dst, nodes.size()); +} + +bool AudioProcessorGraph::isAnInputTo (Node& src, Node& dst, int recursionCheck) const noexcept +{ + for (auto&& i : dst.inputs) + if (i.otherNode == &src) + return true; - return getConnectionBetween (sourceNodeId, sourceChannelIndex, - destNodeId, destChannelIndex) == nullptr; + if (recursionCheck > 0) + for (auto&& i : dst.inputs) + if (isAnInputTo (src, *i.otherNode, recursionCheck - 1)) + return true; + + return false; } -bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, - const int sourceChannelIndex, - const uint32 destNodeId, - const int destChannelIndex) + +bool AudioProcessorGraph::canConnect (Node* source, int sourceChannel, Node* dest, int destChannel) const noexcept { - if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) + bool sourceIsMIDI = sourceChannel == midiChannelIndex; + bool destIsMIDI = destChannel == midiChannelIndex; + + if (sourceChannel < 0 + || destChannel < 0 + || source == dest + || sourceIsMIDI != destIsMIDI) return false; - GraphRenderingOps::ConnectionSorter sorter; - connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, - destNodeId, destChannelIndex)); + if (source == nullptr + || (! sourceIsMIDI && sourceChannel >= source->processor->getTotalNumOutputChannels()) + || (sourceIsMIDI && ! source->processor->producesMidi())) + return false; - if (isPrepared) - triggerAsyncUpdate(); + if (dest == nullptr + || (! destIsMIDI && destChannel >= dest->processor->getTotalNumInputChannels()) + || (destIsMIDI && ! dest->processor->acceptsMidi())) + return false; - return true; + return ! isConnected (source, sourceChannel, dest, destChannel); } -void AudioProcessorGraph::removeConnection (const int index) +bool AudioProcessorGraph::canConnect (const Connection& c) const { - connections.remove (index); + if (auto* source = getNodeForId (c.source.nodeID)) + if (auto* dest = getNodeForId (c.destination.nodeID)) + return canConnect (source, c.source.channelIndex, + dest, c.destination.channelIndex); - if (isPrepared) - triggerAsyncUpdate(); + return false; } -bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, - const uint32 destNodeId, const int destChannelIndex) +bool AudioProcessorGraph::addConnection (const Connection& c) { - bool doneAnything = false; - - for (int i = connections.size(); --i >= 0;) + if (auto* source = getNodeForId (c.source.nodeID)) { - const Connection* const c = connections.getUnchecked(i); - - if (c->sourceNodeId == sourceNodeId - && c->destNodeId == destNodeId - && c->sourceChannelIndex == sourceChannelIndex - && c->destChannelIndex == destChannelIndex) + if (auto* dest = getNodeForId (c.destination.nodeID)) { - removeConnection (i); - doneAnything = true; + auto sourceChan = c.source.channelIndex; + auto destChan = c.destination.channelIndex; + + if (canConnect (source, sourceChan, dest, destChan)) + { + source->outputs.add ({ dest, destChan, sourceChan }); + dest->inputs.add ({ source, sourceChan, destChan }); + jassert (isConnected (c)); + topologyChanged(); + return true; + } } } - return doneAnything; + return false; } -bool AudioProcessorGraph::disconnectNode (const uint32 nodeId) +bool AudioProcessorGraph::removeConnection (const Connection& c) { - bool doneAnything = false; + if (auto* source = getNodeForId (c.source.nodeID)) + { + if (auto* dest = getNodeForId (c.destination.nodeID)) + { + auto sourceChan = c.source.channelIndex; + auto destChan = c.destination.channelIndex; + + if (isConnected (source, sourceChan, dest, destChan)) + { + source->outputs.removeAllInstancesOf ({ dest, destChan, sourceChan }); + dest->inputs.removeAllInstancesOf ({ source, sourceChan, destChan }); + topologyChanged(); + return true; + } + } + } + + return false; +} - for (int i = connections.size(); --i >= 0;) +bool AudioProcessorGraph::disconnectNode (NodeID nodeID) +{ + if (auto* node = getNodeForId (nodeID)) { - const Connection* const c = connections.getUnchecked(i); + std::vector connections; + getNodeConnections (*node, connections); - if (c->sourceNodeId == nodeId || c->destNodeId == nodeId) + if (! connections.empty()) { - removeConnection (i); - doneAnything = true; + for (auto c : connections) + removeConnection (c); + + return true; } } - return doneAnything; + return false; } -bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const +bool AudioProcessorGraph::isLegal (Node* source, int sourceChannel, Node* dest, int destChannel) const noexcept { - jassert (c != nullptr); + return (sourceChannel == midiChannelIndex ? source->processor->producesMidi() + : isPositiveAndBelow (sourceChannel, source->processor->getTotalNumOutputChannels())) + && (destChannel == midiChannelIndex ? dest->processor->acceptsMidi() + : isPositiveAndBelow (destChannel, dest->processor->getTotalNumInputChannels())); +} - const Node* const source = getNodeForId (c->sourceNodeId); - const Node* const dest = getNodeForId (c->destNodeId); +bool AudioProcessorGraph::isConnectionLegal (const Connection& c) const +{ + if (auto* source = getNodeForId (c.source.nodeID)) + if (auto* dest = getNodeForId (c.destination.nodeID)) + return isLegal (source, c.source.channelIndex, dest, c.destination.channelIndex); - return source != nullptr - && dest != nullptr - && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) - : source->processor->producesMidi()) - && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) - : dest->processor->acceptsMidi()); + return false; } bool AudioProcessorGraph::removeIllegalConnections() { - bool doneAnything = false; + bool anyRemoved = false; - for (int i = connections.size(); --i >= 0;) + for (auto* node : nodes) { - if (! isConnectionLegal (connections.getUnchecked(i))) - { - removeConnection (i); - doneAnything = true; - } + std::vector connections; + getNodeConnections (*node, connections); + + for (auto c : connections) + if (! isConnectionLegal (c)) + anyRemoved = removeConnection (c) || anyRemoved; } - return doneAnything; + return anyRemoved; } //============================================================================== -static void deleteRenderOpArray (Array& ops) -{ - for (int i = ops.size(); --i >= 0;) - delete static_cast (ops.getUnchecked(i)); -} - void AudioProcessorGraph::clearRenderingSequence() { - Array oldOps; + ScopedPointer oldSequenceF; + ScopedPointer oldSequenceD; { const ScopedLock sl (getCallbackLock()); - renderingOps.swapWith (oldOps); + renderSequenceFloat.swapWith (oldSequenceF); + renderSequenceDouble.swapWith (oldSequenceD); } - - deleteRenderOpArray (oldOps); } -bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId, - const uint32 possibleDestinationId, - const int recursionCheck) const +bool AudioProcessorGraph::anyNodesNeedPreparing() const noexcept { - if (recursionCheck > 0) - { - for (int i = connections.size(); --i >= 0;) - { - const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i); - - if (c->destNodeId == possibleDestinationId - && (c->sourceNodeId == possibleInputId - || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1))) - return true; - } - } + for (auto* node : nodes) + if (! node->isPrepared) + return true; return false; } void AudioProcessorGraph::buildRenderingSequence() { - Array newRenderingOps; - int numRenderingBuffersNeeded = 2; - int numMidiBuffersNeeded = 1; + ScopedPointer newSequenceF (new RenderSequenceFloat()); + ScopedPointer newSequenceD (new RenderSequenceDouble()); { MessageManagerLock mml; - Array orderedNodes; - - { - const GraphRenderingOps::ConnectionLookupTable table (connections); - - for (int i = 0; i < nodes.size(); ++i) - { - Node* const node = nodes.getUnchecked(i); - - node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); + RenderSequenceBuilder builderF (*this, *newSequenceF); + RenderSequenceBuilder builderD (*this, *newSequenceD); + } - int j = 0; - for (; j < orderedNodes.size(); ++j) - if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId)) - break; + newSequenceF->prepareBuffers (getBlockSize()); + newSequenceD->prepareBuffers (getBlockSize()); - orderedNodes.insert (j, node); - } + if (anyNodesNeedPreparing()) + { + { + const ScopedLock sl (getCallbackLock()); + renderSequenceFloat = nullptr; + renderSequenceDouble = nullptr; } - GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps); - - numRenderingBuffersNeeded = calculator.getNumBuffersNeeded(); - numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded(); + for (auto* node : nodes) + node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); } - { - // swap over to the new rendering sequence.. - const ScopedLock sl (getCallbackLock()); - - audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); - - for (int i = midiBuffers.size(); --i >= 0;) - midiBuffers.getUnchecked(i)->clear(); - - while (midiBuffers.size() < numMidiBuffersNeeded) - midiBuffers.add (new MidiBuffer()); - - renderingOps.swapWith (newRenderingOps); - } + const ScopedLock sl (getCallbackLock()); - // delete the old ones.. - deleteRenderOpArray (newRenderingOps); + renderSequenceFloat.swapWith (newSequenceF); + renderSequenceDouble.swapWith (newSequenceD); } void AudioProcessorGraph::handleAsyncUpdate() @@ -1403,10 +1199,11 @@ void AudioProcessorGraph::handleAsyncUpdate() //============================================================================== void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) { - audioBuffers->prepareInOutBuffers (jmax (1, getTotalNumOutputChannels()), estimatedSamplesPerBlock); + if (renderSequenceFloat != nullptr) + renderSequenceFloat->prepareBuffers (estimatedSamplesPerBlock); - currentMidiInputBuffer = nullptr; - currentMidiOutputBuffer.clear(); + if (renderSequenceDouble != nullptr) + renderSequenceDouble->prepareBuffers (estimatedSamplesPerBlock); clearRenderingSequence(); buildRenderingSequence(); @@ -1423,22 +1220,22 @@ void AudioProcessorGraph::releaseResources() { isPrepared = false; - for (int i = 0; i < nodes.size(); ++i) - nodes.getUnchecked(i)->unprepare(); + for (auto* n : nodes) + n->unprepare(); - audioBuffers->release(); - midiBuffers.clear(); + if (renderSequenceFloat != nullptr) + renderSequenceFloat->releaseBuffers(); - currentMidiInputBuffer = nullptr; - currentMidiOutputBuffer.clear(); + if (renderSequenceDouble != nullptr) + renderSequenceDouble->releaseBuffers(); } void AudioProcessorGraph::reset() { const ScopedLock sl (getCallbackLock()); - for (int i = 0; i < nodes.size(); ++i) - nodes.getUnchecked(i)->getProcessor()->reset(); + for (auto* n : nodes) + n->getProcessor()->reset(); } void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept @@ -1447,8 +1244,8 @@ void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept AudioProcessor::setNonRealtime (isProcessingNonRealtime); - for (int i = 0; i < nodes.size(); ++i) - nodes.getUnchecked(i)->getProcessor()->setNonRealtime (isProcessingNonRealtime); + for (auto* n : nodes) + n->getProcessor()->setNonRealtime (isProcessingNonRealtime); } void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) @@ -1457,58 +1254,8 @@ void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) AudioProcessor::setPlayHead (audioPlayHead); - for (int i = 0; i < nodes.size(); ++i) - nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); -} - -template -void AudioProcessorGraph::processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) -{ - AudioBuffer& renderingBuffers = audioBuffers->renderingBuffers.get(); - AudioBuffer*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer.get(); - AudioBuffer& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get(); - - const int numSamples = buffer.getNumSamples(); - jassert (numSamples <= getBlockSize()); - - currentAudioInputBuffer = &buffer; - currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); - currentAudioOutputBuffer.clear(); - currentMidiInputBuffer = &midiMessages; - currentMidiOutputBuffer.clear(); - - for (int i = 0; i < renderingOps.size(); ++i) - { - GraphRenderingOps::AudioGraphRenderingOpBase* const op - = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); - - op->perform (renderingBuffers, midiBuffers, numSamples); - } - - for (int i = 0; i < buffer.getNumChannels(); ++i) - buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); - - midiMessages.clear(); - midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); -} - -template -void AudioProcessorGraph::sliceAndProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) -{ - auto n = buffer.getNumSamples(); - auto ch = buffer.getNumChannels(); - auto max = 0; - - for (auto pos = 0; pos < n; pos += max) - { - max = jmin (n - pos, getBlockSize()); - - AudioBuffer audioSlice (buffer.getArrayOfWritePointers(), ch, pos, max); - MidiBuffer midiSlice; - - midiSlice.addEvents (midiMessages, pos, max, 0); - processAudio (audioSlice, midiSlice); - } + for (auto* n : nodes) + n->getProcessor()->setPlayHead (audioPlayHead); } double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } @@ -1519,23 +1266,23 @@ void AudioProcessorGraph::setStateInformation (const void*, int) {} void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { - sliceAndProcess (buffer, midiMessages); + const ScopedLock sl (getCallbackLock()); + + if (renderSequenceFloat != nullptr) + renderSequenceFloat->perform (buffer, midiMessages); } void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { - sliceAndProcess (buffer, midiMessages); -} + const ScopedLock sl (getCallbackLock()); -// explicit template instantiation -template void AudioProcessorGraph::processAudio ( AudioBuffer& buffer, - MidiBuffer& midiMessages); -template void AudioProcessorGraph::processAudio (AudioBuffer& buffer, - MidiBuffer& midiMessages); + if (renderSequenceDouble != nullptr) + renderSequenceDouble->perform (buffer, midiMessages); +} //============================================================================== AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) - : type (deviceType), graph (nullptr) + : type (deviceType) { } @@ -1568,10 +1315,12 @@ void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (Plugin d.isInstrument = false; d.numInputChannels = getTotalNumInputChannels(); + if (type == audioOutputNode && graph != nullptr) d.numInputChannels = graph->getTotalNumInputChannels(); d.numOutputChannels = getTotalNumOutputChannels(); + if (type == audioInputNode && graph != nullptr) d.numOutputChannels = graph->getTotalNumOutputChannels(); } @@ -1590,48 +1339,38 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessi return true; } -template -void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer& buffer, - MidiBuffer& midiMessages) +template +static void processIOBlock (AudioProcessorGraph::AudioGraphIOProcessor& io, SequenceType& sequence, + AudioBuffer& buffer, MidiBuffer& midiMessages) { - AudioBuffer*& currentAudioInputBuffer = - graph->audioBuffers->currentAudioInputBuffer.get(); - - AudioBuffer& currentAudioOutputBuffer = - graph->audioBuffers->currentAudioOutputBuffer.get(); - - jassert (graph != nullptr); - - switch (type) + switch (io.getType()) { - case audioOutputNode: + case AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode: { - for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), - buffer.getNumChannels()); --i >= 0;) - { + auto&& currentAudioOutputBuffer = sequence.currentAudioOutputBuffer; + + for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), buffer.getNumChannels()); --i >= 0;) currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); - } break; } - case audioInputNode: + case AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode: { - for (int i = jmin (currentAudioInputBuffer->getNumChannels(), - buffer.getNumChannels()); --i >= 0;) - { - buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); - } + auto* currentInputBuffer = sequence.currentAudioInputBuffer; + + for (int i = jmin (currentInputBuffer->getNumChannels(), buffer.getNumChannels()); --i >= 0;) + buffer.copyFrom (i, 0, *currentInputBuffer, i, 0, buffer.getNumSamples()); break; } - case midiOutputNode: - graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); + case AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode: + sequence.currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); break; - case midiInputNode: - midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); + case AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode: + midiMessages.addEvents (*sequence.currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); break; default: @@ -1639,16 +1378,16 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer& buffer, - MidiBuffer& midiMessages) +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { - processAudio (buffer, midiMessages); + jassert (graph != nullptr); + processIOBlock (*this, *graph->renderSequenceFloat, buffer, midiMessages); } -void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, - MidiBuffer& midiMessages) +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { - processAudio (buffer, midiMessages); + jassert (graph != nullptr); + processIOBlock (*this, *graph->renderSequenceDouble, buffer, midiMessages); } double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index c9a2b1cd88..9f0d4de34f 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -42,6 +42,7 @@ namespace juce AudioProcessorPlayer object. */ class JUCE_API AudioProcessorGraph : public AudioProcessor, + public ChangeBroadcaster, private AsyncUpdater { public: @@ -54,6 +55,32 @@ public: */ ~AudioProcessorGraph(); + /** Each node in the graph has a UID of this type. */ + typedef uint32 NodeID; + + //============================================================================== + /** A special index that represents the midi channel of a node. + + This is used as a channel index value if you want to refer to the midi input + or output instead of an audio channel. + */ + enum { midiChannelIndex = 0x1000 }; + + //============================================================================== + /** + Represents an input or output channel of a node in an AudioProcessorGraph. + */ + struct NodeAndChannel + { + NodeID nodeID; + int channelIndex; + + bool isMIDI() const noexcept { return channelIndex == midiChannelIndex; } + + bool operator== (const NodeAndChannel& other) const noexcept { return nodeID == other.nodeID && channelIndex == other.channelIndex; } + bool operator!= (const NodeAndChannel& other) const noexcept { return ! operator== (other); } + }; + //============================================================================== /** Represents one of the nodes, or processors, in an AudioProcessorGraph. @@ -66,7 +93,7 @@ public: /** The ID number assigned to this node. This is assigned by the graph that owns it, and can't be changed. */ - const uint32 nodeId; + const NodeID nodeID; /** The actual processor object that this node represents. */ AudioProcessor* getProcessor() const noexcept { return processor; } @@ -87,10 +114,19 @@ public: //============================================================================== friend class AudioProcessorGraph; + struct Connection + { + Node* otherNode; + int otherChannel, thisChannel; + + bool operator== (const Connection&) const noexcept; + }; + const ScopedPointer processor; - bool isPrepared; + Array inputs, outputs; + bool isPrepared = false; - Node (uint32 nodeId, AudioProcessor*) noexcept; + Node (NodeID, AudioProcessor*) noexcept; void setParentGraph (AudioProcessorGraph*) const; void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*, ProcessingPrecision); @@ -107,41 +143,21 @@ public: struct JUCE_API Connection { //============================================================================== - Connection (uint32 sourceNodeId, int sourceChannelIndex, - uint32 destNodeId, int destChannelIndex) noexcept; - - //============================================================================== - /** The ID number of the node which is the input source for this connection. - @see AudioProcessorGraph::getNodeForId - */ - uint32 sourceNodeId; - - /** The index of the output channel of the source node from which this - connection takes its data. + Connection (NodeAndChannel source, NodeAndChannel destination) noexcept; - If this value is the special number AudioProcessorGraph::midiChannelIndex, then - it is referring to the source node's midi output. Otherwise, it is the zero-based - index of an audio output channel in the source node. - */ - int sourceChannelIndex; - - /** The ID number of the node which is the destination for this connection. - @see AudioProcessorGraph::getNodeForId - */ - uint32 destNodeId; + Connection (const Connection&) = default; + Connection& operator= (const Connection&) = default; - /** The index of the input channel of the destination node to which this - connection delivers its data. + bool operator== (const Connection&) const noexcept; + bool operator!= (const Connection&) const noexcept; + bool operator< (const Connection&) const noexcept; - If this value is the special number AudioProcessorGraph::midiChannelIndex, then - it is referring to the destination node's midi input. Otherwise, it is the zero-based - index of an audio input channel in the destination node. - */ - int destChannelIndex; - - private: //============================================================================== - JUCE_LEAK_DETECTOR (Connection) + /** The channel and node which is the input source for this connection. */ + NodeAndChannel source; + + /** The channel and node which is the input source for this connection. */ + NodeAndChannel destination; }; //============================================================================== @@ -150,6 +166,9 @@ public: */ void clear(); + /** Returns the array of nodes in the graph. */ + const ReferenceCountedArray& getNodes() const noexcept { return nodes; } + /** Returns the number of nodes in the graph. */ int getNumNodes() const noexcept { return nodes.size(); } @@ -157,13 +176,13 @@ public: This will return nullptr if the index is out of range. @see getNodeForId */ - Node* getNode (const int index) const noexcept { return nodes [index]; } + Node* getNode (int index) const noexcept { return nodes [index]; } /** Searches the graph for a node with the given ID number and returns it. If no such node was found, this returns nullptr. @see getNode */ - Node* getNodeForId (const uint32 nodeId) const; + Node* getNodeForId (NodeID) const; /** Adds a node to the graph. @@ -176,69 +195,56 @@ public: If this succeeds, it returns a pointer to the newly-created node. */ - Node* addNode (AudioProcessor* newProcessor, uint32 nodeId = 0); + Node::Ptr addNode (AudioProcessor* newProcessor, NodeID nodeId = {}); /** Deletes a node within the graph which has the specified ID. This will also delete any connections that are attached to this node. */ - bool removeNode (uint32 nodeId); + bool removeNode (NodeID); /** Deletes a node within the graph. This will also delete any connections that are attached to this node. */ - bool removeNode (Node* node); + bool removeNode (Node*); - //============================================================================== - /** Returns the number of connections in the graph. */ - int getNumConnections() const { return connections.size(); } + /** Returns the list of connections in the graph. */ + std::vector getConnections() const; - /** Returns a pointer to one of the connections in the graph. */ - const Connection* getConnection (int index) const { return connections [index]; } + /** Returns true if the given connection exists. */ + bool isConnected (const Connection&) const noexcept; - /** Searches for a connection between some specified channels. - If no such connection is found, this returns nullptr. + /** Returns true if there is a direct connection between any of the channels of + two specified nodes. */ - const Connection* getConnectionBetween (uint32 sourceNodeId, - int sourceChannelIndex, - uint32 destNodeId, - int destChannelIndex) const; + bool isConnected (NodeID possibleSourceNodeID, NodeID possibleDestNodeID) const noexcept; - /** Returns true if there is a connection between any of the channels of - two specified nodes. + /** Does a recursive check to see if there's a direct or indirect series of connections + between these two nodes. */ - bool isConnected (uint32 possibleSourceNodeId, - uint32 possibleDestNodeId) const; + bool isAnInputTo (Node& source, Node& destination) const noexcept; /** Returns true if it would be legal to connect the specified points. */ - bool canConnect (uint32 sourceNodeId, int sourceChannelIndex, - uint32 destNodeId, int destChannelIndex) const; + bool canConnect (const Connection&) const; /** Attempts to connect two specified channels of two nodes. If this isn't allowed (e.g. because you're trying to connect a midi channel to an audio one or other such nonsense), then it'll return false. */ - bool addConnection (uint32 sourceNodeId, int sourceChannelIndex, - uint32 destNodeId, int destChannelIndex); - - /** Deletes the connection with the specified index. */ - void removeConnection (int index); + bool addConnection (const Connection&); - /** Deletes any connection between two specified points. - Returns true if a connection was actually deleted. - */ - bool removeConnection (uint32 sourceNodeId, int sourceChannelIndex, - uint32 destNodeId, int destChannelIndex); + /** Deletes the given connection. */ + bool removeConnection (const Connection&); /** Removes all connections from the specified node. */ - bool disconnectNode (uint32 nodeId); + bool disconnectNode (NodeID); /** Returns true if the given connection's channel numbers map on to valid channels at each end. Even if a connection is valid when created, its status could change if a node changes its channel config. */ - bool isConnectionLegal (const Connection* connection) const; + bool isConnectionLegal (const Connection&) const; /** Performs a sanity checks of all the connections. @@ -247,15 +253,6 @@ public: */ bool removeIllegalConnections(); - //============================================================================== - /** A special number that represents the midi channel of a node. - - This is used as a channel index value if you want to refer to the midi input - or output instead of an audio channel. - */ - static const int midiChannelIndex; - - //============================================================================== /** A special type of AudioProcessor that can live inside an AudioProcessorGraph in order to use the audio that comes into and out of the graph itself. @@ -305,7 +302,7 @@ public: bool isOutput() const noexcept; //============================================================================== - AudioGraphIOProcessor (const IODeviceType type); + AudioGraphIOProcessor (IODeviceType); ~AudioGraphIOProcessor(); const String getName() const override; @@ -337,11 +334,7 @@ public: private: const IODeviceType type; - AudioProcessorGraph* graph; - - //============================================================================== - template - void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + AudioProcessorGraph* graph = nullptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) }; @@ -373,33 +366,29 @@ public: void setStateInformation (const void* data, int sizeInBytes) override; private: - //============================================================================== - template - void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); - - template - void sliceAndProcess (AudioBuffer& buffer, MidiBuffer& midiMessages); - //============================================================================== ReferenceCountedArray nodes; - OwnedArray connections; - uint32 lastNodeId; - OwnedArray midiBuffers; - Array renderingOps; + NodeID lastNodeID = {}; - friend class AudioGraphIOProcessor; - struct AudioProcessorGraphBufferHelpers; - ScopedPointer audioBuffers; + struct RenderSequenceFloat; + struct RenderSequenceDouble; + ScopedPointer renderSequenceFloat; + ScopedPointer renderSequenceDouble; - MidiBuffer* currentMidiInputBuffer; - MidiBuffer currentMidiOutputBuffer; + friend class AudioGraphIOProcessor; - bool isPrepared; + bool isPrepared = false; + void topologyChanged(); void handleAsyncUpdate() override; void clearRenderingSequence(); void buildRenderingSequence(); - bool isAnInputTo (uint32 possibleInputId, uint32 possibleDestinationId, int recursionCheck) const; + bool anyNodesNeedPreparing() const noexcept; + bool isConnected (Node* src, int sourceChannel, Node* dest, int destChannel) const noexcept; + bool isAnInputTo (Node& src, Node& dst, int recursionCheck) const noexcept; + bool canConnect (Node* src, int sourceChannel, Node* dest, int destChannel) const noexcept; + bool isLegal (Node* src, int sourceChannel, Node* dest, int destChannel) const noexcept; + static void getNodeConnections (Node&, std::vector&); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorGraph) };