Browse Source

ARA Host: Add support for scanning and hosting ARA plugins

pull/22/head
attila 4 years ago
parent
commit
f36949c1b2
31 changed files with 3518 additions and 129 deletions
  1. +12
    -4
      CMakeLists.txt
  2. +4
    -3
      docs/CMake API.md
  3. +2
    -0
      extras/AudioPluginHost/CMakeLists.txt
  4. +26
    -0
      extras/AudioPluginHost/Source/Plugins/ARAPlugin.cpp
  5. +1397
    -0
      extras/AudioPluginHost/Source/Plugins/ARAPlugin.h
  6. +50
    -21
      extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp
  7. +28
    -2
      extras/AudioPluginHost/Source/Plugins/PluginGraph.h
  8. +18
    -3
      extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp
  9. +3
    -2
      extras/AudioPluginHost/Source/UI/GraphEditorPanel.h
  10. +68
    -8
      extras/AudioPluginHost/Source/UI/MainHostWindow.cpp
  11. +5
    -3
      extras/AudioPluginHost/Source/UI/MainHostWindow.h
  12. +13
    -0
      extras/AudioPluginHost/Source/UI/PluginWindow.h
  13. +8
    -0
      extras/Build/CMake/JUCEModuleSupport.cmake
  14. +16
    -0
      extras/Build/CMake/JUCEUtils.cmake
  15. +12
    -0
      modules/juce_audio_processors/format/juce_AudioPluginFormat.h
  16. +16
    -0
      modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp
  17. +16
    -0
      modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h
  18. +69
    -0
      modules/juce_audio_processors/format_types/juce_ARACommon.cpp
  19. +78
    -0
      modules/juce_audio_processors/format_types/juce_ARACommon.h
  20. +451
    -0
      modules/juce_audio_processors/format_types/juce_ARAHosting.cpp
  21. +733
    -0
      modules/juce_audio_processors/format_types/juce_ARAHosting.h
  22. +1
    -0
      modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h
  23. +279
    -80
      modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm
  24. +149
    -3
      modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp
  25. +1
    -0
      modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h
  26. +2
    -0
      modules/juce_audio_processors/juce_audio_processors.cpp
  27. +13
    -0
      modules/juce_audio_processors/juce_audio_processors.h
  28. +33
    -0
      modules/juce_audio_processors/juce_audio_processors_ara.cpp
  29. +2
    -0
      modules/juce_audio_processors/processors/juce_PluginDescription.cpp
  30. +3
    -0
      modules/juce_audio_processors/processors/juce_PluginDescription.h
  31. +10
    -0
      modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h

+ 12
- 4
CMakeLists.txt View File

@@ -69,10 +69,11 @@ juce_disable_default_flags()

add_subdirectory(extras/Build)

# If you want to build the JUCE examples with VST2/AAX support, you'll need to make the VST2/AAX
# headers visible to the juce_audio_processors module. You can either set the paths on the command
# line, (e.g. -DJUCE_GLOBAL_AAX_SDK_PATH=/path/to/sdk) if you're just building the JUCE examples, or
# you can call the `juce_set_*_sdk_path` functions in your own CMakeLists after importing JUCE.
# If you want to build the JUCE examples with VST2/AAX/ARA support, you'll need to make the
# VST2/AAX/ARA headers visible to the juce_audio_processors module. You can either set the paths on
# the command line, (e.g. -DJUCE_GLOBAL_AAX_SDK_PATH=/path/to/sdk) if you're just building the JUCE
# examples, or you can call the `juce_set_*_sdk_path` functions in your own CMakeLists after
# importing JUCE.

if(JUCE_GLOBAL_AAX_SDK_PATH)
juce_set_aax_sdk_path("${JUCE_GLOBAL_AAX_SDK_PATH}")
@@ -82,6 +83,13 @@ if(JUCE_GLOBAL_VST2_SDK_PATH)
juce_set_vst2_sdk_path("${JUCE_GLOBAL_VST2_SDK_PATH}")
endif()

# The ARA_SDK path should point to the "Umbrella installer" ARA_SDK directory.
# The directory can be obtained by recursively cloning https://github.com/Celemony/ARA_SDK and
# checking out the tag releases/2.1.0.
if(JUCE_GLOBAL_ARA_SDK_PATH)
juce_set_ara_sdk_path("${JUCE_GLOBAL_ARA_SDK_PATH}")
endif()

# We don't build anything other than the juceaide by default, because we want to keep configuration
# speedy and the number of targets low. If you want to add targets for the extra projects and
# example PIPs (there's a lot of them!), specify -DJUCE_BUILD_EXAMPLES=ON and/or


+ 4
- 3
docs/CMake API.md View File

@@ -664,10 +664,11 @@ target!).
juce_set_aax_sdk_path(<absolute path>)
juce_set_vst2_sdk_path(<absolute path>)
juce_set_vst3_sdk_path(<absolute path>)
juce_set_ara_sdk_path(<absolute path>)

Call these functions from your CMakeLists to set up your local AAX, VST2, and VST3 SDKs. These
functions should be called *before* adding any targets that may depend on the AAX/VST2/VST3 SDKs
(plugin hosts, AAX/VST2/VST3 plugins etc.).
Call these functions from your CMakeLists to set up your local AAX, VST2, VST3 and ARA SDKs. These
functions should be called *before* adding any targets that may depend on the AAX/VST2/VST3/ARA SDKs
(plugin hosts, AAX/VST2/VST3/ARA plugins etc.).

#### `juce_add_module`



+ 2
- 0
extras/AudioPluginHost/CMakeLists.txt View File

@@ -24,6 +24,7 @@ juce_generate_juce_header(AudioPluginHost)

target_sources(AudioPluginHost PRIVATE
Source/HostStartup.cpp
Source/Plugins/ARAPlugin.cpp
Source/Plugins/IOConfigurationWindow.cpp
Source/Plugins/InternalPlugins.cpp
Source/Plugins/PluginGraph.cpp
@@ -46,6 +47,7 @@ target_compile_definitions(AudioPluginHost PRIVATE
JUCE_PLUGINHOST_LV2=1
JUCE_PLUGINHOST_VST3=1
JUCE_PLUGINHOST_VST=0
JUCE_PLUGINHOST_ARA=0
JUCE_USE_CAMERA=0
JUCE_USE_CDBURNER=0
JUCE_USE_CDREADER=0


+ 26
- 0
extras/AudioPluginHost/Source/Plugins/ARAPlugin.cpp View 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
- 0
extras/AudioPluginHost/Source/Plugins/ARAPlugin.h
File diff suppressed because it is too large
View File


+ 50
- 21
extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp View File

@@ -73,21 +73,23 @@ AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name)
return nullptr;
}
void PluginGraph::addPlugin (const PluginDescription& desc, Point<double> pos)
void PluginGraph::addPlugin (const PluginDescriptionAndPreference& desc, Point<double> pos)
{
std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc);
std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc.pluginDescription);
formatManager.createPluginInstanceAsync (desc,
formatManager.createPluginInstanceAsync (desc.pluginDescription,
graph.getSampleRate(),
graph.getBlockSize(),
[this, pos, dpiDisabler] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
[this, pos, dpiDisabler, useARA = desc.useARA] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
{
addPluginCallback (std::move (instance), error, pos);
addPluginCallback (std::move (instance), error, pos, useARA);
});
}
void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instance,
const String& error, Point<double> pos)
const String& error,
Point<double> pos,
PluginDescriptionAndPreference::UseARA useARA)
{
if (instance == nullptr)
{
@@ -97,12 +99,21 @@ void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instan
}
else
{
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
if (useARA == PluginDescriptionAndPreference::UseARA::yes
&& instance->getPluginDescription().hasARAExtension)
{
instance = std::make_unique<ARAPluginInstanceWrapper> (std::move (instance));
}
#endif
instance->enableAllBuses();
if (auto node = graph.addNode (std::move (instance)))
{
node->properties.set ("x", pos.x);
node->properties.set ("y", pos.y);
node->properties.set ("useARA", useARA == PluginDescriptionAndPreference::UseARA::yes);
changed();
}
}
@@ -193,10 +204,10 @@ void PluginGraph::newDocument()
jassert (internalFormat.getAllTypes().size() > 3);
addPlugin (internalFormat.getAllTypes()[0], { 0.5, 0.1 });
addPlugin (internalFormat.getAllTypes()[1], { 0.25, 0.1 });
addPlugin (internalFormat.getAllTypes()[2], { 0.5, 0.9 });
addPlugin (internalFormat.getAllTypes()[3], { 0.25, 0.9 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[0] }, { 0.5, 0.1 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[1] }, { 0.25, 0.1 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[2] }, { 0.5, 0.9 });
addPlugin (PluginDescriptionAndPreference { internalFormat.getAllTypes()[3] }, { 0.25, 0.9 });
MessageManager::callAsync ([this]
{
@@ -325,6 +336,7 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep
e->setAttribute ("uid", (int) node->nodeID.uid);
e->setAttribute ("x", node->properties ["x"].toString());
e->setAttribute ("y", node->properties ["y"].toString());
e->setAttribute ("useARA", node->properties ["useARA"].toString());
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
{
@@ -365,26 +377,42 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep
void PluginGraph::createNodeFromXml (const XmlElement& xml)
{
PluginDescription pd;
PluginDescriptionAndPreference pd;
const auto nodeUsesARA = xml.getBoolAttribute ("useARA");
for (auto* e : xml.getChildIterator())
{
if (pd.loadFromXml (*e))
if (pd.pluginDescription.loadFromXml (*e))
{
pd.useARA = nodeUsesARA ? PluginDescriptionAndPreference::UseARA::yes
: PluginDescriptionAndPreference::UseARA::no;
break;
}
}
auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance>
{
auto createInstance = [this] (const PluginDescription& description)
auto createInstance = [this] (const PluginDescriptionAndPreference& description) -> std::unique_ptr<AudioPluginInstance>
{
String errorMessage;
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description);
auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.pluginDescription);
return formatManager.createPluginInstance (description,
graph.getSampleRate(),
graph.getBlockSize(),
errorMessage);
auto instance = formatManager.createPluginInstance (description.pluginDescription,
graph.getSampleRate(),
graph.getBlockSize(),
errorMessage);
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
if (instance
&& description.useARA == PluginDescriptionAndPreference::UseARA::yes
&& description.pluginDescription.hasARAExtension)
{
return std::make_unique<ARAPluginInstanceWrapper> (std::move (instance));
}
#endif
return instance;
};
if (auto instance = createInstance (pd))
@@ -392,19 +420,19 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml)
const auto allFormats = formatManager.getFormats();
const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(),
[&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginFormatName; });
[&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginDescription.pluginFormatName; });
if (matchingFormat == allFormats.end())
return nullptr;
const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat);
const auto matchingPlugin = std::find_if (plugins.begin(), plugins.end(),
[&] (const PluginDescription& desc) { return pd.uniqueId == desc.uniqueId; });
[&] (const PluginDescription& desc) { return pd.pluginDescription.uniqueId == desc.uniqueId; });
if (matchingPlugin == plugins.end())
return nullptr;
return createInstance (*matchingPlugin);
return createInstance (PluginDescriptionAndPreference { *matchingPlugin });
};
if (auto instance = createInstanceWithFallback())
@@ -431,6 +459,7 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml)
node->properties.set ("x", xml.getDoubleAttribute ("x"));
node->properties.set ("y", xml.getDoubleAttribute ("y"));
node->properties.set ("useARA", xml.getBoolAttribute ("useARA"));
for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i)
{


+ 28
- 2
extras/AudioPluginHost/Source/Plugins/PluginGraph.h View File

@@ -20,6 +20,29 @@
#include "../UI/PluginWindow.h"
//==============================================================================
/** A type that encapsulates a PluginDescription and some preferences regarding
how plugins of that description should be instantiated.
*/
struct PluginDescriptionAndPreference
{
enum class UseARA { no, yes };
PluginDescriptionAndPreference() = default;
explicit PluginDescriptionAndPreference (PluginDescription pd)
: pluginDescription (std::move (pd)),
useARA (pluginDescription.hasARAExtension ? PluginDescriptionAndPreference::UseARA::yes
: PluginDescriptionAndPreference::UseARA::no)
{}
PluginDescriptionAndPreference (PluginDescription pd, UseARA ara)
: pluginDescription (std::move (pd)), useARA (ara)
{}
PluginDescription pluginDescription;
UseARA useARA = UseARA::no;
};
//==============================================================================
/**
@@ -37,7 +60,7 @@ public:
//==============================================================================
using NodeID = AudioProcessorGraph::NodeID;
void addPlugin (const PluginDescription&, Point<double>);
void addPlugin (const PluginDescriptionAndPreference&, Point<double>);
AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const;
@@ -85,7 +108,10 @@ private:
NodeID getNextUID() noexcept;
void createNodeFromXml (const XmlElement&);
void addPluginCallback (std::unique_ptr<AudioPluginInstance>, const String& error, Point<double>);
void addPluginCallback (std::unique_ptr<AudioPluginInstance>,
const String& error,
Point<double>,
PluginDescriptionAndPreference::UseARA useARA);
void changeListenerCallback (ChangeBroadcaster*) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginGraph)


+ 18
- 3
extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp View File

@@ -391,6 +391,14 @@ struct GraphEditorPanel::PluginComponent : public Component,
return {};
}
bool isNodeUsingARA() const
{
if (auto node = graph.graph.getNodeForId (pluginID))
return node->properties["useARA"];
return false;
}
void showPopupMenu()
{
menu.reset (new PopupMenu);
@@ -412,6 +420,12 @@ struct GraphEditorPanel::PluginComponent : public Component,
menu->addItem ("Show all parameters", [this] { showWindow (PluginWindow::Type::generic); });
menu->addItem ("Show debug log", [this] { showWindow (PluginWindow::Type::debug); });
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
if (auto* instance = dynamic_cast<AudioPluginInstance*> (getProcessor()))
if (instance->getPluginDescription().hasARAExtension && isNodeUsingARA())
menu->addItem ("Show ARA host controls", [this] { showWindow (PluginWindow::Type::araHost); });
#endif
if (autoScaleOptionAvailable)
addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu);
@@ -777,7 +791,7 @@ void GraphEditorPanel::mouseDrag (const MouseEvent& e)
stopTimer();
}
void GraphEditorPanel::createNewPlugin (const PluginDescription& desc, Point<int> position)
void GraphEditorPanel::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> position)
{
graph.addPlugin (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight()));
}
@@ -1273,7 +1287,7 @@ void GraphDocumentComponent::resized()
checkAvailableWidth();
}
void GraphDocumentComponent::createNewPlugin (const PluginDescription& desc, Point<int> pos)
void GraphDocumentComponent::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos)
{
graphPanel->createNewPlugin (desc, pos);
}
@@ -1320,7 +1334,8 @@ void GraphDocumentComponent::itemDropped (const SourceDetails& details)
// must be a valid index!
jassert (isPositiveAndBelow (pluginTypeIndex, pluginList.getNumTypes()));
createNewPlugin (pluginList.getTypes()[pluginTypeIndex], details.localPosition);
createNewPlugin (PluginDescriptionAndPreference { pluginList.getTypes()[pluginTypeIndex] },
details.localPosition);
}
void GraphDocumentComponent::showSidePanel (bool showSettingsPanel)


+ 3
- 2
extras/AudioPluginHost/Source/UI/GraphEditorPanel.h View File

@@ -31,10 +31,11 @@ class GraphEditorPanel : public Component,
private Timer
{
public:
//==============================================================================
GraphEditorPanel (PluginGraph& graph);
~GraphEditorPanel() override;
void createNewPlugin (const PluginDescription&, Point<int> position);
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
void paint (Graphics&) override;
void resized() override;
@@ -103,7 +104,7 @@ public:
~GraphDocumentComponent() override;
//==============================================================================
void createNewPlugin (const PluginDescription&, Point<int> position);
void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position);
void setDoublePrecision (bool doublePrecision);
bool closeAnyOpenPluginWindows();


+ 68
- 8
extras/AudioPluginHost/Source/UI/MainHostWindow.cpp View File

@@ -576,7 +576,7 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/
}
else
{
if (KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuItemID) >= 0)
if (getIndexChosenByMenu (menuItemID) >= 0)
createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) });
}
@@ -588,12 +588,64 @@ void MainHostWindow::menuBarActivated (bool isActivated)
graphHolder->unfocusKeyboardComponent();
}
void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos)
void MainHostWindow::createPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos)
{
if (graphHolder != nullptr)
graphHolder->createNewPlugin (desc, pos);
}
static bool containsDuplicateNames (const Array<PluginDescription>& plugins, const String& name)
{
int matches = 0;
for (auto& p : plugins)
if (p.name == name && ++matches > 1)
return true;
return false;
}
static constexpr int menuIDBase = 0x324503f4;
static void addToMenu (const KnownPluginList::PluginTree& tree,
PopupMenu& m,
const Array<PluginDescription>& allPlugins,
Array<PluginDescriptionAndPreference>& addedPlugins)
{
for (auto* sub : tree.subFolders)
{
PopupMenu subMenu;
addToMenu (*sub, subMenu, allPlugins, addedPlugins);
m.addSubMenu (sub->folder, subMenu, true, nullptr, false, 0);
}
auto addPlugin = [&] (const auto& descriptionAndPreference, const auto& pluginName)
{
addedPlugins.add (descriptionAndPreference);
const auto menuID = addedPlugins.size() - 1 + menuIDBase;
m.addItem (menuID, pluginName, true, false);
};
for (auto& plugin : tree.plugins)
{
auto name = plugin.name;
if (containsDuplicateNames (tree.plugins, name))
name << " (" << plugin.pluginFormatName << ')';
addPlugin (PluginDescriptionAndPreference { plugin, PluginDescriptionAndPreference::UseARA::no }, name);
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
if (plugin.hasARAExtension)
{
name << " (ARA)";
addPlugin (PluginDescriptionAndPreference { plugin }, name);
}
#endif
}
}
void MainHostWindow::addPluginsToMenu (PopupMenu& m)
{
if (graphHolder != nullptr)
@@ -606,7 +658,7 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m)
m.addSeparator();
pluginDescriptions = knownPluginList.getTypes();
auto pluginDescriptions = knownPluginList.getTypes();
// This avoids showing the internal types again later on in the list
pluginDescriptions.removeIf ([] (PluginDescription& desc)
@@ -614,15 +666,23 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m)
return desc.pluginFormatName == InternalPluginFormat::getIdentifier();
});
KnownPluginList::addToMenu (m, pluginDescriptions, pluginSortMethod);
auto tree = KnownPluginList::createTree (pluginDescriptions, pluginSortMethod);
pluginDescriptionsAndPreference = {};
addToMenu (*tree, m, pluginDescriptions, pluginDescriptionsAndPreference);
}
int MainHostWindow::getIndexChosenByMenu (int menuID) const
{
const auto i = menuID - menuIDBase;
return isPositiveAndBelow (i, pluginDescriptionsAndPreference.size()) ? i : -1;
}
PluginDescription MainHostWindow::getChosenType (const int menuID) const
PluginDescriptionAndPreference MainHostWindow::getChosenType (const int menuID) const
{
if (menuID >= 1 && menuID < (int) (1 + internalTypes.size()))
return internalTypes[(size_t) (menuID - 1)];
return PluginDescriptionAndPreference { internalTypes[(size_t) (menuID - 1)] };
return pluginDescriptions[KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuID)];
return pluginDescriptionsAndPreference[getIndexChosenByMenu (menuID)];
}
//==============================================================================
@@ -905,7 +965,7 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
for (int i = 0; i < jmin (5, typesFound.size()); ++i)
if (auto* desc = typesFound.getUnchecked(i))
createPlugin (*desc, pos);
createPlugin (PluginDescriptionAndPreference { *desc }, pos);
}
}
}


+ 5
- 3
extras/AudioPluginHost/Source/UI/MainHostWindow.h View File

@@ -100,10 +100,10 @@ public:
void tryToQuitApplication();
void createPlugin (const PluginDescription&, Point<int> pos);
void createPlugin (const PluginDescriptionAndPreference&, Point<int> pos);
void addPluginsToMenu (PopupMenu&);
PluginDescription getChosenType (int menuID) const;
PluginDescriptionAndPreference getChosenType (int menuID) const;
std::unique_ptr<GraphDocumentComponent> graphHolder;
@@ -117,6 +117,8 @@ private:
void showAudioSettings();
int getIndexChosenByMenu (int menuID) const;
//==============================================================================
AudioDeviceManager deviceManager;
AudioPluginFormatManager formatManager;
@@ -124,7 +126,7 @@ private:
std::vector<PluginDescription> internalTypes;
KnownPluginList knownPluginList;
KnownPluginList::SortMethod pluginSortMethod;
Array<PluginDescription> pluginDescriptions;
Array<PluginDescriptionAndPreference> pluginDescriptionsAndPreference;
class PluginListWindow;
std::unique_ptr<PluginListWindow> pluginListWindow;


+ 13
- 0
extras/AudioPluginHost/Source/UI/PluginWindow.h View File

@@ -19,6 +19,7 @@
#pragma once
#include "../Plugins/IOConfigurationWindow.h"
#include "../Plugins/ARAPlugin.h"
inline String getFormatSuffix (const AudioProcessor* plugin)
{
@@ -148,6 +149,7 @@ public:
programs,
audioIO,
debug,
araHost,
numTypes
};
@@ -234,6 +236,16 @@ private:
type = PluginWindow::Type::generic;
}
if (type == PluginWindow::Type::araHost)
{
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
if (auto* araPluginInstanceWrapper = dynamic_cast<ARAPluginInstanceWrapper*> (&processor))
if (auto* ui = araPluginInstanceWrapper->createARAHostEditor())
return ui;
#endif
return {};
}
if (type == PluginWindow::Type::generic) return new GenericAudioProcessorEditor (processor);
if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor);
if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor);
@@ -252,6 +264,7 @@ private:
case Type::programs: return "Programs";
case Type::audioIO: return "IO";
case Type::debug: return "Debug";
case Type::araHost: return "ARAHost";
case Type::numTypes:
default: return {};
}


+ 8
- 0
extras/Build/CMake/JUCEModuleSupport.cmake View File

@@ -502,9 +502,17 @@ function(juce_add_module module_path)
"${lv2_base_path}/lilv/src")
target_link_libraries(juce_audio_processors INTERFACE juce_lilv_headers)

add_library(juce_ara_headers INTERFACE)

target_include_directories(juce_ara_headers INTERFACE
"$<$<TARGET_EXISTS:juce_ara_sdk>:$<TARGET_PROPERTY:juce_ara_sdk,INTERFACE_INCLUDE_DIRECTORIES>>")

target_link_libraries(juce_audio_processors INTERFACE juce_ara_headers)

if(JUCE_ARG_ALIAS_NAMESPACE)
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_vst3_headers ALIAS juce_vst3_headers)
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_lilv_headers ALIAS juce_lilv_headers)
add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_ara_headers ALIAS juce_ara_headers)
endif()
endif()



+ 16
- 0
extras/Build/CMake/JUCEUtils.cmake View File

@@ -1969,6 +1969,22 @@ function(juce_set_vst3_sdk_path path)
target_include_directories(juce_vst3_sdk INTERFACE "${path}")
endfunction()

function(juce_set_ara_sdk_path path)
if(TARGET juce_ara_sdk)
message(FATAL_ERROR "juce_set_ara_sdk_path should only be called once")
endif()

_juce_make_absolute(path)

if(NOT EXISTS "${path}")
message(FATAL_ERROR "Could not find ARA SDK at the specified path: ${path}")
endif()

add_library(juce_ara_sdk INTERFACE IMPORTED GLOBAL)

target_include_directories(juce_ara_sdk INTERFACE "${path}")
endfunction()

# ==================================================================================================

function(juce_disable_default_flags)


+ 12
- 0
modules/juce_audio_processors/format/juce_AudioPluginFormat.h View File

@@ -133,6 +133,18 @@ public:
/** Returns true if instantiation of this plugin type must be done from a non-message thread. */
virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const = 0;
/** A callback lambda that is passed to getARAFactory() */
using ARAFactoryCreationCallback = std::function<void (ARAFactoryResult)>;
/** Tries to create an ::ARAFactoryWrapper for this description.
The result of the operation will be wrapped into an ARAFactoryResult,
which will be passed to a callback object supplied by the caller.
@see AudioPluginFormatManager::createARAFactoryAsync
*/
virtual void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) { callback ({}); }
protected:
//==============================================================================
friend class AudioPluginFormatManager;


+ 16
- 0
modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp View File

@@ -129,6 +129,22 @@ std::unique_ptr<AudioPluginInstance> AudioPluginFormatManager::createPluginInsta
return {};
}
void AudioPluginFormatManager::createARAFactoryAsync (const PluginDescription& description,
AudioPluginFormat::ARAFactoryCreationCallback callback) const
{
String errorMessage;
if (auto* format = findFormatForDescription (description, errorMessage))
{
format->createARAFactoryAsync (description, callback);
}
else
{
errorMessage = NEEDS_TRANS ("Couldn't find format for the provided description");
callback ({ {}, std::move (errorMessage) });
}
}
void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description,
double initialSampleRate, int initialBufferSize,
AudioPluginFormat::PluginCreationCallback callback)


+ 16
- 0
modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h View File

@@ -102,6 +102,22 @@ public:
double initialSampleRate, int initialBufferSize,
AudioPluginFormat::PluginCreationCallback callback);
/** Tries to create an ::ARAFactoryWrapper for this description.
The result of the operation will be wrapped into an ARAFactoryResult,
which will be passed to a callback object supplied by the caller.
The operation may fail, in which case the callback will be called with
with a result object where ARAFactoryResult::araFactory.get() will return
a nullptr.
In case of success the returned ::ARAFactoryWrapper will ensure that
modules required for the correct functioning of the ARAFactory will remain
loaded for the lifetime of the object.
*/
void createARAFactoryAsync (const PluginDescription& description,
AudioPluginFormat::ARAFactoryCreationCallback callback) const;
/** Checks that the file or component for this plugin actually still exists.
(This won't try to load the plugin)
*/


+ 69
- 0
modules/juce_audio_processors/format_types/juce_ARACommon.cpp View File

@@ -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
- 0
modules/juce_audio_processors/format_types/juce_ARACommon.h View 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
- 0
modules/juce_audio_processors/format_types/juce_ARAHosting.cpp View 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
- 0
modules/juce_audio_processors/format_types/juce_ARAHosting.h View 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 (&region.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

+ 1
- 0
modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h View File

@@ -47,6 +47,7 @@ public:
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
bool doesPluginStillExist (const PluginDescription&) override;
FileSearchPath getDefaultLocationsToSearch() override;
void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override;
private:
//==============================================================================


+ 279
- 80
modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm View File

@@ -24,6 +24,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
#include <AudioUnit/AUCocoaUIView.h>
#include <CoreAudioKit/AUGenericView.h>
#include <AudioToolbox/AudioUnitUtilities.h>
#if JUCE_PLUGINHOST_ARA
#include <ARA_API/ARAAudioUnit.h>
#endif
#endif
#include <CoreMIDI/MIDIServices.h>
@@ -422,6 +427,197 @@ namespace AudioUnitFormatHelpers
}
}
static bool hasARAExtension (AudioUnit audioUnit)
{
#if JUCE_PLUGINHOST_ARA
UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory);
Boolean isWriteable = FALSE;
OSStatus status = AudioUnitGetPropertyInfo (audioUnit,
ARA::kAudioUnitProperty_ARAFactory,
kAudioUnitScope_Global,
0,
&propertySize,
&isWriteable);
if ((status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable)
return true;
#else
ignoreUnused (audioUnit);
#endif
return false;
}
struct AudioUnitDeleter
{
void operator() (AudioUnit au) const { AudioComponentInstanceDispose (au); }
};
using AudioUnitUniquePtr = std::unique_ptr<std::remove_pointer_t<AudioUnit>, AudioUnitDeleter>;
using AudioUnitSharedPtr = std::shared_ptr<std::remove_pointer_t<AudioUnit>>;
using AudioUnitWeakPtr = std::weak_ptr<std::remove_pointer_t<AudioUnit>>;
static std::shared_ptr<const ARA::ARAFactory> getARAFactory (AudioUnitSharedPtr audioUnit)
{
#if JUCE_PLUGINHOST_ARA
jassert (audioUnit != nullptr);
UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory);
ARA::ARAAudioUnitFactory audioUnitFactory { ARA::kARAAudioUnitMagic, nullptr };
if (hasARAExtension (audioUnit.get()))
{
OSStatus status = AudioUnitGetProperty (audioUnit.get(),
ARA::kAudioUnitProperty_ARAFactory,
kAudioUnitScope_Global,
0,
&audioUnitFactory,
&propertySize);
if ((status == noErr)
&& (propertySize == sizeof (ARA::ARAAudioUnitFactory))
&& (audioUnitFactory.inOutMagicNumber == ARA::kARAAudioUnitMagic))
{
jassert (audioUnitFactory.outFactory != nullptr);
return getOrCreateARAFactory (audioUnitFactory.outFactory,
[owningAuPtr = std::move (audioUnit)] (const ARA::ARAFactory*) {});
}
}
#else
ignoreUnused (audioUnit);
#endif
return {};
}
struct VersionedAudioComponent
{
AudioComponent audioComponent = nullptr;
bool isAUv3 = false;
bool operator< (const VersionedAudioComponent& other) const { return audioComponent < other.audioComponent; }
};
using AudioUnitCreationCallback = std::function<void (AudioUnit, OSStatus)>;
static void createAudioUnit (VersionedAudioComponent versionedComponent, AudioUnitCreationCallback callback)
{
struct AUAsyncInitializationCallback
{
typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus);
explicit AUAsyncInitializationCallback (AudioUnitCreationCallback inOriginalCallback)
: originalCallback (std::move (inOriginalCallback))
{
block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion);
}
AUCompletionCallbackBlock getBlock() noexcept { return block; }
void completion (AudioComponentInstance audioUnit, OSStatus err)
{
originalCallback (audioUnit, err);
delete this;
}
double sampleRate;
int framesPerBuffer;
AudioUnitCreationCallback originalCallback;
ObjCBlock<AUCompletionCallbackBlock> block;
};
auto callbackBlock = new AUAsyncInitializationCallback (std::move (callback));
if (versionedComponent.isAUv3)
{
if (@available (macOS 10.11, *))
{
AudioComponentInstantiate (versionedComponent.audioComponent, kAudioComponentInstantiation_LoadOutOfProcess,
callbackBlock->getBlock());
return;
}
}
AudioComponentInstance audioUnit;
auto err = AudioComponentInstanceNew (versionedComponent.audioComponent, &audioUnit);
callbackBlock->completion (err != noErr ? nullptr : audioUnit, err);
}
struct AudioComponentResult
{
explicit AudioComponentResult (String error) : errorMessage (std::move (error)) {}
explicit AudioComponentResult (VersionedAudioComponent auComponent) : component (std::move (auComponent)) {}
bool isValid() const { return component.audioComponent != nullptr; }
VersionedAudioComponent component;
String errorMessage;
};
static AudioComponentResult getAudioComponent (AudioUnitPluginFormat& format, const PluginDescription& desc)
{
using namespace AudioUnitFormatHelpers;
AudioUnitPluginFormat audioUnitPluginFormat;
if (! format.fileMightContainThisPluginType (desc.fileOrIdentifier))
return AudioComponentResult { NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in") };
String pluginName, version, manufacturer;
AudioComponentDescription componentDesc;
AudioComponent auComponent;
String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description");
if (! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)
&& ! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer))
{
return AudioComponentResult { errMessage };
}
if ((auComponent = AudioComponentFindNext (nullptr, &componentDesc)) == nullptr)
{
return AudioComponentResult { errMessage };
}
if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr)
{
return AudioComponentResult { errMessage };
}
const bool isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc);
return AudioComponentResult { { auComponent, isAUv3 } };
}
static void getOrCreateARAAudioUnit (VersionedAudioComponent auComponent, std::function<void (AudioUnitSharedPtr)> callback)
{
static std::map<VersionedAudioComponent, AudioUnitWeakPtr> audioUnitARACache;
if (auto audioUnit = audioUnitARACache[auComponent].lock())
{
callback (std::move (audioUnit));
return;
}
createAudioUnit (auComponent, [auComponent, cb = std::move (callback)] (AudioUnit audioUnit, OSStatus err)
{
cb ([auComponent, audioUnit, err]() -> AudioUnitSharedPtr
{
if (err != noErr)
return nullptr;
AudioUnitSharedPtr auPtr { AudioUnitUniquePtr { audioUnit } };
audioUnitARACache[auComponent] = auPtr;
return auPtr;
}());
});
}
//==============================================================================
class AudioUnitPluginWindowCocoa;
@@ -974,6 +1170,23 @@ public:
desc.numInputChannels = getTotalNumInputChannels();
desc.numOutputChannels = getTotalNumOutputChannels();
desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice);
#if JUCE_PLUGINHOST_ARA
desc.hasARAExtension = [&]
{
UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory);
Boolean isWriteable = FALSE;
OSStatus status = AudioUnitGetPropertyInfo (audioUnit,
ARA::kAudioUnitProperty_ARAFactory,
kAudioUnitScope_Global,
0,
&propertySize,
&isWriteable);
return (status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable;
}();
#endif
}
void getExtensions (ExtensionsVisitor& visitor) const override
@@ -988,6 +1201,33 @@ public:
};
visitor.visitAudioUnitClient (Extensions { this });
#ifdef JUCE_PLUGINHOST_ARA
struct ARAExtensions : public ExtensionsVisitor::ARAClient
{
explicit ARAExtensions (const AudioUnitPluginInstance* instanceIn) : instance (instanceIn) {}
void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const override
{
getOrCreateARAAudioUnit ({ instance->auComponent, instance->isAUv3 },
[origCb = std::move (cb)] (auto dylibKeepAliveAudioUnit)
{
origCb ([&]() -> ARAFactoryWrapper
{
if (dylibKeepAliveAudioUnit != nullptr)
return ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) };
return ARAFactoryWrapper { nullptr };
}());
});
}
const AudioUnitPluginInstance* instance = nullptr;
};
if (hasARAExtension (audioUnit))
visitor.visitARAClient (ARAExtensions (this));
#endif
}
void* getPlatformSpecificData() override { return audioUnit; }
@@ -2639,95 +2879,54 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc,
double rate, int blockSize,
PluginCreationCallback callback)
{
using namespace AudioUnitFormatHelpers;
auto auComponentResult = getAudioComponent (*this, desc);
if (fileMightContainThisPluginType (desc.fileOrIdentifier))
if (! auComponentResult.isValid())
{
String pluginName, version, manufacturer;
AudioComponentDescription componentDesc;
AudioComponent auComponent;
String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description");
if ((! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer))
&& (! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)))
{
callback (nullptr, errMessage);
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));
callback (nullptr, std::move (auComponentResult.errorMessage));
return;
}
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)));
}
createAudioUnit (auComponentResult.component,
[rate, blockSize, origCallback = std::move (callback)] (AudioUnit audioUnit, OSStatus err)
{
if (err == noErr)
{
auto instance = std::make_unique<AudioUnitPluginInstance> (audioUnit);
delete this;
}
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)));
}
});
}
double sampleRate;
int framesPerBuffer;
PluginCreationCallback originalCallback;
ObjCBlock<AUCompletionCallbackBlock> block;
};
void AudioUnitPluginFormat::createARAFactoryAsync (const PluginDescription& desc, ARAFactoryCreationCallback callback)
{
auto auComponentResult = getAudioComponent (*this, desc);
auto callbackBlock = new AUAsyncInitializationCallback (rate, blockSize, std::move (callback));
if (! auComponentResult.isValid())
{
callback ({ {}, "Failed to create AudioComponent for " + desc.descriptiveName });
return;
}
if (AudioUnitFormatHelpers::isPluginAUv3 (componentDesc))
{
if (@available (macOS 10.11, *))
getOrCreateARAAudioUnit (auComponentResult.component, [cb = std::move (callback)] (auto dylibKeepAliveAudioUnit)
{
cb ([&]() -> ARAFactoryResult
{
AudioComponentInstantiate (auComponent, kAudioComponentInstantiation_LoadOutOfProcess,
callbackBlock->getBlock());
return;
}
}
if (dylibKeepAliveAudioUnit != nullptr)
return { ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }, "" };
AudioComponentInstance audioUnit;
auto err = AudioComponentInstanceNew(auComponent, &audioUnit);
callbackBlock->completion (err != noErr ? nullptr : audioUnit, err);
}
else
{
callback (nullptr, NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in"));
}
return { {}, "Failed to create ARAFactory from the provided AudioUnit" };
}());
});
}
bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const


+ 149
- 3
modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp View File

@@ -20,6 +20,18 @@
#include "juce_VST3Headers.h"
#include "juce_VST3Common.h"
#include "juce_ARACommon.h"
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
#include <ARA_API/ARAVST3.h>
namespace ARA
{
DEF_CLASS_IID (IMainFactory)
DEF_CLASS_IID (IPlugInEntryPoint)
DEF_CLASS_IID (IPlugInEntryPoint2)
}
#endif
namespace juce
{
@@ -804,6 +816,20 @@ struct DescriptionFactory
auto numClasses = factory->countClasses();
// Every ARA::IMainFactory must have a matching Steinberg::IComponent.
// The match is determined by the two classes having the same name.
std::unordered_set<String> araMainFactoryClassNames;
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
for (Steinberg::int32 i = 0; i < numClasses; ++i)
{
PClassInfo info;
factory->getClassInfo (i, &info);
if (std::strcmp (info.category, kARAMainFactoryClass) == 0)
araMainFactoryClassNames.insert (info.name);
}
#endif
for (Steinberg::int32 i = 0; i < numClasses; ++i)
{
PClassInfo info;
@@ -867,6 +893,9 @@ struct DescriptionFactory
}
}
if (araMainFactoryClassNames.find (name) != araMainFactoryClassNames.end())
desc.hasARAExtension = true;
if (desc.uniqueId != 0)
result = performOnDescription (desc);
@@ -1330,6 +1359,72 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle)
};
template <typename Type, size_t N>
static int compareWithString (Type (&charArray)[N], const String& str)
{
return std::strncmp (str.toRawUTF8(),
charArray,
std::min (str.getNumBytesAsUTF8(), (size_t) numElementsInArray (charArray)));
}
template <typename Callback>
static void forEachARAFactory (IPluginFactory* pluginFactory, Callback&& cb)
{
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
const auto numClasses = pluginFactory->countClasses();
for (Steinberg::int32 i = 0; i < numClasses; ++i)
{
PClassInfo info;
pluginFactory->getClassInfo (i, &info);
if (std::strcmp (info.category, kARAMainFactoryClass) == 0)
{
const bool keepGoing = cb (info);
if (! keepGoing)
break;
}
}
#else
ignoreUnused (pluginFactory, cb);
#endif
}
static std::shared_ptr<const ARA::ARAFactory> getARAFactory (Steinberg::IPluginFactory* pluginFactory, const String& pluginName)
{
std::shared_ptr<const ARA::ARAFactory> factory;
#if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
forEachARAFactory (pluginFactory,
[&pluginFactory, &pluginName, &factory] (const auto& pcClassInfo)
{
if (compareWithString (pcClassInfo.name, pluginName) == 0)
{
ARA::IMainFactory* source;
if (pluginFactory->createInstance (pcClassInfo.cid, ARA::IMainFactory::iid, (void**) &source)
== Steinberg::kResultOk)
{
factory = getOrCreateARAFactory (source->getFactory(),
[source] (const ARA::ARAFactory*) { source->release(); });
return false;
}
jassert (source == nullptr);
}
return true;
});
#else
ignoreUnused (pluginFactory, pluginName);
#endif
return factory;
}
static std::shared_ptr<const ARA::ARAFactory> getARAFactory (VST3ModuleHandle& module)
{
auto* pluginFactory = module.getPluginFactory();
return getARAFactory (pluginFactory, module.getName());
}
//==============================================================================
struct VST3PluginWindow : public AudioProcessorEditor,
private ComponentMovementWatcher,
@@ -1677,6 +1772,27 @@ private:
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods
//==============================================================================
static bool hasARAExtension (IPluginFactory* pluginFactory, const String& pluginClassName)
{
bool result = false;
forEachARAFactory (pluginFactory,
[&pluginClassName, &result] (const auto& pcClassInfo)
{
if (compareWithString (pcClassInfo.name, pluginClassName) == 0)
{
result = true;
return false;
}
return true;
});
return result;
}
//==============================================================================
struct VST3ComponentHolder
{
@@ -1802,6 +1918,8 @@ struct VST3ComponentHolder
totalNumInputChannels,
totalNumOutputChannels);
description.hasARAExtension = hasARAExtension (factory, description.name);
return;
}
@@ -2280,7 +2398,8 @@ public:
void getExtensions (ExtensionsVisitor& visitor) const override
{
struct Extensions : public ExtensionsVisitor::VST3Client
struct Extensions : public ExtensionsVisitor::VST3Client,
public ExtensionsVisitor::ARAClient
{
explicit Extensions (const VST3PluginInstance* instanceIn) : instance (instanceIn) {}
@@ -2293,10 +2412,21 @@ public:
return instance->setStateFromPresetFile (rawData);
}
void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const noexcept override
{
cb (ARAFactoryWrapper { ::juce::getARAFactory (*(instance->holder->module)) });
}
const VST3PluginInstance* instance = nullptr;
};
visitor.visitVST3Client (Extensions { this });
Extensions extensions { this };
visitor.visitVST3Client (extensions);
if (::juce::getARAFactory (*(holder->module)))
{
visitor.visitARAClient (extensions);
}
}
void* getPlatformSpecificData() override { return holder->component; }
@@ -3107,7 +3237,7 @@ private:
if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0)
bypassParam = param;
std::function<AudioProcessorParameterGroup*(Vst::UnitID)> findOrCreateGroup;
std::function<AudioProcessorParameterGroup* (Vst::UnitID)> findOrCreateGroup;
findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID)
{
auto existingGroup = groupMap.find (groupID);
@@ -3669,6 +3799,22 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& resul
}
}
void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& description, ARAFactoryCreationCallback callback)
{
if (! description.hasARAExtension)
{
jassertfalse;
callback ({ {}, "The provided plugin does not support ARA features" });
}
File file (description.fileOrIdentifier);
VSTComSmartPtr<IPluginFactory> pluginFactory (
DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory());
const auto* pluginName = description.name.toRawUTF8();
callback ({ ARAFactoryWrapper { ::juce::getARAFactory (pluginFactory, pluginName) }, {} });
}
void VST3PluginFormat::createPluginInstance (const PluginDescription& description,
double, int, PluginCreationCallback callback)
{


+ 1
- 0
modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h View File

@@ -61,6 +61,7 @@ public:
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override;
bool doesPluginStillExist (const PluginDescription&) override;
FileSearchPath getDefaultLocationsToSearch() override;
void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override;
private:
//==============================================================================


+ 2
- 0
modules/juce_audio_processors/juce_audio_processors.cpp View File

@@ -199,10 +199,12 @@ private:
#include "processors/juce_AudioProcessorGraph.cpp"
#include "processors/juce_GenericAudioProcessorEditor.cpp"
#include "processors/juce_PluginDescription.cpp"
#include "format_types/juce_ARACommon.cpp"
#include "format_types/juce_LADSPAPluginFormat.cpp"
#include "format_types/juce_VSTPluginFormat.cpp"
#include "format_types/juce_VST3PluginFormat.cpp"
#include "format_types/juce_AudioUnitPluginFormat.mm"
#include "format_types/juce_ARAHosting.cpp"
#include "scanning/juce_KnownPluginList.cpp"
#include "scanning/juce_PluginDirectoryScanner.cpp"
#include "scanning/juce_PluginListComponent.cpp"


+ 13
- 0
modules/juce_audio_processors/juce_audio_processors.h View File

@@ -94,6 +94,17 @@
#define JUCE_PLUGINHOST_LV2 0
#endif
/** Config: JUCE_PLUGINHOST_ARA
Enables the ARA plugin extension hosting classes. You will need to download the ARA SDK and specify the
path to it either in the Projucer, using juce_set_ara_sdk_path() in your CMake project file.
The directory can be obtained by recursively cloning https://github.com/Celemony/ARA_SDK and checking out
the tag releases/2.1.0.
*/
#ifndef JUCE_PLUGINHOST_ARA
#define JUCE_PLUGINHOST_ARA 0
#endif
/** Config: JUCE_CUSTOM_VST3_SDK
If enabled, the embedded VST3 SDK in JUCE will not be added to the project and instead you should
add the path to your custom VST3 SDK to the project's header search paths. Most users shouldn't
@@ -115,6 +126,7 @@
#include "utilities/juce_VSTCallbackHandler.h"
#include "utilities/juce_VST3ClientExtensions.h"
#include "utilities/juce_NativeScaleFactorNotifier.h"
#include "format_types/juce_ARACommon.h"
#include "utilities/juce_ExtensionsVisitor.h"
#include "processors/juce_AudioProcessorParameter.h"
#include "processors/juce_HostedAudioProcessorParameter.h"
@@ -136,6 +148,7 @@
#include "format_types/juce_VST3PluginFormat.h"
#include "format_types/juce_VSTMidiEventList.h"
#include "format_types/juce_VSTPluginFormat.h"
#include "format_types/juce_ARAHosting.h"
#include "scanning/juce_PluginDirectoryScanner.h"
#include "scanning/juce_PluginListComponent.h"
#include "utilities/juce_AudioProcessorParameterWithID.h"


+ 33
- 0
modules/juce_audio_processors/juce_audio_processors_ara.cpp View 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

+ 2
- 0
modules/juce_audio_processors/processors/juce_PluginDescription.cpp View File

@@ -72,6 +72,7 @@ std::unique_ptr<XmlElement> PluginDescription::createXml() const
e->setAttribute ("numInputs", numInputChannels);
e->setAttribute ("numOutputs", numOutputChannels);
e->setAttribute ("isShell", hasSharedContainer);
e->setAttribute ("hasARAExtension", hasARAExtension);
e->setAttribute ("uid", String::toHexString (deprecatedUid));
@@ -95,6 +96,7 @@ bool PluginDescription::loadFromXml (const XmlElement& xml)
numInputChannels = xml.getIntAttribute ("numInputs");
numOutputChannels = xml.getIntAttribute ("numOutputs");
hasSharedContainer = xml.getBoolAttribute ("isShell", false);
hasARAExtension = xml.getBoolAttribute ("hasARAExtension", false);
deprecatedUid = xml.getStringAttribute ("uid").getHexValue32();
uniqueId = xml.getStringAttribute ("uniqueId", "0").getHexValue32();


+ 3
- 0
modules/juce_audio_processors/processors/juce_PluginDescription.h View File

@@ -122,6 +122,9 @@ public:
/** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */
bool hasSharedContainer = false;
/** True if the plug-in is ARA enabled and can supply a valid ARAFactoryWrapper. */
bool hasARAExtension = false;
/** Returns true if the two descriptions refer to the same plug-in.
This isn't quite as simple as them just having the same file (because of


+ 10
- 0
modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h View File

@@ -106,6 +106,13 @@ struct ExtensionsVisitor
virtual AEffect* getAEffectPtr() const noexcept = 0;
};
/** Can be used to retrieve information about a plugin that provides ARA extensions. */
struct ARAClient
{
virtual ~ARAClient() = default;
virtual void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)>) const = 0;
};
virtual ~ExtensionsVisitor() = default;
/** Will be called if there is no platform specific information available. */
@@ -119,6 +126,9 @@ struct ExtensionsVisitor
/** Called with AU-specific information. */
virtual void visitAudioUnitClient (const AudioUnitClient&) {}
/** Called with ARA-specific information. */
virtual void visitARAClient (const ARAClient&) {}
};
} // namespace juce

Loading…
Cancel
Save