First Commit

This commit is contained in:
Legaeli
2025-10-22 09:20:23 +02:00
commit a36f2ca925
4393 changed files with 1570219 additions and 0 deletions
@@ -0,0 +1,391 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#include <JuceHeader.h>
#include "UI/MainHostWindow.h"
#include "Plugins/InternalPlugins.h"
#if ! (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU)
#error "If you're building the audio plugin host, you probably want to enable VST and/or AU support"
#endif
class PluginScannerSubprocess final : private ChildProcessWorker,
private AsyncUpdater
{
public:
PluginScannerSubprocess()
{
formatManager.addDefaultFormats();
}
using ChildProcessWorker::initialiseFromCommandLine;
private:
void handleMessageFromCoordinator (const MemoryBlock& mb) override
{
if (mb.isEmpty())
return;
const std::lock_guard<std::mutex> lock (mutex);
if (const auto results = doScan (mb); ! results.isEmpty())
{
sendResults (results);
}
else
{
pendingBlocks.emplace (mb);
triggerAsyncUpdate();
}
}
void handleConnectionLost() override
{
JUCEApplicationBase::quit();
}
void handleAsyncUpdate() override
{
for (;;)
{
const std::lock_guard<std::mutex> lock (mutex);
if (pendingBlocks.empty())
return;
sendResults (doScan (pendingBlocks.front()));
pendingBlocks.pop();
}
}
OwnedArray<PluginDescription> doScan (const MemoryBlock& block)
{
MemoryInputStream stream { block, false };
const auto formatName = stream.readString();
const auto identifier = stream.readString();
PluginDescription pd;
pd.fileOrIdentifier = identifier;
pd.uniqueId = pd.deprecatedUid = 0;
const auto matchingFormat = [&]() -> AudioPluginFormat*
{
for (auto* format : formatManager.getFormats())
if (format->getName() == formatName)
return format;
return nullptr;
}();
OwnedArray<PluginDescription> results;
if (matchingFormat != nullptr
&& (MessageManager::getInstance()->isThisTheMessageThread()
|| matchingFormat->requiresUnblockedMessageThreadDuringCreation (pd)))
{
matchingFormat->findAllTypesForFile (results, identifier);
}
return results;
}
void sendResults (const OwnedArray<PluginDescription>& results)
{
XmlElement xml ("LIST");
for (const auto& desc : results)
xml.addChildElement (desc->createXml().release());
const auto str = xml.toString();
sendMessageToCoordinator ({ str.toRawUTF8(), str.getNumBytesAsUTF8() });
}
std::mutex mutex;
std::queue<MemoryBlock> pendingBlocks;
AudioPluginFormatManager formatManager;
};
//==============================================================================
class PluginHostApp final : public JUCEApplication,
private AsyncUpdater
{
public:
PluginHostApp() = default;
void initialise (const String& commandLine) override
{
auto scannerSubprocess = std::make_unique<PluginScannerSubprocess>();
if (scannerSubprocess->initialiseFromCommandLine (commandLine, processUID))
{
storedScannerSubprocess = std::move (scannerSubprocess);
return;
}
// initialise our settings file..
PropertiesFile::Options options;
options.applicationName = "Juce Audio Plugin Host";
options.filenameSuffix = "settings";
options.osxLibrarySubFolder = "Preferences";
appProperties.reset (new ApplicationProperties());
appProperties->setStorageParameters (options);
mainWindow.reset (new MainHostWindow());
commandManager.registerAllCommandsForTarget (this);
commandManager.registerAllCommandsForTarget (mainWindow.get());
mainWindow->menuItemsChanged();
// Important note! We're going to use an async update here so that if we need
// to re-open a file and instantiate some plugins, it will happen AFTER this
// initialisation method has returned.
// On Windows this probably won't make a difference, but on OSX there's a subtle event loop
// issue that can happen if a plugin runs one of those irritating modal dialogs while it's
// being loaded. If that happens inside this method, the OSX event loop seems to be in some
// kind of special "initialisation" mode and things get confused. But if we load the plugin
// later when the normal event loop is running, everything's fine.
triggerAsyncUpdate();
}
void handleAsyncUpdate() override
{
File fileToOpen;
#if JUCE_ANDROID || JUCE_IOS
fileToOpen = PluginGraph::getDefaultGraphDocumentOnMobile();
#else
for (int i = 0; i < getCommandLineParameterArray().size(); ++i)
{
fileToOpen = File::getCurrentWorkingDirectory().getChildFile (getCommandLineParameterArray()[i]);
if (fileToOpen.existsAsFile())
break;
}
#endif
if (! fileToOpen.existsAsFile())
{
RecentlyOpenedFilesList recentFiles;
recentFiles.restoreFromString (getAppProperties().getUserSettings()->getValue ("recentFilterGraphFiles"));
if (recentFiles.getNumFiles() > 0)
fileToOpen = recentFiles.getFile (0);
}
if (fileToOpen.existsAsFile())
if (auto* graph = mainWindow->graphHolder.get())
if (auto* ioGraph = graph->graph.get())
ioGraph->loadFrom (fileToOpen, true);
}
void shutdown() override
{
mainWindow = nullptr;
appProperties = nullptr;
LookAndFeel::setDefaultLookAndFeel (nullptr);
}
void suspended() override
{
#if JUCE_ANDROID || JUCE_IOS
if (auto graph = mainWindow->graphHolder.get())
if (auto ioGraph = graph->graph.get())
ioGraph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile());
#endif
}
void systemRequestedQuit() override
{
if (mainWindow != nullptr)
mainWindow->tryToQuitApplication();
else
JUCEApplicationBase::quit();
}
bool backButtonPressed() override
{
if (mainWindow->graphHolder != nullptr)
mainWindow->graphHolder->hideLastSidePanel();
return true;
}
const String getApplicationName() override { return "Juce Plug-In Host"; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return true; }
ApplicationCommandManager commandManager;
std::unique_ptr<ApplicationProperties> appProperties;
private:
std::unique_ptr<MainHostWindow> mainWindow;
std::unique_ptr<PluginScannerSubprocess> storedScannerSubprocess;
};
static PluginHostApp& getApp() { return *dynamic_cast<PluginHostApp*> (JUCEApplication::getInstance()); }
ApplicationProperties& getAppProperties() { return *getApp().appProperties; }
ApplicationCommandManager& getCommandManager() { return getApp().commandManager; }
bool isOnTouchDevice()
{
static bool isTouch = Desktop::getInstance().getMainMouseSource().isTouch();
return isTouch;
}
//==============================================================================
static AutoScale autoScaleFromString (StringRef str)
{
if (str.isEmpty()) return AutoScale::useDefault;
if (str == CharPointer_ASCII { "0" }) return AutoScale::scaled;
if (str == CharPointer_ASCII { "1" }) return AutoScale::unscaled;
jassertfalse;
return AutoScale::useDefault;
}
static const char* autoScaleToString (AutoScale autoScale)
{
if (autoScale == AutoScale::scaled) return "0";
if (autoScale == AutoScale::unscaled) return "1";
return {};
}
AutoScale getAutoScaleValueForPlugin (const String& identifier)
{
if (identifier.isNotEmpty())
{
auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
plugins.removeEmptyStrings();
for (auto& plugin : plugins)
{
auto fromIdentifier = plugin.fromFirstOccurrenceOf (identifier, false, false);
if (fromIdentifier.isNotEmpty())
return autoScaleFromString (fromIdentifier.fromFirstOccurrenceOf (":", false, false));
}
}
return AutoScale::useDefault;
}
void setAutoScaleValueForPlugin (const String& identifier, AutoScale s)
{
auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
plugins.removeEmptyStrings();
auto index = [identifier, plugins]
{
auto it = std::find_if (plugins.begin(), plugins.end(),
[&] (const String& str) { return str.startsWith (identifier); });
return (int) std::distance (plugins.begin(), it);
}();
if (s == AutoScale::useDefault && index != plugins.size())
{
plugins.remove (index);
}
else
{
auto str = identifier + ":" + autoScaleToString (s);
if (index != plugins.size())
plugins.getReference (index) = str;
else
plugins.add (str);
}
getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n"));
}
static bool isAutoScaleAvailableForPlugin (const PluginDescription& description)
{
return autoScaleOptionAvailable
&& (description.pluginFormatName.containsIgnoreCase ("VST")
|| description.pluginFormatName.containsIgnoreCase ("LV2"));
}
bool shouldAutoScalePlugin (const PluginDescription& description)
{
if (! isAutoScaleAvailableForPlugin (description))
return false;
const auto scaleValue = getAutoScaleValueForPlugin (description.fileOrIdentifier);
return (scaleValue == AutoScale::scaled
|| (scaleValue == AutoScale::useDefault
&& getAppProperties().getUserSettings()->getBoolValue ("autoScalePluginWindows")));
}
void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance,
PopupMenu& menu)
{
if (pluginInstance == nullptr)
return;
auto description = pluginInstance->getPluginDescription();
if (! isAutoScaleAvailableForPlugin (description))
return;
auto identifier = description.fileOrIdentifier;
PopupMenu autoScaleMenu;
autoScaleMenu.addItem ("Default",
true,
getAutoScaleValueForPlugin (identifier) == AutoScale::useDefault,
[identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::useDefault); });
autoScaleMenu.addItem ("Enabled",
true,
getAutoScaleValueForPlugin (identifier) == AutoScale::scaled,
[identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::scaled); });
autoScaleMenu.addItem ("Disabled",
true,
getAutoScaleValueForPlugin (identifier) == AutoScale::unscaled,
[identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::unscaled); });
menu.addSubMenu ("Auto-scale window", autoScaleMenu);
}
// This kicks the whole thing off..
START_JUCE_APPLICATION (PluginHostApp)
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

@@ -0,0 +1,42 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#include "ARAPlugin.h"
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
const Identifier ARAPluginInstanceWrapper::ARATestHost::Context::xmlRootTag { "ARATestHostContext" };
const Identifier ARAPluginInstanceWrapper::ARATestHost::Context::xmlAudioFileAttrib { "AudioFile" };
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,557 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#include <JuceHeader.h>
#include "../UI/GraphEditorPanel.h"
#include "InternalPlugins.h"
#include "../UI/MainHostWindow.h"
#include "IOConfigurationWindow.h"
//==============================================================================
struct NumberedBoxes final : public TableListBox,
private TableListBoxModel,
private Button::Listener
{
struct Listener
{
virtual ~Listener() {}
virtual void addColumn() = 0;
virtual void removeColumn() = 0;
virtual void columnSelected (int columnId) = 0;
};
enum
{
plusButtonColumnId = 128,
minusButtonColumnId = 129
};
//==============================================================================
NumberedBoxes (Listener& listenerToUse, bool canCurrentlyAddColumn, bool canCurrentlyRemoveColumn)
: TableListBox ("NumberedBoxes", this),
listener (listenerToUse),
canAddColumn (canCurrentlyAddColumn),
canRemoveColumn (canCurrentlyRemoveColumn)
{
auto& tableHeader = getHeader();
for (int i = 0; i < 16; ++i)
tableHeader.addColumn (String (i + 1), i + 1, 40);
setHeaderHeight (0);
setRowHeight (40);
getHorizontalScrollBar().setAutoHide (false);
}
void setSelected (int columnId)
{
if (auto* button = dynamic_cast<TextButton*> (getCellComponent (columnId, 0)))
button->setToggleState (true, NotificationType::dontSendNotification);
}
void setCanAddColumn (bool canCurrentlyAdd)
{
if (canCurrentlyAdd != canAddColumn)
{
canAddColumn = canCurrentlyAdd;
if (auto* button = dynamic_cast<TextButton*> (getCellComponent (plusButtonColumnId, 0)))
button->setEnabled (true);
}
}
void setCanRemoveColumn (bool canCurrentlyRemove)
{
if (canCurrentlyRemove != canRemoveColumn)
{
canRemoveColumn = canCurrentlyRemove;
if (auto* button = dynamic_cast<TextButton*> (getCellComponent (minusButtonColumnId, 0)))
button->setEnabled (true);
}
}
private:
//==============================================================================
Listener& listener;
bool canAddColumn, canRemoveColumn;
//==============================================================================
int getNumRows() override { return 1; }
void paintCell (Graphics&, int, int, int, int, bool) override {}
void paintRowBackground (Graphics& g, int, int, int, bool) override { g.fillAll (Colours::grey); }
Component* refreshComponentForCell (int, int columnId, bool,
Component* existingComponentToUpdate) override
{
auto* textButton = dynamic_cast<TextButton*> (existingComponentToUpdate);
if (textButton == nullptr)
textButton = new TextButton();
textButton->setButtonText (getButtonName (columnId));
textButton->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight |
Button::ConnectedOnTop | Button::ConnectedOnBottom);
const bool isPlusMinusButton = (columnId == plusButtonColumnId || columnId == minusButtonColumnId);
if (isPlusMinusButton)
{
textButton->setEnabled (columnId == plusButtonColumnId ? canAddColumn : canRemoveColumn);
}
else
{
textButton->setRadioGroupId (1, NotificationType::dontSendNotification);
textButton->setClickingTogglesState (true);
auto busColour = Colours::green.withRotatedHue (static_cast<float> (columnId) / 5.0f);
textButton->setColour (TextButton::buttonColourId, busColour);
textButton->setColour (TextButton::buttonOnColourId, busColour.withMultipliedBrightness (2.0f));
}
textButton->addListener (this);
return textButton;
}
//==============================================================================
String getButtonName (int columnId)
{
if (columnId == plusButtonColumnId) return "+";
if (columnId == minusButtonColumnId) return "-";
return String (columnId);
}
void buttonClicked (Button* btn) override
{
auto text = btn->getButtonText();
if (text == "+") listener.addColumn();
if (text == "-") listener.removeColumn();
}
void buttonStateChanged (Button* btn) override
{
auto text = btn->getButtonText();
if (text == "+" || text == "-")
return;
if (btn->getToggleState())
listener.columnSelected (text.getIntValue());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NumberedBoxes)
};
//==============================================================================
class IOConfigurationWindow::InputOutputConfig final : public Component,
private Button::Listener,
private NumberedBoxes::Listener
{
public:
InputOutputConfig (IOConfigurationWindow& parent, bool direction)
: owner (parent),
ioTitle ("ioLabel", direction ? "Input Configuration" : "Output Configuration"),
ioBuses (*this, false, false),
isInput (direction)
{
ioTitle.setFont (ioTitle.getFont().withStyle (Font::bold));
nameLabel.setFont (nameLabel.getFont().withStyle (Font::bold));
layoutLabel.setFont (layoutLabel.getFont().withStyle (Font::bold));
enabledToggle.setClickingTogglesState (true);
enabledToggle.addListener (this);
addAndMakeVisible (layoutLabel);
addAndMakeVisible (layouts);
addAndMakeVisible (enabledToggle);
addAndMakeVisible (ioTitle);
addAndMakeVisible (nameLabel);
addAndMakeVisible (name);
addAndMakeVisible (ioBuses);
updateBusButtons();
updateBusLayout();
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
auto r = getLocalBounds().reduced (10);
ioTitle.setBounds (r.removeFromTop (14));
r.reduce (10, 0);
r.removeFromTop (16);
ioBuses.setBounds (r.removeFromTop (60));
{
auto label = r.removeFromTop (24);
nameLabel.setBounds (label.removeFromLeft (100));
enabledToggle.setBounds (label.removeFromRight (80));
name.setBounds (label);
}
{
auto label = r.removeFromTop (24);
layoutLabel.setBounds (label.removeFromLeft (100));
layouts.setBounds (label);
}
}
private:
void updateBusButtons()
{
if (auto* plugin = owner.getAudioProcessor())
{
auto& header = ioBuses.getHeader();
header.removeAllColumns();
const int n = plugin->getBusCount (isInput);
for (int i = 0; i < n; ++i)
header.addColumn ("", i + 1, 40);
header.addColumn ("+", NumberedBoxes::plusButtonColumnId, 20);
header.addColumn ("-", NumberedBoxes::minusButtonColumnId, 20);
ioBuses.setCanAddColumn (plugin->canAddBus (isInput));
ioBuses.setCanRemoveColumn (plugin->canRemoveBus (isInput));
}
ioBuses.setSelected (currentBus + 1);
}
void updateBusLayout()
{
if (auto* plugin = owner.getAudioProcessor())
{
if (auto* bus = plugin->getBus (isInput, currentBus))
{
name.setText (bus->getName(), NotificationType::dontSendNotification);
// supported layouts have changed
layouts.clear (dontSendNotification);
auto* menu = layouts.getRootMenu();
auto itemId = 1;
auto selectedId = -1;
for (auto i = 1; i <= AudioChannelSet::maxChannelsOfNamedLayout; ++i)
{
for (const auto& set : AudioChannelSet::channelSetsWithNumberOfChannels (i))
{
if (bus->isLayoutSupported (set))
{
menu->addItem (PopupMenu::Item { set.getDescription() }
.setAction ([this, set] { applyBusLayout (set); })
.setID (itemId));
}
if (bus->getCurrentLayout() == set)
selectedId = itemId;
++itemId;
}
}
layouts.setSelectedId (selectedId);
const bool canBeDisabled = bus->isNumberOfChannelsSupported (0);
if (canBeDisabled != enabledToggle.isEnabled())
enabledToggle.setEnabled (canBeDisabled);
enabledToggle.setToggleState (bus->isEnabled(), NotificationType::dontSendNotification);
}
}
}
//==============================================================================
void applyBusLayout (const AudioChannelSet& set)
{
if (auto* p = owner.getAudioProcessor())
{
if (auto* bus = p->getBus (isInput, currentBus))
{
if (bus->setCurrentLayoutWithoutEnabling (set))
{
if (auto* config = owner.getConfig (! isInput))
config->updateBusLayout();
owner.update();
}
}
}
}
void buttonClicked (Button*) override {}
void buttonStateChanged (Button* btn) override
{
if (btn == &enabledToggle && enabledToggle.isEnabled())
{
if (auto* p = owner.getAudioProcessor())
{
if (auto* bus = p->getBus (isInput, currentBus))
{
if (bus->isEnabled() != enabledToggle.getToggleState())
{
bool success = enabledToggle.getToggleState() ? bus->enable()
: bus->setCurrentLayout (AudioChannelSet::disabled());
if (success)
{
updateBusLayout();
if (auto* config = owner.getConfig (! isInput))
config->updateBusLayout();
owner.update();
}
else
{
enabledToggle.setToggleState (! enabledToggle.getToggleState(),
NotificationType::dontSendNotification);
}
}
}
}
}
}
//==============================================================================
void addColumn() override
{
if (auto* p = owner.getAudioProcessor())
{
if (p->canAddBus (isInput))
{
if (p->addBus (isInput))
{
updateBusButtons();
updateBusLayout();
if (auto* config = owner.getConfig (! isInput))
{
config->updateBusButtons();
config->updateBusLayout();
}
}
owner.update();
}
}
}
void removeColumn() override
{
if (auto* p = owner.getAudioProcessor())
{
if (p->getBusCount (isInput) > 1 && p->canRemoveBus (isInput))
{
if (p->removeBus (isInput))
{
currentBus = jmin (p->getBusCount (isInput) - 1, currentBus);
updateBusButtons();
updateBusLayout();
if (auto* config = owner.getConfig (! isInput))
{
config->updateBusButtons();
config->updateBusLayout();
}
owner.update();
}
}
}
}
void columnSelected (int columnId) override
{
const int newBus = columnId - 1;
if (currentBus != newBus)
{
currentBus = newBus;
ioBuses.setSelected (currentBus + 1);
updateBusLayout();
}
}
//==============================================================================
IOConfigurationWindow& owner;
Label ioTitle, name;
Label nameLabel { "nameLabel", "Bus Name:" };
Label layoutLabel { "layoutLabel", "Channel Layout:" };
ToggleButton enabledToggle { "Enabled" };
ComboBox layouts;
NumberedBoxes ioBuses;
bool isInput;
int currentBus = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputOutputConfig)
};
IOConfigurationWindow::IOConfigurationWindow (AudioProcessor& p)
: AudioProcessorEditor (&p),
title ("title", p.getName())
{
setOpaque (true);
title.setFont (title.getFont().withStyle (Font::bold));
addAndMakeVisible (title);
{
ScopedLock renderLock (p.getCallbackLock());
p.suspendProcessing (true);
p.releaseResources();
}
if (p.getBusCount (true) > 0 || p.canAddBus (true))
{
inConfig.reset (new InputOutputConfig (*this, true));
addAndMakeVisible (inConfig.get());
}
if (p.getBusCount (false) > 0 || p.canAddBus (false))
{
outConfig.reset (new InputOutputConfig (*this, false));
addAndMakeVisible (outConfig.get());
}
currentLayout = p.getBusesLayout();
setSize (400, (inConfig != nullptr && outConfig != nullptr ? 160 : 0) + 200);
}
IOConfigurationWindow::~IOConfigurationWindow()
{
if (auto* graph = getGraph())
{
if (auto* p = getAudioProcessor())
{
ScopedLock renderLock (graph->getCallbackLock());
graph->suspendProcessing (true);
graph->releaseResources();
p->prepareToPlay (graph->getSampleRate(), graph->getBlockSize());
p->suspendProcessing (false);
graph->prepareToPlay (graph->getSampleRate(), graph->getBlockSize());
graph->suspendProcessing (false);
}
}
}
void IOConfigurationWindow::paint (Graphics& g)
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void IOConfigurationWindow::resized()
{
auto r = getLocalBounds().reduced (10);
title.setBounds (r.removeFromTop (14));
r.reduce (10, 0);
if (inConfig != nullptr)
inConfig->setBounds (r.removeFromTop (160));
if (outConfig != nullptr)
outConfig->setBounds (r.removeFromTop (160));
}
void IOConfigurationWindow::update()
{
auto nodeID = getNodeID();
if (auto* graph = getGraph())
if (nodeID != AudioProcessorGraph::NodeID())
graph->disconnectNode (nodeID);
if (auto* graphEditor = getGraphEditor())
if (auto* panel = graphEditor->graphPanel.get())
panel->updateComponents();
}
AudioProcessorGraph::NodeID IOConfigurationWindow::getNodeID() const
{
if (auto* graph = getGraph())
for (auto* node : graph->getNodes())
if (node->getProcessor() == getAudioProcessor())
return node->nodeID;
return {};
}
MainHostWindow* IOConfigurationWindow::getMainWindow() const
{
auto& desktop = Desktop::getInstance();
for (int i = desktop.getNumComponents(); --i >= 0;)
if (auto* mainWindow = dynamic_cast<MainHostWindow*> (desktop.getComponent (i)))
return mainWindow;
return nullptr;
}
GraphDocumentComponent* IOConfigurationWindow::getGraphEditor() const
{
if (auto* mainWindow = getMainWindow())
return mainWindow->graphHolder.get();
return nullptr;
}
AudioProcessorGraph* IOConfigurationWindow::getGraph() const
{
if (auto* graphEditor = getGraphEditor())
if (auto* panel = graphEditor->graph.get())
return &panel->graph;
return nullptr;
}
@@ -0,0 +1,68 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
class MainHostWindow;
class GraphDocumentComponent;
//==============================================================================
class IOConfigurationWindow final : public AudioProcessorEditor
{
public:
IOConfigurationWindow (AudioProcessor&);
~IOConfigurationWindow() override;
//==============================================================================
void paint (Graphics& g) override;
void resized() override;
private:
class InputOutputConfig;
AudioProcessor::BusesLayout currentLayout;
Label title;
std::unique_ptr<InputOutputConfig> inConfig, outConfig;
InputOutputConfig* getConfig (bool isInput) noexcept { return isInput ? inConfig.get() : outConfig.get(); }
void update();
MainHostWindow* getMainWindow() const;
GraphDocumentComponent* getGraphEditor() const;
AudioProcessorGraph* getGraph() const;
AudioProcessorGraph::NodeID getNodeID() const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IOConfigurationWindow)
};
@@ -0,0 +1,491 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#include <JuceHeader.h>
#include <juce_audio_plugin_client/juce_audio_plugin_client.h>
#include "InternalPlugins.h"
#include "PluginGraph.h"
#define PIP_DEMO_UTILITIES_INCLUDED 1
// An alternative version of createAssetInputStream from the demo utilities header
// that fetches resources from embedded binary data instead of files
static std::unique_ptr<InputStream> createAssetInputStream (const char* resourcePath)
{
for (int i = 0; i < BinaryData::namedResourceListSize; ++i)
{
if (String (BinaryData::originalFilenames[i]) == String (resourcePath))
{
int dataSizeInBytes;
auto* resource = BinaryData::getNamedResource (BinaryData::namedResourceList[i], dataSizeInBytes);
return std::make_unique<MemoryInputStream> (resource, dataSizeInBytes, false);
}
}
return {};
}
#include "../../../../examples/Plugins/AUv3SynthPluginDemo.h"
#include "../../../../examples/Plugins/ArpeggiatorPluginDemo.h"
#include "../../../../examples/Plugins/AudioPluginDemo.h"
#include "../../../../examples/Plugins/DSPModulePluginDemo.h"
#include "../../../../examples/Plugins/GainPluginDemo.h"
#include "../../../../examples/Plugins/MidiLoggerPluginDemo.h"
#include "../../../../examples/Plugins/MultiOutSynthPluginDemo.h"
#include "../../../../examples/Plugins/NoiseGatePluginDemo.h"
#include "../../../../examples/Plugins/SamplerPluginDemo.h"
#include "../../../../examples/Plugins/SurroundPluginDemo.h"
//==============================================================================
class InternalPlugin final : public AudioPluginInstance
{
public:
explicit InternalPlugin (std::unique_ptr<AudioProcessor> innerIn)
: inner (std::move (innerIn))
{
jassert (inner != nullptr);
for (auto isInput : { true, false })
matchChannels (isInput);
setBusesLayout (inner->getBusesLayout());
}
//==============================================================================
const String getName() const override { return inner->getName(); }
StringArray getAlternateDisplayNames() const override { return inner->getAlternateDisplayNames(); }
double getTailLengthSeconds() const override { return inner->getTailLengthSeconds(); }
bool acceptsMidi() const override { return inner->acceptsMidi(); }
bool producesMidi() const override { return inner->producesMidi(); }
AudioProcessorEditor* createEditor() override { return inner->createEditor(); }
bool hasEditor() const override { return inner->hasEditor(); }
int getNumPrograms() override { return inner->getNumPrograms(); }
int getCurrentProgram() override { return inner->getCurrentProgram(); }
void setCurrentProgram (int i) override { inner->setCurrentProgram (i); }
const String getProgramName (int i) override { return inner->getProgramName (i); }
void changeProgramName (int i, const String& n) override { inner->changeProgramName (i, n); }
void getStateInformation (juce::MemoryBlock& b) override { inner->getStateInformation (b); }
void setStateInformation (const void* d, int s) override { inner->setStateInformation (d, s); }
void getCurrentProgramStateInformation (juce::MemoryBlock& b) override { inner->getCurrentProgramStateInformation (b); }
void setCurrentProgramStateInformation (const void* d, int s) override { inner->setCurrentProgramStateInformation (d, s); }
void prepareToPlay (double sr, int bs) override { inner->setRateAndBufferSizeDetails (sr, bs); inner->prepareToPlay (sr, bs); }
void releaseResources() override { inner->releaseResources(); }
void memoryWarningReceived() override { inner->memoryWarningReceived(); }
void processBlock (AudioBuffer<float>& a, MidiBuffer& m) override { inner->processBlock (a, m); }
void processBlock (AudioBuffer<double>& a, MidiBuffer& m) override { inner->processBlock (a, m); }
void processBlockBypassed (AudioBuffer<float>& a, MidiBuffer& m) override { inner->processBlockBypassed (a, m); }
void processBlockBypassed (AudioBuffer<double>& a, MidiBuffer& m) override { inner->processBlockBypassed (a, m); }
bool supportsDoublePrecisionProcessing() const override { return inner->supportsDoublePrecisionProcessing(); }
bool supportsMPE() const override { return inner->supportsMPE(); }
bool isMidiEffect() const override { return inner->isMidiEffect(); }
void reset() override { inner->reset(); }
void setNonRealtime (bool b) noexcept override { inner->setNonRealtime (b); }
void refreshParameterList() override { inner->refreshParameterList(); }
void numChannelsChanged() override { inner->numChannelsChanged(); }
void numBusesChanged() override { inner->numBusesChanged(); }
void processorLayoutsChanged() override { inner->processorLayoutsChanged(); }
void setPlayHead (AudioPlayHead* p) override { inner->setPlayHead (p); }
void updateTrackProperties (const TrackProperties& p) override { inner->updateTrackProperties (p); }
bool isBusesLayoutSupported (const BusesLayout& layout) const override { return inner->checkBusesLayoutSupported (layout); }
bool applyBusLayouts (const BusesLayout& layouts) override { return inner->setBusesLayout (layouts) && AudioPluginInstance::applyBusLayouts (layouts); }
bool canAddBus (bool) const override { return true; }
bool canRemoveBus (bool) const override { return true; }
//==============================================================================
void fillInPluginDescription (PluginDescription& description) const override
{
description = getPluginDescription (*inner);
}
private:
static PluginDescription getPluginDescription (const AudioProcessor& proc)
{
const auto ins = proc.getTotalNumInputChannels();
const auto outs = proc.getTotalNumOutputChannels();
const auto identifier = proc.getName();
const auto registerAsGenerator = ins == 0;
const auto acceptsMidi = proc.acceptsMidi();
PluginDescription descr;
descr.name = identifier;
descr.descriptiveName = identifier;
descr.pluginFormatName = InternalPluginFormat::getIdentifier();
descr.category = (registerAsGenerator ? (acceptsMidi ? "Synth" : "Generator") : "Effect");
descr.manufacturerName = "JUCE";
descr.version = ProjectInfo::versionString;
descr.fileOrIdentifier = identifier;
descr.isInstrument = (acceptsMidi && registerAsGenerator);
descr.numInputChannels = ins;
descr.numOutputChannels = outs;
descr.uniqueId = descr.deprecatedUid = identifier.hashCode();
return descr;
}
void matchChannels (bool isInput)
{
const auto inBuses = inner->getBusCount (isInput);
while (getBusCount (isInput) < inBuses)
addBus (isInput);
while (inBuses < getBusCount (isInput))
removeBus (isInput);
}
std::unique_ptr<AudioProcessor> inner;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalPlugin)
};
//==============================================================================
class SineWaveSynth final : public AudioProcessor
{
public:
SineWaveSynth()
: AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo()))
{
const int numVoices = 8;
// Add some voices...
for (int i = numVoices; --i >= 0;)
synth.addVoice (new SineWaveVoice());
// ..and give the synth a sound to play
synth.addSound (new SineWaveSound());
}
static String getIdentifier()
{
return "Sine Wave Synth";
}
//==============================================================================
void prepareToPlay (double newSampleRate, int) override
{
synth.setCurrentPlaybackSampleRate (newSampleRate);
}
void releaseResources() override {}
//==============================================================================
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
const int numSamples = buffer.getNumSamples();
buffer.clear();
synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
buffer.applyGain (0.8f);
}
using AudioProcessor::processBlock;
const String getName() const override { return getIdentifier(); }
double getTailLengthSeconds() const override { return 0.0; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return true; }
AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return {}; }
void changeProgramName (int, const String&) override {}
void getStateInformation (juce::MemoryBlock&) override {}
void setStateInformation (const void*, int) override {}
private:
//==============================================================================
struct SineWaveSound final : public SynthesiserSound
{
SineWaveSound() = default;
bool appliesToNote (int /*midiNoteNumber*/) override { return true; }
bool appliesToChannel (int /*midiChannel*/) override { return true; }
};
struct SineWaveVoice final : public SynthesiserVoice
{
SineWaveVoice() = default;
bool canPlaySound (SynthesiserSound* sound) override
{
return dynamic_cast<SineWaveSound*> (sound) != nullptr;
}
void startNote (int midiNoteNumber, float velocity,
SynthesiserSound* /*sound*/,
int /*currentPitchWheelPosition*/) override
{
currentAngle = 0.0;
level = velocity * 0.15;
tailOff = 0.0;
double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
double cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
}
void stopNote (float /*velocity*/, bool allowTailOff) override
{
if (allowTailOff)
{
// start a tail-off by setting this flag. The render callback will pick up on
// this and do a fade out, calling clearCurrentNote() when it's finished.
if (approximatelyEqual (tailOff, 0.0)) // we only need to begin a tail-off if it's not already doing so - the
// stopNote method could be called more than once.
tailOff = 1.0;
}
else
{
// we're being told to stop playing immediately, so reset everything..
clearCurrentNote();
angleDelta = 0.0;
}
}
void pitchWheelMoved (int /*newValue*/) override
{
// not implemented for the purposes of this demo!
}
void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
{
// not implemented for the purposes of this demo!
}
void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
{
if (! approximatelyEqual (angleDelta, 0.0))
{
if (tailOff > 0)
{
while (--numSamples >= 0)
{
const float currentSample = (float) (sin (currentAngle) * level * tailOff);
for (int i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
tailOff *= 0.99;
if (tailOff <= 0.005)
{
// tells the synth that this voice has stopped
clearCurrentNote();
angleDelta = 0.0;
break;
}
}
}
else
{
while (--numSamples >= 0)
{
const float currentSample = (float) (sin (currentAngle) * level);
for (int i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
}
}
}
}
using SynthesiserVoice::renderNextBlock;
private:
double currentAngle = 0, angleDelta = 0, level = 0, tailOff = 0;
};
//==============================================================================
Synthesiser synth;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SineWaveSynth)
};
//==============================================================================
class ReverbPlugin final : public AudioProcessor
{
public:
ReverbPlugin()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{}
static String getIdentifier()
{
return "Reverb";
}
void prepareToPlay (double newSampleRate, int) override
{
reverb.setSampleRate (newSampleRate);
}
void reset() override
{
reverb.reset();
}
void releaseResources() override {}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
auto numChannels = buffer.getNumChannels();
if (numChannels == 1)
reverb.processMono (buffer.getWritePointer (0), buffer.getNumSamples());
else
reverb.processStereo (buffer.getWritePointer (0),
buffer.getWritePointer (1),
buffer.getNumSamples());
for (int ch = 2; ch < numChannels; ++ch)
buffer.clear (ch, 0, buffer.getNumSamples());
}
using AudioProcessor::processBlock;
const String getName() const override { return getIdentifier(); }
double getTailLengthSeconds() const override { return 0.0; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return {}; }
void changeProgramName (int, const String&) override {}
void getStateInformation (juce::MemoryBlock&) override {}
void setStateInformation (const void*, int) override {}
private:
Reverb reverb;
};
//==============================================================================
InternalPluginFormat::InternalPluginFactory::InternalPluginFactory (const std::initializer_list<Constructor>& constructorsIn)
: constructors (constructorsIn),
descriptions ([&]
{
std::vector<PluginDescription> result;
for (const auto& constructor : constructors)
result.push_back (constructor()->getPluginDescription());
return result;
}())
{}
std::unique_ptr<AudioPluginInstance> InternalPluginFormat::InternalPluginFactory::createInstance (const String& name) const
{
const auto begin = descriptions.begin();
const auto it = std::find_if (begin,
descriptions.end(),
[&] (const PluginDescription& desc) { return name.equalsIgnoreCase (desc.name); });
if (it == descriptions.end())
return nullptr;
const auto index = (size_t) std::distance (begin, it);
return constructors[index]();
}
InternalPluginFormat::InternalPluginFormat()
: factory {
[] { return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor> (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); },
[] { return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor> (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); },
[] { return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor> (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); },
[] { return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor> (AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<SineWaveSynth>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<ReverbPlugin>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<AUv3SynthProcessor>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<Arpeggiator>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<DspModulePluginDemoAudioProcessor>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<GainProcessor>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<JuceDemoPluginAudioProcessor>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<MidiLoggerPluginDemoProcessor>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<MultiOutSynth>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<NoiseGate>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<SamplerAudioProcessor>()); },
[] { return std::make_unique<InternalPlugin> (std::make_unique<SurroundProcessor>()); }
}
{
}
std::unique_ptr<AudioPluginInstance> InternalPluginFormat::createInstance (const String& name)
{
return factory.createInstance (name);
}
void InternalPluginFormat::createPluginInstance (const PluginDescription& desc,
double /*initialSampleRate*/, int /*initialBufferSize*/,
PluginCreationCallback callback)
{
if (auto p = createInstance (desc.name))
callback (std::move (p), {});
else
callback (nullptr, NEEDS_TRANS ("Invalid internal plugin name"));
}
bool InternalPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const
{
return false;
}
const std::vector<PluginDescription>& InternalPluginFormat::getAllTypes() const
{
return factory.getDescriptions();
}
@@ -0,0 +1,93 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
#include "PluginGraph.h"
//==============================================================================
/**
Manages the internal plugin types.
*/
class InternalPluginFormat final : public AudioPluginFormat
{
public:
//==============================================================================
InternalPluginFormat();
//==============================================================================
const std::vector<PluginDescription>& getAllTypes() const;
//==============================================================================
static String getIdentifier() { return "Internal"; }
String getName() const override { return getIdentifier(); }
bool fileMightContainThisPluginType (const String&) override { return true; }
FileSearchPath getDefaultLocationsToSearch() override { return {}; }
bool canScanForPlugins() const override { return false; }
bool isTrivialToScan() const override { return true; }
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String&) override {}
bool doesPluginStillExist (const PluginDescription&) override { return true; }
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override { return fileOrIdentifier; }
bool pluginNeedsRescanning (const PluginDescription&) override { return false; }
StringArray searchPathsForPlugins (const FileSearchPath&, bool, bool) override { return {}; }
private:
class InternalPluginFactory
{
public:
using Constructor = std::function<std::unique_ptr<AudioPluginInstance>()>;
explicit InternalPluginFactory (const std::initializer_list<Constructor>& constructorsIn);
const std::vector<PluginDescription>& getDescriptions() const { return descriptions; }
std::unique_ptr<AudioPluginInstance> createInstance (const String& name) const;
private:
const std::vector<Constructor> constructors;
const std::vector<PluginDescription> descriptions;
};
//==============================================================================
void createPluginInstance (const PluginDescription&,
double initialSampleRate, int initialBufferSize,
PluginCreationCallback) override;
std::unique_ptr<AudioPluginInstance> createInstance (const String& name);
bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const override;
InternalPluginFactory factory;
};
@@ -0,0 +1,547 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#include <JuceHeader.h>
#include "../UI/MainHostWindow.h"
#include "PluginGraph.h"
#include "InternalPlugins.h"
#include "../UI/GraphEditorPanel.h"
static std::unique_ptr<ScopedDPIAwarenessDisabler> makeDPIAwarenessDisablerForPlugin (const PluginDescription& desc)
{
return shouldAutoScalePlugin (desc) ? std::make_unique<ScopedDPIAwarenessDisabler>()
: nullptr;
}
//==============================================================================
PluginGraph::PluginGraph (AudioPluginFormatManager& fm, KnownPluginList& kpl)
: FileBasedDocument (getFilenameSuffix(),
getFilenameWildcard(),
"Load a graph",
"Save a graph"),
formatManager (fm),
knownPlugins (kpl)
{
newDocument();
graph.addListener (this);
}
PluginGraph::~PluginGraph()
{
graph.removeListener (this);
graph.removeChangeListener (this);
graph.clear();
}
PluginGraph::NodeID PluginGraph::getNextUID() noexcept
{
return PluginGraph::NodeID (++(lastUID.uid));
}
//==============================================================================
void PluginGraph::changeListenerCallback (ChangeBroadcaster*)
{
changed();
for (int i = activePluginWindows.size(); --i >= 0;)
if (! graph.getNodes().contains (activePluginWindows.getUnchecked (i)->node))
activePluginWindows.remove (i);
}
AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name) const
{
for (auto* node : graph.getNodes())
if (auto p = node->getProcessor())
if (p->getName().equalsIgnoreCase (name))
return node;
return nullptr;
}
void PluginGraph::addPlugin (const PluginDescriptionAndPreference& desc, Point<double> pos)
{
std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc.pluginDescription);
formatManager.createPluginInstanceAsync (desc.pluginDescription,
graph.getSampleRate(),
graph.getBlockSize(),
[this, pos, dpiDisabler, useARA = desc.useARA] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
{
addPluginCallback (std::move (instance), error, pos, useARA);
});
}
void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instance,
const String& error,
Point<double> pos,
PluginDescriptionAndPreference::UseARA useARA)
{
if (instance == nullptr)
{
auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
TRANS ("Couldn't create plugin"),
error);
messageBox = AlertWindow::showScopedAsync (options, nullptr);
}
else
{
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
if (useARA == PluginDescriptionAndPreference::UseARA::yes
&& instance->getPluginDescription().hasARAExtension)
{
instance = std::make_unique<ARAPluginInstanceWrapper> (std::move (instance));
}
#endif
instance->enableAllBuses();
if (auto node = graph.addNode (std::move (instance)))
{
node->properties.set ("x", pos.x);
node->properties.set ("y", pos.y);
node->properties.set ("useARA", useARA == PluginDescriptionAndPreference::UseARA::yes);
changed();
}
}
}
void PluginGraph::setNodePosition (NodeID nodeID, Point<double> pos)
{
if (auto* n = graph.getNodeForId (nodeID))
{
n->properties.set ("x", jlimit (0.0, 1.0, pos.x));
n->properties.set ("y", jlimit (0.0, 1.0, pos.y));
}
}
Point<double> PluginGraph::getNodePosition (NodeID nodeID) const
{
if (auto* n = graph.getNodeForId (nodeID))
return { static_cast<double> (n->properties ["x"]),
static_cast<double> (n->properties ["y"]) };
return {};
}
//==============================================================================
void PluginGraph::clear()
{
closeAnyOpenPluginWindows();
graph.clear();
changed();
}
PluginWindow* PluginGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node, PluginWindow::Type type)
{
jassert (node != nullptr);
#if JUCE_IOS || JUCE_ANDROID
closeAnyOpenPluginWindows();
#else
for (auto* w : activePluginWindows)
if (w->node.get() == node && w->type == type)
return w;
#endif
if (auto* processor = node->getProcessor())
{
if (auto* plugin = dynamic_cast<AudioPluginInstance*> (processor))
{
auto description = plugin->getPluginDescription();
if (! plugin->hasEditor() && description.pluginFormatName == "Internal")
{
getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false);
return nullptr;
}
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description);
return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows));
}
}
return nullptr;
}
bool PluginGraph::closeAnyOpenPluginWindows()
{
bool wasEmpty = activePluginWindows.isEmpty();
activePluginWindows.clear();
return ! wasEmpty;
}
//==============================================================================
String PluginGraph::getDocumentTitle()
{
if (! getFile().exists())
return "Unnamed";
return getFile().getFileNameWithoutExtension();
}
void PluginGraph::newDocument()
{
clear();
setFile ({});
graph.removeChangeListener (this);
InternalPluginFormat internalFormat;
jassert (internalFormat.getAllTypes().size() > 3);
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[0] }, { 0.5, 0.1 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[1] }, { 0.25, 0.1 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[2] }, { 0.5, 0.9 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[3] }, { 0.25, 0.9 });
MessageManager::callAsync ([this]
{
setChangedFlag (false);
graph.addChangeListener (this);
});
}
Result PluginGraph::loadDocument (const File& file)
{
if (auto xml = parseXMLIfTagMatches (file, "FILTERGRAPH"))
{
graph.removeChangeListener (this);
restoreFromXml (*xml);
MessageManager::callAsync ([this]
{
setChangedFlag (false);
graph.addChangeListener (this);
});
return Result::ok();
}
return Result::fail ("Not a valid graph file");
}
Result PluginGraph::saveDocument (const File& file)
{
auto xml = createXml();
if (! xml->writeTo (file, {}))
return Result::fail ("Couldn't write to the file");
return Result::ok();
}
File PluginGraph::getLastDocumentOpened()
{
RecentlyOpenedFilesList recentFiles;
recentFiles.restoreFromString (getAppProperties().getUserSettings()
->getValue ("recentFilterGraphFiles"));
return recentFiles.getFile (0);
}
void PluginGraph::setLastDocumentOpened (const File& file)
{
RecentlyOpenedFilesList recentFiles;
recentFiles.restoreFromString (getAppProperties().getUserSettings()
->getValue ("recentFilterGraphFiles"));
recentFiles.addFile (file);
getAppProperties().getUserSettings()
->setValue ("recentFilterGraphFiles", recentFiles.toString());
}
//==============================================================================
static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, AudioProcessor& plugin,
const XmlElement& xml, bool isInput)
{
auto& targetBuses = (isInput ? busesLayout.inputBuses
: busesLayout.outputBuses);
int maxNumBuses = 0;
if (auto* buses = xml.getChildByName (isInput ? "INPUTS" : "OUTPUTS"))
{
for (auto* e : buses->getChildWithTagNameIterator ("BUS"))
{
const int busIdx = e->getIntAttribute ("index");
maxNumBuses = jmax (maxNumBuses, busIdx + 1);
// 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;
for (int actualIdx = targetBuses.size() - 1; actualIdx < busIdx; ++actualIdx)
targetBuses.add (plugin.getChannelLayoutOfBus (isInput, busIdx));
auto layout = e->getStringAttribute ("layout");
if (layout.isNotEmpty())
targetBuses.getReference (busIdx) = AudioChannelSet::fromAbbreviatedString (layout);
}
}
// if the plugin has more buses than specified in the xml, then try to remove them!
while (maxNumBuses < targetBuses.size())
{
if (! plugin.removeBus (isInput))
return;
targetBuses.removeLast();
}
}
//==============================================================================
static XmlElement* createBusLayoutXml (const AudioProcessor::BusesLayout& layout, const bool isInput)
{
auto& buses = isInput ? layout.inputBuses
: layout.outputBuses;
auto* xml = new XmlElement (isInput ? "INPUTS" : "OUTPUTS");
for (int busIdx = 0; busIdx < buses.size(); ++busIdx)
{
auto& set = buses.getReference (busIdx);
auto* bus = xml->createNewChildElement ("BUS");
bus->setAttribute ("index", busIdx);
bus->setAttribute ("layout", set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString());
}
return xml;
}
static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcept
{
if (auto* plugin = dynamic_cast<AudioPluginInstance*> (node->getProcessor()))
{
auto e = new XmlElement ("FILTER");
e->setAttribute ("uid", (int) node->nodeID.uid);
e->setAttribute ("x", node->properties ["x"].toString());
e->setAttribute ("y", node->properties ["y"].toString());
e->setAttribute ("useARA", node->properties ["useARA"].toString());
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
{
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().release());
}
{
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;
}
jassertfalse;
return nullptr;
}
void PluginGraph::createNodeFromXml (const XmlElement& xml)
{
PluginDescriptionAndPreference pd;
const auto nodeUsesARA = xml.getBoolAttribute ("useARA");
for (auto* e : xml.getChildIterator())
{
if (pd.pluginDescription.loadFromXml (*e))
{
pd.useARA = nodeUsesARA ? PluginDescriptionAndPreference::UseARA::yes
: PluginDescriptionAndPreference::UseARA::no;
break;
}
}
auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance>
{
auto createInstance = [this] (const PluginDescriptionAndPreference& description) -> std::unique_ptr<AudioPluginInstance>
{
String errorMessage;
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.pluginDescription);
auto instance = formatManager.createPluginInstance (description.pluginDescription,
graph.getSampleRate(),
graph.getBlockSize(),
errorMessage);
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
if (instance
&& description.useARA == PluginDescriptionAndPreference::UseARA::yes
&& description.pluginDescription.hasARAExtension)
{
return std::make_unique<ARAPluginInstanceWrapper> (std::move (instance));
}
#endif
return instance;
};
if (auto instance = createInstance (pd))
return instance;
const auto allFormats = formatManager.getFormats();
const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(),
[&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginDescription.pluginFormatName; });
if (matchingFormat == allFormats.end())
return nullptr;
const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat);
const auto matchingPlugin = std::find_if (plugins.begin(), plugins.end(),
[&] (const PluginDescription& desc) { return pd.pluginDescription.uniqueId == desc.uniqueId; });
if (matchingPlugin == plugins.end())
return nullptr;
return createInstance (PluginDescriptionAndPreference { *matchingPlugin });
};
if (auto instance = createInstanceWithFallback())
{
if (auto* layoutEntity = xml.getChildByName ("LAYOUT"))
{
auto layout = instance->getBusesLayout();
readBusLayoutFromXml (layout, *instance, *layoutEntity, true);
readBusLayoutFromXml (layout, *instance, *layoutEntity, false);
instance->setBusesLayout (layout);
}
if (auto node = graph.addNode (std::move (instance), NodeID ((uint32) xml.getIntAttribute ("uid"))))
{
if (auto* 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"));
node->properties.set ("useARA", xml.getBoolAttribute ("useARA"));
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);
}
}
}
}
}
}
std::unique_ptr<XmlElement> PluginGraph::createXml() const
{
auto xml = std::make_unique<XmlElement> ("FILTERGRAPH");
for (auto* node : graph.getNodes())
xml->addChildElement (createNodeXml (node));
for (auto& connection : graph.getConnections())
{
auto e = xml->createNewChildElement ("CONNECTION");
e->setAttribute ("srcFilter", (int) connection.source.nodeID.uid);
e->setAttribute ("srcChannel", connection.source.channelIndex);
e->setAttribute ("dstFilter", (int) connection.destination.nodeID.uid);
e->setAttribute ("dstChannel", connection.destination.channelIndex);
}
return xml;
}
void PluginGraph::restoreFromXml (const XmlElement& xml)
{
clear();
for (auto* e : xml.getChildWithTagNameIterator ("FILTER"))
{
createNodeFromXml (*e);
changed();
}
for (auto* e : xml.getChildWithTagNameIterator ("CONNECTION"))
{
graph.addConnection ({ { NodeID ((uint32) e->getIntAttribute ("srcFilter")), e->getIntAttribute ("srcChannel") },
{ NodeID ((uint32) e->getIntAttribute ("dstFilter")), e->getIntAttribute ("dstChannel") } });
}
graph.removeIllegalConnections();
}
File PluginGraph::getDefaultGraphDocumentOnMobile()
{
auto persistantStorageLocation = File::getSpecialLocation (File::userApplicationDataDirectory);
return persistantStorageLocation.getChildFile ("state.filtergraph");
}
@@ -0,0 +1,135 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../UI/PluginWindow.h"
//==============================================================================
/** A type that encapsulates a PluginDescription and some preferences regarding
how plugins of that description should be instantiated.
*/
struct PluginDescriptionAndPreference
{
enum class UseARA { no, yes };
PluginDescriptionAndPreference() = default;
explicit PluginDescriptionAndPreference (PluginDescription pd)
: pluginDescription (std::move (pd)),
useARA (pluginDescription.hasARAExtension ? PluginDescriptionAndPreference::UseARA::yes
: PluginDescriptionAndPreference::UseARA::no)
{}
PluginDescriptionAndPreference (PluginDescription pd, UseARA ara)
: pluginDescription (std::move (pd)), useARA (ara)
{}
PluginDescription pluginDescription;
UseARA useARA = UseARA::no;
};
//==============================================================================
/**
A collection of plugins and some connections between them.
*/
class PluginGraph final : public FileBasedDocument,
public AudioProcessorListener,
private ChangeListener
{
public:
//==============================================================================
PluginGraph (AudioPluginFormatManager&, KnownPluginList&);
~PluginGraph() override;
//==============================================================================
using NodeID = AudioProcessorGraph::NodeID;
void addPlugin (const PluginDescriptionAndPreference&, Point<double>);
AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const;
void setNodePosition (NodeID, Point<double>);
Point<double> getNodePosition (NodeID) const;
//==============================================================================
void clear();
PluginWindow* getOrCreateWindowFor (AudioProcessorGraph::Node*, PluginWindow::Type);
void closeCurrentlyOpenWindowsFor (AudioProcessorGraph::NodeID);
bool closeAnyOpenPluginWindows();
//==============================================================================
void audioProcessorParameterChanged (AudioProcessor*, int, float) override {}
void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override { changed(); }
//==============================================================================
std::unique_ptr<XmlElement> createXml() const;
void restoreFromXml (const XmlElement&);
static const char* getFilenameSuffix() { return ".filtergraph"; }
static const char* getFilenameWildcard() { return "*.filtergraph"; }
//==============================================================================
void newDocument();
String getDocumentTitle() override;
Result loadDocument (const File& file) override;
Result saveDocument (const File& file) override;
File getLastDocumentOpened() override;
void setLastDocumentOpened (const File& file) override;
static File getDefaultGraphDocumentOnMobile();
//==============================================================================
AudioProcessorGraph graph;
private:
//==============================================================================
AudioPluginFormatManager& formatManager;
KnownPluginList& knownPlugins;
OwnedArray<PluginWindow> activePluginWindows;
ScopedMessageBox messageBox;
NodeID lastUID;
NodeID getNextUID() noexcept;
void createNodeFromXml (const XmlElement&);
void addPluginCallback (std::unique_ptr<AudioPluginInstance>,
const String& error,
Point<double>,
PluginDescriptionAndPreference::UseARA useARA);
void changeListenerCallback (ChangeBroadcaster*) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginGraph)
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,181 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Plugins/PluginGraph.h"
class MainHostWindow;
//==============================================================================
/**
A panel that displays and edits a PluginGraph.
*/
class GraphEditorPanel final : public Component,
public ChangeListener,
private Timer
{
public:
//==============================================================================
GraphEditorPanel (PluginGraph& graph);
~GraphEditorPanel() override;
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
void paint (Graphics&) override;
void resized() override;
void mouseDown (const MouseEvent&) override;
void mouseUp (const MouseEvent&) override;
void mouseDrag (const MouseEvent&) override;
void changeListenerCallback (ChangeBroadcaster*) override;
//==============================================================================
void updateComponents();
//==============================================================================
void showPopupMenu (Point<int> position);
//==============================================================================
void beginConnectorDrag (AudioProcessorGraph::NodeAndChannel source,
AudioProcessorGraph::NodeAndChannel dest,
const MouseEvent&);
void dragConnector (const MouseEvent&);
void endDraggingConnector (const MouseEvent&);
//==============================================================================
PluginGraph& graph;
private:
struct PluginComponent;
struct ConnectorComponent;
struct PinComponent;
OwnedArray<PluginComponent> nodes;
OwnedArray<ConnectorComponent> connectors;
std::unique_ptr<ConnectorComponent> draggingConnector;
std::unique_ptr<PopupMenu> menu;
PluginComponent* getComponentForPlugin (AudioProcessorGraph::NodeID) const;
ConnectorComponent* getComponentForConnection (const AudioProcessorGraph::Connection&) const;
PinComponent* findPinAt (Point<float>) const;
//==============================================================================
Point<int> originalTouchPos;
void timerCallback() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphEditorPanel)
};
//==============================================================================
/**
A panel that embeds a GraphEditorPanel with a midi keyboard at the bottom.
It also manages the graph itself, and plays it.
*/
class GraphDocumentComponent final : public Component,
public DragAndDropTarget,
public DragAndDropContainer,
private ChangeListener
{
public:
GraphDocumentComponent (AudioPluginFormatManager& formatManager,
AudioDeviceManager& deviceManager,
KnownPluginList& pluginList);
~GraphDocumentComponent() override;
//==============================================================================
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
void setDoublePrecision (bool doublePrecision);
bool closeAnyOpenPluginWindows();
//==============================================================================
std::unique_ptr<PluginGraph> graph;
void resized() override;
void releaseGraph();
//==============================================================================
bool isInterestedInDragSource (const SourceDetails&) override;
void itemDropped (const SourceDetails&) override;
//==============================================================================
std::unique_ptr<GraphEditorPanel> graphPanel;
std::unique_ptr<MidiKeyboardComponent> keyboardComp;
//==============================================================================
void showSidePanel (bool isSettingsPanel);
void hideLastSidePanel();
BurgerMenuComponent burgerMenu;
private:
//==============================================================================
AudioDeviceManager& deviceManager;
KnownPluginList& pluginList;
AudioProcessorPlayer graphPlayer;
MidiKeyboardState keyState;
MidiOutput* midiOutput = nullptr;
struct TooltipBar;
std::unique_ptr<TooltipBar> statusBar;
class TitleBarComponent;
std::unique_ptr<TitleBarComponent> titleBarComponent;
//==============================================================================
struct PluginListBoxModel;
std::unique_ptr<PluginListBoxModel> pluginListBoxModel;
ListBox pluginListBox;
SidePanel mobileSettingsSidePanel { "Settings", 300, true };
SidePanel pluginListSidePanel { "Plugins", 250, false };
SidePanel* lastOpenedSidePanel = nullptr;
//==============================================================================
void changeListenerCallback (ChangeBroadcaster*) override;
void init();
void checkAvailableWidth();
void updateMidiOutput();
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphDocumentComponent)
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,149 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Plugins/PluginGraph.h"
#include "GraphEditorPanel.h"
//==============================================================================
namespace CommandIDs
{
#if ! (JUCE_IOS || JUCE_ANDROID)
static const int open = 0x30000;
static const int save = 0x30001;
static const int saveAs = 0x30002;
static const int newFile = 0x30003;
#endif
static const int showPluginListEditor = 0x30100;
static const int showAudioSettings = 0x30200;
static const int aboutBox = 0x30300;
static const int allWindowsForward = 0x30400;
static const int toggleDoublePrecision = 0x30500;
static const int autoScalePluginWindows = 0x30600;
}
//==============================================================================
ApplicationCommandManager& getCommandManager();
ApplicationProperties& getAppProperties();
bool isOnTouchDevice();
//==============================================================================
enum class AutoScale
{
scaled,
unscaled,
useDefault
};
constexpr bool autoScaleOptionAvailable =
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE
true;
#else
false;
#endif
AutoScale getAutoScaleValueForPlugin (const String&);
void setAutoScaleValueForPlugin (const String&, AutoScale);
bool shouldAutoScalePlugin (const PluginDescription&);
void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance*, PopupMenu&);
constexpr const char* processUID = "juceaudiopluginhost";
//==============================================================================
class MainHostWindow final : public DocumentWindow,
public MenuBarModel,
public ApplicationCommandTarget,
public ChangeListener,
public FileDragAndDropTarget
{
public:
//==============================================================================
MainHostWindow();
~MainHostWindow() override;
//==============================================================================
void closeButtonPressed() override;
void changeListenerCallback (ChangeBroadcaster*) override;
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) override;
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();
void createPlugin (const PluginDescriptionAndPreference&, Point<int> pos);
void addPluginsToMenu (PopupMenu&);
std::optional<PluginDescriptionAndPreference> getChosenType (int menuID) const;
std::unique_ptr<GraphDocumentComponent> graphHolder;
private:
//==============================================================================
static bool isDoublePrecisionProcessingEnabled();
static bool isAutoScalePluginWindowsEnabled();
static void updatePrecisionMenuItem (ApplicationCommandInfo& info);
static void updateAutoScaleMenuItem (ApplicationCommandInfo& info);
void showAudioSettings();
//==============================================================================
AudioDeviceManager deviceManager;
AudioPluginFormatManager formatManager;
std::vector<PluginDescription> internalTypes;
KnownPluginList knownPluginList;
KnownPluginList::SortMethod pluginSortMethod;
Array<PluginDescriptionAndPreference> pluginDescriptionsAndPreference;
class PluginListWindow;
std::unique_ptr<PluginListWindow> pluginListWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainHostWindow)
};
@@ -0,0 +1,418 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Plugins/IOConfigurationWindow.h"
#include "../Plugins/ARAPlugin.h"
inline String getFormatSuffix (const AudioProcessor* plugin)
{
const auto format = [plugin]()
{
if (auto* instance = dynamic_cast<const AudioPluginInstance*> (plugin))
return instance->getPluginDescription().pluginFormatName;
return String();
}();
return format.isNotEmpty() ? (" (" + format + ")") : format;
}
class PluginGraph;
/**
A window that shows a log of parameter change messages sent by the plugin.
*/
class PluginDebugWindow final : public AudioProcessorEditor,
public AudioProcessorParameter::Listener,
public ListBoxModel,
public AsyncUpdater
{
public:
PluginDebugWindow (AudioProcessor& proc)
: AudioProcessorEditor (proc), audioProc (proc)
{
setSize (500, 200);
addAndMakeVisible (list);
for (auto* p : audioProc.getParameters())
p->addListener (this);
log.add ("Parameter debug log started");
}
~PluginDebugWindow() override
{
for (auto* p : audioProc.getParameters())
p->removeListener (this);
}
void parameterValueChanged (int parameterIndex, float newValue) override
{
auto* param = audioProc.getParameters()[parameterIndex];
auto value = param->getCurrentValueAsText().quoted() + " (" + String (newValue, 4) + ")";
appendToLog ("parameter change", *param, value);
}
void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override
{
auto* param = audioProc.getParameters()[parameterIndex];
appendToLog ("gesture", *param, gestureIsStarting ? "start" : "end");
}
private:
void appendToLog (StringRef action, AudioProcessorParameter& param, StringRef value)
{
String entry (action + " " + param.getName (30).quoted() + " [" + String (param.getParameterIndex()) + "]: " + value);
{
ScopedLock lock (pendingLogLock);
pendingLogEntries.add (entry);
}
triggerAsyncUpdate();
}
void resized() override
{
list.setBounds (getLocalBounds());
}
int getNumRows() override
{
return log.size();
}
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool) override
{
g.setColour (getLookAndFeel().findColour (TextEditor::textColourId));
if (isPositiveAndBelow (rowNumber, log.size()))
g.drawText (log[rowNumber], Rectangle<int> { 0, 0, width, height }, Justification::left, true);
}
void handleAsyncUpdate() override
{
if (log.size() > logSizeTrimThreshold)
log.removeRange (0, log.size() - maxLogSize);
{
ScopedLock lock (pendingLogLock);
log.addArray (pendingLogEntries);
pendingLogEntries.clear();
}
list.updateContent();
list.scrollToEnsureRowIsOnscreen (log.size() - 1);
}
constexpr static const int maxLogSize = 300;
constexpr static const int logSizeTrimThreshold = 400;
ListBox list { "Log", this };
StringArray log;
StringArray pendingLogEntries;
CriticalSection pendingLogLock;
AudioProcessor& audioProc;
};
//==============================================================================
/**
A desktop window containing a plugin's GUI.
*/
class PluginWindow final : public DocumentWindow
{
public:
enum class Type
{
normal = 0,
generic,
programs,
audioIO,
debug,
araHost,
numTypes
};
PluginWindow (AudioProcessorGraph::Node* n, Type t, OwnedArray<PluginWindow>& windowList)
: DocumentWindow (n->getProcessor()->getName() + getFormatSuffix (n->getProcessor()),
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);
setResizable (ui->isResizable(), false);
}
setConstrainer (&constrainer);
#if JUCE_IOS || JUCE_ANDROID
const auto screenBounds = Desktop::getInstance().getDisplays().getTotalBounds (true).toFloat();
const auto scaleFactor = jmin ((screenBounds.getWidth() - 50.0f) / (float) getWidth(),
(screenBounds.getHeight() - 50.0f) / (float) getHeight());
if (scaleFactor < 1.0f)
{
setSize ((int) (scaleFactor * (float) getWidth()),
(int) (scaleFactor * (float) getHeight()));
}
setTopLeftPosition (20, 20);
#else
setTopLeftPosition (node->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)),
node->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500)));
#endif
node->properties.set (getOpenProp (type), true);
setVisible (true);
}
~PluginWindow() override
{
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;
BorderSize<int> getBorderThickness() const override
{
#if JUCE_IOS || JUCE_ANDROID
const int border = 10;
return { border, border, border, border };
#else
return DocumentWindow::getBorderThickness();
#endif
}
private:
class DecoratorConstrainer final : public BorderedComponentBoundsConstrainer
{
public:
explicit DecoratorConstrainer (DocumentWindow& windowIn)
: window (windowIn) {}
ComponentBoundsConstrainer* getWrappedConstrainer() const override
{
auto* editor = dynamic_cast<AudioProcessorEditor*> (window.getContentComponent());
return editor != nullptr ? editor->getConstrainer() : nullptr;
}
BorderSize<int> getAdditionalBorder() const override
{
const auto nativeFrame = [&]() -> BorderSize<int>
{
if (auto* peer = window.getPeer())
if (const auto frameSize = peer->getFrameSizeIfPresent())
return *frameSize;
return {};
}();
return nativeFrame.addedTo (window.getContentComponentBorder());
}
private:
DocumentWindow& window;
};
DecoratorConstrainer constrainer { *this };
float getDesktopScaleFactor() const override { return 1.0f; }
static AudioProcessorEditor* createProcessorEditor (AudioProcessor& processor,
PluginWindow::Type type)
{
if (type == PluginWindow::Type::normal)
{
if (processor.hasEditor())
if (auto* ui = processor.createEditorIfNeeded())
return ui;
type = PluginWindow::Type::generic;
}
if (type == PluginWindow::Type::araHost)
{
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
if (auto* araPluginInstanceWrapper = dynamic_cast<ARAPluginInstanceWrapper*> (&processor))
if (auto* ui = araPluginInstanceWrapper->createARAHostEditor())
return ui;
#endif
return {};
}
if (type == PluginWindow::Type::generic)
{
auto* result = new GenericAudioProcessorEditor (processor);
result->setResizeLimits (200, 300, 1'000, 10'000);
return result;
}
if (type == PluginWindow::Type::programs)
return new ProgramAudioProcessorEditor (processor);
if (type == PluginWindow::Type::audioIO)
return new IOConfigurationWindow (processor);
if (type == PluginWindow::Type::debug)
return new PluginDebugWindow (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";
case Type::debug: return "Debug";
case Type::araHost: return "ARAHost";
case Type::numTypes:
default: return {};
}
}
//==============================================================================
struct ProgramAudioProcessorEditor final : public AudioProcessorEditor
{
explicit ProgramAudioProcessorEditor (AudioProcessor& p)
: AudioProcessorEditor (p)
{
setOpaque (true);
addAndMakeVisible (listBox);
listBox.updateContent();
const auto rowHeight = listBox.getRowHeight();
setSize (400, jlimit (rowHeight, 400, p.getNumPrograms() * rowHeight));
}
void paint (Graphics& g) override
{
g.fillAll (Colours::grey);
}
void resized() override
{
listBox.setBounds (getLocalBounds());
}
private:
class Model : public ListBoxModel
{
public:
Model (Component& o, AudioProcessor& p)
: owner (o), proc (p) {}
int getNumRows() override
{
return proc.getNumPrograms();
}
void paintListBoxItem (int rowNumber,
Graphics& g,
int width,
int height,
bool rowIsSelected) override
{
const auto textColour = owner.findColour (ListBox::textColourId);
if (rowIsSelected)
{
const auto defaultColour = owner.findColour (ListBox::backgroundColourId);
const auto c = rowIsSelected ? defaultColour.interpolatedWith (textColour, 0.5f)
: defaultColour;
g.fillAll (c);
}
g.setColour (textColour);
g.drawText (proc.getProgramName (rowNumber),
Rectangle<int> { width, height }.reduced (2),
Justification::left,
true);
}
void selectedRowsChanged (int row) override
{
if (0 <= row)
proc.setCurrentProgram (row);
}
private:
Component& owner;
AudioProcessor& proc;
};
Model model { *this, *getAudioProcessor() };
ListBox listBox { "Programs", &model };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginWindow)
};