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)
|
add_subdirectory(extras/Build)
|
||||||
|
|
||||||
# If you want to build the JUCE examples with VST2/AAX support, you'll need to make the VST2/AAX
|
# If you want to build the JUCE examples with VST2/AAX/ARA support, you'll need to make the
|
||||||
# headers visible to the juce_audio_processors module. You can either set the paths on the command
|
# VST2/AAX/ARA headers visible to the juce_audio_processors module. You can either set the paths on
|
||||||
# line, (e.g. -DJUCE_GLOBAL_AAX_SDK_PATH=/path/to/sdk) if you're just building the JUCE examples, or
|
# the command line, (e.g. -DJUCE_GLOBAL_AAX_SDK_PATH=/path/to/sdk) if you're just building the JUCE
|
||||||
# you can call the `juce_set_*_sdk_path` functions in your own CMakeLists after importing 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)
|
if(JUCE_GLOBAL_AAX_SDK_PATH)
|
||||||
juce_set_aax_sdk_path("${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}")
|
juce_set_vst2_sdk_path("${JUCE_GLOBAL_VST2_SDK_PATH}")
|
||||||
endif()
|
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
|
# 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
|
# 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
|
# 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_aax_sdk_path(<absolute path>)
|
||||||
juce_set_vst2_sdk_path(<absolute path>)
|
juce_set_vst2_sdk_path(<absolute path>)
|
||||||
juce_set_vst3_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
|
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 SDKs
|
functions should be called *before* adding any targets that may depend on the AAX/VST2/VST3/ARA SDKs
|
||||||
(plugin hosts, AAX/VST2/VST3 plugins etc.).
|
(plugin hosts, AAX/VST2/VST3/ARA plugins etc.).
|
||||||
|
|
||||||
#### `juce_add_module`
|
#### `juce_add_module`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ juce_generate_juce_header(AudioPluginHost)
|
||||||
|
|
||||||
target_sources(AudioPluginHost PRIVATE
|
target_sources(AudioPluginHost PRIVATE
|
||||||
Source/HostStartup.cpp
|
Source/HostStartup.cpp
|
||||||
|
Source/Plugins/ARAPlugin.cpp
|
||||||
Source/Plugins/IOConfigurationWindow.cpp
|
Source/Plugins/IOConfigurationWindow.cpp
|
||||||
Source/Plugins/InternalPlugins.cpp
|
Source/Plugins/InternalPlugins.cpp
|
||||||
Source/Plugins/PluginGraph.cpp
|
Source/Plugins/PluginGraph.cpp
|
||||||
|
|
@ -46,6 +47,7 @@ target_compile_definitions(AudioPluginHost PRIVATE
|
||||||
JUCE_PLUGINHOST_LV2=1
|
JUCE_PLUGINHOST_LV2=1
|
||||||
JUCE_PLUGINHOST_VST3=1
|
JUCE_PLUGINHOST_VST3=1
|
||||||
JUCE_PLUGINHOST_VST=0
|
JUCE_PLUGINHOST_VST=0
|
||||||
|
JUCE_PLUGINHOST_ARA=0
|
||||||
JUCE_USE_CAMERA=0
|
JUCE_USE_CAMERA=0
|
||||||
JUCE_USE_CDBURNER=0
|
JUCE_USE_CDBURNER=0
|
||||||
JUCE_USE_CDREADER=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;
|
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.getSampleRate(),
|
||||||
graph.getBlockSize(),
|
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,
|
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)
|
if (instance == nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -97,12 +99,21 @@ void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instan
|
||||||
}
|
}
|
||||||
else
|
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();
|
instance->enableAllBuses();
|
||||||
|
|
||||||
if (auto node = graph.addNode (std::move (instance)))
|
if (auto node = graph.addNode (std::move (instance)))
|
||||||
{
|
{
|
||||||
node->properties.set ("x", pos.x);
|
node->properties.set ("x", pos.x);
|
||||||
node->properties.set ("y", pos.y);
|
node->properties.set ("y", pos.y);
|
||||||
|
node->properties.set ("useARA", useARA == PluginDescriptionAndPreference::UseARA::yes);
|
||||||
changed();
|
changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,10 +204,10 @@ void PluginGraph::newDocument()
|
||||||
|
|
||||||
jassert (internalFormat.getAllTypes().size() > 3);
|
jassert (internalFormat.getAllTypes().size() > 3);
|
||||||
|
|
||||||
addPlugin (internalFormat.getAllTypes()[0], { 0.5, 0.1 });
|
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[0] }, { 0.5, 0.1 });
|
||||||
addPlugin (internalFormat.getAllTypes()[1], { 0.25, 0.1 });
|
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[1] }, { 0.25, 0.1 });
|
||||||
addPlugin (internalFormat.getAllTypes()[2], { 0.5, 0.9 });
|
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[2] }, { 0.5, 0.9 });
|
||||||
addPlugin (internalFormat.getAllTypes()[3], { 0.25, 0.9 });
|
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[3] }, { 0.25, 0.9 });
|
||||||
|
|
||||||
MessageManager::callAsync ([this]
|
MessageManager::callAsync ([this]
|
||||||
{
|
{
|
||||||
|
|
@ -325,6 +336,7 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep
|
||||||
e->setAttribute ("uid", (int) node->nodeID.uid);
|
e->setAttribute ("uid", (int) node->nodeID.uid);
|
||||||
e->setAttribute ("x", node->properties ["x"].toString());
|
e->setAttribute ("x", node->properties ["x"].toString());
|
||||||
e->setAttribute ("y", node->properties ["y"].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)
|
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)
|
void PluginGraph::createNodeFromXml (const XmlElement& xml)
|
||||||
{
|
{
|
||||||
PluginDescription pd;
|
PluginDescriptionAndPreference pd;
|
||||||
|
const auto nodeUsesARA = xml.getBoolAttribute ("useARA");
|
||||||
|
|
||||||
for (auto* e : xml.getChildIterator())
|
for (auto* e : xml.getChildIterator())
|
||||||
{
|
{
|
||||||
if (pd.loadFromXml (*e))
|
if (pd.pluginDescription.loadFromXml (*e))
|
||||||
|
{
|
||||||
|
pd.useARA = nodeUsesARA ? PluginDescriptionAndPreference::UseARA::yes
|
||||||
|
: PluginDescriptionAndPreference::UseARA::no;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance>
|
auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance>
|
||||||
{
|
{
|
||||||
auto createInstance = [this] (const PluginDescription& description)
|
auto createInstance = [this] (const PluginDescriptionAndPreference& description) -> std::unique_ptr<AudioPluginInstance>
|
||||||
{
|
{
|
||||||
String errorMessage;
|
String errorMessage;
|
||||||
|
|
||||||
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description);
|
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.pluginDescription);
|
||||||
|
|
||||||
return formatManager.createPluginInstance (description,
|
auto instance = formatManager.createPluginInstance (description.pluginDescription,
|
||||||
graph.getSampleRate(),
|
graph.getSampleRate(),
|
||||||
graph.getBlockSize(),
|
graph.getBlockSize(),
|
||||||
errorMessage);
|
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))
|
if (auto instance = createInstance (pd))
|
||||||
|
|
@ -392,19 +420,19 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml)
|
||||||
|
|
||||||
const auto allFormats = formatManager.getFormats();
|
const auto allFormats = formatManager.getFormats();
|
||||||
const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(),
|
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())
|
if (matchingFormat == allFormats.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat);
|
const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat);
|
||||||
const auto matchingPlugin = std::find_if (plugins.begin(), plugins.end(),
|
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())
|
if (matchingPlugin == plugins.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return createInstance (*matchingPlugin);
|
return createInstance (PluginDescriptionAndPreference { *matchingPlugin });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto instance = createInstanceWithFallback())
|
if (auto instance = createInstanceWithFallback())
|
||||||
|
|
@ -431,6 +459,7 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml)
|
||||||
|
|
||||||
node->properties.set ("x", xml.getDoubleAttribute ("x"));
|
node->properties.set ("x", xml.getDoubleAttribute ("x"));
|
||||||
node->properties.set ("y", xml.getDoubleAttribute ("y"));
|
node->properties.set ("y", xml.getDoubleAttribute ("y"));
|
||||||
|
node->properties.set ("useARA", xml.getBoolAttribute ("useARA"));
|
||||||
|
|
||||||
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
|
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,29 @@
|
||||||
|
|
||||||
#include "../UI/PluginWindow.h"
|
#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;
|
using NodeID = AudioProcessorGraph::NodeID;
|
||||||
|
|
||||||
void addPlugin (const PluginDescription&, Point<double>);
|
void addPlugin (const PluginDescriptionAndPreference&, Point<double>);
|
||||||
|
|
||||||
AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const;
|
AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const;
|
||||||
|
|
||||||
|
|
@ -85,7 +108,10 @@ private:
|
||||||
NodeID getNextUID() noexcept;
|
NodeID getNextUID() noexcept;
|
||||||
|
|
||||||
void createNodeFromXml (const XmlElement&);
|
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;
|
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginGraph)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginGraph)
|
||||||
|
|
|
||||||
|
|
@ -391,6 +391,14 @@ struct GraphEditorPanel::PluginComponent : public Component,
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isNodeUsingARA() const
|
||||||
|
{
|
||||||
|
if (auto node = graph.graph.getNodeForId (pluginID))
|
||||||
|
return node->properties["useARA"];
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void showPopupMenu()
|
void showPopupMenu()
|
||||||
{
|
{
|
||||||
menu.reset (new PopupMenu);
|
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 all parameters", [this] { showWindow (PluginWindow::Type::generic); });
|
||||||
menu->addItem ("Show debug log", [this] { showWindow (PluginWindow::Type::debug); });
|
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)
|
if (autoScaleOptionAvailable)
|
||||||
addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu);
|
addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu);
|
||||||
|
|
||||||
|
|
@ -777,7 +791,7 @@ void GraphEditorPanel::mouseDrag (const MouseEvent& e)
|
||||||
stopTimer();
|
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()));
|
graph.addPlugin (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight()));
|
||||||
}
|
}
|
||||||
|
|
@ -1273,7 +1287,7 @@ void GraphDocumentComponent::resized()
|
||||||
checkAvailableWidth();
|
checkAvailableWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphDocumentComponent::createNewPlugin (const PluginDescription& desc, Point<int> pos)
|
void GraphDocumentComponent::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos)
|
||||||
{
|
{
|
||||||
graphPanel->createNewPlugin (desc, pos);
|
graphPanel->createNewPlugin (desc, pos);
|
||||||
}
|
}
|
||||||
|
|
@ -1320,7 +1334,8 @@ void GraphDocumentComponent::itemDropped (const SourceDetails& details)
|
||||||
// must be a valid index!
|
// must be a valid index!
|
||||||
jassert (isPositiveAndBelow (pluginTypeIndex, pluginList.getNumTypes()));
|
jassert (isPositiveAndBelow (pluginTypeIndex, pluginList.getNumTypes()));
|
||||||
|
|
||||||
createNewPlugin (pluginList.getTypes()[pluginTypeIndex], details.localPosition);
|
createNewPlugin (PluginDescriptionAndPreference { pluginList.getTypes()[pluginTypeIndex] },
|
||||||
|
details.localPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphDocumentComponent::showSidePanel (bool showSettingsPanel)
|
void GraphDocumentComponent::showSidePanel (bool showSettingsPanel)
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,11 @@ class GraphEditorPanel : public Component,
|
||||||
private Timer
|
private Timer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
//==============================================================================
|
||||||
GraphEditorPanel (PluginGraph& graph);
|
GraphEditorPanel (PluginGraph& graph);
|
||||||
~GraphEditorPanel() override;
|
~GraphEditorPanel() override;
|
||||||
|
|
||||||
void createNewPlugin (const PluginDescription&, Point<int> position);
|
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
|
||||||
|
|
||||||
void paint (Graphics&) override;
|
void paint (Graphics&) override;
|
||||||
void resized() override;
|
void resized() override;
|
||||||
|
|
@ -103,7 +104,7 @@ public:
|
||||||
~GraphDocumentComponent() override;
|
~GraphDocumentComponent() override;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void createNewPlugin (const PluginDescription&, Point<int> position);
|
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
|
||||||
void setDoublePrecision (bool doublePrecision);
|
void setDoublePrecision (bool doublePrecision);
|
||||||
bool closeAnyOpenPluginWindows();
|
bool closeAnyOpenPluginWindows();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -576,7 +576,7 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuItemID) >= 0)
|
if (getIndexChosenByMenu (menuItemID) >= 0)
|
||||||
createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
|
createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
|
||||||
proportionOfHeight (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();
|
graphHolder->unfocusKeyboardComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos)
|
void MainHostWindow::createPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos)
|
||||||
{
|
{
|
||||||
if (graphHolder != nullptr)
|
if (graphHolder != nullptr)
|
||||||
graphHolder->createNewPlugin (desc, pos);
|
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)
|
void MainHostWindow::addPluginsToMenu (PopupMenu& m)
|
||||||
{
|
{
|
||||||
if (graphHolder != nullptr)
|
if (graphHolder != nullptr)
|
||||||
|
|
@ -606,7 +658,7 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m)
|
||||||
|
|
||||||
m.addSeparator();
|
m.addSeparator();
|
||||||
|
|
||||||
pluginDescriptions = knownPluginList.getTypes();
|
auto pluginDescriptions = knownPluginList.getTypes();
|
||||||
|
|
||||||
// This avoids showing the internal types again later on in the list
|
// This avoids showing the internal types again later on in the list
|
||||||
pluginDescriptions.removeIf ([] (PluginDescription& desc)
|
pluginDescriptions.removeIf ([] (PluginDescription& desc)
|
||||||
|
|
@ -614,15 +666,23 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m)
|
||||||
return desc.pluginFormatName == InternalPluginFormat::getIdentifier();
|
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()))
|
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)
|
for (int i = 0; i < jmin (5, typesFound.size()); ++i)
|
||||||
if (auto* desc = typesFound.getUnchecked(i))
|
if (auto* desc = typesFound.getUnchecked(i))
|
||||||
createPlugin (*desc, pos);
|
createPlugin (PluginDescriptionAndPreference { *desc }, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,10 +100,10 @@ public:
|
||||||
|
|
||||||
void tryToQuitApplication();
|
void tryToQuitApplication();
|
||||||
|
|
||||||
void createPlugin (const PluginDescription&, Point<int> pos);
|
void createPlugin (const PluginDescriptionAndPreference&, Point<int> pos);
|
||||||
|
|
||||||
void addPluginsToMenu (PopupMenu&);
|
void addPluginsToMenu (PopupMenu&);
|
||||||
PluginDescription getChosenType (int menuID) const;
|
PluginDescriptionAndPreference getChosenType (int menuID) const;
|
||||||
|
|
||||||
std::unique_ptr<GraphDocumentComponent> graphHolder;
|
std::unique_ptr<GraphDocumentComponent> graphHolder;
|
||||||
|
|
||||||
|
|
@ -117,6 +117,8 @@ private:
|
||||||
|
|
||||||
void showAudioSettings();
|
void showAudioSettings();
|
||||||
|
|
||||||
|
int getIndexChosenByMenu (int menuID) const;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
AudioDeviceManager deviceManager;
|
AudioDeviceManager deviceManager;
|
||||||
AudioPluginFormatManager formatManager;
|
AudioPluginFormatManager formatManager;
|
||||||
|
|
@ -124,7 +126,7 @@ private:
|
||||||
std::vector<PluginDescription> internalTypes;
|
std::vector<PluginDescription> internalTypes;
|
||||||
KnownPluginList knownPluginList;
|
KnownPluginList knownPluginList;
|
||||||
KnownPluginList::SortMethod pluginSortMethod;
|
KnownPluginList::SortMethod pluginSortMethod;
|
||||||
Array<PluginDescription> pluginDescriptions;
|
Array<PluginDescriptionAndPreference> pluginDescriptionsAndPreference;
|
||||||
|
|
||||||
class PluginListWindow;
|
class PluginListWindow;
|
||||||
std::unique_ptr<PluginListWindow> pluginListWindow;
|
std::unique_ptr<PluginListWindow> pluginListWindow;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../Plugins/IOConfigurationWindow.h"
|
#include "../Plugins/IOConfigurationWindow.h"
|
||||||
|
#include "../Plugins/ARAPlugin.h"
|
||||||
|
|
||||||
inline String getFormatSuffix (const AudioProcessor* plugin)
|
inline String getFormatSuffix (const AudioProcessor* plugin)
|
||||||
{
|
{
|
||||||
|
|
@ -148,6 +149,7 @@ public:
|
||||||
programs,
|
programs,
|
||||||
audioIO,
|
audioIO,
|
||||||
debug,
|
debug,
|
||||||
|
araHost,
|
||||||
numTypes
|
numTypes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -234,6 +236,16 @@ private:
|
||||||
type = PluginWindow::Type::generic;
|
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::generic) return new GenericAudioProcessorEditor (processor);
|
||||||
if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor);
|
if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor);
|
||||||
if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor);
|
if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor);
|
||||||
|
|
@ -252,6 +264,7 @@ private:
|
||||||
case Type::programs: return "Programs";
|
case Type::programs: return "Programs";
|
||||||
case Type::audioIO: return "IO";
|
case Type::audioIO: return "IO";
|
||||||
case Type::debug: return "Debug";
|
case Type::debug: return "Debug";
|
||||||
|
case Type::araHost: return "ARAHost";
|
||||||
case Type::numTypes:
|
case Type::numTypes:
|
||||||
default: return {};
|
default: return {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -502,9 +502,17 @@ function(juce_add_module module_path)
|
||||||
"${lv2_base_path}/lilv/src")
|
"${lv2_base_path}/lilv/src")
|
||||||
target_link_libraries(juce_audio_processors INTERFACE juce_lilv_headers)
|
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)
|
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_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_lilv_headers ALIAS juce_lilv_headers)
|
||||||
|
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_ara_headers ALIAS juce_ara_headers)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1969,6 +1969,22 @@ function(juce_set_vst3_sdk_path path)
|
||||||
target_include_directories(juce_vst3_sdk INTERFACE "${path}")
|
target_include_directories(juce_vst3_sdk INTERFACE "${path}")
|
||||||
endfunction()
|
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)
|
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. */
|
/** Returns true if instantiation of this plugin type must be done from a non-message thread. */
|
||||||
virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const = 0;
|
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:
|
protected:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
friend class AudioPluginFormatManager;
|
friend class AudioPluginFormatManager;
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,22 @@ std::unique_ptr<AudioPluginInstance> AudioPluginFormatManager::createPluginInsta
|
||||||
return {};
|
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,
|
void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description,
|
||||||
double initialSampleRate, int initialBufferSize,
|
double initialSampleRate, int initialBufferSize,
|
||||||
AudioPluginFormat::PluginCreationCallback callback)
|
AudioPluginFormat::PluginCreationCallback callback)
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,22 @@ public:
|
||||||
double initialSampleRate, int initialBufferSize,
|
double initialSampleRate, int initialBufferSize,
|
||||||
AudioPluginFormat::PluginCreationCallback callback);
|
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.
|
/** Checks that the file or component for this plugin actually still exists.
|
||||||
(This won't try to load the plugin)
|
(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;
|
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
|
||||||
bool doesPluginStillExist (const PluginDescription&) override;
|
bool doesPluginStillExist (const PluginDescription&) override;
|
||||||
FileSearchPath getDefaultLocationsToSearch() override;
|
FileSearchPath getDefaultLocationsToSearch() override;
|
||||||
|
void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||||
#include <AudioUnit/AUCocoaUIView.h>
|
#include <AudioUnit/AUCocoaUIView.h>
|
||||||
#include <CoreAudioKit/AUGenericView.h>
|
#include <CoreAudioKit/AUGenericView.h>
|
||||||
#include <AudioToolbox/AudioUnitUtilities.h>
|
#include <AudioToolbox/AudioUnitUtilities.h>
|
||||||
|
|
||||||
|
#if JUCE_PLUGINHOST_ARA
|
||||||
|
#include <ARA_API/ARAAudioUnit.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <CoreMIDI/MIDIServices.h>
|
#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;
|
class AudioUnitPluginWindowCocoa;
|
||||||
|
|
||||||
|
|
@ -974,6 +1170,23 @@ public:
|
||||||
desc.numInputChannels = getTotalNumInputChannels();
|
desc.numInputChannels = getTotalNumInputChannels();
|
||||||
desc.numOutputChannels = getTotalNumOutputChannels();
|
desc.numOutputChannels = getTotalNumOutputChannels();
|
||||||
desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice);
|
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
|
void getExtensions (ExtensionsVisitor& visitor) const override
|
||||||
|
|
@ -988,6 +1201,33 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
visitor.visitAudioUnitClient (Extensions { this });
|
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; }
|
void* getPlatformSpecificData() override { return audioUnit; }
|
||||||
|
|
@ -2639,95 +2879,54 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc,
|
||||||
double rate, int blockSize,
|
double rate, int blockSize,
|
||||||
PluginCreationCallback callback)
|
PluginCreationCallback callback)
|
||||||
{
|
{
|
||||||
using namespace AudioUnitFormatHelpers;
|
auto auComponentResult = getAudioComponent (*this, desc);
|
||||||
|
|
||||||
if (fileMightContainThisPluginType (desc.fileOrIdentifier))
|
if (! auComponentResult.isValid())
|
||||||
{
|
{
|
||||||
String pluginName, version, manufacturer;
|
callback (nullptr, std::move (auComponentResult.errorMessage));
|
||||||
AudioComponentDescription componentDesc;
|
return;
|
||||||
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);
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (err == noErr)
|
|
||||||
{
|
|
||||||
std::unique_ptr<AudioUnitPluginInstance> instance (new AudioUnitPluginInstance (audioUnit));
|
|
||||||
|
|
||||||
if (instance->initialise (sampleRate, framesPerBuffer))
|
|
||||||
originalCallback (std::move (instance), {});
|
|
||||||
else
|
|
||||||
originalCallback (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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
|
|
||||||
double sampleRate;
|
|
||||||
int framesPerBuffer;
|
|
||||||
PluginCreationCallback originalCallback;
|
|
||||||
ObjCBlock<AUCompletionCallbackBlock> block;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto callbackBlock = new AUAsyncInitializationCallback (rate, blockSize, std::move (callback));
|
|
||||||
|
|
||||||
if (AudioUnitFormatHelpers::isPluginAUv3 (componentDesc))
|
|
||||||
{
|
|
||||||
if (@available (macOS 10.11, *))
|
|
||||||
{
|
|
||||||
AudioComponentInstantiate (auComponent, kAudioComponentInstantiation_LoadOutOfProcess,
|
|
||||||
callbackBlock->getBlock());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioComponentInstance audioUnit;
|
|
||||||
auto err = AudioComponentInstanceNew(auComponent, &audioUnit);
|
|
||||||
callbackBlock->completion (err != noErr ? nullptr : audioUnit, err);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
createAudioUnit (auComponentResult.component,
|
||||||
|
[rate, blockSize, origCallback = std::move (callback)] (AudioUnit audioUnit, OSStatus err)
|
||||||
|
{
|
||||||
|
if (err == noErr)
|
||||||
|
{
|
||||||
|
auto instance = std::make_unique<AudioUnitPluginInstance> (audioUnit);
|
||||||
|
|
||||||
|
if (instance->initialise (rate, blockSize))
|
||||||
|
origCallback (std::move (instance), {});
|
||||||
|
else
|
||||||
|
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)");
|
||||||
|
origCallback (nullptr, errMsg.replace ("XXX", String (err)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioUnitPluginFormat::createARAFactoryAsync (const PluginDescription& desc, ARAFactoryCreationCallback callback)
|
||||||
|
{
|
||||||
|
auto auComponentResult = getAudioComponent (*this, desc);
|
||||||
|
|
||||||
|
if (! auComponentResult.isValid())
|
||||||
{
|
{
|
||||||
callback (nullptr, NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in"));
|
callback ({ {}, "Failed to create AudioComponent for " + desc.descriptiveName });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrCreateARAAudioUnit (auComponentResult.component, [cb = std::move (callback)] (auto dylibKeepAliveAudioUnit)
|
||||||
|
{
|
||||||
|
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
|
bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,18 @@
|
||||||
|
|
||||||
#include "juce_VST3Headers.h"
|
#include "juce_VST3Headers.h"
|
||||||
#include "juce_VST3Common.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
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
@ -804,6 +816,20 @@ struct DescriptionFactory
|
||||||
|
|
||||||
auto numClasses = factory->countClasses();
|
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)
|
for (Steinberg::int32 i = 0; i < numClasses; ++i)
|
||||||
{
|
{
|
||||||
PClassInfo info;
|
PClassInfo info;
|
||||||
|
|
@ -867,6 +893,9 @@ struct DescriptionFactory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (araMainFactoryClassNames.find (name) != araMainFactoryClassNames.end())
|
||||||
|
desc.hasARAExtension = true;
|
||||||
|
|
||||||
if (desc.uniqueId != 0)
|
if (desc.uniqueId != 0)
|
||||||
result = performOnDescription (desc);
|
result = performOnDescription (desc);
|
||||||
|
|
||||||
|
|
@ -1330,6 +1359,72 @@ private:
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle)
|
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,
|
struct VST3PluginWindow : public AudioProcessorEditor,
|
||||||
private ComponentMovementWatcher,
|
private ComponentMovementWatcher,
|
||||||
|
|
@ -1677,6 +1772,27 @@ private:
|
||||||
|
|
||||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods
|
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
|
struct VST3ComponentHolder
|
||||||
{
|
{
|
||||||
|
|
@ -1802,6 +1918,8 @@ struct VST3ComponentHolder
|
||||||
totalNumInputChannels,
|
totalNumInputChannels,
|
||||||
totalNumOutputChannels);
|
totalNumOutputChannels);
|
||||||
|
|
||||||
|
description.hasARAExtension = hasARAExtension (factory, description.name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2280,7 +2398,8 @@ public:
|
||||||
|
|
||||||
void getExtensions (ExtensionsVisitor& visitor) const override
|
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) {}
|
explicit Extensions (const VST3PluginInstance* instanceIn) : instance (instanceIn) {}
|
||||||
|
|
||||||
|
|
@ -2293,10 +2412,21 @@ public:
|
||||||
return instance->setStateFromPresetFile (rawData);
|
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;
|
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; }
|
void* getPlatformSpecificData() override { return holder->component; }
|
||||||
|
|
@ -3107,7 +3237,7 @@ private:
|
||||||
if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0)
|
if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0)
|
||||||
bypassParam = param;
|
bypassParam = param;
|
||||||
|
|
||||||
std::function<AudioProcessorParameterGroup*(Vst::UnitID)> findOrCreateGroup;
|
std::function<AudioProcessorParameterGroup* (Vst::UnitID)> findOrCreateGroup;
|
||||||
findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID)
|
findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID)
|
||||||
{
|
{
|
||||||
auto existingGroup = groupMap.find (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,
|
void VST3PluginFormat::createPluginInstance (const PluginDescription& description,
|
||||||
double, int, PluginCreationCallback callback)
|
double, int, PluginCreationCallback callback)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ public:
|
||||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
|
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
|
||||||
bool doesPluginStillExist (const PluginDescription&) override;
|
bool doesPluginStillExist (const PluginDescription&) override;
|
||||||
FileSearchPath getDefaultLocationsToSearch() override;
|
FileSearchPath getDefaultLocationsToSearch() override;
|
||||||
|
void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -199,10 +199,12 @@ private:
|
||||||
#include "processors/juce_AudioProcessorGraph.cpp"
|
#include "processors/juce_AudioProcessorGraph.cpp"
|
||||||
#include "processors/juce_GenericAudioProcessorEditor.cpp"
|
#include "processors/juce_GenericAudioProcessorEditor.cpp"
|
||||||
#include "processors/juce_PluginDescription.cpp"
|
#include "processors/juce_PluginDescription.cpp"
|
||||||
|
#include "format_types/juce_ARACommon.cpp"
|
||||||
#include "format_types/juce_LADSPAPluginFormat.cpp"
|
#include "format_types/juce_LADSPAPluginFormat.cpp"
|
||||||
#include "format_types/juce_VSTPluginFormat.cpp"
|
#include "format_types/juce_VSTPluginFormat.cpp"
|
||||||
#include "format_types/juce_VST3PluginFormat.cpp"
|
#include "format_types/juce_VST3PluginFormat.cpp"
|
||||||
#include "format_types/juce_AudioUnitPluginFormat.mm"
|
#include "format_types/juce_AudioUnitPluginFormat.mm"
|
||||||
|
#include "format_types/juce_ARAHosting.cpp"
|
||||||
#include "scanning/juce_KnownPluginList.cpp"
|
#include "scanning/juce_KnownPluginList.cpp"
|
||||||
#include "scanning/juce_PluginDirectoryScanner.cpp"
|
#include "scanning/juce_PluginDirectoryScanner.cpp"
|
||||||
#include "scanning/juce_PluginListComponent.cpp"
|
#include "scanning/juce_PluginListComponent.cpp"
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,17 @@
|
||||||
#define JUCE_PLUGINHOST_LV2 0
|
#define JUCE_PLUGINHOST_LV2 0
|
||||||
#endif
|
#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
|
/** Config: JUCE_CUSTOM_VST3_SDK
|
||||||
If enabled, the embedded VST3 SDK in JUCE will not be added to the project and instead you should
|
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
|
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_VSTCallbackHandler.h"
|
||||||
#include "utilities/juce_VST3ClientExtensions.h"
|
#include "utilities/juce_VST3ClientExtensions.h"
|
||||||
#include "utilities/juce_NativeScaleFactorNotifier.h"
|
#include "utilities/juce_NativeScaleFactorNotifier.h"
|
||||||
|
#include "format_types/juce_ARACommon.h"
|
||||||
#include "utilities/juce_ExtensionsVisitor.h"
|
#include "utilities/juce_ExtensionsVisitor.h"
|
||||||
#include "processors/juce_AudioProcessorParameter.h"
|
#include "processors/juce_AudioProcessorParameter.h"
|
||||||
#include "processors/juce_HostedAudioProcessorParameter.h"
|
#include "processors/juce_HostedAudioProcessorParameter.h"
|
||||||
|
|
@ -136,6 +148,7 @@
|
||||||
#include "format_types/juce_VST3PluginFormat.h"
|
#include "format_types/juce_VST3PluginFormat.h"
|
||||||
#include "format_types/juce_VSTMidiEventList.h"
|
#include "format_types/juce_VSTMidiEventList.h"
|
||||||
#include "format_types/juce_VSTPluginFormat.h"
|
#include "format_types/juce_VSTPluginFormat.h"
|
||||||
|
#include "format_types/juce_ARAHosting.h"
|
||||||
#include "scanning/juce_PluginDirectoryScanner.h"
|
#include "scanning/juce_PluginDirectoryScanner.h"
|
||||||
#include "scanning/juce_PluginListComponent.h"
|
#include "scanning/juce_PluginListComponent.h"
|
||||||
#include "utilities/juce_AudioProcessorParameterWithID.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 ("numInputs", numInputChannels);
|
||||||
e->setAttribute ("numOutputs", numOutputChannels);
|
e->setAttribute ("numOutputs", numOutputChannels);
|
||||||
e->setAttribute ("isShell", hasSharedContainer);
|
e->setAttribute ("isShell", hasSharedContainer);
|
||||||
|
e->setAttribute ("hasARAExtension", hasARAExtension);
|
||||||
|
|
||||||
e->setAttribute ("uid", String::toHexString (deprecatedUid));
|
e->setAttribute ("uid", String::toHexString (deprecatedUid));
|
||||||
|
|
||||||
|
|
@ -95,6 +96,7 @@ bool PluginDescription::loadFromXml (const XmlElement& xml)
|
||||||
numInputChannels = xml.getIntAttribute ("numInputs");
|
numInputChannels = xml.getIntAttribute ("numInputs");
|
||||||
numOutputChannels = xml.getIntAttribute ("numOutputs");
|
numOutputChannels = xml.getIntAttribute ("numOutputs");
|
||||||
hasSharedContainer = xml.getBoolAttribute ("isShell", false);
|
hasSharedContainer = xml.getBoolAttribute ("isShell", false);
|
||||||
|
hasARAExtension = xml.getBoolAttribute ("hasARAExtension", false);
|
||||||
|
|
||||||
deprecatedUid = xml.getStringAttribute ("uid").getHexValue32();
|
deprecatedUid = xml.getStringAttribute ("uid").getHexValue32();
|
||||||
uniqueId = xml.getStringAttribute ("uniqueId", "0").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. */
|
/** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */
|
||||||
bool hasSharedContainer = false;
|
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.
|
/** 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
|
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;
|
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;
|
virtual ~ExtensionsVisitor() = default;
|
||||||
|
|
||||||
/** Will be called if there is no platform specific information available. */
|
/** Will be called if there is no platform specific information available. */
|
||||||
|
|
@ -119,6 +126,9 @@ struct ExtensionsVisitor
|
||||||
|
|
||||||
/** Called with AU-specific information. */
|
/** Called with AU-specific information. */
|
||||||
virtual void visitAudioUnitClient (const AudioUnitClient&) {}
|
virtual void visitAudioUnitClient (const AudioUnitClient&) {}
|
||||||
|
|
||||||
|
/** Called with ARA-specific information. */
|
||||||
|
virtual void visitARAClient (const ARAClient&) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue