mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
ARA Host: Add support for scanning and hosting ARA plugins
This commit is contained in:
parent
d99fbccb66
commit
f36949c1b2
31 changed files with 3522 additions and 133 deletions
|
|
@ -69,10 +69,11 @@ juce_disable_default_flags()
|
|||
|
||||
add_subdirectory(extras/Build)
|
||||
|
||||
# If you want to build the JUCE examples with VST2/AAX support, you'll need to make the VST2/AAX
|
||||
# headers visible to the juce_audio_processors module. You can either set the paths on the command
|
||||
# line, (e.g. -DJUCE_GLOBAL_AAX_SDK_PATH=/path/to/sdk) if you're just building the JUCE examples, or
|
||||
# you can call the `juce_set_*_sdk_path` functions in your own CMakeLists after importing JUCE.
|
||||
# If you want to build the JUCE examples with VST2/AAX/ARA support, you'll need to make the
|
||||
# VST2/AAX/ARA headers visible to the juce_audio_processors module. You can either set the paths on
|
||||
# the command line, (e.g. -DJUCE_GLOBAL_AAX_SDK_PATH=/path/to/sdk) if you're just building the JUCE
|
||||
# examples, or you can call the `juce_set_*_sdk_path` functions in your own CMakeLists after
|
||||
# importing JUCE.
|
||||
|
||||
if(JUCE_GLOBAL_AAX_SDK_PATH)
|
||||
juce_set_aax_sdk_path("${JUCE_GLOBAL_AAX_SDK_PATH}")
|
||||
|
|
@ -82,6 +83,13 @@ if(JUCE_GLOBAL_VST2_SDK_PATH)
|
|||
juce_set_vst2_sdk_path("${JUCE_GLOBAL_VST2_SDK_PATH}")
|
||||
endif()
|
||||
|
||||
# The ARA_SDK path should point to the "Umbrella installer" ARA_SDK directory.
|
||||
# The directory can be obtained by recursively cloning https://github.com/Celemony/ARA_SDK and
|
||||
# checking out the tag releases/2.1.0.
|
||||
if(JUCE_GLOBAL_ARA_SDK_PATH)
|
||||
juce_set_ara_sdk_path("${JUCE_GLOBAL_ARA_SDK_PATH}")
|
||||
endif()
|
||||
|
||||
# We don't build anything other than the juceaide by default, because we want to keep configuration
|
||||
# speedy and the number of targets low. If you want to add targets for the extra projects and
|
||||
# example PIPs (there's a lot of them!), specify -DJUCE_BUILD_EXAMPLES=ON and/or
|
||||
|
|
|
|||
|
|
@ -664,10 +664,11 @@ target!).
|
|||
juce_set_aax_sdk_path(<absolute path>)
|
||||
juce_set_vst2_sdk_path(<absolute path>)
|
||||
juce_set_vst3_sdk_path(<absolute path>)
|
||||
juce_set_ara_sdk_path(<absolute path>)
|
||||
|
||||
Call these functions from your CMakeLists to set up your local AAX, VST2, and VST3 SDKs. These
|
||||
functions should be called *before* adding any targets that may depend on the AAX/VST2/VST3 SDKs
|
||||
(plugin hosts, AAX/VST2/VST3 plugins etc.).
|
||||
Call these functions from your CMakeLists to set up your local AAX, VST2, VST3 and ARA SDKs. These
|
||||
functions should be called *before* adding any targets that may depend on the AAX/VST2/VST3/ARA SDKs
|
||||
(plugin hosts, AAX/VST2/VST3/ARA plugins etc.).
|
||||
|
||||
#### `juce_add_module`
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ juce_generate_juce_header(AudioPluginHost)
|
|||
|
||||
target_sources(AudioPluginHost PRIVATE
|
||||
Source/HostStartup.cpp
|
||||
Source/Plugins/ARAPlugin.cpp
|
||||
Source/Plugins/IOConfigurationWindow.cpp
|
||||
Source/Plugins/InternalPlugins.cpp
|
||||
Source/Plugins/PluginGraph.cpp
|
||||
|
|
@ -46,6 +47,7 @@ target_compile_definitions(AudioPluginHost PRIVATE
|
|||
JUCE_PLUGINHOST_LV2=1
|
||||
JUCE_PLUGINHOST_VST3=1
|
||||
JUCE_PLUGINHOST_VST=0
|
||||
JUCE_PLUGINHOST_ARA=0
|
||||
JUCE_USE_CAMERA=0
|
||||
JUCE_USE_CDBURNER=0
|
||||
JUCE_USE_CDREADER=0
|
||||
|
|
|
|||
26
extras/AudioPluginHost/Source/Plugins/ARAPlugin.cpp
Normal file
26
extras/AudioPluginHost/Source/Plugins/ARAPlugin.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE 7 technical preview.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
You may use this code under the terms of the GPL v3
|
||||
(see www.gnu.org/licenses).
|
||||
|
||||
For the technical preview this file cannot be licensed commercially.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "ARAPlugin.h"
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
|
||||
const Identifier ARAPluginInstanceWrapper::ARATestHost::Context::xmlRootTag { "ARATestHostContext" };
|
||||
const Identifier ARAPluginInstanceWrapper::ARATestHost::Context::xmlAudioFileAttrib { "AudioFile" };
|
||||
|
||||
#endif
|
||||
1397
extras/AudioPluginHost/Source/Plugins/ARAPlugin.h
Normal file
1397
extras/AudioPluginHost/Source/Plugins/ARAPlugin.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -73,21 +73,23 @@ AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void PluginGraph::addPlugin (const PluginDescription& desc, Point<double> pos)
|
||||
void PluginGraph::addPlugin (const PluginDescriptionAndPreference& desc, Point<double> pos)
|
||||
{
|
||||
std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc);
|
||||
std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc.pluginDescription);
|
||||
|
||||
formatManager.createPluginInstanceAsync (desc,
|
||||
formatManager.createPluginInstanceAsync (desc.pluginDescription,
|
||||
graph.getSampleRate(),
|
||||
graph.getBlockSize(),
|
||||
[this, pos, dpiDisabler] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
|
||||
[this, pos, dpiDisabler, useARA = desc.useARA] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
|
||||
{
|
||||
addPluginCallback (std::move (instance), error, pos);
|
||||
addPluginCallback (std::move (instance), error, pos, useARA);
|
||||
});
|
||||
}
|
||||
|
||||
void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instance,
|
||||
const String& error, Point<double> pos)
|
||||
const String& error,
|
||||
Point<double> pos,
|
||||
PluginDescriptionAndPreference::UseARA useARA)
|
||||
{
|
||||
if (instance == nullptr)
|
||||
{
|
||||
|
|
@ -97,12 +99,21 @@ void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instan
|
|||
}
|
||||
else
|
||||
{
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -193,10 +204,10 @@ void PluginGraph::newDocument()
|
|||
|
||||
jassert (internalFormat.getAllTypes().size() > 3);
|
||||
|
||||
addPlugin (internalFormat.getAllTypes()[0], { 0.5, 0.1 });
|
||||
addPlugin (internalFormat.getAllTypes()[1], { 0.25, 0.1 });
|
||||
addPlugin (internalFormat.getAllTypes()[2], { 0.5, 0.9 });
|
||||
addPlugin (internalFormat.getAllTypes()[3], { 0.25, 0.9 });
|
||||
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]
|
||||
{
|
||||
|
|
@ -325,6 +336,7 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep
|
|||
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)
|
||||
{
|
||||
|
|
@ -365,26 +377,42 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep
|
|||
|
||||
void PluginGraph::createNodeFromXml (const XmlElement& xml)
|
||||
{
|
||||
PluginDescription pd;
|
||||
PluginDescriptionAndPreference pd;
|
||||
const auto nodeUsesARA = xml.getBoolAttribute ("useARA");
|
||||
|
||||
for (auto* e : xml.getChildIterator())
|
||||
{
|
||||
if (pd.loadFromXml (*e))
|
||||
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 PluginDescription& description)
|
||||
auto createInstance = [this] (const PluginDescriptionAndPreference& description) -> std::unique_ptr<AudioPluginInstance>
|
||||
{
|
||||
String errorMessage;
|
||||
|
||||
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description);
|
||||
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.pluginDescription);
|
||||
|
||||
return formatManager.createPluginInstance (description,
|
||||
auto instance = formatManager.createPluginInstance (description.pluginDescription,
|
||||
graph.getSampleRate(),
|
||||
graph.getBlockSize(),
|
||||
errorMessage);
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
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))
|
||||
|
|
@ -392,19 +420,19 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml)
|
|||
|
||||
const auto allFormats = formatManager.getFormats();
|
||||
const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(),
|
||||
[&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginFormatName; });
|
||||
[&] (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.uniqueId == desc.uniqueId; });
|
||||
[&] (const PluginDescription& desc) { return pd.pluginDescription.uniqueId == desc.uniqueId; });
|
||||
|
||||
if (matchingPlugin == plugins.end())
|
||||
return nullptr;
|
||||
|
||||
return createInstance (*matchingPlugin);
|
||||
return createInstance (PluginDescriptionAndPreference { *matchingPlugin });
|
||||
};
|
||||
|
||||
if (auto instance = createInstanceWithFallback())
|
||||
|
|
@ -431,6 +459,7 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml)
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,29 @@
|
|||
|
||||
#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;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
|
@ -37,7 +60,7 @@ public:
|
|||
//==============================================================================
|
||||
using NodeID = AudioProcessorGraph::NodeID;
|
||||
|
||||
void addPlugin (const PluginDescription&, Point<double>);
|
||||
void addPlugin (const PluginDescriptionAndPreference&, Point<double>);
|
||||
|
||||
AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const;
|
||||
|
||||
|
|
@ -85,7 +108,10 @@ private:
|
|||
NodeID getNextUID() noexcept;
|
||||
|
||||
void createNodeFromXml (const XmlElement&);
|
||||
void addPluginCallback (std::unique_ptr<AudioPluginInstance>, const String& error, Point<double>);
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -391,6 +391,14 @@ struct GraphEditorPanel::PluginComponent : public Component,
|
|||
return {};
|
||||
}
|
||||
|
||||
bool isNodeUsingARA() const
|
||||
{
|
||||
if (auto node = graph.graph.getNodeForId (pluginID))
|
||||
return node->properties["useARA"];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void showPopupMenu()
|
||||
{
|
||||
menu.reset (new PopupMenu);
|
||||
|
|
@ -412,6 +420,12 @@ struct GraphEditorPanel::PluginComponent : public Component,
|
|||
menu->addItem ("Show all parameters", [this] { showWindow (PluginWindow::Type::generic); });
|
||||
menu->addItem ("Show debug log", [this] { showWindow (PluginWindow::Type::debug); });
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
if (auto* instance = dynamic_cast<AudioPluginInstance*> (getProcessor()))
|
||||
if (instance->getPluginDescription().hasARAExtension && isNodeUsingARA())
|
||||
menu->addItem ("Show ARA host controls", [this] { showWindow (PluginWindow::Type::araHost); });
|
||||
#endif
|
||||
|
||||
if (autoScaleOptionAvailable)
|
||||
addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu);
|
||||
|
||||
|
|
@ -777,7 +791,7 @@ void GraphEditorPanel::mouseDrag (const MouseEvent& e)
|
|||
stopTimer();
|
||||
}
|
||||
|
||||
void GraphEditorPanel::createNewPlugin (const PluginDescription& desc, Point<int> position)
|
||||
void GraphEditorPanel::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> position)
|
||||
{
|
||||
graph.addPlugin (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight()));
|
||||
}
|
||||
|
|
@ -1273,7 +1287,7 @@ void GraphDocumentComponent::resized()
|
|||
checkAvailableWidth();
|
||||
}
|
||||
|
||||
void GraphDocumentComponent::createNewPlugin (const PluginDescription& desc, Point<int> pos)
|
||||
void GraphDocumentComponent::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos)
|
||||
{
|
||||
graphPanel->createNewPlugin (desc, pos);
|
||||
}
|
||||
|
|
@ -1320,7 +1334,8 @@ void GraphDocumentComponent::itemDropped (const SourceDetails& details)
|
|||
// must be a valid index!
|
||||
jassert (isPositiveAndBelow (pluginTypeIndex, pluginList.getNumTypes()));
|
||||
|
||||
createNewPlugin (pluginList.getTypes()[pluginTypeIndex], details.localPosition);
|
||||
createNewPlugin (PluginDescriptionAndPreference { pluginList.getTypes()[pluginTypeIndex] },
|
||||
details.localPosition);
|
||||
}
|
||||
|
||||
void GraphDocumentComponent::showSidePanel (bool showSettingsPanel)
|
||||
|
|
|
|||
|
|
@ -31,10 +31,11 @@ class GraphEditorPanel : public Component,
|
|||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
GraphEditorPanel (PluginGraph& graph);
|
||||
~GraphEditorPanel() override;
|
||||
|
||||
void createNewPlugin (const PluginDescription&, Point<int> position);
|
||||
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
|
||||
|
||||
void paint (Graphics&) override;
|
||||
void resized() override;
|
||||
|
|
@ -103,7 +104,7 @@ public:
|
|||
~GraphDocumentComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
void createNewPlugin (const PluginDescription&, Point<int> position);
|
||||
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
|
||||
void setDoublePrecision (bool doublePrecision);
|
||||
bool closeAnyOpenPluginWindows();
|
||||
|
||||
|
|
|
|||
|
|
@ -576,7 +576,7 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/
|
|||
}
|
||||
else
|
||||
{
|
||||
if (KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuItemID) >= 0)
|
||||
if (getIndexChosenByMenu (menuItemID) >= 0)
|
||||
createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
|
||||
proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) });
|
||||
}
|
||||
|
|
@ -588,12 +588,64 @@ void MainHostWindow::menuBarActivated (bool isActivated)
|
|||
graphHolder->unfocusKeyboardComponent();
|
||||
}
|
||||
|
||||
void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos)
|
||||
void MainHostWindow::createPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos)
|
||||
{
|
||||
if (graphHolder != nullptr)
|
||||
graphHolder->createNewPlugin (desc, pos);
|
||||
}
|
||||
|
||||
static bool containsDuplicateNames (const Array<PluginDescription>& plugins, const String& name)
|
||||
{
|
||||
int matches = 0;
|
||||
|
||||
for (auto& p : plugins)
|
||||
if (p.name == name && ++matches > 1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr int menuIDBase = 0x324503f4;
|
||||
|
||||
static void addToMenu (const KnownPluginList::PluginTree& tree,
|
||||
PopupMenu& m,
|
||||
const Array<PluginDescription>& allPlugins,
|
||||
Array<PluginDescriptionAndPreference>& addedPlugins)
|
||||
{
|
||||
for (auto* sub : tree.subFolders)
|
||||
{
|
||||
PopupMenu subMenu;
|
||||
addToMenu (*sub, subMenu, allPlugins, addedPlugins);
|
||||
|
||||
m.addSubMenu (sub->folder, subMenu, true, nullptr, false, 0);
|
||||
}
|
||||
|
||||
auto addPlugin = [&] (const auto& descriptionAndPreference, const auto& pluginName)
|
||||
{
|
||||
addedPlugins.add (descriptionAndPreference);
|
||||
const auto menuID = addedPlugins.size() - 1 + menuIDBase;
|
||||
m.addItem (menuID, pluginName, true, false);
|
||||
};
|
||||
|
||||
for (auto& plugin : tree.plugins)
|
||||
{
|
||||
auto name = plugin.name;
|
||||
|
||||
if (containsDuplicateNames (tree.plugins, name))
|
||||
name << " (" << plugin.pluginFormatName << ')';
|
||||
|
||||
addPlugin (PluginDescriptionAndPreference { plugin, PluginDescriptionAndPreference::UseARA::no }, name);
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
if (plugin.hasARAExtension)
|
||||
{
|
||||
name << " (ARA)";
|
||||
addPlugin (PluginDescriptionAndPreference { plugin }, name);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void MainHostWindow::addPluginsToMenu (PopupMenu& m)
|
||||
{
|
||||
if (graphHolder != nullptr)
|
||||
|
|
@ -606,7 +658,7 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m)
|
|||
|
||||
m.addSeparator();
|
||||
|
||||
pluginDescriptions = knownPluginList.getTypes();
|
||||
auto pluginDescriptions = knownPluginList.getTypes();
|
||||
|
||||
// This avoids showing the internal types again later on in the list
|
||||
pluginDescriptions.removeIf ([] (PluginDescription& desc)
|
||||
|
|
@ -614,15 +666,23 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m)
|
|||
return desc.pluginFormatName == InternalPluginFormat::getIdentifier();
|
||||
});
|
||||
|
||||
KnownPluginList::addToMenu (m, pluginDescriptions, pluginSortMethod);
|
||||
auto tree = KnownPluginList::createTree (pluginDescriptions, pluginSortMethod);
|
||||
pluginDescriptionsAndPreference = {};
|
||||
addToMenu (*tree, m, pluginDescriptions, pluginDescriptionsAndPreference);
|
||||
}
|
||||
|
||||
PluginDescription MainHostWindow::getChosenType (const int menuID) const
|
||||
int MainHostWindow::getIndexChosenByMenu (int menuID) const
|
||||
{
|
||||
const auto i = menuID - menuIDBase;
|
||||
return isPositiveAndBelow (i, pluginDescriptionsAndPreference.size()) ? i : -1;
|
||||
}
|
||||
|
||||
PluginDescriptionAndPreference MainHostWindow::getChosenType (const int menuID) const
|
||||
{
|
||||
if (menuID >= 1 && menuID < (int) (1 + internalTypes.size()))
|
||||
return internalTypes[(size_t) (menuID - 1)];
|
||||
return PluginDescriptionAndPreference { internalTypes[(size_t) (menuID - 1)] };
|
||||
|
||||
return pluginDescriptions[KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuID)];
|
||||
return pluginDescriptionsAndPreference[getIndexChosenByMenu (menuID)];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -905,7 +965,7 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
|
|||
|
||||
for (int i = 0; i < jmin (5, typesFound.size()); ++i)
|
||||
if (auto* desc = typesFound.getUnchecked(i))
|
||||
createPlugin (*desc, pos);
|
||||
createPlugin (PluginDescriptionAndPreference { *desc }, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,10 +100,10 @@ public:
|
|||
|
||||
void tryToQuitApplication();
|
||||
|
||||
void createPlugin (const PluginDescription&, Point<int> pos);
|
||||
void createPlugin (const PluginDescriptionAndPreference&, Point<int> pos);
|
||||
|
||||
void addPluginsToMenu (PopupMenu&);
|
||||
PluginDescription getChosenType (int menuID) const;
|
||||
PluginDescriptionAndPreference getChosenType (int menuID) const;
|
||||
|
||||
std::unique_ptr<GraphDocumentComponent> graphHolder;
|
||||
|
||||
|
|
@ -117,6 +117,8 @@ private:
|
|||
|
||||
void showAudioSettings();
|
||||
|
||||
int getIndexChosenByMenu (int menuID) const;
|
||||
|
||||
//==============================================================================
|
||||
AudioDeviceManager deviceManager;
|
||||
AudioPluginFormatManager formatManager;
|
||||
|
|
@ -124,7 +126,7 @@ private:
|
|||
std::vector<PluginDescription> internalTypes;
|
||||
KnownPluginList knownPluginList;
|
||||
KnownPluginList::SortMethod pluginSortMethod;
|
||||
Array<PluginDescription> pluginDescriptions;
|
||||
Array<PluginDescriptionAndPreference> pluginDescriptionsAndPreference;
|
||||
|
||||
class PluginListWindow;
|
||||
std::unique_ptr<PluginListWindow> pluginListWindow;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../Plugins/IOConfigurationWindow.h"
|
||||
#include "../Plugins/ARAPlugin.h"
|
||||
|
||||
inline String getFormatSuffix (const AudioProcessor* plugin)
|
||||
{
|
||||
|
|
@ -148,6 +149,7 @@ public:
|
|||
programs,
|
||||
audioIO,
|
||||
debug,
|
||||
araHost,
|
||||
numTypes
|
||||
};
|
||||
|
||||
|
|
@ -234,6 +236,16 @@ private:
|
|||
type = PluginWindow::Type::generic;
|
||||
}
|
||||
|
||||
if (type == PluginWindow::Type::araHost)
|
||||
{
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
if (auto* araPluginInstanceWrapper = dynamic_cast<ARAPluginInstanceWrapper*> (&processor))
|
||||
if (auto* ui = araPluginInstanceWrapper->createARAHostEditor())
|
||||
return ui;
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
if (type == PluginWindow::Type::generic) return new GenericAudioProcessorEditor (processor);
|
||||
if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor);
|
||||
if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor);
|
||||
|
|
@ -252,6 +264,7 @@ private:
|
|||
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 {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -502,9 +502,17 @@ function(juce_add_module module_path)
|
|||
"${lv2_base_path}/lilv/src")
|
||||
target_link_libraries(juce_audio_processors INTERFACE juce_lilv_headers)
|
||||
|
||||
add_library(juce_ara_headers INTERFACE)
|
||||
|
||||
target_include_directories(juce_ara_headers INTERFACE
|
||||
"$<$<TARGET_EXISTS:juce_ara_sdk>:$<TARGET_PROPERTY:juce_ara_sdk,INTERFACE_INCLUDE_DIRECTORIES>>")
|
||||
|
||||
target_link_libraries(juce_audio_processors INTERFACE juce_ara_headers)
|
||||
|
||||
if(JUCE_ARG_ALIAS_NAMESPACE)
|
||||
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_vst3_headers ALIAS juce_vst3_headers)
|
||||
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_lilv_headers ALIAS juce_lilv_headers)
|
||||
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_ara_headers ALIAS juce_ara_headers)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -1969,6 +1969,22 @@ function(juce_set_vst3_sdk_path path)
|
|||
target_include_directories(juce_vst3_sdk INTERFACE "${path}")
|
||||
endfunction()
|
||||
|
||||
function(juce_set_ara_sdk_path path)
|
||||
if(TARGET juce_ara_sdk)
|
||||
message(FATAL_ERROR "juce_set_ara_sdk_path should only be called once")
|
||||
endif()
|
||||
|
||||
_juce_make_absolute(path)
|
||||
|
||||
if(NOT EXISTS "${path}")
|
||||
message(FATAL_ERROR "Could not find ARA SDK at the specified path: ${path}")
|
||||
endif()
|
||||
|
||||
add_library(juce_ara_sdk INTERFACE IMPORTED GLOBAL)
|
||||
|
||||
target_include_directories(juce_ara_sdk INTERFACE "${path}")
|
||||
endfunction()
|
||||
|
||||
# ==================================================================================================
|
||||
|
||||
function(juce_disable_default_flags)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,18 @@ public:
|
|||
/** Returns true if instantiation of this plugin type must be done from a non-message thread. */
|
||||
virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const = 0;
|
||||
|
||||
/** A callback lambda that is passed to getARAFactory() */
|
||||
using ARAFactoryCreationCallback = std::function<void (ARAFactoryResult)>;
|
||||
|
||||
/** Tries to create an ::ARAFactoryWrapper for this description.
|
||||
|
||||
The result of the operation will be wrapped into an ARAFactoryResult,
|
||||
which will be passed to a callback object supplied by the caller.
|
||||
|
||||
@see AudioPluginFormatManager::createARAFactoryAsync
|
||||
*/
|
||||
virtual void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) { callback ({}); }
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
friend class AudioPluginFormatManager;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,22 @@ std::unique_ptr<AudioPluginInstance> AudioPluginFormatManager::createPluginInsta
|
|||
return {};
|
||||
}
|
||||
|
||||
void AudioPluginFormatManager::createARAFactoryAsync (const PluginDescription& description,
|
||||
AudioPluginFormat::ARAFactoryCreationCallback callback) const
|
||||
{
|
||||
String errorMessage;
|
||||
|
||||
if (auto* format = findFormatForDescription (description, errorMessage))
|
||||
{
|
||||
format->createARAFactoryAsync (description, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = NEEDS_TRANS ("Couldn't find format for the provided description");
|
||||
callback ({ {}, std::move (errorMessage) });
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description,
|
||||
double initialSampleRate, int initialBufferSize,
|
||||
AudioPluginFormat::PluginCreationCallback callback)
|
||||
|
|
|
|||
|
|
@ -102,6 +102,22 @@ public:
|
|||
double initialSampleRate, int initialBufferSize,
|
||||
AudioPluginFormat::PluginCreationCallback callback);
|
||||
|
||||
/** Tries to create an ::ARAFactoryWrapper for this description.
|
||||
|
||||
The result of the operation will be wrapped into an ARAFactoryResult,
|
||||
which will be passed to a callback object supplied by the caller.
|
||||
|
||||
The operation may fail, in which case the callback will be called with
|
||||
with a result object where ARAFactoryResult::araFactory.get() will return
|
||||
a nullptr.
|
||||
|
||||
In case of success the returned ::ARAFactoryWrapper will ensure that
|
||||
modules required for the correct functioning of the ARAFactory will remain
|
||||
loaded for the lifetime of the object.
|
||||
*/
|
||||
void createARAFactoryAsync (const PluginDescription& description,
|
||||
AudioPluginFormat::ARAFactoryCreationCallback callback) const;
|
||||
|
||||
/** Checks that the file or component for this plugin actually still exists.
|
||||
(This won't try to load the plugin)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE 7 technical preview.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
You may use this code under the terms of the GPL v3
|
||||
(see www.gnu.org/licenses).
|
||||
|
||||
For the technical preview this file cannot be licensed commercially.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS))
|
||||
|
||||
#include <ARA_Library/Debug/ARADebug.h>
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static void dummyARAInterfaceAssert (ARA::ARAAssertCategory, const void*, const char*)
|
||||
{}
|
||||
|
||||
static ARA::ARAInterfaceConfiguration createInterfaceConfig (const ARA::ARAFactory* araFactory)
|
||||
{
|
||||
static auto* assertFunction = &dummyARAInterfaceAssert;
|
||||
|
||||
#if ARA_VALIDATE_API_CALLS
|
||||
assertFunction = &::ARA::ARAInterfaceAssert;
|
||||
static std::once_flag flag;
|
||||
std::call_once (flag, [] { ARA::ARASetExternalAssertReference (&assertFunction); });
|
||||
#endif
|
||||
|
||||
return makeARASizedStruct (&ARA::ARAInterfaceConfiguration::assertFunctionAddress,
|
||||
jmin (araFactory->highestSupportedApiGeneration, (ARA::ARAAPIGeneration) ARA::kARAAPIGeneration_2_X_Draft),
|
||||
&assertFunction);
|
||||
}
|
||||
|
||||
static std::shared_ptr<const ARA::ARAFactory> getOrCreateARAFactory (const ARA::ARAFactory* ptr,
|
||||
std::function<void (const ARA::ARAFactory*)> onDelete)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
static std::unordered_map<const ARA::ARAFactory*, std::weak_ptr<const ARA::ARAFactory>> cache;
|
||||
|
||||
auto& cachePtr = cache[ptr];
|
||||
|
||||
if (const auto obj = cachePtr.lock())
|
||||
return obj;
|
||||
|
||||
const auto interfaceConfig = createInterfaceConfig (ptr);
|
||||
ptr->initializeARAWithConfiguration (&interfaceConfig);
|
||||
const auto obj = std::shared_ptr<const ARA::ARAFactory> (ptr, [deleter = std::move (onDelete)] (const ARA::ARAFactory* factory)
|
||||
{
|
||||
factory->uninitializeARA();
|
||||
deleter (factory);
|
||||
});
|
||||
cachePtr = obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
78
modules/juce_audio_processors/format_types/juce_ARACommon.h
Normal file
78
modules/juce_audio_processors/format_types/juce_ARACommon.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE 7 technical preview.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
You may use this code under the terms of the GPL v3
|
||||
(see www.gnu.org/licenses).
|
||||
|
||||
For the technical preview this file cannot be licensed commercially.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ARA
|
||||
{
|
||||
struct ARAFactory;
|
||||
}
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** Encapsulates an ARAFactory pointer and makes sure that it remains in a valid state
|
||||
for the lifetime of the ARAFactoryWrapper object.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class ARAFactoryWrapper
|
||||
{
|
||||
public:
|
||||
ARAFactoryWrapper() = default;
|
||||
|
||||
/** @internal
|
||||
|
||||
Used by the framework to encapsulate ARAFactory pointers loaded from plugins.
|
||||
*/
|
||||
explicit ARAFactoryWrapper (std::shared_ptr<const ARA::ARAFactory> factoryIn) : factory (std::move (factoryIn)) {}
|
||||
|
||||
/** Returns the contained ARAFactory pointer, which can be a nullptr.
|
||||
|
||||
The validity of the returned pointer is only guaranteed for the lifetime of this wrapper.
|
||||
*/
|
||||
const ARA::ARAFactory* get() const noexcept { return factory.get(); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<const ARA::ARAFactory> factory;
|
||||
};
|
||||
|
||||
/** Represents the result of AudioPluginFormatManager::createARAFactoryAsync().
|
||||
|
||||
If the operation fails then #araFactory will contain `nullptr`, and #errorMessage may
|
||||
contain a reason for the failure.
|
||||
|
||||
The araFactory member ensures that the module necessary for the correct functioning
|
||||
of the factory will remain loaded.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
struct ARAFactoryResult
|
||||
{
|
||||
ARAFactoryWrapper araFactory;
|
||||
String errorMessage;
|
||||
};
|
||||
|
||||
template <typename Obj, typename Member, typename... Ts>
|
||||
constexpr Obj makeARASizedStruct (Member Obj::* member, Ts&&... ts)
|
||||
{
|
||||
return { reinterpret_cast<uintptr_t> (&(static_cast<const Obj*> (nullptr)->*member)) + sizeof (Member),
|
||||
std::forward<Ts> (ts)... };
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
451
modules/juce_audio_processors/format_types/juce_ARAHosting.cpp
Normal file
451
modules/juce_audio_processors/format_types/juce_ARAHosting.cpp
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE 7 technical preview.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
You may use this code under the terms of the GPL v3
|
||||
(see www.gnu.org/licenses).
|
||||
|
||||
For the technical preview this file cannot be licensed commercially.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS))
|
||||
|
||||
#include "juce_ARAHosting.h"
|
||||
#include <ARA_Library/Debug/ARADebug.h>
|
||||
|
||||
#include <ARA_Library/Dispatch/ARAHostDispatch.cpp>
|
||||
|
||||
namespace juce
|
||||
{
|
||||
struct ARAEditGuardState
|
||||
{
|
||||
public:
|
||||
/* Returns true if this controller wasn't previously present. */
|
||||
bool add (ARA::Host::DocumentController& dc)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock (mutex);
|
||||
return ++counts[&dc] == 1;
|
||||
}
|
||||
|
||||
/* Returns true if this controller is no longer present. */
|
||||
bool remove (ARA::Host::DocumentController& dc)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock (mutex);
|
||||
return --counts[&dc] == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<ARA::Host::DocumentController*, int> counts;
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
static ARAEditGuardState editGuardState;
|
||||
|
||||
ARAEditGuard::ARAEditGuard (ARA::Host::DocumentController& dcIn) : dc (dcIn)
|
||||
{
|
||||
if (editGuardState.add (dc))
|
||||
dc.beginEditing();
|
||||
}
|
||||
|
||||
ARAEditGuard::~ARAEditGuard()
|
||||
{
|
||||
if (editGuardState.remove (dc))
|
||||
dc.endEditing();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace ARAHostModel
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
AudioSource::AudioSource (ARA::ARAAudioSourceHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
const ARA::ARAAudioSourceProperties& props)
|
||||
: ManagedARAHandle (dc, [&]
|
||||
{
|
||||
const ARAEditGuard guard (dc);
|
||||
return dc.createAudioSource (hostRef, &props);
|
||||
}())
|
||||
{
|
||||
}
|
||||
|
||||
void AudioSource::update (const ARA::ARAAudioSourceProperties& props)
|
||||
{
|
||||
const ARAEditGuard guard (getDocumentController());
|
||||
getDocumentController().updateAudioSourceProperties (getPluginRef(), &props);
|
||||
}
|
||||
|
||||
void AudioSource::enableAudioSourceSamplesAccess (bool x)
|
||||
{
|
||||
const ARAEditGuard guard (getDocumentController());
|
||||
getDocumentController().enableAudioSourceSamplesAccess (getPluginRef(), x);
|
||||
}
|
||||
|
||||
void AudioSource::destroy (ARA::Host::DocumentController& dc, Ptr ptr)
|
||||
{
|
||||
dc.destroyAudioSource (ptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioModification::AudioModification (ARA::ARAAudioModificationHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
AudioSource& s,
|
||||
const ARA::ARAAudioModificationProperties& props)
|
||||
: ManagedARAHandle (dc, [&]
|
||||
{
|
||||
const ARAEditGuard guard (dc);
|
||||
return dc.createAudioModification (s.getPluginRef(), hostRef, &props);
|
||||
}()),
|
||||
source (s)
|
||||
{
|
||||
}
|
||||
|
||||
void AudioModification::update (const ARA::ARAAudioModificationProperties& props)
|
||||
{
|
||||
const ARAEditGuard guard (getDocumentController());
|
||||
getDocumentController().updateAudioModificationProperties (getPluginRef(), &props);
|
||||
}
|
||||
|
||||
void AudioModification::destroy (ARA::Host::DocumentController& dc, Ptr ptr)
|
||||
{
|
||||
dc.destroyAudioModification (ptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class PlaybackRegion::Impl : public ManagedARAHandle<Impl, ARA::ARAPlaybackRegionRef>,
|
||||
public DeletionListener
|
||||
{
|
||||
public:
|
||||
Impl (ARA::ARAPlaybackRegionHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
AudioModification& m,
|
||||
const ARA::ARAPlaybackRegionProperties& props);
|
||||
|
||||
~Impl() override
|
||||
{
|
||||
for (const auto& l : listeners)
|
||||
l->removeListener (*this);
|
||||
}
|
||||
|
||||
/* Updates the state of the corresponding %ARA model object.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
You can use getEmptyProperties() to acquire a properties struct where the `structSize`
|
||||
field has already been correctly set.
|
||||
*/
|
||||
void update (const ARA::ARAPlaybackRegionProperties& props);
|
||||
|
||||
auto& getAudioModification() const { return modification; }
|
||||
|
||||
static void destroy (ARA::Host::DocumentController&, Ptr);
|
||||
|
||||
void addListener (DeletionListener& l) { listeners.insert (&l); }
|
||||
void removeListener (DeletionListener& l) noexcept override { listeners.erase (&l); }
|
||||
|
||||
private:
|
||||
AudioModification* modification = nullptr;
|
||||
std::unordered_set<DeletionListener*> listeners;
|
||||
};
|
||||
|
||||
PlaybackRegion::Impl::Impl (ARA::ARAPlaybackRegionHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
AudioModification& m,
|
||||
const ARA::ARAPlaybackRegionProperties& props)
|
||||
: ManagedARAHandle (dc, [&]
|
||||
{
|
||||
const ARAEditGuard guard (dc);
|
||||
return dc.createPlaybackRegion (m.getPluginRef(), hostRef, &props);
|
||||
}()),
|
||||
modification (&m)
|
||||
{
|
||||
}
|
||||
|
||||
PlaybackRegion::~PlaybackRegion() = default;
|
||||
|
||||
void PlaybackRegion::Impl::update (const ARA::ARAPlaybackRegionProperties& props)
|
||||
{
|
||||
const ARAEditGuard guard (getDocumentController());
|
||||
getDocumentController().updatePlaybackRegionProperties (getPluginRef(), &props);
|
||||
}
|
||||
|
||||
void PlaybackRegion::Impl::destroy (ARA::Host::DocumentController& dc, Ptr ptr)
|
||||
{
|
||||
dc.destroyPlaybackRegion (ptr);
|
||||
}
|
||||
|
||||
PlaybackRegion::PlaybackRegion (ARA::ARAPlaybackRegionHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
AudioModification& m,
|
||||
const ARA::ARAPlaybackRegionProperties& props)
|
||||
: impl (std::make_unique<Impl> (hostRef, dc, m, props))
|
||||
{
|
||||
}
|
||||
|
||||
void PlaybackRegion::update (const ARA::ARAPlaybackRegionProperties& props) { impl->update (props); }
|
||||
|
||||
void PlaybackRegion::addListener (DeletionListener& x) { impl->addListener (x); }
|
||||
|
||||
auto& PlaybackRegion::getAudioModification() const { return impl->getAudioModification(); }
|
||||
|
||||
ARA::ARAPlaybackRegionRef PlaybackRegion::getPluginRef() const noexcept { return impl->getPluginRef(); }
|
||||
|
||||
DeletionListener& PlaybackRegion::getDeletionListener() const noexcept { return *impl.get(); }
|
||||
|
||||
//==============================================================================
|
||||
MusicalContext::MusicalContext (ARA::ARAMusicalContextHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
const ARA::ARAMusicalContextProperties& props)
|
||||
: ManagedARAHandle (dc, [&]
|
||||
{
|
||||
const ARAEditGuard guard (dc);
|
||||
return dc.createMusicalContext (hostRef, &props);
|
||||
}())
|
||||
{
|
||||
}
|
||||
|
||||
void MusicalContext::update (const ARA::ARAMusicalContextProperties& props)
|
||||
{
|
||||
const ARAEditGuard guard (getDocumentController());
|
||||
return getDocumentController().updateMusicalContextProperties (getPluginRef(), &props);
|
||||
}
|
||||
|
||||
void MusicalContext::destroy (ARA::Host::DocumentController& dc, Ptr ptr)
|
||||
{
|
||||
dc.destroyMusicalContext (ptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
RegionSequence::RegionSequence (ARA::ARARegionSequenceHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
const ARA::ARARegionSequenceProperties& props)
|
||||
: ManagedARAHandle (dc, [&]
|
||||
{
|
||||
const ARAEditGuard guard (dc);
|
||||
return dc.createRegionSequence (hostRef, &props);
|
||||
}())
|
||||
{
|
||||
}
|
||||
|
||||
void RegionSequence::update (const ARA::ARARegionSequenceProperties& props)
|
||||
{
|
||||
const ARAEditGuard guard (getDocumentController());
|
||||
return getDocumentController().updateRegionSequenceProperties (getPluginRef(), &props);
|
||||
}
|
||||
|
||||
void RegionSequence::destroy (ARA::Host::DocumentController& dc, Ptr ptr)
|
||||
{
|
||||
dc.destroyRegionSequence (ptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
PlaybackRendererInterface PlugInExtensionInstance::getPlaybackRendererInterface() const
|
||||
{
|
||||
if (instance != nullptr)
|
||||
return PlaybackRendererInterface (instance->playbackRendererRef, instance->playbackRendererInterface);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
EditorRendererInterface PlugInExtensionInstance::getEditorRendererInterface() const
|
||||
{
|
||||
if (instance != nullptr)
|
||||
return EditorRendererInterface (instance->editorRendererRef, instance->editorRendererInterface);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ARAHostModel
|
||||
|
||||
//==============================================================================
|
||||
class ARAHostDocumentController::Impl
|
||||
{
|
||||
public:
|
||||
Impl (ARAFactoryWrapper araFactoryIn,
|
||||
std::unique_ptr<ARA::Host::DocumentControllerHostInstance>&& dcHostInstanceIn,
|
||||
const ARA::ARADocumentControllerInstance* documentControllerInstance)
|
||||
: araFactory (std::move (araFactoryIn)),
|
||||
dcHostInstance (std::move (dcHostInstanceIn)),
|
||||
documentController (documentControllerInstance)
|
||||
{
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
documentController.destroyDocumentController();
|
||||
}
|
||||
|
||||
static std::unique_ptr<Impl>
|
||||
createImpl (ARAFactoryWrapper araFactory,
|
||||
const String& documentName,
|
||||
std::unique_ptr<ARA::Host::AudioAccessControllerInterface>&& audioAccessController,
|
||||
std::unique_ptr<ARA::Host::ArchivingControllerInterface>&& archivingController,
|
||||
std::unique_ptr<ARA::Host::ContentAccessControllerInterface>&& contentAccessController,
|
||||
std::unique_ptr<ARA::Host::ModelUpdateControllerInterface>&& modelUpdateController,
|
||||
std::unique_ptr<ARA::Host::PlaybackControllerInterface>&& playbackController)
|
||||
{
|
||||
std::unique_ptr<ARA::Host::DocumentControllerHostInstance> dcHostInstance =
|
||||
std::make_unique<ARA::Host::DocumentControllerHostInstance> (audioAccessController.release(),
|
||||
archivingController.release(),
|
||||
contentAccessController.release(),
|
||||
modelUpdateController.release(),
|
||||
playbackController.release());
|
||||
|
||||
const auto documentProperties = makeARASizedStruct (&ARA::ARADocumentProperties::name, documentName.toRawUTF8());
|
||||
|
||||
if (auto* dci = araFactory.get()->createDocumentControllerWithDocument (dcHostInstance.get(), &documentProperties))
|
||||
return std::make_unique<Impl> (std::move (araFactory), std::move (dcHostInstance), dci);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ARAHostModel::PlugInExtensionInstance bindDocumentToPluginInstance (AudioPluginInstance& instance,
|
||||
ARA::ARAPlugInInstanceRoleFlags knownRoles,
|
||||
ARA::ARAPlugInInstanceRoleFlags assignedRoles)
|
||||
{
|
||||
|
||||
const auto makeVisitor = [] (auto vst3Fn, auto auFn)
|
||||
{
|
||||
using Vst3Fn = decltype (vst3Fn);
|
||||
using AuFn = decltype (auFn);
|
||||
|
||||
struct Visitor : ExtensionsVisitor, Vst3Fn, AuFn
|
||||
{
|
||||
explicit Visitor (Vst3Fn vst3Fn, AuFn auFn) : Vst3Fn (std::move (vst3Fn)), AuFn (std::move (auFn)) {}
|
||||
void visitVST3Client (const VST3Client& x) override { Vst3Fn::operator() (x); }
|
||||
void visitAudioUnitClient (const AudioUnitClient& x) override { AuFn::operator() (x); }
|
||||
};
|
||||
|
||||
return Visitor { std::move (vst3Fn), std::move (auFn) };
|
||||
};
|
||||
|
||||
const ARA::ARAPlugInExtensionInstance* pei = nullptr;
|
||||
auto visitor = makeVisitor ([this, &pei, knownRoles, assignedRoles] (const ExtensionsVisitor::VST3Client& vst3Client)
|
||||
{
|
||||
auto* iComponentPtr = vst3Client.getIComponentPtr();
|
||||
VSTComSmartPtr<ARA::IPlugInEntryPoint2> araEntryPoint;
|
||||
|
||||
if (araEntryPoint.loadFrom (iComponentPtr))
|
||||
pei = araEntryPoint->bindToDocumentControllerWithRoles (documentController.getRef(), knownRoles, assignedRoles);
|
||||
},
|
||||
#if JUCE_PLUGINHOST_AU && JUCE_MAC
|
||||
[this, &pei, knownRoles, assignedRoles] (const ExtensionsVisitor::AudioUnitClient& auClient)
|
||||
{
|
||||
auto audioUnit = auClient.getAudioUnitHandle();
|
||||
auto propertySize = (UInt32) sizeof (ARA::ARAAudioUnitPlugInExtensionBinding);
|
||||
const auto expectedPropertySize = propertySize;
|
||||
ARA::ARAAudioUnitPlugInExtensionBinding audioUnitBinding { ARA::kARAAudioUnitMagic,
|
||||
documentController.getRef(),
|
||||
nullptr,
|
||||
knownRoles,
|
||||
assignedRoles };
|
||||
|
||||
auto status = AudioUnitGetProperty (audioUnit,
|
||||
ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&audioUnitBinding,
|
||||
&propertySize);
|
||||
|
||||
if (status == noErr
|
||||
&& propertySize == expectedPropertySize
|
||||
&& audioUnitBinding.inOutMagicNumber == ARA::kARAAudioUnitMagic
|
||||
&& audioUnitBinding.inDocumentControllerRef == documentController.getRef()
|
||||
&& audioUnitBinding.outPlugInExtension != nullptr)
|
||||
{
|
||||
pei = audioUnitBinding.outPlugInExtension;
|
||||
}
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
#else
|
||||
[] (const auto&) {}
|
||||
#endif
|
||||
);
|
||||
|
||||
instance.getExtensions (visitor);
|
||||
return ARAHostModel::PlugInExtensionInstance { pei };
|
||||
}
|
||||
|
||||
auto& getDocumentController() { return documentController; }
|
||||
|
||||
private:
|
||||
ARAFactoryWrapper araFactory;
|
||||
std::unique_ptr<ARA::Host::DocumentControllerHostInstance> dcHostInstance;
|
||||
ARA::Host::DocumentController documentController;
|
||||
};
|
||||
|
||||
ARAHostDocumentController::ARAHostDocumentController (std::unique_ptr<Impl>&& implIn)
|
||||
: impl { std::move (implIn) }
|
||||
{}
|
||||
|
||||
std::unique_ptr<ARAHostDocumentController> ARAHostDocumentController::create (ARAFactoryWrapper factory,
|
||||
const String& documentName,
|
||||
std::unique_ptr<ARA::Host::AudioAccessControllerInterface> audioAccessController,
|
||||
std::unique_ptr<ARA::Host::ArchivingControllerInterface> archivingController,
|
||||
std::unique_ptr<ARA::Host::ContentAccessControllerInterface> contentAccessController,
|
||||
std::unique_ptr<ARA::Host::ModelUpdateControllerInterface> modelUpdateController,
|
||||
std::unique_ptr<ARA::Host::PlaybackControllerInterface> playbackController)
|
||||
{
|
||||
if (auto impl = Impl::createImpl (std::move (factory),
|
||||
documentName,
|
||||
std::move (audioAccessController),
|
||||
std::move (archivingController),
|
||||
std::move (contentAccessController),
|
||||
std::move (modelUpdateController),
|
||||
std::move (playbackController)))
|
||||
{
|
||||
return rawToUniquePtr (new ARAHostDocumentController (std::move (impl)));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ARAHostDocumentController::~ARAHostDocumentController() = default;
|
||||
|
||||
ARA::Host::DocumentController& ARAHostDocumentController::getDocumentController() const
|
||||
{
|
||||
return impl->getDocumentController();
|
||||
}
|
||||
|
||||
ARAHostModel::PlugInExtensionInstance ARAHostDocumentController::bindDocumentToPluginInstance (AudioPluginInstance& instance,
|
||||
ARA::ARAPlugInInstanceRoleFlags knownRoles,
|
||||
ARA::ARAPlugInInstanceRoleFlags assignedRoles)
|
||||
{
|
||||
return impl->bindDocumentToPluginInstance (instance, knownRoles, assignedRoles);
|
||||
}
|
||||
|
||||
void createARAFactoryAsync (AudioPluginInstance& instance, std::function<void (ARAFactoryWrapper)> cb)
|
||||
{
|
||||
if (! instance.getPluginDescription().hasARAExtension)
|
||||
cb (ARAFactoryWrapper{});
|
||||
|
||||
struct Extensions : public ExtensionsVisitor
|
||||
{
|
||||
Extensions (std::function<void (ARAFactoryWrapper)> callbackIn)
|
||||
: callback (std::move (callbackIn))
|
||||
{}
|
||||
|
||||
void visitARAClient (const ARAClient& araClient) override
|
||||
{
|
||||
araClient.createARAFactoryAsync (std::move (callback));
|
||||
}
|
||||
|
||||
std::function<void (ARAFactoryWrapper)> callback;
|
||||
};
|
||||
|
||||
Extensions extensions { std::move(cb) };
|
||||
instance.getExtensions (extensions);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
||||
#endif
|
||||
733
modules/juce_audio_processors/format_types/juce_ARAHosting.h
Normal file
733
modules/juce_audio_processors/format_types/juce_ARAHosting.h
Normal file
|
|
@ -0,0 +1,733 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE 7 technical preview.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
You may use this code under the terms of the GPL v3
|
||||
(see www.gnu.org/licenses).
|
||||
|
||||
For the technical preview this file cannot be licensed commercially.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS)) || DOXYGEN
|
||||
|
||||
// Include ARA SDK headers
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments")
|
||||
|
||||
#include <ARA_API/ARAInterface.h>
|
||||
#include <ARA_Library/Dispatch/ARAHostDispatch.h>
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
//==============================================================================
|
||||
namespace juce
|
||||
{
|
||||
/** Reference counting helper class to ensure that the DocumentController is in editable state.
|
||||
|
||||
When adding, removing or modifying %ARA model objects the enclosing DocumentController must be
|
||||
in editable state.
|
||||
|
||||
You can achieve this by using the %ARA Library calls
|
||||
ARA::Host::DocumentController::beginEditing() and ARA::Host::DocumentController::endEditing().
|
||||
|
||||
However, putting the DocumentController in and out of editable state is a potentially costly
|
||||
operation, thus it makes sense to group multiple modifications together and change the editable
|
||||
state only once.
|
||||
|
||||
ARAEditGuard keeps track of all scopes that want to edit a particular DocumentController and
|
||||
will trigger beginEditing() and endEditing() only for the outermost scope. This allows you to
|
||||
merge multiple editing operations into one by putting ARAEditGuard in their enclosing scope.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class ARAEditGuard
|
||||
{
|
||||
public:
|
||||
explicit ARAEditGuard (ARA::Host::DocumentController& dcIn);
|
||||
~ARAEditGuard();
|
||||
|
||||
private:
|
||||
ARA::Host::DocumentController& dc;
|
||||
};
|
||||
|
||||
namespace ARAHostModel
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** Allows converting, without warnings, between pointers of two unrelated types.
|
||||
|
||||
This is a bit like ARA_MAP_HOST_REF, but not macro-based.
|
||||
|
||||
To use it, add a line like this to a type that needs to deal in host references:
|
||||
@code
|
||||
using Converter = ConversionFunctions<ThisType*, ARAHostRef>;
|
||||
@endcode
|
||||
|
||||
Now, you can convert back and forth with host references by calling
|
||||
Converter::toHostRef() and Converter::fromHostRef().
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
template <typename A, typename B>
|
||||
struct ConversionFunctions
|
||||
{
|
||||
static_assert (sizeof (A) <= sizeof (B),
|
||||
"It is only possible to convert between types of the same size");
|
||||
|
||||
static B toHostRef (A value)
|
||||
{
|
||||
return readUnaligned<B> (&value);
|
||||
}
|
||||
|
||||
static A fromHostRef (B value)
|
||||
{
|
||||
return readUnaligned<A> (&value);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <typename Base, typename PtrIn>
|
||||
class ManagedARAHandle
|
||||
{
|
||||
public:
|
||||
using Ptr = PtrIn;
|
||||
|
||||
ManagedARAHandle (ARA::Host::DocumentController& dc, Ptr ptr) noexcept
|
||||
: handle (ptr, Deleter { dc }) {}
|
||||
|
||||
auto& getDocumentController() const { return handle.get_deleter().documentController; }
|
||||
|
||||
Ptr getPluginRef() const { return handle.get(); }
|
||||
|
||||
private:
|
||||
struct Deleter
|
||||
{
|
||||
void operator() (Ptr ptr) const noexcept
|
||||
{
|
||||
const ARAEditGuard guard (documentController);
|
||||
Base::destroy (documentController, ptr);
|
||||
}
|
||||
|
||||
ARA::Host::DocumentController& documentController;
|
||||
};
|
||||
|
||||
std::unique_ptr<std::remove_pointer_t<Ptr>, Deleter> handle;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Helper class for the host side implementation of the %ARA %AudioSource model object.
|
||||
|
||||
Its intended use is to add a member variable of this type to your host side %AudioSource
|
||||
implementation. Then it provides a RAII approach to managing the lifetime of the corresponding
|
||||
objects created inside the DocumentController. When the host side object is instantiated an ARA
|
||||
model object is also created in the DocumentController. When the host side object is deleted it
|
||||
will be removed from the DocumentController as well.
|
||||
|
||||
The class will automatically put the DocumentController into editable state for operations that
|
||||
mandate this e.g. creation, deletion or updating.
|
||||
|
||||
You can encapsulate multiple such operations into a scope with an ARAEditGuard in order to invoke
|
||||
the editable state of the DocumentController only once.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class AudioSource : public ManagedARAHandle<AudioSource, ARA::ARAAudioSourceRef>
|
||||
{
|
||||
public:
|
||||
/** Returns an %ARA versioned struct with the `structSize` correctly set for the currently
|
||||
used SDK version.
|
||||
|
||||
You should leave `structSize` unchanged, and fill out the rest of the fields appropriately
|
||||
for the host implementation of the %ARA model object.
|
||||
*/
|
||||
static constexpr auto getEmptyProperties() { return makeARASizedStruct (&ARA::ARAAudioSourceProperties::merits64BitSamples); }
|
||||
|
||||
/** Creates an AudioSource object. During construction it registers an %ARA %AudioSource model
|
||||
object with the DocumentController that refers to the provided hostRef. When this object
|
||||
is deleted the corresponding DocumentController model object will also be deregistered.
|
||||
|
||||
You can acquire a correctly versioned `ARA::ARAAudioSourceProperties` struct by calling
|
||||
getEmptyProperties().
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
@see ARAEditGuard
|
||||
*/
|
||||
AudioSource (ARA::ARAAudioSourceHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
const ARA::ARAAudioSourceProperties& props);
|
||||
|
||||
/** Destructor. Temporarily places the DocumentController in an editable state. */
|
||||
~AudioSource() = default;
|
||||
|
||||
/** Updates the state of the corresponding %ARA model object.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
You can use getEmptyProperties() to acquire a properties struct where the `structSize`
|
||||
field has already been correctly set.
|
||||
*/
|
||||
void update (const ARA::ARAAudioSourceProperties& props);
|
||||
|
||||
/** Changes the plugin's access to the %AudioSource samples through the DocumentController.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
*/
|
||||
void enableAudioSourceSamplesAccess (bool);
|
||||
|
||||
/** Called by ManagedARAHandle to deregister the model object during the destruction of
|
||||
AudioSource.
|
||||
|
||||
You shouldn't call this function manually.
|
||||
*/
|
||||
static void destroy (ARA::Host::DocumentController&, Ptr);
|
||||
};
|
||||
|
||||
/** Helper class for the host side implementation of the %ARA %AudioModification model object.
|
||||
|
||||
Its intended use is to add a member variable of this type to your host side %AudioModification
|
||||
implementation. Then it provides a RAII approach to managing the lifetime of the corresponding
|
||||
objects created inside the DocumentController. When the host side object is instantiated an ARA
|
||||
model object is also created in the DocumentController. When the host side object is deleted it
|
||||
will be removed from the DocumentController as well.
|
||||
|
||||
The class will automatically put the DocumentController into editable state for operations that
|
||||
mandate this e.g. creation, deletion or updating.
|
||||
|
||||
You can encapsulate multiple such operations into a scope with an ARAEditGuard in order to invoke
|
||||
the editable state of the DocumentController only once.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class AudioModification : public ManagedARAHandle<AudioModification, ARA::ARAAudioModificationRef>
|
||||
{
|
||||
public:
|
||||
/** Returns an %ARA versioned struct with the `structSize` correctly set for the currently
|
||||
used SDK version.
|
||||
|
||||
You should leave `structSize` unchanged, and fill out the rest of the fields appropriately
|
||||
for the host implementation of the %ARA model object.
|
||||
*/
|
||||
static constexpr auto getEmptyProperties()
|
||||
{
|
||||
return makeARASizedStruct (&ARA::ARAAudioModificationProperties::persistentID);
|
||||
}
|
||||
|
||||
/** Creates an AudioModification object. During construction it registers an %ARA %AudioModification model
|
||||
object with the DocumentController that refers to the provided hostRef. When this object
|
||||
is deleted the corresponding DocumentController model object will also be deregistered.
|
||||
|
||||
You can acquire a correctly versioned `ARA::ARAAudioModificationProperties` struct by calling
|
||||
getEmptyProperties().
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
@see ARAEditGuard
|
||||
*/
|
||||
AudioModification (ARA::ARAAudioModificationHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
AudioSource& s,
|
||||
const ARA::ARAAudioModificationProperties& props);
|
||||
|
||||
/** Updates the state of the corresponding %ARA model object.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
You can use getEmptyProperties() to acquire a properties struct where the `structSize`
|
||||
field has already been correctly set.
|
||||
*/
|
||||
void update (const ARA::ARAAudioModificationProperties& props);
|
||||
|
||||
/** Returns the AudioSource containing this AudioModification. */
|
||||
auto& getAudioSource() const { return source; }
|
||||
|
||||
/** Called by ManagedARAHandle to deregister the model object during the destruction of
|
||||
AudioModification.
|
||||
|
||||
You shouldn't call this function manually.
|
||||
*/
|
||||
static void destroy (ARA::Host::DocumentController&, Ptr);
|
||||
|
||||
private:
|
||||
AudioSource& source;
|
||||
};
|
||||
|
||||
struct DeletionListener
|
||||
{
|
||||
virtual ~DeletionListener() = default;
|
||||
virtual void removeListener (DeletionListener& other) noexcept = 0;
|
||||
};
|
||||
|
||||
struct PlaybackRegion
|
||||
{
|
||||
public:
|
||||
/** Returns an %ARA versioned struct with the `structSize` correctly set for the currently
|
||||
used SDK version.
|
||||
|
||||
You should leave `structSize` unchanged, and fill out the rest of the fields appropriately
|
||||
for the host implementation of the %ARA model object.
|
||||
*/
|
||||
static constexpr auto getEmptyProperties()
|
||||
{
|
||||
return makeARASizedStruct (&ARA::ARAPlaybackRegionProperties::color);
|
||||
}
|
||||
|
||||
PlaybackRegion (ARA::ARAPlaybackRegionHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
AudioModification& m,
|
||||
const ARA::ARAPlaybackRegionProperties& props);
|
||||
|
||||
~PlaybackRegion();
|
||||
|
||||
/** Updates the state of the corresponding %ARA model object.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
You can use getEmptyProperties() to acquire a properties struct where the `structSize`
|
||||
field has already been correctly set.
|
||||
*/
|
||||
void update (const ARA::ARAPlaybackRegionProperties& props);
|
||||
|
||||
/** Adds a DeletionListener object that will be notified when the PlaybackRegion object
|
||||
is deleted.
|
||||
|
||||
Used by the PlaybackRegionRegistry.
|
||||
|
||||
@see PlaybackRendererInterface, EditorRendererInterface
|
||||
*/
|
||||
void addListener (DeletionListener& x);
|
||||
|
||||
/** Returns the AudioModification containing this PlaybackRegion. */
|
||||
auto& getAudioModification() const;
|
||||
|
||||
/** Returns the plugin side reference to the PlaybackRegion */
|
||||
ARA::ARAPlaybackRegionRef getPluginRef() const noexcept;
|
||||
DeletionListener& getDeletionListener() const noexcept;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
/** Helper class for the host side implementation of the %ARA %MusicalContext model object.
|
||||
|
||||
Its intended use is to add a member variable of this type to your host side %MusicalContext
|
||||
implementation. Then it provides a RAII approach to managing the lifetime of the corresponding
|
||||
objects created inside the DocumentController. When the host side object is instantiated an ARA
|
||||
model object is also created in the DocumentController. When the host side object is deleted it
|
||||
will be removed from the DocumentController as well.
|
||||
|
||||
The class will automatically put the DocumentController into editable state for operations that
|
||||
mandate this e.g. creation, deletion or updating.
|
||||
|
||||
You can encapsulate multiple such operations into a scope with an ARAEditGuard in order to invoke
|
||||
the editable state of the DocumentController only once.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class MusicalContext : public ManagedARAHandle<MusicalContext, ARA::ARAMusicalContextRef>
|
||||
{
|
||||
public:
|
||||
/** Returns an %ARA versioned struct with the `structSize` correctly set for the currently
|
||||
used SDK version.
|
||||
|
||||
You should leave `structSize` unchanged, and fill out the rest of the fields appropriately
|
||||
for the host implementation of the %ARA model object.
|
||||
*/
|
||||
static constexpr auto getEmptyProperties()
|
||||
{
|
||||
return makeARASizedStruct (&ARA::ARAMusicalContextProperties::color);
|
||||
}
|
||||
|
||||
/** Creates a MusicalContext object. During construction it registers an %ARA %MusicalContext model
|
||||
object with the DocumentController that refers to the provided hostRef. When this object
|
||||
is deleted the corresponding DocumentController model object will also be deregistered.
|
||||
|
||||
You can acquire a correctly versioned `ARA::ARAMusicalContextProperties` struct by calling
|
||||
getEmptyProperties().
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
@see ARAEditGuard
|
||||
*/
|
||||
MusicalContext (ARA::ARAMusicalContextHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
const ARA::ARAMusicalContextProperties& props);
|
||||
|
||||
/** Updates the state of the corresponding %ARA model object.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
You can use getEmptyProperties() to acquire a properties struct where the `structSize`
|
||||
field has already been correctly set.
|
||||
*/
|
||||
void update (const ARA::ARAMusicalContextProperties& props);
|
||||
|
||||
/** Called by ManagedARAHandle to deregister the model object during the destruction of
|
||||
AudioModification.
|
||||
|
||||
You shouldn't call this function manually.
|
||||
*/
|
||||
static void destroy (ARA::Host::DocumentController&, Ptr);
|
||||
};
|
||||
|
||||
/** Helper class for the host side implementation of the %ARA %RegionSequence model object.
|
||||
|
||||
Its intended use is to add a member variable of this type to your host side %RegionSequence
|
||||
implementation. Then it provides a RAII approach to managing the lifetime of the corresponding
|
||||
objects created inside the DocumentController. When the host side object is instantiated an ARA
|
||||
model object is also created in the DocumentController. When the host side object is deleted it
|
||||
will be removed from the DocumentController as well.
|
||||
|
||||
The class will automatically put the DocumentController into editable state for operations that
|
||||
mandate this e.g. creation, deletion or updating.
|
||||
|
||||
You can encapsulate multiple such operations into a scope with an ARAEditGuard in order to invoke
|
||||
the editable state of the DocumentController only once.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class RegionSequence : public ManagedARAHandle<RegionSequence, ARA::ARARegionSequenceRef>
|
||||
{
|
||||
public:
|
||||
/** Returns an %ARA versioned struct with the `structSize` correctly set for the currently
|
||||
used SDK version.
|
||||
|
||||
You should leave `structSize` unchanged, and fill out the rest of the fields appropriately
|
||||
for the host implementation of the %ARA model object.
|
||||
*/
|
||||
static constexpr auto getEmptyProperties()
|
||||
{
|
||||
return makeARASizedStruct (&ARA::ARARegionSequenceProperties::color);
|
||||
}
|
||||
|
||||
/** Creates a RegionSequence object. During construction it registers an %ARA %RegionSequence model
|
||||
object with the DocumentController that refers to the provided hostRef. When this object
|
||||
is deleted the corresponding DocumentController model object will also be deregistered.
|
||||
|
||||
You can acquire a correctly versioned `ARA::ARARegionSequenceProperties` struct by calling
|
||||
getEmptyProperties().
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
@see ARAEditGuard
|
||||
*/
|
||||
RegionSequence (ARA::ARARegionSequenceHostRef hostRef,
|
||||
ARA::Host::DocumentController& dc,
|
||||
const ARA::ARARegionSequenceProperties& props);
|
||||
|
||||
/** Updates the state of the corresponding %ARA model object.
|
||||
|
||||
Places the DocumentController in editable state.
|
||||
|
||||
You can use getEmptyProperties() to acquire a properties struct where the `structSize`
|
||||
field has already been correctly set.
|
||||
*/
|
||||
void update (const ARA::ARARegionSequenceProperties& props);
|
||||
|
||||
/** Called by ManagedARAHandle to deregister the model object during the destruction of
|
||||
AudioModification.
|
||||
|
||||
You shouldn't call this function manually.
|
||||
*/
|
||||
static void destroy (ARA::Host::DocumentController&, Ptr);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Base class used by the ::PlaybackRendererInterface and ::EditorRendererInterface
|
||||
plugin extension interfaces.
|
||||
|
||||
Hosts will want to create one or typically more %ARA plugin extension instances per plugin for
|
||||
the purpose of playback and editor rendering. The PlaybackRegions created by the host then have
|
||||
to be assigned to these instances through the appropriate interfaces.
|
||||
|
||||
Whether a PlaybackRegion or an assigned RendererInterface is deleted first depends on the host
|
||||
implementation and exact use case.
|
||||
|
||||
By using these helper classes you can ensure that the %ARA DocumentController remains in a
|
||||
valid state in both situations. In order to use them acquire an object from
|
||||
PlugInExtensionInstance::getPlaybackRendererInterface() or
|
||||
PlugInExtensionInstance::getEditorRendererInterface().
|
||||
|
||||
Then call add() to register a PlaybackRegion with that particular PlugInExtensionInstance's
|
||||
interface.
|
||||
|
||||
Now when you delete that PlaybackRegion it will be deregistered from that extension instance.
|
||||
If however you want to delete the plugin extension instance before the PlaybackRegion, you can
|
||||
delete the PlaybackRegionRegistry instance before deleting the plugin extension instance, which
|
||||
takes care of deregistering all PlaybackRegions.
|
||||
|
||||
When adding or removing PlaybackRegions the plugin instance must be in an unprepared state i.e.
|
||||
before AudioProcessor::prepareToPlay() or after AudioProcessor::releaseResources().
|
||||
|
||||
@code
|
||||
auto playbackRenderer = std::make_unique<PlaybackRendererInterface> (plugInExtensionInstance.getPlaybackRendererInterface());
|
||||
auto playbackRegion = std::make_unique<PlaybackRegion> (documentController, regionSequence, audioModification, audioSource);
|
||||
|
||||
// Either of the following three code variations are valid
|
||||
// (1) ===================================================
|
||||
playbackRenderer.add (playbackRegion);
|
||||
playbackRenderer.remove (playbackRegion);
|
||||
|
||||
// (2) ===================================================
|
||||
playbackRenderer.add (playbackRegion);
|
||||
playbackRegion.reset();
|
||||
|
||||
// (3) ===================================================
|
||||
playbackRenderer.add (playbackRegion);
|
||||
playbackRenderer.reset();
|
||||
@endcode
|
||||
|
||||
@see PluginExtensionInstance
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
template <typename RendererRef, typename Interface>
|
||||
class PlaybackRegionRegistry
|
||||
{
|
||||
public:
|
||||
PlaybackRegionRegistry() = default;
|
||||
|
||||
PlaybackRegionRegistry (RendererRef rendererRefIn, const Interface* interfaceIn)
|
||||
: registry (std::make_unique<Registry> (rendererRefIn, interfaceIn))
|
||||
{
|
||||
}
|
||||
|
||||
/** Adds a PlaybackRegion to the corresponding ::PlaybackRendererInterface or ::EditorRendererInterface.
|
||||
|
||||
The plugin instance must be in an unprepared state i.e. before AudioProcessor::prepareToPlay() or
|
||||
after AudioProcessor::releaseResources().
|
||||
*/
|
||||
void add (PlaybackRegion& region) { registry->add (region); }
|
||||
|
||||
/** Removes a PlaybackRegion from the corresponding ::PlaybackRendererInterface or ::EditorRendererInterface.
|
||||
|
||||
The plugin instance must be in an unprepared state i.e. before AudioProcessor::prepareToPlay() or
|
||||
after AudioProcessor::releaseResources().
|
||||
*/
|
||||
void remove (PlaybackRegion& region) { registry->remove (region); }
|
||||
|
||||
/** Returns true if the underlying %ARA plugin extension instance fulfills the corresponding role. */
|
||||
bool isValid() { return registry->isValid(); }
|
||||
|
||||
private:
|
||||
class Registry : private DeletionListener
|
||||
{
|
||||
public:
|
||||
Registry (RendererRef rendererRefIn, const Interface* interfaceIn)
|
||||
: rendererRef (rendererRefIn), rendererInterface (interfaceIn)
|
||||
{
|
||||
}
|
||||
|
||||
Registry (const Registry&) = delete;
|
||||
Registry (Registry&&) noexcept = delete;
|
||||
|
||||
Registry& operator= (const Registry&) = delete;
|
||||
Registry& operator= (Registry&&) noexcept = delete;
|
||||
|
||||
~Registry() override
|
||||
{
|
||||
for (const auto& region : regions)
|
||||
doRemoveListener (*region.first);
|
||||
}
|
||||
|
||||
bool isValid() { return rendererRef != nullptr && rendererInterface != nullptr; }
|
||||
|
||||
void add (PlaybackRegion& region)
|
||||
{
|
||||
if (isValid())
|
||||
rendererInterface->addPlaybackRegion (rendererRef, region.getPluginRef());
|
||||
|
||||
regions.emplace (®ion.getDeletionListener(), region.getPluginRef());
|
||||
region.addListener (*this);
|
||||
}
|
||||
|
||||
void remove (PlaybackRegion& region)
|
||||
{
|
||||
doRemoveListener (region.getDeletionListener());
|
||||
}
|
||||
|
||||
private:
|
||||
void doRemoveListener (DeletionListener& listener) noexcept
|
||||
{
|
||||
listener.removeListener (*this);
|
||||
removeListener (listener);
|
||||
}
|
||||
|
||||
void removeListener (DeletionListener& listener) noexcept override
|
||||
{
|
||||
const auto it = regions.find (&listener);
|
||||
|
||||
if (it == regions.end())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isValid())
|
||||
rendererInterface->removePlaybackRegion (rendererRef, it->second);
|
||||
|
||||
regions.erase (it);
|
||||
}
|
||||
|
||||
RendererRef rendererRef = nullptr;
|
||||
const Interface* rendererInterface = nullptr;
|
||||
std::map<DeletionListener*, ARA::ARAPlaybackRegionRef> regions;
|
||||
};
|
||||
|
||||
std::unique_ptr<Registry> registry;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Helper class for managing the lifetimes of %ARA plugin extension instances and PlaybackRegions.
|
||||
|
||||
You can read more about its usage at PlaybackRegionRegistry.
|
||||
|
||||
@see PlaybackRegion, PlaybackRegionRegistry
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
using PlaybackRendererInterface = PlaybackRegionRegistry<ARA::ARAPlaybackRendererRef, ARA::ARAPlaybackRendererInterface>;
|
||||
|
||||
//==============================================================================
|
||||
/** Helper class for managing the lifetimes of %ARA plugin extension instances and PlaybackRegions.
|
||||
|
||||
You can read more about its usage at PlaybackRegionRegistry.
|
||||
|
||||
@see PlaybackRegion, PlaybackRegionRegistry
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
using EditorRendererInterface = PlaybackRegionRegistry<ARA::ARAEditorRendererRef, ARA::ARAEditorRendererInterface>;
|
||||
|
||||
//==============================================================================
|
||||
/** Wrapper class for `ARA::ARAPlugInExtensionInstance*`.
|
||||
|
||||
Returned by ARAHostDocumentController::bindDocumentToPluginInstance(). The corresponding
|
||||
ARAHostDocumentController must remain valid as long as the plugin extension is in use.
|
||||
*/
|
||||
class PlugInExtensionInstance final
|
||||
{
|
||||
public:
|
||||
/** Creates an empty PlugInExtensionInstance object.
|
||||
|
||||
Calling isValid() on such an object will return false.
|
||||
*/
|
||||
PlugInExtensionInstance() = default;
|
||||
|
||||
/** Creates a PlugInExtensionInstance object that wraps a `const ARA::ARAPlugInExtensionInstance*`.
|
||||
|
||||
The intended way to obtain a PlugInExtensionInstance object is to call
|
||||
ARAHostDocumentController::bindDocumentToPluginInstance(), which is using this constructor.
|
||||
*/
|
||||
explicit PlugInExtensionInstance (const ARA::ARAPlugInExtensionInstance* instanceIn)
|
||||
: instance (instanceIn)
|
||||
{
|
||||
}
|
||||
|
||||
/** Returns the PlaybackRendererInterface for the extension instance.
|
||||
|
||||
Depending on what roles were passed into
|
||||
ARAHostDocumentController::bindDocumentToPluginInstance() one particular instance may not
|
||||
fulfill a given role. You can use PlaybackRendererInterface::isValid() to see if this
|
||||
interface was provided by the instance.
|
||||
*/
|
||||
PlaybackRendererInterface getPlaybackRendererInterface() const;
|
||||
|
||||
/** Returns the EditorRendererInterface for the extension instance.
|
||||
|
||||
Depending on what roles were passed into
|
||||
ARAHostDocumentController::bindDocumentToPluginInstance() one particular instance may not
|
||||
fulfill a given role. You can use EditorRendererInterface::isValid() to see if this
|
||||
interface was provided by the instance.
|
||||
*/
|
||||
EditorRendererInterface getEditorRendererInterface() const;
|
||||
|
||||
/** Returns false if the PlugInExtensionInstance was default constructed and represents
|
||||
no binding to an ARAHostDocumentController.
|
||||
*/
|
||||
bool isValid() const noexcept { return instance != nullptr; }
|
||||
|
||||
private:
|
||||
const ARA::ARAPlugInExtensionInstance* instance = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ARAHostModel
|
||||
|
||||
//==============================================================================
|
||||
/** Wrapper class for `ARA::Host::DocumentController`.
|
||||
|
||||
In order to create an ARAHostDocumentController from an ARAFactoryWrapper you must
|
||||
provide at least two mandatory host side interfaces. You can create these implementations
|
||||
by inheriting from the base classes in the `ARA::Host` namespace.
|
||||
|
||||
@tags{ARA}
|
||||
*/
|
||||
class ARAHostDocumentController final
|
||||
{
|
||||
public:
|
||||
/** Factory function.
|
||||
|
||||
You must check if the returned pointer is valid.
|
||||
*/
|
||||
static std::unique_ptr<ARAHostDocumentController>
|
||||
create (ARAFactoryWrapper factory,
|
||||
const String& documentName,
|
||||
std::unique_ptr<ARA::Host::AudioAccessControllerInterface> audioAccessController,
|
||||
std::unique_ptr<ARA::Host::ArchivingControllerInterface> archivingController,
|
||||
std::unique_ptr<ARA::Host::ContentAccessControllerInterface> contentAccessController = nullptr,
|
||||
std::unique_ptr<ARA::Host::ModelUpdateControllerInterface> modelUpdateController = nullptr,
|
||||
std::unique_ptr<ARA::Host::PlaybackControllerInterface> playbackController = nullptr);
|
||||
|
||||
~ARAHostDocumentController();
|
||||
|
||||
/** Returns the underlying ARA::Host::DocumentController reference. */
|
||||
ARA::Host::DocumentController& getDocumentController() const;
|
||||
|
||||
/** Binds the ARAHostDocumentController and its enclosed document to a plugin instance.
|
||||
|
||||
The resulting ARAHostModel::PlugInExtensionInstance is responsible for fulfilling the
|
||||
ARA specific roles of the plugin.
|
||||
|
||||
A single DocumentController can be bound to multiple plugin instances, which is a typical
|
||||
practice among hosts.
|
||||
*/
|
||||
ARAHostModel::PlugInExtensionInstance bindDocumentToPluginInstance (AudioPluginInstance& instance,
|
||||
ARA::ARAPlugInInstanceRoleFlags knownRoles,
|
||||
ARA::ARAPlugInInstanceRoleFlags assignedRoles);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
|
||||
explicit ARAHostDocumentController (std::unique_ptr<Impl>&& implIn);
|
||||
};
|
||||
|
||||
/** Calls the provided callback with an ARAFactoryWrapper object obtained from the provided
|
||||
AudioPluginInstance.
|
||||
|
||||
If the provided AudioPluginInstance has no ARA extensions, the callback will be called with an
|
||||
ARAFactoryWrapper that wraps a nullptr.
|
||||
|
||||
The object passed to the callback must be checked even if the plugin instance reports having
|
||||
ARA extensions.
|
||||
*/
|
||||
void createARAFactoryAsync (AudioPluginInstance& instance, std::function<void (ARAFactoryWrapper)> cb);
|
||||
|
||||
} // namespace juce
|
||||
|
||||
//==============================================================================
|
||||
#undef ARA_REF
|
||||
#undef ARA_HOST_REF
|
||||
|
||||
#endif
|
||||
|
|
@ -47,6 +47,7 @@ public:
|
|||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
|
||||
bool doesPluginStillExist (const PluginDescription&) override;
|
||||
FileSearchPath getDefaultLocationsToSearch() override;
|
||||
void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
|||
#include <AudioUnit/AUCocoaUIView.h>
|
||||
#include <CoreAudioKit/AUGenericView.h>
|
||||
#include <AudioToolbox/AudioUnitUtilities.h>
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA
|
||||
#include <ARA_API/ARAAudioUnit.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#include <CoreMIDI/MIDIServices.h>
|
||||
|
|
@ -422,6 +427,197 @@ namespace AudioUnitFormatHelpers
|
|||
}
|
||||
}
|
||||
|
||||
static bool hasARAExtension (AudioUnit audioUnit)
|
||||
{
|
||||
#if JUCE_PLUGINHOST_ARA
|
||||
UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory);
|
||||
Boolean isWriteable = FALSE;
|
||||
|
||||
OSStatus status = AudioUnitGetPropertyInfo (audioUnit,
|
||||
ARA::kAudioUnitProperty_ARAFactory,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&propertySize,
|
||||
&isWriteable);
|
||||
|
||||
if ((status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable)
|
||||
return true;
|
||||
#else
|
||||
ignoreUnused (audioUnit);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct AudioUnitDeleter
|
||||
{
|
||||
void operator() (AudioUnit au) const { AudioComponentInstanceDispose (au); }
|
||||
};
|
||||
|
||||
using AudioUnitUniquePtr = std::unique_ptr<std::remove_pointer_t<AudioUnit>, AudioUnitDeleter>;
|
||||
using AudioUnitSharedPtr = std::shared_ptr<std::remove_pointer_t<AudioUnit>>;
|
||||
using AudioUnitWeakPtr = std::weak_ptr<std::remove_pointer_t<AudioUnit>>;
|
||||
|
||||
static std::shared_ptr<const ARA::ARAFactory> getARAFactory (AudioUnitSharedPtr audioUnit)
|
||||
{
|
||||
#if JUCE_PLUGINHOST_ARA
|
||||
jassert (audioUnit != nullptr);
|
||||
|
||||
UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory);
|
||||
ARA::ARAAudioUnitFactory audioUnitFactory { ARA::kARAAudioUnitMagic, nullptr };
|
||||
|
||||
if (hasARAExtension (audioUnit.get()))
|
||||
{
|
||||
OSStatus status = AudioUnitGetProperty (audioUnit.get(),
|
||||
ARA::kAudioUnitProperty_ARAFactory,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&audioUnitFactory,
|
||||
&propertySize);
|
||||
|
||||
if ((status == noErr)
|
||||
&& (propertySize == sizeof (ARA::ARAAudioUnitFactory))
|
||||
&& (audioUnitFactory.inOutMagicNumber == ARA::kARAAudioUnitMagic))
|
||||
{
|
||||
jassert (audioUnitFactory.outFactory != nullptr);
|
||||
return getOrCreateARAFactory (audioUnitFactory.outFactory,
|
||||
[owningAuPtr = std::move (audioUnit)] (const ARA::ARAFactory*) {});
|
||||
}
|
||||
}
|
||||
#else
|
||||
ignoreUnused (audioUnit);
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
struct VersionedAudioComponent
|
||||
{
|
||||
AudioComponent audioComponent = nullptr;
|
||||
bool isAUv3 = false;
|
||||
|
||||
bool operator< (const VersionedAudioComponent& other) const { return audioComponent < other.audioComponent; }
|
||||
};
|
||||
|
||||
using AudioUnitCreationCallback = std::function<void (AudioUnit, OSStatus)>;
|
||||
|
||||
static void createAudioUnit (VersionedAudioComponent versionedComponent, AudioUnitCreationCallback callback)
|
||||
{
|
||||
struct AUAsyncInitializationCallback
|
||||
{
|
||||
typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus);
|
||||
|
||||
explicit AUAsyncInitializationCallback (AudioUnitCreationCallback inOriginalCallback)
|
||||
: originalCallback (std::move (inOriginalCallback))
|
||||
{
|
||||
block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion);
|
||||
}
|
||||
|
||||
AUCompletionCallbackBlock getBlock() noexcept { return block; }
|
||||
|
||||
void completion (AudioComponentInstance audioUnit, OSStatus err)
|
||||
{
|
||||
originalCallback (audioUnit, err);
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
double sampleRate;
|
||||
int framesPerBuffer;
|
||||
AudioUnitCreationCallback originalCallback;
|
||||
|
||||
ObjCBlock<AUCompletionCallbackBlock> block;
|
||||
};
|
||||
|
||||
auto callbackBlock = new AUAsyncInitializationCallback (std::move (callback));
|
||||
|
||||
if (versionedComponent.isAUv3)
|
||||
{
|
||||
if (@available (macOS 10.11, *))
|
||||
{
|
||||
AudioComponentInstantiate (versionedComponent.audioComponent, kAudioComponentInstantiation_LoadOutOfProcess,
|
||||
callbackBlock->getBlock());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AudioComponentInstance audioUnit;
|
||||
auto err = AudioComponentInstanceNew (versionedComponent.audioComponent, &audioUnit);
|
||||
callbackBlock->completion (err != noErr ? nullptr : audioUnit, err);
|
||||
}
|
||||
|
||||
struct AudioComponentResult
|
||||
{
|
||||
explicit AudioComponentResult (String error) : errorMessage (std::move (error)) {}
|
||||
|
||||
explicit AudioComponentResult (VersionedAudioComponent auComponent) : component (std::move (auComponent)) {}
|
||||
|
||||
bool isValid() const { return component.audioComponent != nullptr; }
|
||||
|
||||
VersionedAudioComponent component;
|
||||
String errorMessage;
|
||||
};
|
||||
|
||||
static AudioComponentResult getAudioComponent (AudioUnitPluginFormat& format, const PluginDescription& desc)
|
||||
{
|
||||
using namespace AudioUnitFormatHelpers;
|
||||
|
||||
AudioUnitPluginFormat audioUnitPluginFormat;
|
||||
|
||||
if (! format.fileMightContainThisPluginType (desc.fileOrIdentifier))
|
||||
return AudioComponentResult { NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in") };
|
||||
|
||||
String pluginName, version, manufacturer;
|
||||
AudioComponentDescription componentDesc;
|
||||
AudioComponent auComponent;
|
||||
String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description");
|
||||
|
||||
if (! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)
|
||||
&& ! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer))
|
||||
{
|
||||
return AudioComponentResult { errMessage };
|
||||
}
|
||||
|
||||
if ((auComponent = AudioComponentFindNext (nullptr, &componentDesc)) == nullptr)
|
||||
{
|
||||
return AudioComponentResult { errMessage };
|
||||
}
|
||||
|
||||
if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr)
|
||||
{
|
||||
return AudioComponentResult { errMessage };
|
||||
}
|
||||
|
||||
const bool isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc);
|
||||
|
||||
return AudioComponentResult { { auComponent, isAUv3 } };
|
||||
}
|
||||
|
||||
static void getOrCreateARAAudioUnit (VersionedAudioComponent auComponent, std::function<void (AudioUnitSharedPtr)> callback)
|
||||
{
|
||||
static std::map<VersionedAudioComponent, AudioUnitWeakPtr> audioUnitARACache;
|
||||
|
||||
if (auto audioUnit = audioUnitARACache[auComponent].lock())
|
||||
{
|
||||
callback (std::move (audioUnit));
|
||||
return;
|
||||
}
|
||||
|
||||
createAudioUnit (auComponent, [auComponent, cb = std::move (callback)] (AudioUnit audioUnit, OSStatus err)
|
||||
{
|
||||
cb ([auComponent, audioUnit, err]() -> AudioUnitSharedPtr
|
||||
{
|
||||
if (err != noErr)
|
||||
return nullptr;
|
||||
|
||||
AudioUnitSharedPtr auPtr { AudioUnitUniquePtr { audioUnit } };
|
||||
audioUnitARACache[auComponent] = auPtr;
|
||||
return auPtr;
|
||||
}());
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AudioUnitPluginWindowCocoa;
|
||||
|
||||
|
|
@ -974,6 +1170,23 @@ public:
|
|||
desc.numInputChannels = getTotalNumInputChannels();
|
||||
desc.numOutputChannels = getTotalNumOutputChannels();
|
||||
desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice);
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA
|
||||
desc.hasARAExtension = [&]
|
||||
{
|
||||
UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory);
|
||||
Boolean isWriteable = FALSE;
|
||||
|
||||
OSStatus status = AudioUnitGetPropertyInfo (audioUnit,
|
||||
ARA::kAudioUnitProperty_ARAFactory,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&propertySize,
|
||||
&isWriteable);
|
||||
|
||||
return (status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable;
|
||||
}();
|
||||
#endif
|
||||
}
|
||||
|
||||
void getExtensions (ExtensionsVisitor& visitor) const override
|
||||
|
|
@ -988,6 +1201,33 @@ public:
|
|||
};
|
||||
|
||||
visitor.visitAudioUnitClient (Extensions { this });
|
||||
|
||||
#ifdef JUCE_PLUGINHOST_ARA
|
||||
struct ARAExtensions : public ExtensionsVisitor::ARAClient
|
||||
{
|
||||
explicit ARAExtensions (const AudioUnitPluginInstance* instanceIn) : instance (instanceIn) {}
|
||||
|
||||
void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const override
|
||||
{
|
||||
getOrCreateARAAudioUnit ({ instance->auComponent, instance->isAUv3 },
|
||||
[origCb = std::move (cb)] (auto dylibKeepAliveAudioUnit)
|
||||
{
|
||||
origCb ([&]() -> ARAFactoryWrapper
|
||||
{
|
||||
if (dylibKeepAliveAudioUnit != nullptr)
|
||||
return ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) };
|
||||
|
||||
return ARAFactoryWrapper { nullptr };
|
||||
}());
|
||||
});
|
||||
}
|
||||
|
||||
const AudioUnitPluginInstance* instance = nullptr;
|
||||
};
|
||||
|
||||
if (hasARAExtension (audioUnit))
|
||||
visitor.visitARAClient (ARAExtensions (this));
|
||||
#endif
|
||||
}
|
||||
|
||||
void* getPlatformSpecificData() override { return audioUnit; }
|
||||
|
|
@ -2639,95 +2879,54 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc,
|
|||
double rate, int blockSize,
|
||||
PluginCreationCallback callback)
|
||||
{
|
||||
using namespace AudioUnitFormatHelpers;
|
||||
auto auComponentResult = getAudioComponent (*this, desc);
|
||||
|
||||
if (fileMightContainThisPluginType (desc.fileOrIdentifier))
|
||||
if (! auComponentResult.isValid())
|
||||
{
|
||||
String pluginName, version, manufacturer;
|
||||
AudioComponentDescription componentDesc;
|
||||
AudioComponent auComponent;
|
||||
String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description");
|
||||
|
||||
if ((! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer))
|
||||
&& (! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)))
|
||||
{
|
||||
callback (nullptr, errMessage);
|
||||
callback (nullptr, std::move (auComponentResult.errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((auComponent = AudioComponentFindNext (nullptr, &componentDesc)) == nullptr)
|
||||
{
|
||||
callback (nullptr, errMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr)
|
||||
{
|
||||
callback (nullptr, errMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
struct AUAsyncInitializationCallback
|
||||
{
|
||||
typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus);
|
||||
|
||||
AUAsyncInitializationCallback (double inSampleRate, int inFramesPerBuffer,
|
||||
PluginCreationCallback inOriginalCallback)
|
||||
: sampleRate (inSampleRate), framesPerBuffer (inFramesPerBuffer),
|
||||
originalCallback (std::move (inOriginalCallback))
|
||||
{
|
||||
block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion);
|
||||
}
|
||||
|
||||
AUCompletionCallbackBlock getBlock() noexcept { return block; }
|
||||
|
||||
void completion (AudioComponentInstance audioUnit, OSStatus err)
|
||||
createAudioUnit (auComponentResult.component,
|
||||
[rate, blockSize, origCallback = std::move (callback)] (AudioUnit audioUnit, OSStatus err)
|
||||
{
|
||||
if (err == noErr)
|
||||
{
|
||||
std::unique_ptr<AudioUnitPluginInstance> instance (new AudioUnitPluginInstance (audioUnit));
|
||||
auto instance = std::make_unique<AudioUnitPluginInstance> (audioUnit);
|
||||
|
||||
if (instance->initialise (sampleRate, framesPerBuffer))
|
||||
originalCallback (std::move (instance), {});
|
||||
if (instance->initialise (rate, blockSize))
|
||||
origCallback (std::move (instance), {});
|
||||
else
|
||||
originalCallback (nullptr, NEEDS_TRANS ("Unable to initialise the AudioUnit plug-in"));
|
||||
origCallback (nullptr, NEEDS_TRANS ("Unable to initialise the AudioUnit plug-in"));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto errMsg = TRANS ("An OS error occurred during initialisation of the plug-in (XXX)");
|
||||
originalCallback (nullptr, errMsg.replace ("XXX", String (err)));
|
||||
origCallback (nullptr, errMsg.replace ("XXX", String (err)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
void AudioUnitPluginFormat::createARAFactoryAsync (const PluginDescription& desc, ARAFactoryCreationCallback callback)
|
||||
{
|
||||
auto auComponentResult = getAudioComponent (*this, desc);
|
||||
|
||||
double sampleRate;
|
||||
int framesPerBuffer;
|
||||
PluginCreationCallback originalCallback;
|
||||
ObjCBlock<AUCompletionCallbackBlock> block;
|
||||
};
|
||||
|
||||
auto callbackBlock = new AUAsyncInitializationCallback (rate, blockSize, std::move (callback));
|
||||
|
||||
if (AudioUnitFormatHelpers::isPluginAUv3 (componentDesc))
|
||||
if (! auComponentResult.isValid())
|
||||
{
|
||||
if (@available (macOS 10.11, *))
|
||||
{
|
||||
AudioComponentInstantiate (auComponent, kAudioComponentInstantiation_LoadOutOfProcess,
|
||||
callbackBlock->getBlock());
|
||||
|
||||
callback ({ {}, "Failed to create AudioComponent for " + desc.descriptiveName });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AudioComponentInstance audioUnit;
|
||||
auto err = AudioComponentInstanceNew(auComponent, &audioUnit);
|
||||
callbackBlock->completion (err != noErr ? nullptr : audioUnit, err);
|
||||
}
|
||||
else
|
||||
getOrCreateARAAudioUnit (auComponentResult.component, [cb = std::move (callback)] (auto dylibKeepAliveAudioUnit)
|
||||
{
|
||||
callback (nullptr, NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in"));
|
||||
}
|
||||
cb ([&]() -> ARAFactoryResult
|
||||
{
|
||||
if (dylibKeepAliveAudioUnit != nullptr)
|
||||
return { ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }, "" };
|
||||
|
||||
return { {}, "Failed to create ARAFactory from the provided AudioUnit" };
|
||||
}());
|
||||
});
|
||||
}
|
||||
|
||||
bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const
|
||||
|
|
|
|||
|
|
@ -20,6 +20,18 @@
|
|||
|
||||
#include "juce_VST3Headers.h"
|
||||
#include "juce_VST3Common.h"
|
||||
#include "juce_ARACommon.h"
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
#include <ARA_API/ARAVST3.h>
|
||||
|
||||
namespace ARA
|
||||
{
|
||||
DEF_CLASS_IID (IMainFactory)
|
||||
DEF_CLASS_IID (IPlugInEntryPoint)
|
||||
DEF_CLASS_IID (IPlugInEntryPoint2)
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
|
@ -804,6 +816,20 @@ struct DescriptionFactory
|
|||
|
||||
auto numClasses = factory->countClasses();
|
||||
|
||||
// Every ARA::IMainFactory must have a matching Steinberg::IComponent.
|
||||
// The match is determined by the two classes having the same name.
|
||||
std::unordered_set<String> araMainFactoryClassNames;
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
for (Steinberg::int32 i = 0; i < numClasses; ++i)
|
||||
{
|
||||
PClassInfo info;
|
||||
factory->getClassInfo (i, &info);
|
||||
if (std::strcmp (info.category, kARAMainFactoryClass) == 0)
|
||||
araMainFactoryClassNames.insert (info.name);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (Steinberg::int32 i = 0; i < numClasses; ++i)
|
||||
{
|
||||
PClassInfo info;
|
||||
|
|
@ -867,6 +893,9 @@ struct DescriptionFactory
|
|||
}
|
||||
}
|
||||
|
||||
if (araMainFactoryClassNames.find (name) != araMainFactoryClassNames.end())
|
||||
desc.hasARAExtension = true;
|
||||
|
||||
if (desc.uniqueId != 0)
|
||||
result = performOnDescription (desc);
|
||||
|
||||
|
|
@ -1330,6 +1359,72 @@ private:
|
|||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle)
|
||||
};
|
||||
|
||||
template <typename Type, size_t N>
|
||||
static int compareWithString (Type (&charArray)[N], const String& str)
|
||||
{
|
||||
return std::strncmp (str.toRawUTF8(),
|
||||
charArray,
|
||||
std::min (str.getNumBytesAsUTF8(), (size_t) numElementsInArray (charArray)));
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
static void forEachARAFactory (IPluginFactory* pluginFactory, Callback&& cb)
|
||||
{
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
const auto numClasses = pluginFactory->countClasses();
|
||||
for (Steinberg::int32 i = 0; i < numClasses; ++i)
|
||||
{
|
||||
PClassInfo info;
|
||||
pluginFactory->getClassInfo (i, &info);
|
||||
|
||||
if (std::strcmp (info.category, kARAMainFactoryClass) == 0)
|
||||
{
|
||||
const bool keepGoing = cb (info);
|
||||
if (! keepGoing)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
ignoreUnused (pluginFactory, cb);
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::shared_ptr<const ARA::ARAFactory> getARAFactory (Steinberg::IPluginFactory* pluginFactory, const String& pluginName)
|
||||
{
|
||||
std::shared_ptr<const ARA::ARAFactory> factory;
|
||||
|
||||
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
|
||||
forEachARAFactory (pluginFactory,
|
||||
[&pluginFactory, &pluginName, &factory] (const auto& pcClassInfo)
|
||||
{
|
||||
if (compareWithString (pcClassInfo.name, pluginName) == 0)
|
||||
{
|
||||
ARA::IMainFactory* source;
|
||||
if (pluginFactory->createInstance (pcClassInfo.cid, ARA::IMainFactory::iid, (void**) &source)
|
||||
== Steinberg::kResultOk)
|
||||
{
|
||||
factory = getOrCreateARAFactory (source->getFactory(),
|
||||
[source] (const ARA::ARAFactory*) { source->release(); });
|
||||
return false;
|
||||
}
|
||||
jassert (source == nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
#else
|
||||
ignoreUnused (pluginFactory, pluginName);
|
||||
#endif
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const ARA::ARAFactory> getARAFactory (VST3ModuleHandle& module)
|
||||
{
|
||||
auto* pluginFactory = module.getPluginFactory();
|
||||
return getARAFactory (pluginFactory, module.getName());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct VST3PluginWindow : public AudioProcessorEditor,
|
||||
private ComponentMovementWatcher,
|
||||
|
|
@ -1677,6 +1772,27 @@ private:
|
|||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods
|
||||
|
||||
//==============================================================================
|
||||
static bool hasARAExtension (IPluginFactory* pluginFactory, const String& pluginClassName)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
forEachARAFactory (pluginFactory,
|
||||
[&pluginClassName, &result] (const auto& pcClassInfo)
|
||||
{
|
||||
if (compareWithString (pcClassInfo.name, pluginClassName) == 0)
|
||||
{
|
||||
result = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct VST3ComponentHolder
|
||||
{
|
||||
|
|
@ -1802,6 +1918,8 @@ struct VST3ComponentHolder
|
|||
totalNumInputChannels,
|
||||
totalNumOutputChannels);
|
||||
|
||||
description.hasARAExtension = hasARAExtension (factory, description.name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2280,7 +2398,8 @@ public:
|
|||
|
||||
void getExtensions (ExtensionsVisitor& visitor) const override
|
||||
{
|
||||
struct Extensions : public ExtensionsVisitor::VST3Client
|
||||
struct Extensions : public ExtensionsVisitor::VST3Client,
|
||||
public ExtensionsVisitor::ARAClient
|
||||
{
|
||||
explicit Extensions (const VST3PluginInstance* instanceIn) : instance (instanceIn) {}
|
||||
|
||||
|
|
@ -2293,10 +2412,21 @@ public:
|
|||
return instance->setStateFromPresetFile (rawData);
|
||||
}
|
||||
|
||||
void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const noexcept override
|
||||
{
|
||||
cb (ARAFactoryWrapper { ::juce::getARAFactory (*(instance->holder->module)) });
|
||||
}
|
||||
|
||||
const VST3PluginInstance* instance = nullptr;
|
||||
};
|
||||
|
||||
visitor.visitVST3Client (Extensions { this });
|
||||
Extensions extensions { this };
|
||||
visitor.visitVST3Client (extensions);
|
||||
|
||||
if (::juce::getARAFactory (*(holder->module)))
|
||||
{
|
||||
visitor.visitARAClient (extensions);
|
||||
}
|
||||
}
|
||||
|
||||
void* getPlatformSpecificData() override { return holder->component; }
|
||||
|
|
@ -3107,7 +3237,7 @@ private:
|
|||
if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0)
|
||||
bypassParam = param;
|
||||
|
||||
std::function<AudioProcessorParameterGroup*(Vst::UnitID)> findOrCreateGroup;
|
||||
std::function<AudioProcessorParameterGroup* (Vst::UnitID)> findOrCreateGroup;
|
||||
findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID)
|
||||
{
|
||||
auto existingGroup = groupMap.find (groupID);
|
||||
|
|
@ -3669,6 +3799,22 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& resul
|
|||
}
|
||||
}
|
||||
|
||||
void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& description, ARAFactoryCreationCallback callback)
|
||||
{
|
||||
if (! description.hasARAExtension)
|
||||
{
|
||||
jassertfalse;
|
||||
callback ({ {}, "The provided plugin does not support ARA features" });
|
||||
}
|
||||
|
||||
File file (description.fileOrIdentifier);
|
||||
VSTComSmartPtr<IPluginFactory> pluginFactory (
|
||||
DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory());
|
||||
const auto* pluginName = description.name.toRawUTF8();
|
||||
|
||||
callback ({ ARAFactoryWrapper { ::juce::getARAFactory (pluginFactory, pluginName) }, {} });
|
||||
}
|
||||
|
||||
void VST3PluginFormat::createPluginInstance (const PluginDescription& description,
|
||||
double, int, PluginCreationCallback callback)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ public:
|
|||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
|
||||
bool doesPluginStillExist (const PluginDescription&) override;
|
||||
FileSearchPath getDefaultLocationsToSearch() override;
|
||||
void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -199,10 +199,12 @@ private:
|
|||
#include "processors/juce_AudioProcessorGraph.cpp"
|
||||
#include "processors/juce_GenericAudioProcessorEditor.cpp"
|
||||
#include "processors/juce_PluginDescription.cpp"
|
||||
#include "format_types/juce_ARACommon.cpp"
|
||||
#include "format_types/juce_LADSPAPluginFormat.cpp"
|
||||
#include "format_types/juce_VSTPluginFormat.cpp"
|
||||
#include "format_types/juce_VST3PluginFormat.cpp"
|
||||
#include "format_types/juce_AudioUnitPluginFormat.mm"
|
||||
#include "format_types/juce_ARAHosting.cpp"
|
||||
#include "scanning/juce_KnownPluginList.cpp"
|
||||
#include "scanning/juce_PluginDirectoryScanner.cpp"
|
||||
#include "scanning/juce_PluginListComponent.cpp"
|
||||
|
|
|
|||
|
|
@ -94,6 +94,17 @@
|
|||
#define JUCE_PLUGINHOST_LV2 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_PLUGINHOST_ARA
|
||||
Enables the ARA plugin extension hosting classes. You will need to download the ARA SDK and specify the
|
||||
path to it either in the Projucer, using juce_set_ara_sdk_path() in your CMake project file.
|
||||
|
||||
The directory can be obtained by recursively cloning https://github.com/Celemony/ARA_SDK and checking out
|
||||
the tag releases/2.1.0.
|
||||
*/
|
||||
#ifndef JUCE_PLUGINHOST_ARA
|
||||
#define JUCE_PLUGINHOST_ARA 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_CUSTOM_VST3_SDK
|
||||
If enabled, the embedded VST3 SDK in JUCE will not be added to the project and instead you should
|
||||
add the path to your custom VST3 SDK to the project's header search paths. Most users shouldn't
|
||||
|
|
@ -115,6 +126,7 @@
|
|||
#include "utilities/juce_VSTCallbackHandler.h"
|
||||
#include "utilities/juce_VST3ClientExtensions.h"
|
||||
#include "utilities/juce_NativeScaleFactorNotifier.h"
|
||||
#include "format_types/juce_ARACommon.h"
|
||||
#include "utilities/juce_ExtensionsVisitor.h"
|
||||
#include "processors/juce_AudioProcessorParameter.h"
|
||||
#include "processors/juce_HostedAudioProcessorParameter.h"
|
||||
|
|
@ -136,6 +148,7 @@
|
|||
#include "format_types/juce_VST3PluginFormat.h"
|
||||
#include "format_types/juce_VSTMidiEventList.h"
|
||||
#include "format_types/juce_VSTPluginFormat.h"
|
||||
#include "format_types/juce_ARAHosting.h"
|
||||
#include "scanning/juce_PluginDirectoryScanner.h"
|
||||
#include "scanning/juce_PluginListComponent.h"
|
||||
#include "utilities/juce_AudioProcessorParameterWithID.h"
|
||||
|
|
|
|||
33
modules/juce_audio_processors/juce_audio_processors_ara.cpp
Normal file
33
modules/juce_audio_processors/juce_audio_processors_ara.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE 7 technical preview.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
You may use this code under the terms of the GPL v3
|
||||
(see www.gnu.org/licenses).
|
||||
|
||||
For the technical preview this file cannot be licensed commercially.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include <juce_core/system/juce_CompilerWarnings.h>
|
||||
#include <juce_core/system/juce_TargetPlatform.h>
|
||||
|
||||
/* Having WIN32_LEAN_AND_MEAN defined at the point of including ARADebug.c will produce warnings.
|
||||
|
||||
To prevent such problems it's easiest to have it in its own translation unit.
|
||||
*/
|
||||
|
||||
#if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS))
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes")
|
||||
#include <ARA_Library/Debug/ARADebug.c>
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
#endif
|
||||
|
|
@ -72,6 +72,7 @@ std::unique_ptr<XmlElement> PluginDescription::createXml() const
|
|||
e->setAttribute ("numInputs", numInputChannels);
|
||||
e->setAttribute ("numOutputs", numOutputChannels);
|
||||
e->setAttribute ("isShell", hasSharedContainer);
|
||||
e->setAttribute ("hasARAExtension", hasARAExtension);
|
||||
|
||||
e->setAttribute ("uid", String::toHexString (deprecatedUid));
|
||||
|
||||
|
|
@ -95,6 +96,7 @@ bool PluginDescription::loadFromXml (const XmlElement& xml)
|
|||
numInputChannels = xml.getIntAttribute ("numInputs");
|
||||
numOutputChannels = xml.getIntAttribute ("numOutputs");
|
||||
hasSharedContainer = xml.getBoolAttribute ("isShell", false);
|
||||
hasARAExtension = xml.getBoolAttribute ("hasARAExtension", false);
|
||||
|
||||
deprecatedUid = xml.getStringAttribute ("uid").getHexValue32();
|
||||
uniqueId = xml.getStringAttribute ("uniqueId", "0").getHexValue32();
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ public:
|
|||
/** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */
|
||||
bool hasSharedContainer = false;
|
||||
|
||||
/** True if the plug-in is ARA enabled and can supply a valid ARAFactoryWrapper. */
|
||||
bool hasARAExtension = false;
|
||||
|
||||
/** Returns true if the two descriptions refer to the same plug-in.
|
||||
|
||||
This isn't quite as simple as them just having the same file (because of
|
||||
|
|
|
|||
|
|
@ -106,6 +106,13 @@ struct ExtensionsVisitor
|
|||
virtual AEffect* getAEffectPtr() const noexcept = 0;
|
||||
};
|
||||
|
||||
/** Can be used to retrieve information about a plugin that provides ARA extensions. */
|
||||
struct ARAClient
|
||||
{
|
||||
virtual ~ARAClient() = default;
|
||||
virtual void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)>) const = 0;
|
||||
};
|
||||
|
||||
virtual ~ExtensionsVisitor() = default;
|
||||
|
||||
/** Will be called if there is no platform specific information available. */
|
||||
|
|
@ -119,6 +126,9 @@ struct ExtensionsVisitor
|
|||
|
||||
/** Called with AU-specific information. */
|
||||
virtual void visitAudioUnitClient (const AudioUnitClient&) {}
|
||||
|
||||
/** Called with ARA-specific information. */
|
||||
virtual void visitARAClient (const ARAClient&) {}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue