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