| @@ -69,10 +69,11 @@ juce_disable_default_flags() | |||||
| add_subdirectory(extras/Build) | add_subdirectory(extras/Build) | ||||
| # If you want to build the JUCE examples with VST2/AAX support, you'll need to make the VST2/AAX | |||||
| # 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) | if(JUCE_GLOBAL_AAX_SDK_PATH) | ||||
| juce_set_aax_sdk_path("${JUCE_GLOBAL_AAX_SDK_PATH}") | juce_set_aax_sdk_path("${JUCE_GLOBAL_AAX_SDK_PATH}") | ||||
| @@ -82,6 +83,13 @@ if(JUCE_GLOBAL_VST2_SDK_PATH) | |||||
| juce_set_vst2_sdk_path("${JUCE_GLOBAL_VST2_SDK_PATH}") | juce_set_vst2_sdk_path("${JUCE_GLOBAL_VST2_SDK_PATH}") | ||||
| endif() | endif() | ||||
| # The ARA_SDK path should point to the "Umbrella installer" ARA_SDK directory. | |||||
| # The directory can be obtained by recursively cloning https://github.com/Celemony/ARA_SDK and | |||||
| # checking out the tag releases/2.1.0. | |||||
| if(JUCE_GLOBAL_ARA_SDK_PATH) | |||||
| juce_set_ara_sdk_path("${JUCE_GLOBAL_ARA_SDK_PATH}") | |||||
| endif() | |||||
| # We don't build anything other than the juceaide by default, because we want to keep configuration | # We don't build anything other than the juceaide by default, because we want to keep configuration | ||||
| # speedy and the number of targets low. If you want to add targets for the extra projects and | # speedy and the number of targets low. If you want to add targets for the extra projects and | ||||
| # example PIPs (there's a lot of them!), specify -DJUCE_BUILD_EXAMPLES=ON and/or | # example PIPs (there's a lot of them!), specify -DJUCE_BUILD_EXAMPLES=ON and/or | ||||
| @@ -664,10 +664,11 @@ target!). | |||||
| juce_set_aax_sdk_path(<absolute path>) | juce_set_aax_sdk_path(<absolute path>) | ||||
| juce_set_vst2_sdk_path(<absolute path>) | juce_set_vst2_sdk_path(<absolute path>) | ||||
| juce_set_vst3_sdk_path(<absolute path>) | juce_set_vst3_sdk_path(<absolute path>) | ||||
| juce_set_ara_sdk_path(<absolute path>) | |||||
| Call these functions from your CMakeLists to set up your local AAX, VST2, and VST3 SDKs. These | |||||
| 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` | #### `juce_add_module` | ||||
| @@ -24,6 +24,7 @@ juce_generate_juce_header(AudioPluginHost) | |||||
| target_sources(AudioPluginHost PRIVATE | target_sources(AudioPluginHost PRIVATE | ||||
| Source/HostStartup.cpp | Source/HostStartup.cpp | ||||
| Source/Plugins/ARAPlugin.cpp | |||||
| Source/Plugins/IOConfigurationWindow.cpp | Source/Plugins/IOConfigurationWindow.cpp | ||||
| Source/Plugins/InternalPlugins.cpp | Source/Plugins/InternalPlugins.cpp | ||||
| Source/Plugins/PluginGraph.cpp | Source/Plugins/PluginGraph.cpp | ||||
| @@ -46,6 +47,7 @@ target_compile_definitions(AudioPluginHost PRIVATE | |||||
| JUCE_PLUGINHOST_LV2=1 | JUCE_PLUGINHOST_LV2=1 | ||||
| JUCE_PLUGINHOST_VST3=1 | JUCE_PLUGINHOST_VST3=1 | ||||
| JUCE_PLUGINHOST_VST=0 | JUCE_PLUGINHOST_VST=0 | ||||
| JUCE_PLUGINHOST_ARA=0 | |||||
| JUCE_USE_CAMERA=0 | JUCE_USE_CAMERA=0 | ||||
| JUCE_USE_CDBURNER=0 | JUCE_USE_CDBURNER=0 | ||||
| JUCE_USE_CDREADER=0 | JUCE_USE_CDREADER=0 | ||||
| @@ -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; | return nullptr; | ||||
| } | } | ||||
| void PluginGraph::addPlugin (const PluginDescription& desc, Point<double> pos) | |||||
| void PluginGraph::addPlugin (const PluginDescriptionAndPreference& desc, Point<double> pos) | |||||
| { | { | ||||
| std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc); | |||||
| std::shared_ptr<ScopedDPIAwarenessDisabler> dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc.pluginDescription); | |||||
| formatManager.createPluginInstanceAsync (desc, | |||||
| formatManager.createPluginInstanceAsync (desc.pluginDescription, | |||||
| graph.getSampleRate(), | graph.getSampleRate(), | ||||
| graph.getBlockSize(), | graph.getBlockSize(), | ||||
| [this, pos, dpiDisabler] (std::unique_ptr<AudioPluginInstance> instance, const String& error) | |||||
| [this, pos, dpiDisabler, useARA = desc.useARA] (std::unique_ptr<AudioPluginInstance> instance, const String& error) | |||||
| { | { | ||||
| addPluginCallback (std::move (instance), error, pos); | |||||
| addPluginCallback (std::move (instance), error, pos, useARA); | |||||
| }); | }); | ||||
| } | } | ||||
| void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instance, | void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instance, | ||||
| const String& error, Point<double> pos) | |||||
| const String& error, | |||||
| Point<double> pos, | |||||
| PluginDescriptionAndPreference::UseARA useARA) | |||||
| { | { | ||||
| if (instance == nullptr) | if (instance == nullptr) | ||||
| { | { | ||||
| @@ -97,12 +99,21 @@ void PluginGraph::addPluginCallback (std::unique_ptr<AudioPluginInstance> instan | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| if (useARA == PluginDescriptionAndPreference::UseARA::yes | |||||
| && instance->getPluginDescription().hasARAExtension) | |||||
| { | |||||
| instance = std::make_unique<ARAPluginInstanceWrapper> (std::move (instance)); | |||||
| } | |||||
| #endif | |||||
| instance->enableAllBuses(); | instance->enableAllBuses(); | ||||
| if (auto node = graph.addNode (std::move (instance))) | if (auto node = graph.addNode (std::move (instance))) | ||||
| { | { | ||||
| node->properties.set ("x", pos.x); | node->properties.set ("x", pos.x); | ||||
| node->properties.set ("y", pos.y); | node->properties.set ("y", pos.y); | ||||
| node->properties.set ("useARA", useARA == PluginDescriptionAndPreference::UseARA::yes); | |||||
| changed(); | changed(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -193,10 +204,10 @@ void PluginGraph::newDocument() | |||||
| jassert (internalFormat.getAllTypes().size() > 3); | jassert (internalFormat.getAllTypes().size() > 3); | ||||
| addPlugin (internalFormat.getAllTypes()[0], { 0.5, 0.1 }); | |||||
| addPlugin (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] | MessageManager::callAsync ([this] | ||||
| { | { | ||||
| @@ -325,6 +336,7 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep | |||||
| e->setAttribute ("uid", (int) node->nodeID.uid); | e->setAttribute ("uid", (int) node->nodeID.uid); | ||||
| e->setAttribute ("x", node->properties ["x"].toString()); | e->setAttribute ("x", node->properties ["x"].toString()); | ||||
| e->setAttribute ("y", node->properties ["y"].toString()); | e->setAttribute ("y", node->properties ["y"].toString()); | ||||
| e->setAttribute ("useARA", node->properties ["useARA"].toString()); | |||||
| for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) | for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) | ||||
| { | { | ||||
| @@ -365,26 +377,42 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep | |||||
| void PluginGraph::createNodeFromXml (const XmlElement& xml) | void PluginGraph::createNodeFromXml (const XmlElement& xml) | ||||
| { | { | ||||
| PluginDescription pd; | |||||
| PluginDescriptionAndPreference pd; | |||||
| const auto nodeUsesARA = xml.getBoolAttribute ("useARA"); | |||||
| for (auto* e : xml.getChildIterator()) | for (auto* e : xml.getChildIterator()) | ||||
| { | { | ||||
| if (pd.loadFromXml (*e)) | |||||
| if (pd.pluginDescription.loadFromXml (*e)) | |||||
| { | |||||
| pd.useARA = nodeUsesARA ? PluginDescriptionAndPreference::UseARA::yes | |||||
| : PluginDescriptionAndPreference::UseARA::no; | |||||
| break; | break; | ||||
| } | |||||
| } | } | ||||
| auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance> | auto createInstanceWithFallback = [&]() -> std::unique_ptr<AudioPluginInstance> | ||||
| { | { | ||||
| auto createInstance = [this] (const PluginDescription& description) | |||||
| auto createInstance = [this] (const PluginDescriptionAndPreference& description) -> std::unique_ptr<AudioPluginInstance> | |||||
| { | { | ||||
| String errorMessage; | String errorMessage; | ||||
| auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description); | |||||
| auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.pluginDescription); | |||||
| return formatManager.createPluginInstance (description, | |||||
| 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)) | if (auto instance = createInstance (pd)) | ||||
| @@ -392,19 +420,19 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) | |||||
| const auto allFormats = formatManager.getFormats(); | const auto allFormats = formatManager.getFormats(); | ||||
| const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(), | const auto matchingFormat = std::find_if (allFormats.begin(), allFormats.end(), | ||||
| [&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginFormatName; }); | |||||
| [&] (const AudioPluginFormat* f) { return f->getName() == pd.pluginDescription.pluginFormatName; }); | |||||
| if (matchingFormat == allFormats.end()) | if (matchingFormat == allFormats.end()) | ||||
| return nullptr; | return nullptr; | ||||
| const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat); | const auto plugins = knownPlugins.getTypesForFormat (**matchingFormat); | ||||
| const auto matchingPlugin = std::find_if (plugins.begin(), plugins.end(), | const auto matchingPlugin = std::find_if (plugins.begin(), plugins.end(), | ||||
| [&] (const PluginDescription& desc) { return pd.uniqueId == desc.uniqueId; }); | |||||
| [&] (const PluginDescription& desc) { return pd.pluginDescription.uniqueId == desc.uniqueId; }); | |||||
| if (matchingPlugin == plugins.end()) | if (matchingPlugin == plugins.end()) | ||||
| return nullptr; | return nullptr; | ||||
| return createInstance (*matchingPlugin); | |||||
| return createInstance (PluginDescriptionAndPreference { *matchingPlugin }); | |||||
| }; | }; | ||||
| if (auto instance = createInstanceWithFallback()) | if (auto instance = createInstanceWithFallback()) | ||||
| @@ -431,6 +459,7 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) | |||||
| node->properties.set ("x", xml.getDoubleAttribute ("x")); | node->properties.set ("x", xml.getDoubleAttribute ("x")); | ||||
| node->properties.set ("y", xml.getDoubleAttribute ("y")); | node->properties.set ("y", xml.getDoubleAttribute ("y")); | ||||
| node->properties.set ("useARA", xml.getBoolAttribute ("useARA")); | |||||
| for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) | for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) | ||||
| { | { | ||||
| @@ -20,6 +20,29 @@ | |||||
| #include "../UI/PluginWindow.h" | #include "../UI/PluginWindow.h" | ||||
| //============================================================================== | |||||
| /** A type that encapsulates a PluginDescription and some preferences regarding | |||||
| how plugins of that description should be instantiated. | |||||
| */ | |||||
| struct PluginDescriptionAndPreference | |||||
| { | |||||
| enum class UseARA { no, yes }; | |||||
| PluginDescriptionAndPreference() = default; | |||||
| explicit PluginDescriptionAndPreference (PluginDescription pd) | |||||
| : pluginDescription (std::move (pd)), | |||||
| useARA (pluginDescription.hasARAExtension ? PluginDescriptionAndPreference::UseARA::yes | |||||
| : PluginDescriptionAndPreference::UseARA::no) | |||||
| {} | |||||
| PluginDescriptionAndPreference (PluginDescription pd, UseARA ara) | |||||
| : pluginDescription (std::move (pd)), useARA (ara) | |||||
| {} | |||||
| PluginDescription pluginDescription; | |||||
| UseARA useARA = UseARA::no; | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -37,7 +60,7 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| using NodeID = AudioProcessorGraph::NodeID; | using NodeID = AudioProcessorGraph::NodeID; | ||||
| void addPlugin (const PluginDescription&, Point<double>); | |||||
| void addPlugin (const PluginDescriptionAndPreference&, Point<double>); | |||||
| AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const; | AudioProcessorGraph::Node::Ptr getNodeForName (const String& name) const; | ||||
| @@ -85,7 +108,10 @@ private: | |||||
| NodeID getNextUID() noexcept; | NodeID getNextUID() noexcept; | ||||
| void createNodeFromXml (const XmlElement&); | void createNodeFromXml (const XmlElement&); | ||||
| void addPluginCallback (std::unique_ptr<AudioPluginInstance>, const String& error, Point<double>); | |||||
| void addPluginCallback (std::unique_ptr<AudioPluginInstance>, | |||||
| const String& error, | |||||
| Point<double>, | |||||
| PluginDescriptionAndPreference::UseARA useARA); | |||||
| void changeListenerCallback (ChangeBroadcaster*) override; | void changeListenerCallback (ChangeBroadcaster*) override; | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginGraph) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginGraph) | ||||
| @@ -391,6 +391,14 @@ struct GraphEditorPanel::PluginComponent : public Component, | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| bool isNodeUsingARA() const | |||||
| { | |||||
| if (auto node = graph.graph.getNodeForId (pluginID)) | |||||
| return node->properties["useARA"]; | |||||
| return false; | |||||
| } | |||||
| void showPopupMenu() | void showPopupMenu() | ||||
| { | { | ||||
| menu.reset (new PopupMenu); | menu.reset (new PopupMenu); | ||||
| @@ -412,6 +420,12 @@ struct GraphEditorPanel::PluginComponent : public Component, | |||||
| menu->addItem ("Show all parameters", [this] { showWindow (PluginWindow::Type::generic); }); | menu->addItem ("Show all parameters", [this] { showWindow (PluginWindow::Type::generic); }); | ||||
| menu->addItem ("Show debug log", [this] { showWindow (PluginWindow::Type::debug); }); | menu->addItem ("Show debug log", [this] { showWindow (PluginWindow::Type::debug); }); | ||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| if (auto* instance = dynamic_cast<AudioPluginInstance*> (getProcessor())) | |||||
| if (instance->getPluginDescription().hasARAExtension && isNodeUsingARA()) | |||||
| menu->addItem ("Show ARA host controls", [this] { showWindow (PluginWindow::Type::araHost); }); | |||||
| #endif | |||||
| if (autoScaleOptionAvailable) | if (autoScaleOptionAvailable) | ||||
| addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu); | addPluginAutoScaleOptionsSubMenu (dynamic_cast<AudioPluginInstance*> (getProcessor()), *menu); | ||||
| @@ -777,7 +791,7 @@ void GraphEditorPanel::mouseDrag (const MouseEvent& e) | |||||
| stopTimer(); | stopTimer(); | ||||
| } | } | ||||
| void GraphEditorPanel::createNewPlugin (const PluginDescription& desc, Point<int> position) | |||||
| void GraphEditorPanel::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> position) | |||||
| { | { | ||||
| graph.addPlugin (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight())); | graph.addPlugin (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight())); | ||||
| } | } | ||||
| @@ -1273,7 +1287,7 @@ void GraphDocumentComponent::resized() | |||||
| checkAvailableWidth(); | checkAvailableWidth(); | ||||
| } | } | ||||
| void GraphDocumentComponent::createNewPlugin (const PluginDescription& desc, Point<int> pos) | |||||
| void GraphDocumentComponent::createNewPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos) | |||||
| { | { | ||||
| graphPanel->createNewPlugin (desc, pos); | graphPanel->createNewPlugin (desc, pos); | ||||
| } | } | ||||
| @@ -1320,7 +1334,8 @@ void GraphDocumentComponent::itemDropped (const SourceDetails& details) | |||||
| // must be a valid index! | // must be a valid index! | ||||
| jassert (isPositiveAndBelow (pluginTypeIndex, pluginList.getNumTypes())); | jassert (isPositiveAndBelow (pluginTypeIndex, pluginList.getNumTypes())); | ||||
| createNewPlugin (pluginList.getTypes()[pluginTypeIndex], details.localPosition); | |||||
| createNewPlugin (PluginDescriptionAndPreference { pluginList.getTypes()[pluginTypeIndex] }, | |||||
| details.localPosition); | |||||
| } | } | ||||
| void GraphDocumentComponent::showSidePanel (bool showSettingsPanel) | void GraphDocumentComponent::showSidePanel (bool showSettingsPanel) | ||||
| @@ -31,10 +31,11 @@ class GraphEditorPanel : public Component, | |||||
| private Timer | private Timer | ||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | |||||
| GraphEditorPanel (PluginGraph& graph); | GraphEditorPanel (PluginGraph& graph); | ||||
| ~GraphEditorPanel() override; | ~GraphEditorPanel() override; | ||||
| void createNewPlugin (const PluginDescription&, Point<int> position); | |||||
| void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position); | |||||
| void paint (Graphics&) override; | void paint (Graphics&) override; | ||||
| void resized() override; | void resized() override; | ||||
| @@ -103,7 +104,7 @@ public: | |||||
| ~GraphDocumentComponent() override; | ~GraphDocumentComponent() override; | ||||
| //============================================================================== | //============================================================================== | ||||
| void createNewPlugin (const PluginDescription&, Point<int> position); | |||||
| void createNewPlugin (const PluginDescriptionAndPreference&, Point<int> position); | |||||
| void setDoublePrecision (bool doublePrecision); | void setDoublePrecision (bool doublePrecision); | ||||
| bool closeAnyOpenPluginWindows(); | bool closeAnyOpenPluginWindows(); | ||||
| @@ -576,7 +576,7 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/ | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| if (KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuItemID) >= 0) | |||||
| if (getIndexChosenByMenu (menuItemID) >= 0) | |||||
| createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f), | createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f), | ||||
| proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) }); | proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) }); | ||||
| } | } | ||||
| @@ -588,12 +588,64 @@ void MainHostWindow::menuBarActivated (bool isActivated) | |||||
| graphHolder->unfocusKeyboardComponent(); | graphHolder->unfocusKeyboardComponent(); | ||||
| } | } | ||||
| void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos) | |||||
| void MainHostWindow::createPlugin (const PluginDescriptionAndPreference& desc, Point<int> pos) | |||||
| { | { | ||||
| if (graphHolder != nullptr) | if (graphHolder != nullptr) | ||||
| graphHolder->createNewPlugin (desc, pos); | graphHolder->createNewPlugin (desc, pos); | ||||
| } | } | ||||
| static bool containsDuplicateNames (const Array<PluginDescription>& plugins, const String& name) | |||||
| { | |||||
| int matches = 0; | |||||
| for (auto& p : plugins) | |||||
| if (p.name == name && ++matches > 1) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| static constexpr int menuIDBase = 0x324503f4; | |||||
| static void addToMenu (const KnownPluginList::PluginTree& tree, | |||||
| PopupMenu& m, | |||||
| const Array<PluginDescription>& allPlugins, | |||||
| Array<PluginDescriptionAndPreference>& addedPlugins) | |||||
| { | |||||
| for (auto* sub : tree.subFolders) | |||||
| { | |||||
| PopupMenu subMenu; | |||||
| addToMenu (*sub, subMenu, allPlugins, addedPlugins); | |||||
| m.addSubMenu (sub->folder, subMenu, true, nullptr, false, 0); | |||||
| } | |||||
| auto addPlugin = [&] (const auto& descriptionAndPreference, const auto& pluginName) | |||||
| { | |||||
| addedPlugins.add (descriptionAndPreference); | |||||
| const auto menuID = addedPlugins.size() - 1 + menuIDBase; | |||||
| m.addItem (menuID, pluginName, true, false); | |||||
| }; | |||||
| for (auto& plugin : tree.plugins) | |||||
| { | |||||
| auto name = plugin.name; | |||||
| if (containsDuplicateNames (tree.plugins, name)) | |||||
| name << " (" << plugin.pluginFormatName << ')'; | |||||
| addPlugin (PluginDescriptionAndPreference { plugin, PluginDescriptionAndPreference::UseARA::no }, name); | |||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| if (plugin.hasARAExtension) | |||||
| { | |||||
| name << " (ARA)"; | |||||
| addPlugin (PluginDescriptionAndPreference { plugin }, name); | |||||
| } | |||||
| #endif | |||||
| } | |||||
| } | |||||
| void MainHostWindow::addPluginsToMenu (PopupMenu& m) | void MainHostWindow::addPluginsToMenu (PopupMenu& m) | ||||
| { | { | ||||
| if (graphHolder != nullptr) | if (graphHolder != nullptr) | ||||
| @@ -606,7 +658,7 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m) | |||||
| m.addSeparator(); | m.addSeparator(); | ||||
| pluginDescriptions = knownPluginList.getTypes(); | |||||
| auto pluginDescriptions = knownPluginList.getTypes(); | |||||
| // This avoids showing the internal types again later on in the list | // This avoids showing the internal types again later on in the list | ||||
| pluginDescriptions.removeIf ([] (PluginDescription& desc) | pluginDescriptions.removeIf ([] (PluginDescription& desc) | ||||
| @@ -614,15 +666,23 @@ void MainHostWindow::addPluginsToMenu (PopupMenu& m) | |||||
| return desc.pluginFormatName == InternalPluginFormat::getIdentifier(); | return desc.pluginFormatName == InternalPluginFormat::getIdentifier(); | ||||
| }); | }); | ||||
| KnownPluginList::addToMenu (m, pluginDescriptions, pluginSortMethod); | |||||
| auto tree = KnownPluginList::createTree (pluginDescriptions, pluginSortMethod); | |||||
| pluginDescriptionsAndPreference = {}; | |||||
| addToMenu (*tree, m, pluginDescriptions, pluginDescriptionsAndPreference); | |||||
| } | |||||
| 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())) | if (menuID >= 1 && menuID < (int) (1 + internalTypes.size())) | ||||
| return internalTypes[(size_t) (menuID - 1)]; | |||||
| return PluginDescriptionAndPreference { internalTypes[(size_t) (menuID - 1)] }; | |||||
| return pluginDescriptions[KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuID)]; | |||||
| return pluginDescriptionsAndPreference[getIndexChosenByMenu (menuID)]; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -905,7 +965,7 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y) | |||||
| for (int i = 0; i < jmin (5, typesFound.size()); ++i) | for (int i = 0; i < jmin (5, typesFound.size()); ++i) | ||||
| if (auto* desc = typesFound.getUnchecked(i)) | if (auto* desc = typesFound.getUnchecked(i)) | ||||
| createPlugin (*desc, pos); | |||||
| createPlugin (PluginDescriptionAndPreference { *desc }, pos); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -100,10 +100,10 @@ public: | |||||
| void tryToQuitApplication(); | void tryToQuitApplication(); | ||||
| void createPlugin (const PluginDescription&, Point<int> pos); | |||||
| void createPlugin (const PluginDescriptionAndPreference&, Point<int> pos); | |||||
| void addPluginsToMenu (PopupMenu&); | void addPluginsToMenu (PopupMenu&); | ||||
| PluginDescription getChosenType (int menuID) const; | |||||
| PluginDescriptionAndPreference getChosenType (int menuID) const; | |||||
| std::unique_ptr<GraphDocumentComponent> graphHolder; | std::unique_ptr<GraphDocumentComponent> graphHolder; | ||||
| @@ -117,6 +117,8 @@ private: | |||||
| void showAudioSettings(); | void showAudioSettings(); | ||||
| int getIndexChosenByMenu (int menuID) const; | |||||
| //============================================================================== | //============================================================================== | ||||
| AudioDeviceManager deviceManager; | AudioDeviceManager deviceManager; | ||||
| AudioPluginFormatManager formatManager; | AudioPluginFormatManager formatManager; | ||||
| @@ -124,7 +126,7 @@ private: | |||||
| std::vector<PluginDescription> internalTypes; | std::vector<PluginDescription> internalTypes; | ||||
| KnownPluginList knownPluginList; | KnownPluginList knownPluginList; | ||||
| KnownPluginList::SortMethod pluginSortMethod; | KnownPluginList::SortMethod pluginSortMethod; | ||||
| Array<PluginDescription> pluginDescriptions; | |||||
| Array<PluginDescriptionAndPreference> pluginDescriptionsAndPreference; | |||||
| class PluginListWindow; | class PluginListWindow; | ||||
| std::unique_ptr<PluginListWindow> pluginListWindow; | std::unique_ptr<PluginListWindow> pluginListWindow; | ||||
| @@ -19,6 +19,7 @@ | |||||
| #pragma once | #pragma once | ||||
| #include "../Plugins/IOConfigurationWindow.h" | #include "../Plugins/IOConfigurationWindow.h" | ||||
| #include "../Plugins/ARAPlugin.h" | |||||
| inline String getFormatSuffix (const AudioProcessor* plugin) | inline String getFormatSuffix (const AudioProcessor* plugin) | ||||
| { | { | ||||
| @@ -148,6 +149,7 @@ public: | |||||
| programs, | programs, | ||||
| audioIO, | audioIO, | ||||
| debug, | debug, | ||||
| araHost, | |||||
| numTypes | numTypes | ||||
| }; | }; | ||||
| @@ -234,6 +236,16 @@ private: | |||||
| type = PluginWindow::Type::generic; | type = PluginWindow::Type::generic; | ||||
| } | } | ||||
| if (type == PluginWindow::Type::araHost) | |||||
| { | |||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| if (auto* araPluginInstanceWrapper = dynamic_cast<ARAPluginInstanceWrapper*> (&processor)) | |||||
| if (auto* ui = araPluginInstanceWrapper->createARAHostEditor()) | |||||
| return ui; | |||||
| #endif | |||||
| return {}; | |||||
| } | |||||
| if (type == PluginWindow::Type::generic) return new GenericAudioProcessorEditor (processor); | if (type == PluginWindow::Type::generic) return new GenericAudioProcessorEditor (processor); | ||||
| if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor); | if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor); | ||||
| if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor); | if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor); | ||||
| @@ -252,6 +264,7 @@ private: | |||||
| case Type::programs: return "Programs"; | case Type::programs: return "Programs"; | ||||
| case Type::audioIO: return "IO"; | case Type::audioIO: return "IO"; | ||||
| case Type::debug: return "Debug"; | case Type::debug: return "Debug"; | ||||
| case Type::araHost: return "ARAHost"; | |||||
| case Type::numTypes: | case Type::numTypes: | ||||
| default: return {}; | default: return {}; | ||||
| } | } | ||||
| @@ -502,9 +502,17 @@ function(juce_add_module module_path) | |||||
| "${lv2_base_path}/lilv/src") | "${lv2_base_path}/lilv/src") | ||||
| target_link_libraries(juce_audio_processors INTERFACE juce_lilv_headers) | target_link_libraries(juce_audio_processors INTERFACE juce_lilv_headers) | ||||
| add_library(juce_ara_headers INTERFACE) | |||||
| target_include_directories(juce_ara_headers INTERFACE | |||||
| "$<$<TARGET_EXISTS:juce_ara_sdk>:$<TARGET_PROPERTY:juce_ara_sdk,INTERFACE_INCLUDE_DIRECTORIES>>") | |||||
| target_link_libraries(juce_audio_processors INTERFACE juce_ara_headers) | |||||
| if(JUCE_ARG_ALIAS_NAMESPACE) | if(JUCE_ARG_ALIAS_NAMESPACE) | ||||
| add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_vst3_headers ALIAS juce_vst3_headers) | add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_vst3_headers ALIAS juce_vst3_headers) | ||||
| add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_lilv_headers ALIAS juce_lilv_headers) | add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_lilv_headers ALIAS juce_lilv_headers) | ||||
| add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_ara_headers ALIAS juce_ara_headers) | |||||
| endif() | endif() | ||||
| endif() | endif() | ||||
| @@ -1969,6 +1969,22 @@ function(juce_set_vst3_sdk_path path) | |||||
| target_include_directories(juce_vst3_sdk INTERFACE "${path}") | target_include_directories(juce_vst3_sdk INTERFACE "${path}") | ||||
| endfunction() | endfunction() | ||||
| function(juce_set_ara_sdk_path path) | |||||
| if(TARGET juce_ara_sdk) | |||||
| message(FATAL_ERROR "juce_set_ara_sdk_path should only be called once") | |||||
| endif() | |||||
| _juce_make_absolute(path) | |||||
| if(NOT EXISTS "${path}") | |||||
| message(FATAL_ERROR "Could not find ARA SDK at the specified path: ${path}") | |||||
| endif() | |||||
| add_library(juce_ara_sdk INTERFACE IMPORTED GLOBAL) | |||||
| target_include_directories(juce_ara_sdk INTERFACE "${path}") | |||||
| endfunction() | |||||
| # ================================================================================================== | # ================================================================================================== | ||||
| function(juce_disable_default_flags) | function(juce_disable_default_flags) | ||||
| @@ -133,6 +133,18 @@ public: | |||||
| /** Returns true if instantiation of this plugin type must be done from a non-message thread. */ | /** Returns true if instantiation of this plugin type must be done from a non-message thread. */ | ||||
| virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const = 0; | virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const = 0; | ||||
| /** A callback lambda that is passed to getARAFactory() */ | |||||
| using ARAFactoryCreationCallback = std::function<void (ARAFactoryResult)>; | |||||
| /** Tries to create an ::ARAFactoryWrapper for this description. | |||||
| The result of the operation will be wrapped into an ARAFactoryResult, | |||||
| which will be passed to a callback object supplied by the caller. | |||||
| @see AudioPluginFormatManager::createARAFactoryAsync | |||||
| */ | |||||
| virtual void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) { callback ({}); } | |||||
| protected: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| friend class AudioPluginFormatManager; | friend class AudioPluginFormatManager; | ||||
| @@ -129,6 +129,22 @@ std::unique_ptr<AudioPluginInstance> AudioPluginFormatManager::createPluginInsta | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| void AudioPluginFormatManager::createARAFactoryAsync (const PluginDescription& description, | |||||
| AudioPluginFormat::ARAFactoryCreationCallback callback) const | |||||
| { | |||||
| String errorMessage; | |||||
| if (auto* format = findFormatForDescription (description, errorMessage)) | |||||
| { | |||||
| format->createARAFactoryAsync (description, callback); | |||||
| } | |||||
| else | |||||
| { | |||||
| errorMessage = NEEDS_TRANS ("Couldn't find format for the provided description"); | |||||
| callback ({ {}, std::move (errorMessage) }); | |||||
| } | |||||
| } | |||||
| void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | ||||
| double initialSampleRate, int initialBufferSize, | double initialSampleRate, int initialBufferSize, | ||||
| AudioPluginFormat::PluginCreationCallback callback) | AudioPluginFormat::PluginCreationCallback callback) | ||||
| @@ -102,6 +102,22 @@ public: | |||||
| double initialSampleRate, int initialBufferSize, | double initialSampleRate, int initialBufferSize, | ||||
| AudioPluginFormat::PluginCreationCallback callback); | AudioPluginFormat::PluginCreationCallback callback); | ||||
| /** Tries to create an ::ARAFactoryWrapper for this description. | |||||
| The result of the operation will be wrapped into an ARAFactoryResult, | |||||
| which will be passed to a callback object supplied by the caller. | |||||
| The operation may fail, in which case the callback will be called with | |||||
| with a result object where ARAFactoryResult::araFactory.get() will return | |||||
| a nullptr. | |||||
| In case of success the returned ::ARAFactoryWrapper will ensure that | |||||
| modules required for the correct functioning of the ARAFactory will remain | |||||
| loaded for the lifetime of the object. | |||||
| */ | |||||
| void createARAFactoryAsync (const PluginDescription& description, | |||||
| AudioPluginFormat::ARAFactoryCreationCallback callback) const; | |||||
| /** Checks that the file or component for this plugin actually still exists. | /** Checks that the file or component for this plugin actually still exists. | ||||
| (This won't try to load the plugin) | (This won't try to load the plugin) | ||||
| */ | */ | ||||
| @@ -0,0 +1,69 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE 7 technical preview. | |||||
| Copyright (c) 2022 - Raw Material Software Limited | |||||
| You may use this code under the terms of the GPL v3 | |||||
| (see www.gnu.org/licenses). | |||||
| For the technical preview this file cannot be licensed commercially. | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS)) | |||||
| #include <ARA_Library/Debug/ARADebug.h> | |||||
| namespace juce | |||||
| { | |||||
| static void dummyARAInterfaceAssert (ARA::ARAAssertCategory, const void*, const char*) | |||||
| {} | |||||
| static ARA::ARAInterfaceConfiguration createInterfaceConfig (const ARA::ARAFactory* araFactory) | |||||
| { | |||||
| static auto* assertFunction = &dummyARAInterfaceAssert; | |||||
| #if ARA_VALIDATE_API_CALLS | |||||
| assertFunction = &::ARA::ARAInterfaceAssert; | |||||
| static std::once_flag flag; | |||||
| std::call_once (flag, [] { ARA::ARASetExternalAssertReference (&assertFunction); }); | |||||
| #endif | |||||
| return makeARASizedStruct (&ARA::ARAInterfaceConfiguration::assertFunctionAddress, | |||||
| jmin (araFactory->highestSupportedApiGeneration, (ARA::ARAAPIGeneration) ARA::kARAAPIGeneration_2_X_Draft), | |||||
| &assertFunction); | |||||
| } | |||||
| static std::shared_ptr<const ARA::ARAFactory> getOrCreateARAFactory (const ARA::ARAFactory* ptr, | |||||
| std::function<void (const ARA::ARAFactory*)> onDelete) | |||||
| { | |||||
| JUCE_ASSERT_MESSAGE_THREAD | |||||
| static std::unordered_map<const ARA::ARAFactory*, std::weak_ptr<const ARA::ARAFactory>> cache; | |||||
| auto& cachePtr = cache[ptr]; | |||||
| if (const auto obj = cachePtr.lock()) | |||||
| return obj; | |||||
| const auto interfaceConfig = createInterfaceConfig (ptr); | |||||
| ptr->initializeARAWithConfiguration (&interfaceConfig); | |||||
| const auto obj = std::shared_ptr<const ARA::ARAFactory> (ptr, [deleter = std::move (onDelete)] (const ARA::ARAFactory* factory) | |||||
| { | |||||
| factory->uninitializeARA(); | |||||
| deleter (factory); | |||||
| }); | |||||
| cachePtr = obj; | |||||
| return obj; | |||||
| } | |||||
| } | |||||
| #endif | |||||
| @@ -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; | StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | ||||
| bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
| FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
| void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -24,6 +24,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||||
| #include <AudioUnit/AUCocoaUIView.h> | #include <AudioUnit/AUCocoaUIView.h> | ||||
| #include <CoreAudioKit/AUGenericView.h> | #include <CoreAudioKit/AUGenericView.h> | ||||
| #include <AudioToolbox/AudioUnitUtilities.h> | #include <AudioToolbox/AudioUnitUtilities.h> | ||||
| #if JUCE_PLUGINHOST_ARA | |||||
| #include <ARA_API/ARAAudioUnit.h> | |||||
| #endif | |||||
| #endif | #endif | ||||
| #include <CoreMIDI/MIDIServices.h> | #include <CoreMIDI/MIDIServices.h> | ||||
| @@ -422,6 +427,197 @@ namespace AudioUnitFormatHelpers | |||||
| } | } | ||||
| } | } | ||||
| static bool hasARAExtension (AudioUnit audioUnit) | |||||
| { | |||||
| #if JUCE_PLUGINHOST_ARA | |||||
| UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); | |||||
| Boolean isWriteable = FALSE; | |||||
| OSStatus status = AudioUnitGetPropertyInfo (audioUnit, | |||||
| ARA::kAudioUnitProperty_ARAFactory, | |||||
| kAudioUnitScope_Global, | |||||
| 0, | |||||
| &propertySize, | |||||
| &isWriteable); | |||||
| if ((status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable) | |||||
| return true; | |||||
| #else | |||||
| ignoreUnused (audioUnit); | |||||
| #endif | |||||
| return false; | |||||
| } | |||||
| struct AudioUnitDeleter | |||||
| { | |||||
| void operator() (AudioUnit au) const { AudioComponentInstanceDispose (au); } | |||||
| }; | |||||
| using AudioUnitUniquePtr = std::unique_ptr<std::remove_pointer_t<AudioUnit>, AudioUnitDeleter>; | |||||
| using AudioUnitSharedPtr = std::shared_ptr<std::remove_pointer_t<AudioUnit>>; | |||||
| using AudioUnitWeakPtr = std::weak_ptr<std::remove_pointer_t<AudioUnit>>; | |||||
| static std::shared_ptr<const ARA::ARAFactory> getARAFactory (AudioUnitSharedPtr audioUnit) | |||||
| { | |||||
| #if JUCE_PLUGINHOST_ARA | |||||
| jassert (audioUnit != nullptr); | |||||
| UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); | |||||
| ARA::ARAAudioUnitFactory audioUnitFactory { ARA::kARAAudioUnitMagic, nullptr }; | |||||
| if (hasARAExtension (audioUnit.get())) | |||||
| { | |||||
| OSStatus status = AudioUnitGetProperty (audioUnit.get(), | |||||
| ARA::kAudioUnitProperty_ARAFactory, | |||||
| kAudioUnitScope_Global, | |||||
| 0, | |||||
| &audioUnitFactory, | |||||
| &propertySize); | |||||
| if ((status == noErr) | |||||
| && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) | |||||
| && (audioUnitFactory.inOutMagicNumber == ARA::kARAAudioUnitMagic)) | |||||
| { | |||||
| jassert (audioUnitFactory.outFactory != nullptr); | |||||
| return getOrCreateARAFactory (audioUnitFactory.outFactory, | |||||
| [owningAuPtr = std::move (audioUnit)] (const ARA::ARAFactory*) {}); | |||||
| } | |||||
| } | |||||
| #else | |||||
| ignoreUnused (audioUnit); | |||||
| #endif | |||||
| return {}; | |||||
| } | |||||
| struct VersionedAudioComponent | |||||
| { | |||||
| AudioComponent audioComponent = nullptr; | |||||
| bool isAUv3 = false; | |||||
| bool operator< (const VersionedAudioComponent& other) const { return audioComponent < other.audioComponent; } | |||||
| }; | |||||
| using AudioUnitCreationCallback = std::function<void (AudioUnit, OSStatus)>; | |||||
| static void createAudioUnit (VersionedAudioComponent versionedComponent, AudioUnitCreationCallback callback) | |||||
| { | |||||
| struct AUAsyncInitializationCallback | |||||
| { | |||||
| typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus); | |||||
| explicit AUAsyncInitializationCallback (AudioUnitCreationCallback inOriginalCallback) | |||||
| : originalCallback (std::move (inOriginalCallback)) | |||||
| { | |||||
| block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion); | |||||
| } | |||||
| AUCompletionCallbackBlock getBlock() noexcept { return block; } | |||||
| void completion (AudioComponentInstance audioUnit, OSStatus err) | |||||
| { | |||||
| originalCallback (audioUnit, err); | |||||
| delete this; | |||||
| } | |||||
| double sampleRate; | |||||
| int framesPerBuffer; | |||||
| AudioUnitCreationCallback originalCallback; | |||||
| ObjCBlock<AUCompletionCallbackBlock> block; | |||||
| }; | |||||
| auto callbackBlock = new AUAsyncInitializationCallback (std::move (callback)); | |||||
| if (versionedComponent.isAUv3) | |||||
| { | |||||
| if (@available (macOS 10.11, *)) | |||||
| { | |||||
| AudioComponentInstantiate (versionedComponent.audioComponent, kAudioComponentInstantiation_LoadOutOfProcess, | |||||
| callbackBlock->getBlock()); | |||||
| return; | |||||
| } | |||||
| } | |||||
| AudioComponentInstance audioUnit; | |||||
| auto err = AudioComponentInstanceNew (versionedComponent.audioComponent, &audioUnit); | |||||
| callbackBlock->completion (err != noErr ? nullptr : audioUnit, err); | |||||
| } | |||||
| struct AudioComponentResult | |||||
| { | |||||
| explicit AudioComponentResult (String error) : errorMessage (std::move (error)) {} | |||||
| explicit AudioComponentResult (VersionedAudioComponent auComponent) : component (std::move (auComponent)) {} | |||||
| bool isValid() const { return component.audioComponent != nullptr; } | |||||
| VersionedAudioComponent component; | |||||
| String errorMessage; | |||||
| }; | |||||
| static AudioComponentResult getAudioComponent (AudioUnitPluginFormat& format, const PluginDescription& desc) | |||||
| { | |||||
| using namespace AudioUnitFormatHelpers; | |||||
| AudioUnitPluginFormat audioUnitPluginFormat; | |||||
| if (! format.fileMightContainThisPluginType (desc.fileOrIdentifier)) | |||||
| return AudioComponentResult { NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in") }; | |||||
| String pluginName, version, manufacturer; | |||||
| AudioComponentDescription componentDesc; | |||||
| AudioComponent auComponent; | |||||
| String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description"); | |||||
| if (! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer) | |||||
| && ! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) | |||||
| { | |||||
| return AudioComponentResult { errMessage }; | |||||
| } | |||||
| if ((auComponent = AudioComponentFindNext (nullptr, &componentDesc)) == nullptr) | |||||
| { | |||||
| return AudioComponentResult { errMessage }; | |||||
| } | |||||
| if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr) | |||||
| { | |||||
| return AudioComponentResult { errMessage }; | |||||
| } | |||||
| const bool isAUv3 = AudioUnitFormatHelpers::isPluginAUv3 (componentDesc); | |||||
| return AudioComponentResult { { auComponent, isAUv3 } }; | |||||
| } | |||||
| static void getOrCreateARAAudioUnit (VersionedAudioComponent auComponent, std::function<void (AudioUnitSharedPtr)> callback) | |||||
| { | |||||
| static std::map<VersionedAudioComponent, AudioUnitWeakPtr> audioUnitARACache; | |||||
| if (auto audioUnit = audioUnitARACache[auComponent].lock()) | |||||
| { | |||||
| callback (std::move (audioUnit)); | |||||
| return; | |||||
| } | |||||
| createAudioUnit (auComponent, [auComponent, cb = std::move (callback)] (AudioUnit audioUnit, OSStatus err) | |||||
| { | |||||
| cb ([auComponent, audioUnit, err]() -> AudioUnitSharedPtr | |||||
| { | |||||
| if (err != noErr) | |||||
| return nullptr; | |||||
| AudioUnitSharedPtr auPtr { AudioUnitUniquePtr { audioUnit } }; | |||||
| audioUnitARACache[auComponent] = auPtr; | |||||
| return auPtr; | |||||
| }()); | |||||
| }); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class AudioUnitPluginWindowCocoa; | class AudioUnitPluginWindowCocoa; | ||||
| @@ -974,6 +1170,23 @@ public: | |||||
| desc.numInputChannels = getTotalNumInputChannels(); | desc.numInputChannels = getTotalNumInputChannels(); | ||||
| desc.numOutputChannels = getTotalNumOutputChannels(); | desc.numOutputChannels = getTotalNumOutputChannels(); | ||||
| desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); | desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); | ||||
| #if JUCE_PLUGINHOST_ARA | |||||
| desc.hasARAExtension = [&] | |||||
| { | |||||
| UInt32 propertySize = sizeof (ARA::ARAAudioUnitFactory); | |||||
| Boolean isWriteable = FALSE; | |||||
| OSStatus status = AudioUnitGetPropertyInfo (audioUnit, | |||||
| ARA::kAudioUnitProperty_ARAFactory, | |||||
| kAudioUnitScope_Global, | |||||
| 0, | |||||
| &propertySize, | |||||
| &isWriteable); | |||||
| return (status == noErr) && (propertySize == sizeof (ARA::ARAAudioUnitFactory)) && ! isWriteable; | |||||
| }(); | |||||
| #endif | |||||
| } | } | ||||
| void getExtensions (ExtensionsVisitor& visitor) const override | void getExtensions (ExtensionsVisitor& visitor) const override | ||||
| @@ -988,6 +1201,33 @@ public: | |||||
| }; | }; | ||||
| visitor.visitAudioUnitClient (Extensions { this }); | visitor.visitAudioUnitClient (Extensions { this }); | ||||
| #ifdef JUCE_PLUGINHOST_ARA | |||||
| struct ARAExtensions : public ExtensionsVisitor::ARAClient | |||||
| { | |||||
| explicit ARAExtensions (const AudioUnitPluginInstance* instanceIn) : instance (instanceIn) {} | |||||
| void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const override | |||||
| { | |||||
| getOrCreateARAAudioUnit ({ instance->auComponent, instance->isAUv3 }, | |||||
| [origCb = std::move (cb)] (auto dylibKeepAliveAudioUnit) | |||||
| { | |||||
| origCb ([&]() -> ARAFactoryWrapper | |||||
| { | |||||
| if (dylibKeepAliveAudioUnit != nullptr) | |||||
| return ARAFactoryWrapper { ::juce::getARAFactory (std::move (dylibKeepAliveAudioUnit)) }; | |||||
| return ARAFactoryWrapper { nullptr }; | |||||
| }()); | |||||
| }); | |||||
| } | |||||
| const AudioUnitPluginInstance* instance = nullptr; | |||||
| }; | |||||
| if (hasARAExtension (audioUnit)) | |||||
| visitor.visitARAClient (ARAExtensions (this)); | |||||
| #endif | |||||
| } | } | ||||
| void* getPlatformSpecificData() override { return audioUnit; } | void* getPlatformSpecificData() override { return audioUnit; } | ||||
| @@ -2639,95 +2879,54 @@ void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, | |||||
| double rate, int blockSize, | double rate, int blockSize, | ||||
| PluginCreationCallback callback) | PluginCreationCallback callback) | ||||
| { | { | ||||
| using namespace AudioUnitFormatHelpers; | |||||
| auto auComponentResult = getAudioComponent (*this, desc); | |||||
| if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | |||||
| if (! auComponentResult.isValid()) | |||||
| { | { | ||||
| String pluginName, version, manufacturer; | |||||
| 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 | bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const | ||||
| @@ -20,6 +20,18 @@ | |||||
| #include "juce_VST3Headers.h" | #include "juce_VST3Headers.h" | ||||
| #include "juce_VST3Common.h" | #include "juce_VST3Common.h" | ||||
| #include "juce_ARACommon.h" | |||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| #include <ARA_API/ARAVST3.h> | |||||
| namespace ARA | |||||
| { | |||||
| DEF_CLASS_IID (IMainFactory) | |||||
| DEF_CLASS_IID (IPlugInEntryPoint) | |||||
| DEF_CLASS_IID (IPlugInEntryPoint2) | |||||
| } | |||||
| #endif | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| @@ -804,6 +816,20 @@ struct DescriptionFactory | |||||
| auto numClasses = factory->countClasses(); | auto numClasses = factory->countClasses(); | ||||
| // Every ARA::IMainFactory must have a matching Steinberg::IComponent. | |||||
| // The match is determined by the two classes having the same name. | |||||
| std::unordered_set<String> araMainFactoryClassNames; | |||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| for (Steinberg::int32 i = 0; i < numClasses; ++i) | |||||
| { | |||||
| PClassInfo info; | |||||
| factory->getClassInfo (i, &info); | |||||
| if (std::strcmp (info.category, kARAMainFactoryClass) == 0) | |||||
| araMainFactoryClassNames.insert (info.name); | |||||
| } | |||||
| #endif | |||||
| for (Steinberg::int32 i = 0; i < numClasses; ++i) | for (Steinberg::int32 i = 0; i < numClasses; ++i) | ||||
| { | { | ||||
| PClassInfo info; | PClassInfo info; | ||||
| @@ -867,6 +893,9 @@ struct DescriptionFactory | |||||
| } | } | ||||
| } | } | ||||
| if (araMainFactoryClassNames.find (name) != araMainFactoryClassNames.end()) | |||||
| desc.hasARAExtension = true; | |||||
| if (desc.uniqueId != 0) | if (desc.uniqueId != 0) | ||||
| result = performOnDescription (desc); | result = performOnDescription (desc); | ||||
| @@ -1330,6 +1359,72 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) | ||||
| }; | }; | ||||
| template <typename Type, size_t N> | |||||
| static int compareWithString (Type (&charArray)[N], const String& str) | |||||
| { | |||||
| return std::strncmp (str.toRawUTF8(), | |||||
| charArray, | |||||
| std::min (str.getNumBytesAsUTF8(), (size_t) numElementsInArray (charArray))); | |||||
| } | |||||
| template <typename Callback> | |||||
| static void forEachARAFactory (IPluginFactory* pluginFactory, Callback&& cb) | |||||
| { | |||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| const auto numClasses = pluginFactory->countClasses(); | |||||
| for (Steinberg::int32 i = 0; i < numClasses; ++i) | |||||
| { | |||||
| PClassInfo info; | |||||
| pluginFactory->getClassInfo (i, &info); | |||||
| if (std::strcmp (info.category, kARAMainFactoryClass) == 0) | |||||
| { | |||||
| const bool keepGoing = cb (info); | |||||
| if (! keepGoing) | |||||
| break; | |||||
| } | |||||
| } | |||||
| #else | |||||
| ignoreUnused (pluginFactory, cb); | |||||
| #endif | |||||
| } | |||||
| static std::shared_ptr<const ARA::ARAFactory> getARAFactory (Steinberg::IPluginFactory* pluginFactory, const String& pluginName) | |||||
| { | |||||
| std::shared_ptr<const ARA::ARAFactory> factory; | |||||
| #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS) | |||||
| forEachARAFactory (pluginFactory, | |||||
| [&pluginFactory, &pluginName, &factory] (const auto& pcClassInfo) | |||||
| { | |||||
| if (compareWithString (pcClassInfo.name, pluginName) == 0) | |||||
| { | |||||
| ARA::IMainFactory* source; | |||||
| if (pluginFactory->createInstance (pcClassInfo.cid, ARA::IMainFactory::iid, (void**) &source) | |||||
| == Steinberg::kResultOk) | |||||
| { | |||||
| factory = getOrCreateARAFactory (source->getFactory(), | |||||
| [source] (const ARA::ARAFactory*) { source->release(); }); | |||||
| return false; | |||||
| } | |||||
| jassert (source == nullptr); | |||||
| } | |||||
| return true; | |||||
| }); | |||||
| #else | |||||
| ignoreUnused (pluginFactory, pluginName); | |||||
| #endif | |||||
| return factory; | |||||
| } | |||||
| static std::shared_ptr<const ARA::ARAFactory> getARAFactory (VST3ModuleHandle& module) | |||||
| { | |||||
| auto* pluginFactory = module.getPluginFactory(); | |||||
| return getARAFactory (pluginFactory, module.getName()); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| struct VST3PluginWindow : public AudioProcessorEditor, | struct VST3PluginWindow : public AudioProcessorEditor, | ||||
| private ComponentMovementWatcher, | private ComponentMovementWatcher, | ||||
| @@ -1677,6 +1772,27 @@ private: | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods | JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) // warning about overriding deprecated methods | ||||
| //============================================================================== | |||||
| static bool hasARAExtension (IPluginFactory* pluginFactory, const String& pluginClassName) | |||||
| { | |||||
| bool result = false; | |||||
| forEachARAFactory (pluginFactory, | |||||
| [&pluginClassName, &result] (const auto& pcClassInfo) | |||||
| { | |||||
| if (compareWithString (pcClassInfo.name, pluginClassName) == 0) | |||||
| { | |||||
| result = true; | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| }); | |||||
| return result; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| struct VST3ComponentHolder | struct VST3ComponentHolder | ||||
| { | { | ||||
| @@ -1802,6 +1918,8 @@ struct VST3ComponentHolder | |||||
| totalNumInputChannels, | totalNumInputChannels, | ||||
| totalNumOutputChannels); | totalNumOutputChannels); | ||||
| description.hasARAExtension = hasARAExtension (factory, description.name); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -2280,7 +2398,8 @@ public: | |||||
| void getExtensions (ExtensionsVisitor& visitor) const override | void getExtensions (ExtensionsVisitor& visitor) const override | ||||
| { | { | ||||
| struct Extensions : public ExtensionsVisitor::VST3Client | |||||
| struct Extensions : public ExtensionsVisitor::VST3Client, | |||||
| public ExtensionsVisitor::ARAClient | |||||
| { | { | ||||
| explicit Extensions (const VST3PluginInstance* instanceIn) : instance (instanceIn) {} | explicit Extensions (const VST3PluginInstance* instanceIn) : instance (instanceIn) {} | ||||
| @@ -2293,10 +2412,21 @@ public: | |||||
| return instance->setStateFromPresetFile (rawData); | return instance->setStateFromPresetFile (rawData); | ||||
| } | } | ||||
| void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)> cb) const noexcept override | |||||
| { | |||||
| cb (ARAFactoryWrapper { ::juce::getARAFactory (*(instance->holder->module)) }); | |||||
| } | |||||
| const VST3PluginInstance* instance = nullptr; | const VST3PluginInstance* instance = nullptr; | ||||
| }; | }; | ||||
| visitor.visitVST3Client (Extensions { this }); | |||||
| Extensions extensions { this }; | |||||
| visitor.visitVST3Client (extensions); | |||||
| if (::juce::getARAFactory (*(holder->module))) | |||||
| { | |||||
| visitor.visitARAClient (extensions); | |||||
| } | |||||
| } | } | ||||
| void* getPlatformSpecificData() override { return holder->component; } | void* getPlatformSpecificData() override { return holder->component; } | ||||
| @@ -3107,7 +3237,7 @@ private: | |||||
| if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0) | if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0) | ||||
| bypassParam = param; | bypassParam = param; | ||||
| std::function<AudioProcessorParameterGroup*(Vst::UnitID)> findOrCreateGroup; | |||||
| std::function<AudioProcessorParameterGroup* (Vst::UnitID)> findOrCreateGroup; | |||||
| findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID) | findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup] (Vst::UnitID groupID) | ||||
| { | { | ||||
| auto existingGroup = groupMap.find (groupID); | auto existingGroup = groupMap.find (groupID); | ||||
| @@ -3669,6 +3799,22 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& resul | |||||
| } | } | ||||
| } | } | ||||
| void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& description, ARAFactoryCreationCallback callback) | |||||
| { | |||||
| if (! description.hasARAExtension) | |||||
| { | |||||
| jassertfalse; | |||||
| callback ({ {}, "The provided plugin does not support ARA features" }); | |||||
| } | |||||
| File file (description.fileOrIdentifier); | |||||
| VSTComSmartPtr<IPluginFactory> pluginFactory ( | |||||
| DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory()); | |||||
| const auto* pluginName = description.name.toRawUTF8(); | |||||
| callback ({ ARAFactoryWrapper { ::juce::getARAFactory (pluginFactory, pluginName) }, {} }); | |||||
| } | |||||
| void VST3PluginFormat::createPluginInstance (const PluginDescription& description, | void VST3PluginFormat::createPluginInstance (const PluginDescription& description, | ||||
| double, int, PluginCreationCallback callback) | double, int, PluginCreationCallback callback) | ||||
| { | { | ||||
| @@ -61,6 +61,7 @@ public: | |||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | ||||
| bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
| FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
| void createARAFactoryAsync (const PluginDescription&, ARAFactoryCreationCallback callback) override; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -199,10 +199,12 @@ private: | |||||
| #include "processors/juce_AudioProcessorGraph.cpp" | #include "processors/juce_AudioProcessorGraph.cpp" | ||||
| #include "processors/juce_GenericAudioProcessorEditor.cpp" | #include "processors/juce_GenericAudioProcessorEditor.cpp" | ||||
| #include "processors/juce_PluginDescription.cpp" | #include "processors/juce_PluginDescription.cpp" | ||||
| #include "format_types/juce_ARACommon.cpp" | |||||
| #include "format_types/juce_LADSPAPluginFormat.cpp" | #include "format_types/juce_LADSPAPluginFormat.cpp" | ||||
| #include "format_types/juce_VSTPluginFormat.cpp" | #include "format_types/juce_VSTPluginFormat.cpp" | ||||
| #include "format_types/juce_VST3PluginFormat.cpp" | #include "format_types/juce_VST3PluginFormat.cpp" | ||||
| #include "format_types/juce_AudioUnitPluginFormat.mm" | #include "format_types/juce_AudioUnitPluginFormat.mm" | ||||
| #include "format_types/juce_ARAHosting.cpp" | |||||
| #include "scanning/juce_KnownPluginList.cpp" | #include "scanning/juce_KnownPluginList.cpp" | ||||
| #include "scanning/juce_PluginDirectoryScanner.cpp" | #include "scanning/juce_PluginDirectoryScanner.cpp" | ||||
| #include "scanning/juce_PluginListComponent.cpp" | #include "scanning/juce_PluginListComponent.cpp" | ||||
| @@ -94,6 +94,17 @@ | |||||
| #define JUCE_PLUGINHOST_LV2 0 | #define JUCE_PLUGINHOST_LV2 0 | ||||
| #endif | #endif | ||||
| /** Config: JUCE_PLUGINHOST_ARA | |||||
| Enables the ARA plugin extension hosting classes. You will need to download the ARA SDK and specify the | |||||
| path to it either in the Projucer, using juce_set_ara_sdk_path() in your CMake project file. | |||||
| The directory can be obtained by recursively cloning https://github.com/Celemony/ARA_SDK and checking out | |||||
| the tag releases/2.1.0. | |||||
| */ | |||||
| #ifndef JUCE_PLUGINHOST_ARA | |||||
| #define JUCE_PLUGINHOST_ARA 0 | |||||
| #endif | |||||
| /** Config: JUCE_CUSTOM_VST3_SDK | /** Config: JUCE_CUSTOM_VST3_SDK | ||||
| If enabled, the embedded VST3 SDK in JUCE will not be added to the project and instead you should | If enabled, the embedded VST3 SDK in JUCE will not be added to the project and instead you should | ||||
| add the path to your custom VST3 SDK to the project's header search paths. Most users shouldn't | add the path to your custom VST3 SDK to the project's header search paths. Most users shouldn't | ||||
| @@ -115,6 +126,7 @@ | |||||
| #include "utilities/juce_VSTCallbackHandler.h" | #include "utilities/juce_VSTCallbackHandler.h" | ||||
| #include "utilities/juce_VST3ClientExtensions.h" | #include "utilities/juce_VST3ClientExtensions.h" | ||||
| #include "utilities/juce_NativeScaleFactorNotifier.h" | #include "utilities/juce_NativeScaleFactorNotifier.h" | ||||
| #include "format_types/juce_ARACommon.h" | |||||
| #include "utilities/juce_ExtensionsVisitor.h" | #include "utilities/juce_ExtensionsVisitor.h" | ||||
| #include "processors/juce_AudioProcessorParameter.h" | #include "processors/juce_AudioProcessorParameter.h" | ||||
| #include "processors/juce_HostedAudioProcessorParameter.h" | #include "processors/juce_HostedAudioProcessorParameter.h" | ||||
| @@ -136,6 +148,7 @@ | |||||
| #include "format_types/juce_VST3PluginFormat.h" | #include "format_types/juce_VST3PluginFormat.h" | ||||
| #include "format_types/juce_VSTMidiEventList.h" | #include "format_types/juce_VSTMidiEventList.h" | ||||
| #include "format_types/juce_VSTPluginFormat.h" | #include "format_types/juce_VSTPluginFormat.h" | ||||
| #include "format_types/juce_ARAHosting.h" | |||||
| #include "scanning/juce_PluginDirectoryScanner.h" | #include "scanning/juce_PluginDirectoryScanner.h" | ||||
| #include "scanning/juce_PluginListComponent.h" | #include "scanning/juce_PluginListComponent.h" | ||||
| #include "utilities/juce_AudioProcessorParameterWithID.h" | #include "utilities/juce_AudioProcessorParameterWithID.h" | ||||
| @@ -0,0 +1,33 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE 7 technical preview. | |||||
| Copyright (c) 2022 - Raw Material Software Limited | |||||
| You may use this code under the terms of the GPL v3 | |||||
| (see www.gnu.org/licenses). | |||||
| For the technical preview this file cannot be licensed commercially. | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include <juce_core/system/juce_CompilerWarnings.h> | |||||
| #include <juce_core/system/juce_TargetPlatform.h> | |||||
| /* Having WIN32_LEAN_AND_MEAN defined at the point of including ARADebug.c will produce warnings. | |||||
| To prevent such problems it's easiest to have it in its own translation unit. | |||||
| */ | |||||
| #if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS)) | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") | |||||
| #include <ARA_Library/Debug/ARADebug.c> | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| #endif | |||||
| @@ -72,6 +72,7 @@ std::unique_ptr<XmlElement> PluginDescription::createXml() const | |||||
| e->setAttribute ("numInputs", numInputChannels); | e->setAttribute ("numInputs", numInputChannels); | ||||
| e->setAttribute ("numOutputs", numOutputChannels); | e->setAttribute ("numOutputs", numOutputChannels); | ||||
| e->setAttribute ("isShell", hasSharedContainer); | e->setAttribute ("isShell", hasSharedContainer); | ||||
| e->setAttribute ("hasARAExtension", hasARAExtension); | |||||
| e->setAttribute ("uid", String::toHexString (deprecatedUid)); | e->setAttribute ("uid", String::toHexString (deprecatedUid)); | ||||
| @@ -95,6 +96,7 @@ bool PluginDescription::loadFromXml (const XmlElement& xml) | |||||
| numInputChannels = xml.getIntAttribute ("numInputs"); | numInputChannels = xml.getIntAttribute ("numInputs"); | ||||
| numOutputChannels = xml.getIntAttribute ("numOutputs"); | numOutputChannels = xml.getIntAttribute ("numOutputs"); | ||||
| hasSharedContainer = xml.getBoolAttribute ("isShell", false); | hasSharedContainer = xml.getBoolAttribute ("isShell", false); | ||||
| hasARAExtension = xml.getBoolAttribute ("hasARAExtension", false); | |||||
| deprecatedUid = xml.getStringAttribute ("uid").getHexValue32(); | deprecatedUid = xml.getStringAttribute ("uid").getHexValue32(); | ||||
| uniqueId = xml.getStringAttribute ("uniqueId", "0").getHexValue32(); | uniqueId = xml.getStringAttribute ("uniqueId", "0").getHexValue32(); | ||||
| @@ -122,6 +122,9 @@ public: | |||||
| /** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */ | /** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */ | ||||
| bool hasSharedContainer = false; | bool hasSharedContainer = false; | ||||
| /** True if the plug-in is ARA enabled and can supply a valid ARAFactoryWrapper. */ | |||||
| bool hasARAExtension = false; | |||||
| /** Returns true if the two descriptions refer to the same plug-in. | /** Returns true if the two descriptions refer to the same plug-in. | ||||
| This isn't quite as simple as them just having the same file (because of | This isn't quite as simple as them just having the same file (because of | ||||
| @@ -106,6 +106,13 @@ struct ExtensionsVisitor | |||||
| virtual AEffect* getAEffectPtr() const noexcept = 0; | virtual AEffect* getAEffectPtr() const noexcept = 0; | ||||
| }; | }; | ||||
| /** Can be used to retrieve information about a plugin that provides ARA extensions. */ | |||||
| struct ARAClient | |||||
| { | |||||
| virtual ~ARAClient() = default; | |||||
| virtual void createARAFactoryAsync (std::function<void (ARAFactoryWrapper)>) const = 0; | |||||
| }; | |||||
| virtual ~ExtensionsVisitor() = default; | virtual ~ExtensionsVisitor() = default; | ||||
| /** Will be called if there is no platform specific information available. */ | /** Will be called if there is no platform specific information available. */ | ||||
| @@ -119,6 +126,9 @@ struct ExtensionsVisitor | |||||
| /** Called with AU-specific information. */ | /** Called with AU-specific information. */ | ||||
| virtual void visitAudioUnitClient (const AudioUnitClient&) {} | virtual void visitAudioUnitClient (const AudioUnitClient&) {} | ||||
| /** Called with ARA-specific information. */ | |||||
| virtual void visitARAClient (const ARAClient&) {} | |||||
| }; | }; | ||||
| } // namespace juce | } // namespace juce | ||||