1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Cleanup and refactoring work on the AudioProcessorGraph and the audio plugin host demo

This commit is contained in:
jules 2017-10-26 17:26:24 +01:00
parent f1f2a34d04
commit d0514c7924
24 changed files with 1850 additions and 2266 deletions

View file

@ -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
=============

View file

@ -23,6 +23,8 @@ ifndef CONFIG
CONFIG=Debug
endif
JUCE_ARCH_LABEL := $(shell uname -m)
ifeq ($(CONFIG),Debug)
JUCE_BINDIR := build
JUCE_LIBDIR := build

View file

@ -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 = "<group>"; };
6692043E22BB181F01767845,
714C53257417E615916687E5, ); name = "Plugin Host"; sourceTree = "<group>"; };
9D8FE1F65CAD416AA606C47A = {isa = PBXGroup; children = (
6A71B2BCAC4239072BC2BD7E,
5313EB852E41EE58B199B9A2,

View file

@ -1779,6 +1779,7 @@
<ClInclude Include="..\..\Source\GraphEditorPanel.h"/>
<ClInclude Include="..\..\Source\InternalFilters.h"/>
<ClInclude Include="..\..\Source\MainHostWindow.h"/>
<ClInclude Include="..\..\Source\PluginWindow.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\audio_play_head\juce_AudioPlayHead.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\buffers\juce_AudioChannelSet.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\buffers\juce_AudioDataConverters.h"/>

View file

@ -2169,6 +2169,9 @@
<ClInclude Include="..\..\Source\MainHostWindow.h">
<Filter>Plugin Host</Filter>
</ClInclude>
<ClInclude Include="..\..\Source\PluginWindow.h">
<Filter>Plugin Host</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\audio_play_head\juce_AudioPlayHead.h">
<Filter>Juce Modules\juce_audio_basics\audio_play_head</Filter>
</ClInclude>

View file

@ -1779,6 +1779,7 @@
<ClInclude Include="..\..\Source\GraphEditorPanel.h"/>
<ClInclude Include="..\..\Source\InternalFilters.h"/>
<ClInclude Include="..\..\Source\MainHostWindow.h"/>
<ClInclude Include="..\..\Source\PluginWindow.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\audio_play_head\juce_AudioPlayHead.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\buffers\juce_AudioChannelSet.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\buffers\juce_AudioDataConverters.h"/>

View file

@ -2169,6 +2169,9 @@
<ClInclude Include="..\..\Source\MainHostWindow.h">
<Filter>Plugin Host</Filter>
</ClInclude>
<ClInclude Include="..\..\Source\PluginWindow.h">
<Filter>Plugin Host</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\audio_play_head\juce_AudioPlayHead.h">
<Filter>Juce Modules\juce_audio_basics\audio_play_head</Filter>
</ClInclude>

View file

@ -1779,6 +1779,7 @@
<ClInclude Include="..\..\Source\GraphEditorPanel.h"/>
<ClInclude Include="..\..\Source\InternalFilters.h"/>
<ClInclude Include="..\..\Source\MainHostWindow.h"/>
<ClInclude Include="..\..\Source\PluginWindow.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\audio_play_head\juce_AudioPlayHead.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\buffers\juce_AudioChannelSet.h"/>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\buffers\juce_AudioDataConverters.h"/>

View file

@ -2169,6 +2169,9 @@
<ClInclude Include="..\..\Source\MainHostWindow.h">
<Filter>Plugin Host</Filter>
</ClInclude>
<ClInclude Include="..\..\Source\PluginWindow.h">
<Filter>Plugin Host</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\modules\juce_audio_basics\audio_play_head\juce_AudioPlayHead.h">
<Filter>Juce Modules\juce_audio_basics\audio_play_head</Filter>
</ClInclude>

View file

@ -10,9 +10,11 @@
objCExtraSuffix="M73TRi" vst3Folder="" extraCompilerFlags="-Wall -Wshadow -Wstrict-aliasing -Wconversion -Wsign-compare -Woverloaded-virtual -Wextra-semi">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="Plugin Host"
osxSDK="default" osxCompatibility="default" osxArchitecture="default"/>
osxSDK="default" osxCompatibility="default" osxArchitecture="default"
enablePluginBinaryCopyStep="1"/>
<CONFIGURATION name="Release" isDebug="0" optimisation="2" targetName="Plugin Host"
osxSDK="default" osxCompatibility="default" osxArchitecture="default"/>
osxSDK="default" osxCompatibility="default" osxArchitecture="default"
enablePluginBinaryCopyStep="1"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_video" path="../../modules"/>
@ -58,9 +60,11 @@
<VS2013 targetFolder="Builds/VisualStudio2013" vstFolder="" vst3Folder="">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="32-bit"
isDebug="1" optimisation="1" targetName="Plugin Host"/>
isDebug="1" optimisation="1" targetName="Plugin Host" debugInformationFormat="ProgramDatabase"
enablePluginBinaryCopyStep="0"/>
<CONFIGURATION name="Release" winWarningLevel="4" generateManifest="1" winArchitecture="32-bit"
isDebug="0" optimisation="3" targetName="Plugin Host"/>
isDebug="0" optimisation="3" targetName="Plugin Host" debugInformationFormat="ProgramDatabase"
enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_video" path="../../modules"/>
@ -82,9 +86,11 @@
<VS2015 targetFolder="Builds/VisualStudio2015" vstFolder="" vst3Folder="">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="x64"
isDebug="1" optimisation="1" targetName="Plugin Host"/>
isDebug="1" optimisation="1" targetName="Plugin Host" debugInformationFormat="ProgramDatabase"
enablePluginBinaryCopyStep="0"/>
<CONFIGURATION name="Release" winWarningLevel="4" generateManifest="1" winArchitecture="x64"
isDebug="0" optimisation="3" targetName="Plugin Host"/>
isDebug="0" optimisation="3" targetName="Plugin Host" debugInformationFormat="ProgramDatabase"
enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_video" path="../../modules"/>
@ -106,9 +112,11 @@
<VS2017 targetFolder="Builds/VisualStudio2017" vst3Folder="">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="x64"
isDebug="1" optimisation="1" targetName="Plugin Host"/>
isDebug="1" optimisation="1" targetName="Plugin Host" debugInformationFormat="ProgramDatabase"
enablePluginBinaryCopyStep="0"/>
<CONFIGURATION name="Release" winWarningLevel="4" generateManifest="1" winArchitecture="x64"
isDebug="0" optimisation="3" targetName="Plugin Host"/>
isDebug="0" optimisation="3" targetName="Plugin Host" debugInformationFormat="ProgramDatabase"
enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/>
</CONFIGURATIONS>
<MODULEPATHS>
<MODULEPATH id="juce_video" path="../../modules"/>
@ -151,6 +159,7 @@
file="Source/MainHostWindow.cpp"/>
<FILE id="h1kpxyzHi" name="MainHostWindow.h" compile="0" resource="0"
file="Source/MainHostWindow.h"/>
<FILE id="ZwQDmm" name="PluginWindow.h" compile="0" resource="0" file="Source/PluginWindow.h"/>
</MAINGROUP>
<JUCEOPTIONS JUCE_WASAPI="enabled" JUCE_DIRECTSOUND="enabled" JUCE_ALSA="enabled"
JUCE_QUICKTIME="disabled" JUCE_USE_FLAC="disabled" JUCE_USE_OGGVORBIS="disabled"

View file

@ -32,22 +32,17 @@
//==============================================================================
const int FilterGraph::midiChannelNumber = 0x1000;
FilterGraph::FilterGraph (AudioPluginFormatManager& fm)
: FileBasedDocument (filenameSuffix,
filenameWildcard,
: FileBasedDocument (getFilenameSuffix(),
getFilenameWildcard(),
"Load a filter graph",
"Save a filter graph"),
formatManager (fm)
{
InternalPluginFormat internalFormat;
addFilter (internalFormat.audioInDesc, { 0.5, 0.1 });
addFilter (internalFormat.midiInDesc, { 0.25, 0.1 });
addFilter (internalFormat.audioOutDesc, { 0.5, 0.9 });
newDocument();
graph.addListener (this);
graph.addChangeListener (this);
setChangedFlag (false);
}
@ -55,42 +50,36 @@ FilterGraph::FilterGraph (AudioPluginFormatManager& fm)
FilterGraph::~FilterGraph()
{
graph.addListener (this);
graph.removeChangeListener (this);
graph.clear();
}
uint32 FilterGraph::getNextUID() noexcept
FilterGraph::NodeID FilterGraph::getNextUID() noexcept
{
return ++lastUID;
}
//==============================================================================
int FilterGraph::getNumFilters() const noexcept
void FilterGraph::changeListenerCallback (ChangeBroadcaster*)
{
return graph.getNumNodes();
}
changed();
AudioProcessorGraph::Node::Ptr FilterGraph::getNode (int index) const noexcept
{
return graph.getNode (index);
}
AudioProcessorGraph::Node::Ptr FilterGraph::getNodeForId (uint32 uid) const
{
return graph.getNodeForId (uid);
for (int i = activePluginWindows.size(); --i >= 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<double> p)
void FilterGraph::addPlugin (const PluginDescription& desc, Point<double> p)
{
struct AsyncCallback : public AudioPluginFormat::InstantiationCompletionCallback
{
@ -106,7 +95,9 @@ void FilterGraph::addFilter (const PluginDescription& desc, Point<double> p)
Point<double> 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<double> 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<double> FilterGraph::getNodePosition (const uint32 nodeId) const
Point<double> FilterGraph::getNodePosition (NodeID nodeID) const
{
if (auto n = graph.getNodeForId (nodeId))
if (auto* n = graph.getNodeForId (nodeID))
return { static_cast<double> (n->properties ["x"]),
static_cast<double> (n->properties ["y"]) };
@ -170,64 +141,47 @@ Point<double> 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
{
return graph.getConnectionBetween (sourceFilterUID, sourceFilterChannel,
destFilterUID, destFilterChannel);
}
bool FilterGraph::canConnect (uint32 sourceFilterUID, int sourceFilterChannel,
uint32 destFilterUID, int destFilterChannel) const noexcept
{
return graph.canConnect (sourceFilterUID, sourceFilterChannel,
destFilterUID, destFilterChannel);
}
bool FilterGraph::addConnection (uint32 sourceFilterUID, int sourceFilterChannel,
uint32 destFilterUID, int destFilterChannel)
{
const bool result = graph.addConnection (sourceFilterUID, sourceFilterChannel,
destFilterUID, destFilterChannel);
if (result)
changed();
return result;
}
void FilterGraph::removeConnection (const int index)
{
graph.removeConnection (index);
changed();
}
void FilterGraph::removeConnection (uint32 sourceFilterUID, int sourceFilterChannel,
uint32 destFilterUID, int destFilterChannel)
{
if (graph.removeConnection (sourceFilterUID, sourceFilterChannel,
destFilterUID, destFilterChannel))
changed();
}
void FilterGraph::clear()
{
PluginWindow::closeAllCurrentlyOpenWindows();
closeAnyOpenPluginWindows();
graph.clear();
changed();
}
PluginWindow* FilterGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node, PluginWindow::Type type)
{
jassert (node != nullptr);
for (auto* w : activePluginWindows)
if (w->node == node && w->type == type)
return w;
if (auto* processor = node->getProcessor())
{
if (auto* plugin = dynamic_cast<AudioPluginInstance*> (processor))
{
auto description = plugin->getPluginDescription();
if (description.pluginFormatName == "Internal")
{
getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false);
return nullptr;
}
}
return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows));
}
return nullptr;
}
bool FilterGraph::closeAnyOpenPluginWindows()
{
bool wasEmpty = activePluginWindows.isEmpty();
activePluginWindows.clear();
return ! wasEmpty;
}
//==============================================================================
String FilterGraph::getDocumentTitle()
{
@ -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<XmlElement> 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<AudioChannelSet>& 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<AudioChannelSet>& 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");
auto& set = buses.getReference (busIdx);
auto* bus = xml->createNewChildElement ("BUS");
bus->setAttribute ("index", busIdx);
const AudioChannelSet& set = buses.getReference (busIdx);
const String layoutName = set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString();
bus->setAttribute ("layout", layoutName);
xml->addChildElement (bus);
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<AudioPluginInstance*> (node->getProcessor());
if (plugin == nullptr)
if (auto* plugin = dynamic_cast<AudioPluginInstance*> (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 < PluginWindow::NumTypes; ++i)
{
PluginWindow::WindowFormatType type = (PluginWindow::WindowFormatType) i;
if (node->properties.contains (getOpenProp (type)))
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
{
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());
auto type = (PluginWindow::Type) 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());
}
}
{
PluginDescription pd;
plugin->fillInPluginDescription (pd);
e->addChildElement (pd.createXml());
}
{
MemoryBlock m;
node->getProcessor()->getStateInformation (m);
e->createNewChildElement ("STATE")->addTextElement (m.toBase64Encoding());
}
auto layout = plugin->getBusesLayout();
auto layouts = e->createNewChildElement ("LAYOUT");
layouts->addChildElement (createBusLayoutXml (layout, true));
layouts->addChildElement (createBusLayoutXml (layout, false));
return e;
}
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);
XmlElement* layouts = new XmlElement ("LAYOUT");
const AudioProcessor::BusesLayout layout = plugin->getBusesLayout();
const bool isInputChoices[] = { true, false };
for (bool isInput : isInputChoices)
layouts->addChildElement (createBusLayoutXml (layout, isInput));
e->addChildElement (layouts);
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 (const XmlElement* const state = xml.getChildByName ("STATE"))
{
MemoryBlock m;
m.fromBase64Encoding (state->getAllSubText());
node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize());
}
node->properties.set ("x", xml.getDoubleAttribute ("x"));
node->properties.set ("y", xml.getDoubleAttribute ("y"));
for (int i = 0; i < PluginWindow::NumTypes; ++i)
{
PluginWindow::WindowFormatType type = (PluginWindow::WindowFormatType) i;
if (xml.hasAttribute (getOpenProp (type)))
if (auto* layoutEntity = xml.getChildByName ("LAYOUT"))
{
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)));
auto layout = instance->getBusesLayout();
if (node->properties[getOpenProp (type)])
readBusLayoutFromXml (layout, instance, *layoutEntity, true);
readBusLayoutFromXml (layout, instance, *layoutEntity, false);
instance->setBusesLayout (layout);
}
if (auto node = graph.addNode (instance, (NodeID) xml.getIntAttribute ("uid")))
{
if (auto* state = xml.getChildByName ("STATE"))
{
jassert (node->getProcessor() != nullptr);
MemoryBlock m;
m.fromBase64Encoding (state->getAllSubText());
if (PluginWindow* const w = PluginWindow::getWindowFor (node, type))
w->toFront (true);
node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize());
}
node->properties.set ("x", xml.getDoubleAttribute ("x"));
node->properties.set ("y", xml.getDoubleAttribute ("y"));
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
{
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);
auto e = xml->createNewChildElement ("CONNECTION");
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);
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();

View file

@ -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<double>);
AudioProcessorGraph::Node::Ptr getNodeForId (uint32 uid) const;
AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const;
void addFilter (const PluginDescription&, Point<double>);
void addFilterCallback (AudioPluginInstance*, const String& error, Point<double> pos);
void removeFilter (const uint32 filterUID);
void disconnectFilter (const uint32 filterUID);
void removeIllegalConnections();
void setNodePosition (uint32 nodeId, double x, double y);
Point<double> getNodePosition (uint32 nodeId) const;
void setNodePosition (NodeID, Point<double>);
Point<double> 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<PluginWindow> 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<double>);
void changeListenerCallback (ChangeBroadcaster*) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilterGraph)
};

View file

@ -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<uint32> (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 (auto* node : graph->getNodes())
if (node->getProcessor() == getAudioProcessor())
return node->nodeID;
for (int i = 0; i < n; ++i)
if (auto* node = graph->getNode (i))
if (node->getProcessor() == getAudioProcessor())
return static_cast<int32> (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<MainHostWindow*> (comp))
for (int i = desktop.getNumComponents(); --i >= 0;)
if (auto* mainWindow = dynamic_cast<MainHostWindow*> (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;
}

View file

@ -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<InputOutputConfig> 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)
};

View file

@ -28,203 +28,20 @@
#include "GraphEditorPanel.h"
#include "InternalFilters.h"
#include "MainHostWindow.h"
#include "FilterIOConfiguration.h"
//==============================================================================
static Array<PluginWindow*> 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)
PinComponent (GraphEditorPanel& p, AudioProcessorGraph::NodeAndChannel pinToUse, bool isIn)
: panel (p), graph (p.graph), pin (pinToUse), isInput (isIn)
{
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<PropertyComponent*> 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<AudioPluginInstance*> (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<AudioPluginInstance*> (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)
{
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<float> (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<GraphEditorPanel>();
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);
graph.setNodePosition (pluginID,
(pos.getX() + getWidth() / 2) / (double) getParentWidth(),
(pos.getY() + getHeight() / 2) / (double) getParentHeight());
pos += getLocalBounds().getCentre();
getGraphPanel()->updateComponents();
graph.setNodePosition (pluginID,
{ pos.x / (double) getParentWidth(),
pos.y / (double) getParentHeight() });
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
{
auto boxArea = getLocalBounds().reduced (4, pinSize);
g.setColour (findColour (TextEditor::backgroundColourId));
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.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<PinComponent*> (child))
{
const bool isInput = pin->isInput;
int busIdx = 0;
processor->getOffsetInBusBufferForAbsoluteChannelIndex (isInput, pin->index, busIdx);
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->index == FilterGraph::midiChannelNumber ? (total - 1) : pin->index;
const int total = isInput ? numIns : numOuts;
const int index = pin->pin.isMIDI() ? (total - 1) : channelIndex;
auto totalSpaces = static_cast<float> (total) + (static_cast<float> (jmax (0, processor->getBusCount (isInput) - 1)) * 0.5f);
auto indexPos = static_cast<float> (index) + (static_cast<float> (busIdx) * 0.5f);
auto totalSpaces = static_cast<float> (total) + (static_cast<float> (jmax (0, processor->getBusCount (isInput) - 1)) * 0.5f);
auto indexPos = static_cast<float> (index) + (static_cast<float> (busIdx) * 0.5f);
pin->setBounds (proportionOfWidth ((1.0f + indexPos) / (totalSpaces + 1.0f)) - pinSize / 2,
pin->isInput ? 0 : (getHeight() - pinSize),
pinSize, pinSize);
}
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<float> getPinPos (int index, bool isInput) const
{
for (auto* child : getChildren())
if (auto* pin = dynamic_cast<PinComponent*> (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<double> 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
{
return findParentComponentOfClass<GraphEditorPanel>();
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()
{
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<PinComponent> pins;
int numInputs = 0, numOutputs = 0;
int pinSize = 16;
Point<int> 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<GraphEditorPanel>();
}
void getDistancesFromEnds (Point<float> p, double& distanceFromStart, double& distanceFromEnd) const
{
Point<float> 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<float> 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<int> position)
{
graph.addFilter (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight()));
graph.addPlugin (desc, position.toDouble() / Point<double> ((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<FilterComponent*> (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<ConnectorComponent*> (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<float> pos) const
GraphEditorPanel::PinComponent* GraphEditorPanel::findPinAt (Point<float> pos) const
{
for (auto* child : getChildren())
if (auto* fc = dynamic_cast<FilterComponent*> (child))
if (auto* pin = dynamic_cast<PinComponent*> (fc->getComponentAt (pos.toInt() - fc->getPosition())))
return pin;
for (auto* fc : nodes)
if (auto* pin = dynamic_cast<PinComponent*> (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<FilterComponent*> (static_cast<Component*> (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;)
for (int i = connectors.size(); --i >= 0;)
if (! graph.graph.isConnected (connectors.getUnchecked(i)->connection))
connectors.remove (i);
for (auto* fc : nodes)
fc->update();
for (auto* cc : connectors)
cc->update();
for (auto* f : graph.graph.getNodes())
{
auto* cc = dynamic_cast<ConnectorComponent*> (getChildComponent (i));
if (cc != nullptr && cc != draggingConnector)
if (getComponentForFilter (f->nodeID) == 0)
{
if (graph.getConnectionBetween (cc->sourceFilterID, cc->sourceFilterChannel,
cc->destFilterID, cc->destFilterChannel) == nullptr)
{
delete cc;
}
else
{
cc->update();
}
}
}
for (int i = graph.getNumFilters(); --i >= 0;)
{
auto f = graph.getNode (i);
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<ConnectorComponent*> (e.originalComponent);
auto* c = dynamic_cast<ConnectorComponent*> (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();
}

View file

@ -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<int> position);
FilterComponent* getComponentForFilter (uint32 filterID) const;
ConnectorComponent* getComponentForConnection (const AudioProcessorGraph::Connection& conn) const;
PinComponent* findPinAt (Point<float>) 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<FilterComponent> nodes;
OwnedArray<ConnectorComponent> connectors;
ScopedPointer<ConnectorComponent> draggingConnector;
FilterComponent* getComponentForFilter (AudioProcessorGraph::NodeID) const;
ConnectorComponent* getComponentForConnection (const AudioProcessorGraph::Connection&) const;
PinComponent* findPinAt (Point<float>) 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<int> position);
void setDoublePrecision (bool doublePrecision);
bool closeAnyOpenPluginWindows();
//==============================================================================
ScopedPointer<FilterGraph> graph;
//==============================================================================
void resized();
//==============================================================================
void unfocusKeyboardComponent();
//==============================================================================
void releaseGraph();
ScopedPointer<GraphEditorPanel> graphPanel;
ScopedPointer<MidiKeyboardComponent> keyboardComp;
private:
//==============================================================================
AudioDeviceManager& deviceManager;
AudioProcessorPlayer graphPlayer;
MidiKeyboardState keyState;
public:
GraphEditorPanel* graphPanel;
private:
Component* keyboardComp;
Component* statusBar;
struct TooltipBar;
ScopedPointer<TooltipBar> 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); }

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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;
};

View file

@ -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<int> 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<PluginDescription> typesFound;
knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
auto pos = graphEditor->getLocalPoint (this, Point<int> (x, y));
auto pos = graphHolder->getLocalPoint (this, Point<int> (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<GraphDocumentComponent*> (getContentComponent());
}
bool MainHostWindow::isDoublePrecisionProcessing()
{
if (auto* props = getAppProperties().getUserSettings())

View file

@ -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<CommandID>& 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<CommandID>&) 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<GraphDocumentComponent> graphHolder;
private:
//==============================================================================
AudioDeviceManager deviceManager;

View file

@ -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<PluginWindow>& 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<PluginWindow>& 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<PropertyComponent*> 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)
};

View file

@ -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;
const ScopedPointer<AudioProcessor> processor;
bool isPrepared;
struct Connection
{
Node* otherNode;
int otherChannel, thisChannel;
Node (uint32 nodeId, AudioProcessor*) noexcept;
bool operator== (const Connection&) const noexcept;
};
const ScopedPointer<AudioProcessor> processor;
Array<Connection> inputs, outputs;
bool isPrepared = false;
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;
Connection (NodeAndChannel source, NodeAndChannel destination) noexcept;
Connection (const Connection&) = default;
Connection& operator= (const Connection&) = default;
bool operator== (const Connection&) const noexcept;
bool operator!= (const Connection&) const noexcept;
bool operator< (const Connection&) const noexcept;
//==============================================================================
/** The ID number of the node which is the input source for this connection.
@see AudioProcessorGraph::getNodeForId
*/
uint32 sourceNodeId;
/** The channel and node which is the input source for this connection. */
NodeAndChannel source;
/** The index of the output channel of the source node from which this
connection takes its data.
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;
/** The index of the input channel of the destination node to which this
connection delivers its data.
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 destination;
};
//==============================================================================
@ -150,6 +166,9 @@ public:
*/
void clear();
/** Returns the array of nodes in the graph. */
const ReferenceCountedArray<Node>& 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<Connection> 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.
*/
const Connection* getConnectionBetween (uint32 sourceNodeId,
int sourceChannelIndex,
uint32 destNodeId,
int destChannelIndex) const;
/** Returns true if there is a connection between any of the channels of
/** Returns true if there is a direct connection between any of the channels of
two specified nodes.
*/
bool isConnected (uint32 possibleSourceNodeId,
uint32 possibleDestNodeId) const;
bool isConnected (NodeID possibleSourceNodeID, NodeID possibleDestNodeID) const noexcept;
/** Does a recursive check to see if there's a direct or indirect series of connections
between these two nodes.
*/
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);
bool addConnection (const Connection&);
/** Deletes the connection with the specified index. */
void removeConnection (int index);
/** 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 <typename floatType>
void processAudio (AudioBuffer<floatType>& 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 <typename floatType>
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages);
template <typename floatType>
void sliceAndProcess (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages);
//==============================================================================
ReferenceCountedArray<Node> nodes;
OwnedArray<Connection> connections;
uint32 lastNodeId;
OwnedArray<MidiBuffer> midiBuffers;
Array<void*> renderingOps;
NodeID lastNodeID = {};
struct RenderSequenceFloat;
struct RenderSequenceDouble;
ScopedPointer<RenderSequenceFloat> renderSequenceFloat;
ScopedPointer<RenderSequenceDouble> renderSequenceDouble;
friend class AudioGraphIOProcessor;
struct AudioProcessorGraphBufferHelpers;
ScopedPointer<AudioProcessorGraphBufferHelpers> audioBuffers;
MidiBuffer* currentMidiInputBuffer;
MidiBuffer currentMidiOutputBuffer;
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<Connection>&);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorGraph)
};