diff --git a/extras/Build/CMake/JUCEModuleSupport.cmake b/extras/Build/CMake/JUCEModuleSupport.cmake index d23e7df38d..dea5d61e8f 100644 --- a/extras/Build/CMake/JUCEModuleSupport.cmake +++ b/extras/Build/CMake/JUCEModuleSupport.cmake @@ -313,7 +313,6 @@ endfunction() function(_juce_add_plugin_wrapper_target format path out_path) _juce_module_sources("${path}" "${out_path}" out_var headers) - list(FILTER out_var EXCLUDE REGEX "/juce_audio_plugin_client_utils.cpp$") set(target_name juce_audio_plugin_client_${format}) _juce_add_interface_library("${target_name}" ${out_var}) @@ -452,16 +451,6 @@ function(juce_add_module module_path) _juce_add_plugin_wrapper_target(${kind} "${module_path}" "${base_path}") endforeach() - set(utils_source - "${base_path}/${module_name}/juce_audio_plugin_client_utils.cpp") - add_library(juce_audio_plugin_client_utils INTERFACE) - target_sources(juce_audio_plugin_client_utils INTERFACE "${utils_source}") - - if(JUCE_ARG_ALIAS_NAMESPACE) - add_library(${JUCE_ARG_ALIAS_NAMESPACE}::juce_audio_plugin_client_utils - ALIAS juce_audio_plugin_client_utils) - endif() - file(GLOB_RECURSE all_module_files CONFIGURE_DEPENDS LIST_DIRECTORIES FALSE RELATIVE "${module_parent_path}" diff --git a/extras/Build/CMake/JUCEUtils.cmake b/extras/Build/CMake/JUCEUtils.cmake index 41dbc4595b..4a435a5426 100644 --- a/extras/Build/CMake/JUCEUtils.cmake +++ b/extras/Build/CMake/JUCEUtils.cmake @@ -1211,7 +1211,7 @@ endfunction() function(_juce_configure_plugin_targets target) _juce_set_output_name(${target} $_SharedCode) - target_link_libraries(${target} PRIVATE juce::juce_audio_plugin_client_utils) + target_link_libraries(${target} PRIVATE juce::juce_audio_plugin_client) get_target_property(enabled_formats ${target} JUCE_FORMATS) diff --git a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp deleted file mode 100644 index 7e64340baf..0000000000 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ /dev/null @@ -1,2584 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include - -#if JucePlugin_Build_AAX && (JUCE_MAC || JUCE_WINDOWS) - -#include -#include -#include - -#include - -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4127 4512 4996) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor", - "-Wsign-conversion", - "-Wextra-semi", - "-Wshift-sign-overflow", - "-Wpragma-pack", - "-Wzero-as-null-pointer-constant", - "-Winconsistent-missing-destructor-override", - "-Wfour-char-constants", - "-Wtautological-overlap-compare", - "-Wdeprecated-declarations") - -#include - -static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p4p0_REVISION, "JUCE requires AAX SDK version 2.4.0 or higher"); - -#define INITACFIDS - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfour-char-constants") - -#undef check - -#include "juce_AAX_Modifier_Injector.h" - -using namespace juce; - -#ifndef JucePlugin_AAX_Chunk_Identifier - #define JucePlugin_AAX_Chunk_Identifier 'juce' -#endif - -const int32_t juceChunkType = JucePlugin_AAX_Chunk_Identifier; - -//============================================================================== -namespace AAXClasses -{ - static int32 getAAXParamHash (AAX_CParamID paramID) noexcept - { - int32 result = 0; - - while (*paramID != 0) - result = (31 * result) + (*paramID++); - - return result; - } - - static void check (AAX_Result result) - { - jassertquiet (result == AAX_SUCCESS); - } - - // maps a channel index of an AAX format to an index of a juce format - struct AAXChannelStreamOrder - { - AAX_EStemFormat aaxStemFormat; - AudioChannelSet::ChannelType speakerOrder[10]; - }; - - static AAX_EStemFormat stemFormatForAmbisonicOrder (int order) - { - switch (order) - { - case 1: return AAX_eStemFormat_Ambi_1_ACN; - case 2: return AAX_eStemFormat_Ambi_2_ACN; - case 3: return AAX_eStemFormat_Ambi_3_ACN; - default: break; - } - - return AAX_eStemFormat_INT32_MAX; - } - - static AAXChannelStreamOrder aaxChannelOrder[] = - { - { AAX_eStemFormat_Mono, { AudioChannelSet::centre, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_Stereo, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_LCR, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_LCRS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::centreSurround, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_Quad, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_5_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_5_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, - AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_6_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, - AudioChannelSet::rightSurround, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_6_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, - AudioChannelSet::rightSurround, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_0_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, - AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_0_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_1_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, - AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_1_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_0_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_1_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, - - { AAX_eStemFormat_None, { AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - }; - - static AAX_EStemFormat aaxFormats[] = - { - AAX_eStemFormat_Mono, - AAX_eStemFormat_Stereo, - AAX_eStemFormat_LCR, - AAX_eStemFormat_LCRS, - AAX_eStemFormat_Quad, - AAX_eStemFormat_5_0, - AAX_eStemFormat_5_1, - AAX_eStemFormat_6_0, - AAX_eStemFormat_6_1, - AAX_eStemFormat_7_0_SDDS, - AAX_eStemFormat_7_1_SDDS, - AAX_eStemFormat_7_0_DTS, - AAX_eStemFormat_7_1_DTS, - AAX_eStemFormat_7_0_2, - AAX_eStemFormat_7_1_2, - AAX_eStemFormat_Ambi_1_ACN, - AAX_eStemFormat_Ambi_2_ACN, - AAX_eStemFormat_Ambi_3_ACN - }; - - static AAX_EStemFormat getFormatForAudioChannelSet (const AudioChannelSet& set, bool ignoreLayout) noexcept - { - // if the plug-in ignores layout, it is ok to convert between formats only by their numchannnels - if (ignoreLayout) - { - auto numChannels = set.size(); - - switch (numChannels) - { - case 0: return AAX_eStemFormat_None; - case 1: return AAX_eStemFormat_Mono; - case 2: return AAX_eStemFormat_Stereo; - case 3: return AAX_eStemFormat_LCR; - case 4: return AAX_eStemFormat_Quad; - case 5: return AAX_eStemFormat_5_0; - case 6: return AAX_eStemFormat_5_1; - case 7: return AAX_eStemFormat_7_0_DTS; - case 8: return AAX_eStemFormat_7_1_DTS; - case 9: return AAX_eStemFormat_7_0_2; - case 10: return AAX_eStemFormat_7_1_2; - default: break; - } - - // check for ambisonics support - auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; - auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); - - if (static_cast (ambisonicOrder) == sqrtMinusOne) - return stemFormatForAmbisonicOrder (ambisonicOrder); - - return AAX_eStemFormat_INT32_MAX; - } - - if (set == AudioChannelSet::disabled()) return AAX_eStemFormat_None; - if (set == AudioChannelSet::mono()) return AAX_eStemFormat_Mono; - if (set == AudioChannelSet::stereo()) return AAX_eStemFormat_Stereo; - if (set == AudioChannelSet::createLCR()) return AAX_eStemFormat_LCR; - if (set == AudioChannelSet::createLCRS()) return AAX_eStemFormat_LCRS; - if (set == AudioChannelSet::quadraphonic()) return AAX_eStemFormat_Quad; - if (set == AudioChannelSet::create5point0()) return AAX_eStemFormat_5_0; - if (set == AudioChannelSet::create5point1()) return AAX_eStemFormat_5_1; - if (set == AudioChannelSet::create6point0()) return AAX_eStemFormat_6_0; - if (set == AudioChannelSet::create6point1()) return AAX_eStemFormat_6_1; - if (set == AudioChannelSet::create7point0()) return AAX_eStemFormat_7_0_DTS; - if (set == AudioChannelSet::create7point1()) return AAX_eStemFormat_7_1_DTS; - if (set == AudioChannelSet::create7point0SDDS()) return AAX_eStemFormat_7_0_SDDS; - if (set == AudioChannelSet::create7point1SDDS()) return AAX_eStemFormat_7_1_SDDS; - if (set == AudioChannelSet::create7point0point2()) return AAX_eStemFormat_7_0_2; - if (set == AudioChannelSet::create7point1point2()) return AAX_eStemFormat_7_1_2; - - auto order = set.getAmbisonicOrder(); - if (order >= 0) - return stemFormatForAmbisonicOrder (order); - - return AAX_eStemFormat_INT32_MAX; - } - - static inline AudioChannelSet channelSetFromStemFormat (AAX_EStemFormat format, bool ignoreLayout) noexcept - { - if (! ignoreLayout) - { - switch (format) - { - case AAX_eStemFormat_None: return AudioChannelSet::disabled(); - case AAX_eStemFormat_Mono: return AudioChannelSet::mono(); - case AAX_eStemFormat_Stereo: return AudioChannelSet::stereo(); - case AAX_eStemFormat_LCR: return AudioChannelSet::createLCR(); - case AAX_eStemFormat_LCRS: return AudioChannelSet::createLCRS(); - case AAX_eStemFormat_Quad: return AudioChannelSet::quadraphonic(); - case AAX_eStemFormat_5_0: return AudioChannelSet::create5point0(); - case AAX_eStemFormat_5_1: return AudioChannelSet::create5point1(); - case AAX_eStemFormat_6_0: return AudioChannelSet::create6point0(); - case AAX_eStemFormat_6_1: return AudioChannelSet::create6point1(); - case AAX_eStemFormat_7_0_SDDS: return AudioChannelSet::create7point0SDDS(); - case AAX_eStemFormat_7_0_DTS: return AudioChannelSet::create7point0(); - case AAX_eStemFormat_7_1_SDDS: return AudioChannelSet::create7point1SDDS(); - case AAX_eStemFormat_7_1_DTS: return AudioChannelSet::create7point1(); - case AAX_eStemFormat_7_0_2: return AudioChannelSet::create7point0point2(); - case AAX_eStemFormat_7_1_2: return AudioChannelSet::create7point1point2(); - case AAX_eStemFormat_Ambi_1_ACN: return AudioChannelSet::ambisonic (1); - case AAX_eStemFormat_Ambi_2_ACN: return AudioChannelSet::ambisonic (2); - case AAX_eStemFormat_Ambi_3_ACN: return AudioChannelSet::ambisonic (3); - case AAX_eStemFormat_Reserved_1: - case AAX_eStemFormat_Reserved_2: - case AAX_eStemFormat_Reserved_3: - case AAX_eStemFormatNum: - case AAX_eStemFormat_Any: - case AAX_eStemFormat_INT32_MAX: - default: return AudioChannelSet::disabled(); - } - } - - return AudioChannelSet::discreteChannels (jmax (0, static_cast (AAX_STEM_FORMAT_CHANNEL_COUNT (format)))); - } - - static AAX_EMeterType getMeterTypeForCategory (AudioProcessorParameter::Category category) - { - switch (category) - { - case AudioProcessorParameter::inputMeter: return AAX_eMeterType_Input; - case AudioProcessorParameter::outputMeter: return AAX_eMeterType_Output; - case AudioProcessorParameter::compressorLimiterGainReductionMeter: return AAX_eMeterType_CLGain; - case AudioProcessorParameter::expanderGateGainReductionMeter: return AAX_eMeterType_EGGain; - case AudioProcessorParameter::analysisMeter: return AAX_eMeterType_Analysis; - case AudioProcessorParameter::genericParameter: - case AudioProcessorParameter::inputGain: - case AudioProcessorParameter::outputGain: - case AudioProcessorParameter::otherMeter: - default: return AAX_eMeterType_Other; - } - } - - static Colour getColourFromHighlightEnum (AAX_EHighlightColor colour) noexcept - { - switch (colour) - { - case AAX_eHighlightColor_Red: return Colours::red; - case AAX_eHighlightColor_Blue: return Colours::blue; - case AAX_eHighlightColor_Green: return Colours::green; - case AAX_eHighlightColor_Yellow: return Colours::yellow; - case AAX_eHighlightColor_Num: - default: jassertfalse; break; - } - - return Colours::black; - } - - static int juceChannelIndexToAax (int juceIndex, const AudioChannelSet& channelSet) - { - auto isAmbisonic = (channelSet.getAmbisonicOrder() >= 0); - auto currentLayout = getFormatForAudioChannelSet (channelSet, false); - int layoutIndex; - - if (isAmbisonic && currentLayout != AAX_eStemFormat_INT32_MAX) - return juceIndex; - - for (layoutIndex = 0; aaxChannelOrder[layoutIndex].aaxStemFormat != currentLayout; ++layoutIndex) - if (aaxChannelOrder[layoutIndex].aaxStemFormat == 0) return juceIndex; - - auto& channelOrder = aaxChannelOrder[layoutIndex]; - auto channelType = channelSet.getTypeOfChannel (static_cast (juceIndex)); - auto numSpeakers = numElementsInArray (channelOrder.speakerOrder); - - for (int i = 0; i < numSpeakers && channelOrder.speakerOrder[i] != 0; ++i) - if (channelOrder.speakerOrder[i] == channelType) - return i; - - return juceIndex; - } - - //============================================================================== - class JuceAAX_Processor; - - struct PluginInstanceInfo - { - PluginInstanceInfo (JuceAAX_Processor& p) : parameters (p) {} - - JuceAAX_Processor& parameters; - - JUCE_DECLARE_NON_COPYABLE (PluginInstanceInfo) - }; - - //============================================================================== - struct JUCEAlgorithmContext - { - float** inputChannels; - float** outputChannels; - int32_t* bufferSize; - int32_t* bypass; - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - AAX_IMIDINode* midiNodeIn; - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - AAX_IMIDINode* midiNodeOut; - #endif - - PluginInstanceInfo* pluginInstance; - int32_t* isPrepared; - float* const* meterTapBuffers; - int32_t* sideChainBuffers; - }; - - struct JUCEAlgorithmIDs - { - enum - { - inputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, inputChannels), - outputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, outputChannels), - bufferSize = AAX_FIELD_INDEX (JUCEAlgorithmContext, bufferSize), - bypass = AAX_FIELD_INDEX (JUCEAlgorithmContext, bypass), - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - midiNodeIn = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeIn), - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - midiNodeOut = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeOut), - #endif - - pluginInstance = AAX_FIELD_INDEX (JUCEAlgorithmContext, pluginInstance), - preparedFlag = AAX_FIELD_INDEX (JUCEAlgorithmContext, isPrepared), - - meterTapBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, meterTapBuffers), - - sideChainBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, sideChainBuffers) - }; - }; - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeIn; } - #else - static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext&) noexcept { return nullptr; } - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - AAX_IMIDINode* midiNodeOut; - static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeOut; } - #else - static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext&) noexcept { return nullptr; } - #endif - - //============================================================================== - class JuceAAX_Processor; - - class JuceAAX_GUI : public AAX_CEffectGUI, - public ModifierKeyProvider - { - public: - JuceAAX_GUI() = default; - ~JuceAAX_GUI() override { DeleteViewContainer(); } - - static AAX_IEffectGUI* AAX_CALLBACK Create() { return new JuceAAX_GUI(); } - - void CreateViewContents() override; - - void CreateViewContainer() override - { - CreateViewContents(); - - if (void* nativeViewToAttachTo = GetViewContainerPtr()) - { - #if JUCE_MAC - if (GetViewContainerType() == AAX_eViewContainer_Type_NSView) - #else - if (GetViewContainerType() == AAX_eViewContainer_Type_HWND) - #endif - { - component->setVisible (true); - component->addToDesktop (detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()), nativeViewToAttachTo); - - if (ModifierKeyReceiver* modReceiver = dynamic_cast (component->getPeer())) - modReceiver->setModifierKeyProvider (this); - } - } - } - - void DeleteViewContainer() override - { - if (component != nullptr) - { - JUCE_AUTORELEASEPOOL - { - if (auto* modReceiver = dynamic_cast (component->getPeer())) - modReceiver->removeModifierKeyProvider(); - - component->removeFromDesktop(); - component = nullptr; - } - } - } - - AAX_Result GetViewSize (AAX_Point* viewSize) const override - { - if (component != nullptr) - { - *viewSize = convertToHostBounds ({ (float) component->getHeight(), - (float) component->getWidth() }); - - return AAX_SUCCESS; - } - - return AAX_ERROR_NULL_OBJECT; - } - - AAX_Result ParameterUpdated (AAX_CParamID) override - { - return AAX_SUCCESS; - } - - AAX_Result SetControlHighlightInfo (AAX_CParamID paramID, AAX_CBoolean isHighlighted, AAX_EHighlightColor colour) override - { - if (component != nullptr && component->pluginEditor != nullptr) - { - auto index = getParamIndexFromID (paramID); - - if (index >= 0) - { - AudioProcessorEditor::ParameterControlHighlightInfo info; - info.parameterIndex = index; - info.isHighlighted = (isHighlighted != 0); - info.suggestedColour = getColourFromHighlightEnum (colour); - - component->pluginEditor->setControlHighlight (info); - } - - return AAX_SUCCESS; - } - - return AAX_ERROR_NULL_OBJECT; - } - - int getWin32Modifiers() const override - { - int modifierFlags = 0; - - if (auto* viewContainer = GetViewContainer()) - { - uint32 aaxViewMods = 0; - const_cast (viewContainer)->GetModifiers (&aaxViewMods); - - if ((aaxViewMods & AAX_eModifiers_Shift) != 0) modifierFlags |= ModifierKeys::shiftModifier; - if ((aaxViewMods & AAX_eModifiers_Alt ) != 0) modifierFlags |= ModifierKeys::altModifier; - } - - return modifierFlags; - } - - private: - //============================================================================== - int getParamIndexFromID (AAX_CParamID paramID) const noexcept; - AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept; - - //============================================================================== - static AAX_Point convertToHostBounds (AAX_Point pluginSize) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return pluginSize; - - return { pluginSize.vert * desktopScale, - pluginSize.horz * desktopScale }; - } - - //============================================================================== - struct ContentWrapperComponent : public Component - { - ContentWrapperComponent (JuceAAX_GUI& gui, AudioProcessor& plugin) - : owner (gui) - { - setOpaque (true); - setBroughtToFrontOnMouseClick (true); - - pluginEditor.reset (plugin.createEditorIfNeeded()); - addAndMakeVisible (pluginEditor.get()); - - if (pluginEditor != nullptr) - { - lastValidSize = pluginEditor->getLocalBounds(); - setBounds (lastValidSize); - pluginEditor->addMouseListener (this, true); - } - } - - ~ContentWrapperComponent() override - { - if (pluginEditor != nullptr) - { - PopupMenu::dismissAllActiveMenus(); - pluginEditor->removeMouseListener (this); - pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); - } - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::black); - } - - template - void callMouseMethod (const MouseEvent& e, MethodType method) - { - if (auto* vc = owner.GetViewContainer()) - { - auto parameterIndex = pluginEditor->getControlParameterIndex (*e.eventComponent); - - if (auto aaxParamID = owner.getAAXParamIDFromJuceIndex (parameterIndex)) - { - uint32_t mods = 0; - vc->GetModifiers (&mods); - - (vc->*method) (aaxParamID, mods); - } - } - } - - void mouseDown (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDown); } - void mouseUp (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseUp); } - void mouseDrag (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDrag); } - - void parentSizeChanged() override - { - resizeHostWindow(); - - if (pluginEditor != nullptr) - pluginEditor->repaint(); - } - - void childBoundsChanged (Component*) override - { - if (resizeHostWindow()) - { - setSize (pluginEditor->getWidth(), pluginEditor->getHeight()); - lastValidSize = getBounds(); - } - else - { - pluginEditor->setBoundsConstrained (pluginEditor->getBounds().withSize (lastValidSize.getWidth(), - lastValidSize.getHeight())); - } - } - - bool resizeHostWindow() - { - if (pluginEditor != nullptr) - { - auto newSize = convertToHostBounds ({ (float) pluginEditor->getHeight(), - (float) pluginEditor->getWidth() }); - - return owner.GetViewContainer()->SetViewSize (newSize) == AAX_SUCCESS; - } - - return false; - } - - std::unique_ptr pluginEditor; - JuceAAX_GUI& owner; - - #if JUCE_WINDOWS - detail::WindowsHooks hooks; - #endif - juce::Rectangle lastValidSize; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) - }; - - std::unique_ptr component; - ScopedJuceInitialiser_GUI libraryInitialiser; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAAX_GUI) - }; - - // Copied here, because not all versions of the AAX SDK define all of these values - enum JUCE_AAX_EFrameRate : std::underlying_type_t - { - JUCE_AAX_eFrameRate_Undeclared = 0, - JUCE_AAX_eFrameRate_24Frame = 1, - JUCE_AAX_eFrameRate_25Frame = 2, - JUCE_AAX_eFrameRate_2997NonDrop = 3, - JUCE_AAX_eFrameRate_2997DropFrame = 4, - JUCE_AAX_eFrameRate_30NonDrop = 5, - JUCE_AAX_eFrameRate_30DropFrame = 6, - JUCE_AAX_eFrameRate_23976 = 7, - JUCE_AAX_eFrameRate_47952 = 8, - JUCE_AAX_eFrameRate_48Frame = 9, - JUCE_AAX_eFrameRate_50Frame = 10, - JUCE_AAX_eFrameRate_5994NonDrop = 11, - JUCE_AAX_eFrameRate_5994DropFrame = 12, - JUCE_AAX_eFrameRate_60NonDrop = 13, - JUCE_AAX_eFrameRate_60DropFrame = 14, - JUCE_AAX_eFrameRate_100Frame = 15, - JUCE_AAX_eFrameRate_11988NonDrop = 16, - JUCE_AAX_eFrameRate_11988DropFrame = 17, - JUCE_AAX_eFrameRate_120NonDrop = 18, - JUCE_AAX_eFrameRate_120DropFrame = 19 - }; - - static void AAX_CALLBACK algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); - - static Array activeProcessors; - - //============================================================================== - class JuceAAX_Processor : public AAX_CEffectParameters, - public juce::AudioPlayHead, - public AudioProcessorListener, - private AsyncUpdater - { - public: - JuceAAX_Processor() - : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_AAX)) - { - inParameterChangedCallback = false; - - pluginInstance->setPlayHead (this); - pluginInstance->addListener (this); - - rebuildChannelMapArrays(); - - AAX_CEffectParameters::GetNumberOfChunks (&juceChunkIndex); - activeProcessors.add (this); - } - - ~JuceAAX_Processor() override - { - activeProcessors.removeAllInstancesOf (this); - } - - static AAX_CEffectParameters* AAX_CALLBACK Create() - { - if (PluginHostType::jucePlugInIsRunningInAudioSuiteFn == nullptr) - { - PluginHostType::jucePlugInIsRunningInAudioSuiteFn = [] (AudioProcessor& processor) - { - for (auto* p : activeProcessors) - if (&p->getPluginInstance() == &processor) - return p->isInAudioSuite(); - - return false; - }; - } - - return new JuceAAX_Processor(); - } - - AAX_Result Uninitialize() override - { - cancelPendingUpdate(); - juceParameters.clear(); - - if (isPrepared && pluginInstance != nullptr) - { - isPrepared = false; - processingSidechainChange = false; - - pluginInstance->releaseResources(); - } - - return AAX_CEffectParameters::Uninitialize(); - } - - AAX_Result EffectInit() override - { - cancelPendingUpdate(); - check (Controller()->GetSampleRate (&sampleRate)); - processingSidechainChange = false; - auto err = preparePlugin(); - - if (err != AAX_SUCCESS) - return err; - - addAudioProcessorParameters(); - - return AAX_SUCCESS; - } - - AAX_Result GetNumberOfChunks (int32_t* numChunks) const override - { - // The juceChunk is the last chunk. - *numChunks = juceChunkIndex + 1; - return AAX_SUCCESS; - } - - AAX_Result GetChunkIDFromIndex (int32_t index, AAX_CTypeID* chunkID) const override - { - if (index != juceChunkIndex) - return AAX_CEffectParameters::GetChunkIDFromIndex (index, chunkID); - - *chunkID = juceChunkType; - return AAX_SUCCESS; - } - - AAX_Result GetChunkSize (AAX_CTypeID chunkID, uint32_t* oSize) const override - { - if (chunkID != juceChunkType) - return AAX_CEffectParameters::GetChunkSize (chunkID, oSize); - - auto& chunkMemoryBlock = perThreadFilterData.get(); - - chunkMemoryBlock.data.reset(); - pluginInstance->getStateInformation (chunkMemoryBlock.data); - chunkMemoryBlock.isValid = true; - - *oSize = (uint32_t) chunkMemoryBlock.data.getSize(); - return AAX_SUCCESS; - } - - AAX_Result GetChunk (AAX_CTypeID chunkID, AAX_SPlugInChunk* oChunk) const override - { - if (chunkID != juceChunkType) - return AAX_CEffectParameters::GetChunk (chunkID, oChunk); - - auto& chunkMemoryBlock = perThreadFilterData.get(); - - if (! chunkMemoryBlock.isValid) - return 20700; // AAX_ERROR_PLUGIN_API_INVALID_THREAD - - oChunk->fSize = (int32_t) chunkMemoryBlock.data.getSize(); - chunkMemoryBlock.data.copyTo (oChunk->fData, 0, chunkMemoryBlock.data.getSize()); - chunkMemoryBlock.isValid = false; - - return AAX_SUCCESS; - } - - AAX_Result SetChunk (AAX_CTypeID chunkID, const AAX_SPlugInChunk* chunk) override - { - if (chunkID != juceChunkType) - return AAX_CEffectParameters::SetChunk (chunkID, chunk); - - pluginInstance->setStateInformation ((void*) chunk->fData, chunk->fSize); - - // Notify Pro Tools that the parameters were updated. - // Without it a bug happens in these circumstances: - // * A preset is saved with the RTAS version of the plugin (".tfx" preset format). - // * The preset is loaded in PT 10 using the AAX version. - // * The session is then saved, and closed. - // * The saved session is loaded, but acting as if the preset was never loaded. - // IMPORTANT! If the plugin doesn't manage its own bypass parameter, don't try - // to overwrite the bypass parameter value. - auto numParameters = juceParameters.getNumParameters(); - - for (int i = 0; i < numParameters; ++i) - if (auto* juceParam = juceParameters.getParamForIndex (i)) - if (juceParam != ownedBypassParameter.get()) - if (auto paramID = getAAXParamIDFromJuceIndex (i)) - SetParameterNormalizedValue (paramID, juceParam->getValue()); - - return AAX_SUCCESS; - } - - AAX_Result ResetFieldData (AAX_CFieldIndex fieldIndex, void* data, uint32_t dataSize) const override - { - switch (fieldIndex) - { - case JUCEAlgorithmIDs::pluginInstance: - { - auto numObjects = dataSize / sizeof (PluginInstanceInfo); - auto* objects = static_cast (data); - - jassert (numObjects == 1); // not sure how to handle more than one.. - - for (size_t i = 0; i < numObjects; ++i) - new (objects + i) PluginInstanceInfo (const_cast (*this)); - - break; - } - - case JUCEAlgorithmIDs::preparedFlag: - { - const_cast (this)->preparePlugin(); - - auto numObjects = dataSize / sizeof (uint32_t); - auto* objects = static_cast (data); - - for (size_t i = 0; i < numObjects; ++i) - objects[i] = 1; - - break; - } - - case JUCEAlgorithmIDs::meterTapBuffers: - { - // this is a dummy field only when there are no aaxMeters - jassert (aaxMeters.size() == 0); - - { - auto numObjects = dataSize / sizeof (float*); - auto* objects = static_cast (data); - - for (size_t i = 0; i < numObjects; ++i) - objects[i] = nullptr; - } - break; - } - } - - return AAX_SUCCESS; - } - - void setAudioProcessorParameter (AAX_CParamID paramID, double value) - { - if (auto* param = getParameterFromID (paramID)) - { - auto newValue = static_cast (value); - - if (newValue != param->getValue()) - { - param->setValue (newValue); - - inParameterChangedCallback = true; - param->sendValueChangedMessageToListeners (newValue); - } - } - } - - AAX_Result GetNumberOfChanges (int32_t* numChanges) const override - { - const auto result = AAX_CEffectParameters::GetNumberOfChanges (numChanges); - *numChanges += numSetDirtyCalls; - return result; - } - - AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override - { - auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); - setAudioProcessorParameter (paramID, value); - - return result; - } - - AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override - { - if (auto* param = getParameterFromID (paramID)) - { - if (! LegacyAudioParameter::isLegacy (param)) - { - *result = param->getValueForText (text.Get()); - return AAX_SUCCESS; - } - } - - return AAX_CEffectParameters::GetParameterValueFromString (paramID, result, text); - } - - AAX_Result GetParameterStringFromValue (AAX_CParamID paramID, double value, AAX_IString* result, int32_t maxLen) const override - { - if (auto* param = getParameterFromID (paramID)) - result->Set (param->getText ((float) value, maxLen).toRawUTF8()); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterNumberofSteps (AAX_CParamID paramID, int32_t* result) const - { - if (auto* param = getParameterFromID (paramID)) - *result = getSafeNumberOfParameterSteps (*param); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override - { - if (auto* param = getParameterFromID (paramID)) - *result = (double) param->getValue(); - else - *result = 0.0; - - return AAX_SUCCESS; - } - - AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override - { - if (auto* p = mParameterManager.GetParameterByID (paramID)) - p->SetValueWithFloat ((float) newValue); - - setAudioProcessorParameter (paramID, (float) newValue); - - return AAX_SUCCESS; - } - - AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override - { - if (auto* param = getParameterFromID (paramID)) - { - auto newValue = param->getValue() + (float) newDeltaValue; - - setAudioProcessorParameter (paramID, jlimit (0.0f, 1.0f, newValue)); - - if (auto* p = mParameterManager.GetParameterByID (paramID)) - p->SetValueWithFloat (newValue); - } - - return AAX_SUCCESS; - } - - AAX_Result GetParameterNameOfLength (AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override - { - if (auto* param = getParameterFromID (paramID)) - result->Set (param->getName (maxLen).toRawUTF8()); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override - { - if (auto* param = getParameterFromID (paramID)) - result->Set (param->getName (31).toRawUTF8()); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override - { - if (auto* param = getParameterFromID (paramID)) - *result = (double) param->getDefaultValue(); - else - *result = 0.0; - - jassert (*result >= 0 && *result <= 1.0f); - - return AAX_SUCCESS; - } - - AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } - - Optional getPosition() const override - { - PositionInfo info; - - const AAX_ITransport& transport = *Transport(); - - info.setBpm ([&] - { - double bpm = 0.0; - - return transport.GetCurrentTempo (&bpm) == AAX_SUCCESS ? makeOptional (bpm) : nullopt; - }()); - - const auto signature = [&] - { - int32_t num = 4, den = 4; - - return transport.GetCurrentMeter (&num, &den) == AAX_SUCCESS - ? makeOptional (TimeSignature { (int) num, (int) den }) - : nullopt; - }(); - - info.setTimeSignature (signature); - - info.setIsPlaying ([&] - { - bool isPlaying = false; - - return transport.IsTransportPlaying (&isPlaying) == AAX_SUCCESS && isPlaying; - }()); - - info.setIsRecording (recordingState.get().orFallback (false)); - - const auto optionalTimeInSamples = [&info, &transport] - { - int64_t timeInSamples = 0; - return ((! info.getIsPlaying() && transport.GetTimelineSelectionStartPosition (&timeInSamples) == AAX_SUCCESS) - || transport.GetCurrentNativeSampleLocation (&timeInSamples) == AAX_SUCCESS) - ? makeOptional (timeInSamples) - : nullopt; - }(); - - info.setTimeInSamples (optionalTimeInSamples); - info.setTimeInSeconds ((float) optionalTimeInSamples.orFallback (0) / sampleRate); - - const auto tickPosition = [&] - { - int64_t ticks = 0; - - return ((info.getIsPlaying() && transport.GetCustomTickPosition (&ticks, optionalTimeInSamples.orFallback (0))) == AAX_SUCCESS) - || transport.GetCurrentTickPosition (&ticks) == AAX_SUCCESS - ? makeOptional (ticks) - : nullopt; - }(); - - info.setPpqPosition (tickPosition.hasValue() ? makeOptional (static_cast (*tickPosition) / 960'000.0) : nullopt); - - bool isLooping = false; - int64_t loopStartTick = 0, loopEndTick = 0; - - if (transport.GetCurrentLoopPosition (&isLooping, &loopStartTick, &loopEndTick) == AAX_SUCCESS) - { - info.setIsLooping (isLooping); - info.setLoopPoints (LoopPoints { (double) loopStartTick / 960000.0, (double) loopEndTick / 960000.0 }); - } - - struct RateAndOffset - { - AAX_EFrameRate frameRate{}; - int64_t offset{}; - }; - - const auto timeCodeIfAvailable = [&]() -> std::optional - { - RateAndOffset result{}; - - if (transport.GetHDTimeCodeInfo (&result.frameRate, &result.offset) == AAX_SUCCESS) - return result; - - int32_t offset32{}; - - if (transport.GetTimeCodeInfo (&result.frameRate, &offset32) == AAX_SUCCESS) - { - result.offset = offset32; - return result; - } - - return {}; - }(); - - if (timeCodeIfAvailable.has_value()) - { - info.setFrameRate ([&]() -> Optional - { - switch ((JUCE_AAX_EFrameRate) timeCodeIfAvailable->frameRate) - { - case JUCE_AAX_eFrameRate_24Frame: return FrameRate().withBaseRate (24); - case JUCE_AAX_eFrameRate_23976: return FrameRate().withBaseRate (24).withPullDown(); - - case JUCE_AAX_eFrameRate_25Frame: return FrameRate().withBaseRate (25); - - case JUCE_AAX_eFrameRate_30NonDrop: return FrameRate().withBaseRate (30); - case JUCE_AAX_eFrameRate_30DropFrame: return FrameRate().withBaseRate (30).withDrop(); - case JUCE_AAX_eFrameRate_2997NonDrop: return FrameRate().withBaseRate (30).withPullDown(); - case JUCE_AAX_eFrameRate_2997DropFrame: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - - case JUCE_AAX_eFrameRate_48Frame: return FrameRate().withBaseRate (48); - case JUCE_AAX_eFrameRate_47952: return FrameRate().withBaseRate (48).withPullDown(); - - case JUCE_AAX_eFrameRate_50Frame: return FrameRate().withBaseRate (50); - - case JUCE_AAX_eFrameRate_60NonDrop: return FrameRate().withBaseRate (60); - case JUCE_AAX_eFrameRate_60DropFrame: return FrameRate().withBaseRate (60).withDrop(); - case JUCE_AAX_eFrameRate_5994NonDrop: return FrameRate().withBaseRate (60).withPullDown(); - case JUCE_AAX_eFrameRate_5994DropFrame: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - - case JUCE_AAX_eFrameRate_100Frame: return FrameRate().withBaseRate (100); - - case JUCE_AAX_eFrameRate_120NonDrop: return FrameRate().withBaseRate (120); - case JUCE_AAX_eFrameRate_120DropFrame: return FrameRate().withBaseRate (120).withDrop(); - case JUCE_AAX_eFrameRate_11988NonDrop: return FrameRate().withBaseRate (120).withPullDown(); - case JUCE_AAX_eFrameRate_11988DropFrame: return FrameRate().withBaseRate (120).withPullDown().withDrop(); - - case JUCE_AAX_eFrameRate_Undeclared: break; - } - - return {}; - }()); - } - - const auto offset = timeCodeIfAvailable.has_value() ? timeCodeIfAvailable->offset : int64_t{}; - const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; - info.setEditOriginTime (makeOptional (effectiveRate != 0.0 ? offset / effectiveRate : offset)); - - { - int32_t bars{}, beats{}; - int64_t displayTicks{}; - - if (optionalTimeInSamples.hasValue() - && transport.GetBarBeatPosition (&bars, &beats, &displayTicks, *optionalTimeInSamples) == AAX_SUCCESS) - { - info.setBarCount (bars); - - if (signature.hasValue()) - { - const auto ticksSinceBar = static_cast (((beats - 1) * 4 * 960'000) / signature->denominator) + displayTicks; - - if (tickPosition.hasValue() && ticksSinceBar <= tickPosition) - { - const auto barStartInTicks = static_cast (*tickPosition - ticksSinceBar); - info.setPpqPositionOfLastBarStart (barStartInTicks / 960'000.0); - } - } - } - } - - return info; - } - - void audioProcessorParameterChanged (AudioProcessor* /*processor*/, int parameterIndex, float newValue) override - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) - SetParameterNormalizedValue (paramID, (double) newValue); - } - - void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override - { - ++mNumPlugInChanges; - - if (details.parameterInfoChanged) - { - for (const auto* param : juceParameters) - if (auto* aaxParam = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (param->getParameterIndex()))) - syncParameterAttributes (aaxParam, param); - } - - if (details.latencyChanged) - check (Controller()->SetSignalLatency (processor->getLatencySamples())); - - if (details.nonParameterStateChanged) - ++numSetDirtyCalls; - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override - { - if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) - TouchParameter (paramID); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override - { - if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) - ReleaseParameter (paramID); - } - - AAX_Result NotificationReceived (AAX_CTypeID type, const void* data, uint32_t size) override - { - switch (type) - { - case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; - case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); break; - - case AAX_eNotificationEvent_ASProcessingState: - { - if (data != nullptr && size == sizeof (AAX_EProcessingState)) - { - const auto state = *static_cast (data); - const auto nonRealtime = state == AAX_eProcessingState_StartPass - || state == AAX_eProcessingState_BeginPassGroup; - pluginInstance->setNonRealtime (nonRealtime); - } - - break; - } - - case AAX_eNotificationEvent_TrackNameChanged: - if (data != nullptr) - { - AudioProcessor::TrackProperties props; - props.name = String::fromUTF8 (static_cast (data)->Get()); - - pluginInstance->updateTrackProperties (props); - } - break; - - case AAX_eNotificationEvent_SideChainBeingConnected: - case AAX_eNotificationEvent_SideChainBeingDisconnected: - { - processingSidechainChange = true; - sidechainDesired = (type == AAX_eNotificationEvent_SideChainBeingConnected); - updateSidechainState(); - break; - } - - case AAX_eNotificationEvent_TransportStateChanged: - if (data != nullptr) - { - const auto& info = *static_cast (data); - recordingState.set (info.mIsRecording); - } - break; - } - - return AAX_CEffectParameters::NotificationReceived (type, data, size); - } - - const float* getAudioBufferForInput (const float* const* inputs, int sidechain, int mainNumIns, int idx) const noexcept - { - jassert (idx < (mainNumIns + 1)); - - if (idx < mainNumIns) - return inputs[inputLayoutMap[idx]]; - - return (sidechain != -1 ? inputs[sidechain] : sideChainBuffer.data()); - } - - void process (const float* const* inputs, float* const* outputs, const int sideChainBufferIdx, - const int bufferSize, const bool bypass, - AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut, - float* const meterBuffers) - { - auto numIns = pluginInstance->getTotalNumInputChannels(); - auto numOuts = pluginInstance->getTotalNumOutputChannels(); - auto numMeters = aaxMeters.size(); - - const ScopedLock sl (pluginInstance->getCallbackLock()); - - bool isSuspended = [this, sideChainBufferIdx] - { - if (processingSidechainChange) - return true; - - bool processWantsSidechain = (sideChainBufferIdx != -1); - - if (hasSidechain && canDisableSidechain && (sidechainDesired != processWantsSidechain)) - { - sidechainDesired = processWantsSidechain; - processingSidechainChange = true; - triggerAsyncUpdate(); - return true; - } - - return pluginInstance->isSuspended(); - }(); - - if (isSuspended) - { - for (int i = 0; i < numOuts; ++i) - FloatVectorOperations::clear (outputs[i], bufferSize); - - if (meterBuffers != nullptr) - FloatVectorOperations::clear (meterBuffers, numMeters); - } - else - { - auto mainNumIns = pluginInstance->getMainBusNumInputChannels(); - auto sidechain = (pluginInstance->getChannelCountOfBus (true, 1) > 0 ? sideChainBufferIdx : -1); - auto numChans = jmax (numIns, numOuts); - - if (numChans == 0) - return; - - if (channelList.size() <= numChans) - channelList.insertMultiple (-1, nullptr, 1 + numChans - channelList.size()); - - float** channels = channelList.getRawDataPointer(); - - if (numOuts >= numIns) - { - for (int i = 0; i < numOuts; ++i) - channels[i] = outputs[outputLayoutMap[i]]; - - for (int i = 0; i < numIns; ++i) - memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); - - for (int i = numIns; i < numOuts; ++i) - zeromem (channels[i], (size_t) bufferSize * sizeof (float)); - - process (channels, numOuts, bufferSize, bypass, midiNodeIn, midiNodesOut); - } - else - { - for (int i = 0; i < numOuts; ++i) - channels[i] = outputs[outputLayoutMap[i]]; - - for (int i = 0; i < numOuts; ++i) - memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); - - for (int i = numOuts; i < numIns; ++i) - channels[i] = const_cast (getAudioBufferForInput (inputs, sidechain, mainNumIns, i)); - - process (channels, numIns, bufferSize, bypass, midiNodeIn, midiNodesOut); - } - - if (meterBuffers != nullptr) - for (int i = 0; i < numMeters; ++i) - meterBuffers[i] = aaxMeters[i]->getValue(); - } - } - - //============================================================================== - // In aax, the format of the aux and sidechain buses need to be fully determined - // by the format on the main buses. This function tried to provide such a mapping. - // Returns false if the in/out main layout is not supported - static bool fullBusesLayoutFromMainLayout (const AudioProcessor& p, - const AudioChannelSet& mainInput, const AudioChannelSet& mainOutput, - AudioProcessor::BusesLayout& fullLayout) - { - auto currentLayout = getDefaultLayout (p, true); - bool success = p.checkBusesLayoutSupported (currentLayout); - jassertquiet (success); - - auto numInputBuses = p.getBusCount (true); - auto numOutputBuses = p.getBusCount (false); - - if (auto* bus = p.getBus (true, 0)) - if (! bus->isLayoutSupported (mainInput, ¤tLayout)) - return false; - - if (auto* bus = p.getBus (false, 0)) - if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) - return false; - - // did this change the input again - if (numInputBuses > 0 && currentLayout.inputBuses.getReference (0) != mainInput) - return false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - - if (! AudioProcessor::containsLayout (currentLayout, configs)) - return false; - #endif - - bool foundValid = false; - { - auto onlyMains = currentLayout; - - for (int i = 1; i < numInputBuses; ++i) - onlyMains.inputBuses.getReference (i) = AudioChannelSet::disabled(); - - for (int i = 1; i < numOutputBuses; ++i) - onlyMains.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (p.checkBusesLayoutSupported (onlyMains)) - { - foundValid = true; - fullLayout = onlyMains; - } - } - - if (numInputBuses > 1) - { - // can the first bus be a sidechain or disabled, if not then we can't use this layout combination - if (auto* bus = p.getBus (true, 1)) - if (! bus->isLayoutSupported (AudioChannelSet::mono(), ¤tLayout) && ! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) - return foundValid; - - // can all the other inputs be disabled, if not then we can't use this layout combination - for (int i = 2; i < numInputBuses; ++i) - if (auto* bus = p.getBus (true, i)) - if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (true, 0)) - if (! bus->isLayoutSupported (mainInput, ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (false, 0)) - if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) - return foundValid; - - // recheck if the format is correct - if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) - || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) - return foundValid; - - auto& sidechainBus = currentLayout.inputBuses.getReference (1); - - if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) - return foundValid; - - for (int i = 2; i < numInputBuses; ++i) - if (! currentLayout.inputBuses.getReference (i).isDisabled()) - return foundValid; - } - - const bool hasSidechain = (numInputBuses > 1 && currentLayout.inputBuses.getReference (1) == AudioChannelSet::mono()); - - if (hasSidechain) - { - auto onlyMainsAndSidechain = currentLayout; - - for (int i = 1; i < numOutputBuses; ++i) - onlyMainsAndSidechain.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (p.checkBusesLayoutSupported (onlyMainsAndSidechain)) - { - foundValid = true; - fullLayout = onlyMainsAndSidechain; - } - } - - if (numOutputBuses > 1) - { - auto copy = currentLayout; - int maxAuxBuses = jmin (16, numOutputBuses); - - for (int i = 1; i < maxAuxBuses; ++i) - copy.outputBuses.getReference (i) = mainOutput; - - for (int i = maxAuxBuses; i < numOutputBuses; ++i) - copy.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (p.checkBusesLayoutSupported (copy)) - { - fullLayout = copy; - foundValid = true; - } - else - { - for (int i = 1; i < maxAuxBuses; ++i) - if (currentLayout.outputBuses.getReference (i).isDisabled()) - return foundValid; - - for (int i = maxAuxBuses; i < numOutputBuses; ++i) - if (auto* bus = p.getBus (false, i)) - if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (true, 0)) - if (! bus->isLayoutSupported (mainInput, ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (false, 0)) - if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) - return foundValid; - - if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) - || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) - return foundValid; - - if (numInputBuses > 1) - { - auto& sidechainBus = currentLayout.inputBuses.getReference (1); - - if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) - return foundValid; - } - - for (int i = maxAuxBuses; i < numOutputBuses; ++i) - if (! currentLayout.outputBuses.getReference (i).isDisabled()) - return foundValid; - - fullLayout = currentLayout; - foundValid = true; - } - } - - return foundValid; - } - - bool isInAudioSuite() - { - AAX_CBoolean res; - Controller()->GetIsAudioSuite (&res); - - return res > 0; - } - - private: - friend class JuceAAX_GUI; - friend void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); - - void process (float* const* channels, const int numChans, const int bufferSize, - const bool bypass, [[maybe_unused]] AAX_IMIDINode* midiNodeIn, [[maybe_unused]] AAX_IMIDINode* midiNodesOut) - { - AudioBuffer buffer (channels, numChans, bufferSize); - midiBuffer.clear(); - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - { - auto* midiStream = midiNodeIn->GetNodeBuffer(); - auto numMidiEvents = midiStream->mBufferSize; - - for (uint32_t i = 0; i < numMidiEvents; ++i) - { - auto& m = midiStream->mBuffer[i]; - jassert ((int) m.mTimestamp < bufferSize); - - midiBuffer.addEvent (m.mData, (int) m.mLength, - jlimit (0, (int) bufferSize - 1, (int) m.mTimestamp)); - } - } - #endif - - { - if (lastBufferSize != bufferSize) - { - lastBufferSize = bufferSize; - pluginInstance->setRateAndBufferSizeDetails (sampleRate, lastBufferSize); - - // we only call prepareToPlay here if the new buffer size is larger than - // the one used last time prepareToPlay was called. - // currently, this should never actually happen, because as of Pro Tools 12, - // the maximum possible value is 1024, and we call prepareToPlay with that - // value during initialisation. - if (bufferSize > maxBufferSize) - prepareProcessorWithSampleRateAndBufferSize (sampleRate, bufferSize); - } - - if (bypass && pluginInstance->getBypassParameter() == nullptr) - pluginInstance->processBlockBypassed (buffer, midiBuffer); - else - pluginInstance->processBlock (buffer, midiBuffer); - } - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - { - AAX_CMidiPacket packet; - packet.mIsImmediate = false; - - for (const auto metadata : midiBuffer) - { - jassert (isPositiveAndBelow (metadata.samplePosition, bufferSize)); - - if (metadata.numBytes <= 4) - { - packet.mTimestamp = (uint32_t) metadata.samplePosition; - packet.mLength = (uint32_t) metadata.numBytes; - memcpy (packet.mData, metadata.data, (size_t) metadata.numBytes); - - check (midiNodesOut->PostMIDIPacket (&packet)); - } - } - } - #endif - } - - bool isBypassPartOfRegularParemeters() const - { - auto& audioProcessor = getPluginInstance(); - - int n = juceParameters.getNumParameters(); - - if (auto* bypassParam = audioProcessor.getBypassParameter()) - for (int i = 0; i < n; ++i) - if (juceParameters.getParamForIndex (i) == bypassParam) - return true; - - return false; - } - - // Some older Pro Tools control surfaces (EUCON [PT version 12.4] and - // Avid S6 before version 2.1) cannot cope with a large number of - // parameter steps. - static int32_t getSafeNumberOfParameterSteps (const AudioProcessorParameter& param) - { - return jmin (param.getNumSteps(), 2048); - } - - void addAudioProcessorParameters() - { - auto& audioProcessor = getPluginInstance(); - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - const bool forceLegacyParamIDs = true; - #else - const bool forceLegacyParamIDs = false; - #endif - - auto bypassPartOfRegularParams = isBypassPartOfRegularParemeters(); - - juceParameters.update (audioProcessor, forceLegacyParamIDs); - - auto* bypassParameter = pluginInstance->getBypassParameter(); - - if (bypassParameter == nullptr) - { - ownedBypassParameter.reset (new AudioParameterBool (cDefaultMasterBypassID, "Master Bypass", false)); - bypassParameter = ownedBypassParameter.get(); - } - - if (! bypassPartOfRegularParams) - juceParameters.addNonOwning (bypassParameter); - - int parameterIndex = 0; - - for (auto* juceParam : juceParameters) - { - auto isBypassParameter = (juceParam == bypassParameter); - - auto category = juceParam->getCategory(); - auto paramID = isBypassParameter ? String (cDefaultMasterBypassID) - : juceParameters.getParamID (audioProcessor, parameterIndex); - - aaxParamIDs.add (paramID); - auto* aaxParamID = aaxParamIDs.getReference (parameterIndex++).toRawUTF8(); - - paramMap.set (AAXClasses::getAAXParamHash (aaxParamID), juceParam); - - // is this a meter? - if (((category & 0xffff0000) >> 16) == 2) - { - aaxMeters.add (juceParam); - continue; - } - - auto parameter = new AAX_CParameter (aaxParamID, - AAX_CString (juceParam->getName (31).toRawUTF8()), - juceParam->getDefaultValue(), - AAX_CLinearTaperDelegate(), - AAX_CNumberDisplayDelegate(), - juceParam->isAutomatable()); - - parameter->AddShortenedName (juceParam->getName (4).toRawUTF8()); - - auto parameterNumSteps = getSafeNumberOfParameterSteps (*juceParam); - parameter->SetNumberOfSteps ((uint32_t) parameterNumSteps); - - #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous - : AAX_eParameterType_Discrete); - #else - parameter->SetType (juceParam->isDiscrete() ? AAX_eParameterType_Discrete - : AAX_eParameterType_Continuous); - #endif - - parameter->SetOrientation (juceParam->isOrientationInverted() - ? (AAX_eParameterOrientation_RightMinLeftMax | AAX_eParameterOrientation_TopMinBottomMax - | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) - : (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax - | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax)); - - mParameterManager.AddParameter (parameter); - - if (isBypassParameter) - mPacketDispatcher.RegisterPacket (aaxParamID, JUCEAlgorithmIDs::bypass); - } - } - - bool getMainBusFormats (AudioChannelSet& inputSet, AudioChannelSet& outputSet) - { - auto& audioProcessor = getPluginInstance(); - - #if JucePlugin_IsMidiEffect - // MIDI effect plug-ins do not support any audio channels - jassert (audioProcessor.getTotalNumInputChannels() == 0 - && audioProcessor.getTotalNumOutputChannels() == 0); - - inputSet = outputSet = AudioChannelSet(); - return true; - #else - auto inputBuses = audioProcessor.getBusCount (true); - auto outputBuses = audioProcessor.getBusCount (false); - - AAX_EStemFormat inputStemFormat = AAX_eStemFormat_None; - check (Controller()->GetInputStemFormat (&inputStemFormat)); - - AAX_EStemFormat outputStemFormat = AAX_eStemFormat_None; - check (Controller()->GetOutputStemFormat (&outputStemFormat)); - - #if JucePlugin_IsSynth - if (inputBuses == 0) - inputStemFormat = AAX_eStemFormat_None; - #endif - - inputSet = (inputBuses > 0 ? channelSetFromStemFormat (inputStemFormat, false) : AudioChannelSet()); - outputSet = (outputBuses > 0 ? channelSetFromStemFormat (outputStemFormat, false) : AudioChannelSet()); - - if ((inputSet == AudioChannelSet::disabled() && inputStemFormat != AAX_eStemFormat_None) || (outputSet == AudioChannelSet::disabled() && outputStemFormat != AAX_eStemFormat_None) - || (inputSet != AudioChannelSet::disabled() && inputBuses == 0) || (outputSet != AudioChannelSet::disabled() && outputBuses == 0)) - return false; - - return true; - #endif - } - - AAX_Result preparePlugin() - { - auto& audioProcessor = getPluginInstance(); - auto oldLayout = audioProcessor.getBusesLayout(); - AudioChannelSet inputSet, outputSet; - - if (! getMainBusFormats (inputSet, outputSet)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - AudioProcessor::BusesLayout newLayout; - - if (! fullBusesLayoutFromMainLayout (audioProcessor, inputSet, outputSet, newLayout)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - hasSidechain = (newLayout.getNumChannels (true, 1) == 1); - - if (hasSidechain) - { - sidechainDesired = true; - - auto disabledSidechainLayout = newLayout; - disabledSidechainLayout.inputBuses.getReference (1) = AudioChannelSet::disabled(); - - canDisableSidechain = audioProcessor.checkBusesLayoutSupported (disabledSidechainLayout); - - if (canDisableSidechain && ! lastSideChainState) - { - sidechainDesired = false; - newLayout = disabledSidechainLayout; - } - } - - if (isInAudioSuite()) - { - // AudioSuite doesn't support multiple output buses - for (int i = 1; i < newLayout.outputBuses.size(); ++i) - newLayout.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (! audioProcessor.checkBusesLayoutSupported (newLayout)) - { - // your plug-in needs to support a single output bus if running in AudioSuite - jassertfalse; - - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - } - - const bool layoutChanged = (oldLayout != newLayout); - - if (layoutChanged) - { - if (! audioProcessor.setBusesLayout (newLayout)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - rebuildChannelMapArrays(); - } - - if (layoutChanged || (! isPrepared)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - prepareProcessorWithSampleRateAndBufferSize (sampleRate, lastBufferSize); - - midiBuffer.ensureSize (2048); - midiBuffer.clear(); - } - - check (Controller()->SetSignalLatency (audioProcessor.getLatencySamples())); - isPrepared = true; - - return AAX_SUCCESS; - } - - void rebuildChannelMapArrays() - { - auto& audioProcessor = getPluginInstance(); - - for (int dir = 0; dir < 2; ++dir) - { - bool isInput = (dir == 0); - auto& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; - layoutMap.clear(); - - auto numBuses = audioProcessor.getBusCount (isInput); - int chOffset = 0; - - for (int busIdx = 0; busIdx < numBuses; ++busIdx) - { - auto channelFormat = audioProcessor.getChannelLayoutOfBus (isInput, busIdx); - - if (channelFormat != AudioChannelSet::disabled()) - { - auto numChannels = channelFormat.size(); - - for (int ch = 0; ch < numChannels; ++ch) - layoutMap.add (juceChannelIndexToAax (ch, channelFormat) + chOffset); - - chOffset += numChannels; - } - } - } - } - - static void algorithmCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) - { - for (auto iter = instancesBegin; iter < instancesEnd; ++iter) - { - auto& i = **iter; - - int sideChainBufferIdx = i.pluginInstance->parameters.hasSidechain && i.sideChainBuffers != nullptr - ? static_cast (*i.sideChainBuffers) : -1; - - // sidechain index of zero is an invalid index - if (sideChainBufferIdx <= 0) - sideChainBufferIdx = -1; - - auto numMeters = i.pluginInstance->parameters.aaxMeters.size(); - float* const meterTapBuffers = (i.meterTapBuffers != nullptr && numMeters > 0 ? *i.meterTapBuffers : nullptr); - - i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, sideChainBufferIdx, - *(i.bufferSize), *(i.bypass) != 0, - getMidiNodeIn(i), getMidiNodeOut(i), - meterTapBuffers); - } - } - - void prepareProcessorWithSampleRateAndBufferSize (double sr, int bs) - { - maxBufferSize = jmax (maxBufferSize, bs); - - auto& audioProcessor = getPluginInstance(); - audioProcessor.setRateAndBufferSizeDetails (sr, maxBufferSize); - audioProcessor.prepareToPlay (sr, maxBufferSize); - sideChainBuffer.resize (static_cast (maxBufferSize)); - } - - //============================================================================== - void updateSidechainState() - { - if (! processingSidechainChange) - return; - - auto& audioProcessor = getPluginInstance(); - const auto sidechainActual = audioProcessor.getChannelCountOfBus (true, 1) > 0; - - if (hasSidechain && canDisableSidechain && sidechainDesired != sidechainActual) - { - lastSideChainState = sidechainDesired; - - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - if (auto* bus = audioProcessor.getBus (true, 1)) - bus->setCurrentLayout (lastSideChainState ? AudioChannelSet::mono() - : AudioChannelSet::disabled()); - - prepareProcessorWithSampleRateAndBufferSize (audioProcessor.getSampleRate(), maxBufferSize); - isPrepared = true; - } - - processingSidechainChange = false; - } - - void handleAsyncUpdate() override - { - updateSidechainState(); - } - - //============================================================================== - static AudioProcessor::CurveData::Type aaxCurveTypeToJUCE (AAX_CTypeID type) noexcept - { - switch (type) - { - case AAX_eCurveType_EQ: return AudioProcessor::CurveData::Type::EQ; - case AAX_eCurveType_Dynamics: return AudioProcessor::CurveData::Type::Dynamics; - case AAX_eCurveType_Reduction: return AudioProcessor::CurveData::Type::GainReduction; - default: break; - } - - return AudioProcessor::CurveData::Type::Unknown; - } - - uint32_t getAAXMeterIdForParamId (const String& paramID) const noexcept - { - int idx; - - for (idx = 0; idx < aaxMeters.size(); ++idx) - if (LegacyAudioParameter::getParamID (aaxMeters[idx], false) == paramID) - break; - - // you specified a parameter id in your curve but the parameter does not have the meter - // category - jassert (idx < aaxMeters.size()); - return 'Metr' + static_cast (idx); - } - - //============================================================================== - AAX_Result GetCurveData (AAX_CTypeID iCurveType, const float * iValues, uint32_t iNumValues, float * oValues ) const override - { - auto curveType = aaxCurveTypeToJUCE (iCurveType); - - if (curveType != AudioProcessor::CurveData::Type::Unknown) - { - auto& audioProcessor = getPluginInstance(); - auto curve = audioProcessor.getResponseCurve (curveType); - - if (curve.curve) - { - if (oValues != nullptr && iValues != nullptr) - { - for (uint32_t i = 0; i < iNumValues; ++i) - oValues[i] = curve.curve (iValues[i]); - } - - return AAX_SUCCESS; - } - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - AAX_Result GetCurveDataMeterIds (AAX_CTypeID iCurveType, uint32_t *oXMeterId, uint32_t *oYMeterId) const override - { - auto curveType = aaxCurveTypeToJUCE (iCurveType); - - if (curveType != AudioProcessor::CurveData::Type::Unknown) - { - auto& audioProcessor = getPluginInstance(); - auto curve = audioProcessor.getResponseCurve (curveType); - - if (curve.curve && curve.xMeterID.isNotEmpty() && curve.yMeterID.isNotEmpty()) - { - if (oXMeterId != nullptr) *oXMeterId = getAAXMeterIdForParamId (curve.xMeterID); - if (oYMeterId != nullptr) *oYMeterId = getAAXMeterIdForParamId (curve.yMeterID); - - return AAX_SUCCESS; - } - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - AAX_Result GetCurveDataDisplayRange (AAX_CTypeID iCurveType, float *oXMin, float *oXMax, float *oYMin, float *oYMax) const override - { - auto curveType = aaxCurveTypeToJUCE (iCurveType); - - if (curveType != AudioProcessor::CurveData::Type::Unknown) - { - auto& audioProcessor = getPluginInstance(); - auto curve = audioProcessor.getResponseCurve (curveType); - - if (curve.curve) - { - if (oXMin != nullptr) *oXMin = curve.xRange.getStart(); - if (oXMax != nullptr) *oXMax = curve.xRange.getEnd(); - if (oYMin != nullptr) *oYMin = curve.yRange.getStart(); - if (oYMax != nullptr) *oYMax = curve.yRange.getEnd(); - - return AAX_SUCCESS; - } - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - //============================================================================== - inline int getParamIndexFromID (AAX_CParamID paramID) const noexcept - { - if (auto* param = getParameterFromID (paramID)) - return LegacyAudioParameter::getParamIndex (getPluginInstance(), param); - - return -1; - } - - inline AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept - { - if (isPositiveAndBelow (index, aaxParamIDs.size())) - return aaxParamIDs.getReference (index).toRawUTF8(); - - return nullptr; - } - - AudioProcessorParameter* getParameterFromID (AAX_CParamID paramID) const noexcept - { - return paramMap [AAXClasses::getAAXParamHash (paramID)]; - } - - //============================================================================== - static AudioProcessor::BusesLayout getDefaultLayout (const AudioProcessor& p, bool enableAll) - { - AudioProcessor::BusesLayout defaultLayout; - - for (int dir = 0; dir < 2; ++dir) - { - bool isInput = (dir == 0); - auto numBuses = p.getBusCount (isInput); - auto& layouts = (isInput ? defaultLayout.inputBuses : defaultLayout.outputBuses); - - for (int i = 0; i < numBuses; ++i) - if (auto* bus = p.getBus (isInput, i)) - layouts.add (enableAll || bus->isEnabledByDefault() ? bus->getDefaultLayout() : AudioChannelSet()); - } - - return defaultLayout; - } - - static AudioProcessor::BusesLayout getDefaultLayout (AudioProcessor& p) - { - auto defaultLayout = getDefaultLayout (p, true); - - if (! p.checkBusesLayoutSupported (defaultLayout)) - defaultLayout = getDefaultLayout (p, false); - - // Your processor must support the default layout - jassert (p.checkBusesLayoutSupported (defaultLayout)); - return defaultLayout; - } - - void syncParameterAttributes (AAX_IParameter* aaxParam, const AudioProcessorParameter* juceParam) - { - if (juceParam == nullptr) - return; - - { - auto newName = juceParam->getName (31); - - if (aaxParam->Name() != newName.toRawUTF8()) - aaxParam->SetName (AAX_CString (newName.toRawUTF8())); - } - - { - auto newType = juceParam->isDiscrete() ? AAX_eParameterType_Discrete : AAX_eParameterType_Continuous; - - if (aaxParam->GetType() != newType) - aaxParam->SetType (newType); - } - - { - auto newNumSteps = static_cast (juceParam->getNumSteps()); - - if (aaxParam->GetNumberOfSteps() != newNumSteps) - aaxParam->SetNumberOfSteps (newNumSteps); - } - - { - auto defaultValue = juceParam->getDefaultValue(); - - if (! approximatelyEqual (static_cast (aaxParam->GetNormalizedDefaultValue()), defaultValue)) - aaxParam->SetNormalizedDefaultValue (defaultValue); - } - } - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - std::unique_ptr pluginInstance; - - static constexpr auto maxSamplesPerBlock = 1 << AAX_eAudioBufferLength_Max; - - bool isPrepared = false; - MidiBuffer midiBuffer; - Array channelList; - int32_t juceChunkIndex = 0, numSetDirtyCalls = 0; - AAX_CSampleRate sampleRate = 0; - int lastBufferSize = maxSamplesPerBlock, maxBufferSize = maxSamplesPerBlock; - bool hasSidechain = false, canDisableSidechain = false, lastSideChainState = false; - - /* Pro Tools 2021 sends TransportStateChanged on the main thread, but we read - the recording state on the audio thread. - I'm not sure whether Pro Tools ensures that these calls are mutually - exclusive, so to ensure there are no data races, we store the recording - state in an atomic int and convert it to/from an Optional as necessary. - */ - class RecordingState - { - public: - /* This uses Optional rather than std::optional for consistency with get() */ - void set (const Optional newState) - { - state.store (newState.hasValue() ? (flagValid | (*newState ? flagActive : 0)) - : 0, - std::memory_order_relaxed); - } - - /* PositionInfo::setIsRecording takes an Optional, so we use that type rather - than std::optional to avoid having to convert. - */ - Optional get() const - { - const auto loaded = state.load (std::memory_order_relaxed); - return ((loaded & flagValid) != 0) ? makeOptional ((loaded & flagActive) != 0) - : nullopt; - } - - private: - enum RecordingFlags - { - flagValid = 1 << 0, - flagActive = 1 << 1 - }; - - std::atomic state { 0 }; - }; - - RecordingState recordingState; - - std::atomic processingSidechainChange, sidechainDesired; - - std::vector sideChainBuffer; - Array inputLayoutMap, outputLayoutMap; - - Array aaxParamIDs; - HashMap paramMap; - LegacyAudioParametersWrapper juceParameters; - std::unique_ptr ownedBypassParameter; - - Array aaxMeters; - - struct ChunkMemoryBlock - { - juce::MemoryBlock data; - bool isValid; - }; - - // temporary filter data is generated in GetChunkSize - // and the size of the data returned. To avoid generating - // it again in GetChunk, we need to store it somewhere. - // However, as GetChunkSize and GetChunk can be called - // on different threads, we store it in thread dependent storage - // in a hash map with the thread id as a key. - mutable ThreadLocalValue perThreadFilterData; - CriticalSection perThreadDataLock; - - ThreadLocalValue inParameterChangedCallback; - - JUCE_DECLARE_NON_COPYABLE (JuceAAX_Processor) - }; - - //============================================================================== - void JuceAAX_GUI::CreateViewContents() - { - if (component == nullptr) - { - if (auto* params = dynamic_cast (GetEffectParameters())) - component.reset (new ContentWrapperComponent (*this, params->getPluginInstance())); - else - jassertfalse; - } - } - - int JuceAAX_GUI::getParamIndexFromID (AAX_CParamID paramID) const noexcept - { - if (auto* params = dynamic_cast (GetEffectParameters())) - return params->getParamIndexFromID (paramID); - - return -1; - } - - AAX_CParamID JuceAAX_GUI::getAAXParamIDFromJuceIndex (int index) const noexcept - { - if (auto* params = dynamic_cast (GetEffectParameters())) - return params->getAAXParamIDFromJuceIndex (index); - - return nullptr; - } - - //============================================================================== - struct AAXFormatConfiguration - { - AAXFormatConfiguration() noexcept {} - - AAXFormatConfiguration (AAX_EStemFormat inFormat, AAX_EStemFormat outFormat) noexcept - : inputFormat (inFormat), outputFormat (outFormat) {} - - AAX_EStemFormat inputFormat = AAX_eStemFormat_None, - outputFormat = AAX_eStemFormat_None; - - bool operator== (const AAXFormatConfiguration other) const noexcept - { - return inputFormat == other.inputFormat && outputFormat == other.outputFormat; - } - - bool operator< (const AAXFormatConfiguration other) const noexcept - { - return inputFormat == other.inputFormat ? (outputFormat < other.outputFormat) - : (inputFormat < other.inputFormat); - } - }; - - //============================================================================== - static int addAAXMeters (AudioProcessor& p, AAX_IEffectDescriptor& descriptor) - { - LegacyAudioParametersWrapper params; - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - const bool forceLegacyParamIDs = true; - #else - const bool forceLegacyParamIDs = false; - #endif - - params.update (p, forceLegacyParamIDs); - - int meterIdx = 0; - - for (auto* param : params) - { - auto category = param->getCategory(); - - // is this a meter? - if (((category & 0xffff0000) >> 16) == 2) - { - if (auto* meterProperties = descriptor.NewPropertyMap()) - { - meterProperties->AddProperty (AAX_eProperty_Meter_Type, getMeterTypeForCategory (category)); - meterProperties->AddProperty (AAX_eProperty_Meter_Orientation, AAX_eMeterOrientation_TopRight); - - descriptor.AddMeterDescription ('Metr' + static_cast (meterIdx++), - param->getName (1024).toRawUTF8(), meterProperties); - } - } - } - - return meterIdx; - } - - static void createDescriptor (AAX_IComponentDescriptor& desc, - const AudioProcessor::BusesLayout& fullLayout, - AudioProcessor& processor, - Array& pluginIds, - const int numMeters) - { - auto aaxInputFormat = getFormatForAudioChannelSet (fullLayout.getMainInputChannelSet(), false); - auto aaxOutputFormat = getFormatForAudioChannelSet (fullLayout.getMainOutputChannelSet(), false); - - #if JucePlugin_IsSynth - if (aaxInputFormat == AAX_eStemFormat_None) - aaxInputFormat = aaxOutputFormat; - #endif - - #if JucePlugin_IsMidiEffect - aaxInputFormat = aaxOutputFormat = AAX_eStemFormat_Mono; - #endif - - check (desc.AddAudioIn (JUCEAlgorithmIDs::inputChannels)); - check (desc.AddAudioOut (JUCEAlgorithmIDs::outputChannels)); - - check (desc.AddAudioBufferLength (JUCEAlgorithmIDs::bufferSize)); - check (desc.AddDataInPort (JUCEAlgorithmIDs::bypass, sizeof (int32_t))); - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeIn, AAX_eMIDINodeType_LocalInput, - JucePlugin_Name, 0xffff)); - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeOut, AAX_eMIDINodeType_LocalOutput, - JucePlugin_Name " Out", 0xffff)); - #endif - - check (desc.AddPrivateData (JUCEAlgorithmIDs::pluginInstance, sizeof (PluginInstanceInfo))); - check (desc.AddPrivateData (JUCEAlgorithmIDs::preparedFlag, sizeof (int32_t))); - - if (numMeters > 0) - { - HeapBlock meterIDs (static_cast (numMeters)); - - for (int i = 0; i < numMeters; ++i) - meterIDs[i] = 'Metr' + static_cast (i); - - check (desc.AddMeters (JUCEAlgorithmIDs::meterTapBuffers, meterIDs.getData(), static_cast (numMeters))); - } - else - { - // AAX does not allow there to be any gaps in the fields of the algorithm context structure - // so just add a dummy one here if there aren't any meters - check (desc.AddPrivateData (JUCEAlgorithmIDs::meterTapBuffers, sizeof (uintptr_t))); - } - - // Create a property map - AAX_IPropertyMap* const properties = desc.NewPropertyMap(); - jassert (properties != nullptr); - - properties->AddProperty (AAX_eProperty_ManufacturerID, JucePlugin_AAXManufacturerCode); - properties->AddProperty (AAX_eProperty_ProductID, JucePlugin_AAXProductId); - - #if JucePlugin_AAXDisableBypass - properties->AddProperty (AAX_eProperty_CanBypass, false); - #else - properties->AddProperty (AAX_eProperty_CanBypass, true); - #endif - - properties->AddProperty (AAX_eProperty_InputStemFormat, static_cast (aaxInputFormat)); - properties->AddProperty (AAX_eProperty_OutputStemFormat, static_cast (aaxOutputFormat)); - - // This value needs to match the RTAS wrapper's Type ID, so that - // the host knows that the RTAS/AAX plugins are equivalent. - const int32 pluginID = processor.getAAXPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), - fullLayout.getMainOutputChannelSet(), - false); - - // The plugin id generated from your AudioProcessor's getAAXPluginIDForMainBusConfig callback - // it not unique. Please fix your implementation! - jassert (! pluginIds.contains (pluginID)); - pluginIds.add (pluginID); - - properties->AddProperty (AAX_eProperty_PlugInID_Native, pluginID); - - #if ! JucePlugin_AAXDisableAudioSuite - properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, - processor.getAAXPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), - fullLayout.getMainOutputChannelSet(), - true)); - #endif - - #if JucePlugin_AAXDisableMultiMono - properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, false); - #else - properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, true); - #endif - - #if JucePlugin_AAXDisableDynamicProcessing - properties->AddProperty (AAX_eProperty_Constraint_AlwaysProcess, true); - #endif - - #if JucePlugin_AAXDisableDefaultSettingsChunks - properties->AddProperty (AAX_eProperty_Constraint_DoNotApplyDefaultSettings, true); - #endif - - #if JucePlugin_AAXDisableSaveRestore - properties->AddProperty (AAX_eProperty_SupportsSaveRestore, false); - #endif - - properties->AddProperty (AAX_eProperty_ObservesTransportState, true); - - if (fullLayout.getChannelSet (true, 1) == AudioChannelSet::mono()) - { - check (desc.AddSideChainIn (JUCEAlgorithmIDs::sideChainBuffers)); - properties->AddProperty (AAX_eProperty_SupportsSideChainInput, true); - } - else - { - // AAX does not allow there to be any gaps in the fields of the algorithm context structure - // so just add a dummy one here if there aren't any side chains - check (desc.AddPrivateData (JUCEAlgorithmIDs::sideChainBuffers, sizeof (uintptr_t))); - } - - auto maxAuxBuses = jmax (0, jmin (15, fullLayout.outputBuses.size() - 1)); - - // add the output buses - // This is incredibly dumb: the output bus format must be well defined - // for every main bus in/out format pair. This means that there cannot - // be two configurations with different aux formats but - // identical main bus in/out formats. - for (int busIdx = 1; busIdx < maxAuxBuses + 1; ++busIdx) - { - auto set = fullLayout.getChannelSet (false, busIdx); - - if (set.isDisabled()) - break; - - auto auxFormat = getFormatForAudioChannelSet (set, true); - - if (auxFormat != AAX_eStemFormat_INT32_MAX && auxFormat != AAX_eStemFormat_None) - { - auto& name = processor.getBus (false, busIdx)->getName(); - check (desc.AddAuxOutputStem (0, static_cast (auxFormat), name.toRawUTF8())); - } - } - - check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); - } - - static inline bool hostSupportsStemFormat (AAX_EStemFormat stemFormat, const AAX_IFeatureInfo* featureInfo) - { - if (featureInfo != nullptr) - { - AAX_ESupportLevel supportLevel; - - if (featureInfo->SupportLevel (supportLevel) == AAX_SUCCESS && supportLevel == AAX_eSupportLevel_ByProperty) - { - std::unique_ptr props (featureInfo->AcquireProperties()); - - // Due to a bug in ProTools 12.8, ProTools thinks that AAX_eStemFormat_Ambi_1_ACN is not supported - // To workaround this bug, check if ProTools supports AAX_eStemFormat_Ambi_2_ACN, and, if yes, - // we can safely assume that it will also support AAX_eStemFormat_Ambi_1_ACN - if (stemFormat == AAX_eStemFormat_Ambi_1_ACN) - stemFormat = AAX_eStemFormat_Ambi_2_ACN; - - if (props != nullptr && props->GetProperty ((AAX_EProperty) stemFormat, (AAX_CPropertyValue*) &supportLevel) != 0) - return (supportLevel == AAX_eSupportLevel_Supported); - } - } - - return (AAX_STEM_FORMAT_INDEX (stemFormat) <= 12); - } - - static void getPlugInDescription (AAX_IEffectDescriptor& descriptor, [[maybe_unused]] const AAX_IFeatureInfo* featureInfo) - { - auto plugin = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); - auto numInputBuses = plugin->getBusCount (true); - auto numOutputBuses = plugin->getBusCount (false); - - auto pluginNames = plugin->getAlternateDisplayNames(); - - pluginNames.insert (0, JucePlugin_Name); - - pluginNames.removeDuplicates (false); - - for (auto name : pluginNames) - descriptor.AddName (name.toRawUTF8()); - - descriptor.AddCategory (JucePlugin_AAXCategory); - - const int numMeters = addAAXMeters (*plugin, descriptor); - - #ifdef JucePlugin_AAXPageTableFile - // optional page table setting - define this macro in your project if you want - // to set this value - see Avid documentation for details about its format. - descriptor.AddResourceInfo (AAX_eResourceType_PageTable, JucePlugin_AAXPageTableFile); - #endif - - check (descriptor.AddProcPtr ((void*) JuceAAX_GUI::Create, kAAX_ProcPtrID_Create_EffectGUI)); - check (descriptor.AddProcPtr ((void*) JuceAAX_Processor::Create, kAAX_ProcPtrID_Create_EffectParameters)); - - Array pluginIds; - #if JucePlugin_IsMidiEffect - // MIDI effect plug-ins do not support any audio channels - jassert (numInputBuses == 0 && numOutputBuses == 0); - - if (auto* desc = descriptor.NewComponentDescriptor()) - { - createDescriptor (*desc, plugin->getBusesLayout(), *plugin, pluginIds, numMeters); - check (descriptor.AddComponent (desc)); - } - #else - const int numIns = numInputBuses > 0 ? numElementsInArray (aaxFormats) : 0; - const int numOuts = numOutputBuses > 0 ? numElementsInArray (aaxFormats) : 0; - - for (int inIdx = 0; inIdx < jmax (numIns, 1); ++inIdx) - { - auto aaxInFormat = numIns > 0 ? aaxFormats[inIdx] : AAX_eStemFormat_None; - auto inLayout = channelSetFromStemFormat (aaxInFormat, false); - - for (int outIdx = 0; outIdx < jmax (numOuts, 1); ++outIdx) - { - auto aaxOutFormat = numOuts > 0 ? aaxFormats[outIdx] : AAX_eStemFormat_None; - auto outLayout = channelSetFromStemFormat (aaxOutFormat, false); - - if (hostSupportsStemFormat (aaxInFormat, featureInfo) - && hostSupportsStemFormat (aaxOutFormat, featureInfo)) - { - AudioProcessor::BusesLayout fullLayout; - - if (! JuceAAX_Processor::fullBusesLayoutFromMainLayout (*plugin, inLayout, outLayout, fullLayout)) - continue; - - if (auto* desc = descriptor.NewComponentDescriptor()) - { - createDescriptor (*desc, fullLayout, *plugin, pluginIds, numMeters); - check (descriptor.AddComponent (desc)); - } - } - } - } - - // You don't have any supported layouts - jassert (pluginIds.size() > 0); - #endif - } -} // namespace AAXClasses - -void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) -{ - AAXClasses::JuceAAX_Processor::algorithmCallback (instancesBegin, instancesEnd); -} - -//============================================================================== -AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection*); -AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection* collection) -{ - ScopedJuceInitialiser_GUI libraryInitialiser; - - std::unique_ptr stemFormatFeatureInfo; - - if (const auto* hostDescription = collection->DescriptionHost()) - stemFormatFeatureInfo.reset (hostDescription->AcquireFeatureProperties (AAXATTR_ClientFeature_StemFormat)); - - if (auto* descriptor = collection->NewDescriptor()) - { - AAXClasses::getPlugInDescription (*descriptor, stemFormatFeatureInfo.get()); - collection->AddEffect (JUCE_STRINGIFY (JucePlugin_AAXIdentifier), descriptor); - - collection->SetManufacturerName (JucePlugin_Manufacturer); - collection->AddPackageName (JucePlugin_Desc); - collection->AddPackageName (JucePlugin_Name); - collection->SetPackageVersion (JucePlugin_VersionCode); - - return AAX_SUCCESS; - } - - return AAX_ERROR_NULL_OBJECT; -} - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -//============================================================================== -#if _MSC_VER || JUCE_MINGW -extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } -#endif - -#endif diff --git a/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp b/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp deleted file mode 100644 index 732b4d6388..0000000000 --- a/modules/juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include - -#if JucePlugin_Enable_ARA - -#include -#include - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", "-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) - -#include -#include -#include -#include - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -#endif diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm deleted file mode 100644 index 40d85050ff..0000000000 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ /dev/null @@ -1,2620 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include -#include - -#if JucePlugin_Build_AU - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshorten-64-to-32", - "-Wunused-parameter", - "-Wdeprecated-declarations", - "-Wsign-conversion", - "-Wconversion", - "-Woverloaded-virtual", - "-Wextra-semi", - "-Wcast-align", - "-Wshadow", - "-Wswitch-enum", - "-Wzero-as-null-pointer-constant", - "-Wnullable-to-nonnull-conversion", - "-Wgnu-zero-variadic-macro-arguments", - "-Wformat-pedantic", - "-Wdeprecated-anon-enum-enum-conversion") - -#include - -#include -#include -#include -#include -#include -#include "AudioUnitSDK/MusicDeviceBase.h" - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 - -#include - -#include -#include -#include -#include - -#if JucePlugin_Enable_ARA - #include - #include - #if ARA_SUPPORT_VERSION_1 - #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current JUCE ARA implementation" - #endif -#endif - -#include - -//============================================================================== -using namespace juce; - -static Array activePlugins, activeUIs; - -static const AudioUnitPropertyID juceFilterObjectPropertyID = 0x1a45ffe9; - -template <> struct ContainerDeletePolicy { static void destroy (const __CFString* o) { if (o != nullptr) CFRelease (o); } }; - -// make sure the audio processor is initialized before the AUBase class -struct AudioProcessorHolder -{ - AudioProcessorHolder (bool initialiseGUI) - { - if (initialiseGUI) - initialiseJuce_GUI(); - - juceFilter = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnit); - - // audio units do not have a notion of enabled or un-enabled buses - juceFilter->enableAllBuses(); - } - - std::unique_ptr juceFilter; -}; - -//============================================================================== -class JuceAU : public AudioProcessorHolder, - public ausdk::MusicDeviceBase, - public AudioProcessorListener, - public AudioProcessorParameter::Listener -{ -public: - JuceAU (AudioUnit component) - : AudioProcessorHolder (activePlugins.size() + activeUIs.size() == 0), - MusicDeviceBase (component, - (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true), - (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)) - { - inParameterChangedCallback = false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - const int numConfigs = sizeof (configs) / sizeof (short[2]); - - jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - juceFilter->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); - - for (int i = 0; i < numConfigs; ++i) - { - AUChannelInfo info; - - info.inChannels = configs[i][0]; - info.outChannels = configs[i][1]; - - channelInfo.add (info); - } - #else - channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); - #endif - - AddPropertyListener (kAudioUnitProperty_ContextName, auPropertyListenerDispatcher, this); - - totalInChannels = juceFilter->getTotalNumInputChannels(); - totalOutChannels = juceFilter->getTotalNumOutputChannels(); - - juceFilter->addListener (this); - - addParameters(); - - activePlugins.add (this); - - zerostruct (auEvent); - auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance(); - auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global; - auEvent.mArgument.mParameter.mElement = 0; - - zerostruct (midiCallback); - - CreateElements(); - - if (syncAudioUnitWithProcessor() != noErr) - jassertfalse; - } - - ~JuceAU() override - { - if (bypassParam != nullptr) - bypassParam->removeListener (this); - - deleteActiveEditors(); - juceFilter = nullptr; - clearPresetsArray(); - - jassert (activePlugins.contains (this)); - activePlugins.removeFirstMatchingValue (this); - - if (activePlugins.size() + activeUIs.size() == 0) - shutdownJuce_GUI(); - } - - //============================================================================== - ComponentResult Initialize() override - { - ComponentResult err; - - if ((err = syncProcessorWithAudioUnit()) != noErr) - return err; - - if ((err = MusicDeviceBase::Initialize()) != noErr) - return err; - - mapper.alloc (*juceFilter); - pulledSucceeded.calloc (static_cast (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true))); - - prepareToPlay(); - - return noErr; - } - - void Cleanup() override - { - MusicDeviceBase::Cleanup(); - - pulledSucceeded.free(); - mapper.release(); - - if (juceFilter != nullptr) - juceFilter->releaseResources(); - - audioBuffer.release(); - midiEvents.clear(); - incomingEvents.clear(); - prepared = false; - } - - ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) override - { - if (! prepared) - prepareToPlay(); - - if (juceFilter != nullptr) - juceFilter->reset(); - - return MusicDeviceBase::Reset (inScope, inElement); - } - - //============================================================================== - void prepareToPlay() - { - if (juceFilter != nullptr) - { - juceFilter->setRateAndBufferSizeDetails (getSampleRate(), (int) GetMaxFramesPerSlice()); - - audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (juceFilter.get()), (int) GetMaxFramesPerSlice() + 32); - juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); - - midiEvents.ensureSize (2048); - midiEvents.clear(); - incomingEvents.ensureSize (2048); - incomingEvents.clear(); - - prepared = true; - } - } - - //============================================================================== - bool BusCountWritable ([[maybe_unused]] AudioUnitScope scope) override - { - #ifdef JucePlugin_PreferredChannelConfigurations - return false; - #else - bool isInput; - - if (scopeToDirection (scope, isInput) != noErr) - return false; - - #if JucePlugin_IsMidiEffect - return false; - #elif JucePlugin_IsSynth - if (isInput) return false; - #endif - - const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - return (juceFilter->canAddBus (isInput) || (busCount > 0 && juceFilter->canRemoveBus (isInput))); - #endif - } - - OSStatus SetBusCount (AudioUnitScope scope, UInt32 count) override - { - OSStatus err = noErr; - bool isInput; - - if ((err = scopeToDirection (scope, isInput)) != noErr) - return err; - - if (count != (UInt32) AudioUnitHelpers::getBusCount (*juceFilter, isInput)) - { - #ifdef JucePlugin_PreferredChannelConfigurations - return kAudioUnitErr_PropertyNotWritable; - #else - const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - - if ((! juceFilter->canAddBus (isInput)) && ((busCount == 0) || (! juceFilter->canRemoveBus (isInput)))) - return kAudioUnitErr_PropertyNotWritable; - - // we need to already create the underlying elements so that we can change their formats - err = MusicDeviceBase::SetBusCount (scope, count); - - if (err != noErr) - return err; - - // however we do need to update the format tag: we need to do the same thing in SetFormat, for example - const int requestedNumBus = static_cast (count); - { - (isInput ? currentInputLayout : currentOutputLayout).resize (requestedNumBus); - - int busNr; - - for (busNr = (busCount - 1); busNr != (requestedNumBus - 1); busNr += (requestedNumBus > busCount ? 1 : -1)) - { - if (requestedNumBus > busCount) - { - if (! juceFilter->addBus (isInput)) - break; - - err = syncAudioUnitWithChannelSet (isInput, busNr, - juceFilter->getBus (isInput, busNr + 1)->getDefaultLayout()); - if (err != noErr) - break; - } - else - { - if (! juceFilter->removeBus (isInput)) - break; - } - } - - err = (busNr == (requestedNumBus - 1) ? (OSStatus) noErr : (OSStatus) kAudioUnitErr_FormatNotSupported); - } - - // was there an error? - if (err != noErr) - { - // restore bus state - const int newBusCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - for (int i = newBusCount; i != busCount; i += (busCount > newBusCount ? 1 : -1)) - { - if (busCount > newBusCount) - juceFilter->addBus (isInput); - else - juceFilter->removeBus (isInput); - } - - (isInput ? currentInputLayout : currentOutputLayout).resize (busCount); - MusicDeviceBase::SetBusCount (scope, static_cast (busCount)); - - return kAudioUnitErr_FormatNotSupported; - } - - // update total channel count - totalInChannels = juceFilter->getTotalNumInputChannels(); - totalOutChannels = juceFilter->getTotalNumOutputChannels(); - - addSupportedLayoutTagsForDirection (isInput); - - if (err != noErr) - return err; - #endif - } - - return noErr; - } - - UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) override - { - if (outInfo != nullptr) - *outInfo = channelInfo.getRawDataPointer(); - - return (UInt32) channelInfo.size(); - } - - //============================================================================== - ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - UInt32& outDataSize, - bool& outWritable) override - { - if (inScope == kAudioUnitScope_Global) - { - switch (inID) - { - case juceFilterObjectPropertyID: - outWritable = false; - outDataSize = sizeof (void*) * 2; - return noErr; - - case kAudioUnitProperty_OfflineRender: - outWritable = true; - outDataSize = sizeof (UInt32); - return noErr; - - case kMusicDeviceProperty_InstrumentCount: - outDataSize = sizeof (UInt32); - outWritable = false; - return noErr; - - case kAudioUnitProperty_CocoaUI: - outDataSize = sizeof (AudioUnitCocoaViewInfo); - outWritable = true; - return noErr; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - case kAudioUnitProperty_AudioUnitMIDIProtocol: - outDataSize = sizeof (SInt32); - outWritable = false; - return noErr; - - case kAudioUnitProperty_HostMIDIProtocol: - outDataSize = sizeof (SInt32); - outWritable = true; - return noErr; - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - case kAudioUnitProperty_MIDIOutputCallbackInfo: - outDataSize = sizeof (CFArrayRef); - outWritable = false; - return noErr; - - case kAudioUnitProperty_MIDIOutputCallback: - outDataSize = sizeof (AUMIDIOutputCallbackStruct); - outWritable = true; - return noErr; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - case kAudioUnitProperty_MIDIOutputEventListCallback: - outDataSize = sizeof (AUMIDIEventListBlock); - outWritable = true; - return noErr; - #endif - #endif - - case kAudioUnitProperty_ParameterStringFromValue: - outDataSize = sizeof (AudioUnitParameterStringFromValue); - outWritable = false; - return noErr; - - case kAudioUnitProperty_ParameterValueFromString: - outDataSize = sizeof (AudioUnitParameterValueFromString); - outWritable = false; - return noErr; - - case kAudioUnitProperty_BypassEffect: - outDataSize = sizeof (UInt32); - outWritable = true; - return noErr; - - case kAudioUnitProperty_SupportsMPE: - outDataSize = sizeof (UInt32); - outWritable = false; - return noErr; - - #if JucePlugin_Enable_ARA - case ARA::kAudioUnitProperty_ARAFactory: - outWritable = false; - outDataSize = sizeof (ARA::ARAAudioUnitFactory); - return noErr; - case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: - outWritable = false; - outDataSize = sizeof (ARA::ARAAudioUnitPlugInExtensionBinding); - return noErr; - #endif - - default: break; - } - } - - return MusicDeviceBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); - } - - ComponentResult GetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - void* outData) override - { - if (inScope == kAudioUnitScope_Global) - { - switch (inID) - { - case kAudioUnitProperty_ParameterClumpName: - - if (auto* clumpNameInfo = (AudioUnitParameterNameInfo*) outData) - { - if (juceFilter != nullptr) - { - auto clumpIndex = clumpNameInfo->inID - 1; - const auto* group = parameterGroups[(int) clumpIndex]; - auto name = group->getName(); - - while (group->getParent() != &juceFilter->getParameterTree()) - { - group = group->getParent(); - name = group->getName() + group->getSeparator() + name; - } - - clumpNameInfo->outName = name.toCFString(); - return noErr; - } - } - - // Failed to find a group corresponding to the clump ID. - jassertfalse; - break; - - //============================================================================== - #if JucePlugin_Enable_ARA - case ARA::kAudioUnitProperty_ARAFactory: - { - auto auFactory = static_cast (outData); - if (auFactory->inOutMagicNumber != ARA::kARAAudioUnitMagic) - return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics - - auFactory->outFactory = createARAFactory(); - return noErr; - } - - case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: - { - auto binding = static_cast (outData); - if (binding->inOutMagicNumber != ARA::kARAAudioUnitMagic) - return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics - - AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (juceFilter.get()); - binding->outPlugInExtension = araAudioProcessorExtension->bindToARA (binding->inDocumentControllerRef, binding->knownRoles, binding->assignedRoles); - if (binding->outPlugInExtension == nullptr) - return kAudioUnitErr_CannotDoInCurrentContext; // bindToARA() returns null if binding is already established - - return noErr; - } - #endif - - case juceFilterObjectPropertyID: - ((void**) outData)[0] = (void*) static_cast (juceFilter.get()); - ((void**) outData)[1] = (void*) this; - return noErr; - - case kAudioUnitProperty_OfflineRender: - *(UInt32*) outData = (juceFilter != nullptr && juceFilter->isNonRealtime()) ? 1 : 0; - return noErr; - - case kMusicDeviceProperty_InstrumentCount: - *(UInt32*) outData = 1; - return noErr; - - case kAudioUnitProperty_BypassEffect: - if (bypassParam != nullptr) - *(UInt32*) outData = (bypassParam->getValue() != 0.0f ? 1 : 0); - else - *(UInt32*) outData = isBypassed ? 1 : 0; - return noErr; - - case kAudioUnitProperty_SupportsMPE: - *(UInt32*) outData = (juceFilter != nullptr && juceFilter->supportsMPE()) ? 1 : 0; - return noErr; - - case kAudioUnitProperty_CocoaUI: - { - JUCE_AUTORELEASEPOOL - { - static JuceUICreationClass cls; - - // (NB: this may be the host's bundle, not necessarily the component's) - NSBundle* bundle = [NSBundle bundleForClass: cls.cls]; - - AudioUnitCocoaViewInfo* info = static_cast (outData); - info->mCocoaAUViewClass[0] = (CFStringRef) [juceStringToNS (class_getName (cls.cls)) retain]; - info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [bundle bundlePath]] retain]; - } - - return noErr; - } - - break; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - case kAudioUnitProperty_AudioUnitMIDIProtocol: - { - // This will become configurable in the future - *static_cast (outData) = kMIDIProtocol_1_0; - return noErr; - } - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - case kAudioUnitProperty_MIDIOutputCallbackInfo: - { - CFStringRef strs[1]; - strs[0] = CFSTR ("MIDI Callback"); - - CFArrayRef callbackArray = CFArrayCreate (nullptr, (const void**) strs, 1, &kCFTypeArrayCallBacks); - *(CFArrayRef*) outData = callbackArray; - return noErr; - } - #endif - - case kAudioUnitProperty_ParameterValueFromString: - { - if (AudioUnitParameterValueFromString* pv = (AudioUnitParameterValueFromString*) outData) - { - if (juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (pv->inParamID)) - { - const String text (String::fromCFString (pv->inString)); - - if (LegacyAudioParameter::isLegacy (param)) - pv->outValue = text.getFloatValue(); - else - pv->outValue = param->getValueForText (text) * getMaximumParameterValue (param); - - - return noErr; - } - } - } - } - break; - - case kAudioUnitProperty_ParameterStringFromValue: - { - if (AudioUnitParameterStringFromValue* pv = (AudioUnitParameterStringFromValue*) outData) - { - if (juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (pv->inParamID)) - { - const float value = (float) *(pv->inValue); - String text; - - if (LegacyAudioParameter::isLegacy (param)) - text = String (value); - else - text = param->getText (value / getMaximumParameterValue (param), 0); - - pv->outString = text.toCFString(); - - return noErr; - } - } - } - } - break; - - default: - break; - } - } - - return MusicDeviceBase::GetProperty (inID, inScope, inElement, outData); - } - - ComponentResult SetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - const void* inData, - UInt32 inDataSize) override - { - if (inScope == kAudioUnitScope_Global) - { - switch (inID) - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - case kAudioUnitProperty_MIDIOutputCallback: - if (inDataSize < sizeof (AUMIDIOutputCallbackStruct)) - return kAudioUnitErr_InvalidPropertyValue; - - if (AUMIDIOutputCallbackStruct* callbackStruct = (AUMIDIOutputCallbackStruct*) inData) - midiCallback = *callbackStruct; - - return noErr; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - case kAudioUnitProperty_MIDIOutputEventListCallback: - { - if (inDataSize != sizeof (AUMIDIEventListBlock)) - return kAudioUnitErr_InvalidPropertyValue; - - midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast (inData)); - return noErr; - } - #endif - #endif - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - case kAudioUnitProperty_HostMIDIProtocol: - { - if (inDataSize != sizeof (SInt32)) - return kAudioUnitErr_InvalidPropertyValue; - - hostProtocol = *static_cast (inData); - return noErr; - } - #endif - - case kAudioUnitProperty_BypassEffect: - { - if (inDataSize < sizeof (UInt32)) - return kAudioUnitErr_InvalidPropertyValue; - - const bool newBypass = *((UInt32*) inData) != 0; - const bool currentlyBypassed = (bypassParam != nullptr ? (bypassParam->getValue() != 0.0f) : isBypassed); - - if (newBypass != currentlyBypassed) - { - if (bypassParam != nullptr) - bypassParam->setValueNotifyingHost (newBypass ? 1.0f : 0.0f); - else - isBypassed = newBypass; - - if (! currentlyBypassed && IsInitialized()) // turning bypass off and we're initialized - Reset (0, 0); - } - - return noErr; - } - - case kAudioUnitProperty_OfflineRender: - { - const auto shouldBeOffline = (*reinterpret_cast (inData) != 0); - - if (juceFilter != nullptr) - { - const auto isOffline = juceFilter->isNonRealtime(); - - if (isOffline != shouldBeOffline) - { - const ScopedLock sl (juceFilter->getCallbackLock()); - - juceFilter->setNonRealtime (shouldBeOffline); - - if (prepared) - juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); - } - } - - return noErr; - } - - case kAudioUnitProperty_AUHostIdentifier: - { - if (inDataSize < sizeof (AUHostVersionIdentifier)) - return kAudioUnitErr_InvalidPropertyValue; - - const auto* identifier = static_cast (inData); - PluginHostType::hostIdReportedByWrapper = String::fromCFString (identifier->hostName); - - return noErr; - } - - default: break; - } - } - - return MusicDeviceBase::SetProperty (inID, inScope, inElement, inData, inDataSize); - } - - //============================================================================== - ComponentResult SaveState (CFPropertyListRef* outData) override - { - ComponentResult err = MusicDeviceBase::SaveState (outData); - - if (err != noErr) - return err; - - jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); - - CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; - - if (juceFilter != nullptr) - { - juce::MemoryBlock state; - - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - juceFilter->getCurrentProgramStateInformation (state); - #else - juceFilter->getStateInformation (state); - #endif - - if (state.getSize() > 0) - { - CFUniquePtr ourState (CFDataCreate (kCFAllocatorDefault, (const UInt8*) state.getData(), (CFIndex) state.getSize())); - CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); - CFDictionarySetValue (dict, key.get(), ourState.get()); - } - } - - return noErr; - } - - ComponentResult RestoreState (CFPropertyListRef inData) override - { - const ScopedValueSetter scope { restoringState, true }; - - { - // Remove the data entry from the state to prevent the superclass loading the parameters - CFUniquePtr copyWithoutData (CFDictionaryCreateMutableCopy (nullptr, 0, (CFDictionaryRef) inData)); - CFDictionaryRemoveValue (copyWithoutData.get(), CFSTR (kAUPresetDataKey)); - ComponentResult err = MusicDeviceBase::RestoreState (copyWithoutData.get()); - - if (err != noErr) - return err; - } - - if (juceFilter != nullptr) - { - CFDictionaryRef dict = (CFDictionaryRef) inData; - CFDataRef data = nullptr; - - CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); - - bool valuePresent = CFDictionaryGetValueIfPresent (dict, key.get(), (const void**) &data); - - if (valuePresent) - { - if (data != nullptr) - { - const int numBytes = (int) CFDataGetLength (data); - const juce::uint8* const rawBytes = CFDataGetBytePtr (data); - - if (numBytes > 0) - { - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - juceFilter->setCurrentProgramStateInformation (rawBytes, numBytes); - #else - juceFilter->setStateInformation (rawBytes, numBytes); - #endif - } - } - } - } - - return noErr; - } - - //============================================================================== - bool busIgnoresLayout ([[maybe_unused]] bool isInput, [[maybe_unused]] int busNr) const - { - #ifdef JucePlugin_PreferredChannelConfigurations - return true; - #else - if (const AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNr)) - { - AudioChannelSet discreteRangeSet; - const int n = bus->getDefaultLayout().size(); - for (int i = 0; i < n; ++i) - discreteRangeSet.addChannel ((AudioChannelSet::ChannelType) (256 + i)); - - // if the audioprocessor supports this it cannot - // really be interested in the bus layouts - return bus->isLayoutSupported (discreteRangeSet); - } - - return true; - #endif - } - - UInt32 GetAudioChannelLayout (AudioUnitScope scope, - AudioUnitElement element, - AudioChannelLayout* outLayoutPtr, - bool& outWritable) override - { - outWritable = false; - - const auto info = getElementInfo (scope, element); - - if (info.error != noErr) - return 0; - - if (busIgnoresLayout (info.isInput, info.busNr)) - return 0; - - outWritable = true; - - const size_t sizeInBytes = sizeof (AudioChannelLayout) - sizeof (AudioChannelDescription); - - if (outLayoutPtr != nullptr) - { - zeromem (outLayoutPtr, sizeInBytes); - outLayoutPtr->mChannelLayoutTag = getCurrentLayout (info.isInput, info.busNr); - } - - return sizeInBytes; - } - - std::vector GetChannelLayoutTags (AudioUnitScope inScope, AudioUnitElement inElement) override - { - const auto info = getElementInfo (inScope, inElement); - - if (info.error != noErr) - return {}; - - if (busIgnoresLayout (info.isInput, info.busNr)) - return {}; - - return getSupportedBusLayouts (info.isInput, info.busNr); - } - - OSStatus SetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout) override - { - const auto info = getElementInfo (scope, element); - - if (info.error != noErr) - return info.error; - - if (busIgnoresLayout (info.isInput, info.busNr)) - return kAudioUnitErr_PropertyNotWritable; - - if (inLayout == nullptr) - return kAudioUnitErr_InvalidPropertyValue; - - auto& ioElement = IOElement (info.isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element); - - const AudioChannelSet newChannelSet = CoreAudioLayouts::fromCoreAudio (*inLayout); - const int currentNumChannels = static_cast (ioElement.NumberChannels()); - const int newChannelNum = newChannelSet.size(); - - if (currentNumChannels != newChannelNum) - return kAudioUnitErr_InvalidPropertyValue; - - // check if the new layout could be potentially set - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newChannelNum, configs)) - return kAudioUnitErr_FormatNotSupported; - #else - if (! juceFilter->getBus (info.isInput, info.busNr)->isLayoutSupported (newChannelSet)) - return kAudioUnitErr_FormatNotSupported; - #endif - - getCurrentLayout (info.isInput, info.busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet); - - return noErr; - } - - //============================================================================== - // When parameters are discrete we need to use integer values. - float getMaximumParameterValue ([[maybe_unused]] AudioProcessorParameter* juceParam) - { - #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - return 1.0f; - #else - return juceParam->isDiscrete() ? (float) (juceParam->getNumSteps() - 1) : 1.0f; - #endif - } - - ComponentResult GetParameterInfo (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - AudioUnitParameterInfo& outParameterInfo) override - { - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inParameterID)) - { - outParameterInfo.unit = kAudioUnitParameterUnit_Generic; - outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable - | kAudioUnitParameterFlag_IsReadable - | kAudioUnitParameterFlag_HasCFNameString - | kAudioUnitParameterFlag_ValuesHaveStrings); - - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; - #endif - - const String name = param->getName (1024); - - // Set whether the param is automatable (unnamed parameters aren't allowed to be automated) - if (name.isEmpty() || ! param->isAutomatable()) - outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; - - const bool isParameterDiscrete = param->isDiscrete(); - - if (! isParameterDiscrete) - outParameterInfo.flags |= kAudioUnitParameterFlag_CanRamp; - - if (param->isMetaParameter()) - outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; - - auto parameterGroupHierarchy = juceFilter->getParameterTree().getGroupsForParameter (param); - - if (! parameterGroupHierarchy.isEmpty()) - { - outParameterInfo.flags |= kAudioUnitParameterFlag_HasClump; - outParameterInfo.clumpID = (UInt32) parameterGroups.indexOf (parameterGroupHierarchy.getLast()) + 1; - } - - // Is this a meter? - if ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2) - { - outParameterInfo.flags &= ~kAudioUnitParameterFlag_IsWritable; - outParameterInfo.flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; - outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; - } - else - { - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - if (isParameterDiscrete) - outParameterInfo.unit = param->isBoolean() ? kAudioUnitParameterUnit_Boolean - : kAudioUnitParameterUnit_Indexed; - #endif - } - - MusicDeviceBase::FillInParameterName (outParameterInfo, name.toCFString(), true); - - outParameterInfo.minValue = 0.0f; - outParameterInfo.maxValue = getMaximumParameterValue (param); - outParameterInfo.defaultValue = param->getDefaultValue() * getMaximumParameterValue (param); - jassert (outParameterInfo.defaultValue >= outParameterInfo.minValue - && outParameterInfo.defaultValue <= outParameterInfo.maxValue); - - return noErr; - } - } - - return kAudioUnitErr_InvalidParameter; - } - - ComponentResult GetParameterValueStrings (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - CFArrayRef *outStrings) override - { - if (outStrings == nullptr) - return noErr; - - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inParameterID)) - { - if (param->isDiscrete()) - { - auto index = LegacyAudioParameter::getParamIndex (*juceFilter, param); - - if (auto* valueStrings = parameterValueStringArrays[index]) - { - *outStrings = CFArrayCreate (nullptr, - (const void **) valueStrings->getRawDataPointer(), - valueStrings->size(), - nullptr); - - return noErr; - } - } - } - } - - return kAudioUnitErr_InvalidParameter; - } - - ComponentResult GetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32& outValue) override - { - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inID)) - { - const auto normValue = param->getValue(); - - outValue = normValue * getMaximumParameterValue (param); - return noErr; - } - } - - return MusicDeviceBase::GetParameter (inID, inScope, inElement, outValue); - } - - ComponentResult SetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32 inValue, - UInt32 inBufferOffsetInFrames) override - { - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inID)) - { - auto value = inValue / getMaximumParameterValue (param); - - if (value != param->getValue()) - { - inParameterChangedCallback = true; - param->setValueNotifyingHost (value); - } - - return noErr; - } - } - - return MusicDeviceBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); - } - - // No idea what this method actually does or what it should return. Current Apple docs say nothing about it. - // (Note that this isn't marked 'override' in case older versions of the SDK don't include it) - bool CanScheduleParameters() const override { return false; } - - //============================================================================== - bool SupportsTail() override { return true; } - Float64 GetTailTime() override { return juceFilter->getTailLengthSeconds(); } - - double getSampleRate() - { - if (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false) > 0) - return Output (0).GetStreamFormat().mSampleRate; - - return 44100.0; - } - - Float64 GetLatency() override - { - const double rate = getSampleRate(); - jassert (rate > 0); - #if JucePlugin_Enable_ARA - jassert (juceFilter->getLatencySamples() == 0 || ! dynamic_cast (juceFilter.get())->isBoundToARA()); - #endif - return rate > 0 ? juceFilter->getLatencySamples() / rate : 0; - } - - class ScopedPlayHead : private AudioPlayHead - { - public: - explicit ScopedPlayHead (JuceAU& juceAudioUnit) - : audioUnit (juceAudioUnit) - { - audioUnit.juceFilter->setPlayHead (this); - } - - ~ScopedPlayHead() override - { - audioUnit.juceFilter->setPlayHead (nullptr); - } - - private: - Optional getPosition() const override - { - PositionInfo info; - - info.setFrameRate ([this]() -> Optional - { - switch (audioUnit.lastTimeStamp.mSMPTETime.mType) - { - case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); - case kSMPTETimeType24: return FrameRate().withBaseRate (24); - case kSMPTETimeType25: return FrameRate().withBaseRate (25); - case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); - case kSMPTETimeType30: return FrameRate().withBaseRate (30); - case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); - case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - case kSMPTETimeType60: return FrameRate().withBaseRate (60); - case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); - case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); - case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - case kSMPTETimeType50: return FrameRate().withBaseRate (50); - default: break; - } - - return {}; - }()); - - double ppqPosition = 0.0; - double bpm = 0.0; - - if (audioUnit.CallHostBeatAndTempo (&ppqPosition, &bpm) == noErr) - { - info.setPpqPosition (ppqPosition); - info.setBpm (bpm); - } - - UInt32 outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat; - float num; - UInt32 den; - - if (audioUnit.CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, - &num, - &den, - &outCurrentMeasureDownBeat) == noErr) - { - info.setTimeSignature (TimeSignature { (int) num, (int) den }); - info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); - } - - double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; - Boolean playing = false, looping = false, playchanged; - - const auto setTimeInSamples = [&] (auto timeInSamples) - { - info.setTimeInSamples ((int64) (timeInSamples + 0.5)); - info.setTimeInSeconds (*info.getTimeInSamples() / audioUnit.getSampleRate()); - }; - - if (audioUnit.CallHostTransportState (&playing, - &playchanged, - &outCurrentSampleInTimeLine, - &looping, - &outCycleStartBeat, - &outCycleEndBeat) == noErr) - { - info.setIsPlaying (playing); - info.setIsLooping (looping); - info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); - setTimeInSamples (outCurrentSampleInTimeLine); - } - else - { - // If the host doesn't support this callback, then use the sample time from lastTimeStamp - setTimeInSamples (audioUnit.lastTimeStamp.mSampleTime); - } - - info.setHostTimeNs ((audioUnit.lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0 - ? makeOptional (audioUnit.timeConversions.hostTimeToNanos (audioUnit.lastTimeStamp.mHostTime)) - : nullopt); - - return info; - } - - JuceAU& audioUnit; - }; - - //============================================================================== - void sendAUEvent (const AudioUnitEventType type, const int juceParamIndex) - { - if (restoringState) - return; - - auEvent.mEventType = type; - auEvent.mArgument.mParameter.mParameterID = getAUParameterIDForIndex (juceParamIndex); - AUEventListenerNotify (nullptr, nullptr, &auEvent); - } - - void audioProcessorParameterChanged (AudioProcessor*, int index, float /*newValue*/) override - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - sendAUEvent (kAudioUnitEvent_ParameterValueChange, index); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override - { - sendAUEvent (kAudioUnitEvent_BeginParameterChangeGesture, index); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override - { - sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); - } - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - audioProcessorChangedUpdater.update (details); - } - - //============================================================================== - // this will only ever be called by the bypass parameter - void parameterValueChanged (int, float) override - { - if (! restoringState) - PropertyChanged (kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0); - } - - void parameterGestureChanged (int, bool) override {} - - //============================================================================== - bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override - { - const auto info = getElementInfo (scope, element); - - return ((! IsInitialized()) && (info.error == noErr)); - } - - bool ValidFormat (AudioUnitScope inScope, - AudioUnitElement inElement, - const AudioStreamBasicDescription& inNewFormat) override - { - // DSP Quattro incorrectly uses global scope for the ValidFormat call - if (inScope == kAudioUnitScope_Global) - return ValidFormat (kAudioUnitScope_Input, inElement, inNewFormat) - || ValidFormat (kAudioUnitScope_Output, inElement, inNewFormat); - - const auto info = getElementInfo (inScope, inElement); - - if (info.error != noErr) - return false; - - if (info.kind == BusKind::wrapperOnly) - return true; - - const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); - const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); - - if (newNumChannels == oldNumChannels) - return true; - - if ([[maybe_unused]] AudioProcessor::Bus* bus = juceFilter->getBus (info.isInput, info.busNr)) - { - if (! MusicDeviceBase::ValidFormat (inScope, inElement, inNewFormat)) - return false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - return AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs); - #else - return bus->isNumberOfChannelsSupported (newNumChannels); - #endif - } - - return false; - } - - // AU requires us to override this for the sole reason that we need to find a default layout tag if the number of channels have changed - OSStatus ChangeStreamFormat (AudioUnitScope inScope, - AudioUnitElement inElement, - const AudioStreamBasicDescription& inPrevFormat, - const AudioStreamBasicDescription& inNewFormat) override - { - const auto info = getElementInfo (inScope, inElement); - - if (info.error != noErr) - return info.error; - - AudioChannelLayoutTag& currentTag = getCurrentLayout (info.isInput, info.busNr); - - const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); - const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs)) - return kAudioUnitErr_FormatNotSupported; - #endif - - // predict channel layout - const auto set = [&] - { - if (info.kind == BusKind::wrapperOnly) - return AudioChannelSet::discreteChannels (newNumChannels); - - if (newNumChannels != oldNumChannels) - return juceFilter->getBus (info.isInput, info.busNr)->supportedLayoutWithChannels (newNumChannels); - - return juceFilter->getChannelLayoutOfBus (info.isInput, info.busNr); - }(); - - if (set == AudioChannelSet()) - return kAudioUnitErr_FormatNotSupported; - - const auto err = MusicDeviceBase::ChangeStreamFormat (inScope, inElement, inPrevFormat, inNewFormat); - - if (err == noErr) - currentTag = CoreAudioLayouts::toCoreAudio (set); - - return err; - } - - //============================================================================== - ComponentResult Render (AudioUnitRenderActionFlags& ioActionFlags, - const AudioTimeStamp& inTimeStamp, - const UInt32 nFrames) override - { - lastTimeStamp = inTimeStamp; - - // prepare buffers - { - pullInputAudio (ioActionFlags, inTimeStamp, nFrames); - prepareOutputBuffers (nFrames); - audioBuffer.reset(); - } - - ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; - - const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); - const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); - - // set buffer pointers to minimize copying - { - int chIdx = 0, numChannels = 0; - bool interleaved = false; - AudioBufferList* buffer = nullptr; - - // use output pointers - for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) - { - GetAudioBufferList (false, busIdx, buffer, interleaved, numChannels); - const int* outLayoutMap = mapper.get (false, busIdx); - - for (int ch = 0; ch < numChannels; ++ch) - audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); - } - - // use input pointers on remaining channels - for (int busIdx = 0; chIdx < totalInChannels;) - { - int channelIndexInBus = juceFilter->getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); - const bool badData = ! pulledSucceeded[busIdx]; - - if (! badData) - GetAudioBufferList (true, busIdx, buffer, interleaved, numChannels); - - const int* inLayoutMap = mapper.get (true, busIdx); - - const int n = juceFilter->getChannelCountOfBus (true, busIdx); - for (int ch = channelIndexInBus; ch < n; ++ch) - audioBuffer.setBuffer (chIdx++, interleaved || badData ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[ch]].mData)); - } - } - - // copy input - { - for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) - { - if (pulledSucceeded[busIdx]) - audioBuffer.set (busIdx, Input ((UInt32) busIdx).GetBufferList(), mapper.get (true, busIdx)); - else - audioBuffer.clearInputBus (busIdx, (int) nFrames); - } - - audioBuffer.clearUnusedChannels ((int) nFrames); - } - - // swap midi buffers - { - const ScopedLock sl (incomingMidiLock); - midiEvents.clear(); - incomingEvents.swapWith (midiEvents); - } - - // process audio - processBlock (audioBuffer.getBuffer (nFrames), midiEvents); - - // copy back - { - for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) - audioBuffer.get (busIdx, Output ((UInt32) busIdx).GetBufferList(), mapper.get (false, busIdx)); - } - - // process midi output - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - pushMidiOutput (nFrames); - #endif - - midiEvents.clear(); - - return noErr; - } - - //============================================================================== - ComponentResult StartNote (MusicDeviceInstrumentID, MusicDeviceGroupID, NoteInstanceID*, UInt32, const MusicDeviceNoteParams&) override { return noErr; } - ComponentResult StopNote (MusicDeviceGroupID, NoteInstanceID, UInt32) override { return noErr; } - - //============================================================================== - OSStatus HandleMIDIEvent ([[maybe_unused]] UInt8 inStatus, - [[maybe_unused]] UInt8 inChannel, - [[maybe_unused]] UInt8 inData1, - [[maybe_unused]] UInt8 inData2, - [[maybe_unused]] UInt32 inStartFrame) override - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - const juce::uint8 data[] = { (juce::uint8) (inStatus | inChannel), - (juce::uint8) inData1, - (juce::uint8) inData2 }; - - const ScopedLock sl (incomingMidiLock); - incomingEvents.addEvent (data, 3, (int) inStartFrame); - return noErr; - #else - return kAudioUnitErr_PropertyNotInUse; - #endif - } - - OSStatus HandleSysEx ([[maybe_unused]] const UInt8* inData, [[maybe_unused]] UInt32 inLength) override - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - const ScopedLock sl (incomingMidiLock); - incomingEvents.addEvent (inData, (int) inLength, 0); - return noErr; - #else - return kAudioUnitErr_PropertyNotInUse; - #endif - } - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - OSStatus MIDIEventList (UInt32 inOffsetSampleFrame, const struct MIDIEventList* list) override - { - const ScopedLock sl (incomingMidiLock); - - auto* packet = &list->packet[0]; - - for (uint32_t i = 0; i < list->numPackets; ++i) - { - toBytestreamDispatcher.dispatch (reinterpret_cast (packet->words), - reinterpret_cast (packet->words + packet->wordCount), - static_cast (packet->timeStamp + inOffsetSampleFrame), - [this] (const ump::BytestreamMidiView& message) - { - incomingEvents.addEvent (message.getMessage(), (int) message.timestamp); - }); - - packet = MIDIEventPacketNext (packet); - } - - return noErr; - } - #endif - - //============================================================================== - ComponentResult GetPresets (CFArrayRef* outData) const override - { - if (outData != nullptr) - { - const int numPrograms = juceFilter->getNumPrograms(); - - clearPresetsArray(); - presetsArray.insertMultiple (0, AUPreset(), numPrograms); - - CFMutableArrayRef presetsArrayRef = CFArrayCreateMutable (nullptr, numPrograms, nullptr); - - for (int i = 0; i < numPrograms; ++i) - { - String name (juceFilter->getProgramName(i)); - if (name.isEmpty()) - name = "Untitled"; - - AUPreset& p = presetsArray.getReference(i); - p.presetNumber = i; - p.presetName = name.toCFString(); - - CFArrayAppendValue (presetsArrayRef, &p); - } - - *outData = (CFArrayRef) presetsArrayRef; - } - - return noErr; - } - - OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset) override - { - const int numPrograms = juceFilter->getNumPrograms(); - const SInt32 chosenPresetNumber = (int) inNewFactoryPreset.presetNumber; - - if (chosenPresetNumber >= numPrograms) - return kAudioUnitErr_InvalidProperty; - - AUPreset chosenPreset; - chosenPreset.presetNumber = chosenPresetNumber; - chosenPreset.presetName = juceFilter->getProgramName (chosenPresetNumber).toCFString(); - - juceFilter->setCurrentProgram (chosenPresetNumber); - SetAFactoryPresetAsCurrent (chosenPreset); - - return noErr; - } - - //============================================================================== - class EditorCompHolder : public Component - { - public: - EditorCompHolder (AudioProcessorEditor* const editor) - { - addAndMakeVisible (editor); - - #if ! JucePlugin_EditorRequiresKeyboardFocus - setWantsKeyboardFocus (false); - #else - setWantsKeyboardFocus (true); - #endif - - setBounds (getSizeToContainChild()); - - lastBounds = getBounds(); - } - - ~EditorCompHolder() override - { - deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may - // have been transferred to another parent which takes over ownership. - } - - Rectangle getSizeToContainChild() - { - if (auto* editor = getChildComponent (0)) - return getLocalArea (editor, editor->getLocalBounds()); - - return {}; - } - - static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor) - { - auto* editorCompHolder = new EditorCompHolder (editor); - auto r = convertToHostBounds (makeNSRect (editorCompHolder->getSizeToContainChild())); - - static JuceUIViewClass cls; - auto* view = [[cls.createInstance() initWithFrame: r] autorelease]; - - JuceUIViewClass::setFilter (view, filter); - JuceUIViewClass::setAU (view, au); - JuceUIViewClass::setEditor (view, editorCompHolder); - - [view setHidden: NO]; - [view setPostsFrameChangedNotifications: YES]; - - [[NSNotificationCenter defaultCenter] addObserver: view - selector: @selector (applicationWillTerminate:) - name: NSApplicationWillTerminateNotification - object: nil]; - activeUIs.add (view); - - editorCompHolder->addToDesktop (detail::PluginUtilities::getDesktopFlags (editor), view); - editorCompHolder->setVisible (view); - - return view; - } - - void parentSizeChanged() override - { - resizeHostWindow(); - - if (auto* editor = getChildComponent (0)) - editor->repaint(); - } - - void childBoundsChanged (Component*) override - { - auto b = getSizeToContainChild(); - - if (lastBounds != b) - { - lastBounds = b; - setSize (jmax (32, b.getWidth()), jmax (32, b.getHeight())); - - resizeHostWindow(); - } - } - - bool keyPressed (const KeyPress&) override - { - if (detail::PluginUtilities::getHostType().isAbletonLive()) - { - static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event - NSTimeInterval eventTime = [[NSApp currentEvent] timestamp]; - - if (lastEventTime != eventTime) - { - lastEventTime = eventTime; - - NSView* view = (NSView*) getWindowHandle(); - NSView* hostView = [view superview]; - NSWindow* hostWindow = [hostView window]; - - [hostWindow makeFirstResponder: hostView]; - [hostView keyDown: (NSEvent*) [NSApp currentEvent]]; - [hostWindow makeFirstResponder: view]; - } - } - - return false; - } - - void resizeHostWindow() - { - [CATransaction begin]; - [CATransaction setValue:(id) kCFBooleanTrue forKey:kCATransactionDisableActions]; - - auto rect = convertToHostBounds (makeNSRect (lastBounds)); - auto* view = (NSView*) getWindowHandle(); - - auto superRect = [[view superview] frame]; - superRect.size.width = rect.size.width; - superRect.size.height = rect.size.height; - - [[view superview] setFrame: superRect]; - [view setFrame: rect]; - [CATransaction commit]; - - [view setNeedsDisplay: YES]; - } - - private: - Rectangle lastBounds; - - JUCE_DECLARE_NON_COPYABLE (EditorCompHolder) - }; - - void deleteActiveEditors() - { - for (int i = activeUIs.size(); --i >= 0;) - { - id ui = (id) activeUIs.getUnchecked(i); - - if (JuceUIViewClass::getAU (ui) == this) - JuceUIViewClass::deleteEditor (ui); - } - } - - //============================================================================== - struct JuceUIViewClass : public ObjCClass - { - JuceUIViewClass() : ObjCClass ("JUCEAUView_") - { - addIvar ("filter"); - addIvar ("au"); - addIvar ("editor"); - - addMethod (@selector (dealloc), dealloc); - addMethod (@selector (applicationWillTerminate:), applicationWillTerminate); - addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow); - addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow); - - registerClass(); - } - - static void deleteEditor (id self) - { - std::unique_ptr editorComp (getEditor (self)); - - if (editorComp != nullptr) - { - if (editorComp->getChildComponent(0) != nullptr - && activePlugins.contains (getAU (self))) // plugin may have been deleted before the UI - { - AudioProcessor* const filter = getIvar (self, "filter"); - filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); - } - - editorComp = nullptr; - setEditor (self, nullptr); - } - } - - static JuceAU* getAU (id self) { return getIvar (self, "au"); } - static EditorCompHolder* getEditor (id self) { return getIvar (self, "editor"); } - - static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); } - static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); } - static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); } - - private: - static void dealloc (id self, SEL) - { - if (activeUIs.contains (self)) - shutdown (self); - - sendSuperclassMessage (self, @selector (dealloc)); - } - - static void applicationWillTerminate (id self, SEL, NSNotification*) - { - shutdown (self); - } - - static void shutdown (id self) - { - [[NSNotificationCenter defaultCenter] removeObserver: self]; - deleteEditor (self); - - jassert (activeUIs.contains (self)); - activeUIs.removeFirstMatchingValue (self); - - if (activePlugins.size() + activeUIs.size() == 0) - { - // there's some kind of component currently modal, but the host - // is trying to delete our plugin.. - jassert (Component::getCurrentlyModalComponent() == nullptr); - - shutdownJuce_GUI(); - } - } - - static void viewDidMoveToWindow (id self, SEL) - { - if (NSWindow* w = [(NSView*) self window]) - { - [w setAcceptsMouseMovedEvents: YES]; - - if (EditorCompHolder* const editorComp = getEditor (self)) - [w makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; - } - } - - static BOOL mouseDownCanMoveWindow (id, SEL) - { - return NO; - } - }; - - //============================================================================== - struct JuceUICreationClass : public ObjCClass - { - JuceUICreationClass() : ObjCClass ("JUCE_AUCocoaViewClass_") - { - addMethod (@selector (interfaceVersion), interfaceVersion); - addMethod (@selector (description), description); - addMethod (@selector (uiViewForAudioUnit:withSize:), uiViewForAudioUnit); - - addProtocol (@protocol (AUCocoaUIBase)); - - registerClass(); - } - - private: - static unsigned int interfaceVersion (id, SEL) { return 0; } - - static NSString* description (id, SEL) - { - return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; - } - - static NSView* uiViewForAudioUnit (id, SEL, AudioUnit inAudioUnit, NSSize) - { - void* pointers[2]; - UInt32 propertySize = sizeof (pointers); - - if (AudioUnitGetProperty (inAudioUnit, juceFilterObjectPropertyID, - kAudioUnitScope_Global, 0, pointers, &propertySize) == noErr) - { - if (AudioProcessor* filter = static_cast (pointers[0])) - if (AudioProcessorEditor* editorComp = filter->createEditorIfNeeded()) - { - #if JucePlugin_Enable_ARA - jassert (dynamic_cast (editorComp) != nullptr); - // for proper view embedding, ARA plug-ins must be resizable - jassert (editorComp->isResizable()); - #endif - return EditorCompHolder::createViewFor (filter, static_cast (pointers[1]), editorComp); - } - } - - return nil; - } - }; - -private: - //============================================================================== - /* The call to AUBase::PropertyChanged may allocate hence the need for this class */ - class AudioProcessorChangedUpdater final : private AsyncUpdater - { - public: - explicit AudioProcessorChangedUpdater (JuceAU& o) : owner (o) {} - ~AudioProcessorChangedUpdater() override { cancelPendingUpdate(); } - - void update (const ChangeDetails& details) - { - int flags = 0; - - if (details.latencyChanged) - flags |= latencyChangedFlag; - - if (details.parameterInfoChanged) - flags |= parameterInfoChangedFlag; - - if (details.programChanged) - flags |= programChangedFlag; - - if (flags != 0) - { - callbackFlags.fetch_or (flags); - - if (MessageManager::getInstance()->isThisTheMessageThread()) - handleAsyncUpdate(); - else - triggerAsyncUpdate(); - } - } - - private: - void handleAsyncUpdate() override - { - const auto flags = callbackFlags.exchange (0); - - if ((flags & latencyChangedFlag) != 0) - owner.PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); - - if ((flags & parameterInfoChangedFlag) != 0) - { - owner.PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); - owner.PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); - } - - owner.PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); - - if ((flags & programChangedFlag) != 0) - { - owner.refreshCurrentPreset(); - owner.PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); - } - } - - JuceAU& owner; - - static constexpr int latencyChangedFlag = 1 << 0, - parameterInfoChangedFlag = 1 << 1, - programChangedFlag = 1 << 2; - - std::atomic callbackFlags { 0 }; - }; - - //============================================================================== - AudioUnitHelpers::CoreAudioBufferList audioBuffer; - MidiBuffer midiEvents, incomingEvents; - bool prepared = false, isBypassed = false, restoringState = false; - - //============================================================================== - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - static constexpr bool forceUseLegacyParamIDs = true; - #else - static constexpr bool forceUseLegacyParamIDs = false; - #endif - - //============================================================================== - LegacyAudioParametersWrapper juceParameters; - std::unordered_map paramMap; - Array auParamIDs; - Array parameterGroups; - - // Stores the parameter IDs in the order that they will be reported to the host. - std::vector cachedParameterList; - - //============================================================================== - // According to the docs, this is the maximum size of a MIDIPacketList. - static constexpr UInt32 packetListBytes = 65536; - - CoreAudioTimeConversions timeConversions; - AudioUnitEvent auEvent; - mutable Array presetsArray; - CriticalSection incomingMidiLock; - AUMIDIOutputCallbackStruct midiCallback; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - class ScopedMIDIEventListBlock - { - public: - ScopedMIDIEventListBlock() = default; - - ScopedMIDIEventListBlock (ScopedMIDIEventListBlock&& other) noexcept - : midiEventListBlock (std::exchange (other.midiEventListBlock, nil)) {} - - ScopedMIDIEventListBlock& operator= (ScopedMIDIEventListBlock&& other) noexcept - { - ScopedMIDIEventListBlock { std::move (other) }.swap (*this); - return *this; - } - - ~ScopedMIDIEventListBlock() - { - if (midiEventListBlock != nil) - [midiEventListBlock release]; - } - - static ScopedMIDIEventListBlock copy (AUMIDIEventListBlock b) - { - return ScopedMIDIEventListBlock { b }; - } - - explicit operator bool() const { return midiEventListBlock != nil; } - - void operator() (AUEventSampleTime eventSampleTime, uint8_t cable, const struct MIDIEventList * eventList) const - { - jassert (midiEventListBlock != nil); - midiEventListBlock (eventSampleTime, cable, eventList); - } - - private: - void swap (ScopedMIDIEventListBlock& other) noexcept - { - std::swap (other.midiEventListBlock, midiEventListBlock); - } - - explicit ScopedMIDIEventListBlock (AUMIDIEventListBlock b) : midiEventListBlock ([b copy]) {} - - AUMIDIEventListBlock midiEventListBlock = nil; - }; - - ScopedMIDIEventListBlock midiEventListBlock; - std::optional hostProtocol; - ump::ToUMP1Converter toUmp1Converter; - ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 }; - #endif - - AudioTimeStamp lastTimeStamp; - int totalInChannels, totalOutChannels; - HeapBlock pulledSucceeded; - HeapBlock packetList { packetListBytes, 1 }; - - ThreadLocalValue inParameterChangedCallback; - - AudioProcessorChangedUpdater audioProcessorChangedUpdater { *this }; - - //============================================================================== - Array channelInfo; - Array> supportedInputLayouts, supportedOutputLayouts; - Array currentInputLayout, currentOutputLayout; - - //============================================================================== - AudioUnitHelpers::ChannelRemapper mapper; - - //============================================================================== - OwnedArray> parameterValueStringArrays; - - //============================================================================== - AudioProcessorParameter* bypassParam = nullptr; - - //============================================================================== - static NSRect convertToHostBounds (NSRect pluginRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return pluginRect; - - return NSMakeRect (static_cast (pluginRect.origin.x * desktopScale), - static_cast (pluginRect.origin.y * desktopScale), - static_cast (pluginRect.size.width * desktopScale), - static_cast (pluginRect.size.height * desktopScale)); - } - - static NSRect convertFromHostBounds (NSRect hostRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return hostRect; - - return NSMakeRect (static_cast (hostRect.origin.x / desktopScale), - static_cast (hostRect.origin.y / desktopScale), - static_cast (hostRect.size.width / desktopScale), - static_cast (hostRect.size.height / desktopScale)); - } - - //============================================================================== - void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept - { - const unsigned int numInputBuses = GetScope (kAudioUnitScope_Input).GetNumberOfElements(); - - for (unsigned int i = 0; i < numInputBuses; ++i) - { - auto& input = Input (i); - - const bool succeeded = (input.PullInput (flags, timestamp, i, nFrames) == noErr); - - if ((flags & kAudioUnitRenderAction_OutputIsSilence) != 0 && succeeded) - AudioUnitHelpers::clearAudioBuffer (input.GetBufferList()); - - pulledSucceeded[i] = succeeded; - } - } - - void prepareOutputBuffers (const UInt32 nFrames) noexcept - { - const auto numProcessorBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); - const auto numWrapperBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements(); - - for (UInt32 busIdx = 0; busIdx < numWrapperBuses; ++busIdx) - { - auto& output = Output (busIdx); - - if (output.WillAllocateBuffer()) - output.PrepareBuffer (nFrames); - - if (busIdx >= (UInt32) numProcessorBuses) - AudioUnitHelpers::clearAudioBuffer (output.GetBufferList()); - } - } - - void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept - { - const ScopedLock sl (juceFilter->getCallbackLock()); - const ScopedPlayHead playhead { *this }; - - if (juceFilter->isSuspended()) - { - buffer.clear(); - } - else if (bypassParam == nullptr && isBypassed) - { - juceFilter->processBlockBypassed (buffer, midiBuffer); - } - else - { - juceFilter->processBlock (buffer, midiBuffer); - } - } - - void pushMidiOutput ([[maybe_unused]] UInt32 nFrames) noexcept - { - if (midiEvents.isEmpty()) - return; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - if (@available (macOS 12.0, iOS 15.0, *)) - { - if (midiEventListBlock) - { - struct MIDIEventList stackList = {}; - MIDIEventPacket* end = nullptr; - - const auto init = [&] - { - end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0); - }; - - const auto send = [&] - { - midiEventListBlock (static_cast (lastTimeStamp.mSampleTime), 0, &stackList); - }; - - const auto add = [&] (const ump::View& view, int timeStamp) - { - static_assert (sizeof (uint32_t) == sizeof (UInt32) - && alignof (uint32_t) == alignof (UInt32), - "If this fails, the cast below will be broken too!"); - using List = struct MIDIEventList; - end = MIDIEventListAdd (&stackList, - sizeof (List::packet), - end, - (MIDITimeStamp) timeStamp, - view.size(), - reinterpret_cast (view.data())); - }; - - init(); - - for (const auto metadata : midiEvents) - { - toUmp1Converter.convert (ump::BytestreamMidiView (metadata), [&] (const ump::View& view) - { - add (view, metadata.samplePosition); - - if (end != nullptr) - return; - - send(); - init(); - add (view, metadata.samplePosition); - }); - - } - - send(); - - return; - } - } - #endif - - if (midiCallback.midiOutputCallback) - { - MIDIPacket* end = nullptr; - - const auto init = [&] - { - end = MIDIPacketListInit (packetList); - }; - - const auto send = [&] - { - midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); - }; - - const auto add = [&] (const MidiMessageMetadata& metadata) - { - end = MIDIPacketListAdd (packetList, - packetListBytes, - end, - static_cast (metadata.samplePosition), - static_cast (metadata.numBytes), - metadata.data); - }; - - init(); - - for (const auto metadata : midiEvents) - { - jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); - - add (metadata); - - if (end == nullptr) - { - send(); - init(); - add (metadata); - - if (end == nullptr) - { - // If this is hit, the size of this midi packet exceeds the maximum size of - // a MIDIPacketList. Large SysEx messages should be broken up into smaller - // chunks. - jassertfalse; - init(); - } - } - } - - send(); - } - } - - void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) - { - auto* element = Element (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, static_cast (busIdx)).AsIOElement(); - jassert (element != nullptr); - - bufferList = &element->GetBufferList(); - - jassert (bufferList->mNumberBuffers > 0); - - interleaved = AudioUnitHelpers::isAudioBufferInterleaved (*bufferList); - numChannels = static_cast (interleaved ? bufferList->mBuffers[0].mNumberChannels : bufferList->mNumberBuffers); - } - - //============================================================================== - static OSStatus scopeToDirection (AudioUnitScope scope, bool& isInput) noexcept - { - isInput = (scope == kAudioUnitScope_Input); - - return (scope != kAudioUnitScope_Input - && scope != kAudioUnitScope_Output) - ? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; - } - - enum class BusKind - { - processor, - wrapperOnly, - }; - - struct ElementInfo - { - int busNr; - BusKind kind; - bool isInput; - OSStatus error; - }; - - ElementInfo getElementInfo (AudioUnitScope scope, AudioUnitElement element) noexcept - { - bool isInput = false; - OSStatus err; - - if ((err = scopeToDirection (scope, isInput)) != noErr) - return { {}, {}, {}, err }; - - const auto busIdx = static_cast (element); - - if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (*juceFilter, isInput))) - return { busIdx, BusKind::processor, isInput, noErr }; - - if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCountForWrapper (*juceFilter, isInput))) - return { busIdx, BusKind::wrapperOnly, isInput, noErr }; - - return { {}, {}, {}, kAudioUnitErr_InvalidElement }; - } - - OSStatus GetParameterList (AudioUnitScope inScope, AudioUnitParameterID* outParameterList, UInt32& outNumParameters) override - { - if (forceUseLegacyParamIDs || inScope != kAudioUnitScope_Global) - return MusicDeviceBase::GetParameterList (inScope, outParameterList, outNumParameters); - - outNumParameters = (UInt32) juceParameters.size(); - - if (outParameterList == nullptr) - return noErr; - - if (cachedParameterList.empty()) - { - struct ParamInfo - { - AudioUnitParameterID identifier; - int versionHint; - }; - - std::vector vec; - vec.reserve (juceParameters.size()); - - for (const auto* param : juceParameters) - vec.push_back ({ generateAUParameterID (*param), param->getVersionHint() }); - - std::sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.identifier < b.identifier; }); - std::stable_sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.versionHint < b.versionHint; }); - std::transform (vec.begin(), vec.end(), std::back_inserter (cachedParameterList), [] (auto x) { return x.identifier; }); - } - - std::copy (cachedParameterList.begin(), cachedParameterList.end(), outParameterList); - - return noErr; - } - - //============================================================================== - void addParameters() - { - parameterGroups = juceFilter->getParameterTree().getSubgroups (true); - - juceParameters.update (*juceFilter, forceUseLegacyParamIDs); - const int numParams = juceParameters.getNumParameters(); - - if (forceUseLegacyParamIDs) - { - Globals()->UseIndexedParameters (static_cast (numParams)); - } - else - { - for (auto* param : juceParameters) - { - const AudioUnitParameterID auParamID = generateAUParameterID (*param); - - // Consider yourself very unlucky if you hit this assertion. The hash codes of your - // parameter ids are not unique. - jassert (paramMap.find (static_cast (auParamID)) == paramMap.end()); - - auParamIDs.add (auParamID); - paramMap.emplace (static_cast (auParamID), param); - Globals()->SetParameter (auParamID, param->getValue()); - } - } - - #if JUCE_DEBUG - // Some hosts can't handle the huge numbers of discrete parameter values created when - // using the default number of steps. - for (auto* param : juceParameters) - if (param->isDiscrete()) - jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); - #endif - - parameterValueStringArrays.ensureStorageAllocated (numParams); - - for (auto* param : juceParameters) - { - OwnedArray* stringValues = nullptr; - - auto initialValue = param->getValue(); - bool paramIsLegacy = dynamic_cast (param) != nullptr; - - if (param->isDiscrete() && (! forceUseLegacyParamIDs)) - { - const auto numSteps = param->getNumSteps(); - stringValues = new OwnedArray(); - stringValues->ensureStorageAllocated (numSteps); - - const auto maxValue = getMaximumParameterValue (param); - - auto getTextValue = [param, paramIsLegacy] (float value) - { - if (paramIsLegacy) - { - param->setValue (value); - return param->getCurrentValueAsText(); - } - - return param->getText (value, 256); - }; - - for (int i = 0; i < numSteps; ++i) - { - auto value = (float) i / maxValue; - stringValues->add (CFStringCreateCopy (nullptr, (getTextValue (value).toCFString()))); - } - } - - if (paramIsLegacy) - param->setValue (initialValue); - - parameterValueStringArrays.add (stringValues); - } - - if ((bypassParam = juceFilter->getBypassParameter()) != nullptr) - bypassParam->addListener (this); - } - - //============================================================================== - static AudioUnitParameterID generateAUParameterID (const AudioProcessorParameter& param) - { - const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceUseLegacyParamIDs); - AudioUnitParameterID paramHash = static_cast (juceParamID.hashCode()); - - #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS - // studio one doesn't like negative parameters - paramHash &= ~(((AudioUnitParameterID) 1) << (sizeof (AudioUnitParameterID) * 8 - 1)); - #endif - - return forceUseLegacyParamIDs ? static_cast (juceParamID.getIntValue()) - : paramHash; - } - - inline AudioUnitParameterID getAUParameterIDForIndex (int paramIndex) const noexcept - { - return forceUseLegacyParamIDs ? static_cast (paramIndex) - : auParamIDs.getReference (paramIndex); - } - - AudioProcessorParameter* getParameterForAUParameterID (AudioUnitParameterID address) const noexcept - { - const auto index = static_cast (address); - - if (forceUseLegacyParamIDs) - return juceParameters.getParamForIndex (index); - - const auto iter = paramMap.find (index); - return iter != paramMap.end() ? iter->second : nullptr; - } - - //============================================================================== - OSStatus syncAudioUnitWithProcessor() - { - OSStatus err = noErr; - const auto numWrapperInputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true); - const auto numWrapperOutputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false); - - if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast (numWrapperInputs))) != noErr) - return err; - - if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast (numWrapperOutputs))) != noErr) - return err; - - addSupportedLayoutTags(); - - const auto numProcessorInputs = AudioUnitHelpers::getBusCount (*juceFilter, true); - const auto numProcessorOutputs = AudioUnitHelpers::getBusCount (*juceFilter, false); - - for (int i = 0; i < numProcessorInputs; ++i) - if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) - return err; - - for (int i = 0; i < numProcessorOutputs; ++i) - if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) - return err; - - return noErr; - } - - OSStatus syncProcessorWithAudioUnit() - { - const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); - const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); - - const int numInputElements = static_cast (GetScope (kAudioUnitScope_Input). GetNumberOfElements()); - const int numOutputElements = static_cast (GetScope (kAudioUnitScope_Output).GetNumberOfElements()); - - AudioProcessor::BusesLayout requestedLayouts; - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - const int n = (isInput ? numInputBuses : numOutputBuses); - const int numAUElements = (isInput ? numInputElements : numOutputElements); - Array& requestedBuses = (isInput ? requestedLayouts.inputBuses : requestedLayouts.outputBuses); - - for (int busIdx = 0; busIdx < n; ++busIdx) - { - const auto* element = (busIdx < numAUElements ? &IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busIdx) : nullptr); - const int numChannels = (element != nullptr ? static_cast (element->NumberChannels()) : 0); - - AudioChannelLayoutTag currentLayoutTag = isInput ? currentInputLayout[busIdx] : currentOutputLayout[busIdx]; - const int tagNumChannels = currentLayoutTag & 0xffff; - - if (numChannels != tagNumChannels) - return kAudioUnitErr_FormatNotSupported; - - requestedBuses.add (CoreAudioLayouts::fromCoreAudio (currentLayoutTag)); - } - } - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioProcessor::containsLayout (requestedLayouts, configs)) - return kAudioUnitErr_FormatNotSupported; - #endif - - if (! AudioUnitHelpers::setBusesLayout (juceFilter.get(), requestedLayouts)) - return kAudioUnitErr_FormatNotSupported; - - // update total channel count - totalInChannels = juceFilter->getTotalNumInputChannels(); - totalOutChannels = juceFilter->getTotalNumOutputChannels(); - - return noErr; - } - - OSStatus syncAudioUnitWithChannelSet (bool isInput, int busNr, const AudioChannelSet& channelSet) - { - const int numChannels = channelSet.size(); - - getCurrentLayout (isInput, busNr) = CoreAudioLayouts::toCoreAudio (channelSet); - - // is this bus activated? - if (numChannels == 0) - return noErr; - - auto& element = IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busNr); - - element.SetName ((CFStringRef) juceStringToNS (juceFilter->getBus (isInput, busNr)->getName())); - - const auto streamDescription = ausdk::ASBD::CreateCommonFloat32 (getSampleRate(), (UInt32) numChannels); - return element.SetStreamFormat (streamDescription); - } - - //============================================================================== - void clearPresetsArray() const - { - for (int i = presetsArray.size(); --i >= 0;) - CFRelease (presetsArray.getReference(i).presetName); - - presetsArray.clear(); - } - - void refreshCurrentPreset() - { - // this will make the AU host re-read and update the current preset name - // in case it was changed here in the plug-in: - - const int currentProgramNumber = juceFilter->getCurrentProgram(); - const String currentProgramName = juceFilter->getProgramName (currentProgramNumber); - - AUPreset currentPreset; - currentPreset.presetNumber = currentProgramNumber; - currentPreset.presetName = currentProgramName.toCFString(); - - SetAFactoryPresetAsCurrent (currentPreset); - } - - //============================================================================== - std::vector& getSupportedBusLayouts (bool isInput, int bus) noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } - const std::vector& getSupportedBusLayouts (bool isInput, int bus) const noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } - AudioChannelLayoutTag& getCurrentLayout (bool isInput, int bus) noexcept { return (isInput ? currentInputLayout : currentOutputLayout).getReference (bus); } - AudioChannelLayoutTag getCurrentLayout (bool isInput, int bus) const noexcept { return (isInput ? currentInputLayout : currentOutputLayout)[bus]; } - - //============================================================================== - std::vector getSupportedLayoutTagsForBus (bool isInput, int busNum) const - { - std::set tags; - - if (AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNum)) - { - #ifndef JucePlugin_PreferredChannelConfigurations - auto& knownTags = CoreAudioLayouts::getKnownCoreAudioTags(); - - for (auto tag : knownTags) - if (bus->isLayoutSupported (CoreAudioLayouts::fromCoreAudio (tag))) - tags.insert (tag); - #endif - - // add discrete layout tags - int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); - - for (int ch = 0; ch < n; ++ch) - { - #ifdef JucePlugin_PreferredChannelConfigurations - const short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - if (AudioUnitHelpers::isLayoutSupported (*juceFilter, isInput, busNum, ch, configs)) - tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); - #else - if (bus->isLayoutSupported (AudioChannelSet::discreteChannels (ch))) - tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); - #endif - } - } - - return std::vector (tags.begin(), tags.end()); - } - - void addSupportedLayoutTagsForDirection (bool isInput) - { - auto& layouts = isInput ? supportedInputLayouts : supportedOutputLayouts; - layouts.clearQuick(); - auto numBuses = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - - for (int busNr = 0; busNr < numBuses; ++busNr) - layouts.add (getSupportedLayoutTagsForBus (isInput, busNr)); - } - - void addSupportedLayoutTags() - { - currentInputLayout.clear(); currentOutputLayout.clear(); - - currentInputLayout. resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true)); - currentOutputLayout.resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)); - - addSupportedLayoutTagsForDirection (true); - addSupportedLayoutTagsForDirection (false); - } - - static int maxChannelsToProbeFor() - { - return (detail::PluginUtilities::getHostType().isLogic() ? 8 : 64); - } - - //============================================================================== - void auPropertyListener (AudioUnitPropertyID propId, AudioUnitScope scope, AudioUnitElement) - { - if (scope == kAudioUnitScope_Global && propId == kAudioUnitProperty_ContextName - && juceFilter != nullptr && GetContextName() != nullptr) - { - AudioProcessor::TrackProperties props; - props.name = String::fromCFString (GetContextName()); - - juceFilter->updateTrackProperties (props); - } - } - - static void auPropertyListenerDispatcher (void* inRefCon, AudioUnit, AudioUnitPropertyID propId, - AudioUnitScope scope, AudioUnitElement element) - { - static_cast (inRefCon)->auPropertyListener (propId, scope, element); - } - - JUCE_DECLARE_NON_COPYABLE (JuceAU) -}; - -//============================================================================== -#if JucePlugin_ProducesMidiOutput || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - #define FACTORY_BASE_CLASS ausdk::AUMusicDeviceFactory -#else - #define FACTORY_BASE_CLASS ausdk::AUBaseFactory -#endif - -AUSDK_COMPONENT_ENTRY (FACTORY_BASE_CLASS, JuceAU) - -#define JUCE_AU_ENTRY_POINT_NAME JUCE_CONCAT (JucePlugin_AUExportPrefix, Factory) - - extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc); -AUSDK_EXPORT extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc) -{ - return JuceAUFactory (inDesc); -} - -#endif diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm deleted file mode 100644 index fef366b52f..0000000000 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ /dev/null @@ -1,2013 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include - -#if JucePlugin_Build_AUv3 - -#if JUCE_MAC && ! (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) - #error AUv3 needs Deployment Target OS X 10.11 or higher to compile -#endif - -#ifndef __OBJC2__ - #error AUv3 needs Objective-C 2 support (compile with 64-bit) -#endif - -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 - -#include -#include - -#import -#import -#import - -#include -#include -#include -#include -#include - -#define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) - -#if JUCE_IOS - #define JUCE_IOS_MAC_VIEW UIView -#else - #define JUCE_IOS_MAC_VIEW NSView -#endif - -#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) - -#include - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") - -using namespace juce; - -struct AudioProcessorHolder : public ReferenceCountedObject -{ - AudioProcessorHolder() = default; - explicit AudioProcessorHolder (std::unique_ptr p) : processor (std::move (p)) {} - AudioProcessor& operator*() noexcept { return *processor; } - AudioProcessor* operator->() noexcept { return processor.get(); } - AudioProcessor* get() noexcept { return processor.get(); } - - struct ViewConfig - { - double width; - double height; - bool hostHasMIDIController; - }; - - std::unique_ptr viewConfiguration; - - using Ptr = ReferenceCountedObjectPtr; - -private: - std::unique_ptr processor; - - AudioProcessorHolder& operator= (AudioProcessor*) = delete; - AudioProcessorHolder (AudioProcessorHolder&) = delete; - AudioProcessorHolder& operator= (AudioProcessorHolder&) = delete; -}; - -//============================================================================== -//=========================== The actual AudioUnit ============================= -//============================================================================== -class JuceAudioUnitv3 : public AudioProcessorListener, - public AudioPlayHead, - private AudioProcessorParameter::Listener -{ -public: - JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, - const AudioComponentDescription& descr, - AudioComponentInstantiationOptions options, - NSError** error) - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wobjc-method-access") - : au ([getClass().createInstance() initWithComponentDescription: descr - options: options - error: error - juceClass: this]), - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - processorHolder (processor) - { - init(); - } - - JuceAudioUnitv3 (AUAudioUnit* audioUnit, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**) - : au (audioUnit), - processorHolder (new AudioProcessorHolder (createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))) - { - jassert (MessageManager::getInstance()->isThisTheMessageThread()); - initialiseJuce_GUI(); - - init(); - } - - ~JuceAudioUnitv3() override - { - auto& processor = getAudioProcessor(); - processor.removeListener (this); - - if (bypassParam != nullptr) - bypassParam->removeListener (this); - - removeEditor (processor); - } - - //============================================================================== - void init() - { - inParameterChangedCallback = false; - - AudioProcessor& processor = getAudioProcessor(); - const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - const int numConfigs = sizeof (configs) / sizeof (short[2]); - - jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - processor.setPlayConfigDetails (configs[0][0], configs[0][1], kDefaultSampleRate, static_cast (maxFrames)); - - Array channelInfos; - - for (int i = 0; i < numConfigs; ++i) - { - AUChannelInfo channelInfo; - - channelInfo.inChannels = configs[i][0]; - channelInfo.outChannels = configs[i][1]; - - channelInfos.add (channelInfo); - } - #else - Array channelInfos = AudioUnitHelpers::getAUChannelInfo (processor); - #endif - - processor.setPlayHead (this); - - totalInChannels = processor.getTotalNumInputChannels(); - totalOutChannels = processor.getTotalNumOutputChannels(); - - { - channelCapabilities.reset ([[NSMutableArray alloc] init]); - - for (int i = 0; i < channelInfos.size(); ++i) - { - AUChannelInfo& info = channelInfos.getReference (i); - - [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.inChannels]]; - [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.outChannels]]; - } - } - - internalRenderBlock = CreateObjCBlock (this, &JuceAudioUnitv3::renderCallback); - - processor.setRateAndBufferSizeDetails (kDefaultSampleRate, static_cast (maxFrames)); - processor.prepareToPlay (kDefaultSampleRate, static_cast (maxFrames)); - processor.addListener (this); - - addParameters(); - addPresets(); - - addAudioUnitBusses (true); - addAudioUnitBusses (false); - } - - AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } - - //============================================================================== - void reset() - { - midiMessages.clear(); - lastTimeStamp.mSampleTime = std::numeric_limits::max(); - lastTimeStamp.mFlags = 0; - } - - //============================================================================== - AUAudioUnitPreset* getCurrentPreset() const - { - return factoryPresets.getAtIndex (getAudioProcessor().getCurrentProgram()); - } - - void setCurrentPreset (AUAudioUnitPreset* preset) - { - getAudioProcessor().setCurrentProgram (static_cast ([preset number])); - } - - NSArray* getFactoryPresets() const - { - return factoryPresets.get(); - } - - NSDictionary* getFullState() const - { - NSMutableDictionary* retval = [[NSMutableDictionary alloc] init]; - - { - auto* superRetval = ObjCMsgSendSuper*> (au, @selector (fullState)); - - if (superRetval != nullptr) - [retval addEntriesFromDictionary:superRetval]; - } - - juce::MemoryBlock state; - - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - getAudioProcessor().getCurrentProgramStateInformation (state); - #else - getAudioProcessor().getStateInformation (state); - #endif - - if (state.getSize() > 0) - { - NSData* ourState = [[NSData alloc] initWithBytes: state.getData() - length: state.getSize()]; - - NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; - - [retval setObject: ourState - forKey: nsKey]; - - [nsKey release]; - [ourState release]; - } - - return [retval autorelease]; - } - - void setFullState (NSDictionary* state) - { - if (state == nullptr) - return; - - NSMutableDictionary* modifiedState = [[NSMutableDictionary alloc] init]; - [modifiedState addEntriesFromDictionary: state]; - - NSString* nsPresetKey = [[NSString alloc] initWithUTF8String: kAUPresetDataKey]; - [modifiedState removeObjectForKey: nsPresetKey]; - [nsPresetKey release]; - - ObjCMsgSendSuper (au, @selector (setFullState:), state); - - NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; - NSObject* obj = [modifiedState objectForKey: nsKey]; - [nsKey release]; - - if (obj != nullptr) - { - if ([obj isKindOfClass:[NSData class]]) - { - NSData* data = reinterpret_cast (obj); - const int numBytes = static_cast ([data length]); - const juce::uint8* const rawBytes = reinterpret_cast< const juce::uint8* const> ([data bytes]); - - if (numBytes > 0) - { - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - getAudioProcessor().setCurrentProgramStateInformation (rawBytes, numBytes); - #else - getAudioProcessor().setStateInformation (rawBytes, numBytes); - #endif - } - } - } - - [modifiedState release]; - } - - AUParameterTree* getParameterTree() const - { - return paramTree.get(); - } - - NSArray* parametersForOverviewWithCount (int count) const - { - auto* retval = [[[NSMutableArray alloc] init] autorelease]; - - for (const auto& address : addressForIndex) - { - if (static_cast (count) <= [retval count]) - break; - - [retval addObject: [NSNumber numberWithUnsignedLongLong: address]]; - } - - return retval; - } - - //============================================================================== - NSTimeInterval getLatency() const - { - auto& p = getAudioProcessor(); - return p.getLatencySamples() / p.getSampleRate(); - } - - NSTimeInterval getTailTime() const - { - return getAudioProcessor().getTailLengthSeconds(); - } - - //============================================================================== - AUAudioUnitBusArray* getInputBusses() const { return inputBusses.get(); } - AUAudioUnitBusArray* getOutputBusses() const { return outputBusses.get(); } - NSArray* getChannelCapabilities() const { return channelCapabilities.get(); } - - bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) - { - const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); - const int busIdx = static_cast ([auBus index]); - const int newNumChannels = static_cast ([format channelCount]); - - AudioProcessor& processor = getAudioProcessor(); - - if ([[maybe_unused]] AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) - { - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) - return false; - #else - const AVAudioChannelLayout* layout = [format channelLayout]; - const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); - - if (layoutTag != 0) - { - AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); - - if (newLayout.size() != newNumChannels) - return false; - - if (! bus->isLayoutSupported (newLayout)) - return false; - } - else - { - if (! bus->isNumberOfChannelsSupported (newNumChannels)) - return false; - } - #endif - - return true; - } - - return false; - } - - //============================================================================== - int getVirtualMIDICableCount() const - { - #if JucePlugin_WantsMidiInput - return 1; - #else - return 0; - #endif - } - - bool getSupportsMPE() const - { - return getAudioProcessor().supportsMPE(); - } - - NSArray* getMIDIOutputNames() const - { - #if JucePlugin_ProducesMidiOutput - return @[@"MIDI Out"]; - #else - return @[]; - #endif - } - - //============================================================================== - AUInternalRenderBlock getInternalRenderBlock() const { return internalRenderBlock; } - bool getRenderingOffline() const { return getAudioProcessor().isNonRealtime(); } - void setRenderingOffline (bool offline) - { - auto& processor = getAudioProcessor(); - auto isCurrentlyNonRealtime = processor.isNonRealtime(); - - if (isCurrentlyNonRealtime != offline) - { - ScopedLock callbackLock (processor.getCallbackLock()); - - processor.setNonRealtime (offline); - processor.prepareToPlay (processor.getSampleRate(), processor.getBlockSize()); - } - } - - bool getShouldBypassEffect() const - { - if (bypassParam != nullptr) - return (bypassParam->getValue() != 0.0f); - - return (ObjCMsgSendSuper (au, @selector (shouldBypassEffect)) == YES); - } - - void setShouldBypassEffect (bool shouldBypass) - { - if (bypassParam != nullptr) - bypassParam->setValue (shouldBypass ? 1.0f : 0.0f); - - ObjCMsgSendSuper (au, @selector (setShouldBypassEffect:), shouldBypass ? YES : NO); - } - - //============================================================================== - NSString* getContextName() const { return juceStringToNS (contextName); } - void setContextName (NSString* str) - { - if (str != nullptr) - { - AudioProcessor::TrackProperties props; - props.name = nsStringToJuce (str); - - getAudioProcessor().updateTrackProperties (props); - } - } - - //============================================================================== - bool allocateRenderResourcesAndReturnError (NSError **outError) - { - AudioProcessor& processor = getAudioProcessor(); - const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - - if (ObjCMsgSendSuper (au, @selector (allocateRenderResourcesAndReturnError:), outError) == NO) - return false; - - if (outError != nullptr) - *outError = nullptr; - - AudioProcessor::BusesLayout layouts; - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - const int n = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); - Array& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses); - - AUAudioUnitBusArray* auBuses = (isInput ? [au inputBusses] : [au outputBusses]); - jassert ([auBuses count] == static_cast (n)); - - for (int busIdx = 0; busIdx < n; ++busIdx) - { - if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) - { - AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast (busIdx)] format]; - - AudioChannelSet newLayout; - const AVAudioChannelLayout* layout = [format channelLayout]; - const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); - - if (layoutTag != 0) - newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); - else - newLayout = bus->supportedLayoutWithChannels (static_cast ([format channelCount])); - - if (newLayout.isDisabled()) - return false; - - channelSets.add (newLayout); - } - } - } - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioProcessor::containsLayout (layouts, configs)) - { - if (outError != nullptr) - *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; - - return false; - } - #endif - - if (! AudioUnitHelpers::setBusesLayout (&getAudioProcessor(), layouts)) - { - if (outError != nullptr) - *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; - - return false; - } - - totalInChannels = processor.getTotalNumInputChannels(); - totalOutChannels = processor.getTotalNumOutputChannels(); - - allocateBusBuffer (true); - allocateBusBuffer (false); - - mapper.alloc (processor); - - audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (&processor), static_cast (maxFrames)); - - auto sampleRate = [&] - { - for (auto* buffer : { inputBusses.get(), outputBusses.get() }) - if ([buffer count] > 0) - return [[[buffer objectAtIndexedSubscript: 0] format] sampleRate]; - - return 44100.0; - }(); - - processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); - processor.prepareToPlay (sampleRate, static_cast (maxFrames)); - - midiMessages.ensureSize (2048); - midiMessages.clear(); - - hostMusicalContextCallback = [au musicalContextBlock]; - hostTransportStateCallback = [au transportStateBlock]; - - if (@available (macOS 10.13, iOS 11.0, *)) - midiOutputEventBlock = [au MIDIOutputEventBlock]; - - reset(); - - return true; - } - - void deallocateRenderResources() - { - midiOutputEventBlock = nullptr; - - hostMusicalContextCallback = nullptr; - hostTransportStateCallback = nullptr; - - getAudioProcessor().releaseResources(); - audioBuffer.release(); - - inBusBuffers. clear(); - outBusBuffers.clear(); - - mapper.release(); - - ObjCMsgSendSuper (au, @selector (deallocateRenderResources)); - } - - //============================================================================== - struct ScopedKeyChange - { - ScopedKeyChange (AUAudioUnit* a, NSString* k) - : au (a), key (k) - { - [au willChangeValueForKey: key]; - } - - ~ScopedKeyChange() - { - [au didChangeValueForKey: key]; - } - - AUAudioUnit* au; - NSString* key; - }; - - //============================================================================== - void audioProcessorChanged ([[maybe_unused]] AudioProcessor* processor, const ChangeDetails& details) override - { - if (details.programChanged) - { - { - ScopedKeyChange scope (au, @"allParameterValues"); - addPresets(); - } - - { - ScopedKeyChange scope (au, @"currentPreset"); - } - } - - if (details.latencyChanged) - { - ScopedKeyChange scope (au, @"latency"); - } - - if (details.parameterInfoChanged) - { - ScopedKeyChange scope (au, @"parameterTree"); - auto nodes = createParameterNodes (processor->getParameterTree()); - installNewParameterTree (std::move (nodes.nodeArray)); - } - } - - void sendParameterEvent (int idx, const float* newValue, AUParameterAutomationEventType type) - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - if (auto* juceParam = juceParameters.getParamForIndex (idx)) - { - if (auto* param = [paramTree.get() parameterWithAddress: getAUParameterAddressForIndex (idx)]) - { - const auto value = (newValue != nullptr ? *newValue : juceParam->getValue()) * getMaximumParameterValue (*juceParam); - - if (@available (macOS 10.12, iOS 10.0, *)) - { - [param setValue: value - originator: editorObserverToken.get() - atHostTime: lastTimeStamp.mHostTime - eventType: type]; - } - else if (type == AUParameterAutomationEventTypeValue) - { - [param setValue: value originator: editorObserverToken.get()]; - } - } - } - } - - void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override - { - sendParameterEvent (idx, &newValue, AUParameterAutomationEventTypeValue); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int idx) override - { - sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeTouch); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int idx) override - { - sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeRelease); - } - - //============================================================================== - Optional getPosition() const override - { - PositionInfo info; - info.setTimeInSamples ((int64) (lastTimeStamp.mSampleTime + 0.5)); - info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); - - info.setFrameRate ([this] - { - switch (lastTimeStamp.mSMPTETime.mType) - { - case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); - case kSMPTETimeType24: return FrameRate().withBaseRate (24); - case kSMPTETimeType25: return FrameRate().withBaseRate (25); - case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); - case kSMPTETimeType30: return FrameRate().withBaseRate (30); - case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); - case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - case kSMPTETimeType60: return FrameRate().withBaseRate (60); - case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); - case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); - case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - case kSMPTETimeType50: return FrameRate().withBaseRate (50); - default: break; - } - - return FrameRate(); - }()); - - double num; - NSInteger den; - NSInteger outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat, bpm; - double ppqPosition; - - if (hostMusicalContextCallback != nullptr) - { - AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback; - - if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat)) - { - info.setTimeSignature (TimeSignature { (int) num, (int) den }); - info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); - info.setBpm (bpm); - info.setPpqPosition (ppqPosition); - } - } - - double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; - AUHostTransportStateFlags flags; - - if (hostTransportStateCallback != nullptr) - { - AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback; - - if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) - { - info.setTimeInSamples ((int64) (outCurrentSampleInTimeLine + 0.5)); - info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); - info.setIsPlaying ((flags & AUHostTransportStateMoving) != 0); - info.setIsLooping ((flags & AUHostTransportStateCycling) != 0); - info.setIsRecording ((flags & AUHostTransportStateRecording) != 0); - info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); - } - } - - if ((lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) - info.setHostTimeNs (timeConversions.hostTimeToNanos (lastTimeStamp.mHostTime)); - - return info; - } - - //============================================================================== - static void removeEditor (AudioProcessor& processor) - { - ScopedLock editorLock (processor.getCallbackLock()); - - if (AudioProcessorEditor* editor = processor.getActiveEditor()) - { - processor.editorBeingDeleted (editor); - delete editor; - } - } - - AUAudioUnit* getAudioUnit() const { return au; } - -private: - struct Class : public ObjCClass - { - Class() : ObjCClass ("AUAudioUnit_") - { - addIvar ("cppObject"); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (initWithComponentDescription:options:error:juceClass:), [] (id _self, - SEL, - AudioComponentDescription descr, - AudioComponentInstantiationOptions options, - NSError** error, - JuceAudioUnitv3* juceAU) - { - AUAudioUnit* self = _self; - - self = ObjCMsgSendSuper (self, @selector(initWithComponentDescription:options:error:), descr, options, error); - - setThis (self, juceAU); - return self; - }); - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - addMethod (@selector (initWithComponentDescription:options:error:), [] (id _self, - SEL, - AudioComponentDescription descr, - AudioComponentInstantiationOptions options, - NSError** error) - { - AUAudioUnit* self = _self; - - self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); - - auto* juceAU = JuceAudioUnitv3::create (self, descr, options, error); - - setThis (self, juceAU); - return self; - }); - - addMethod (@selector (dealloc), [] (id self, SEL) - { - if (! MessageManager::getInstance()->isThisTheMessageThread()) - { - WaitableEvent deletionEvent; - - struct AUDeleter : public CallbackMessage - { - AUDeleter (id selfToDelete, WaitableEvent& event) - : parentSelf (selfToDelete), parentDeletionEvent (event) - { - } - - void messageCallback() override - { - delete _this (parentSelf); - parentDeletionEvent.signal(); - } - - id parentSelf; - WaitableEvent& parentDeletionEvent; - }; - - (new AUDeleter (self, deletionEvent))->post(); - deletionEvent.wait (-1); - } - else - { - delete _this (self); - } - }); - - //============================================================================== - addMethod (@selector (reset), [] (id self, SEL) { return _this (self)->reset(); }); - - //============================================================================== - addMethod (@selector (currentPreset), [] (id self, SEL) { return _this (self)->getCurrentPreset(); }); - addMethod (@selector (setCurrentPreset:), [] (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); }); - addMethod (@selector (factoryPresets), [] (id self, SEL) { return _this (self)->getFactoryPresets(); }); - addMethod (@selector (fullState), [] (id self, SEL) { return _this (self)->getFullState(); }); - addMethod (@selector (setFullState:), [] (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); }); - addMethod (@selector (parameterTree), [] (id self, SEL) { return _this (self)->getParameterTree(); }); - addMethod (@selector (parametersForOverviewWithCount:), [] (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); }); - - //============================================================================== - addMethod (@selector (latency), [] (id self, SEL) { return _this (self)->getLatency(); }); - addMethod (@selector (tailTime), [] (id self, SEL) { return _this (self)->getTailTime(); }); - - //============================================================================== - addMethod (@selector (inputBusses), [] (id self, SEL) { return _this (self)->getInputBusses(); }); - addMethod (@selector (outputBusses), [] (id self, SEL) { return _this (self)->getOutputBusses(); }); - addMethod (@selector (channelCapabilities), [] (id self, SEL) { return _this (self)->getChannelCapabilities(); }); - addMethod (@selector (shouldChangeToFormat:forBus:), [] (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; }); - - //============================================================================== - addMethod (@selector (virtualMIDICableCount), [] (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); }); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (supportsMPE), [] (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; }); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - if (@available (macOS 10.13, iOS 11.0, *)) - addMethod (@selector (MIDIOutputNames), [] (id self, SEL) { return _this (self)->getMIDIOutputNames(); }); - - //============================================================================== - addMethod (@selector (internalRenderBlock), [] (id self, SEL) { return _this (self)->getInternalRenderBlock(); }); - addMethod (@selector (canProcessInPlace), [] (id, SEL) { return NO; }); - addMethod (@selector (isRenderingOffline), [] (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; }); - addMethod (@selector (setRenderingOffline:), [] (id self, SEL, BOOL renderingOffline) { return _this (self)->setRenderingOffline (renderingOffline); }); - addMethod (@selector (shouldBypassEffect), [] (id self, SEL) { return _this (self)->getShouldBypassEffect() ? YES : NO; }); - addMethod (@selector (setShouldBypassEffect:), [] (id self, SEL, BOOL shouldBypass) { return _this (self)->setShouldBypassEffect (shouldBypass); }); - addMethod (@selector (allocateRenderResourcesAndReturnError:), [] (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; }); - addMethod (@selector (deallocateRenderResources), [] (id self, SEL) { return _this (self)->deallocateRenderResources(); }); - - //============================================================================== - addMethod (@selector (contextName), [] (id self, SEL) { return _this (self)->getContextName(); }); - addMethod (@selector (setContextName:), [](id self, SEL, NSString* str) { return _this (self)->setContextName (str); }); - - //============================================================================== - if (@available (macOS 10.13, iOS 11.0, *)) - { - addMethod (@selector (supportedViewConfigurations:), [] (id self, SEL, NSArray* configs) - { - auto supportedViewIndices = [[NSMutableIndexSet alloc] init]; - auto n = [configs count]; - - if (auto* editor = _this (self)->getAudioProcessor().createEditorIfNeeded()) - { - // If you hit this assertion then your plug-in's editor is reporting that it doesn't support - // any host MIDI controller configurations! - jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); - - for (auto i = 0u; i < n; ++i) - { - if (auto viewConfiguration = [configs objectAtIndex: i]) - { - if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) - { - auto* constrainer = editor->getConstrainer(); - auto height = (int) [viewConfiguration height]; - auto width = (int) [viewConfiguration width]; - - const auto maxLimits = std::numeric_limits::max() / 2; - const Rectangle requestedBounds { width, height }; - auto modifiedBounds = requestedBounds; - constrainer->checkBounds (modifiedBounds, editor->getBounds().withZeroOrigin(), { maxLimits, maxLimits }, false, false, false, false); - - if (modifiedBounds == requestedBounds) - [supportedViewIndices addIndex: i]; - } - } - } - } - - return [supportedViewIndices autorelease]; - }); - - addMethod (@selector (selectViewConfiguration:), [] (id self, SEL, AUAudioUnitViewConfiguration* config) - { - _this (self)->processorHolder->viewConfiguration.reset (new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }); - }); - } - - registerClass(); - } - - //============================================================================== - static JuceAudioUnitv3* _this (id self) { return getIvar (self, "cppObject"); } - static void setThis (id self, JuceAudioUnitv3* cpp) { object_setInstanceVariable (self, "cppObject", cpp); } - }; - - static JuceAudioUnitv3* create (AUAudioUnit* audioUnit, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) - { - return new JuceAudioUnitv3 (audioUnit, descr, options, error); - } - - //============================================================================== - static Class& getClass() - { - static Class result; - return result; - } - - //============================================================================== - struct BusBuffer - { - BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) - : auBus (bus), - maxFrames (maxFramesPerBuffer), - numberOfChannels (static_cast ([[auBus format] channelCount])), - isInterleaved ([[auBus format] isInterleaved]) - { - alloc(); - } - - //============================================================================== - void alloc() - { - const int numBuffers = isInterleaved ? 1 : numberOfChannels; - int bytes = static_cast (sizeof (AudioBufferList)) - + ((numBuffers - 1) * static_cast (sizeof (::AudioBuffer))); - jassert (bytes > 0); - - bufferListStorage.calloc (static_cast (bytes)); - bufferList = reinterpret_cast (bufferListStorage.getData()); - - const int bufferChannels = isInterleaved ? numberOfChannels : 1; - scratchBuffer.setSize (numBuffers, bufferChannels * maxFrames); - } - - void dealloc() - { - bufferList = nullptr; - bufferListStorage.free(); - scratchBuffer.setSize (0, 0); - } - - //============================================================================== - int numChannels() const noexcept { return numberOfChannels; } - bool interleaved() const noexcept { return isInterleaved; } - AudioBufferList* get() const noexcept { return bufferList; } - - //============================================================================== - void prepare (UInt32 nFrames, const AudioBufferList* other = nullptr) noexcept - { - const int numBuffers = isInterleaved ? 1 : numberOfChannels; - const bool isCompatible = isCompatibleWith (other); - - bufferList->mNumberBuffers = static_cast (numBuffers); - - for (int i = 0; i < numBuffers; ++i) - { - const UInt32 bufferChannels = static_cast (isInterleaved ? numberOfChannels : 1); - bufferList->mBuffers[i].mNumberChannels = bufferChannels; - bufferList->mBuffers[i].mData = (isCompatible ? other->mBuffers[i].mData - : scratchBuffer.getWritePointer (i)); - bufferList->mBuffers[i].mDataByteSize = nFrames * bufferChannels * sizeof (float); - } - } - - //============================================================================== - bool isCompatibleWith (const AudioBufferList* other) const noexcept - { - if (other == nullptr) - return false; - - if (other->mNumberBuffers > 0) - { - const bool otherInterleaved = AudioUnitHelpers::isAudioBufferInterleaved (*other); - const int otherChannels = static_cast (otherInterleaved ? other->mBuffers[0].mNumberChannels - : other->mNumberBuffers); - - return otherInterleaved == isInterleaved - && numberOfChannels == otherChannels; - } - - return numberOfChannels == 0; - } - - private: - AUAudioUnitBus* auBus; - HeapBlock bufferListStorage; - AudioBufferList* bufferList = nullptr; - int maxFrames, numberOfChannels; - bool isInterleaved; - juce::AudioBuffer scratchBuffer; - }; - - class FactoryPresets - { - public: - using Presets = std::unique_ptr, NSObjectDeleter>; - - void set (Presets newPresets) - { - std::lock_guard lock (mutex); - std::swap (presets, newPresets); - } - - NSArray* get() const - { - std::lock_guard lock (mutex); - return presets.get(); - } - - AUAudioUnitPreset* getAtIndex (int index) const - { - std::lock_guard lock (mutex); - - if (index < (int) [presets.get() count]) - return [presets.get() objectAtIndex: (unsigned int) index]; - - return nullptr; - } - - private: - Presets presets; - mutable std::mutex mutex; - }; - - //============================================================================== - void addAudioUnitBusses (bool isInput) - { - std::unique_ptr, NSObjectDeleter> array ([[NSMutableArray alloc] init]); - AudioProcessor& processor = getAudioProcessor(); - const auto numWrapperBuses = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); - const auto numProcessorBuses = AudioUnitHelpers::getBusCount (processor, isInput); - - for (int i = 0; i < numWrapperBuses; ++i) - { - using AVAudioFormatPtr = std::unique_ptr; - - const auto audioFormat = [&]() -> AVAudioFormatPtr - { - const auto tag = i < numProcessorBuses ? CoreAudioLayouts::toCoreAudio (processor.getChannelLayoutOfBus (isInput, i)) - : kAudioChannelLayoutTag_Stereo; - const std::unique_ptr layout { [[AVAudioChannelLayout alloc] initWithLayoutTag: tag] }; - - if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate - channelLayout: layout.get()] }) - return format; - - const auto channels = i < numProcessorBuses ? processor.getChannelCountOfBus (isInput, i) - : 2; - - // According to the docs, this will fail if the number of channels is greater than 2. - if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate - channels: static_cast (channels)] }) - return format; - - jassertfalse; - return nullptr; - }(); - - using AUAudioUnitBusPtr = std::unique_ptr; - - const auto audioUnitBus = [&]() -> AUAudioUnitBusPtr - { - if (audioFormat != nullptr) - return AUAudioUnitBusPtr { [[AUAudioUnitBus alloc] initWithFormat: audioFormat.get() error: nullptr] }; - - jassertfalse; - return nullptr; - }(); - - if (audioUnitBus != nullptr) - [array.get() addObject: audioUnitBus.get()]; - } - - (isInput ? inputBusses : outputBusses).reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit: au - busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput) - busses: array.get()]); - } - - // When parameters are discrete we need to use integer values. - static float getMaximumParameterValue ([[maybe_unused]] const AudioProcessorParameter& juceParam) - { - #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - return 1.0f; - #else - return juceParam.isDiscrete() ? (float) (juceParam.getNumSteps() - 1) : 1.0f; - #endif - } - - static auto createParameter (const AudioProcessorParameter& parameter) - { - const String name (parameter.getName (512)); - - AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; - AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable - | kAudioUnitParameterFlag_IsReadable - | kAudioUnitParameterFlag_HasCFNameString - | kAudioUnitParameterFlag_ValuesHaveStrings); - - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; - #endif - - // Set whether the param is automatable (unnamed parameters aren't allowed to be automated). - if (name.isEmpty() || ! parameter.isAutomatable()) - flags |= kAudioUnitParameterFlag_NonRealTime; - - const bool isParameterDiscrete = parameter.isDiscrete(); - - if (! isParameterDiscrete) - flags |= kAudioUnitParameterFlag_CanRamp; - - if (parameter.isMetaParameter()) - flags |= kAudioUnitParameterFlag_IsGlobalMeta; - - std::unique_ptr valueStrings; - - // Is this a meter? - if (((parameter.getCategory() & 0xffff0000) >> 16) == 2) - { - flags &= ~kAudioUnitParameterFlag_IsWritable; - flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; - unit = kAudioUnitParameterUnit_LinearGain; - } - else - { - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - if (parameter.isDiscrete()) - { - unit = parameter.isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; - auto maxValue = getMaximumParameterValue (parameter); - auto numSteps = parameter.getNumSteps(); - - // Some hosts can't handle the huge numbers of discrete parameter values created when - // using the default number of steps. - jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); - - valueStrings.reset ([NSMutableArray new]); - - for (int i = 0; i < numSteps; ++i) - [valueStrings.get() addObject: juceStringToNS (parameter.getText ((float) i / maxValue, 0))]; - } - #endif - } - - const auto address = generateAUParameterAddress (parameter); - - auto getParameterIdentifier = [¶meter] - { - if (const auto* paramWithID = dynamic_cast (¶meter)) - return paramWithID->paramID; - - // This could clash if any groups have been given integer IDs! - return String (parameter.getParameterIndex()); - }; - - std::unique_ptr param; - - @try - { - // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h - param.reset([[AUParameterTree createParameterWithIdentifier: juceStringToNS (getParameterIdentifier()) - name: juceStringToNS (name) - address: address - min: 0.0f - max: getMaximumParameterValue (parameter) - unit: unit - unitName: nullptr - flags: flags - valueStrings: valueStrings.get() - dependentParameters: nullptr] - retain]); - } - - @catch (NSException* exception) - { - // Do you have duplicate identifiers in any of your groups or parameters, - // or do your identifiers have unusual characters in them? - jassertfalse; - } - - [param.get() setValue: parameter.getDefaultValue()]; - return param; - } - - struct NodeArrayResult - { - std::unique_ptr, NSObjectDeleter> nodeArray { [NSMutableArray new] }; - - void addParameter (const AudioProcessorParameter&, std::unique_ptr auParam) - { - [nodeArray.get() addObject: [auParam.get() retain]]; - } - - void addGroup (const AudioProcessorParameterGroup& group, const NodeArrayResult& r) - { - @try - { - // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h - [nodeArray.get() addObject: [[AUParameterTree createGroupWithIdentifier: juceStringToNS (group.getID()) - name: juceStringToNS (group.getName()) - children: r.nodeArray.get()] retain]]; - } - @catch (NSException* exception) - { - // Do you have duplicate identifiers in any of your groups or parameters, - // or do your identifiers have unusual characters in them? - jassertfalse; - } - } - }; - - struct AddressedNodeArrayResult - { - NodeArrayResult nodeArray; - std::map addressForIndex; - - void addParameter (const AudioProcessorParameter& juceParam, std::unique_ptr auParam) - { - const auto index = juceParam.getParameterIndex(); - const auto address = [auParam.get() address]; - - if (const auto iter = addressForIndex.find (index); iter == addressForIndex.cend()) - addressForIndex.emplace (index, address); - else - jassertfalse; // If you hit this assertion then you have put a parameter in two groups. - - nodeArray.addParameter (juceParam, std::move (auParam)); - } - - void addGroup (const AudioProcessorParameterGroup& group, const AddressedNodeArrayResult& r) - { - nodeArray.addGroup (group, r.nodeArray); - - [[maybe_unused]] const auto initialSize = addressForIndex.size(); - addressForIndex.insert (r.addressForIndex.begin(), r.addressForIndex.end()); - [[maybe_unused]] const auto finalSize = addressForIndex.size(); - - // If this is hit, the same parameter index exists in multiple groups. - jassert (finalSize == initialSize + r.addressForIndex.size()); - } - }; - - template - static Result createParameterNodes (const AudioProcessorParameterGroup& group) - { - Result result; - - for (auto* node : group) - { - if (auto* childGroup = node->getGroup()) - { - result.addGroup (*childGroup, createParameterNodes (*childGroup)); - } - else if (auto* juceParam = node->getParameter()) - { - result.addParameter (*juceParam, createParameter (*juceParam)); - } - else - { - // No group or parameter at this node! - jassertfalse; - } - } - - return result; - } - - void addParameters() - { - auto& processor = getAudioProcessor(); - juceParameters.update (processor, forceLegacyParamIDs); - - if ((bypassParam = processor.getBypassParameter()) != nullptr) - bypassParam->addListener (this); - - auto nodes = createParameterNodes (processor.getParameterTree()); - installNewParameterTree (std::move (nodes.nodeArray.nodeArray)); - - // When we first create the parameter tree, we also create structures to allow lookup by index/address. - // These structures are not rebuilt, i.e. we assume that the parameter addresses and indices are stable. - // These structures aren't modified after creation, so there should be no need to synchronize access to them. - - addressForIndex = [&] - { - std::vector addresses (static_cast (processor.getParameters().size())); - - for (size_t i = 0; i < addresses.size(); ++i) - { - if (const auto iter = nodes.addressForIndex.find (static_cast (i)); iter != nodes.addressForIndex.cend()) - addresses[i] = iter->second; - else - jassertfalse; // Somehow, there's a parameter missing... - } - - return addresses; - }(); - - #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS - indexForAddress = [&] - { - std::map indices; - - for (const auto& [index, address] : nodes.addressForIndex) - { - if (const auto iter = indices.find (address); iter == indices.cend()) - indices.emplace (address, index); - else - jassertfalse; // The parameter at index 'iter->first' has the same address as the parameter at index 'index' - } - - return indices; - }(); - #endif - } - - void installNewParameterTree (std::unique_ptr, NSObjectDeleter> topLevelNodes) - { - editorObserverToken.reset(); - - @try - { - // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h - paramTree.reset ([[AUParameterTree createTreeWithChildren: topLevelNodes.get()] retain]); - } - @catch (NSException* exception) - { - // Do you have duplicate identifiers in any of your groups or parameters, - // or do your identifiers have unusual characters in them? - jassertfalse; - } - - [paramTree.get() setImplementorValueObserver: ^(AUParameter* param, AUValue value) { this->valueChangedFromHost (param, value); }]; - [paramTree.get() setImplementorValueProvider: ^(AUParameter* param) { return this->getValue (param); }]; - [paramTree.get() setImplementorStringFromValueCallback: ^(AUParameter* param, const AUValue* value) { return this->stringFromValue (param, value); }]; - [paramTree.get() setImplementorValueFromStringCallback: ^(AUParameter* param, NSString* str) { return this->valueFromString (param, str); }]; - - if (getAudioProcessor().hasEditor()) - { - editorObserverToken = ObserverPtr ([paramTree.get() tokenByAddingParameterObserver: ^(AUParameterAddress, AUValue) - { - // this will have already been handled by valueChangedFromHost - }], - ObserverDestructor { paramTree.get() }); - } - } - - void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) - { - if (value != juceParam->getValue()) - { - juceParam->setValue (value); - - inParameterChangedCallback = true; - juceParam->sendValueChangedMessageToListeners (value); - } - } - - void addPresets() - { - FactoryPresets::Presets newPresets { [[NSMutableArray alloc] init] }; - - const int n = getAudioProcessor().getNumPrograms(); - - for (int idx = 0; idx < n; ++idx) - { - String name = getAudioProcessor().getProgramName (idx); - - std::unique_ptr preset ([[AUAudioUnitPreset alloc] init]); - [preset.get() setName: juceStringToNS (name)]; - [preset.get() setNumber: static_cast (idx)]; - - [newPresets.get() addObject: preset.get()]; - } - - factoryPresets.set (std::move (newPresets)); - } - - //============================================================================== - void allocateBusBuffer (bool isInput) - { - OwnedArray& busBuffers = isInput ? inBusBuffers : outBusBuffers; - busBuffers.clear(); - - const int n = AudioUnitHelpers::getBusCountForWrapper (getAudioProcessor(), isInput); - const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - - for (int busIdx = 0; busIdx < n; ++busIdx) - busBuffers.add (new BusBuffer ([(isInput ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: static_cast (busIdx)], - static_cast (maxFrames))); - } - - //============================================================================== - void processEvents (const AURenderEvent *__nullable realtimeEventListHead, [[maybe_unused]] int numParams, AUEventSampleTime startTime) - { - for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) - { - switch (event->head.eventType) - { - case AURenderEventMIDI: - { - const AUMIDIEvent& midiEvent = event->MIDI; - midiMessages.addEvent (midiEvent.data, midiEvent.length, static_cast (midiEvent.eventSampleTime - startTime)); - } - break; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - case AURenderEventMIDIEventList: - { - const auto& list = event->MIDIEventsList.eventList; - auto* packet = &list.packet[0]; - - for (uint32_t i = 0; i < list.numPackets; ++i) - { - converter.dispatch (reinterpret_cast (packet->words), - reinterpret_cast (packet->words + packet->wordCount), - static_cast (packet->timeStamp - (MIDITimeStamp) startTime), - [this] (const ump::BytestreamMidiView& message) - { - midiMessages.addEvent (message.getMessage(), (int) message.timestamp); - }); - - packet = MIDIEventPacketNext (packet); - } - } - break; - #endif - - case AURenderEventParameter: - case AURenderEventParameterRamp: - { - const AUParameterEvent& paramEvent = event->parameter; - - if (auto* p = getJuceParameterForAUAddress (paramEvent.parameterAddress)) - { - auto normalisedValue = paramEvent.value / getMaximumParameterValue (*p); - setAudioProcessorParameter (p, normalisedValue); - } - } - break; - - case AURenderEventMIDISysEx: - default: - break; - } - } - } - - AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount, - NSInteger outputBusNumber, AudioBufferList* outputData, const AURenderEvent *__nullable realtimeEventListHead, - AURenderPullInputBlock __nullable pullInputBlock) - { - auto& processor = getAudioProcessor(); - jassert (static_cast (frameCount) <= getAudioProcessor().getBlockSize()); - - const auto numProcessorBusesOut = AudioUnitHelpers::getBusCount (processor, false); - - if (lastTimeStamp.mSampleTime != timestamp->mSampleTime) - { - // process params and incoming midi (only once for a given timestamp) - midiMessages.clear(); - - const int numParams = juceParameters.getNumParameters(); - processEvents (realtimeEventListHead, numParams, static_cast (timestamp->mSampleTime)); - - lastTimeStamp = *timestamp; - - const auto numWrapperBusesIn = AudioUnitHelpers::getBusCountForWrapper (processor, true); - const auto numWrapperBusesOut = AudioUnitHelpers::getBusCountForWrapper (processor, false); - const auto numProcessorBusesIn = AudioUnitHelpers::getBusCount (processor, true); - - // prepare buffers - { - for (int busIdx = 0; busIdx < numWrapperBusesOut; ++busIdx) - { - BusBuffer& busBuffer = *outBusBuffers[busIdx]; - const bool canUseDirectOutput = - (busIdx == outputBusNumber && outputData != nullptr && outputData->mNumberBuffers > 0); - - busBuffer.prepare (frameCount, canUseDirectOutput ? outputData : nullptr); - - if (numProcessorBusesOut <= busIdx) - AudioUnitHelpers::clearAudioBuffer (*busBuffer.get()); - } - - for (int busIdx = 0; busIdx < numWrapperBusesIn; ++busIdx) - { - BusBuffer& busBuffer = *inBusBuffers[busIdx]; - busBuffer.prepare (frameCount, busIdx < numWrapperBusesOut ? outBusBuffers[busIdx]->get() : nullptr); - } - - audioBuffer.reset(); - } - - // pull inputs - { - for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) - { - BusBuffer& busBuffer = *inBusBuffers[busIdx]; - AudioBufferList* buffer = busBuffer.get(); - - if (pullInputBlock == nullptr || pullInputBlock (actionFlags, timestamp, frameCount, busIdx, buffer) != noErr) - AudioUnitHelpers::clearAudioBuffer (*buffer); - - if (actionFlags != nullptr && (*actionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0) - AudioUnitHelpers::clearAudioBuffer (*buffer); - } - } - - // set buffer pointer to minimize copying - { - int chIdx = 0; - - for (int busIdx = 0; busIdx < numProcessorBusesOut; ++busIdx) - { - BusBuffer& busBuffer = *outBusBuffers[busIdx]; - AudioBufferList* buffer = busBuffer.get(); - - const bool interleaved = busBuffer.interleaved(); - const int numChannels = busBuffer.numChannels(); - - const int* outLayoutMap = mapper.get (false, busIdx); - - for (int ch = 0; ch < numChannels; ++ch) - audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); - } - - // use input pointers on remaining channels - - for (int busIdx = 0; chIdx < totalInChannels;) - { - const int channelOffset = processor.getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); - - BusBuffer& busBuffer = *inBusBuffers[busIdx]; - AudioBufferList* buffer = busBuffer.get(); - - const int* inLayoutMap = mapper.get (true, busIdx); - audioBuffer.setBuffer (chIdx++, busBuffer.interleaved() ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[channelOffset]].mData)); - } - } - - // copy input - { - for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) - audioBuffer.set (busIdx, *inBusBuffers[busIdx]->get(), mapper.get (true, busIdx)); - - audioBuffer.clearUnusedChannels ((int) frameCount); - } - - // process audio - processBlock (audioBuffer.getBuffer (frameCount), midiMessages); - - // send MIDI - #if JucePlugin_ProducesMidiOutput - if (@available (macOS 10.13, iOS 11.0, *)) - { - if (auto midiOut = midiOutputEventBlock) - for (const auto metadata : midiMessages) - if (isPositiveAndBelow (metadata.samplePosition, frameCount)) - midiOut ((int64_t) metadata.samplePosition + (int64_t) (timestamp->mSampleTime + 0.5), - 0, - metadata.numBytes, - metadata.data); - } - #endif - } - - // copy back - if (outputBusNumber < numProcessorBusesOut && outputData != nullptr) - audioBuffer.get ((int) outputBusNumber, *outputData, mapper.get (false, (int) outputBusNumber)); - - return noErr; - } - - void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept - { - auto& processor = getAudioProcessor(); - const ScopedLock sl (processor.getCallbackLock()); - - if (processor.isSuspended()) - buffer.clear(); - else if (bypassParam == nullptr && [au shouldBypassEffect]) - processor.processBlockBypassed (buffer, midiBuffer); - else - processor.processBlock (buffer, midiBuffer); - } - - //============================================================================== - void valueChangedFromHost (AUParameter* param, AUValue value) - { - if (param != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - { - auto normalisedValue = value / getMaximumParameterValue (*p); - setAudioProcessorParameter (p, normalisedValue); - } - } - } - - AUValue getValue (AUParameter* param) const - { - if (param != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - return p->getValue() * getMaximumParameterValue (*p); - } - - return 0; - } - - NSString* stringFromValue (AUParameter* param, const AUValue* value) - { - String text; - - if (param != nullptr && value != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - { - if (LegacyAudioParameter::isLegacy (p)) - text = String (*value); - else - text = p->getText (*value / getMaximumParameterValue (*p), 0); - } - } - - return juceStringToNS (text); - } - - AUValue valueFromString (AUParameter* param, NSString* str) - { - if (param != nullptr && str != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - { - const String text (nsStringToJuce (str)); - - if (LegacyAudioParameter::isLegacy (p)) - return text.getFloatValue(); - - return p->getValueForText (text) * getMaximumParameterValue (*p); - } - } - - return 0; - } - - //============================================================================== - // this is only ever called for the bypass parameter - void parameterValueChanged (int, float newValue) override - { - JuceAudioUnitv3::setShouldBypassEffect (newValue != 0.0f); - } - - void parameterGestureChanged (int, bool) override {} - - //============================================================================== - inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept - { - if (isPositiveAndBelow (paramIndex, addressForIndex.size())) - return addressForIndex[static_cast (paramIndex)]; - - return {}; - } - - inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept - { - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - return static_cast (address); - #else - if (const auto iter = indexForAddress.find (address); iter != indexForAddress.cend()) - return iter->second; - - return {}; - #endif - } - - static AUParameterAddress generateAUParameterAddress (const AudioProcessorParameter& param) - { - const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceLegacyParamIDs); - - return static_cast (forceLegacyParamIDs ? juceParamID.getIntValue() - : juceParamID.hashCode64()); - } - - AudioProcessorParameter* getJuceParameterForAUAddress (AUParameterAddress address) const noexcept - { - return juceParameters.getParamForIndex (getJuceParameterIndexForAUAddress (address)); - } - - //============================================================================== - static constexpr double kDefaultSampleRate = 44100.0; - - struct ObserverDestructor - { - void operator() (AUParameterObserverToken ptr) const - { - if (ptr != nullptr) - [tree removeParameterObserver: ptr]; - } - - AUParameterTree* tree; - }; - - using ObserverPtr = std::unique_ptr, ObserverDestructor>; - - AUAudioUnit* au; - AudioProcessorHolder::Ptr processorHolder; - - int totalInChannels, totalOutChannels; - - CoreAudioTimeConversions timeConversions; - std::unique_ptr inputBusses, outputBusses; - - #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS - std::map indexForAddress; - #endif - std::vector addressForIndex; - LegacyAudioParametersWrapper juceParameters; - - // to avoid recursion on parameter changes, we need to add an - // editor observer to do the parameter changes - std::unique_ptr paramTree; - ObserverPtr editorObserverToken; - - std::unique_ptr, NSObjectDeleter> channelCapabilities; - - FactoryPresets factoryPresets; - - ObjCBlock internalRenderBlock; - - AudioUnitHelpers::CoreAudioBufferList audioBuffer; - AudioUnitHelpers::ChannelRemapper mapper; - - OwnedArray inBusBuffers, outBusBuffers; - MidiBuffer midiMessages; - AUMIDIOutputEventBlock midiOutputEventBlock = nullptr; - - #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED - ump::ToBytestreamDispatcher converter { 2048 }; - #endif - - ObjCBlock hostMusicalContextCallback; - ObjCBlock hostTransportStateCallback; - - AudioTimeStamp lastTimeStamp; - - String contextName; - - ThreadLocalValue inParameterChangedCallback; - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - static constexpr bool forceLegacyParamIDs = true; - #else - static constexpr bool forceLegacyParamIDs = false; - #endif - AudioProcessorParameter* bypassParam = nullptr; -}; - -#if JUCE_IOS -namespace juce -{ -struct UIViewPeerControllerReceiver -{ - virtual ~UIViewPeerControllerReceiver(); - virtual void setViewController (UIViewController*) = 0; -}; -} -#endif - -//============================================================================== -class JuceAUViewController -{ -public: - JuceAUViewController (AUViewController* p) - : myself (p) - { - initialiseJuce_GUI(); - } - - ~JuceAUViewController() - { - JUCE_ASSERT_MESSAGE_THREAD - - if (processorHolder.get() != nullptr) - JuceAudioUnitv3::removeEditor (getAudioProcessor()); - } - - //============================================================================== - void loadView() - { - JUCE_ASSERT_MESSAGE_THREAD - - if (auto p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3)) - { - processorHolder = new AudioProcessorHolder (std::move (p)); - auto& processor = getAudioProcessor(); - - if (processor.hasEditor()) - { - if (AudioProcessorEditor* editor = processor.createEditorIfNeeded()) - { - preferredSize = editor->getBounds(); - - JUCE_IOS_MAC_VIEW* view = [[[JUCE_IOS_MAC_VIEW alloc] initWithFrame: convertToCGRect (editor->getBounds())] autorelease]; - [myself setView: view]; - - #if JUCE_IOS - editor->setVisible (false); - #else - editor->setVisible (true); - #endif - - detail::PluginUtilities::addToDesktop (*editor, view); - - #if JUCE_IOS - if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) - [peerView setContentMode: UIViewContentModeTop]; - - if (auto* peer = dynamic_cast (editor->getPeer())) - peer->setViewController (myself); - #endif - } - } - } - } - - void viewDidLayoutSubviews() - { - if (auto holder = processorHolder.get()) - { - if ([myself view] != nullptr) - { - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) - { - if (holder->viewConfiguration != nullptr) - editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); - - editor->setBounds (convertToRectInt ([[myself view] bounds])); - - if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) - { - #if JUCE_IOS - [peerView setNeedsDisplay]; - #else - [peerView setNeedsDisplay: YES]; - #endif - } - } - } - } - } - - void didReceiveMemoryWarning() - { - if (auto ptr = processorHolder.get()) - if (auto* processor = ptr->get()) - processor->memoryWarningReceived(); - } - - void viewDidAppear (bool) - { - if (processorHolder.get() != nullptr) - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) - editor->setVisible (true); - } - - void viewDidDisappear (bool) - { - if (processorHolder.get() != nullptr) - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) - editor->setVisible (false); - } - - CGSize getPreferredContentSize() const - { - return CGSizeMake (static_cast (preferredSize.getWidth()), - static_cast (preferredSize.getHeight())); - } - - //============================================================================== - AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) - { - const auto holder = [&] - { - if (auto initialisedHolder = processorHolder.get()) - return initialisedHolder; - - waitForExecutionOnMainThread ([this] { [myself view]; }); - return processorHolder.get(); - }(); - - if (holder == nullptr) - return nullptr; - - return [(new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit() autorelease]; - } - -private: - template - static void waitForExecutionOnMainThread (Callback&& callback) - { - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - callback(); - return; - } - - std::promise promise; - - MessageManager::callAsync ([&] - { - callback(); - promise.set_value(); - }); - - promise.get_future().get(); - } - - // There's a chance that createAudioUnit will be called from a background - // thread while the processorHolder is being updated on the main thread. - class LockedProcessorHolder - { - public: - AudioProcessorHolder::Ptr get() const - { - const ScopedLock lock (mutex); - return holder; - } - - LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) - { - const ScopedLock lock (mutex); - holder = other; - return *this; - } - - private: - mutable CriticalSection mutex; - AudioProcessorHolder::Ptr holder; - }; - - //============================================================================== - AUViewController* myself; - LockedProcessorHolder processorHolder; - Rectangle preferredSize { 1, 1 }; - - //============================================================================== - AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } -}; - -//============================================================================== -// necessary glue code -@interface JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) : AUViewController -@end - -@implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) -{ - std::unique_ptr cpp; -} - -- (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp.reset (new JuceAUViewController (self)); return self; } -- (void) loadView { cpp->loadView(); } -- (AUAudioUnit *) createAudioUnitWithComponentDescription: (AudioComponentDescription) desc error: (NSError **) error { return cpp->createAudioUnit (desc, error); } -- (CGSize) preferredContentSize { return cpp->getPreferredContentSize(); } - -// NSViewController and UIViewController have slightly different names for this function -- (void) viewDidLayoutSubviews { cpp->viewDidLayoutSubviews(); } -- (void) viewDidLayout { cpp->viewDidLayoutSubviews(); } - -- (void) didReceiveMemoryWarning { cpp->didReceiveMemoryWarning(); } -#if JUCE_IOS -- (void) viewDidAppear: (BOOL) animated { cpp->viewDidAppear (animated); [super viewDidAppear:animated]; } -- (void) viewDidDisappear: (BOOL) animated { cpp->viewDidDisappear (animated); [super viewDidDisappear:animated]; } -#endif -@end - -//============================================================================== -#if JUCE_IOS -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -bool JUCE_CALLTYPE juce_isInterAppAudioConnected() { return false; } -void JUCE_CALLTYPE juce_switchToHostApplication() {} -Image JUCE_CALLTYPE juce_getIAAHostIcon (int) { return {}; } - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif diff --git a/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp b/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp deleted file mode 100644 index 04f1efe7f1..0000000000 --- a/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp +++ /dev/null @@ -1,1815 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#if JucePlugin_Build_LV2 && (! (JUCE_ANDROID || JUCE_IOS)) - -#ifndef _SCL_SECURE_NO_WARNINGS - #define _SCL_SECURE_NO_WARNINGS -#endif - -#ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS -#endif - -#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 - -#include -#include -#include -#include - -#include -#include - -#include "JuceLV2Defines.h" -#include - -#include - -#define JUCE_TURTLE_RECALL_URI "https://lv2-extensions.juce.com/turtle_recall" - -#ifndef JucePlugin_LV2URI - #error "You need to define the JucePlugin_LV2URI value! If you're using the Projucer/CMake, the definition will be written into JuceLV2Defines.h automatically." -#endif - -namespace juce -{ -namespace lv2_client -{ - -constexpr auto uriSeparator = ":"; -const auto JucePluginLV2UriUi = String (JucePlugin_LV2URI) + uriSeparator + "UI"; -const auto JucePluginLV2UriState = String (JucePlugin_LV2URI) + uriSeparator + "StateString"; -const auto JucePluginLV2UriProgram = String (JucePlugin_LV2URI) + uriSeparator + "Program"; - -static const LV2_Feature* findMatchingFeature (const LV2_Feature* const* features, const char* uri) -{ - for (auto feature = features; *feature != nullptr; ++feature) - if (std::strcmp ((*feature)->URI, uri) == 0) - return *feature; - - return nullptr; -} - -static bool hasFeature (const LV2_Feature* const* features, const char* uri) -{ - return findMatchingFeature (features, uri) != nullptr; -} - -template -Data findMatchingFeatureData (const LV2_Feature* const* features, const char* uri) -{ - if (const auto* feature = findMatchingFeature (features, uri)) - return static_cast (feature->data); - - return {}; -} - -static const LV2_Options_Option* findMatchingOption (const LV2_Options_Option* options, LV2_URID urid) -{ - for (auto option = options; option->value != nullptr; ++option) - if (option->key == urid) - return option; - - return nullptr; -} - -class ParameterStorage : private AudioProcessorListener -{ -public: - ParameterStorage (AudioProcessor& proc, LV2_URID_Map map) - : processor (proc), - mapFeature (map), - legacyParameters (proc, false) - { - processor.addListener (this); - } - - ~ParameterStorage() override - { - processor.removeListener (this); - } - - /* This is the string that will be used to uniquely identify the parameter. - - This string will be written into the plugin's manifest as an IRI, so it must be - syntactically valid. - - We escape this string rather than writing the user-defined parameter ID directly to avoid - writing a malformed manifest in the case that user IDs contain spaces or other reserved - characters. This should allow users to keep the same param IDs for all plugin formats. - */ - static String getIri (const AudioProcessorParameter& param) - { - const auto urlSanitised = URL::addEscapeChars (LegacyAudioParameter::getParamID (¶m, false), true); - const auto ttlSanitised = lv2_shared::sanitiseStringAsTtlName (urlSanitised); - - // If this is hit, the parameter ID could not be represented directly in the plugin ttl. - // We'll replace offending characters with '_'. - jassert (urlSanitised == ttlSanitised); - - return ttlSanitised; - } - - void setValueFromHost (LV2_URID urid, float value) noexcept - { - const auto it = uridToIndexMap.find (urid); - - if (it == uridToIndexMap.end()) - { - // No such parameter. - jassertfalse; - return; - } - - if (auto* param = legacyParameters.getParamForIndex ((int) it->second)) - { - const auto scaledValue = [&] - { - if (auto* rangedParam = dynamic_cast (param)) - return rangedParam->convertTo0to1 (value); - - return value; - }(); - - if (scaledValue != param->getValue()) - { - ScopedValueSetter scope (ignoreCallbacks, true); - param->setValueNotifyingHost (scaledValue); - } - } - } - - struct Options - { - bool parameterValue, gestureBegin, gestureEnd; - }; - - static constexpr auto newClientValue = 1 << 0, - gestureBegan = 1 << 1, - gestureEnded = 1 << 2; - - template - void forEachChangedParameter (Callback&& callback) - { - stateCache.ifSet ([this, &callback] (size_t parameterIndex, float, uint32_t bits) - { - const Options options { (bits & newClientValue) != 0, - (bits & gestureBegan) != 0, - (bits & gestureEnded) != 0 }; - - callback (*legacyParameters.getParamForIndex ((int) parameterIndex), - indexToUridMap[parameterIndex], - options); - }); - } - -private: - void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float value) override - { - if (! ignoreCallbacks) - stateCache.setValueAndBits ((size_t) parameterIndex, value, newClientValue); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override - { - if (! ignoreCallbacks) - stateCache.setBits ((size_t) parameterIndex, gestureBegan); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override - { - if (! ignoreCallbacks) - stateCache.setBits ((size_t) parameterIndex, gestureEnded); - } - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} - - AudioProcessor& processor; - const LV2_URID_Map mapFeature; - const LegacyAudioParametersWrapper legacyParameters; - const std::vector indexToUridMap = [&] - { - std::vector result; - - for (auto* param : legacyParameters) - { - jassert ((size_t) param->getParameterIndex() == result.size()); - - const auto uri = JucePlugin_LV2URI + String (uriSeparator) + getIri (*param); - const auto urid = mapFeature.map (mapFeature.handle, uri.toRawUTF8()); - result.push_back (urid); - } - - // If this is hit, some parameters have duplicate IDs. - // This may be because the IDs resolve to the same string when removing characters that - // are invalid in a TTL name. - jassert (std::set (result.begin(), result.end()).size() == result.size()); - - return result; - }(); - const std::map uridToIndexMap = [&] - { - std::map result; - size_t index = 0; - - for (const auto& urid : indexToUridMap) - result.emplace (urid, index++); - - return result; - }(); - FlaggedFloatCache<3> stateCache { (size_t) legacyParameters.getNumParameters() }; - bool ignoreCallbacks = false; - - JUCE_LEAK_DETECTOR (ParameterStorage) -}; - -enum class PortKind { seqInput, seqOutput, latencyOutput, freeWheelingInput, enabledInput }; - -struct PortIndices -{ - PortIndices (int numInputsIn, int numOutputsIn) - : numInputs (numInputsIn), numOutputs (numOutputsIn) {} - - int getPortIndexForAudioInput (int audioIndex) const noexcept - { - return audioIndex; - } - - int getPortIndexForAudioOutput (int audioIndex) const noexcept - { - return audioIndex + numInputs; - } - - int getPortIndexFor (PortKind p) const noexcept { return getMaxAudioPortIndex() + (int) p; } - - // Audio ports are numbered from 0 to numInputs + numOutputs - int getMaxAudioPortIndex() const noexcept { return numInputs + numOutputs; } - - int numInputs, numOutputs; -}; - -//============================================================================== -class PlayHead : public AudioPlayHead -{ -public: - PlayHead (LV2_URID_Map mapFeatureIn, double sampleRateIn) - : parser (mapFeatureIn), sampleRate (sampleRateIn) - { - } - - void invalidate() { info = nullopt; } - - void readNewInfo (const LV2_Atom_Event* event) - { - if (event->body.type != mLV2_ATOM__Object && event->body.type != mLV2_ATOM__Blank) - return; - - const auto* object = reinterpret_cast (&event->body); - - if (object->body.otype != mLV2_TIME__Position) - return; - - const LV2_Atom* atomFrame = nullptr; - const LV2_Atom* atomSpeed = nullptr; - const LV2_Atom* atomBar = nullptr; - const LV2_Atom* atomBeat = nullptr; - const LV2_Atom* atomBeatUnit = nullptr; - const LV2_Atom* atomBeatsPerBar = nullptr; - const LV2_Atom* atomBeatsPerMinute = nullptr; - - LV2_Atom_Object_Query query[] { { mLV2_TIME__frame, &atomFrame }, - { mLV2_TIME__speed, &atomSpeed }, - { mLV2_TIME__bar, &atomBar }, - { mLV2_TIME__beat, &atomBeat }, - { mLV2_TIME__beatUnit, &atomBeatUnit }, - { mLV2_TIME__beatsPerBar, &atomBeatsPerBar }, - { mLV2_TIME__beatsPerMinute, &atomBeatsPerMinute }, - LV2_ATOM_OBJECT_QUERY_END }; - - lv2_atom_object_query (object, query); - - info.emplace(); - - // Carla always seems to give us an integral 'beat' even though I'd expect - // it to be a floating-point value - - const auto numerator = parser.parseNumericAtom (atomBeatsPerBar); - const auto denominator = parser.parseNumericAtom (atomBeatUnit); - - if (numerator.hasValue() && denominator.hasValue()) - info->setTimeSignature (TimeSignature { (int) *numerator, (int) *denominator }); - - info->setBpm (parser.parseNumericAtom (atomBeatsPerMinute)); - info->setPpqPosition (parser.parseNumericAtom (atomBeat)); - info->setIsPlaying (parser.parseNumericAtom (atomSpeed).orFallback (0.0f) != 0.0f); - info->setBarCount (parser.parseNumericAtom (atomBar)); - - if (const auto parsed = parser.parseNumericAtom (atomFrame)) - { - info->setTimeInSamples (*parsed); - info->setTimeInSeconds ((double) *parsed / sampleRate); - } - } - - Optional getPosition() const override - { - return info; - } - -private: - lv2_shared::NumericAtomParser parser; - Optional info; - double sampleRate; - - #define X(str) const LV2_URID m##str = parser.map (str); - X (LV2_ATOM__Blank) - X (LV2_ATOM__Object) - X (LV2_TIME__Position) - X (LV2_TIME__beat) - X (LV2_TIME__beatUnit) - X (LV2_TIME__beatsPerBar) - X (LV2_TIME__beatsPerMinute) - X (LV2_TIME__frame) - X (LV2_TIME__speed) - X (LV2_TIME__bar) - #undef X - - JUCE_LEAK_DETECTOR (PlayHead) -}; - -//============================================================================== -class Ports -{ -public: - Ports (LV2_URID_Map map, int numInputsIn, int numOutputsIn) - : forge (map), - indices (numInputsIn, numOutputsIn), - mLV2_ATOM__Sequence (map.map (map.handle, LV2_ATOM__Sequence)) - { - audioBuffers.resize (static_cast (numInputsIn + numOutputsIn), nullptr); - } - - void connect (int port, void* data) - { - // The following is not UB _if_ data really points to an object with the expected type. - - if (port == indices.getPortIndexFor (PortKind::seqInput)) - { - inputData = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::seqOutput)) - { - outputData = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::latencyOutput)) - { - latency = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::freeWheelingInput)) - { - freeWheeling = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::enabledInput)) - { - enabled = static_cast (data); - } - else if (isPositiveAndBelow (port, indices.getMaxAudioPortIndex())) - { - audioBuffers[(size_t) port] = static_cast (data); - } - else - { - // This port was not declared! - jassertfalse; - } - } - - template - void forEachInputEvent (Callback&& callback) - { - if (inputData != nullptr && inputData->atom.type == mLV2_ATOM__Sequence) - for (const auto* event : lv2_shared::SequenceIterator { lv2_shared::SequenceWithSize { inputData } }) - callback (event); - } - - void prepareToWrite() - { - // Note: Carla seems to have a bug (verified with the eg-fifths plugin) where - // the output buffer size is incorrect on alternate calls. - forge.setBuffer (reinterpret_cast (outputData), outputData->atom.size); - } - - void writeLatency (int value) - { - if (latency != nullptr) - *latency = (float) value; - } - - const float* getBufferForAudioInput (int index) const noexcept - { - return audioBuffers[(size_t) indices.getPortIndexForAudioInput (index)]; - } - - float* getBufferForAudioOutput (int index) const noexcept - { - return audioBuffers[(size_t) indices.getPortIndexForAudioOutput (index)]; - } - - bool isFreeWheeling() const noexcept - { - if (freeWheeling != nullptr) - return *freeWheeling > 0.5f; - - return false; - } - - bool isEnabled() const noexcept - { - if (enabled != nullptr) - return *enabled > 0.5f; - - return true; - } - - lv2_shared::AtomForge forge; - PortIndices indices; - -private: - static constexpr auto numParamPorts = 3; - const LV2_Atom_Sequence* inputData = nullptr; - LV2_Atom_Sequence* outputData = nullptr; - float* latency = nullptr; - float* freeWheeling = nullptr; - float* enabled = nullptr; - std::vector audioBuffers; - const LV2_URID mLV2_ATOM__Sequence; - - JUCE_LEAK_DETECTOR (Ports) -}; - -class LV2PluginInstance : private AudioProcessorListener -{ -public: - LV2PluginInstance (double sampleRate, - int64_t maxBlockSize, - const char*, - LV2_URID_Map mapFeatureIn) - : mapFeature (mapFeatureIn), - playHead (mapFeature, sampleRate) - { - processor->addListener (this); - processor->setPlayHead (&playHead); - prepare (sampleRate, (int) maxBlockSize); - } - - void connect (uint32_t port, void* data) - { - ports.connect ((int) port, data); - } - - void activate() {} - - template - static void iterateAudioBuffer (AudioBuffer& ab, UnaryFunction fn) - { - float* const* sampleData = ab.getArrayOfWritePointers(); - - for (int c = ab.getNumChannels(); --c >= 0;) - for (int s = ab.getNumSamples(); --s >= 0;) - fn (sampleData[c][s]); - } - - static int countNaNs (AudioBuffer& ab) noexcept - { - int count = 0; - iterateAudioBuffer (ab, [&count] (float s) - { - if (std::isnan (s)) - ++count; - }); - - return count; - } - - void run (uint32_t numSteps) - { - // If this is hit, the host is trying to process more samples than it told us to prepare - jassert (static_cast (numSteps) <= processor->getBlockSize()); - - midi.clear(); - playHead.invalidate(); - audio.setSize (audio.getNumChannels(), static_cast (numSteps), true, false, true); - - ports.forEachInputEvent ([&] (const LV2_Atom_Event* event) - { - struct Callback - { - explicit Callback (LV2PluginInstance& s) : self (s) {} - - void setParameter (LV2_URID property, float value) const noexcept - { - self.parameters.setValueFromHost (property, value); - } - - // The host probably shouldn't send us 'touched' messages. - void gesture (LV2_URID, bool) const noexcept {} - - LV2PluginInstance& self; - }; - - patchSetHelper.processPatchSet (event, Callback { *this }); - - playHead.readNewInfo (event); - - if (event->body.type == mLV2_MIDI__MidiEvent) - midi.addEvent (event + 1, static_cast (event->body.size), static_cast (event->time.frames)); - }); - - processor->setNonRealtime (ports.isFreeWheeling()); - - for (auto i = 0, end = processor->getTotalNumInputChannels(); i < end; ++i) - audio.copyFrom (i, 0, ports.getBufferForAudioInput (i), audio.getNumSamples()); - - jassert (countNaNs (audio) == 0); - - { - const ScopedLock lock { processor->getCallbackLock() }; - - if (processor->isSuspended()) - { - for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) - { - const auto ptr = ports.getBufferForAudioOutput (i); - std::fill (ptr, ptr + numSteps, 0.0f); - } - } - else - { - const auto isEnabled = ports.isEnabled(); - - if (auto* param = processor->getBypassParameter()) - { - param->setValueNotifyingHost (isEnabled ? 0.0f : 1.0f); - processor->processBlock (audio, midi); - } - else if (isEnabled) - { - processor->processBlock (audio, midi); - } - else - { - processor->processBlockBypassed (audio, midi); - } - } - } - - for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) - { - const auto src = audio.getReadPointer (i); - const auto dst = ports.getBufferForAudioOutput (i); - - if (dst != nullptr) - std::copy (src, src + numSteps, dst); - } - - ports.prepareToWrite(); - auto* forge = ports.forge.get(); - lv2_shared::SequenceFrame sequence { forge, (uint32_t) 0 }; - - parameters.forEachChangedParameter ([&] (const AudioProcessorParameter& param, - LV2_URID paramUrid, - const ParameterStorage::Options& options) - { - const auto sendTouched = [&] (bool state) - { - // TODO Implement begin/end change gesture support once it's supported by LV2 - ignoreUnused (state); - }; - - if (options.gestureBegin) - sendTouched (true); - - if (options.parameterValue) - { - lv2_atom_forge_frame_time (forge, 0); - - lv2_shared::ObjectFrame object { forge, (uint32_t) 0, patchSetHelper.mLV2_PATCH__Set }; - - lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__property); - lv2_atom_forge_urid (forge, paramUrid); - - lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__value); - lv2_atom_forge_float (forge, [&] - { - if (auto* rangedParam = dynamic_cast (¶m)) - return rangedParam->convertFrom0to1 (rangedParam->getValue()); - - return param.getValue(); - }()); - } - - if (options.gestureEnd) - sendTouched (false); - }); - - if (shouldSendStateChange.exchange (false)) - { - lv2_atom_forge_frame_time (forge, 0); - lv2_shared::ObjectFrame { forge, (uint32_t) 0, mLV2_STATE__StateChanged }; - } - - for (const auto meta : midi) - { - const auto bytes = static_cast (meta.numBytes); - lv2_atom_forge_frame_time (forge, meta.samplePosition); - lv2_atom_forge_atom (forge, bytes, mLV2_MIDI__MidiEvent); - lv2_atom_forge_write (forge, meta.data, bytes); - } - - ports.writeLatency (processor->getLatencySamples()); - } - - void deactivate() {} - - LV2_State_Status store (LV2_State_Store_Function storeFn, - LV2_State_Handle handle, - uint32_t, - const LV2_Feature* const*) - { - MemoryBlock block; - processor->getStateInformation (block); - const auto text = block.toBase64Encoding(); - storeFn (handle, - mJucePluginLV2UriState, - text.toRawUTF8(), - text.getNumBytesAsUTF8() + 1, - mLV2_ATOM__String, - LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); - - return LV2_STATE_SUCCESS; - } - - LV2_State_Status retrieve (LV2_State_Retrieve_Function retrieveFn, - LV2_State_Handle handle, - uint32_t, - const LV2_Feature* const*) - { - size_t size = 0; - uint32_t type = 0; - uint32_t dataFlags = 0; - - // Try retrieving a port index (if this is a 'program' preset). - const auto* programData = retrieveFn (handle, mJucePluginLV2UriProgram, &size, &type, &dataFlags); - - if (programData != nullptr && type == mLV2_ATOM__Int && size == sizeof (int32_t)) - { - const auto programIndex = readUnaligned (programData); - processor->setCurrentProgram (programIndex); - return LV2_STATE_SUCCESS; - } - - // This doesn't seem to be a 'program' preset, try setting the full state from a string instead. - const auto* data = retrieveFn (handle, mJucePluginLV2UriState, &size, &type, &dataFlags); - - if (data == nullptr) - return LV2_STATE_ERR_NO_PROPERTY; - - if (type != mLV2_ATOM__String) - return LV2_STATE_ERR_BAD_TYPE; - - String text (static_cast (data), (size_t) size); - MemoryBlock block; - block.fromBase64Encoding (text); - processor->setStateInformation (block.getData(), (int) block.getSize()); - - return LV2_STATE_SUCCESS; - } - - std::unique_ptr createEditor() - { - return std::unique_ptr (processor->createEditorIfNeeded()); - } - - void editorBeingDeleted (AudioProcessorEditor* editor) - { - processor->editorBeingDeleted (editor); - } - - static std::unique_ptr createProcessorInstance() - { - auto result = createPluginFilterOfType (AudioProcessor::wrapperType_LV2); - - #if defined (JucePlugin_PreferredChannelConfigurations) - constexpr short channelConfigurations[][2] { JucePlugin_PreferredChannelConfigurations }; - - static_assert (numElementsInArray (channelConfigurations) > 0, - "JucePlugin_PreferredChannelConfigurations must contain at least one entry"); - static_assert (channelConfigurations[0][0] > 0 || channelConfigurations[0][1] > 0, - "JucePlugin_PreferredChannelConfigurations must have at least one input or output channel"); - result->setPlayConfigDetails (channelConfigurations[0][0], channelConfigurations[0][1], 44100.0, 1024); - - const auto desiredChannels = std::make_tuple (channelConfigurations[0][0], channelConfigurations[0][1]); - const auto actualChannels = std::make_tuple (result->getTotalNumInputChannels(), result->getTotalNumOutputChannels()); - - if (desiredChannels != actualChannels) - Logger::outputDebugString ("Failed to apply requested channel configuration!"); - #else - result->enableAllBuses(); - #endif - - return result; - } - -private: - void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - // Only check for non-parameter state here because: - // - Latency is automatically written every block. - // - There's no way for an LV2 plugin to report an internal program change. - // - Parameter info is hard-coded in the plugin's turtle description. - if (details.nonParameterStateChanged) - shouldSendStateChange = true; - } - - void prepare (double sampleRate, int maxBlockSize) - { - jassert (processor != nullptr); - processor->setRateAndBufferSizeDetails (sampleRate, maxBlockSize); - processor->prepareToPlay (sampleRate, maxBlockSize); - - const auto numChannels = jmax (processor->getTotalNumInputChannels(), - processor->getTotalNumOutputChannels()); - - midi.ensureSize (8192); - audio.setSize (numChannels, maxBlockSize); - audio.clear(); - } - - LV2_URID map (StringRef uri) const { return mapFeature.map (mapFeature.handle, uri); } - - ScopedJuceInitialiser_GUI scopedJuceInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - std::unique_ptr processor = createProcessorInstance(); - const LV2_URID_Map mapFeature; - ParameterStorage parameters { *processor, mapFeature }; - Ports ports { mapFeature, - processor->getTotalNumInputChannels(), - processor->getTotalNumOutputChannels() }; - lv2_shared::PatchSetHelper patchSetHelper { mapFeature, JucePlugin_LV2URI }; - PlayHead playHead; - MidiBuffer midi; - AudioBuffer audio; - std::atomic shouldSendStateChange { false }; - - #define X(str) const LV2_URID m##str = map (str); - X (JucePluginLV2UriProgram) - X (JucePluginLV2UriState) - X (LV2_ATOM__Int) - X (LV2_ATOM__String) - X (LV2_BUF_SIZE__maxBlockLength) - X (LV2_BUF_SIZE__sequenceSize) - X (LV2_MIDI__MidiEvent) - X (LV2_PATCH__Set) - X (LV2_STATE__StateChanged) - #undef X - - JUCE_LEAK_DETECTOR (LV2PluginInstance) -}; - -//============================================================================== -struct RecallFeature -{ - int (*doRecall) (const char*) = [] (const char* libraryPath) -> int - { - const ScopedJuceInitialiser_GUI scope; - const auto processor = LV2PluginInstance::createProcessorInstance(); - - const String pathString { CharPointer_UTF8 { libraryPath } }; - - const auto absolutePath = File::isAbsolutePath (pathString) ? File (pathString) - : File::getCurrentWorkingDirectory().getChildFile (pathString); - - const auto writers = { writeManifestTtl, writeDspTtl, writeUiTtl }; - - const auto wroteSuccessfully = [&processor, &absolutePath] (auto* fn) - { - const auto result = fn (*processor, absolutePath); - - if (! result.wasOk()) - std::cerr << result.getErrorMessage() << '\n'; - - return result.wasOk(); - }; - - return std::all_of (writers.begin(), writers.end(), wroteSuccessfully) ? 0 : 1; - }; - -private: - static String getPresetUri (int index) - { - return JucePlugin_LV2URI + String (uriSeparator) + "preset" + String (index + 1); - } - - static FileOutputStream openStream (const File& libraryPath, StringRef name) - { - return FileOutputStream { libraryPath.getSiblingFile (name + ".ttl") }; - } - - static Result prepareStream (FileOutputStream& stream) - { - if (const auto result = stream.getStatus(); ! result) - return result; - - stream.setPosition (0); - stream.truncate(); - return Result::ok(); - } - - static Result writeManifestTtl (AudioProcessor& proc, const File& libraryPath) - { - auto os = openStream (libraryPath, "manifest"); - - if (const auto result = prepareStream (os); ! result) - return result; - - os << "@prefix lv2: .\n" - "@prefix rdfs: .\n" - "@prefix pset: .\n" - "@prefix state: .\n" - "@prefix ui: .\n" - "@prefix xsd: .\n" - "\n" - "<" JucePlugin_LV2URI ">\n" - "\ta lv2:Plugin ;\n" - "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" - "\trdfs:seeAlso .\n"; - - if (proc.hasEditor()) - { - #if JUCE_MAC - #define JUCE_LV2_UI_KIND "CocoaUI" - #elif JUCE_WINDOWS - #define JUCE_LV2_UI_KIND "WindowsUI" - #elif JUCE_LINUX || JUCE_BSD - #define JUCE_LV2_UI_KIND "X11UI" - #else - #error "LV2 UI is unsupported on this platform" - #endif - - os << "\n" - "<" << JucePluginLV2UriUi << ">\n" - "\ta ui:" JUCE_LV2_UI_KIND " ;\n" - "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" - "\trdfs:seeAlso .\n" - "\n"; - } - - for (auto i = 0, end = proc.getNumPrograms(); i < end; ++i) - { - os << "<" << getPresetUri (i) << ">\n" - "\ta pset:Preset ;\n" - "\tlv2:appliesTo <" JucePlugin_LV2URI "> ;\n" - "\trdfs:label \"" << proc.getProgramName (i) << "\" ;\n" - "\tstate:state [ <" << JucePluginLV2UriProgram << "> \"" << i << "\"^^xsd:int ; ] .\n" - "\n"; - } - - return Result::ok(); - } - - static std::vector findAllSubgroupsDepthFirst (const AudioProcessorParameterGroup& group, - std::vector foundSoFar = {}) - { - foundSoFar.push_back (&group); - - for (auto* node : group) - { - if (auto* subgroup = node->getGroup()) - foundSoFar = findAllSubgroupsDepthFirst (*subgroup, std::move (foundSoFar)); - } - - return foundSoFar; - } - - using GroupSymbolMap = std::map; - - static String getFlattenedGroupSymbol (const AudioProcessorParameterGroup& group, String symbol = "") - { - if (auto* parent = group.getParent()) - return getFlattenedGroupSymbol (*parent, group.getID() + (symbol.isEmpty() ? "" : group.getSeparator() + symbol)); - - return symbol; - } - - static String getSymbolForGroup (const AudioProcessorParameterGroup& group) - { - const String allowedCharacters ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"); - const auto base = getFlattenedGroupSymbol (group); - - if (base.isEmpty()) - return {}; - - String copy; - - for (const auto character : base) - copy << String::charToString (allowedCharacters.containsChar (character) ? character : (juce_wchar) '_'); - - return "paramgroup_" + copy; - } - - static GroupSymbolMap getGroupsAndSymbols (const std::vector& groups) - { - std::set symbols; - GroupSymbolMap result; - - for (const auto& group : groups) - { - const auto symbol = [&] - { - const auto idealSymbol = getSymbolForGroup (*group); - - if (symbols.find (idealSymbol) == symbols.cend()) - return idealSymbol; - - for (auto i = 2; i < std::numeric_limits::max(); ++i) - { - const auto toTest = idealSymbol + "_" + String (i); - - if (symbols.find (toTest) == symbols.cend()) - return toTest; - } - - jassertfalse; - return String(); - }(); - - symbols.insert (symbol); - result.emplace (group, symbol); - } - - return result; - } - - template - static void visitAllParameters (const GroupSymbolMap& groups, Fn&& fn) - { - for (const auto& group : groups) - for (const auto* node : *group.first) - if (auto* param = node->getParameter()) - fn (group.second, *param); - } - - static Result writeDspTtl (AudioProcessor& proc, const File& libraryPath) - { - auto os = openStream (libraryPath, "dsp"); - - if (const auto result = prepareStream (os); ! result) - return result; - - os << "@prefix atom: .\n" - "@prefix bufs: .\n" - "@prefix doap: .\n" - "@prefix foaf: .\n" - "@prefix lv2: .\n" - "@prefix midi: .\n" - "@prefix opts: .\n" - "@prefix param: .\n" - "@prefix patch: .\n" - "@prefix pg: .\n" - "@prefix plug: <" JucePlugin_LV2URI << uriSeparator << "> .\n" - "@prefix pprop: .\n" - "@prefix rdfs: .\n" - "@prefix rdf: .\n" - "@prefix rsz: .\n" - "@prefix state: .\n" - "@prefix time: .\n" - "@prefix ui: .\n" - "@prefix units: .\n" - "@prefix urid: .\n" - "@prefix xsd: .\n" - "\n"; - - LegacyAudioParametersWrapper legacyParameters (proc, false); - - const auto allGroups = findAllSubgroupsDepthFirst (legacyParameters.getGroup()); - const auto groupsAndSymbols = getGroupsAndSymbols (allGroups); - - const auto parameterVisitor = [&] (const String& symbol, - const AudioProcessorParameter& param) - { - os << "plug:" << ParameterStorage::getIri (param) << "\n" - "\ta lv2:Parameter ;\n" - "\trdfs:label \"" << param.getName (1024) << "\" ;\n"; - - if (symbol.isNotEmpty()) - os << "\tpg:group plug:" << symbol << " ;\n"; - - os << "\trdfs:range atom:Float ;\n"; - - if (auto* rangedParam = dynamic_cast (¶m)) - { - os << "\tlv2:default " << rangedParam->convertFrom0to1 (rangedParam->getDefaultValue()) << " ;\n" - "\tlv2:minimum " << rangedParam->getNormalisableRange().start << " ;\n" - "\tlv2:maximum " << rangedParam->getNormalisableRange().end; - } - else - { - os << "\tlv2:default " << param.getDefaultValue() << " ;\n" - "\tlv2:minimum 0.0 ;\n" - "\tlv2:maximum 1.0"; - } - - // Avoid writing out loads of scale points for parameters with lots of steps - constexpr auto stepLimit = 1000; - const auto numSteps = param.getNumSteps(); - - if (param.isDiscrete() && 2 <= numSteps && numSteps < stepLimit) - { - os << "\t ;\n" - "\tlv2:portProperty lv2:enumeration " << (param.isBoolean() ? ", lv2:toggled " : "") << ";\n" - "\tlv2:scalePoint "; - - const auto maxIndex = numSteps - 1; - - for (int i = 0; i < numSteps; ++i) - { - const auto value = (float) i / (float) maxIndex; - const auto text = param.getText (value, 1024); - - os << (i != 0 ? ", " : "") << "[\n" - "\t\trdfs:label \"" << text << "\" ;\n" - "\t\trdf:value " << value << " ;\n" - "\t]"; - } - } - - os << " .\n\n"; - }; - - visitAllParameters (groupsAndSymbols, parameterVisitor); - - for (const auto& groupInfo : groupsAndSymbols) - { - if (groupInfo.second.isEmpty()) - continue; - - os << "plug:" << groupInfo.second << "\n" - "\ta pg:Group ;\n"; - - if (auto* parent = groupInfo.first->getParent()) - { - if (parent->getParent() != nullptr) - { - const auto it = groupsAndSymbols.find (parent); - - if (it != groupsAndSymbols.cend()) - os << "\tpg:subGroupOf plug:" << it->second << " ;\n"; - } - } - - os << "\tlv2:symbol \"" << groupInfo.second << "\" ;\n" - "\tlv2:name \"" << groupInfo.first->getName() << "\" .\n\n"; - } - - const auto getBaseBusName = [] (bool isInput) { return isInput ? "input_group_" : "output_group_"; }; - - for (const auto isInput : { true, false }) - { - const auto baseBusName = getBaseBusName (isInput); - const auto groupKind = isInput ? "InputGroup" : "OutputGroup"; - const auto busCount = proc.getBusCount (isInput); - - for (auto i = 0; i < busCount; ++i) - { - if (const auto* bus = proc.getBus (isInput, i)) - { - os << "plug:" << baseBusName << (i + 1) << "\n" - "\ta pg:" << groupKind << " ;\n" - "\tlv2:name \"" << bus->getName() << "\" ;\n" - "\tlv2:symbol \"" << baseBusName << (i + 1) << "\" .\n\n"; - } - } - } - - os << "<" JucePlugin_LV2URI ">\n"; - - if (proc.hasEditor()) - os << "\tui:ui <" << JucePluginLV2UriUi << "> ;\n"; - - const auto versionParts = StringArray::fromTokens (JucePlugin_VersionString, ".", ""); - - const auto getVersionOrZero = [&] (int indexFromBack) - { - const auto str = versionParts[versionParts.size() - indexFromBack]; - return str.isEmpty() ? 0 : str.getIntValue(); - }; - - const auto minorVersion = getVersionOrZero (2); - const auto microVersion = getVersionOrZero (1); - - os << "\ta " - #if JucePlugin_IsSynth - "lv2:InstrumentPlugin" - #else - "lv2:Plugin" - #endif - " ;\n" - "\tdoap:name \"" JucePlugin_Name "\" ;\n" - "\tdoap:description \"" JucePlugin_Desc "\" ;\n" - "\tlv2:minorVersion " << minorVersion << " ;\n" - "\tlv2:microVersion " << microVersion << " ;\n" - "\tdoap:maintainer [\n" - "\t\ta foaf:Person ;\n" - "\t\tfoaf:name \"" JucePlugin_Manufacturer "\" ;\n" - "\t\tfoaf:homepage <" JucePlugin_ManufacturerWebsite "> ;\n" - "\t\tfoaf:mbox <" JucePlugin_ManufacturerEmail "> ;\n" - "\t] ;\n" - "\tdoap:release [\n" - "\t\ta doap:Version ;\n" - "\t\tdoap:revision \"" JucePlugin_VersionString "\" ;\n" - "\t] ;\n" - "\tlv2:optionalFeature\n" - "\t\tlv2:hardRTCapable ;\n" - "\tlv2:extensionData\n" - "\t\tstate:interface ;\n" - "\tlv2:requiredFeature\n" - "\t\turid:map ,\n" - "\t\topts:options ,\n" - "\t\tbufs:boundedBlockLength ;\n"; - - for (const auto isInput : { true, false }) - { - const auto kind = isInput ? "mainInput" : "mainOutput"; - - if (proc.getBusCount (isInput) > 0) - os << "\tpg:" << kind << " plug:" << getBaseBusName (isInput) << "1 ;\n"; - } - - if (legacyParameters.size() != 0) - { - for (const auto header : { "writable", "readable" }) - { - os << "\tpatch:" << header; - - bool isFirst = true; - - for (const auto* param : legacyParameters) - { - os << (isFirst ? "" : " ,") << "\n\t\tplug:" << ParameterStorage::getIri (*param); - isFirst = false; - } - - os << " ;\n"; - } - } - - os << "\tlv2:port [\n"; - - const PortIndices indices (proc.getTotalNumInputChannels(), - proc.getTotalNumOutputChannels()); - - const auto designationMap = [&] - { - std::map result; - - for (const auto& pair : lv2_shared::channelDesignationMap) - result.emplace (pair.second, pair.first); - - return result; - }(); - - // TODO add support for specific audio group kinds - for (const auto isInput : { true, false }) - { - const auto baseBusName = getBaseBusName (isInput); - const auto portKind = isInput ? "InputPort" : "OutputPort"; - const auto portName = isInput ? "Audio In " : "Audio Out "; - const auto portSymbol = isInput ? "audio_in_" : "audio_out_"; - const auto busCount = proc.getBusCount (isInput); - - auto channelCounter = 0; - - for (auto busIndex = 0; busIndex < busCount; ++busIndex) - { - if (const auto* bus = proc.getBus (isInput, busIndex)) - { - const auto channelCount = bus->getNumberOfChannels(); - const auto optionalBus = ! bus->isEnabledByDefault(); - - for (auto channelIndex = 0; channelIndex < channelCount; ++channelIndex, ++channelCounter) - { - const auto portIndex = isInput ? indices.getPortIndexForAudioInput (channelCounter) - : indices.getPortIndexForAudioOutput (channelCounter); - - os << "\t\ta lv2:" << portKind << " , lv2:AudioPort ;\n" - "\t\tlv2:index " << portIndex << " ;\n" - "\t\tlv2:symbol \"" << portSymbol << (channelCounter + 1) << "\" ;\n" - "\t\tlv2:name \"" << portName << (channelCounter + 1) << "\" ;\n" - "\t\tpg:group plug:" << baseBusName << (busIndex + 1) << " ;\n"; - - if (optionalBus) - os << "\t\tlv2:portProperty lv2:connectionOptional ;\n"; - - const auto designation = bus->getCurrentLayout().getTypeOfChannel (channelIndex); - const auto it = designationMap.find (designation); - - if (it != designationMap.end()) - os << "\t\tlv2:designation <" << it->second << "> ;\n"; - - os << "\t] , [\n"; - } - } - } - } - - // In the event that the plugin decides to send all of its parameters in one go, - // we should ensure that the output buffer is large enough to accommodate, with some - // extra room for the sequence header, MIDI messages etc.. - const auto patchSetSizeBytes = 72; - const auto additionalSize = 8192; - const auto atomPortMinSize = proc.getParameters().size() * patchSetSizeBytes + additionalSize; - - os << "\t\ta lv2:InputPort , atom:AtomPort ;\n" - "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" - "\t\tatom:bufferType atom:Sequence ;\n" - "\t\tatom:supports\n"; - - #if ! JucePlugin_IsSynth && ! JucePlugin_IsMidiEffect - if (proc.acceptsMidi()) - #endif - os << "\t\t\tmidi:MidiEvent ,\n"; - - os << "\t\t\tpatch:Message ,\n" - "\t\t\ttime:Position ;\n" - "\t\tlv2:designation lv2:control ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqInput) << " ;\n" - "\t\tlv2:symbol \"in\" ;\n" - "\t\tlv2:name \"In\" ;\n" - "\t] , [\n" - "\t\ta lv2:OutputPort , atom:AtomPort ;\n" - "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" - "\t\tatom:bufferType atom:Sequence ;\n" - "\t\tatom:supports\n"; - - #if ! JucePlugin_IsMidiEffect - if (proc.producesMidi()) - #endif - os << "\t\t\tmidi:MidiEvent ,\n"; - - os << "\t\t\tpatch:Message ;\n" - "\t\tlv2:designation lv2:control ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqOutput) << " ;\n" - "\t\tlv2:symbol \"out\" ;\n" - "\t\tlv2:name \"Out\" ;\n" - "\t] , [\n" - "\t\ta lv2:OutputPort , lv2:ControlPort ;\n" - "\t\tlv2:designation lv2:latency ;\n" - "\t\tlv2:symbol \"latency\" ;\n" - "\t\tlv2:name \"Latency\" ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::latencyOutput) << " ;\n" - "\t\tlv2:portProperty lv2:reportsLatency , lv2:integer , lv2:connectionOptional , pprop:notOnGUI ;\n" - "\t\tunits:unit units:frame ;\n" - "\t] , [\n" - "\t\ta lv2:InputPort , lv2:ControlPort ;\n" - "\t\tlv2:designation lv2:freeWheeling ;\n" - "\t\tlv2:symbol \"freeWheeling\" ;\n" - "\t\tlv2:name \"Free Wheeling\" ;\n" - "\t\tlv2:default 0.0 ;\n" - "\t\tlv2:minimum 0.0 ;\n" - "\t\tlv2:maximum 1.0 ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::freeWheelingInput) << " ;\n" - "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" - "\t] , [\n" - "\t\ta lv2:InputPort , lv2:ControlPort ;\n" - "\t\tlv2:designation lv2:enabled ;\n" - "\t\tlv2:symbol \"enabled\" ;\n" - "\t\tlv2:name \"Enabled\" ;\n" - "\t\tlv2:default 1.0 ;\n" - "\t\tlv2:minimum 0.0 ;\n" - "\t\tlv2:maximum 1.0 ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::enabledInput) << " ;\n" - "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" - "\t] ;\n" - "\topts:supportedOption\n" - "\t\tbufs:maxBlockLength .\n"; - - return Result::ok(); - } - - static Result writeUiTtl (AudioProcessor& proc, const File& libraryPath) - { - if (! proc.hasEditor()) - return Result::ok(); - - auto os = openStream (libraryPath, "ui"); - - if (const auto result = prepareStream (os); ! result) - return result; - - const auto editorInstance = rawToUniquePtr (proc.createEditor()); - const auto resizeFeatureString = editorInstance->isResizable() ? "ui:resize" : "ui:noUserResize"; - - os << "@prefix lv2: .\n" - "@prefix opts: .\n" - "@prefix param: .\n" - "@prefix ui: .\n" - "@prefix urid: .\n" - "\n" - "<" << JucePluginLV2UriUi << ">\n" - "\tlv2:extensionData\n" - #if JUCE_LINUX || JUCE_BSD - "\t\tui:idleInterface ,\n" - #endif - "\t\topts:interface ,\n" - "\t\tui:noUserResize ,\n" // resize and noUserResize are always present in the extension data array - "\t\tui:resize ;\n" - "\n" - "\tlv2:requiredFeature\n" - #if JUCE_LINUX || JUCE_BSD - "\t\tui:idleInterface ,\n" - #endif - "\t\turid:map ,\n" - "\t\tui:parent ,\n" - "\t\t ;\n" - "\n" - "\tlv2:optionalFeature\n" - "\t\t" << resizeFeatureString << " ,\n" - "\t\topts:interface ,\n" - "\t\topts:options ;\n\n" - "\topts:supportedOption\n" - "\t\tui:scaleFactor ,\n" - "\t\tparam:sampleRate .\n"; - - return Result::ok(); - } - - JUCE_LEAK_DETECTOR (RecallFeature) -}; - -//============================================================================== -LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index) -{ - if (index != 0) - return nullptr; - - static const LV2_Descriptor descriptor - { - JucePlugin_LV2URI, // TODO some constexpr check that this is a valid URI in terms of RFC 3986 - [] (const LV2_Descriptor*, - double sampleRate, - const char* pathToBundle, - const LV2_Feature* const* features) -> LV2_Handle - { - const auto* mapFeature = findMatchingFeatureData (features, LV2_URID__map); - - if (mapFeature == nullptr) - { - // The host doesn't provide the 'urid map' feature - jassertfalse; - return nullptr; - } - - const auto boundedBlockLength = hasFeature (features, LV2_BUF_SIZE__boundedBlockLength); - - if (! boundedBlockLength) - { - // The host doesn't provide the 'bounded block length' feature - jassertfalse; - return nullptr; - } - - const auto* options = findMatchingFeatureData (features, LV2_OPTIONS__options); - - if (options == nullptr) - { - // The host doesn't provide the 'options' feature - jassertfalse; - return nullptr; - } - - const lv2_shared::NumericAtomParser parser { *mapFeature }; - const auto blockLengthUrid = mapFeature->map (mapFeature->handle, LV2_BUF_SIZE__maxBlockLength); - const auto blockSize = parser.parseNumericOption (findMatchingOption (options, blockLengthUrid)); - - if (! blockSize.hasValue()) - { - // The host doesn't specify a maximum block size - jassertfalse; - return nullptr; - } - - return new LV2PluginInstance { sampleRate, *blockSize, pathToBundle, *mapFeature }; - }, - [] (LV2_Handle instance, uint32_t port, void* data) - { - static_cast (instance)->connect (port, data); - }, - [] (LV2_Handle instance) { static_cast (instance)->activate(); }, - [] (LV2_Handle instance, uint32_t sampleCount) - { - static_cast (instance)->run (sampleCount); - }, - [] (LV2_Handle instance) { static_cast (instance)->deactivate(); }, - [] (LV2_Handle instance) - { - JUCE_AUTORELEASEPOOL - { - delete static_cast (instance); - } - }, - [] (const char* uri) -> const void* - { - const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; - - static RecallFeature recallFeature; - - static LV2_State_Interface stateInterface - { - [] (LV2_Handle instance, - LV2_State_Store_Function store, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -> LV2_State_Status - { - return static_cast (instance)->store (store, handle, flags, features); - }, - [] (LV2_Handle instance, - LV2_State_Retrieve_Function retrieve, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -> LV2_State_Status - { - return static_cast (instance)->retrieve (retrieve, handle, flags, features); - } - }; - - static const LV2_Feature features[] { { JUCE_TURTLE_RECALL_URI, &recallFeature }, - { LV2_STATE__interface, &stateInterface } }; - - const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); - return it != std::end (features) ? it->data : nullptr; - } - }; - - return &descriptor; -} - -static Optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options) -{ - if (options == nullptr || symap == nullptr) - return {}; - - const lv2_shared::NumericAtomParser parser { *symap }; - const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto* scaleFactorOption = findMatchingOption (options, scaleFactorUrid); - return parser.parseNumericOption (scaleFactorOption); -} - -class LV2UIInstance : private Component, - private ComponentListener -{ -public: - LV2UIInstance (const char*, - const char*, - LV2UI_Write_Function writeFunctionIn, - LV2UI_Controller controllerIn, - LV2UI_Widget* widget, - LV2PluginInstance* pluginIn, - LV2UI_Widget parentIn, - const LV2_URID_Map* symapIn, - const LV2UI_Resize* resizeFeatureIn, - Optional scaleFactorIn) - : writeFunction (writeFunctionIn), - controller (controllerIn), - plugin (pluginIn), - parent (parentIn), - symap (symapIn), - resizeFeature (resizeFeatureIn), - scaleFactor (scaleFactorIn), - editor (plugin->createEditor()) - { - jassert (plugin != nullptr); - jassert (parent != nullptr); - jassert (editor != nullptr); - - if (editor == nullptr) - return; - - const auto bounds = getSizeToContainChild(); - setSize (bounds.getWidth(), bounds.getHeight()); - - addAndMakeVisible (*editor); - - setBroughtToFrontOnMouseClick (true); - setOpaque (true); - setVisible (false); - removeFromDesktop(); - addToDesktop (detail::PluginUtilities::getDesktopFlags (editor.get()), parent); - editor->addComponentListener (this); - - *widget = getWindowHandle(); - - setVisible (true); - - editor->setScaleFactor (getScaleFactor()); - requestResize(); - } - - ~LV2UIInstance() override - { - plugin->editorBeingDeleted (editor.get()); - } - - // This is called by the host when a parameter changes. - // We don't care, our UI will listen to the processor directly. - void portEvent (uint32_t, uint32_t, uint32_t, const void*) {} - - // Called when the host requests a resize - int resize (int width, int height) - { - const ScopedValueSetter scope (hostRequestedResize, true); - setSize (width, height); - return 0; - } - - // Called by the host to give us an opportunity to process UI events - void idleCallback() - { - #if JUCE_LINUX || JUCE_BSD - messageThread->processPendingEvents(); - #endif - } - - void resized() override - { - const ScopedValueSetter scope (hostRequestedResize, true); - - if (editor != nullptr) - { - const auto localArea = editor->getLocalArea (this, getLocalBounds()); - editor->setBoundsConstrained ({ localArea.getWidth(), localArea.getHeight() }); - } - } - - void paint (Graphics& g) override { g.fillAll (Colours::black); } - - uint32_t getOptions (LV2_Options_Option* options) - { - const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; - - for (auto* opt = options; opt->key != 0; ++opt) - { - if (opt->context != LV2_OPTIONS_INSTANCE || opt->subject != 0 || opt->key != scaleFactorUrid) - continue; - - if (scaleFactor.hasValue()) - { - opt->type = floatUrid; - opt->size = sizeof (float); - opt->value = &(*scaleFactor); - } - } - - return LV2_OPTIONS_SUCCESS; - } - - uint32_t setOptions (const LV2_Options_Option* options) - { - const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; - - for (auto* opt = options; opt->key != 0; ++opt) - { - if (opt->context != LV2_OPTIONS_INSTANCE - || opt->subject != 0 - || opt->key != scaleFactorUrid - || opt->type != floatUrid - || opt->size != sizeof (float)) - { - continue; - } - - scaleFactor = *static_cast (opt->value); - updateScale(); - } - - return LV2_OPTIONS_SUCCESS; - } - -private: - void updateScale() - { - editor->setScaleFactor (getScaleFactor()); - requestResize(); - } - - Rectangle getSizeToContainChild() const - { - if (editor != nullptr) - return getLocalArea (editor.get(), editor->getLocalBounds()); - - return {}; - } - - float getScaleFactor() const noexcept - { - return scaleFactor.hasValue() ? *scaleFactor : 1.0f; - } - - void componentMovedOrResized (Component&, bool, bool wasResized) override - { - if (! hostRequestedResize && wasResized) - requestResize(); - } - - void write (uint32_t portIndex, uint32_t bufferSize, uint32_t portProtocol, const void* data) - { - writeFunction (controller, portIndex, bufferSize, portProtocol, data); - } - - void requestResize() - { - if (editor == nullptr) - return; - - const auto bounds = getSizeToContainChild(); - - if (resizeFeature == nullptr) - return; - - if (auto* fn = resizeFeature->ui_resize) - fn (resizeFeature->handle, bounds.getWidth(), bounds.getHeight()); - - setSize (bounds.getWidth(), bounds.getHeight()); - repaint(); - } - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - LV2UI_Write_Function writeFunction; - LV2UI_Controller controller; - LV2PluginInstance* plugin; - LV2UI_Widget parent; - const LV2_URID_Map* symap = nullptr; - const LV2UI_Resize* resizeFeature = nullptr; - Optional scaleFactor; - std::unique_ptr editor; - bool hostRequestedResize = false; - - JUCE_LEAK_DETECTOR (LV2UIInstance) -}; - -LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor (uint32_t index) -{ - if (index != 0) - return nullptr; - - static const LV2UI_Descriptor descriptor - { - JucePluginLV2UriUi.toRawUTF8(), // TODO some constexpr check that this is a valid URI in terms of RFC 3986 - [] (const LV2UI_Descriptor*, - const char* pluginUri, - const char* bundlePath, - LV2UI_Write_Function writeFunction, - LV2UI_Controller controller, - LV2UI_Widget* widget, - const LV2_Feature* const* features) -> LV2UI_Handle - { - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - auto* plugin = findMatchingFeatureData (features, LV2_INSTANCE_ACCESS_URI); - - if (plugin == nullptr) - { - // No instance access - jassertfalse; - return nullptr; - } - - auto* parent = findMatchingFeatureData (features, LV2_UI__parent); - - if (parent == nullptr) - { - // No parent access - jassertfalse; - return nullptr; - } - - auto* resizeFeature = findMatchingFeatureData (features, LV2_UI__resize); - - const auto* symap = findMatchingFeatureData (features, LV2_URID__map); - const auto scaleFactor = findScaleFactor (symap, findMatchingFeatureData (features, LV2_OPTIONS__options)); - - return new LV2UIInstance { pluginUri, - bundlePath, - writeFunction, - controller, - widget, - plugin, - parent, - symap, - resizeFeature, - scaleFactor }; - }, - [] (LV2UI_Handle ui) - { - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - JUCE_AUTORELEASEPOOL - { - delete static_cast (ui); - } - }, - [] (LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) - { - JUCE_ASSERT_MESSAGE_THREAD - static_cast (ui)->portEvent (portIndex, bufferSize, format, buffer); - }, - [] (const char* uri) -> const void* - { - const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; - - static LV2UI_Resize resize { nullptr, [] (LV2UI_Feature_Handle handle, int width, int height) -> int - { - JUCE_ASSERT_MESSAGE_THREAD - return static_cast (handle)->resize (width, height); - } }; - - static LV2UI_Idle_Interface idle { [] (LV2UI_Handle handle) - { - static_cast (handle)->idleCallback(); - return 0; - } }; - - static LV2_Options_Interface options - { - [] (LV2_Handle handle, LV2_Options_Option* optionsIn) - { - return static_cast (handle)->getOptions (optionsIn); - }, - [] (LV2_Handle handle, const LV2_Options_Option* optionsIn) - { - return static_cast (handle)->setOptions (optionsIn); - } - }; - - // We'll always define noUserResize and idle in the extension data array, but we'll - // only declare them in the ui.ttl if the UI is actually non-resizable, or requires - // idle callbacks. - // Well-behaved hosts should check the ttl before trying to search the - // extension-data array. - static const LV2_Feature features[] { { LV2_UI__resize, &resize }, - { LV2_UI__noUserResize, nullptr }, - { LV2_UI__idleInterface, &idle }, - { LV2_OPTIONS__interface, &options } }; - - const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); - return it != std::end (features) ? it->data : nullptr; - } - }; - - return &descriptor; -} - -} -} - -#endif diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp deleted file mode 100644 index 0ccae14f57..0000000000 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include - -#include -#include -#include - -#include -#include -#include - -// You can set this flag in your build if you need to specify a different -// standalone JUCEApplication class for your app to use. If you don't -// set it then by default we'll just create a simple one as below. -#if ! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP - -#include - -namespace juce -{ - -//============================================================================== -class StandaloneFilterApp : public JUCEApplication -{ -public: - StandaloneFilterApp() - { - PropertiesFile::Options options; - - options.applicationName = getApplicationName(); - options.filenameSuffix = ".settings"; - options.osxLibrarySubFolder = "Application Support"; - #if JUCE_LINUX || JUCE_BSD - options.folderName = "~/.config"; - #else - options.folderName = ""; - #endif - - appProperties.setStorageParameters (options); - } - - const String getApplicationName() override { return CharPointer_UTF8 (JucePlugin_Name); } - const String getApplicationVersion() override { return JucePlugin_VersionString; } - bool moreThanOneInstanceAllowed() override { return true; } - void anotherInstanceStarted (const String&) override {} - - virtual StandaloneFilterWindow* createWindow() - { - #ifdef JucePlugin_PreferredChannelConfigurations - StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations }; - #endif - - return new StandaloneFilterWindow (getApplicationName(), - LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), - appProperties.getUserSettings(), - false, {}, nullptr - #ifdef JucePlugin_PreferredChannelConfigurations - , juce::Array (channels, juce::numElementsInArray (channels)) - #else - , {} - #endif - #if JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE - , false - #endif - ); - } - - //============================================================================== - void initialise (const String&) override - { - mainWindow.reset (createWindow()); - - #if JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE - Desktop::getInstance().setKioskModeComponent (mainWindow.get(), false); - #endif - - mainWindow->setVisible (true); - } - - void shutdown() override - { - mainWindow = nullptr; - appProperties.saveIfNeeded(); - } - - //============================================================================== - void systemRequestedQuit() override - { - if (mainWindow.get() != nullptr) - mainWindow->pluginHolder->savePluginState(); - - if (ModalComponentManager::getInstance()->cancelAllModalComponents()) - { - Timer::callAfterDelay (100, []() - { - if (auto app = JUCEApplicationBase::getInstance()) - app->systemRequestedQuit(); - }); - } - else - { - quit(); - } - } - -protected: - ApplicationProperties appProperties; - std::unique_ptr mainWindow; -}; - -} // namespace juce - -#if JucePlugin_Build_Standalone && JUCE_IOS - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -using namespace juce; - -bool JUCE_CALLTYPE juce_isInterAppAudioConnected() -{ - if (auto holder = StandalonePluginHolder::getInstance()) - return holder->isInterAppAudioConnected(); - - return false; -} - -void JUCE_CALLTYPE juce_switchToHostApplication() -{ - if (auto holder = StandalonePluginHolder::getInstance()) - holder->switchToHostApplication(); -} - -Image JUCE_CALLTYPE juce_getIAAHostIcon (int size) -{ - if (auto holder = StandalonePluginHolder::getInstance()) - return holder->getIAAHostIcon (size); - - return Image(); -} - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -#endif - -#endif diff --git a/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp b/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp deleted file mode 100644 index f25583f5cf..0000000000 --- a/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp +++ /dev/null @@ -1,774 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include - -#if JucePlugin_Build_Unity - -#include -#include - -#if JUCE_WINDOWS - #include -#endif - -#include "juce_UnityPluginInterface.h" - -//============================================================================== -namespace juce -{ - -typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); -extern createUnityPeerFunctionType juce_createUnityPeerFn; - -//============================================================================== -class UnityPeer : public ComponentPeer, - public AsyncUpdater -{ -public: - UnityPeer (Component& ed) - : ComponentPeer (ed, 0), - mouseWatcher (*this) - { - getEditor().setResizable (false, false); - } - - //============================================================================== - Rectangle getBounds() const override { return bounds; } - Point localToGlobal (Point relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } - Point globalToLocal (Point screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } - - using ComponentPeer::localToGlobal; - using ComponentPeer::globalToLocal; - - StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } - - void setBounds (const Rectangle& newBounds, bool) override - { - bounds = newBounds; - mouseWatcher.setBoundsToWatch (bounds); - } - - bool contains (Point localPos, bool) const override - { - if (isPositiveAndBelow (localPos.getX(), getBounds().getWidth()) - && isPositiveAndBelow (localPos.getY(), getBounds().getHeight())) - return true; - - return false; - } - - void handleAsyncUpdate() override - { - fillPixels(); - } - - //============================================================================== - AudioProcessorEditor& getEditor() { return *dynamic_cast (&getComponent()); } - - void setPixelDataHandle (uint8* handle, int width, int height) - { - pixelData = handle; - - textureWidth = width; - textureHeight = height; - - renderImage = Image (new UnityBitmapImage (pixelData, width, height)); - } - - // N.B. This is NOT an efficient way to do this and you shouldn't use this method in your own code. - // It works for our purposes here but a much more efficient way would be to use a GL texture. - void fillPixels() - { - if (pixelData == nullptr) - return; - - LowLevelGraphicsSoftwareRenderer renderer (renderImage); - renderer.addTransform (AffineTransform::verticalFlip ((float) getComponent().getHeight())); - - handlePaint (renderer); - - for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) - { - auto r = pixelData[i + 2]; - auto g = pixelData[i + 1]; - auto b = pixelData[i + 0]; - - pixelData[i + 0] = r; - pixelData[i + 1] = g; - pixelData[i + 2] = b; - } - } - - void forwardMouseEvent (Point position, ModifierKeys mods) - { - ModifierKeys::currentModifiers = mods; - - handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::defaultPressure, - juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); - } - - void forwardKeyPress (int code, String name, ModifierKeys mods) - { - ModifierKeys::currentModifiers = mods; - - handleKeyPress (getKeyPress (code, name)); - } - -private: - //============================================================================== - struct UnityBitmapImage : public ImagePixelData - { - UnityBitmapImage (uint8* data, int w, int h) - : ImagePixelData (Image::PixelFormat::ARGB, w, h), - imageData (data), - lineStride (width * pixelStride) - { - } - - std::unique_ptr createType() const override - { - return std::make_unique(); - } - - std::unique_ptr createLowLevelContext() override - { - return std::make_unique (Image (this)); - } - - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, [[maybe_unused]] Image::BitmapData::ReadWriteMode mode) override - { - const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; - bitmap.data = imageData + offset; - bitmap.size = (size_t) (lineStride * height) - offset; - bitmap.pixelFormat = pixelFormat; - bitmap.lineStride = lineStride; - bitmap.pixelStride = pixelStride; - } - - ImagePixelData::Ptr clone() override - { - auto im = new UnityBitmapImage (imageData, width, height); - - for (int i = 0; i < height; ++i) - memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); - - return im; - } - - uint8* imageData; - int pixelStride = 4, lineStride; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityBitmapImage) - }; - - //============================================================================== - struct MouseWatcher : public Timer - { - MouseWatcher (ComponentPeer& o) : owner (o) {} - - void timerCallback() override - { - auto pos = Desktop::getMousePosition(); - - if (boundsToWatch.contains (pos) && pos != lastMousePos) - { - auto ms = Desktop::getInstance().getMainMouseSource(); - - if (! ms.getCurrentModifiers().isLeftButtonDown()) - owner.handleMouseEvent (juce::MouseInputSource::mouse, owner.globalToLocal (pos.toFloat()), {}, - juce::MouseInputSource::defaultPressure, juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); - - lastMousePos = pos; - } - - } - - void setBoundsToWatch (Rectangle b) - { - if (boundsToWatch != b) - boundsToWatch = b; - - startTimer (250); - } - - ComponentPeer& owner; - Rectangle boundsToWatch; - Point lastMousePos; - }; - - //============================================================================== - KeyPress getKeyPress (int keyCode, String name) - { - if (keyCode >= 32 && keyCode <= 64) - return { keyCode, ModifierKeys::currentModifiers, juce::juce_wchar (keyCode) }; - - if (keyCode >= 91 && keyCode <= 122) - return { keyCode, ModifierKeys::currentModifiers, name[0] }; - - if (keyCode >= 256 && keyCode <= 265) - return { juce::KeyPress::numberPad0 + (keyCode - 256), ModifierKeys::currentModifiers, juce::String (keyCode - 256).getCharPointer()[0] }; - - if (keyCode == 8) return { juce::KeyPress::backspaceKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 127) return { juce::KeyPress::deleteKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 9) return { juce::KeyPress::tabKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 13) return { juce::KeyPress::returnKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 27) return { juce::KeyPress::escapeKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 32) return { juce::KeyPress::spaceKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 266) return { juce::KeyPress::numberPadDecimalPoint, ModifierKeys::currentModifiers, {} }; - if (keyCode == 267) return { juce::KeyPress::numberPadDivide, ModifierKeys::currentModifiers, {} }; - if (keyCode == 268) return { juce::KeyPress::numberPadMultiply, ModifierKeys::currentModifiers, {} }; - if (keyCode == 269) return { juce::KeyPress::numberPadSubtract, ModifierKeys::currentModifiers, {} }; - if (keyCode == 270) return { juce::KeyPress::numberPadAdd, ModifierKeys::currentModifiers, {} }; - if (keyCode == 272) return { juce::KeyPress::numberPadEquals, ModifierKeys::currentModifiers, {} }; - if (keyCode == 273) return { juce::KeyPress::upKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 274) return { juce::KeyPress::downKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 275) return { juce::KeyPress::rightKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 276) return { juce::KeyPress::leftKey, ModifierKeys::currentModifiers, {} }; - - return {}; - } - - //============================================================================== - Rectangle bounds; - MouseWatcher mouseWatcher; - - uint8* pixelData = nullptr; - int textureWidth, textureHeight; - Image renderImage; - - //============================================================================== - void setMinimised (bool) override {} - bool isMinimised() const override { return false; } - void setFullScreen (bool) override {} - bool isFullScreen() const override { return false; } - bool setAlwaysOnTop (bool) override { return false; } - void toFront (bool) override {} - void toBehind (ComponentPeer*) override {} - bool isFocused() const override { return true; } - void grabFocus() override {} - void* getNativeHandle() const override { return nullptr; } - OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } - BorderSize getFrameSize() const override { return {}; } - void setVisible (bool) override {} - void setTitle (const String&) override {} - void setIcon (const Image&) override {} - void textInputRequired (Point, TextInputTarget&) override {} - void setAlpha (float) override {} - void performAnyPendingRepaintsNow() override {} - void repaint (const Rectangle&) override {} - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) -}; - -static ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } - -//============================================================================== -class AudioProcessorUnityWrapper -{ -public: - AudioProcessorUnityWrapper (bool isTemporary) - { - pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_Unity); - - if (! isTemporary && pluginInstance->hasEditor()) - { - pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); - pluginInstanceEditor->setVisible (true); - detail::PluginUtilities::addToDesktop (*pluginInstanceEditor, nullptr); - } - - juceParameters.update (*pluginInstance, false); - } - - ~AudioProcessorUnityWrapper() - { - if (pluginInstanceEditor != nullptr) - { - pluginInstanceEditor->removeFromDesktop(); - - PopupMenu::dismissAllActiveMenus(); - pluginInstanceEditor->processor.editorBeingDeleted (pluginInstanceEditor.get()); - pluginInstanceEditor = nullptr; - } - } - - void create (UnityAudioEffectState* state) - { - // only supported in Unity plugin API > 1.0 - if (state->structSize >= sizeof (UnityAudioEffectState)) - samplesPerBlock = static_cast (state->dspBufferSize); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - const int numConfigs = sizeof (configs) / sizeof (short[2]); - - jassertquiet (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - - pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], state->sampleRate, samplesPerBlock); - #else - pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); - #endif - - pluginInstance->prepareToPlay (state->sampleRate, samplesPerBlock); - - scratchBuffer.setSize (jmax (pluginInstance->getTotalNumInputChannels(), pluginInstance->getTotalNumOutputChannels()), samplesPerBlock); - } - - void release() - { - pluginInstance->releaseResources(); - } - - void reset() - { - pluginInstance->reset(); - } - - void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) - { - // If the plugin has a bypass parameter, set it to the current bypass state - if (auto* param = pluginInstance->getBypassParameter()) - if (isBypassed != (param->getValue() >= 0.5f)) - param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); - - for (int pos = 0; pos < bufferSize;) - { - auto max = jmin (bufferSize - pos, samplesPerBlock); - processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); - - pos += max; - } - } - - void declareParameters (UnityAudioEffectDefinition& definition) - { - static std::unique_ptr parametersPtr; - static int numParams = 0; - - if (parametersPtr == nullptr) - { - numParams = (int) juceParameters.size(); - - parametersPtr.reset (static_cast (std::calloc (static_cast (numParams), - sizeof (UnityAudioParameterDefinition)))); - - parameterDescriptions.clear(); - - for (int i = 0; i < numParams; ++i) - { - auto* parameter = juceParameters.getParamForIndex (i); - auto& paramDef = parametersPtr.get()[i]; - - const auto nameLength = (size_t) numElementsInArray (paramDef.name); - const auto unitLength = (size_t) numElementsInArray (paramDef.unit); - - parameter->getName ((int) nameLength - 1).copyToUTF8 (paramDef.name, nameLength); - - if (parameter->getLabel().isNotEmpty()) - parameter->getLabel().copyToUTF8 (paramDef.unit, unitLength); - - parameterDescriptions.add (parameter->getName (15)); - paramDef.description = parameterDescriptions[i].toRawUTF8(); - - paramDef.defaultVal = parameter->getDefaultValue(); - paramDef.min = 0.0f; - paramDef.max = 1.0f; - paramDef.displayScale = 1.0f; - paramDef.displayExponent = 1.0f; - } - } - - definition.numParameters = static_cast (numParams); - definition.parameterDefintions = parametersPtr.get(); - } - - void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValueNotifyingHost (value); } - float getParameter (int index) const noexcept { return juceParameters.getParamForIndex (index)->getValue(); } - - String getParameterString (int index) const noexcept - { - auto* param = juceParameters.getParamForIndex (index); - return param->getText (param->getValue(), 16); - } - - int getNumInputChannels() const noexcept { return pluginInstance->getTotalNumInputChannels(); } - int getNumOutputChannels() const noexcept { return pluginInstance->getTotalNumOutputChannels(); } - - bool hasEditor() const noexcept { return pluginInstance->hasEditor(); } - - UnityPeer& getEditorPeer() const - { - auto* peer = dynamic_cast (pluginInstanceEditor->getPeer()); - - jassert (peer != nullptr); - return *peer; - } - -private: - //============================================================================== - void processBuffers (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) - { - int ch; - for (ch = 0; ch < numInChannels; ++ch) - { - using DstSampleType = AudioData::Pointer; - using SrcSampleType = AudioData::Pointer; - - DstSampleType dstData (scratchBuffer.getWritePointer (ch)); - SrcSampleType srcData (inBuffer + ch, numInChannels); - dstData.convertSamples (srcData, bufferSize); - } - - for (; ch < numOutChannels; ++ch) - scratchBuffer.clear (ch, 0, bufferSize); - - { - const ScopedLock sl (pluginInstance->getCallbackLock()); - - if (pluginInstance->isSuspended()) - { - scratchBuffer.clear(); - } - else - { - MidiBuffer mb; - - if (isBypassed && pluginInstance->getBypassParameter() == nullptr) - pluginInstance->processBlockBypassed (scratchBuffer, mb); - else - pluginInstance->processBlock (scratchBuffer, mb); - } - } - - for (ch = 0; ch < numOutChannels; ++ch) - { - using DstSampleType = AudioData::Pointer; - using SrcSampleType = AudioData::Pointer; - - DstSampleType dstData (outBuffer + ch, numOutChannels); - SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); - dstData.convertSamples (srcData, bufferSize); - } - } - - //============================================================================== - std::unique_ptr pluginInstance; - std::unique_ptr pluginInstanceEditor; - - int samplesPerBlock = 1024; - StringArray parameterDescriptions; - - AudioBuffer scratchBuffer; - - LegacyAudioParametersWrapper juceParameters; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) -}; - -//============================================================================== -static HashMap& getWrapperMap() -{ - static HashMap wrapperMap; - return wrapperMap; -} - -static void onWrapperCreation (AudioProcessorUnityWrapper* wrapperToAdd) -{ - getWrapperMap().set (std::abs (Random::getSystemRandom().nextInt (65536)), wrapperToAdd); -} - -static void onWrapperDeletion (AudioProcessorUnityWrapper* wrapperToRemove) -{ - getWrapperMap().removeValue (wrapperToRemove); -} - -//============================================================================== -static UnityAudioEffectDefinition getEffectDefinition() -{ - const auto wrapper = std::make_unique (true); - const String originalName { JucePlugin_Name }; - const auto name = (! originalName.startsWithIgnoreCase ("audioplugin") ? "audioplugin_" : "") + originalName; - - UnityAudioEffectDefinition result{}; - name.copyToUTF8 (result.name, (size_t) numElementsInArray (result.name)); - - result.structSize = sizeof (UnityAudioEffectDefinition); - result.parameterStructSize = sizeof (UnityAudioParameterDefinition); - - result.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; - result.pluginVersion = JucePlugin_VersionCode; - - // effects must set this to 0, generators > 0 - result.channels = (wrapper->getNumInputChannels() != 0 ? 0 - : static_cast (wrapper->getNumOutputChannels())); - - wrapper->declareParameters (result); - - result.create = [] (UnityAudioEffectState* state) - { - auto* pluginInstance = new AudioProcessorUnityWrapper (false); - pluginInstance->create (state); - - state->effectData = pluginInstance; - - onWrapperCreation (pluginInstance); - - return 0; - }; - - result.release = [] (UnityAudioEffectState* state) - { - auto* pluginInstance = state->getEffectData(); - pluginInstance->release(); - - onWrapperDeletion (pluginInstance); - delete pluginInstance; - - if (getWrapperMap().size() == 0) - shutdownJuce_GUI(); - - return 0; - }; - - result.reset = [] (UnityAudioEffectState* state) - { - auto* pluginInstance = state->getEffectData(); - pluginInstance->reset(); - - return 0; - }; - - result.setPosition = [] (UnityAudioEffectState* state, unsigned int pos) - { - ignoreUnused (state, pos); - return 0; - }; - - result.process = [] (UnityAudioEffectState* state, - float* inBuffer, - float* outBuffer, - unsigned int bufferSize, - int numInChannels, - int numOutChannels) - { - auto* pluginInstance = state->getEffectData(); - - if (pluginInstance != nullptr) - { - auto isPlaying = ((state->flags & stateIsPlaying) != 0); - auto isMuted = ((state->flags & stateIsMuted) != 0); - auto isPaused = ((state->flags & stateIsPaused) != 0); - - const auto bypassed = ! isPlaying || (isMuted || isPaused); - pluginInstance->process (inBuffer, outBuffer, static_cast (bufferSize), numInChannels, numOutChannels, bypassed); - } - else - { - FloatVectorOperations::clear (outBuffer, static_cast (bufferSize) * numOutChannels); - } - - return 0; - }; - - result.setFloatParameter = [] (UnityAudioEffectState* state, int index, float value) - { - auto* pluginInstance = state->getEffectData(); - pluginInstance->setParameter (index, value); - - return 0; - }; - - result.getFloatParameter = [] (UnityAudioEffectState* state, int index, float* value, char* valueStr) - { - auto* pluginInstance = state->getEffectData(); - *value = pluginInstance->getParameter (index); - - pluginInstance->getParameterString (index).copyToUTF8 (valueStr, 15); - - return 0; - }; - - result.getFloatBuffer = [] (UnityAudioEffectState* state, const char* kind, float* buffer, int numSamples) - { - ignoreUnused (numSamples); - - const StringRef kindStr { kind }; - - if (kindStr == StringRef ("Editor")) - { - auto* pluginInstance = state->getEffectData(); - - buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; - } - else if (kindStr == StringRef ("ID")) - { - auto* pluginInstance = state->getEffectData(); - - for (HashMap::Iterator i (getWrapperMap()); i.next();) - { - if (i.getValue() == pluginInstance) - { - buffer[0] = (float) i.getKey(); - break; - } - } - - return 0; - } - else if (kindStr == StringRef ("Size")) - { - auto* pluginInstance = state->getEffectData(); - - auto& editor = pluginInstance->getEditorPeer().getEditor(); - - buffer[0] = (float) editor.getBounds().getWidth(); - buffer[1] = (float) editor.getBounds().getHeight(); - buffer[2] = (float) editor.getConstrainer()->getMinimumWidth(); - buffer[3] = (float) editor.getConstrainer()->getMinimumHeight(); - buffer[4] = (float) editor.getConstrainer()->getMaximumWidth(); - buffer[5] = (float) editor.getConstrainer()->getMaximumHeight(); - } - - return 0; - }; - - return result; -} - -} // namespace juce - -// From reading the example code, it seems that the triple indirection indicates -// an out-value of an array of pointers. That is, after calling this function, definitionsPtr -// should point to a pre-existing/static array of pointer-to-effect-definition. -UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) -{ - if (juce::getWrapperMap().size() == 0) - juce::initialiseJuce_GUI(); - - static std::once_flag flag; - std::call_once (flag, [] { juce::juce_createUnityPeerFn = juce::createUnityPeer; }); - - static auto definition = juce::getEffectDefinition(); - static UnityAudioEffectDefinition* definitions[] { &definition }; - *definitionsPtr = definitions; - - return 1; -} - -//============================================================================== -static juce::ModifierKeys unityModifiersToJUCE (UnityEventModifiers mods, bool mouseDown, int mouseButton = -1) -{ - int flags = 0; - - if (mouseDown) - { - if (mouseButton == 0) - flags |= juce::ModifierKeys::leftButtonModifier; - else if (mouseButton == 1) - flags |= juce::ModifierKeys::rightButtonModifier; - else if (mouseButton == 2) - flags |= juce::ModifierKeys::middleButtonModifier; - } - - if (mods == 0) - return flags; - - if ((mods & UnityEventModifiers::shift) != 0) flags |= juce::ModifierKeys::shiftModifier; - if ((mods & UnityEventModifiers::control) != 0) flags |= juce::ModifierKeys::ctrlModifier; - if ((mods & UnityEventModifiers::alt) != 0) flags |= juce::ModifierKeys::altModifier; - if ((mods & UnityEventModifiers::command) != 0) flags |= juce::ModifierKeys::commandModifier; - - return { flags }; -} - -//============================================================================== -static juce::AudioProcessorUnityWrapper* getWrapperChecked (int id) -{ - auto* wrapper = juce::getWrapperMap()[id]; - jassert (wrapper != nullptr); - - return wrapper; -} - -//============================================================================== -static void UNITY_INTERFACE_API onRenderEvent (int id) -{ - getWrapperChecked (id)->getEditorPeer().triggerAsyncUpdate(); -} - -UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback() -{ - return onRenderEvent; -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* data, int w, int h) -{ - getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast (data), w, h); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers unityMods, int button) -{ - getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers unityMods, int button) -{ - getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers unityMods) -{ - getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, false)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name) -{ - getWrapperChecked (id)->getEditorPeer().forwardKeyPress (code, name, unityModifiersToJUCE (mods, false)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h) -{ - getWrapperChecked (id)->getEditorPeer().getEditor().setBounds ({ (int) x, (int) y, (int) w, (int) h }); -} - -//============================================================================== -#if JUCE_WINDOWS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - - extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) - { - if (reason == DLL_PROCESS_ATTACH) - juce::Process::setCurrentModuleInstanceHandle (instance); - - return true; - } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -#endif diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp deleted file mode 100644 index 487b356b6f..0000000000 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ /dev/null @@ -1,2208 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include -#include - -#if JucePlugin_Build_VST - -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) - -#include -#include - -#if JucePlugin_VersionCode < 0x010000 // Major < 0 - - #if (JucePlugin_VersionCode & 0x00FF00) > (9 * 0x100) // check if Minor number exceeds 9 - JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'minor' exceeding 9") - #endif - - #if (JucePlugin_VersionCode & 0xFF) > 9 // check if Bugfix number exceeds 9 - JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'bugfix' exceeding 9") - #endif - -#elif JucePlugin_VersionCode >= 0x650000 // Major >= 101 - - #if (JucePlugin_VersionCode & 0x00FF00) > (99 * 0x100) // check if Minor number exceeds 99 - JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'minor' exceeding 99") - #endif - - #if (JucePlugin_VersionCode & 0xFF) > 99 // check if Bugfix number exceeds 99 - JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'bugfix' exceeding 99") - #endif - -#endif - -#ifdef PRAGMA_ALIGN_SUPPORTED - #undef PRAGMA_ALIGN_SUPPORTED - #define PRAGMA_ALIGN_SUPPORTED 1 -#endif - -#if ! JUCE_MSVC && ! defined (__cdecl) - #define __cdecl -#endif - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion", - "-Wshadow", - "-Wdeprecated-register", - "-Wdeprecated-declarations", - "-Wunused-parameter", - "-Wdeprecated-writable-strings", - "-Wnon-virtual-dtor", - "-Wzero-as-null-pointer-constant") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) - -#define VST_FORCE_DEPRECATED 0 - -namespace Vst2 -{ -// If the following files cannot be found then you are probably trying to build -// a VST2 plug-in or a VST2-compatible VST3 plug-in. To do this you must have a -// VST2 SDK in your header search paths or use the "VST (Legacy) SDK Folder" -// field in the Projucer. The VST2 SDK can be obtained from the -// vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK or JUCE version 5.3.2. You -// also need a VST2 license from Steinberg to distribute VST2 plug-ins. -#include "pluginterfaces/vst2.x/aeffect.h" -#include "pluginterfaces/vst2.x/aeffectx.h" -} - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -//============================================================================== -#if JUCE_MSVC - #pragma pack (push, 8) -#endif - -#define JUCE_VSTINTERFACE_H_INCLUDED 1 -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 - -#include - -using namespace juce; - -#include -#include -#include - -#include -#include - -#ifdef JUCE_MSVC - #pragma pack (pop) -#endif - -#undef MemoryBlock - -class JuceVSTWrapper; -static bool recursionCheck = false; - -namespace juce -{ - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - JUCE_API double getScaleFactorForWindow (HWND); - #endif -} - -//============================================================================== -#if JUCE_WINDOWS - -namespace -{ - // Returns the actual container window, unlike GetParent, which can also return a separate owner window. - static HWND getWindowParent (HWND w) noexcept { return GetAncestor (w, GA_PARENT); } - - static HWND findMDIParentOf (HWND w) - { - const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); - - while (w != nullptr) - { - auto parent = getWindowParent (w); - - if (parent == nullptr) - break; - - TCHAR windowType[32] = { 0 }; - GetClassName (parent, windowType, 31); - - if (String (windowType).equalsIgnoreCase ("MDIClient")) - return parent; - - RECT windowPos, parentPos; - GetWindowRect (w, &windowPos); - GetWindowRect (parent, &parentPos); - - auto dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); - auto dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); - - if (dw > 100 || dh > 100) - break; - - w = parent; - - if (dw == 2 * frameThickness) - break; - } - - return w; - } - - static int numActivePlugins = 0; - static bool messageThreadIsDefinitelyCorrect = false; -} - -#endif - -//============================================================================== -// Ableton Live host specific commands -struct AbletonLiveHostSpecific -{ - enum - { - KCantBeSuspended = (1 << 2) - }; - - uint32 magic; // 'AbLi' - int cmd; // 5 = realtime properties - size_t commandSize; // sizeof (int) - int flags; // KCantBeSuspended = (1 << 2) -}; - -//============================================================================== -/** - This is an AudioEffectX object that holds and wraps our AudioProcessor... -*/ -class JuceVSTWrapper : public AudioProcessorListener, - public AudioPlayHead, - private Timer, - private AudioProcessorParameter::Listener -{ -private: - //============================================================================== - template - struct VstTempBuffers - { - VstTempBuffers() {} - ~VstTempBuffers() { release(); } - - void release() noexcept - { - for (auto* c : tempChannels) - delete[] c; - - tempChannels.clear(); - } - - HeapBlock channels; - Array tempChannels; // see note in processReplacing() - juce::AudioBuffer processTempBuffer; - }; - - /** Use the same names as the VST SDK. */ - struct VstOpCodeArguments - { - int32 index; - pointer_sized_int value; - void* ptr; - float opt; - }; - -public: - //============================================================================== - JuceVSTWrapper (Vst2::audioMasterCallback cb, std::unique_ptr af) - : hostCallback (cb), - processor (std::move (af)) - { - inParameterChangedCallback = false; - - // VST-2 does not support disabling buses: so always enable all of them - processor->enableAllBuses(); - - findMaxTotalChannels (maxNumInChannels, maxNumOutChannels); - - // You must at least have some channels - jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); - - if (processor->isMidiEffect()) - maxNumInChannels = maxNumOutChannels = 2; - - #ifdef JucePlugin_PreferredChannelConfigurations - processor->setPlayConfigDetails (maxNumInChannels, maxNumOutChannels, 44100.0, 1024); - #endif - - processor->setRateAndBufferSizeDetails (0, 0); - processor->setPlayHead (this); - processor->addListener (this); - - if (auto* juceParam = processor->getBypassParameter()) - juceParam->addListener (this); - - juceParameters.update (*processor, false); - - memset (&vstEffect, 0, sizeof (vstEffect)); - vstEffect.magic = 0x56737450 /* 'VstP' */; - vstEffect.dispatcher = (Vst2::AEffectDispatcherProc) dispatcherCB; - vstEffect.process = nullptr; - vstEffect.setParameter = (Vst2::AEffectSetParameterProc) setParameterCB; - vstEffect.getParameter = (Vst2::AEffectGetParameterProc) getParameterCB; - vstEffect.numPrograms = jmax (1, processor->getNumPrograms()); - vstEffect.numParams = juceParameters.getNumParameters(); - vstEffect.numInputs = maxNumInChannels; - vstEffect.numOutputs = maxNumOutChannels; - vstEffect.initialDelay = processor->getLatencySamples(); - vstEffect.object = this; - vstEffect.uniqueID = JucePlugin_VSTUniqueID; - - #ifdef JucePlugin_VSTChunkStructureVersion - vstEffect.version = JucePlugin_VSTChunkStructureVersion; - #else - vstEffect.version = JucePlugin_VersionCode; - #endif - - vstEffect.processReplacing = (Vst2::AEffectProcessProc) processReplacingCB; - vstEffect.processDoubleReplacing = (Vst2::AEffectProcessDoubleProc) processDoubleReplacingCB; - - vstEffect.flags |= Vst2::effFlagsHasEditor; - - vstEffect.flags |= Vst2::effFlagsCanReplacing; - if (processor->supportsDoublePrecisionProcessing()) - vstEffect.flags |= Vst2::effFlagsCanDoubleReplacing; - - vstEffect.flags |= Vst2::effFlagsProgramChunks; - - #if JucePlugin_IsSynth - vstEffect.flags |= Vst2::effFlagsIsSynth; - #else - if (processor->getTailLengthSeconds() == 0.0) - vstEffect.flags |= Vst2::effFlagsNoSoundInStop; - #endif - - #if JUCE_WINDOWS - ++numActivePlugins; - #endif - } - - ~JuceVSTWrapper() override - { - JUCE_AUTORELEASEPOOL - { - #if JUCE_LINUX || JUCE_BSD - MessageManagerLock mmLock; - #endif - - stopTimer(); - deleteEditor (false); - - hasShutdown = true; - - processor = nullptr; - - jassert (editorComp == nullptr); - - deleteTempChannels(); - - #if JUCE_WINDOWS - if (--numActivePlugins == 0) - messageThreadIsDefinitelyCorrect = false; - #endif - } - } - - Vst2::AEffect* getAEffect() noexcept { return &vstEffect; } - - template - void internalProcessReplacing (FloatType** inputs, FloatType** outputs, - int32 numSamples, VstTempBuffers& tmpBuffers) - { - const bool isMidiEffect = processor->isMidiEffect(); - - if (firstProcessCallback) - { - firstProcessCallback = false; - - // if this fails, the host hasn't called resume() before processing - jassert (isProcessing); - - // (tragically, some hosts actually need this, although it's stupid to have - // to do it here.) - if (! isProcessing) - resume(); - - processor->setNonRealtime (isProcessLevelOffline()); - - #if JUCE_WINDOWS - if (detail::PluginUtilities::getHostType().isWavelab()) - { - int priority = GetThreadPriority (GetCurrentThread()); - - if (priority <= THREAD_PRIORITY_NORMAL && priority >= THREAD_PRIORITY_LOWEST) - processor->setNonRealtime (true); - } - #endif - } - - #if JUCE_DEBUG && ! (JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect) - const int numMidiEventsComingIn = midiEvents.getNumEvents(); - #endif - - { - const int numIn = processor->getTotalNumInputChannels(); - const int numOut = processor->getTotalNumOutputChannels(); - - const ScopedLock sl (processor->getCallbackLock()); - - if (processor->isSuspended()) - { - for (int i = 0; i < numOut; ++i) - if (outputs[i] != nullptr) - FloatVectorOperations::clear (outputs[i], numSamples); - } - else - { - updateCallbackContextInfo(); - - int i; - for (i = 0; i < numOut; ++i) - { - auto* chan = tmpBuffers.tempChannels.getUnchecked(i); - - if (chan == nullptr) - { - chan = outputs[i]; - - bool bufferPointerReusedForOtherChannels = false; - - for (int j = i; --j >= 0;) - { - if (outputs[j] == chan) - { - bufferPointerReusedForOtherChannels = true; - break; - } - } - - // if some output channels are disabled, some hosts supply the same buffer - // for multiple channels or supply a nullptr - this buggers up our method - // of copying the inputs over the outputs, so we need to create unique temp - // buffers in this case.. - if (bufferPointerReusedForOtherChannels || chan == nullptr) - { - chan = new FloatType [(size_t) blockSize * 2]; - tmpBuffers.tempChannels.set (i, chan); - } - } - - if (i < numIn) - { - if (chan != inputs[i]) - memcpy (chan, inputs[i], (size_t) numSamples * sizeof (FloatType)); - } - else - { - FloatVectorOperations::clear (chan, numSamples); - } - - tmpBuffers.channels[i] = chan; - } - - for (; i < numIn; ++i) - tmpBuffers.channels[i] = inputs[i]; - - { - const int numChannels = jmax (numIn, numOut); - AudioBuffer chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); - - if (isBypassed && processor->getBypassParameter() == nullptr) - processor->processBlockBypassed (chans, midiEvents); - else - processor->processBlock (chans, midiEvents); - } - - // copy back any temp channels that may have been used.. - for (i = 0; i < numOut; ++i) - if (auto* chan = tmpBuffers.tempChannels.getUnchecked(i)) - if (auto* dest = outputs[i]) - memcpy (dest, chan, (size_t) numSamples * sizeof (FloatType)); - } - } - - if (! midiEvents.isEmpty()) - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - auto numEvents = midiEvents.getNumEvents(); - - outgoingEvents.ensureSize (numEvents); - outgoingEvents.clear(); - - for (const auto metadata : midiEvents) - { - jassert (metadata.samplePosition >= 0 && metadata.samplePosition < numSamples); - - outgoingEvents.addEvent (metadata.data, metadata.numBytes, metadata.samplePosition); - } - - // Send VST events to the host. - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterProcessEvents, 0, 0, outgoingEvents.events, 0); - #elif JUCE_DEBUG - /* This assertion is caused when you've added some events to the - midiMessages array in your processBlock() method, which usually means - that you're trying to send them somewhere. But in this case they're - getting thrown away. - - If your plugin does want to send midi messages, you'll need to set - the JucePlugin_ProducesMidiOutput macro to 1 in your - JucePluginCharacteristics.h file. - - If you don't want to produce any midi output, then you should clear the - midiMessages array at the end of your processBlock() method, to - indicate that you don't want any of the events to be passed through - to the output. - */ - jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); - #endif - - midiEvents.clear(); - } - } - - void processReplacing (float** inputs, float** outputs, int32 sampleFrames) - { - jassert (! processor->isUsingDoublePrecision()); - internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); - } - - static void processReplacingCB (Vst2::AEffect* vstInterface, float** inputs, float** outputs, int32 sampleFrames) - { - getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); - } - - void processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames) - { - jassert (processor->isUsingDoublePrecision()); - internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); - } - - static void processDoubleReplacingCB (Vst2::AEffect* vstInterface, double** inputs, double** outputs, int32 sampleFrames) - { - getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); - } - - //============================================================================== - void resume() - { - if (processor != nullptr) - { - isProcessing = true; - - auto numInAndOutChannels = static_cast (vstEffect.numInputs + vstEffect.numOutputs); - floatTempBuffers .channels.calloc (numInAndOutChannels); - doubleTempBuffers.channels.calloc (numInAndOutChannels); - - auto currentRate = sampleRate; - auto currentBlockSize = blockSize; - - firstProcessCallback = true; - - processor->setNonRealtime (isProcessLevelOffline()); - processor->setRateAndBufferSizeDetails (currentRate, currentBlockSize); - - deleteTempChannels(); - - processor->prepareToPlay (currentRate, currentBlockSize); - - midiEvents.ensureSize (2048); - midiEvents.clear(); - - vstEffect.initialDelay = processor->getLatencySamples(); - - /** If this plug-in is a synth or it can receive midi events we need to tell the - host that we want midi. In the SDK this method is marked as deprecated, but - some hosts rely on this behaviour. - */ - if (vstEffect.flags & Vst2::effFlagsIsSynth || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect) - { - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterWantMidi, 0, 1, nullptr, 0); - } - - if (detail::PluginUtilities::getHostType().isAbletonLive() - && hostCallback != nullptr - && processor->getTailLengthSeconds() == std::numeric_limits::infinity()) - { - AbletonLiveHostSpecific hostCmd; - - hostCmd.magic = 0x41624c69; // 'AbLi' - hostCmd.cmd = 5; - hostCmd.commandSize = sizeof (int); - hostCmd.flags = AbletonLiveHostSpecific::KCantBeSuspended; - - hostCallback (&vstEffect, Vst2::audioMasterVendorSpecific, 0, 0, &hostCmd, 0.0f); - } - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - outgoingEvents.ensureSize (512); - #endif - } - } - - void suspend() - { - if (processor != nullptr) - { - processor->releaseResources(); - outgoingEvents.freeEvents(); - - isProcessing = false; - floatTempBuffers.channels.free(); - doubleTempBuffers.channels.free(); - - deleteTempChannels(); - } - } - - void updateCallbackContextInfo() - { - const Vst2::VstTimeInfo* ti = nullptr; - - if (hostCallback != nullptr) - { - int32 flags = Vst2::kVstPpqPosValid | Vst2::kVstTempoValid - | Vst2::kVstBarsValid | Vst2::kVstCyclePosValid - | Vst2::kVstTimeSigValid | Vst2::kVstSmpteValid - | Vst2::kVstClockValid | Vst2::kVstNanosValid; - - auto result = hostCallback (&vstEffect, Vst2::audioMasterGetTime, 0, flags, nullptr, 0); - ti = reinterpret_cast (result); - } - - if (ti == nullptr || ti->sampleRate <= 0) - { - currentPosition.reset(); - return; - } - - auto& info = currentPosition.emplace(); - info.setBpm ((ti->flags & Vst2::kVstTempoValid) != 0 ? makeOptional (ti->tempo) : nullopt); - - info.setTimeSignature ((ti->flags & Vst2::kVstTimeSigValid) != 0 ? makeOptional (TimeSignature { ti->timeSigNumerator, ti->timeSigDenominator }) - : nullopt); - - info.setTimeInSamples ((int64) (ti->samplePos + 0.5)); - info.setTimeInSeconds (ti->samplePos / ti->sampleRate); - info.setPpqPosition ((ti->flags & Vst2::kVstPpqPosValid) != 0 ? makeOptional (ti->ppqPos) : nullopt); - info.setPpqPositionOfLastBarStart ((ti->flags & Vst2::kVstBarsValid) != 0 ? makeOptional (ti->barStartPos) : nullopt); - - if ((ti->flags & Vst2::kVstSmpteValid) != 0) - { - info.setFrameRate ([&]() -> Optional - { - switch (ti->smpteFrameRate) - { - case Vst2::kVstSmpte24fps: return FrameRate().withBaseRate (24); - case Vst2::kVstSmpte239fps: return FrameRate().withBaseRate (24).withPullDown(); - - case Vst2::kVstSmpte25fps: return FrameRate().withBaseRate (25); - case Vst2::kVstSmpte249fps: return FrameRate().withBaseRate (25).withPullDown(); - - case Vst2::kVstSmpte30fps: return FrameRate().withBaseRate (30); - case Vst2::kVstSmpte30dfps: return FrameRate().withBaseRate (30).withDrop(); - case Vst2::kVstSmpte2997fps: return FrameRate().withBaseRate (30).withPullDown(); - case Vst2::kVstSmpte2997dfps: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - - case Vst2::kVstSmpte60fps: return FrameRate().withBaseRate (60); - case Vst2::kVstSmpte599fps: return FrameRate().withBaseRate (60).withPullDown(); - - case Vst2::kVstSmpteFilm16mm: - case Vst2::kVstSmpteFilm35mm: return FrameRate().withBaseRate (24); - } - - return nullopt; - }()); - - const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; - info.setEditOriginTime (effectiveRate != 0.0 ? makeOptional (ti->smpteOffset / (80.0 * effectiveRate)) : nullopt); - } - - info.setIsRecording ((ti->flags & Vst2::kVstTransportRecording) != 0); - info.setIsPlaying ((ti->flags & (Vst2::kVstTransportRecording | Vst2::kVstTransportPlaying)) != 0); - info.setIsLooping ((ti->flags & Vst2::kVstTransportCycleActive) != 0); - - info.setLoopPoints ((ti->flags & Vst2::kVstCyclePosValid) != 0 ? makeOptional (LoopPoints { ti->cycleStartPos, ti->cycleEndPos }) - : nullopt); - - info.setHostTimeNs ((ti->flags & Vst2::kVstNanosValid) != 0 ? makeOptional ((uint64_t) ti->nanoSeconds) : nullopt); - } - - //============================================================================== - Optional getPosition() const override - { - return currentPosition; - } - - //============================================================================== - float getParameter (int32 index) const - { - if (auto* param = juceParameters.getParamForIndex (index)) - return param->getValue(); - - return 0.0f; - } - - static float getParameterCB (Vst2::AEffect* vstInterface, int32 index) - { - return getWrapper (vstInterface)->getParameter (index); - } - - void setParameter (int32 index, float value) - { - if (auto* param = juceParameters.getParamForIndex (index)) - setValueAndNotifyIfChanged (*param, value); - } - - static void setParameterCB (Vst2::AEffect* vstInterface, int32 index, float value) - { - getWrapper (vstInterface)->setParameter (index, value); - } - - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterAutomate, index, 0, nullptr, newValue); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override - { - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterBeginEdit, index, 0, nullptr, 0); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override - { - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterEndEdit, index, 0, nullptr, 0); - } - - void parameterValueChanged (int, float newValue) override - { - // this can only come from the bypass parameter - isBypassed = (newValue >= 0.5f); - } - - void parameterGestureChanged (int, bool) override {} - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - hostChangeUpdater.update (details); - } - - bool getPinProperties (Vst2::VstPinProperties& properties, bool direction, int index) const - { - if (processor->isMidiEffect()) - return false; - - int channelIdx, busIdx; - - // fill with default - properties.flags = 0; - properties.label[0] = 0; - properties.shortLabel[0] = 0; - properties.arrangementType = Vst2::kSpeakerArrEmpty; - - if ((channelIdx = processor->getOffsetInBusBufferForAbsoluteChannelIndex (direction, index, busIdx)) >= 0) - { - auto& bus = *processor->getBus (direction, busIdx); - auto& channelSet = bus.getCurrentLayout(); - auto channelType = channelSet.getTypeOfChannel (channelIdx); - - properties.flags = Vst2::kVstPinIsActive | Vst2::kVstPinUseSpeaker; - properties.arrangementType = SpeakerMappings::channelSetToVstArrangementType (channelSet); - String label = bus.getName(); - - #ifdef JucePlugin_PreferredChannelConfigurations - label += " " + String (channelIdx); - #else - if (channelSet.size() > 1) - label += " " + AudioChannelSet::getAbbreviatedChannelTypeName (channelType); - #endif - - label.copyToUTF8 (properties.label, (size_t) (Vst2::kVstMaxLabelLen + 1)); - label.copyToUTF8 (properties.shortLabel, (size_t) (Vst2::kVstMaxShortLabelLen + 1)); - - if (channelType == AudioChannelSet::left - || channelType == AudioChannelSet::leftSurround - || channelType == AudioChannelSet::leftCentre - || channelType == AudioChannelSet::leftSurroundSide - || channelType == AudioChannelSet::topFrontLeft - || channelType == AudioChannelSet::topRearLeft - || channelType == AudioChannelSet::leftSurroundRear - || channelType == AudioChannelSet::wideLeft) - properties.flags |= Vst2::kVstPinIsStereo; - - return true; - } - - return false; - } - - //============================================================================== - void timerCallback() override - { - if (shouldDeleteEditor) - { - shouldDeleteEditor = false; - deleteEditor (true); - } - - { - ScopedLock lock (stateInformationLock); - - if (chunkMemoryTime > 0 - && chunkMemoryTime < juce::Time::getApproximateMillisecondCounter() - 2000 - && ! recursionCheck) - { - chunkMemory.reset(); - chunkMemoryTime = 0; - } - } - } - - void setHasEditorFlag (bool shouldSetHasEditor) - { - auto hasEditor = (vstEffect.flags & Vst2::effFlagsHasEditor) != 0; - - if (shouldSetHasEditor == hasEditor) - return; - - if (shouldSetHasEditor) - vstEffect.flags |= Vst2::effFlagsHasEditor; - else - vstEffect.flags &= ~Vst2::effFlagsHasEditor; - } - - void createEditorComp() - { - if (hasShutdown || processor == nullptr) - return; - - if (editorComp == nullptr) - { - if (auto* ed = processor->createEditorIfNeeded()) - { - setHasEditorFlag (true); - editorComp.reset (new EditorCompWrapper (*this, *ed, editorScaleFactor)); - } - else - { - setHasEditorFlag (false); - } - } - - shouldDeleteEditor = false; - } - - void deleteEditor (bool canDeleteLaterIfModal) - { - JUCE_AUTORELEASEPOOL - { - PopupMenu::dismissAllActiveMenus(); - - jassert (! recursionCheck); - ScopedValueSetter svs (recursionCheck, true, false); - - if (editorComp != nullptr) - { - if (auto* modalComponent = Component::getCurrentlyModalComponent()) - { - modalComponent->exitModalState (0); - - if (canDeleteLaterIfModal) - { - shouldDeleteEditor = true; - return; - } - } - - editorComp->detachHostWindow(); - - if (auto* ed = editorComp->getEditorComp()) - processor->editorBeingDeleted (ed); - - editorComp = nullptr; - - // there's some kind of component currently modal, but the host - // is trying to delete our plugin. You should try to avoid this happening.. - jassert (Component::getCurrentlyModalComponent() == nullptr); - } - } - } - - pointer_sized_int dispatcher (int32 opCode, VstOpCodeArguments args) - { - if (hasShutdown) - return 0; - - switch (opCode) - { - case Vst2::effOpen: return handleOpen (args); - case Vst2::effClose: return handleClose (args); - case Vst2::effSetProgram: return handleSetCurrentProgram (args); - case Vst2::effGetProgram: return handleGetCurrentProgram (args); - case Vst2::effSetProgramName: return handleSetCurrentProgramName (args); - case Vst2::effGetProgramName: return handleGetCurrentProgramName (args); - case Vst2::effGetParamLabel: return handleGetParameterLabel (args); - case Vst2::effGetParamDisplay: return handleGetParameterText (args); - case Vst2::effGetParamName: return handleGetParameterName (args); - case Vst2::effSetSampleRate: return handleSetSampleRate (args); - case Vst2::effSetBlockSize: return handleSetBlockSize (args); - case Vst2::effMainsChanged: return handleResumeSuspend (args); - case Vst2::effEditGetRect: return handleGetEditorBounds (args); - case Vst2::effEditOpen: return handleOpenEditor (args); - case Vst2::effEditClose: return handleCloseEditor (args); - case Vst2::effIdentify: return (pointer_sized_int) ByteOrder::bigEndianInt ("NvEf"); - case Vst2::effGetChunk: return handleGetData (args); - case Vst2::effSetChunk: return handleSetData (args); - case Vst2::effProcessEvents: return handlePreAudioProcessingEvents (args); - case Vst2::effCanBeAutomated: return handleIsParameterAutomatable (args); - case Vst2::effString2Parameter: return handleParameterValueForText (args); - case Vst2::effGetProgramNameIndexed: return handleGetProgramName (args); - case Vst2::effGetInputProperties: return handleGetInputPinProperties (args); - case Vst2::effGetOutputProperties: return handleGetOutputPinProperties (args); - case Vst2::effGetPlugCategory: return handleGetPlugInCategory (args); - case Vst2::effSetSpeakerArrangement: return handleSetSpeakerConfiguration (args); - case Vst2::effSetBypass: return handleSetBypass (args); - case Vst2::effGetEffectName: return handleGetPlugInName (args); - case Vst2::effGetProductString: return handleGetPlugInName (args); - case Vst2::effGetVendorString: return handleGetManufacturerName (args); - case Vst2::effGetVendorVersion: return handleGetManufacturerVersion (args); - case Vst2::effVendorSpecific: return handleManufacturerSpecific (args); - case Vst2::effCanDo: return handleCanPlugInDo (args); - case Vst2::effGetTailSize: return handleGetTailSize (args); - case Vst2::effKeysRequired: return handleKeyboardFocusRequired (args); - case Vst2::effGetVstVersion: return handleGetVstInterfaceVersion (args); - case Vst2::effGetCurrentMidiProgram: return handleGetCurrentMidiProgram (args); - case Vst2::effGetSpeakerArrangement: return handleGetSpeakerConfiguration (args); - case Vst2::effSetTotalSampleToProcess: return handleSetNumberOfSamplesToProcess (args); - case Vst2::effSetProcessPrecision: return handleSetSampleFloatType (args); - case Vst2::effGetNumMidiInputChannels: return handleGetNumMidiInputChannels(); - case Vst2::effGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels(); - case Vst2::effEditIdle: return handleEditIdle(); - default: return 0; - } - } - - static pointer_sized_int dispatcherCB (Vst2::AEffect* vstInterface, int32 opCode, int32 index, - pointer_sized_int value, void* ptr, float opt) - { - auto* wrapper = getWrapper (vstInterface); - VstOpCodeArguments args = { index, value, ptr, opt }; - - if (opCode == Vst2::effClose) - { - wrapper->dispatcher (opCode, args); - delete wrapper; - return 1; - } - - return wrapper->dispatcher (opCode, args); - } - - //============================================================================== - // A component to hold the AudioProcessorEditor, and cope with some housekeeping - // chores when it changes or repaints. - struct EditorCompWrapper : public Component - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - , public Timer - #endif - { - EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor, [[maybe_unused]] float initialScale) - : wrapper (w) - { - editor.setOpaque (true); - #if ! JUCE_MAC - editor.setScaleFactor (initialScale); - #endif - addAndMakeVisible (editor); - - auto editorBounds = getSizeToContainChild(); - setSize (editorBounds.getWidth(), editorBounds.getHeight()); - - #if JUCE_WINDOWS - if (! detail::PluginUtilities::getHostType().isReceptor()) - addMouseListener (this, true); - #endif - - setOpaque (true); - } - - ~EditorCompWrapper() override - { - deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may - // have been transferred to another parent which takes over ownership. - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::black); - } - - void getEditorBounds (Vst2::ERect& bounds) - { - auto editorBounds = getSizeToContainChild(); - bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); - } - - void attachToHost (VstOpCodeArguments args) - { - setVisible (false); - - const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (getEditorComp()); - - #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD - addToDesktop (desktopFlags, args.ptr); - hostWindow = (HostWindowType) args.ptr; - - #if JUCE_LINUX || JUCE_BSD - X11Symbols::getInstance()->xReparentWindow (display, - (Window) getWindowHandle(), - (HostWindowType) hostWindow, - 0, 0); - // The host is likely to attempt to move/resize the window directly after this call, - // and we need to ensure that the X server knows that our window has been attached - // before that happens. - X11Symbols::getInstance()->xFlush (display); - #elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - checkHostWindowScaleFactor (true); - startTimer (500); - #endif - #elif JUCE_MAC - hostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (this, desktopFlags, args.ptr); - #endif - - setVisible (true); - } - - void detachHostWindow() - { - #if JUCE_MAC - if (hostWindow != nullptr) - detail::VSTWindowUtilities::detachComponentFromWindowRefVST (this, hostWindow); - #endif - - hostWindow = {}; - } - - AudioProcessorEditor* getEditorComp() const noexcept - { - return dynamic_cast (getChildComponent (0)); - } - - void resized() override - { - if (auto* pluginEditor = getEditorComp()) - { - if (! resizingParent) - { - auto newBounds = getLocalBounds(); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); - } - - lastBounds = newBounds; - } - - updateWindowSize(); - } - } - - void parentSizeChanged() override - { - updateWindowSize(); - repaint(); - } - - void childBoundsChanged (Component*) override - { - if (resizingChild) - return; - - auto newBounds = getSizeToContainChild(); - - if (newBounds != lastBounds) - { - updateWindowSize(); - lastBounds = newBounds; - } - } - - juce::Rectangle getSizeToContainChild() - { - if (auto* pluginEditor = getEditorComp()) - return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); - - return {}; - } - - void resizeHostWindow (juce::Rectangle bounds) - { - auto rect = convertToHostBounds ({ 0, 0, (int16) bounds.getHeight(), (int16) bounds.getWidth() }); - const auto newWidth = rect.right - rect.left; - const auto newHeight = rect.bottom - rect.top; - - bool sizeWasSuccessful = false; - - if (auto host = wrapper.hostCallback) - { - auto status = host (wrapper.getAEffect(), Vst2::audioMasterCanDo, 0, 0, const_cast ("sizeWindow"), 0); - - if (status == (pointer_sized_int) 1 || detail::PluginUtilities::getHostType().isAbletonLive()) - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - - sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::audioMasterSizeWindow, - newWidth, newHeight, nullptr, 0) != 0); - } - } - - // some hosts don't support the sizeWindow call, so do it manually.. - if (! sizeWasSuccessful) - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - - #if JUCE_MAC - detail::VSTWindowUtilities::setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight); - #elif JUCE_LINUX || JUCE_BSD - // (Currently, all linux hosts support sizeWindow, so this should never need to happen) - setSize (newWidth, newHeight); - #else - int dw = 0; - int dh = 0; - const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); - - HWND w = (HWND) getWindowHandle(); - - while (w != nullptr) - { - HWND parent = getWindowParent (w); - - if (parent == nullptr) - break; - - TCHAR windowType [32] = { 0 }; - GetClassName (parent, windowType, 31); - - if (String (windowType).equalsIgnoreCase ("MDIClient")) - break; - - RECT windowPos, parentPos; - GetWindowRect (w, &windowPos); - GetWindowRect (parent, &parentPos); - - if (w != (HWND) getWindowHandle()) - SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); - - dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); - dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); - - w = parent; - - if (dw == 2 * frameThickness) - break; - - if (dw > 100 || dh > 100) - w = nullptr; - } - - if (w != nullptr) - SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); - #endif - } - - #if JUCE_LINUX || JUCE_BSD - X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), - static_cast (rect.right - rect.left), - static_cast (rect.bottom - rect.top)); - #endif - } - - void setContentScaleFactor (float scale) - { - if (auto* pluginEditor = getEditorComp()) - { - auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - pluginEditor->setScaleFactor (scale); - pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); - } - - lastBounds = getSizeToContainChild(); - updateWindowSize(); - } - } - - #if JUCE_WINDOWS - void mouseDown (const MouseEvent&) override - { - broughtToFront(); - } - - void broughtToFront() override - { - // for hosts like nuendo, need to also pop the MDI container to the - // front when our comp is clicked on. - if (! isCurrentlyBlockedByAnotherModalComponent()) - if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) - SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } - - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor (bool force = false) - { - auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); - - if (force || (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, wrapper.editorScaleFactor))) - wrapper.handleSetContentScaleFactor (hostWindowScale, force); - } - - void timerCallback() override - { - checkHostWindowScaleFactor(); - } - #endif - #endif - - private: - void updateWindowSize() - { - if (! resizingParent - && getEditorComp() != nullptr - && hostWindow != HostWindowType{}) - { - const auto editorBounds = getSizeToContainChild(); - resizeHostWindow (editorBounds); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - - // setSize() on linux causes renoise and energyxt to fail. - // We'll resize our peer during resizeHostWindow() instead. - #if ! (JUCE_LINUX || JUCE_BSD) - setSize (editorBounds.getWidth(), editorBounds.getHeight()); - #endif - - if (auto* p = getPeer()) - p->updateBounds(); - } - - #if JUCE_MAC - resizeHostWindow (editorBounds); // (doing this a second time seems to be necessary in tracktion) - #endif - } - } - - //============================================================================== - static Vst2::ERect convertToHostBounds (const Vst2::ERect& rect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return rect; - - return { (int16) roundToInt (rect.top * desktopScale), - (int16) roundToInt (rect.left * desktopScale), - (int16) roundToInt (rect.bottom * desktopScale), - (int16) roundToInt (rect.right * desktopScale) }; - } - - //============================================================================== - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostEventLoop; - #endif - - //============================================================================== - JuceVSTWrapper& wrapper; - bool resizingChild = false, resizingParent = false; - - juce::Rectangle lastBounds; - - #if JUCE_LINUX || JUCE_BSD - using HostWindowType = ::Window; - ::Display* display = XWindowSystem::getInstance()->getDisplay(); - #elif JUCE_WINDOWS - using HostWindowType = HWND; - detail::WindowsHooks hooks; - #else - using HostWindowType = void*; - #endif - - HostWindowType hostWindow = {}; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) - }; - - //============================================================================== -private: - struct HostChangeUpdater : private AsyncUpdater - { - explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} - ~HostChangeUpdater() override { cancelPendingUpdate(); } - - void update (const ChangeDetails& details) - { - if (details.latencyChanged) - { - owner.vstEffect.initialDelay = owner.processor->getLatencySamples(); - callbackBits |= audioMasterIOChangedBit; - } - - if (details.parameterInfoChanged || details.programChanged) - callbackBits |= audioMasterUpdateDisplayBit; - - triggerAsyncUpdate(); - } - - private: - void handleAsyncUpdate() override - { - const auto callbacksToFire = callbackBits.exchange (0); - - if (auto* callback = owner.hostCallback) - { - struct FlagPair - { - Vst2::AudioMasterOpcodesX opcode; - int bit; - }; - - constexpr FlagPair pairs[] { { Vst2::audioMasterUpdateDisplay, audioMasterUpdateDisplayBit }, - { Vst2::audioMasterIOChanged, audioMasterIOChangedBit } }; - - for (const auto& pair : pairs) - if ((callbacksToFire & pair.bit) != 0) - callback (&owner.vstEffect, pair.opcode, 0, 0, nullptr, 0); - } - } - - static constexpr auto audioMasterUpdateDisplayBit = 1 << 0; - static constexpr auto audioMasterIOChangedBit = 1 << 1; - - JuceVSTWrapper& owner; - std::atomic callbackBits { 0 }; - }; - - static JuceVSTWrapper* getWrapper (Vst2::AEffect* v) noexcept { return static_cast (v->object); } - - bool isProcessLevelOffline() - { - return hostCallback != nullptr - && (int32) hostCallback (&vstEffect, Vst2::audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0) == 4; - } - - static int32 convertHexVersionToDecimal (const unsigned int hexVersion) - { - #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY - return (int32) hexVersion; - #else - // Currently, only Cubase displays the version number to the user - // We are hoping here that when other DAWs start to display the version - // number, that they do so according to yfede's encoding table in the link - // below. If not, then this code will need an if (isSteinberg()) in the - // future. - int major = (hexVersion >> 16) & 0xff; - int minor = (hexVersion >> 8) & 0xff; - int bugfix = hexVersion & 0xff; - - // for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867 - - // Encoding B - if (major < 1) - return major * 1000 + minor * 100 + bugfix * 10; - - // Encoding E - if (major > 100) - return major * 10000000 + minor * 100000 + bugfix * 1000; - - // Encoding D - return static_cast (hexVersion); - #endif - } - - //============================================================================== - #if JUCE_WINDOWS - // Workarounds for hosts which attempt to open editor windows on a non-GUI thread.. (Grrrr...) - static void checkWhetherMessageThreadIsCorrect() - { - auto host = detail::PluginUtilities::getHostType(); - - if (host.isWavelab() || host.isCubaseBridged() || host.isPremiere()) - { - if (! messageThreadIsDefinitelyCorrect) - { - MessageManager::getInstance()->setCurrentThreadAsMessageThread(); - - struct MessageThreadCallback : public CallbackMessage - { - MessageThreadCallback (bool& tr) : triggered (tr) {} - void messageCallback() override { triggered = true; } - - bool& triggered; - }; - - (new MessageThreadCallback (messageThreadIsDefinitelyCorrect))->post(); - } - } - } - #else - static void checkWhetherMessageThreadIsCorrect() {} - #endif - - void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) - { - if (param.getValue() == newValue) - return; - - inParameterChangedCallback = true; - param.setValueNotifyingHost (newValue); - } - - //============================================================================== - template - void deleteTempChannels (VstTempBuffers& tmpBuffers) - { - tmpBuffers.release(); - - if (processor != nullptr) - tmpBuffers.tempChannels.insertMultiple (0, nullptr, vstEffect.numInputs - + vstEffect.numOutputs); - } - - void deleteTempChannels() - { - deleteTempChannels (floatTempBuffers); - deleteTempChannels (doubleTempBuffers); - } - - //============================================================================== - void findMaxTotalChannels (int& maxTotalIns, int& maxTotalOuts) - { - #ifdef JucePlugin_PreferredChannelConfigurations - int configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - maxTotalIns = maxTotalOuts = 0; - - for (auto& config : configs) - { - maxTotalIns = jmax (maxTotalIns, config[0]); - maxTotalOuts = jmax (maxTotalOuts, config[1]); - } - #else - auto numInputBuses = processor->getBusCount (true); - auto numOutputBuses = processor->getBusCount (false); - - if (numInputBuses > 1 || numOutputBuses > 1) - { - maxTotalIns = maxTotalOuts = 0; - - for (int i = 0; i < numInputBuses; ++i) - maxTotalIns += processor->getChannelCountOfBus (true, i); - - for (int i = 0; i < numOutputBuses; ++i) - maxTotalOuts += processor->getChannelCountOfBus (false, i); - } - else - { - maxTotalIns = numInputBuses > 0 ? processor->getBus (true, 0)->getMaxSupportedChannels (64) : 0; - maxTotalOuts = numOutputBuses > 0 ? processor->getBus (false, 0)->getMaxSupportedChannels (64) : 0; - } - #endif - } - - bool pluginHasSidechainsOrAuxs() const { return (processor->getBusCount (true) > 1 || processor->getBusCount (false) > 1); } - - //============================================================================== - /** Host to plug-in calls. */ - - pointer_sized_int handleOpen (VstOpCodeArguments) - { - // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. - setHasEditorFlag (processor->hasEditor()); - - return 0; - } - - pointer_sized_int handleClose (VstOpCodeArguments) - { - // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. - stopTimer(); - - if (MessageManager::getInstance()->isThisTheMessageThread()) - deleteEditor (false); - - return 0; - } - - pointer_sized_int handleSetCurrentProgram (VstOpCodeArguments args) - { - if (processor != nullptr && isPositiveAndBelow ((int) args.value, processor->getNumPrograms())) - processor->setCurrentProgram ((int) args.value); - - return 0; - } - - pointer_sized_int handleGetCurrentProgram (VstOpCodeArguments) - { - return (processor != nullptr && processor->getNumPrograms() > 0 ? processor->getCurrentProgram() : 0); - } - - pointer_sized_int handleSetCurrentProgramName (VstOpCodeArguments args) - { - if (processor != nullptr && processor->getNumPrograms() > 0) - processor->changeProgramName (processor->getCurrentProgram(), (char*) args.ptr); - - return 0; - } - - pointer_sized_int handleGetCurrentProgramName (VstOpCodeArguments args) - { - if (processor != nullptr && processor->getNumPrograms() > 0) - processor->getProgramName (processor->getCurrentProgram()).copyToUTF8 ((char*) args.ptr, 24 + 1); - - return 0; - } - - pointer_sized_int handleGetParameterLabel (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. - param->getLabel().copyToUTF8 ((char*) args.ptr, 24 + 1); - } - - return 0; - } - - pointer_sized_int handleGetParameterText (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. - param->getCurrentValueAsText().copyToUTF8 ((char*) args.ptr, 24 + 1); - } - - return 0; - } - - pointer_sized_int handleGetParameterName (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. - param->getName (32).copyToUTF8 ((char*) args.ptr, 32 + 1); - } - - return 0; - } - - pointer_sized_int handleSetSampleRate (VstOpCodeArguments args) - { - sampleRate = args.opt; - return 0; - } - - pointer_sized_int handleSetBlockSize (VstOpCodeArguments args) - { - blockSize = (int32) args.value; - return 0; - } - - pointer_sized_int handleResumeSuspend (VstOpCodeArguments args) - { - if (args.value) - resume(); - else - suspend(); - - return 0; - } - - pointer_sized_int handleGetEditorBounds (VstOpCodeArguments args) - { - checkWhetherMessageThreadIsCorrect(); - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - createEditorComp(); - - if (editorComp != nullptr) - { - editorComp->getEditorBounds (editorRect); - *((Vst2::ERect**) args.ptr) = &editorRect; - return (pointer_sized_int) &editorRect; - } - - return 0; - } - - pointer_sized_int handleOpenEditor (VstOpCodeArguments args) - { - checkWhetherMessageThreadIsCorrect(); - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - jassert (! recursionCheck); - - startTimerHz (4); // performs misc housekeeping chores - - deleteEditor (true); - createEditorComp(); - - if (editorComp != nullptr) - { - editorComp->attachToHost (args); - return 1; - } - - return 0; - } - - pointer_sized_int handleCloseEditor (VstOpCodeArguments) - { - checkWhetherMessageThreadIsCorrect(); - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - - deleteEditor (true); - - return 0; - } - - pointer_sized_int handleGetData (VstOpCodeArguments args) - { - if (processor == nullptr) - return 0; - - auto data = (void**) args.ptr; - bool onlyStoreCurrentProgramData = (args.index != 0); - - MemoryBlock block; - - if (onlyStoreCurrentProgramData) - processor->getCurrentProgramStateInformation (block); - else - processor->getStateInformation (block); - - // IMPORTANT! Don't call getStateInfo while holding this lock! - const ScopedLock lock (stateInformationLock); - - chunkMemory = std::move (block); - *data = (void*) chunkMemory.getData(); - - // because the chunk is only needed temporarily by the host (or at least you'd - // hope so) we'll give it a while and then free it in the timer callback. - chunkMemoryTime = juce::Time::getApproximateMillisecondCounter(); - - return (int32) chunkMemory.getSize(); - } - - pointer_sized_int handleSetData (VstOpCodeArguments args) - { - if (processor != nullptr) - { - void* data = args.ptr; - int32 byteSize = (int32) args.value; - bool onlyRestoreCurrentProgramData = (args.index != 0); - - { - const ScopedLock lock (stateInformationLock); - - chunkMemory.reset(); - chunkMemoryTime = 0; - } - - if (byteSize > 0 && data != nullptr) - { - if (onlyRestoreCurrentProgramData) - processor->setCurrentProgramStateInformation (data, byteSize); - else - processor->setStateInformation (data, byteSize); - } - } - - return 0; - } - - pointer_sized_int handlePreAudioProcessingEvents ([[maybe_unused]] VstOpCodeArguments args) - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - VSTMidiEventList::addEventsToMidiBuffer ((Vst2::VstEvents*) args.ptr, midiEvents); - return 1; - #else - return 0; - #endif - } - - pointer_sized_int handleIsParameterAutomatable (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - const bool isMeter = ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2); - return (param->isAutomatable() && (! isMeter) ? 1 : 0); - } - - return 0; - } - - pointer_sized_int handleParameterValueForText (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - if (! LegacyAudioParameter::isLegacy (param)) - { - setValueAndNotifyIfChanged (*param, param->getValueForText (String::fromUTF8 ((char*) args.ptr))); - return 1; - } - } - - return 0; - } - - pointer_sized_int handleGetProgramName (VstOpCodeArguments args) - { - if (processor != nullptr && isPositiveAndBelow (args.index, processor->getNumPrograms())) - { - processor->getProgramName (args.index).copyToUTF8 ((char*) args.ptr, 24 + 1); - return 1; - } - - return 0; - } - - pointer_sized_int handleGetInputPinProperties (VstOpCodeArguments args) - { - return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, true, args.index)) ? 1 : 0; - } - - pointer_sized_int handleGetOutputPinProperties (VstOpCodeArguments args) - { - return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, false, args.index)) ? 1 : 0; - } - - pointer_sized_int handleGetPlugInCategory (VstOpCodeArguments) - { - return Vst2::JucePlugin_VSTCategory; - } - - pointer_sized_int handleSetSpeakerConfiguration (VstOpCodeArguments args) - { - auto* pluginInput = reinterpret_cast (args.value); - auto* pluginOutput = reinterpret_cast (args.ptr); - - if (processor->isMidiEffect()) - return 0; - - auto numIns = processor->getBusCount (true); - auto numOuts = processor->getBusCount (false); - - if (pluginInput != nullptr && pluginInput->type >= 0) - { - // inconsistent request? - if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput).size() != pluginInput->numChannels) - return 0; - } - - if (pluginOutput != nullptr && pluginOutput->type >= 0) - { - // inconsistent request? - if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput).size() != pluginOutput->numChannels) - return 0; - } - - if (pluginInput != nullptr && pluginInput->numChannels > 0 && numIns == 0) - return 0; - - if (pluginOutput != nullptr && pluginOutput->numChannels > 0 && numOuts == 0) - return 0; - - auto layouts = processor->getBusesLayout(); - - if (pluginInput != nullptr && pluginInput-> numChannels >= 0 && numIns > 0) - layouts.getChannelSet (true, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput); - - if (pluginOutput != nullptr && pluginOutput->numChannels >= 0 && numOuts > 0) - layouts.getChannelSet (false, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - if (! AudioProcessor::containsLayout (layouts, configs)) - return 0; - #endif - - return processor->setBusesLayout (layouts) ? 1 : 0; - } - - pointer_sized_int handleSetBypass (VstOpCodeArguments args) - { - isBypassed = args.value != 0; - - if (auto* param = processor->getBypassParameter()) - param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); - - return 1; - } - - pointer_sized_int handleGetPlugInName (VstOpCodeArguments args) - { - String (JucePlugin_Name).copyToUTF8 ((char*) args.ptr, 64 + 1); - return 1; - } - - pointer_sized_int handleGetManufacturerName (VstOpCodeArguments args) - { - String (JucePlugin_Manufacturer).copyToUTF8 ((char*) args.ptr, 64 + 1); - return 1; - } - - pointer_sized_int handleGetManufacturerVersion (VstOpCodeArguments) - { - return convertHexVersionToDecimal (JucePlugin_VersionCode); - } - - pointer_sized_int handleManufacturerSpecific (VstOpCodeArguments args) - { - if (detail::PluginUtilities::handleManufacturerSpecificVST2Opcode (args.index, args.value, args.ptr, args.opt)) - return 1; - - if (args.index == (int32) ByteOrder::bigEndianInt ("PreS") - && args.value == (int32) ByteOrder::bigEndianInt ("AeCs")) - return handleSetContentScaleFactor (args.opt); - - if (args.index == Vst2::effGetParamDisplay) - return handleCockosGetParameterText (args.value, args.ptr, args.opt); - - if (auto callbackHandler = dynamic_cast (processor.get())) - return callbackHandler->handleVstManufacturerSpecific (args.index, args.value, args.ptr, args.opt); - - return 0; - } - - pointer_sized_int handleCanPlugInDo (VstOpCodeArguments args) - { - auto text = (const char*) args.ptr; - auto matches = [=] (const char* s) { return strcmp (text, s) == 0; }; - - if (matches ("receiveVstEvents") - || matches ("receiveVstMidiEvent") - || matches ("receiveVstMidiEvents")) - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - return 1; - #else - return -1; - #endif - } - - if (matches ("sendVstEvents") - || matches ("sendVstMidiEvent") - || matches ("sendVstMidiEvents")) - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - return 1; - #else - return -1; - #endif - } - - if (matches ("receiveVstTimeInfo") - || matches ("conformsToWindowRules") - || matches ("supportsViewDpiScaling") - || matches ("bypass")) - { - return 1; - } - - // This tells Wavelab to use the UI thread to invoke open/close, - // like all other hosts do. - if (matches ("openCloseAnyThread")) - return -1; - - if (matches ("MPE")) - return processor->supportsMPE() ? 1 : 0; - - #if JUCE_MAC - if (matches ("hasCockosViewAsConfig")) - { - return (int32) 0xbeef0000; - } - #endif - - if (matches ("hasCockosExtensions")) - return (int32) 0xbeef0000; - - if (auto callbackHandler = dynamic_cast (processor.get())) - return callbackHandler->handleVstPluginCanDo (args.index, args.value, args.ptr, args.opt); - - return 0; - } - - pointer_sized_int handleGetTailSize (VstOpCodeArguments) - { - if (processor != nullptr) - { - int32 result; - - auto tailSeconds = processor->getTailLengthSeconds(); - - if (tailSeconds == std::numeric_limits::infinity()) - result = std::numeric_limits::max(); - else - result = static_cast (tailSeconds * sampleRate); - - return result; // Vst2 expects an int32 upcasted to a intptr_t here - } - - return 0; - } - - pointer_sized_int handleKeyboardFocusRequired (VstOpCodeArguments) - { - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6326) - return (JucePlugin_EditorRequiresKeyboardFocus != 0) ? 1 : 0; - JUCE_END_IGNORE_WARNINGS_MSVC - } - - pointer_sized_int handleGetVstInterfaceVersion (VstOpCodeArguments) - { - return kVstVersion; - } - - pointer_sized_int handleGetCurrentMidiProgram (VstOpCodeArguments) - { - return -1; - } - - pointer_sized_int handleGetSpeakerConfiguration (VstOpCodeArguments args) - { - auto** pluginInput = reinterpret_cast (args.value); - auto** pluginOutput = reinterpret_cast (args.ptr); - - if (pluginHasSidechainsOrAuxs() || processor->isMidiEffect()) - return false; - - auto inputLayout = processor->getChannelLayoutOfBus (true, 0); - auto outputLayout = processor->getChannelLayoutOfBus (false, 0); - - const auto speakerBaseSize = offsetof (Vst2::VstSpeakerArrangement, speakers); - - cachedInArrangement .malloc (speakerBaseSize + (static_cast (inputLayout. size()) * sizeof (Vst2::VstSpeakerProperties)), 1); - cachedOutArrangement.malloc (speakerBaseSize + (static_cast (outputLayout.size()) * sizeof (Vst2::VstSpeakerProperties)), 1); - - *pluginInput = cachedInArrangement. getData(); - *pluginOutput = cachedOutArrangement.getData(); - - SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (true, 0), **pluginInput); - SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (false, 0), **pluginOutput); - - return 1; - } - - pointer_sized_int handleSetNumberOfSamplesToProcess (VstOpCodeArguments args) - { - return args.value; - } - - pointer_sized_int handleSetSampleFloatType (VstOpCodeArguments args) - { - if (! isProcessing) - { - if (processor != nullptr) - { - processor->setProcessingPrecision ((args.value == Vst2::kVstProcessPrecision64 - && processor->supportsDoublePrecisionProcessing()) - ? AudioProcessor::doublePrecision - : AudioProcessor::singlePrecision); - - return 1; - } - } - - return 0; - } - - pointer_sized_int handleSetContentScaleFactor ([[maybe_unused]] float scale, [[maybe_unused]] bool force = false) - { - checkWhetherMessageThreadIsCorrect(); - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - - #if ! JUCE_MAC - if (force || ! approximatelyEqual (scale, editorScaleFactor)) - { - editorScaleFactor = scale; - - if (editorComp != nullptr) - editorComp->setContentScaleFactor (editorScaleFactor); - } - #endif - - return 1; - } - - pointer_sized_int handleCockosGetParameterText (pointer_sized_int paramIndex, - void* dest, - float value) - { - if (processor != nullptr && dest != nullptr) - { - if (auto* param = juceParameters.getParamForIndex ((int) paramIndex)) - { - if (! LegacyAudioParameter::isLegacy (param)) - { - String text (param->getText (value, 1024)); - memcpy (dest, text.toRawUTF8(), ((size_t) text.length()) + 1); - return 0xbeef; - } - } - } - - return 0; - } - - //============================================================================== - pointer_sized_int handleGetNumMidiInputChannels() - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - #ifdef JucePlugin_VSTNumMidiInputs - return JucePlugin_VSTNumMidiInputs; - #else - return 16; - #endif - #else - return 0; - #endif - } - - pointer_sized_int handleGetNumMidiOutputChannels() - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - #ifdef JucePlugin_VSTNumMidiOutputs - return JucePlugin_VSTNumMidiOutputs; - #else - return 16; - #endif - #else - return 0; - #endif - } - - pointer_sized_int handleEditIdle() - { - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - hostDrivenEventLoop->processPendingEvents(); - #endif - - return 0; - } - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - Vst2::audioMasterCallback hostCallback; - std::unique_ptr processor; - double sampleRate = 44100.0; - int32 blockSize = 1024; - Vst2::AEffect vstEffect; - CriticalSection stateInformationLock; - juce::MemoryBlock chunkMemory; - uint32 chunkMemoryTime = 0; - float editorScaleFactor = 1.0f; - std::unique_ptr editorComp; - Vst2::ERect editorRect; - MidiBuffer midiEvents; - VSTMidiEventList outgoingEvents; - Optional currentPosition; - - LegacyAudioParametersWrapper juceParameters; - - bool isProcessing = false, isBypassed = false, hasShutdown = false; - bool firstProcessCallback = true, shouldDeleteEditor = false; - - VstTempBuffers floatTempBuffers; - VstTempBuffers doubleTempBuffers; - int maxNumInChannels = 0, maxNumOutChannels = 0; - - HeapBlock cachedInArrangement, cachedOutArrangement; - - ThreadLocalValue inParameterChangedCallback; - - HostChangeUpdater hostChangeUpdater { *this }; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) -}; - - -//============================================================================== -namespace -{ - Vst2::AEffect* pluginEntryPoint (Vst2::audioMasterCallback audioMaster) - { - JUCE_AUTORELEASEPOOL - { - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #endif - - try - { - if (audioMaster (nullptr, Vst2::audioMasterVersion, 0, 0, nullptr, 0) != 0) - { - std::unique_ptr processor { createPluginFilterOfType (AudioProcessor::wrapperType_VST) }; - auto* processorPtr = processor.get(); - auto* wrapper = new JuceVSTWrapper (audioMaster, std::move (processor)); - auto* aEffect = wrapper->getAEffect(); - - if (auto* callbackHandler = dynamic_cast (processorPtr)) - { - callbackHandler->handleVstHostCallbackAvailable ([audioMaster, aEffect] (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) - { - return audioMaster (aEffect, opcode, index, value, ptr, opt); - }); - } - - return aEffect; - } - } - catch (...) - {} - } - - return nullptr; - } -} - -#if ! JUCE_WINDOWS - #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) -#endif - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -//============================================================================== -// Mac startup code.. -#if JUCE_MAC - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) - { - return pluginEntryPoint (audioMaster); - } - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster) - { - return pluginEntryPoint (audioMaster); - } - -//============================================================================== -// Linux startup code.. -#elif JUCE_LINUX || JUCE_BSD - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) - { - return pluginEntryPoint (audioMaster); - } - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) asm ("main"); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) - { - return VSTPluginMain (audioMaster); - } - - // don't put initialiseJuce_GUI or shutdownJuce_GUI in these... it will crash! - __attribute__((constructor)) void myPluginInit() {} - __attribute__((destructor)) void myPluginFini() {} - -//============================================================================== -// Win32 startup code.. -#else - - extern "C" __declspec (dllexport) Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) - { - return pluginEntryPoint (audioMaster); - } - - #if ! defined (JUCE_64BIT) && JUCE_MSVC // (can't compile this on win64, but it's not needed anyway with VST2.4) - extern "C" __declspec (dllexport) int main (Vst2::audioMasterCallback audioMaster) - { - return (int) pluginEntryPoint (audioMaster); - } - #endif - - extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) - { - if (reason == DLL_PROCESS_ATTACH) - Process::setCurrentModuleInstanceHandle (instance); - - return true; - } -#endif - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -JUCE_END_IGNORE_WARNINGS_MSVC - -#endif diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp deleted file mode 100644 index 818488e007..0000000000 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ /dev/null @@ -1,4281 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include - -//============================================================================== -#if JucePlugin_Build_VST3 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD) - -JUCE_BEGIN_NO_SANITIZE ("vptr") - -#if JUCE_PLUGINHOST_VST3 - #if JUCE_MAC - #include - #endif - #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY - #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 -#endif - -#include - -#undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifndef JUCE_VST3_CAN_REPLACE_VST2 - #define JUCE_VST3_CAN_REPLACE_VST2 1 -#endif - -#if JUCE_VST3_CAN_REPLACE_VST2 - - #if ! JUCE_MSVC && ! defined (__cdecl) - #define __cdecl - #endif - - namespace Vst2 - { - struct AEffect; - #include "pluginterfaces/vst2.x/vstfxstore.h" - } - -#endif - -#ifndef JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - #if JucePlugin_WantsMidiInput - #define JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS 1 - #endif -#endif - -#if JUCE_LINUX || JUCE_BSD - #include - #include -#endif - -#if JUCE_MAC - #include -#endif - -//============================================================================== -#if JucePlugin_Enable_ARA - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wpragma-pack") - #include - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - #if ARA_SUPPORT_VERSION_1 - #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current implementation" - #endif - - DEF_CLASS_IID(ARA::IPlugInEntryPoint) - DEF_CLASS_IID(ARA::IPlugInEntryPoint2) - DEF_CLASS_IID(ARA::IMainFactory) -#endif - -namespace juce -{ - -using namespace Steinberg; - -//============================================================================== -#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - double getScaleFactorForWindow (HWND); -#endif - -//============================================================================== -#if JUCE_LINUX || JUCE_BSD - -enum class HostMessageThreadAttached { no, yes }; - -class HostMessageThreadState -{ -public: - template - void setStateWithAction (HostMessageThreadAttached stateIn, Callback&& action) - { - const std::lock_guard lock { m }; - state = stateIn; - action(); - } - - void assertHostMessageThread() - { - const std::lock_guard lock { m }; - - if (state == HostMessageThreadAttached::no) - return; - - JUCE_ASSERT_MESSAGE_THREAD - } - -private: - HostMessageThreadAttached state = HostMessageThreadAttached::no; - std::mutex m; -}; - -class EventHandler final : public Steinberg::Linux::IEventHandler, - private LinuxEventLoopInternal::Listener -{ -public: - EventHandler() - { - LinuxEventLoopInternal::registerLinuxEventLoopListener (*this); - } - - ~EventHandler() override - { - jassert (hostRunLoops.empty()); - - LinuxEventLoopInternal::deregisterLinuxEventLoopListener (*this); - - if (! messageThread->isRunning()) - hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::no, - [this]() { messageThread->start(); }); - } - - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - return testFor (*this, targetIID, UniqueBase{}).extract (obj); - } - - void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override - { - updateCurrentMessageThread(); - LinuxEventLoopInternal::invokeEventLoopCallbackForFd (fd); - } - - //============================================================================== - void registerHandlerForFrame (IPlugFrame* plugFrame) - { - if (auto* runLoop = getRunLoopFromFrame (plugFrame)) - { - refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.insert (runLoop); }); - updateCurrentMessageThread(); - } - } - - void unregisterHandlerForFrame (IPlugFrame* plugFrame) - { - if (auto* runLoop = getRunLoopFromFrame (plugFrame)) - refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.erase (runLoop); }); - } - - /* Asserts if it can be established that the calling thread is different from the host's message - thread. - - On Linux this can only be determined if the host has already registered its run loop. Until - then JUCE messages are serviced by a background thread internal to the plugin. - */ - static void assertHostMessageThread() - { - hostMessageThreadState.assertHostMessageThread(); - } - -private: - //============================================================================== - /* Connects all known FDs to a single host event loop instance. */ - class AttachedEventLoop - { - public: - AttachedEventLoop() = default; - - AttachedEventLoop (Steinberg::Linux::IRunLoop* loopIn, Steinberg::Linux::IEventHandler* handlerIn) - : loop (loopIn), handler (handlerIn) - { - for (auto& fd : LinuxEventLoopInternal::getRegisteredFds()) - loop->registerEventHandler (handler, fd); - } - - AttachedEventLoop (AttachedEventLoop&& other) noexcept - { - swap (other); - } - - AttachedEventLoop& operator= (AttachedEventLoop&& other) noexcept - { - swap (other); - return *this; - } - - AttachedEventLoop (const AttachedEventLoop&) = delete; - AttachedEventLoop& operator= (const AttachedEventLoop&) = delete; - - ~AttachedEventLoop() - { - if (loop == nullptr) - return; - - loop->unregisterEventHandler (handler); - } - - private: - void swap (AttachedEventLoop& other) - { - std::swap (other.loop, loop); - std::swap (other.handler, handler); - } - - Steinberg::Linux::IRunLoop* loop = nullptr; - Steinberg::Linux::IEventHandler* handler = nullptr; - }; - - //============================================================================== - static Steinberg::Linux::IRunLoop* getRunLoopFromFrame (IPlugFrame* plugFrame) - { - Steinberg::Linux::IRunLoop* runLoop = nullptr; - - if (plugFrame != nullptr) - plugFrame->queryInterface (Steinberg::Linux::IRunLoop::iid, (void**) &runLoop); - - jassert (runLoop != nullptr); - return runLoop; - } - - void updateCurrentMessageThread() - { - if (! MessageManager::getInstance()->isThisTheMessageThread()) - { - if (messageThread->isRunning()) - messageThread->stop(); - - hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::yes, - [] { MessageManager::getInstance()->setCurrentThreadAsMessageThread(); }); - } - } - - void fdCallbacksChanged() override - { - // The set of active FDs has changed, so deregister from the current event loop and then - // re-register the current set of FDs. - refreshAttachedEventLoop ([]{}); - } - - /* Deregisters from any attached event loop, updates the set of known event loops, and then - attaches all FDs to the first known event loop. - - The same event loop instance is shared between all plugin instances. Every time an event - loop is added or removed, this function should be called to register all FDs with a - suitable event loop. - - Note that there's no API to deregister a single FD for a given event loop. Instead, we must - deregister all FDs, and then register all known FDs again. - */ - template - void refreshAttachedEventLoop (Callback&& modifyKnownRunLoops) - { - // Deregister the old event loop. - // It's important to call the destructor from the old attached loop before calling the - // constructor of the new attached loop. - attachedEventLoop = AttachedEventLoop(); - - modifyKnownRunLoops(); - - // If we still know about an extant event loop, attach to it. - if (hostRunLoops.begin() != hostRunLoops.end()) - attachedEventLoop = AttachedEventLoop (*hostRunLoops.begin(), this); - } - - SharedResourcePointer messageThread; - - std::atomic refCount { 1 }; - - std::multiset hostRunLoops; - AttachedEventLoop attachedEventLoop; - - static HostMessageThreadState hostMessageThreadState; - - //============================================================================== - JUCE_DECLARE_NON_MOVEABLE (EventHandler) - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler) -}; - -HostMessageThreadState EventHandler::hostMessageThreadState; - -#endif - -static void assertHostMessageThread() -{ - #if JUCE_LINUX || JUCE_BSD - EventHandler::assertHostMessageThread(); - #else - JUCE_ASSERT_MESSAGE_THREAD - #endif -} - -//============================================================================== -class InParameterChangedCallbackSetter -{ -public: - explicit InParameterChangedCallbackSetter (bool& ref) - : inner ([&]() -> auto& { jassert (! ref); return ref; }(), true, false) {} - -private: - ScopedValueSetter inner; -}; - -template -static QueryInterfaceResult queryAdditionalInterfaces (AudioProcessor* processor, - const TUID targetIID, - Member&& member) -{ - if (processor == nullptr) - return {}; - - void* obj = nullptr; - - if (auto* extensions = dynamic_cast (processor)) - { - const auto result = (extensions->*member) (targetIID, &obj); - return { result, obj }; - } - - return {}; -} - -static tresult extractResult (const QueryInterfaceResult& userInterface, - const InterfaceResultWithDeferredAddRef& juceInterface, - void** obj) -{ - if (userInterface.isOk() && juceInterface.isOk()) - { - // If you hit this assertion, you've provided a custom implementation of an interface - // that JUCE implements already. As a result, your plugin may not behave correctly. - // Consider removing your custom implementation. - jassertfalse; - - return userInterface.extract (obj); - } - - if (userInterface.isOk()) - return userInterface.extract (obj); - - return juceInterface.extract (obj); -} - -//============================================================================== -class JuceAudioProcessor : public Vst::IUnitInfo -{ -public: - explicit JuceAudioProcessor (AudioProcessor* source) noexcept - : audioProcessor (source) - { - setupParameters(); - } - - virtual ~JuceAudioProcessor() = default; - - AudioProcessor* get() const noexcept { return audioProcessor.get(); } - - JUCE_DECLARE_VST3_COM_QUERY_METHODS - JUCE_DECLARE_VST3_COM_REF_METHODS - - //============================================================================== - enum InternalParameters - { - paramPreset = 0x70727374, // 'prst' - paramMidiControllerOffset = 0x6d636d00, // 'mdm*' - paramBypass = 0x62797073 // 'byps' - }; - - //============================================================================== - Steinberg::int32 PLUGIN_API getUnitCount() override - { - return parameterGroups.size() + 1; - } - - tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override - { - if (unitIndex == 0) - { - info.id = Vst::kRootUnitId; - info.parentUnitId = Vst::kNoParentUnitId; - info.programListId = getProgramListCount() > 0 - ? static_cast (programParamID) - : Vst::kNoProgramListId; - - toString128 (info.name, TRANS ("Root Unit")); - - return kResultTrue; - } - - if (auto* group = parameterGroups[unitIndex - 1]) - { - info.id = JuceAudioProcessor::getUnitID (group); - info.parentUnitId = JuceAudioProcessor::getUnitID (group->getParent()); - info.programListId = Vst::kNoProgramListId; - - toString128 (info.name, group->getName()); - - return kResultTrue; - } - - return kResultFalse; - } - - Steinberg::int32 PLUGIN_API getProgramListCount() override - { - if (audioProcessor->getNumPrograms() > 0) - return 1; - - return 0; - } - - tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override - { - if (listIndex == 0) - { - info.id = static_cast (programParamID); - info.programCount = static_cast (audioProcessor->getNumPrograms()); - - toString128 (info.name, TRANS ("Factory Presets")); - - return kResultTrue; - } - - jassertfalse; - zerostruct (info); - return kResultFalse; - } - - tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override - { - if (listId == static_cast (programParamID) - && isPositiveAndBelow ((int) programIndex, audioProcessor->getNumPrograms())) - { - toString128 (name, audioProcessor->getProgramName ((int) programIndex)); - return kResultTrue; - } - - jassertfalse; - toString128 (name, juce::String()); - return kResultFalse; - } - - tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override { return kNotImplemented; } - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override { return kNotImplemented; } - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override { return kNotImplemented; } - tresult PLUGIN_API selectUnit (Vst::UnitID) override { return kNotImplemented; } - tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } - Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } - - tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection, Steinberg::int32, Steinberg::int32, Vst::UnitID& unitId) override - { - unitId = Vst::kRootUnitId; - return kResultOk; - } - - //============================================================================== - inline Vst::ParamID getVSTParamIDForIndex (int paramIndex) const noexcept - { - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - return static_cast (paramIndex); - #else - return vstParamIDs.getReference (paramIndex); - #endif - } - - AudioProcessorParameter* getParamForVSTParamID (Vst::ParamID paramID) const noexcept - { - return paramMap[static_cast (paramID)]; - } - - AudioProcessorParameter* getBypassParameter() const noexcept - { - return getParamForVSTParamID (bypassParamID); - } - - AudioProcessorParameter* getProgramParameter() const noexcept - { - return getParamForVSTParamID (programParamID); - } - - static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) - { - if (group == nullptr || group->getParent() == nullptr) - return Vst::kRootUnitId; - - // From the VST3 docs (also applicable to unit IDs!): - // Up to 2^31 parameters can be exported with id range [0, 2147483648] - // (the range [2147483649, 429496729] is reserved for host application). - auto unitID = group->getID().hashCode() & 0x7fffffff; - - // If you hit this assertion then your group ID is hashing to a value - // reserved by the VST3 SDK. Please use a different group ID. - jassert (unitID != Vst::kRootUnitId); - - return unitID; - } - - const Array& getParamIDs() const noexcept { return vstParamIDs; } - Vst::ParamID getBypassParamID() const noexcept { return bypassParamID; } - Vst::ParamID getProgramParamID() const noexcept { return programParamID; } - bool isBypassRegularParameter() const noexcept { return bypassIsRegularParameter; } - - int findCacheIndexForParamID (Vst::ParamID paramID) const noexcept { return vstParamIDs.indexOf (paramID); } - - void setParameterValue (Steinberg::int32 paramIndex, float value) - { - cachedParamValues.set (paramIndex, value); - } - - template - void forAllChangedParameters (Callback&& callback) - { - cachedParamValues.ifSet ([&] (Steinberg::int32 index, float value) - { - callback (cachedParamValues.getParamID (index), value); - }); - } - - bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } - - //============================================================================== - static const FUID iid; - -private: - //============================================================================== - void setupParameters() - { - parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); - - #if JUCE_DEBUG - auto allGroups = parameterGroups; - allGroups.add (&audioProcessor->getParameterTree()); - std::unordered_set unitIDs; - - for (auto* group : allGroups) - { - auto insertResult = unitIDs.insert (getUnitID (group)); - - // If you hit this assertion then either a group ID is not unique or - // you are very unlucky and a hashed group ID is not unique - jassert (insertResult.second); - } - #endif - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - const bool forceLegacyParamIDs = true; - #else - const bool forceLegacyParamIDs = false; - #endif - - juceParameters.update (*audioProcessor, forceLegacyParamIDs); - auto numParameters = juceParameters.getNumParameters(); - - bool vst3WrapperProvidedBypassParam = false; - auto* bypassParameter = audioProcessor->getBypassParameter(); - - if (bypassParameter == nullptr) - { - vst3WrapperProvidedBypassParam = true; - ownedBypassParameter.reset (new AudioParameterBool ("byps", "Bypass", false)); - bypassParameter = ownedBypassParameter.get(); - } - - // if the bypass parameter is not part of the exported parameters that the plug-in supports - // then add it to the end of the list as VST3 requires the bypass parameter to be exported! - bypassIsRegularParameter = juceParameters.contains (audioProcessor->getBypassParameter()); - - if (! bypassIsRegularParameter) - juceParameters.addNonOwning (bypassParameter); - - int i = 0; - for (auto* juceParam : juceParameters) - { - bool isBypassParameter = (juceParam == bypassParameter); - - Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast (i++) - : generateVSTParamIDForParam (juceParam); - - if (isBypassParameter) - { - // we need to remain backward compatible with the old bypass id - if (vst3WrapperProvidedBypassParam) - { - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6240) - vstParamID = static_cast ((isUsingManagedParameters() && ! forceLegacyParamIDs) ? paramBypass : numParameters); - JUCE_END_IGNORE_WARNINGS_MSVC - } - - bypassParamID = vstParamID; - } - - vstParamIDs.add (vstParamID); - paramMap.set (static_cast (vstParamID), juceParam); - } - - auto numPrograms = audioProcessor->getNumPrograms(); - - if (numPrograms > 1) - { - ownedProgramParameter = std::make_unique ("juceProgramParameter", "Program", - 0, numPrograms - 1, - audioProcessor->getCurrentProgram()); - - juceParameters.addNonOwning (ownedProgramParameter.get()); - - if (forceLegacyParamIDs) - programParamID = static_cast (i++); - - vstParamIDs.add (programParamID); - paramMap.set (static_cast (programParamID), ownedProgramParameter.get()); - } - - cachedParamValues = CachedParamValues { { vstParamIDs.begin(), vstParamIDs.end() } }; - } - - Vst::ParamID generateVSTParamIDForParam (const AudioProcessorParameter* param) - { - auto juceParamID = LegacyAudioParameter::getParamID (param, false); - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - return static_cast (juceParamID.getIntValue()); - #else - auto paramHash = static_cast (juceParamID.hashCode()); - - #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS - // studio one doesn't like negative parameters - paramHash &= ~(((Vst::ParamID) 1) << (sizeof (Vst::ParamID) * 8 - 1)); - #endif - - return paramHash; - #endif - } - - //============================================================================== - Array vstParamIDs; - CachedParamValues cachedParamValues; - Vst::ParamID bypassParamID = 0, programParamID = static_cast (paramPreset); - bool bypassIsRegularParameter = false; - - //============================================================================== - std::atomic refCount { 0 }; - std::unique_ptr audioProcessor; - - //============================================================================== - LegacyAudioParametersWrapper juceParameters; - HashMap paramMap; - std::unique_ptr ownedBypassParameter, ownedProgramParameter; - Array parameterGroups; - - JuceAudioProcessor() = delete; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor) -}; - -class JuceVST3Component; - -static thread_local bool inParameterChangedCallback = false; - -static void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) -{ - if (param.getValue() == newValue) - return; - - const InParameterChangedCallbackSetter scopedSetter { inParameterChangedCallback }; - param.setValueNotifyingHost (newValue); -} - -//============================================================================== -class JuceVST3EditController : public Vst::EditController, - public Vst::IMidiMapping, - public Vst::IUnitInfo, - public Vst::ChannelContext::IInfoListener, - #if JucePlugin_Enable_ARA - public Presonus::IPlugInViewEmbedding, - #endif - public AudioProcessorListener, - private ComponentRestarter::Listener -{ -public: - explicit JuceVST3EditController (Vst::IHostApplication* host) - { - if (host != nullptr) - host->queryInterface (FUnknown::iid, (void**) &hostContext); - - blueCatPatchwork |= isBlueCatHost (host); - } - - //============================================================================== - static const FUID iid; - - //============================================================================== - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Winconsistent-missing-override") - - REFCOUNT_METHODS (ComponentBase) - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto userProvidedInterface = queryAdditionalInterfaces (getPluginInstance(), - targetIID, - &VST3ClientExtensions::queryIEditController); - - const auto juceProvidedInterface = queryInterfaceInternal (targetIID); - - return extractResult (userProvidedInterface, juceProvidedInterface, obj); - } - - //============================================================================== - tresult PLUGIN_API initialize (FUnknown* context) override - { - if (hostContext != context) - hostContext = context; - - blueCatPatchwork |= isBlueCatHost (context); - - return kResultTrue; - } - - tresult PLUGIN_API terminate() override - { - if (auto* pluginInstance = getPluginInstance()) - pluginInstance->removeListener (this); - - audioProcessor = nullptr; - - return EditController::terminate(); - } - - //============================================================================== - struct Param : public Vst::Parameter - { - Param (JuceVST3EditController& editController, AudioProcessorParameter& p, - Vst::ParamID vstParamID, Vst::UnitID vstUnitID, - bool isBypassParameter) - : owner (editController), param (p) - { - info.id = vstParamID; - info.unitId = vstUnitID; - - updateParameterInfo(); - - info.stepCount = (Steinberg::int32) 0; - - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - if (param.isDiscrete()) - #endif - { - const int numSteps = param.getNumSteps(); - info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); - } - - info.defaultNormalizedValue = param.getDefaultValue(); - jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); - - // Is this a meter? - if ((((unsigned int) param.getCategory() & 0xffff0000) >> 16) == 2) - info.flags = Vst::ParameterInfo::kIsReadOnly; - else - info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; - - if (isBypassParameter) - info.flags |= Vst::ParameterInfo::kIsBypass; - - valueNormalized = info.defaultNormalizedValue; - } - - bool updateParameterInfo() - { - auto updateParamIfChanged = [] (Vst::String128& paramToUpdate, const String& newValue) - { - if (juce::toString (paramToUpdate) == newValue) - return false; - - toString128 (paramToUpdate, newValue); - return true; - }; - - auto anyUpdated = updateParamIfChanged (info.title, param.getName (128)); - anyUpdated |= updateParamIfChanged (info.shortTitle, param.getName (8)); - anyUpdated |= updateParamIfChanged (info.units, param.getLabel()); - - return anyUpdated; - } - - bool setNormalized (Vst::ParamValue v) override - { - v = jlimit (0.0, 1.0, v); - - if (v != valueNormalized) - { - valueNormalized = v; - - // Only update the AudioProcessor here if we're not playing, - // otherwise we get parallel streams of parameter value updates - // during playback - if (! owner.vst3IsPlaying) - setValueAndNotifyIfChanged (param, (float) v); - - changed(); - return true; - } - - return false; - } - - void toString (Vst::ParamValue value, Vst::String128 result) const override - { - if (LegacyAudioParameter::isLegacy (¶m)) - // remain backward-compatible with old JUCE code - toString128 (result, param.getCurrentValueAsText()); - else - toString128 (result, param.getText ((float) value, 128)); - } - - bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override - { - if (! LegacyAudioParameter::isLegacy (¶m)) - { - outValueNormalized = param.getValueForText (getStringFromVstTChars (text)); - return true; - } - - return false; - } - - static String getStringFromVstTChars (const Vst::TChar* text) - { - return juce::String (juce::CharPointer_UTF16 (reinterpret_cast (text))); - } - - Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v; } - Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v; } - - private: - JuceVST3EditController& owner; - AudioProcessorParameter& param; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param) - }; - - //============================================================================== - struct ProgramChangeParameter : public Vst::Parameter - { - ProgramChangeParameter (AudioProcessor& p, Vst::ParamID vstParamID) - : owner (p) - { - jassert (owner.getNumPrograms() > 1); - - info.id = vstParamID; - toString128 (info.title, "Program"); - toString128 (info.shortTitle, "Program"); - toString128 (info.units, ""); - info.stepCount = owner.getNumPrograms() - 1; - info.defaultNormalizedValue = static_cast (owner.getCurrentProgram()) - / static_cast (info.stepCount); - info.unitId = Vst::kRootUnitId; - info.flags = Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kCanAutomate; - } - - ~ProgramChangeParameter() override = default; - - bool setNormalized (Vst::ParamValue v) override - { - const auto programValue = getProgramValueFromNormalised (v); - - if (programValue != owner.getCurrentProgram()) - owner.setCurrentProgram (programValue); - - if (valueNormalized != v) - { - valueNormalized = v; - changed(); - - return true; - } - - return false; - } - - void toString (Vst::ParamValue value, Vst::String128 result) const override - { - toString128 (result, owner.getProgramName (roundToInt (value * info.stepCount))); - } - - bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override - { - auto paramValueString = getStringFromVstTChars (text); - auto n = owner.getNumPrograms(); - - for (int i = 0; i < n; ++i) - { - if (paramValueString == owner.getProgramName (i)) - { - outValueNormalized = static_cast (i) / info.stepCount; - return true; - } - } - - return false; - } - - static String getStringFromVstTChars (const Vst::TChar* text) - { - return String (CharPointer_UTF16 (reinterpret_cast (text))); - } - - Steinberg::int32 getProgramValueFromNormalised (Vst::ParamValue v) const - { - return jmin (info.stepCount, (Steinberg::int32) (v * (info.stepCount + 1))); - } - - Vst::ParamValue toPlain (Vst::ParamValue v) const override { return getProgramValueFromNormalised (v); } - Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v / info.stepCount; } - - private: - AudioProcessor& owner; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) - }; - - //============================================================================== - tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override - { - if (auto* instance = getPluginInstance()) - { - if (list != nullptr) - { - AudioProcessor::TrackProperties trackProperties; - - { - Vst::String128 channelName; - if (list->getString (Vst::ChannelContext::kChannelNameKey, channelName, sizeof (channelName)) == kResultTrue) - trackProperties.name = toString (channelName); - } - - { - int64 colour; - if (list->getInt (Vst::ChannelContext::kChannelColorKey, colour) == kResultTrue) - trackProperties.colour = Colour (Vst::ChannelContext::GetRed ((uint32) colour), Vst::ChannelContext::GetGreen ((uint32) colour), - Vst::ChannelContext::GetBlue ((uint32) colour), Vst::ChannelContext::GetAlpha ((uint32) colour)); - } - - - - if (MessageManager::getInstance()->isThisTheMessageThread()) - instance->updateTrackProperties (trackProperties); - else - MessageManager::callAsync ([trackProperties, instance] - { instance->updateTrackProperties (trackProperties); }); - } - } - - return kResultOk; - } - - //============================================================================== - #if JucePlugin_Enable_ARA - Steinberg::TBool PLUGIN_API isViewEmbeddingSupported() override - { - if (auto* pluginInstance = getPluginInstance()) - return (Steinberg::TBool) dynamic_cast (pluginInstance)->isEditorView(); - return (Steinberg::TBool) false; - } - - Steinberg::tresult PLUGIN_API setViewIsEmbedded (Steinberg::IPlugView* /*view*/, Steinberg::TBool /*embedded*/) override - { - return kResultOk; - } - #endif - - //============================================================================== - tresult PLUGIN_API setComponentState (IBStream* stream) override - { - // As an IEditController member, the host should only call this from the message thread. - assertHostMessageThread(); - - if (auto* pluginInstance = getPluginInstance()) - { - for (auto vstParamId : audioProcessor->getParamIDs()) - { - auto paramValue = [&] - { - if (vstParamId == audioProcessor->getProgramParamID()) - return EditController::plainParamToNormalized (audioProcessor->getProgramParamID(), - pluginInstance->getCurrentProgram()); - - return (double) audioProcessor->getParamForVSTParamID (vstParamId)->getValue(); - }(); - - setParamNormalized (vstParamId, paramValue); - } - } - - if (auto* handler = getComponentHandler()) - handler->restartComponent (Vst::kParamValuesChanged); - - return Vst::EditController::setComponentState (stream); - } - - void setAudioProcessor (JuceAudioProcessor* audioProc) - { - if (audioProcessor != audioProc) - installAudioProcessor (audioProc); - } - - tresult PLUGIN_API connect (IConnectionPoint* other) override - { - if (other != nullptr && audioProcessor == nullptr) - { - auto result = ComponentBase::connect (other); - - if (! audioProcessor.loadFrom (other)) - sendIntMessage ("JuceVST3EditController", (Steinberg::int64) (pointer_sized_int) this); - else - installAudioProcessor (audioProcessor); - - return result; - } - - jassertfalse; - return kResultFalse; - } - - //============================================================================== - tresult PLUGIN_API getMidiControllerAssignment ([[maybe_unused]] Steinberg::int32 busIndex, - [[maybe_unused]] Steinberg::int16 channel, - [[maybe_unused]] Vst::CtrlNumber midiControllerNumber, - [[maybe_unused]] Vst::ParamID& resultID) override - { - #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - resultID = midiControllerToParameter[channel][midiControllerNumber]; - return kResultTrue; // Returning false makes some hosts stop asking for further MIDI Controller Assignments - #else - return kResultFalse; - #endif - } - - // Converts an incoming parameter index to a MIDI controller: - bool getMidiControllerForParameter (Vst::ParamID index, int& channel, int& ctrlNumber) - { - auto mappedIndex = static_cast (index - parameterToMidiControllerOffset); - - if (isPositiveAndBelow (mappedIndex, numElementsInArray (parameterToMidiController))) - { - auto& mc = parameterToMidiController[mappedIndex]; - - if (mc.channel != -1 && mc.ctrlNumber != -1) - { - channel = jlimit (1, 16, mc.channel + 1); - ctrlNumber = mc.ctrlNumber; - return true; - } - } - - return false; - } - - inline bool isMidiControllerParamID (Vst::ParamID paramID) const noexcept - { - return (paramID >= parameterToMidiControllerOffset - && isPositiveAndBelow (paramID - parameterToMidiControllerOffset, - static_cast (numElementsInArray (parameterToMidiController)))); - } - - //============================================================================== - Steinberg::int32 PLUGIN_API getUnitCount() override - { - if (audioProcessor != nullptr) - return audioProcessor->getUnitCount(); - - jassertfalse; - return 1; - } - - tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override - { - if (audioProcessor != nullptr) - return audioProcessor->getUnitInfo (unitIndex, info); - - jassertfalse; - if (unitIndex == 0) - { - info.id = Vst::kRootUnitId; - info.parentUnitId = Vst::kNoParentUnitId; - info.programListId = Vst::kNoProgramListId; - - toString128 (info.name, TRANS ("Root Unit")); - - return kResultTrue; - } - - zerostruct (info); - return kResultFalse; - } - - Steinberg::int32 PLUGIN_API getProgramListCount() override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramListCount(); - - jassertfalse; - return 0; - } - - tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramListInfo (listIndex, info); - - jassertfalse; - zerostruct (info); - return kResultFalse; - } - - tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramName (listId, programIndex, name); - - jassertfalse; - toString128 (name, juce::String()); - return kResultFalse; - } - - tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Vst::CString attributeId, Vst::String128 attributeValue) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramInfo (listId, programIndex, attributeId, attributeValue); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override - { - if (audioProcessor != nullptr) - return audioProcessor->hasProgramPitchNames (listId, programIndex); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Steinberg::int16 midiPitch, Vst::String128 name) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramPitchName (listId, programIndex, midiPitch, name); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override - { - if (audioProcessor != nullptr) - return audioProcessor->selectUnit (unitId); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, - Steinberg::IBStream* data) override - { - if (audioProcessor != nullptr) - return audioProcessor->setUnitProgramData (listOrUnitId, programIndex, data); - - jassertfalse; - return kResultFalse; - } - - Vst::UnitID PLUGIN_API getSelectedUnit() override - { - if (audioProcessor != nullptr) - return audioProcessor->getSelectedUnit(); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, - Steinberg::int32 channel, Vst::UnitID& unitId) override - { - if (audioProcessor != nullptr) - return audioProcessor->getUnitByBus (type, dir, busIndex, channel, unitId); - - jassertfalse; - return kResultFalse; - } - - //============================================================================== - IPlugView* PLUGIN_API createView (const char* name) override - { - if (auto* pluginInstance = getPluginInstance()) - { - const auto mayCreateEditor = pluginInstance->hasEditor() - && name != nullptr - && std::strcmp (name, Vst::ViewType::kEditor) == 0 - && (pluginInstance->getActiveEditor() == nullptr - || detail::PluginUtilities::getHostType().isAdobeAudition() - || detail::PluginUtilities::getHostType().isPremiere()); - - if (mayCreateEditor) - return new JuceVST3Editor (*this, *audioProcessor); - } - - return nullptr; - } - - //============================================================================== - void beginGesture (Vst::ParamID vstParamId) - { - if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) - beginEdit (vstParamId); - } - - void endGesture (Vst::ParamID vstParamId) - { - if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) - endEdit (vstParamId); - } - - void paramChanged (Steinberg::int32 parameterIndex, Vst::ParamID vstParamId, double newValue) - { - if (inParameterChangedCallback || inSetState) - return; - - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - // NB: Cubase has problems if performEdit is called without setParamNormalized - EditController::setParamNormalized (vstParamId, newValue); - performEdit (vstParamId, newValue); - } - else - { - audioProcessor->setParameterValue (parameterIndex, (float) newValue); - } - } - - //============================================================================== - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override - { - beginGesture (audioProcessor->getVSTParamIDForIndex (index)); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override - { - endGesture (audioProcessor->getVSTParamIDForIndex (index)); - } - - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override - { - paramChanged (index, audioProcessor->getVSTParamIDForIndex (index), newValue); - } - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - int32 flags = 0; - - if (details.parameterInfoChanged) - { - for (int32 i = 0; i < parameters.getParameterCount(); ++i) - if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) - if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) - flags |= Vst::kParamTitlesChanged; - } - - if (auto* pluginInstance = getPluginInstance()) - { - if (details.programChanged) - { - const auto programParameterId = audioProcessor->getProgramParamID(); - - if (audioProcessor->getParamForVSTParamID (programParameterId) != nullptr) - { - const auto currentProgram = pluginInstance->getCurrentProgram(); - const auto paramValue = roundToInt (EditController::normalizedParamToPlain (programParameterId, - EditController::getParamNormalized (programParameterId))); - - if (currentProgram != paramValue) - { - beginGesture (programParameterId); - paramChanged (audioProcessor->findCacheIndexForParamID (programParameterId), - programParameterId, - EditController::plainParamToNormalized (programParameterId, currentProgram)); - endGesture (programParameterId); - - flags |= Vst::kParamValuesChanged; - } - } - } - - auto latencySamples = pluginInstance->getLatencySamples(); - - #if JucePlugin_Enable_ARA - jassert (latencySamples == 0 || ! dynamic_cast (pluginInstance)->isBoundToARA()); - #endif - - if (details.latencyChanged && latencySamples != lastLatencySamples) - { - flags |= Vst::kLatencyChanged; - lastLatencySamples = latencySamples; - } - } - - if (details.nonParameterStateChanged) - flags |= pluginShouldBeMarkedDirtyFlag; - - if (inSetupProcessing) - flags &= Vst::kLatencyChanged; - - componentRestarter.restart (flags); - } - - //============================================================================== - AudioProcessor* getPluginInstance() const noexcept - { - if (audioProcessor != nullptr) - return audioProcessor->get(); - - return nullptr; - } - - static constexpr auto pluginShouldBeMarkedDirtyFlag = 1 << 16; - -private: - bool isBlueCatHost (FUnknown* context) const - { - // We can't use the normal PluginHostType mechanism here because that will give us the name - // of the host process. However, this plugin instance might be loaded in an instance of - // the BlueCat PatchWork host, which might itself be a plugin. - - VSTComSmartPtr host; - host.loadFrom (context); - - if (host == nullptr) - return false; - - Vst::String128 name; - - if (host->getName (name) != kResultOk) - return false; - - const auto hostName = toString (name); - return hostName.contains ("Blue Cat's VST3 Host"); - } - - friend class JuceVST3Component; - friend struct Param; - - //============================================================================== - VSTComSmartPtr audioProcessor; - - struct MidiController - { - int channel = -1, ctrlNumber = -1; - }; - - ComponentRestarter componentRestarter { *this }; - - enum { numMIDIChannels = 16 }; - Vst::ParamID parameterToMidiControllerOffset; - MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; - Vst::ParamID midiControllerToParameter[numMIDIChannels][Vst::kCountCtrlNumber]; - - void restartComponentOnMessageThread (int32 flags) override - { - if ((flags & pluginShouldBeMarkedDirtyFlag) != 0) - setDirty (true); - - flags &= ~pluginShouldBeMarkedDirtyFlag; - - if (auto* handler = componentHandler) - handler->restartComponent (flags); - } - - //============================================================================== - struct OwnedParameterListener : public AudioProcessorParameter::Listener - { - OwnedParameterListener (JuceVST3EditController& editController, - AudioProcessorParameter& parameter, - Vst::ParamID paramID, - int cacheIndex) - : owner (editController), - vstParamID (paramID), - parameterIndex (cacheIndex) - { - // We shouldn't be using an OwnedParameterListener for parameters that have - // been added directly to the AudioProcessor. We observe those via the - // normal audioProcessorParameterChanged mechanism. - jassert (parameter.getParameterIndex() == -1); - // The parameter must have a non-negative index in the parameter cache. - jassert (parameterIndex >= 0); - parameter.addListener (this); - } - - void parameterValueChanged (int, float newValue) override - { - owner.paramChanged (parameterIndex, vstParamID, newValue); - } - - void parameterGestureChanged (int, bool gestureIsStarting) override - { - if (gestureIsStarting) - owner.beginGesture (vstParamID); - else - owner.endGesture (vstParamID); - } - - JuceVST3EditController& owner; - const Vst::ParamID vstParamID = Vst::kNoParamId; - const int parameterIndex = -1; - }; - - std::vector> ownedParameterListeners; - - //============================================================================== - bool inSetState = false; - std::atomic vst3IsPlaying { false }, - inSetupProcessing { false }; - - int lastLatencySamples = 0; - bool blueCatPatchwork = isBlueCatHost (hostContext.get()); - - #if ! JUCE_MAC - float lastScaleFactorReceived = 1.0f; - #endif - - InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - SharedBase{}, - UniqueBase{}, - #if JucePlugin_Enable_ARA - UniqueBase{}, - #endif - SharedBase{}); - - if (result.isOk()) - return result; - - if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) - return { kResultOk, audioProcessor.get() }; - - return {}; - } - - void installAudioProcessor (const VSTComSmartPtr& newAudioProcessor) - { - audioProcessor = newAudioProcessor; - - if (auto* extensions = dynamic_cast (audioProcessor->get())) - { - extensions->setIComponentHandler (componentHandler); - extensions->setIHostApplication (hostContext.get()); - } - - if (auto* pluginInstance = getPluginInstance()) - { - lastLatencySamples = pluginInstance->getLatencySamples(); - - pluginInstance->addListener (this); - - // as the bypass is not part of the regular parameters we need to listen for it explicitly - if (! audioProcessor->isBypassRegularParameter()) - { - const auto paramID = audioProcessor->getBypassParamID(); - ownedParameterListeners.push_back (std::make_unique (*this, - *audioProcessor->getParamForVSTParamID (paramID), - paramID, - audioProcessor->findCacheIndexForParamID (paramID))); - } - - if (parameters.getParameterCount() <= 0) - { - auto n = audioProcessor->getParamIDs().size(); - - for (int i = 0; i < n; ++i) - { - auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); - - if (vstParamID == audioProcessor->getProgramParamID()) - continue; - - auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); - auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); - auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); - - parameters.addParameter (new Param (*this, *juceParam, vstParamID, unitID, - (vstParamID == audioProcessor->getBypassParamID()))); - } - - const auto programParamId = audioProcessor->getProgramParamID(); - - if (auto* programParam = audioProcessor->getParamForVSTParamID (programParamId)) - { - ownedParameterListeners.push_back (std::make_unique (*this, - *programParam, - programParamId, - audioProcessor->findCacheIndexForParamID (programParamId))); - - parameters.addParameter (new ProgramChangeParameter (*pluginInstance, audioProcessor->getProgramParamID())); - } - } - - #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - parameterToMidiControllerOffset = static_cast (audioProcessor->isUsingManagedParameters() ? JuceAudioProcessor::paramMidiControllerOffset - : parameters.getParameterCount()); - - initialiseMidiControllerMappings(); - #endif - - audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); - } - } - - void initialiseMidiControllerMappings() - { - for (int c = 0, p = 0; c < numMIDIChannels; ++c) - { - for (int i = 0; i < Vst::kCountCtrlNumber; ++i, ++p) - { - midiControllerToParameter[c][i] = static_cast (p) + parameterToMidiControllerOffset; - parameterToMidiController[p].channel = c; - parameterToMidiController[p].ctrlNumber = i; - - parameters.addParameter (new Vst::Parameter (toString ("MIDI CC " + String (c) + "|" + String (i)), - static_cast (p) + parameterToMidiControllerOffset, nullptr, 0, 0, - 0, Vst::kRootUnitId)); - } - } - } - - void sendIntMessage (const char* idTag, const Steinberg::int64 value) - { - jassert (hostContext != nullptr); - - if (auto* message = allocateMessage()) - { - const FReleaser releaser (message); - message->setMessageID (idTag); - message->getAttributes()->setInt (idTag, value); - sendMessage (message); - } - } - - class EditorContextMenu : public HostProvidedContextMenu - { - public: - EditorContextMenu (AudioProcessorEditor& editorIn, - VSTComSmartPtr contextMenuIn) - : editor (editorIn), contextMenu (contextMenuIn) {} - - PopupMenu getEquivalentPopupMenu() const override - { - using MenuItem = Steinberg::Vst::IContextMenuItem; - using MenuTarget = Steinberg::Vst::IContextMenuTarget; - - struct Submenu - { - PopupMenu menu; - String name; - bool enabled; - }; - - std::vector menuStack (1); - - for (int32_t i = 0, end = contextMenu->getItemCount(); i < end; ++i) - { - MenuItem item{}; - MenuTarget* target = nullptr; - contextMenu->getItem (i, item, &target); - - if ((item.flags & MenuItem::kIsGroupStart) == MenuItem::kIsGroupStart) - { - menuStack.push_back ({ PopupMenu{}, - toString (item.name), - (item.flags & MenuItem::kIsDisabled) == 0 }); - } - else if ((item.flags & MenuItem::kIsGroupEnd) == MenuItem::kIsGroupEnd) - { - const auto back = menuStack.back(); - menuStack.pop_back(); - - if (menuStack.empty()) - { - // malformed menu - jassertfalse; - return {}; - } - - menuStack.back().menu.addSubMenu (back.name, back.menu, back.enabled); - } - else if ((item.flags & MenuItem::kIsSeparator) == MenuItem::kIsSeparator) - { - menuStack.back().menu.addSeparator(); - } - else - { - VSTComSmartPtr ownedTarget (target); - const auto tag = item.tag; - menuStack.back().menu.addItem (toString (item.name), - (item.flags & MenuItem::kIsDisabled) == 0, - (item.flags & MenuItem::kIsChecked) != 0, - [ownedTarget, tag] { ownedTarget->executeMenuItem (tag); }); - } - } - - if (menuStack.size() != 1) - { - // malformed menu - jassertfalse; - return {}; - } - - return menuStack.back().menu; - } - - void showNativeMenu (Point pos) const override - { - const auto scaled = pos * Component::getApproximateScaleFactorForComponent (&editor); - contextMenu->popup (scaled.x, scaled.y); - } - - private: - AudioProcessorEditor& editor; - VSTComSmartPtr contextMenu; - }; - - class EditorHostContext : public AudioProcessorEditorHostContext - { - public: - EditorHostContext (JuceAudioProcessor& processorIn, - AudioProcessorEditor& editorIn, - Steinberg::Vst::IComponentHandler* handler, - Steinberg::IPlugView* viewIn) - : processor (processorIn), editor (editorIn), componentHandler (handler), view (viewIn) {} - - std::unique_ptr getContextMenuForParameter (const AudioProcessorParameter* parameter) const override - { - if (componentHandler == nullptr || view == nullptr) - return {}; - - Steinberg::FUnknownPtr handler (componentHandler); - - if (handler == nullptr) - return {}; - - const auto idToUse = parameter != nullptr ? processor.getVSTParamIDForIndex (parameter->getParameterIndex()) : 0; - const auto menu = VSTComSmartPtr (handler->createContextMenu (view, &idToUse)); - return std::make_unique (editor, menu); - } - - private: - JuceAudioProcessor& processor; - AudioProcessorEditor& editor; - Steinberg::Vst::IComponentHandler* componentHandler = nullptr; - Steinberg::IPlugView* view = nullptr; - }; - - //============================================================================== - class JuceVST3Editor : public Vst::EditorView, - public Steinberg::IPlugViewContentScaleSupport, - private Timer - { - public: - JuceVST3Editor (JuceVST3EditController& ec, JuceAudioProcessor& p) - : EditorView (&ec, nullptr), - owner (&ec), - pluginInstance (*p.get()) - { - createContentWrapperComponentIfNeeded(); - - #if JUCE_MAC - if (detail::PluginUtilities::getHostType().type == PluginHostType::SteinbergCubase10) - cubase10Workaround.reset (new Cubase10WindowResizeWorkaround (*this)); - #endif - } - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto result = testFor (*this, targetIID, UniqueBase{}); - - if (result.isOk()) - return result.extract (obj); - - return Vst::EditorView::queryInterface (targetIID, obj); - } - - REFCOUNT_METHODS (Vst::EditorView) - - //============================================================================== - tresult PLUGIN_API isPlatformTypeSupported (FIDString type) override - { - if (type != nullptr && pluginInstance.hasEditor()) - { - #if JUCE_WINDOWS - if (strcmp (type, kPlatformTypeHWND) == 0) - #elif JUCE_MAC - if (strcmp (type, kPlatformTypeNSView) == 0 || strcmp (type, kPlatformTypeHIView) == 0) - #elif JUCE_LINUX || JUCE_BSD - if (strcmp (type, kPlatformTypeX11EmbedWindowID) == 0) - #endif - return kResultTrue; - } - - return kResultFalse; - } - - tresult PLUGIN_API attached (void* parent, FIDString type) override - { - if (parent == nullptr || isPlatformTypeSupported (type) == kResultFalse) - return kResultFalse; - - #if JUCE_LINUX || JUCE_BSD - eventHandler->registerHandlerForFrame (plugFrame); - #endif - - systemWindow = parent; - - createContentWrapperComponentIfNeeded(); - - const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()); - - #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD - // If the plugin was last opened at a particular scale, try to reapply that scale here. - // Note that we do this during attach(), rather than in JuceVST3Editor(). During the - // constructor, we don't have a host plugFrame, so - // ContentWrapperComponent::resizeHostWindow() won't do anything, and the content - // wrapper component will be left at the wrong size. - applyScaleFactor (StoredScaleFactor{}.withInternal (owner->lastScaleFactorReceived)); - - // Check the host scale factor *before* calling addToDesktop, so that the initial - // window size during addToDesktop is correct for the current platform scale factor. - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - component->checkHostWindowScaleFactor(); - #endif - - component->setOpaque (true); - component->addToDesktop (desktopFlags, systemWindow); - component->setVisible (true); - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - component->startTimer (500); - #endif - - #else - macHostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (component.get(), desktopFlags, parent); - #endif - - component->resizeHostWindow(); - attachedToParent(); - - // Life's too short to faff around with wave lab - if (detail::PluginUtilities::getHostType().isWavelab()) - startTimer (200); - - return kResultTrue; - } - - tresult PLUGIN_API removed() override - { - if (component != nullptr) - { - #if JUCE_WINDOWS - component->removeFromDesktop(); - #elif JUCE_MAC - if (macHostWindow != nullptr) - { - detail::VSTWindowUtilities::detachComponentFromWindowRefVST (component.get(), macHostWindow); - macHostWindow = nullptr; - } - #endif - - component = nullptr; - } - - #if JUCE_LINUX || JUCE_BSD - eventHandler->unregisterHandlerForFrame (plugFrame); - #endif - - return CPluginView::removed(); - } - - tresult PLUGIN_API onSize (ViewRect* newSize) override - { - if (newSize != nullptr) - { - rect = convertFromHostBounds (*newSize); - - if (component != nullptr) - { - component->setSize (rect.getWidth(), rect.getHeight()); - - #if JUCE_MAC - if (cubase10Workaround != nullptr) - { - cubase10Workaround->triggerAsyncUpdate(); - } - else - #endif - { - if (auto* peer = component->getPeer()) - peer->updateBounds(); - } - } - - return kResultTrue; - } - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getSize (ViewRect* size) override - { - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - if (detail::PluginUtilities::getHostType().isAbletonLive() && systemWindow == nullptr) - return kResultFalse; - #endif - - if (size != nullptr && component != nullptr) - { - auto editorBounds = component->getSizeToContainChild(); - - *size = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); - return kResultTrue; - } - - return kResultFalse; - } - - tresult PLUGIN_API canResize() override - { - if (component != nullptr) - if (auto* editor = component->pluginEditor.get()) - if (editor->isResizable()) - return kResultTrue; - - return kResultFalse; - } - - tresult PLUGIN_API checkSizeConstraint (ViewRect* rectToCheck) override - { - if (rectToCheck != nullptr && component != nullptr) - { - if (auto* editor = component->pluginEditor.get()) - { - if (canResize() == kResultFalse) - { - // Ableton Live will call checkSizeConstraint even if the view returns false - // from canResize. Set the out param to an appropriate size for the editor - // and return. - auto constrainedRect = component->getLocalArea (editor, editor->getLocalBounds()) - .getSmallestIntegerContainer(); - - *rectToCheck = convertFromHostBounds (*rectToCheck); - rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); - rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); - *rectToCheck = convertToHostBounds (*rectToCheck); - } - else if (auto* constrainer = editor->getConstrainer()) - { - *rectToCheck = convertFromHostBounds (*rectToCheck); - - auto editorBounds = editor->getLocalArea (component.get(), - Rectangle::leftTopRightBottom (rectToCheck->left, rectToCheck->top, - rectToCheck->right, rectToCheck->bottom).toFloat()); - - auto minW = (float) constrainer->getMinimumWidth(); - auto maxW = (float) constrainer->getMaximumWidth(); - auto minH = (float) constrainer->getMinimumHeight(); - auto maxH = (float) constrainer->getMaximumHeight(); - - auto width = jlimit (minW, maxW, editorBounds.getWidth()); - auto height = jlimit (minH, maxH, editorBounds.getHeight()); - - auto aspectRatio = (float) constrainer->getFixedAspectRatio(); - - if (aspectRatio != 0.0) - { - bool adjustWidth = (width / height > aspectRatio); - - if (detail::PluginUtilities::getHostType().type == PluginHostType::SteinbergCubase9) - { - auto currentEditorBounds = editor->getBounds().toFloat(); - - if (currentEditorBounds.getWidth() == width && currentEditorBounds.getHeight() != height) - adjustWidth = true; - else if (currentEditorBounds.getHeight() == height && currentEditorBounds.getWidth() != width) - adjustWidth = false; - } - - if (adjustWidth) - { - width = height * aspectRatio; - - if (width > maxW || width < minW) - { - width = jlimit (minW, maxW, width); - height = width / aspectRatio; - } - } - else - { - height = width / aspectRatio; - - if (height > maxH || height < minH) - { - height = jlimit (minH, maxH, height); - width = height * aspectRatio; - } - } - } - - auto constrainedRect = component->getLocalArea (editor, Rectangle (width, height)) - .getSmallestIntegerContainer(); - - rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); - rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); - - *rectToCheck = convertToHostBounds (*rectToCheck); - } - } - - return kResultTrue; - } - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API setContentScaleFactor ([[maybe_unused]] const Steinberg::IPlugViewContentScaleSupport::ScaleFactor factor) override - { - #if ! JUCE_MAC - const auto scaleToApply = [&] - { - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - // Cubase 10 only sends integer scale factors, so correct this for fractional scales - if (detail::PluginUtilities::getHostType().type != PluginHostType::SteinbergCubase10) - return factor; - - const auto hostWindowScale = (Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getScaleFactorForWindow (static_cast (systemWindow)); - - if (hostWindowScale <= 0.0 || approximatelyEqual (factor, hostWindowScale)) - return factor; - - return hostWindowScale; - #else - return factor; - #endif - }(); - - applyScaleFactor (scaleFactor.withHost (scaleToApply)); - - return kResultTrue; - #else - return kResultFalse; - #endif - } - - private: - void timerCallback() override - { - stopTimer(); - - ViewRect viewRect; - getSize (&viewRect); - onSize (&viewRect); - } - - static ViewRect convertToHostBounds (ViewRect pluginRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return pluginRect; - - return { roundToInt ((float) pluginRect.left * desktopScale), - roundToInt ((float) pluginRect.top * desktopScale), - roundToInt ((float) pluginRect.right * desktopScale), - roundToInt ((float) pluginRect.bottom * desktopScale) }; - } - - static ViewRect convertFromHostBounds (ViewRect hostRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return hostRect; - - return { roundToInt ((float) hostRect.left / desktopScale), - roundToInt ((float) hostRect.top / desktopScale), - roundToInt ((float) hostRect.right / desktopScale), - roundToInt ((float) hostRect.bottom / desktopScale) }; - } - - //============================================================================== - struct ContentWrapperComponent : public Component - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - , public Timer - #endif - { - ContentWrapperComponent (JuceVST3Editor& editor) : owner (editor) - { - setOpaque (true); - setBroughtToFrontOnMouseClick (true); - } - - ~ContentWrapperComponent() override - { - if (pluginEditor != nullptr) - { - PopupMenu::dismissAllActiveMenus(); - pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); - } - } - - void createEditor (AudioProcessor& plugin) - { - pluginEditor.reset (plugin.createEditorIfNeeded()); - - #if JucePlugin_Enable_ARA - jassert (dynamic_cast (pluginEditor.get()) != nullptr); - // for proper view embedding, ARA plug-ins must be resizable - jassert (pluginEditor->isResizable()); - #endif - - if (pluginEditor != nullptr) - { - editorHostContext = std::make_unique (*owner.owner->audioProcessor, - *pluginEditor, - owner.owner->getComponentHandler(), - &owner); - - pluginEditor->setHostContext (editorHostContext.get()); - #if ! JUCE_MAC - pluginEditor->setScaleFactor (owner.scaleFactor.get()); - #endif - - addAndMakeVisible (pluginEditor.get()); - pluginEditor->setTopLeftPosition (0, 0); - - lastBounds = getSizeToContainChild(); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - setBounds (lastBounds); - } - - resizeHostWindow(); - } - else - { - // if hasEditor() returns true then createEditorIfNeeded has to return a valid editor - jassertfalse; - } - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::black); - } - - juce::Rectangle getSizeToContainChild() - { - if (pluginEditor != nullptr) - return getLocalArea (pluginEditor.get(), pluginEditor->getLocalBounds()); - - return {}; - } - - void childBoundsChanged (Component*) override - { - if (resizingChild) - return; - - auto newBounds = getSizeToContainChild(); - - if (newBounds != lastBounds) - { - resizeHostWindow(); - - #if JUCE_LINUX || JUCE_BSD - if (detail::PluginUtilities::getHostType().isBitwigStudio()) - repaint(); - #endif - - lastBounds = newBounds; - } - } - - void resized() override - { - if (pluginEditor != nullptr) - { - if (! resizingParent) - { - auto newBounds = getLocalBounds(); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); - } - - lastBounds = newBounds; - } - } - } - - void parentSizeChanged() override - { - if (pluginEditor != nullptr) - { - resizeHostWindow(); - pluginEditor->repaint(); - } - } - - void resizeHostWindow() - { - if (pluginEditor != nullptr) - { - if (owner.plugFrame != nullptr) - { - auto editorBounds = getSizeToContainChild(); - auto newSize = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - owner.plugFrame->resizeView (&owner, &newSize); - } - - auto host = detail::PluginUtilities::getHostType(); - - #if JUCE_MAC - if (host.isWavelab() || host.isReaper() || owner.owner->blueCatPatchwork) - #else - if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio() || owner.owner->blueCatPatchwork) - #endif - setBounds (editorBounds.withPosition (0, 0)); - } - } - } - - void setEditorScaleFactor (float scale) - { - if (pluginEditor != nullptr) - { - auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - pluginEditor->setScaleFactor (scale); - pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); - } - - lastBounds = getSizeToContainChild(); - - resizeHostWindow(); - repaint(); - } - } - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor() - { - const auto estimatedScale = (float) getScaleFactorForWindow (static_cast (owner.systemWindow)); - - if (estimatedScale > 0.0) - owner.applyScaleFactor (owner.scaleFactor.withInternal (estimatedScale)); - } - - void timerCallback() override - { - checkHostWindowScaleFactor(); - } - #endif - - std::unique_ptr pluginEditor; - - private: - JuceVST3Editor& owner; - std::unique_ptr editorHostContext; - Rectangle lastBounds; - bool resizingChild = false, resizingParent = false; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) - }; - - void createContentWrapperComponentIfNeeded() - { - if (component == nullptr) - { - #if JUCE_LINUX || JUCE_BSD - const MessageManagerLock mmLock; - #endif - - component.reset (new ContentWrapperComponent (*this)); - component->createEditor (pluginInstance); - } - } - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - SharedResourcePointer eventHandler; - #endif - - VSTComSmartPtr owner; - AudioProcessor& pluginInstance; - - #if JUCE_LINUX || JUCE_BSD - struct MessageManagerLockedDeleter - { - template - void operator() (ObjectType* object) const noexcept - { - const MessageManagerLock mmLock; - delete object; - } - }; - - std::unique_ptr component; - #else - std::unique_ptr component; - #endif - - friend struct ContentWrapperComponent; - - #if JUCE_MAC - void* macHostWindow = nullptr; - - // On macOS Cubase 10 resizes the host window after calling onSize() resulting in the peer - // bounds being a step behind the plug-in. Calling updateBounds() asynchronously seems to fix things... - struct Cubase10WindowResizeWorkaround : public AsyncUpdater - { - Cubase10WindowResizeWorkaround (JuceVST3Editor& o) : owner (o) {} - - void handleAsyncUpdate() override - { - if (owner.component != nullptr) - if (auto* peer = owner.component->getPeer()) - peer->updateBounds(); - } - - JuceVST3Editor& owner; - }; - - std::unique_ptr cubase10Workaround; - #else - class StoredScaleFactor - { - public: - StoredScaleFactor withHost (float x) const { return withMember (*this, &StoredScaleFactor::host, x); } - StoredScaleFactor withInternal (float x) const { return withMember (*this, &StoredScaleFactor::internal, x); } - float get() const { return host.value_or (internal); } - - private: - std::optional host; - float internal = 1.0f; - }; - - void applyScaleFactor (const StoredScaleFactor newFactor) - { - const auto previous = std::exchange (scaleFactor, newFactor).get(); - - if (previous == scaleFactor.get()) - return; - - if (owner != nullptr) - owner->lastScaleFactorReceived = scaleFactor.get(); - - if (component != nullptr) - { - #if JUCE_LINUX || JUCE_BSD - const MessageManagerLock mmLock; - #endif - component->setEditorScaleFactor (scaleFactor.get()); - } - } - - StoredScaleFactor scaleFactor; - - #if JUCE_WINDOWS - detail::WindowsHooks hooks; - #endif - - #endif - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Editor) - }; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) -}; - - -//============================================================================== -#if JucePlugin_Enable_ARA - class JuceARAFactory : public ARA::IMainFactory - { - public: - JuceARAFactory() = default; - virtual ~JuceARAFactory() = default; - - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const ::Steinberg::TUID targetIID, void** obj) override - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}); - - if (result.isOk()) - return result.extract (obj); - - if (doUIDsMatch (targetIID, JuceARAFactory::iid)) - { - addRef(); - *obj = this; - return kResultOk; - } - - *obj = nullptr; - return kNoInterface; - } - - //---from ARA::IMainFactory------- - const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE - { - return createARAFactory(); - } - static const FUID iid; - - private: - //============================================================================== - std::atomic refCount { 1 }; - }; -#endif - -//============================================================================== -class JuceVST3Component : public Vst::IComponent, - public Vst::IAudioProcessor, - public Vst::IUnitInfo, - public Vst::IConnectionPoint, - public Vst::IProcessContextRequirements, - #if JucePlugin_Enable_ARA - public ARA::IPlugInEntryPoint, - public ARA::IPlugInEntryPoint2, - #endif - public AudioPlayHead -{ -public: - JuceVST3Component (Vst::IHostApplication* h) - : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_VST3).release()), - host (h) - { - inParameterChangedCallback = false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - [[maybe_unused]] const int numConfigs = numElementsInArray (configs); - - jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - - pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); - #endif - - // VST-3 requires your default layout to be non-discrete! - // For example, your default layout must be mono, stereo, quadrophonic - // and not AudioChannelSet::discreteChannels (2) etc. - jassert (checkBusFormatsAreNotDiscrete()); - - comPluginInstance = VSTComSmartPtr { new JuceAudioProcessor (pluginInstance) }; - - zerostruct (processContext); - - processSetup.maxSamplesPerBlock = 1024; - processSetup.processMode = Vst::kRealtime; - processSetup.sampleRate = 44100.0; - processSetup.symbolicSampleSize = Vst::kSample32; - - pluginInstance->setPlayHead (this); - - // Constructing the underlying static object involves dynamic allocation. - // This call ensures that the construction won't happen on the audio thread. - detail::PluginUtilities::getHostType(); - } - - ~JuceVST3Component() override - { - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = false; - - if (pluginInstance != nullptr) - if (pluginInstance->getPlayHead() == this) - pluginInstance->setPlayHead (nullptr); - } - - //============================================================================== - AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } - - //============================================================================== - static const FUID iid; - - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto userProvidedInterface = queryAdditionalInterfaces (&getPluginInstance(), - targetIID, - &VST3ClientExtensions::queryIAudioProcessor); - - const auto juceProvidedInterface = queryInterfaceInternal (targetIID); - - return extractResult (userProvidedInterface, juceProvidedInterface, obj); - } - - enum class CallPrepareToPlay { no, yes }; - - //============================================================================== - tresult PLUGIN_API initialize (FUnknown* hostContext) override - { - if (host != hostContext) - host.loadFrom (hostContext); - - processContext.sampleRate = processSetup.sampleRate; - preparePlugin (processSetup.sampleRate, (int) processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); - - return kResultTrue; - } - - tresult PLUGIN_API terminate() override - { - getPluginInstance().releaseResources(); - return kResultTrue; - } - - //============================================================================== - tresult PLUGIN_API connect (IConnectionPoint* other) override - { - if (other != nullptr && juceVST3EditController == nullptr) - juceVST3EditController.loadFrom (other); - - return kResultTrue; - } - - tresult PLUGIN_API disconnect (IConnectionPoint*) override - { - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = false; - - juceVST3EditController = {}; - return kResultTrue; - } - - tresult PLUGIN_API notify (Vst::IMessage* message) override - { - if (message != nullptr && juceVST3EditController == nullptr) - { - Steinberg::int64 value = 0; - - if (message->getAttributes()->getInt ("JuceVST3EditController", value) == kResultTrue) - { - juceVST3EditController = VSTComSmartPtr { (JuceVST3EditController*) (pointer_sized_int) value }; - - if (juceVST3EditController != nullptr) - juceVST3EditController->setAudioProcessor (comPluginInstance); - else - jassertfalse; - } - } - - return kResultTrue; - } - - tresult PLUGIN_API getControllerClassId (TUID classID) override - { - memcpy (classID, JuceVST3EditController::iid, sizeof (TUID)); - return kResultTrue; - } - - //============================================================================== - tresult PLUGIN_API setActive (TBool state) override - { - const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); - - const auto willBeActive = (state != 0); - - active = false; - // Some hosts may call setBusArrangements in response to calls made during prepareToPlay - // or releaseResources. Specifically, Wavelab 11.1 calls setBusArrangements in the same - // call stack when the AudioProcessor calls setLatencySamples inside prepareToPlay. - // In order for setBusArrangements to return successfully, the plugin must not be activated - // until after prepareToPlay has completely finished. - const ScopeGuard scope { [&] { active = willBeActive; } }; - - if (willBeActive) - { - const auto sampleRate = processSetup.sampleRate > 0.0 - ? processSetup.sampleRate - : getPluginInstance().getSampleRate(); - - const auto bufferSize = processSetup.maxSamplesPerBlock > 0 - ? (int) processSetup.maxSamplesPerBlock - : getPluginInstance().getBlockSize(); - - preparePlugin (sampleRate, bufferSize, CallPrepareToPlay::yes); - } - else - { - getPluginInstance().releaseResources(); - } - - return kResultOk; - } - - tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } - tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } - - //============================================================================== - bool isBypassed() const - { - if (auto* bypassParam = comPluginInstance->getBypassParameter()) - return bypassParam->getValue() >= 0.5f; - - return false; - } - - void setBypassed (bool shouldBeBypassed) - { - if (auto* bypassParam = comPluginInstance->getBypassParameter()) - setValueAndNotifyIfChanged (*bypassParam, shouldBeBypassed ? 1.0f : 0.0f); - } - - //============================================================================== - void writeJucePrivateStateInformation (MemoryOutputStream& out) - { - if (pluginInstance->getBypassParameter() == nullptr) - { - ValueTree privateData (kJucePrivateDataIdentifier); - - // for now we only store the bypass value - privateData.setProperty ("Bypass", var (isBypassed()), nullptr); - privateData.writeToStream (out); - } - } - - void setJucePrivateStateInformation (const void* data, int sizeInBytes) - { - if (pluginInstance->getBypassParameter() == nullptr) - { - if (comPluginInstance->getBypassParameter() != nullptr) - { - auto privateData = ValueTree::readFromData (data, static_cast (sizeInBytes)); - setBypassed (static_cast (privateData.getProperty ("Bypass", var (false)))); - } - } - } - - void getStateInformation (MemoryBlock& destData) - { - pluginInstance->getStateInformation (destData); - - // With bypass support, JUCE now needs to store private state data. - // Put this at the end of the plug-in state and add a few null characters - // so that plug-ins built with older versions of JUCE will hopefully ignore - // this data. Additionally, we need to add some sort of magic identifier - // at the very end of the private data so that JUCE has some sort of - // way to figure out if the data was stored with a newer JUCE version. - MemoryOutputStream extraData; - - extraData.writeInt64 (0); - writeJucePrivateStateInformation (extraData); - auto privateDataSize = (int64) (extraData.getDataSize() - sizeof (int64)); - extraData.writeInt64 (privateDataSize); - extraData << kJucePrivateDataIdentifier; - - // write magic string - destData.append (extraData.getData(), extraData.getDataSize()); - } - - void setStateInformation (const void* data, int sizeAsInt) - { - bool unusedState = false; - auto& flagToSet = juceVST3EditController != nullptr ? juceVST3EditController->inSetState : unusedState; - const ScopedValueSetter scope (flagToSet, true); - - auto size = (uint64) sizeAsInt; - - // Check if this data was written with a newer JUCE version - // and if it has the JUCE private data magic code at the end - auto jucePrivDataIdentifierSize = std::strlen (kJucePrivateDataIdentifier); - - if ((size_t) size >= jucePrivDataIdentifierSize + sizeof (int64)) - { - auto buffer = static_cast (data); - - String magic (CharPointer_UTF8 (buffer + size - jucePrivDataIdentifierSize), - CharPointer_UTF8 (buffer + size)); - - if (magic == kJucePrivateDataIdentifier) - { - // found a JUCE private data section - uint64 privateDataSize; - - std::memcpy (&privateDataSize, - buffer + ((size_t) size - jucePrivDataIdentifierSize - sizeof (uint64)), - sizeof (uint64)); - - privateDataSize = ByteOrder::swapIfBigEndian (privateDataSize); - size -= privateDataSize + jucePrivDataIdentifierSize + sizeof (uint64); - - if (privateDataSize > 0) - setJucePrivateStateInformation (buffer + size, static_cast (privateDataSize)); - - size -= sizeof (uint64); - } - } - - if (size > 0) - pluginInstance->setStateInformation (data, static_cast (size)); - } - - //============================================================================== - #if JUCE_VST3_CAN_REPLACE_VST2 - bool loadVST2VstWBlock (const char* data, int size) - { - jassert (ByteOrder::bigEndianInt ("VstW") == htonl ((uint32) readUnaligned (data))); - jassert (1 == htonl ((uint32) readUnaligned (data + 8))); // version should be 1 according to Steinberg's docs - - auto headerLen = (int) htonl ((uint32) readUnaligned (data + 4)) + 8; - return loadVST2CcnKBlock (data + headerLen, size - headerLen); - } - - bool loadVST2CcnKBlock (const char* data, int size) - { - auto* bank = reinterpret_cast (data); - - jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); - jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); - jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); - jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); - - setStateInformation (bank->content.data.chunk, - jmin ((int) (size - (bank->content.data.chunk - data)), - (int) htonl ((uint32) bank->content.data.size))); - return true; - } - - bool loadVST3PresetFile (const char* data, int size) - { - if (size < 48) - return false; - - // At offset 4 there's a little-endian version number which seems to typically be 1 - // At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" - auto chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); - jassert (memcmp (data + chunkListOffset, "List", 4) == 0); - auto entryCount = (int) ByteOrder::littleEndianInt (data + chunkListOffset + 4); - jassert (entryCount > 0); - - for (int i = 0; i < entryCount; ++i) - { - auto entryOffset = chunkListOffset + 8 + 20 * i; - - if (entryOffset + 20 > size) - return false; - - if (memcmp (data + entryOffset, "Comp", 4) == 0) - { - // "Comp" entries seem to contain the data. - auto chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); - auto chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); - - if (static_cast (chunkOffset + chunkSize) > static_cast (size)) - { - jassertfalse; - return false; - } - - loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); - } - } - - return true; - } - - bool loadVST2CompatibleState (const char* data, int size) - { - if (size < 4) - return false; - - auto header = htonl ((uint32) readUnaligned (data)); - - if (header == ByteOrder::bigEndianInt ("VstW")) - return loadVST2VstWBlock (data, size); - - if (header == ByteOrder::bigEndianInt ("CcnK")) - return loadVST2CcnKBlock (data, size); - - if (memcmp (data, "VST3", 4) == 0) - { - // In Cubase 5, when loading VST3 .vstpreset files, - // we get the whole content of the files to load. - // In Cubase 7 we get just the contents within and - // we go directly to the loadVST2VstW codepath instead. - return loadVST3PresetFile (data, size); - } - - return false; - } - #endif - - void loadStateData (const void* data, int size) - { - #if JUCE_VST3_CAN_REPLACE_VST2 - if (loadVST2CompatibleState ((const char*) data, size)) - return; - #endif - setStateInformation (data, size); - } - - bool readFromMemoryStream (IBStream* state) - { - FUnknownPtr s (state); - Steinberg::int64 size = 0; - - if (s != nullptr - && s->getStreamSize (size) == kResultOk - && size > 0 - && size < 1024 * 1024 * 100) // (some hosts seem to return junk for the size) - { - MemoryBlock block (static_cast (size)); - - // turns out that Cubase 9 might give you the incorrect stream size :-( - Steinberg::int32 bytesRead = 1; - int len; - - for (len = 0; bytesRead > 0 && len < static_cast (block.getSize()); len += bytesRead) - if (state->read (block.getData(), static_cast (block.getSize()), &bytesRead) != kResultOk) - break; - - if (len == 0) - return false; - - block.setSize (static_cast (len)); - - // Adobe Audition CS6 hack to avoid trying to use corrupted streams: - if (detail::PluginUtilities::getHostType().isAdobeAudition()) - if (block.getSize() >= 5 && memcmp (block.getData(), "VC2!E", 5) == 0) - return false; - - loadStateData (block.getData(), (int) block.getSize()); - return true; - } - - return false; - } - - bool readFromUnknownStream (IBStream* state) - { - MemoryOutputStream allData; - - { - const size_t bytesPerBlock = 4096; - HeapBlock buffer (bytesPerBlock); - - for (;;) - { - Steinberg::int32 bytesRead = 0; - auto status = state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead); - - if (bytesRead <= 0 || (status != kResultTrue && ! detail::PluginUtilities::getHostType().isWavelab())) - break; - - allData.write (buffer, static_cast (bytesRead)); - } - } - - const size_t dataSize = allData.getDataSize(); - - if (dataSize <= 0 || dataSize >= 0x7fffffff) - return false; - - loadStateData (allData.getData(), (int) dataSize); - return true; - } - - tresult PLUGIN_API setState (IBStream* state) override - { - // The VST3 spec requires that this function is called from the UI thread. - // If this assertion fires, your host is misbehaving! - assertHostMessageThread(); - - if (state == nullptr) - return kInvalidArgument; - - FUnknownPtr stateRefHolder (state); // just in case the caller hasn't properly ref-counted the stream object - - if (state->seek (0, IBStream::kIBSeekSet, nullptr) == kResultTrue) - { - if (! detail::PluginUtilities::getHostType().isFruityLoops() && readFromMemoryStream (state)) - return kResultTrue; - - if (readFromUnknownStream (state)) - return kResultTrue; - } - - return kResultFalse; - } - - #if JUCE_VST3_CAN_REPLACE_VST2 - static tresult writeVST2Header (IBStream* state, bool bypassed) - { - auto writeVST2IntToState = [state] (uint32 n) - { - auto t = (int32) htonl (n); - return state->write (&t, 4); - }; - - auto status = writeVST2IntToState (ByteOrder::bigEndianInt ("VstW")); - - if (status == kResultOk) status = writeVST2IntToState (8); // header size - if (status == kResultOk) status = writeVST2IntToState (1); // version - if (status == kResultOk) status = writeVST2IntToState (bypassed ? 1 : 0); // bypass - - return status; - } - #endif - - tresult PLUGIN_API getState (IBStream* state) override - { - if (state == nullptr) - return kInvalidArgument; - - MemoryBlock mem; - getStateInformation (mem); - - #if JUCE_VST3_CAN_REPLACE_VST2 - tresult status = writeVST2Header (state, isBypassed()); - - if (status != kResultOk) - return status; - - const int bankBlockSize = 160; - Vst2::fxBank bank; - - zerostruct (bank); - bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); - bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); - bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); - bank.version = (int32) htonl (2); - bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); - bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); - bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); - - status = state->write (&bank, bankBlockSize); - - if (status != kResultOk) - return status; - #endif - - return state->write (mem.getData(), (Steinberg::int32) mem.getSize()); - } - - //============================================================================== - Steinberg::int32 PLUGIN_API getUnitCount() override { return comPluginInstance->getUnitCount(); } - tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override { return comPluginInstance->getUnitInfo (unitIndex, info); } - Steinberg::int32 PLUGIN_API getProgramListCount() override { return comPluginInstance->getProgramListCount(); } - tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override { return comPluginInstance->getProgramListInfo (listIndex, info); } - tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override { return comPluginInstance->getProgramName (listId, programIndex, name); } - tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Vst::CString attributeId, Vst::String128 attributeValue) override { return comPluginInstance->getProgramInfo (listId, programIndex, attributeId, attributeValue); } - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override { return comPluginInstance->hasProgramPitchNames (listId, programIndex); } - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Steinberg::int16 midiPitch, Vst::String128 name) override { return comPluginInstance->getProgramPitchName (listId, programIndex, midiPitch, name); } - tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override { return comPluginInstance->selectUnit (unitId); } - tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, - Steinberg::IBStream* data) override { return comPluginInstance->setUnitProgramData (listOrUnitId, programIndex, data); } - Vst::UnitID PLUGIN_API getSelectedUnit() override { return comPluginInstance->getSelectedUnit(); } - tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, - Steinberg::int32 channel, Vst::UnitID& unitId) override { return comPluginInstance->getUnitByBus (type, dir, busIndex, channel, unitId); } - - //============================================================================== - Optional getPosition() const override - { - PositionInfo info; - info.setTimeInSamples (jmax ((juce::int64) 0, processContext.projectTimeSamples)); - info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processContext.sampleRate); - info.setIsRecording ((processContext.state & Vst::ProcessContext::kRecording) != 0); - info.setIsPlaying ((processContext.state & Vst::ProcessContext::kPlaying) != 0); - info.setIsLooping ((processContext.state & Vst::ProcessContext::kCycleActive) != 0); - - info.setBpm ((processContext.state & Vst::ProcessContext::kTempoValid) != 0 - ? makeOptional (processContext.tempo) - : nullopt); - - info.setTimeSignature ((processContext.state & Vst::ProcessContext::kTimeSigValid) != 0 - ? makeOptional (TimeSignature { processContext.timeSigNumerator, processContext.timeSigDenominator }) - : nullopt); - - info.setLoopPoints ((processContext.state & Vst::ProcessContext::kCycleValid) != 0 - ? makeOptional (LoopPoints { processContext.cycleStartMusic, processContext.cycleEndMusic }) - : nullopt); - - info.setPpqPosition ((processContext.state & Vst::ProcessContext::kProjectTimeMusicValid) != 0 - ? makeOptional (processContext.projectTimeMusic) - : nullopt); - - info.setPpqPositionOfLastBarStart ((processContext.state & Vst::ProcessContext::kBarPositionValid) != 0 - ? makeOptional (processContext.barPositionMusic) - : nullopt); - - info.setFrameRate ((processContext.state & Vst::ProcessContext::kSmpteValid) != 0 - ? makeOptional (FrameRate().withBaseRate ((int) processContext.frameRate.framesPerSecond) - .withDrop ((processContext.frameRate.flags & Vst::FrameRate::kDropRate) != 0) - .withPullDown ((processContext.frameRate.flags & Vst::FrameRate::kPullDownRate) != 0)) - : nullopt); - - info.setEditOriginTime (info.getFrameRate().hasValue() - ? makeOptional ((double) processContext.smpteOffsetSubframes / (80.0 * info.getFrameRate()->getEffectiveRate())) - : nullopt); - - info.setHostTimeNs ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0 - ? makeOptional ((uint64_t) processContext.systemTime) - : nullopt); - - return info; - } - - //============================================================================== - int getNumAudioBuses (bool isInput) const - { - int busCount = pluginInstance->getBusCount (isInput); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - const int numConfigs = numElementsInArray (configs); - - bool hasOnlyZeroChannels = true; - - for (int i = 0; i < numConfigs && hasOnlyZeroChannels == true; ++i) - if (configs[i][isInput ? 0 : 1] != 0) - hasOnlyZeroChannels = false; - - busCount = jmin (busCount, hasOnlyZeroChannels ? 0 : 1); - #endif - - return busCount; - } - - //============================================================================== - Steinberg::int32 PLUGIN_API getBusCount (Vst::MediaType type, Vst::BusDirection dir) override - { - if (type == Vst::kAudio) - return getNumAudioBuses (dir == Vst::kInput); - - if (type == Vst::kEvent) - { - #if JucePlugin_WantsMidiInput - if (dir == Vst::kInput) - return 1; - #endif - - #if JucePlugin_ProducesMidiOutput - if (dir == Vst::kOutput) - return 1; - #endif - } - - return 0; - } - - tresult PLUGIN_API getBusInfo (Vst::MediaType type, Vst::BusDirection dir, - Steinberg::int32 index, Vst::BusInfo& info) override - { - if (type == Vst::kAudio) - { - if (index < 0 || index >= getNumAudioBuses (dir == Vst::kInput)) - return kResultFalse; - - if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) - { - info.mediaType = Vst::kAudio; - info.direction = dir; - info.channelCount = bus->getLastEnabledLayout().size(); - - [[maybe_unused]] const auto lastEnabledVst3Layout = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); - jassert (lastEnabledVst3Layout.has_value() && info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (*lastEnabledVst3Layout)); - toString128 (info.name, bus->getName()); - - info.busType = [&] - { - const auto isFirstBus = (index == 0); - - if (dir == Vst::kInput) - { - if (isFirstBus) - { - if (auto* extensions = dynamic_cast (pluginInstance)) - return extensions->getPluginHasMainInput() ? Vst::kMain : Vst::kAux; - - return Vst::kMain; - } - - return Vst::kAux; - } - - #if JucePlugin_IsSynth - return Vst::kMain; - #else - return isFirstBus ? Vst::kMain : Vst::kAux; - #endif - }(); - - #ifdef JucePlugin_PreferredChannelConfigurations - info.flags = Vst::BusInfo::kDefaultActive; - #else - info.flags = (bus->isEnabledByDefault()) ? Vst::BusInfo::kDefaultActive : 0; - #endif - - return kResultTrue; - } - } - - if (type == Vst::kEvent) - { - info.flags = Vst::BusInfo::kDefaultActive; - - #if JucePlugin_WantsMidiInput - if (dir == Vst::kInput && index == 0) - { - info.mediaType = Vst::kEvent; - info.direction = dir; - - #ifdef JucePlugin_VSTNumMidiInputs - info.channelCount = JucePlugin_VSTNumMidiInputs; - #else - info.channelCount = 16; - #endif - - toString128 (info.name, TRANS("MIDI Input")); - info.busType = Vst::kMain; - return kResultTrue; - } - #endif - - #if JucePlugin_ProducesMidiOutput - if (dir == Vst::kOutput && index == 0) - { - info.mediaType = Vst::kEvent; - info.direction = dir; - - #ifdef JucePlugin_VSTNumMidiOutputs - info.channelCount = JucePlugin_VSTNumMidiOutputs; - #else - info.channelCount = 16; - #endif - - toString128 (info.name, TRANS("MIDI Output")); - info.busType = Vst::kMain; - return kResultTrue; - } - #endif - } - - zerostruct (info); - return kResultFalse; - } - - tresult PLUGIN_API activateBus (Vst::MediaType type, - Vst::BusDirection dir, - Steinberg::int32 index, - TBool state) override - { - const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); - - // The host is misbehaving! The plugin must be deactivated before setting new arrangements. - jassert (! active); - - if (type == Vst::kEvent) - { - #if JucePlugin_WantsMidiInput - if (index == 0 && dir == Vst::kInput) - { - isMidiInputBusEnabled = (state != 0); - return kResultTrue; - } - #endif - - #if JucePlugin_ProducesMidiOutput - if (index == 0 && dir == Vst::kOutput) - { - isMidiOutputBusEnabled = (state != 0); - return kResultTrue; - } - #endif - - return kResultFalse; - } - - if (type == Vst::kAudio) - { - const auto numInputBuses = getNumAudioBuses (true); - const auto numOutputBuses = getNumAudioBuses (false); - - if (! isPositiveAndBelow (index, dir == Vst::kInput ? numInputBuses : numOutputBuses)) - return kResultFalse; - - // The host is allowed to enable/disable buses as it sees fit, so the plugin needs to be - // able to handle any set of enabled/disabled buses, including layouts for which - // AudioProcessor::isBusesLayoutSupported would return false. - // Our strategy is to keep track of the layout that the host last requested, and to - // attempt to apply that layout directly. - // If the layout isn't supported by the processor, we'll try enabling all the buses - // instead. - // If the host enables a bus that the processor refused to enable, then we'll ignore - // that bus (and return silence for output buses). If the host disables a bus that the - // processor refuses to disable, the wrapper will provide the processor with silence for - // input buses, and ignore the contents of output buses. - // Note that some hosts (old bitwig and cakewalk) may incorrectly call this function - // when the plugin is in an activated state. - if (dir == Vst::kInput) - bufferMapper.setInputBusHostActive ((size_t) index, state != 0); - else - bufferMapper.setOutputBusHostActive ((size_t) index, state != 0); - - AudioProcessor::BusesLayout desiredLayout; - - for (auto i = 0; i < numInputBuses; ++i) - desiredLayout.inputBuses.add (bufferMapper.getRequestedLayoutForInputBus ((size_t) i)); - - for (auto i = 0; i < numOutputBuses; ++i) - desiredLayout.outputBuses.add (bufferMapper.getRequestedLayoutForOutputBus ((size_t) i)); - - const auto prev = pluginInstance->getBusesLayout(); - - const auto busesLayoutSupported = [&] - { - #ifdef JucePlugin_PreferredChannelConfigurations - struct ChannelPair - { - short ins, outs; - - auto tie() const { return std::tie (ins, outs); } - bool operator== (ChannelPair x) const { return tie() == x.tie(); } - }; - - const auto countChannels = [] (auto& range) - { - return std::accumulate (range.begin(), range.end(), 0, [] (auto acc, auto set) - { - return acc + set.size(); - }); - }; - - const auto toShort = [] (int x) - { - jassert (0 <= x && x <= std::numeric_limits::max()); - return (short) x; - }; - - const ChannelPair requested { toShort (countChannels (desiredLayout.inputBuses)), - toShort (countChannels (desiredLayout.outputBuses)) }; - const ChannelPair configs[] = { JucePlugin_PreferredChannelConfigurations }; - return std::find (std::begin (configs), std::end (configs), requested) != std::end (configs); - #else - return pluginInstance->checkBusesLayoutSupported (desiredLayout); - #endif - }(); - - if (busesLayoutSupported) - pluginInstance->setBusesLayout (desiredLayout); - else - pluginInstance->enableAllBuses(); - - bufferMapper.updateActiveClientBuses (pluginInstance->getBusesLayout()); - - return kResultTrue; - } - - return kResultFalse; - } - - bool checkBusFormatsAreNotDiscrete() - { - auto numInputBuses = pluginInstance->getBusCount (true); - auto numOutputBuses = pluginInstance->getBusCount (false); - - for (int i = 0; i < numInputBuses; ++i) - { - auto layout = pluginInstance->getChannelLayoutOfBus (true, i); - - if (layout.isDiscreteLayout() && ! layout.isDisabled()) - return false; - } - - for (int i = 0; i < numOutputBuses; ++i) - { - auto layout = pluginInstance->getChannelLayoutOfBus (false, i); - - if (layout.isDiscreteLayout() && ! layout.isDisabled()) - return false; - } - - return true; - } - - tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, - Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override - { - const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); - - if (active) - { - // The host is misbehaving! The plugin must be deactivated before setting new arrangements. - jassertfalse; - return kResultFalse; - } - - auto numInputBuses = pluginInstance->getBusCount (true); - auto numOutputBuses = pluginInstance->getBusCount (false); - - if (numIns > numInputBuses || numOuts > numOutputBuses) - return kResultFalse; - - // see the following documentation to understand the correct way to react to this callback - // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 - - const auto toLayoutsArray = [] (auto begin, auto end) -> std::optional> - { - Array result; - - for (auto it = begin; it != end; ++it) - { - const auto set = getChannelSetForSpeakerArrangement (*it); - - if (! set.has_value()) - return {}; - - result.add (*set); - } - - return result; - }; - - const auto optionalRequestedLayout = [&]() -> std::optional - { - const auto ins = toLayoutsArray (inputs, inputs + numIns); - const auto outs = toLayoutsArray (outputs, outputs + numOuts); - - if (! ins.has_value() || ! outs.has_value()) - return {}; - - AudioProcessor::BusesLayout result; - result.inputBuses = *ins; - result.outputBuses = *outs; - return result; - }(); - - if (! optionalRequestedLayout.has_value()) - return kResultFalse; - - const auto& requestedLayout = *optionalRequestedLayout; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - if (! AudioProcessor::containsLayout (requestedLayout, configs)) - return kResultFalse; - #endif - - if (pluginInstance->checkBusesLayoutSupported (requestedLayout)) - { - if (! pluginInstance->setBusesLayoutWithoutEnabling (requestedLayout)) - return kResultFalse; - - bufferMapper.updateFromProcessor (*pluginInstance); - return kResultTrue; - } - - // apply layout changes in reverse order as Steinberg says we should prioritize main buses - const auto nextBest = [this, numInputBuses, numOutputBuses, &requestedLayout] - { - auto layout = pluginInstance->getBusesLayout(); - - for (auto busIdx = jmax (numInputBuses, numOutputBuses) - 1; busIdx >= 0; --busIdx) - for (const auto isInput : { true, false }) - if (auto* bus = pluginInstance->getBus (isInput, busIdx)) - bus->isLayoutSupported (requestedLayout.getChannelSet (isInput, busIdx), &layout); - - return layout; - }(); - - if (pluginInstance->setBusesLayoutWithoutEnabling (nextBest)) - bufferMapper.updateFromProcessor (*pluginInstance); - - return kResultFalse; - } - - tresult PLUGIN_API getBusArrangement (Vst::BusDirection dir, Steinberg::int32 index, Vst::SpeakerArrangement& arr) override - { - if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) - { - if (const auto arrangement = getVst3SpeakerArrangement (bus->getLastEnabledLayout())) - { - arr = *arrangement; - return kResultTrue; - } - - // There's a bus here, but we can't represent its layout in terms of VST3 speakers! - jassertfalse; - } - - return kResultFalse; - } - - //============================================================================== - tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override - { - return (symbolicSampleSize == Vst::kSample32 - || (getPluginInstance().supportsDoublePrecisionProcessing() - && symbolicSampleSize == Vst::kSample64)) ? kResultTrue : kResultFalse; - } - - Steinberg::uint32 PLUGIN_API getLatencySamples() override - { - return (Steinberg::uint32) jmax (0, getPluginInstance().getLatencySamples()); - } - - tresult PLUGIN_API setupProcessing (Vst::ProcessSetup& newSetup) override - { - ScopedInSetupProcessingSetter inSetupProcessingSetter (juceVST3EditController); - - if (canProcessSampleSize (newSetup.symbolicSampleSize) != kResultTrue) - return kResultFalse; - - processSetup = newSetup; - processContext.sampleRate = processSetup.sampleRate; - - getPluginInstance().setProcessingPrecision (newSetup.symbolicSampleSize == Vst::kSample64 - ? AudioProcessor::doublePrecision - : AudioProcessor::singlePrecision); - getPluginInstance().setNonRealtime (newSetup.processMode == Vst::kOffline); - - preparePlugin (processSetup.sampleRate, processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); - - return kResultTrue; - } - - tresult PLUGIN_API setProcessing (TBool state) override - { - if (! state) - getPluginInstance().reset(); - - return kResultTrue; - } - - Steinberg::uint32 PLUGIN_API getTailSamples() override - { - auto tailLengthSeconds = getPluginInstance().getTailLengthSeconds(); - - if (tailLengthSeconds <= 0.0 || processSetup.sampleRate <= 0.0) - return Vst::kNoTail; - - if (tailLengthSeconds == std::numeric_limits::infinity()) - return Vst::kInfiniteTail; - - return (Steinberg::uint32) roundToIntAccurate (tailLengthSeconds * processSetup.sampleRate); - } - - //============================================================================== - void processParameterChanges (Vst::IParameterChanges& paramChanges) - { - jassert (pluginInstance != nullptr); - - struct ParamChangeInfo - { - Steinberg::int32 offsetSamples = 0; - double value = 0.0; - }; - - const auto getPointFromQueue = [] (Steinberg::Vst::IParamValueQueue* queue, Steinberg::int32 index) - { - ParamChangeInfo result; - return queue->getPoint (index, result.offsetSamples, result.value) == kResultTrue - ? makeOptional (result) - : nullopt; - }; - - const auto numParamsChanged = paramChanges.getParameterCount(); - - for (Steinberg::int32 i = 0; i < numParamsChanged; ++i) - { - if (auto* paramQueue = paramChanges.getParameterData (i)) - { - const auto vstParamID = paramQueue->getParameterId(); - const auto numPoints = paramQueue->getPointCount(); - - #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) - { - for (Steinberg::int32 point = 0; point < numPoints; ++point) - { - if (const auto change = getPointFromQueue (paramQueue, point)) - addParameterChangeToMidiBuffer (change->offsetSamples, vstParamID, change->value); - } - } - else - #endif - if (const auto change = getPointFromQueue (paramQueue, numPoints - 1)) - { - if (auto* param = comPluginInstance->getParamForVSTParamID (vstParamID)) - setValueAndNotifyIfChanged (*param, (float) change->value); - } - } - } - } - - void addParameterChangeToMidiBuffer (const Steinberg::int32 offsetSamples, const Vst::ParamID id, const double value) - { - // If the parameter is mapped to a MIDI CC message then insert it into the midiBuffer. - int channel, ctrlNumber; - - if (juceVST3EditController->getMidiControllerForParameter (id, channel, ctrlNumber)) - { - if (ctrlNumber == Vst::kAfterTouch) - midiBuffer.addEvent (MidiMessage::channelPressureChange (channel, - jlimit (0, 127, (int) (value * 128.0))), offsetSamples); - else if (ctrlNumber == Vst::kPitchBend) - midiBuffer.addEvent (MidiMessage::pitchWheel (channel, - jlimit (0, 0x3fff, (int) (value * 0x4000))), offsetSamples); - else - midiBuffer.addEvent (MidiMessage::controllerEvent (channel, - jlimit (0, 127, ctrlNumber), - jlimit (0, 127, (int) (value * 128.0))), offsetSamples); - } - } - - tresult PLUGIN_API process (Vst::ProcessData& data) override - { - const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); - - if (pluginInstance == nullptr) - return kResultFalse; - - if ((processSetup.symbolicSampleSize == Vst::kSample64) != pluginInstance->isUsingDoublePrecision()) - return kResultFalse; - - if (data.processContext != nullptr) - { - processContext = *data.processContext; - - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; - } - else - { - zerostruct (processContext); - - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = false; - } - - midiBuffer.clear(); - - if (data.inputParameterChanges != nullptr) - processParameterChanges (*data.inputParameterChanges); - - #if JucePlugin_WantsMidiInput - if (isMidiInputBusEnabled && data.inputEvents != nullptr) - MidiEventList::toMidiBuffer (midiBuffer, *data.inputEvents); - #endif - - if (detail::PluginUtilities::getHostType().isWavelab()) - { - const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; - const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; - - if ((pluginInstance->getTotalNumInputChannels() + pluginInstance->getTotalNumOutputChannels()) > 0 - && (numInputChans + numOutputChans) == 0) - return kResultFalse; - } - - // If all of these are zero, the host is attempting to flush parameters without processing audio. - if (data.numSamples != 0 || data.numInputs != 0 || data.numOutputs != 0) - { - if (processSetup.symbolicSampleSize == Vst::kSample32) processAudio (data); - else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio (data); - else jassertfalse; - } - - if (auto* changes = data.outputParameterChanges) - { - comPluginInstance->forAllChangedParameters ([&] (Vst::ParamID paramID, float value) - { - Steinberg::int32 queueIndex = 0; - - if (auto* queue = changes->addParameterData (paramID, queueIndex)) - { - Steinberg::int32 pointIndex = 0; - queue->addPoint (0, value, pointIndex); - } - }); - } - - #if JucePlugin_ProducesMidiOutput - if (isMidiOutputBusEnabled && data.outputEvents != nullptr) - MidiEventList::pluginToHostEventList (*data.outputEvents, midiBuffer); - #endif - - return kResultTrue; - } - -private: - /* FL's Patcher implements the VST3 specification incorrectly, calls process() before/during - setActive(). - */ - class [[nodiscard]] FLStudioDIYSpecificationEnforcementLock - { - public: - explicit FLStudioDIYSpecificationEnforcementLock (CriticalSection& mutex) - { - static const auto lockRequired = PluginHostType().isFruityLoops(); - - if (lockRequired) - lock.emplace (mutex); - } - - private: - std::optional lock; - }; - - InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - #if JucePlugin_Enable_ARA - UniqueBase{}, - UniqueBase{}, - #endif - SharedBase{}); - - if (result.isOk()) - return result; - - if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) - return { kResultOk, comPluginInstance.get() }; - - return {}; - } - - //============================================================================== - struct ScopedInSetupProcessingSetter - { - ScopedInSetupProcessingSetter (JuceVST3EditController* c) - : controller (c) - { - if (controller != nullptr) - controller->inSetupProcessing = true; - } - - ~ScopedInSetupProcessingSetter() - { - if (controller != nullptr) - controller->inSetupProcessing = false; - } - - private: - JuceVST3EditController* controller = nullptr; - }; - - //============================================================================== - template - void processAudio (Vst::ProcessData& data) - { - ClientRemappedBuffer remappedBuffer { bufferMapper, data }; - auto& buffer = remappedBuffer.buffer; - - jassert ((int) buffer.getNumChannels() == jmax (pluginInstance->getTotalNumInputChannels(), - pluginInstance->getTotalNumOutputChannels())); - - { - const ScopedLock sl (pluginInstance->getCallbackLock()); - - pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); - - #if JUCE_DEBUG && ! JucePlugin_ProducesMidiOutput - const int numMidiEventsComingIn = midiBuffer.getNumEvents(); - #endif - - if (pluginInstance->isSuspended()) - { - buffer.clear(); - } - else - { - // processBlockBypassed should only ever be called if the AudioProcessor doesn't - // return a valid parameter from getBypassParameter - if (pluginInstance->getBypassParameter() == nullptr && comPluginInstance->getBypassParameter()->getValue() >= 0.5f) - pluginInstance->processBlockBypassed (buffer, midiBuffer); - else - pluginInstance->processBlock (buffer, midiBuffer); - } - - #if JUCE_DEBUG && (! JucePlugin_ProducesMidiOutput) - /* This assertion is caused when you've added some events to the - midiMessages array in your processBlock() method, which usually means - that you're trying to send them somewhere. But in this case they're - getting thrown away. - - If your plugin does want to send MIDI messages, you'll need to set - the JucePlugin_ProducesMidiOutput macro to 1 in your - JucePluginCharacteristics.h file. - - If you don't want to produce any MIDI output, then you should clear the - midiMessages array at the end of your processBlock() method, to - indicate that you don't want any of the events to be passed through - to the output. - */ - jassert (midiBuffer.getNumEvents() <= numMidiEventsComingIn); - #endif - } - } - - //============================================================================== - Steinberg::uint32 PLUGIN_API getProcessContextRequirements() override - { - return kNeedSystemTime - | kNeedContinousTimeSamples - | kNeedProjectTimeMusic - | kNeedBarPositionMusic - | kNeedCycleMusic - | kNeedSamplesToNextClock - | kNeedTempo - | kNeedTimeSignature - | kNeedChord - | kNeedFrameRate - | kNeedTransportState; - } - - void preparePlugin (double sampleRate, int bufferSize, CallPrepareToPlay callPrepareToPlay) - { - auto& p = getPluginInstance(); - - p.setRateAndBufferSizeDetails (sampleRate, bufferSize); - - if (callPrepareToPlay == CallPrepareToPlay::yes) - p.prepareToPlay (sampleRate, bufferSize); - - midiBuffer.ensureSize (2048); - midiBuffer.clear(); - - bufferMapper.updateFromProcessor (p); - bufferMapper.prepare (bufferSize); - } - - //============================================================================== - #if JucePlugin_Enable_ARA - const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE - { - return createARAFactory(); - } - - const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentController (ARA::ARADocumentControllerRef /*controllerRef*/) SMTG_OVERRIDE - { - ARA_VALIDATE_API_STATE (false && "call is deprecated in ARA 2, host must not call this"); - return nullptr; - } - - const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentControllerWithRoles (ARA::ARADocumentControllerRef documentControllerRef, - ARA::ARAPlugInInstanceRoleFlags knownRoles, ARA::ARAPlugInInstanceRoleFlags assignedRoles) SMTG_OVERRIDE - { - AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (pluginInstance); - return araAudioProcessorExtension->bindToARA (documentControllerRef, knownRoles, assignedRoles); - } - #endif - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - std::atomic refCount { 1 }; - AudioProcessor* pluginInstance = nullptr; - - #if JUCE_LINUX || JUCE_BSD - template - struct LockedVSTComSmartPtr - { - LockedVSTComSmartPtr() = default; - LockedVSTComSmartPtr (const VSTComSmartPtr& ptrIn) : ptr (ptrIn) {} - LockedVSTComSmartPtr (const LockedVSTComSmartPtr&) = default; - LockedVSTComSmartPtr& operator= (const LockedVSTComSmartPtr&) = default; - - ~LockedVSTComSmartPtr() - { - const MessageManagerLock mmLock; - ptr = {}; - } - - T* operator->() const { return ptr.operator->(); } - T* get() const noexcept { return ptr.get(); } - operator T*() const noexcept { return ptr.get(); } - - template - bool loadFrom (Args&&... args) { return ptr.loadFrom (std::forward (args)...); } - - private: - VSTComSmartPtr ptr; - }; - - LockedVSTComSmartPtr host; - LockedVSTComSmartPtr comPluginInstance; - LockedVSTComSmartPtr juceVST3EditController; - #else - VSTComSmartPtr host; - VSTComSmartPtr comPluginInstance; - VSTComSmartPtr juceVST3EditController; - #endif - - /** - Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, - this object needs to be copied on every call to process() to be up-to-date... - */ - Vst::ProcessContext processContext; - Vst::ProcessSetup processSetup; - - MidiBuffer midiBuffer; - ClientBufferMapper bufferMapper; - - bool active = false; - - #if JucePlugin_WantsMidiInput - std::atomic isMidiInputBusEnabled { true }; - #endif - #if JucePlugin_ProducesMidiOutput - std::atomic isMidiOutputBusEnabled { true }; - #endif - - static const char* kJucePrivateDataIdentifier; - CriticalSection flStudioDIYSpecificationEnforcementMutex; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Component) -}; - -const char* JuceVST3Component::kJucePrivateDataIdentifier = "JUCEPrivateData"; - -//============================================================================== -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4310) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wall") - -DECLARE_CLASS_IID (JuceAudioProcessor, 0x0101ABAB, 0xABCDEF01, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) -DEF_CLASS_IID (JuceAudioProcessor) - -#if JUCE_VST3_CAN_REPLACE_VST2 - static FUID getFUIDForVST2ID (bool forControllerUID) - { - TUID uuid; - detail::PluginUtilities::getUUIDForVST2ID (forControllerUID, (uint8*) uuid); - return FUID (uuid); - } - const Steinberg::FUID JuceVST3Component ::iid (getFUIDForVST2ID (false)); - const Steinberg::FUID JuceVST3EditController::iid (getFUIDForVST2ID (true)); -#else - DECLARE_CLASS_IID (JuceVST3EditController, 0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) - DEF_CLASS_IID (JuceVST3EditController) - - DECLARE_CLASS_IID (JuceVST3Component, 0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) - DEF_CLASS_IID (JuceVST3Component) -#endif - -#if JucePlugin_Enable_ARA - DECLARE_CLASS_IID (JuceARAFactory, 0xABCDEF01, 0xA1B2C3D4, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) - DEF_CLASS_IID (JuceARAFactory) -#endif - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -//============================================================================== -bool initModule(); -bool initModule() -{ - return true; -} - -bool shutdownModule(); -bool shutdownModule() -{ - return true; -} - -#undef JUCE_EXPORTED_FUNCTION - -#if JUCE_WINDOWS - #define JUCE_EXPORTED_FUNCTION -#else - #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) -#endif - -#if JUCE_WINDOWS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - - extern "C" __declspec (dllexport) bool InitDll() { return initModule(); } - extern "C" __declspec (dllexport) bool ExitDll() { return shutdownModule(); } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#elif JUCE_LINUX || JUCE_BSD - void* moduleHandle = nullptr; - int moduleEntryCounter = 0; - - JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle); - JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle) - { - if (++moduleEntryCounter == 1) - { - moduleHandle = sharedLibraryHandle; - return initModule(); - } - - return true; - } - - JUCE_EXPORTED_FUNCTION bool ModuleExit(); - JUCE_EXPORTED_FUNCTION bool ModuleExit() - { - if (--moduleEntryCounter == 0) - { - moduleHandle = nullptr; - return shutdownModule(); - } - - return true; - } -#elif JUCE_MAC - CFBundleRef globalBundleInstance = nullptr; - juce::uint32 numBundleRefs = 0; - juce::Array bundleRefs; - - enum { MaxPathLength = 2048 }; - char modulePath[MaxPathLength] = { 0 }; - void* moduleHandle = nullptr; - - JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref); - JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref) - { - if (ref != nullptr) - { - ++numBundleRefs; - CFRetain (ref); - - bundleRefs.add (ref); - - if (moduleHandle == nullptr) - { - globalBundleInstance = ref; - moduleHandle = ref; - - CFUniquePtr tempURL (CFBundleCopyBundleURL (ref)); - CFURLGetFileSystemRepresentation (tempURL.get(), true, (UInt8*) modulePath, MaxPathLength); - } - } - - return initModule(); - } - - JUCE_EXPORTED_FUNCTION bool bundleExit(); - JUCE_EXPORTED_FUNCTION bool bundleExit() - { - if (shutdownModule()) - { - if (--numBundleRefs == 0) - { - for (int i = 0; i < bundleRefs.size(); ++i) - CFRelease (bundleRefs.getUnchecked (i)); - - bundleRefs.clear(); - } - - return true; - } - - return false; - } -#endif - -//============================================================================== -/** This typedef represents VST3's createInstance() function signature */ -using CreateFunction = FUnknown* (*)(Vst::IHostApplication*); - -static FUnknown* createComponentInstance (Vst::IHostApplication* host) -{ - return static_cast (new JuceVST3Component (host)); -} - -static FUnknown* createControllerInstance (Vst::IHostApplication* host) -{ - return static_cast (new JuceVST3EditController (host)); -} - -#if JucePlugin_Enable_ARA - static FUnknown* createARAFactoryInstance (Vst::IHostApplication* /*host*/) - { - return static_cast (new JuceARAFactory()); - } -#endif - -//============================================================================== -struct JucePluginFactory; -static JucePluginFactory* globalFactory = nullptr; - -//============================================================================== -struct JucePluginFactory : public IPluginFactory3 -{ - JucePluginFactory() - : factoryInfo (JucePlugin_Manufacturer, JucePlugin_ManufacturerWebsite, - JucePlugin_ManufacturerEmail, Vst::kDefaultFactoryFlags) - { - } - - virtual ~JucePluginFactory() - { - if (globalFactory == this) - globalFactory = nullptr; - } - - //============================================================================== - bool registerClass (const PClassInfo2& info, CreateFunction createFunction) - { - if (createFunction == nullptr) - { - jassertfalse; - return false; - } - - auto entry = std::make_unique (info, createFunction); - entry->infoW.fromAscii (info); - - classes.push_back (std::move (entry)); - - return true; - } - - //============================================================================== - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}); - - if (result.isOk()) - return result.extract (obj); - - jassertfalse; // Something new? - *obj = nullptr; - return kNotImplemented; - } - - //============================================================================== - Steinberg::int32 PLUGIN_API countClasses() override - { - return (Steinberg::int32) classes.size(); - } - - tresult PLUGIN_API getFactoryInfo (PFactoryInfo* info) override - { - if (info == nullptr) - return kInvalidArgument; - - memcpy (info, &factoryInfo, sizeof (PFactoryInfo)); - return kResultOk; - } - - tresult PLUGIN_API getClassInfo (Steinberg::int32 index, PClassInfo* info) override - { - return getPClassInfo (index, info); - } - - tresult PLUGIN_API getClassInfo2 (Steinberg::int32 index, PClassInfo2* info) override - { - return getPClassInfo (index, info); - } - - tresult PLUGIN_API getClassInfoUnicode (Steinberg::int32 index, PClassInfoW* info) override - { - if (info != nullptr) - { - if (auto& entry = classes[(size_t) index]) - { - memcpy (info, &entry->infoW, sizeof (PClassInfoW)); - return kResultOk; - } - } - - return kInvalidArgument; - } - - tresult PLUGIN_API createInstance (FIDString cid, FIDString sourceIid, void** obj) override - { - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - *obj = nullptr; - - TUID tuid; - memcpy (tuid, sourceIid, sizeof (TUID)); - - #if VST_VERSION >= 0x030608 - auto sourceFuid = FUID::fromTUID (tuid); - #else - FUID sourceFuid; - sourceFuid = tuid; - #endif - - if (cid == nullptr || sourceIid == nullptr || ! sourceFuid.isValid()) - { - jassertfalse; // The host you're running in has severe implementation issues! - return kInvalidArgument; - } - - TUID iidToQuery; - sourceFuid.toTUID (iidToQuery); - - for (auto& entry : classes) - { - if (doUIDsMatch (entry->infoW.cid, cid)) - { - if (auto* instance = entry->createFunction (host)) - { - const FReleaser releaser (instance); - - if (instance->queryInterface (iidToQuery, obj) == kResultOk) - return kResultOk; - } - - break; - } - } - - return kNoInterface; - } - - tresult PLUGIN_API setHostContext (FUnknown* context) override - { - host.loadFrom (context); - - if (host != nullptr) - { - Vst::String128 name; - host->getName (name); - - return kResultTrue; - } - - return kNotImplemented; - } - -private: - //============================================================================== - std::atomic refCount { 1 }; - const PFactoryInfo factoryInfo; - VSTComSmartPtr host; - - //============================================================================== - struct ClassEntry - { - ClassEntry() noexcept {} - - ClassEntry (const PClassInfo2& info, CreateFunction fn) noexcept - : info2 (info), createFunction (fn) {} - - PClassInfo2 info2; - PClassInfoW infoW; - CreateFunction createFunction = {}; - bool isUnicode = false; - - private: - JUCE_DECLARE_NON_COPYABLE (ClassEntry) - }; - - std::vector> classes; - - //============================================================================== - template - tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) - { - if (info != nullptr) - { - zerostruct (*info); - - if (auto& entry = classes[(size_t) index]) - { - if (entry->isUnicode) - return kResultFalse; - - memcpy (info, (PClassInfoType*) &entry->info2, sizeof (PClassInfoType)); - return kResultOk; - } - } - - jassertfalse; - return kInvalidArgument; - } - - //============================================================================== - // no leak detector here to prevent it firing on shutdown when running in hosts that - // don't release the factory object correctly... - JUCE_DECLARE_NON_COPYABLE (JucePluginFactory) -}; - -} // namespace juce - -//============================================================================== -#ifndef JucePlugin_Vst3ComponentFlags - #if JucePlugin_IsSynth - #define JucePlugin_Vst3ComponentFlags Vst::kSimpleModeSupported - #else - #define JucePlugin_Vst3ComponentFlags 0 - #endif -#endif - -#ifndef JucePlugin_Vst3Category - #if JucePlugin_IsSynth - #define JucePlugin_Vst3Category Vst::PlugType::kInstrumentSynth - #else - #define JucePlugin_Vst3Category Vst::PlugType::kFx - #endif -#endif - -using namespace juce; - -//============================================================================== -// The VST3 plugin entry point. -extern "C" SMTG_EXPORT_SYMBOL IPluginFactory* PLUGIN_API GetPluginFactory() -{ - #if (JUCE_MSVC || (JUCE_WINDOWS && JUCE_CLANG)) && JUCE_32BIT - // Cunning trick to force this function to be exported. Life's too short to - // faff around creating .def files for this kind of thing. - // Unnecessary for 64-bit builds because those don't use decorated function names. - #pragma comment(linker, "/EXPORT:GetPluginFactory=_GetPluginFactory@0") - #endif - - if (globalFactory == nullptr) - { - globalFactory = new JucePluginFactory(); - - static const PClassInfo2 componentClass (JuceVST3Component::iid, - PClassInfo::kManyInstances, - kVstAudioEffectClass, - JucePlugin_Name, - JucePlugin_Vst3ComponentFlags, - JucePlugin_Vst3Category, - JucePlugin_Manufacturer, - JucePlugin_VersionString, - kVstVersionString); - - globalFactory->registerClass (componentClass, createComponentInstance); - - static const PClassInfo2 controllerClass (JuceVST3EditController::iid, - PClassInfo::kManyInstances, - kVstComponentControllerClass, - JucePlugin_Name, - JucePlugin_Vst3ComponentFlags, - JucePlugin_Vst3Category, - JucePlugin_Manufacturer, - JucePlugin_VersionString, - kVstVersionString); - - globalFactory->registerClass (controllerClass, createControllerInstance); - - #if JucePlugin_Enable_ARA - static const PClassInfo2 araFactoryClass (JuceARAFactory::iid, - PClassInfo::kManyInstances, - kARAMainFactoryClass, - JucePlugin_Name, - JucePlugin_Vst3ComponentFlags, - JucePlugin_Vst3Category, - JucePlugin_Manufacturer, - JucePlugin_VersionString, - kVstVersionString); - - globalFactory->registerClass (araFactoryClass, createARAFactoryInstance); - #endif - } - else - { - globalFactory->addRef(); - } - - return dynamic_cast (globalFactory); -} - -//============================================================================== -#if JUCE_WINDOWS -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -JUCE_END_NO_SANITIZE - -#endif //JucePlugin_Build_VST3 diff --git a/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.cpp b/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.cpp deleted file mode 100644 index 2fc19ae41f..0000000000 --- a/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include - -#if JUCE_WINDOWS - #include -#endif - -namespace juce::detail -{ - -#if JucePlugin_Build_Unity -bool isRunningInUnity(); -bool isRunningInUnity() { return PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Unity; } -#endif - -#if VST3_REPLACEMENT_AVAILABLE - -// NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. -void PluginUtilities::getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) -{ - #if JUCE_MSVC - const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, numElementsInArray (head), tail...); }; - const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, numElementsInArray (head), tail...); }; - const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, numElementsInArray (head), tail...); }; - const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; - #else - const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; - const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; - const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; - const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; - #endif - - char uidString[33]; - - const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); - char vstfxidStr[7] = { 0 }; - juce_sprintf (vstfxidStr, "%06X", vstfxid); - - juce_strcpy (uidString, vstfxidStr); - - char uidStr[9] = { 0 }; - juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); - juce_strcat (uidString, uidStr); - - char nameidStr[3] = { 0 }; - const size_t len = strlen (JucePlugin_Name); - - for (size_t i = 0; i <= 8; ++i) - { - juce::uint8 c = i < len ? static_cast (JucePlugin_Name[i]) : 0; - - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - - juce_sprintf (nameidStr, "%02X", c); - juce_strcat (uidString, nameidStr); - } - - unsigned long p0; - unsigned int p1, p2; - unsigned int p3[8]; - - juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", - &p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); - - union q0_u { - uint32 word; - uint8 bytes[4]; - } q0; - - union q1_u { - uint16 half; - uint8 bytes[2]; - } q1, q2; - - q0.word = static_cast (p0); - q1.half = static_cast (p1); - q2.half = static_cast (p2); - - // VST3 doesn't use COM compatible UUIDs on non windows platforms - #if ! JUCE_WINDOWS - q0.word = ByteOrder::swap (q0.word); - q1.half = ByteOrder::swap (q1.half); - q2.half = ByteOrder::swap (q2.half); - #endif - - for (int i = 0; i < 4; ++i) - uuid[i+0] = q0.bytes[i]; - - for (int i = 0; i < 2; ++i) - uuid[i+4] = q1.bytes[i]; - - for (int i = 0; i < 2; ++i) - uuid[i+6] = q2.bytes[i]; - - for (int i = 0; i < 8; ++i) - uuid[i+8] = static_cast (p3[i]); -} -#endif - -#if JucePlugin_Build_VST -bool PluginUtilities::handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, - [[maybe_unused]] pointer_sized_int value, - [[maybe_unused]] void* ptr, - float) -{ - #if VST3_REPLACEMENT_AVAILABLE - if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) - && value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) - { - uint8 fuid[16]; - getUUIDForVST2ID (false, fuid); - ::memcpy (ptr, fuid, 16); - return true; - } - #endif - return false; -} -#endif - -} // namespace juce diff --git a/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h b/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h index 7e718e13d9..762261174f 100644 --- a/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h +++ b/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h @@ -30,6 +30,13 @@ namespace juce::detail { +bool isRunningInUnity(); +#if JucePlugin_Build_Unity +bool isRunningInUnity() { return PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Unity; } +#else +bool isRunningInUnity() { return false; } +#endif + struct PluginUtilities { PluginUtilities() = delete; @@ -61,20 +68,106 @@ struct PluginUtilities #define JUCE_VST3_CAN_REPLACE_VST2 1 #endif - #if JucePlugin_Build_VST3 && JUCE_VST3_CAN_REPLACE_VST2 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD) - #define VST3_REPLACEMENT_AVAILABLE 1 - - static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]); - - #else - #define VST3_REPLACEMENT_AVAILABLE 0 - #endif + // NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. + static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) + { + #if JUCE_WINDOWS && ! JUCE_MINGW + const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; + #else + const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; + const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; + const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; + #endif + + char uidString[33]; + + const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); + char vstfxidStr[7] = { 0 }; + juce_sprintf (vstfxidStr, "%06X", vstfxid); + + juce_strcpy (uidString, vstfxidStr); + + char uidStr[9] = { 0 }; + juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); + juce_strcat (uidString, uidStr); + + char nameidStr[3] = { 0 }; + const size_t len = strlen (JucePlugin_Name); + + for (size_t i = 0; i <= 8; ++i) + { + juce::uint8 c = i < len ? static_cast (JucePlugin_Name[i]) : 0; + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + juce_sprintf (nameidStr, "%02X", c); + juce_strcat (uidString, nameidStr); + } + + unsigned long p0; + unsigned int p1, p2; + unsigned int p3[8]; + + juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", + &p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); + + union q0_u { + uint32 word; + uint8 bytes[4]; + } q0; + + union q1_u { + uint16 half; + uint8 bytes[2]; + } q1, q2; + + q0.word = static_cast (p0); + q1.half = static_cast (p1); + q2.half = static_cast (p2); + + // VST3 doesn't use COM compatible UUIDs on non windows platforms + #if ! JUCE_WINDOWS + q0.word = ByteOrder::swap (q0.word); + q1.half = ByteOrder::swap (q1.half); + q2.half = ByteOrder::swap (q2.half); + #endif + + for (int i = 0; i < 4; ++i) + uuid[i+0] = q0.bytes[i]; + + for (int i = 0; i < 2; ++i) + uuid[i+4] = q1.bytes[i]; + + for (int i = 0; i < 2; ++i) + uuid[i+6] = q2.bytes[i]; + + for (int i = 0; i < 8; ++i) + uuid[i+8] = static_cast (p3[i]); + } #if JucePlugin_Build_VST - static bool handleManufacturerSpecificVST2Opcode (int32 index, - pointer_sized_int value, - void* ptr, - float); + static bool handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, + [[maybe_unused]] pointer_sized_int value, + [[maybe_unused]] void* ptr, + float) + { + #if JUCE_VST3_CAN_REPLACE_VST2 + if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) + && value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) + { + uint8 fuid[16]; + getUUIDForVST2ID (false, fuid); + ::memcpy (ptr, fuid, 16); + return true; + } + #endif + return false; + } #endif }; diff --git a/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h b/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h index b7439d1ef8..7eb88a2d9a 100644 --- a/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h +++ b/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h @@ -38,15 +38,59 @@ struct VSTWindowUtilities static void* attachComponentToWindowRefVST (Component* comp, int desktopFlags, - void* parentWindowOrView); + void* parentWindowOrView) + { + JUCE_AUTORELEASEPOOL + { + NSView* parentView = [(NSView*) parentWindowOrView retain]; + + const auto defaultFlags = JucePlugin_EditorRequiresKeyboardFocus + ? 0 + : ComponentPeer::windowIgnoresKeyPresses; + comp->addToDesktop (desktopFlags | defaultFlags, parentView); + + // (this workaround is because Wavelab provides a zero-size parent view..) + if ([parentView frame].size.height == 0) + [((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; + + comp->setVisible (true); + comp->toFront (false); + + [[parentView window] setAcceptsMouseMovedEvents: YES]; + return parentView; + } + } static void detachComponentFromWindowRefVST (Component* comp, - void* window); + void* window) + { + JUCE_AUTORELEASEPOOL + { + comp->removeFromDesktop(); + [(id) window release]; + } + } static void setNativeHostWindowSizeVST (void* window, Component* component, int newWidth, - int newHeight); + int newHeight) + { + JUCE_AUTORELEASEPOOL + { + if (NSView* hostView = (NSView*) window) + { + const int dx = newWidth - component->getWidth(); + const int dy = newHeight - component->getHeight(); + + NSRect r = [hostView frame]; + r.size.width += dx; + r.size.height += dy; + r.origin.y -= dy; + [hostView setFrame: r]; + } + } + } }; } // namespace juce::detail diff --git a/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.mm b/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.mm deleted file mode 100644 index 1014dd4510..0000000000 --- a/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.mm +++ /dev/null @@ -1,96 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 - -#include - -#if JUCE_MAC - -#include -#include -#include - -namespace juce::detail -{ - -void* VSTWindowUtilities::attachComponentToWindowRefVST (Component* comp, - int desktopFlags, - void* parentWindowOrView) -{ - JUCE_AUTORELEASEPOOL - { - NSView* parentView = [(NSView*) parentWindowOrView retain]; - - const auto defaultFlags = JucePlugin_EditorRequiresKeyboardFocus - ? 0 - : ComponentPeer::windowIgnoresKeyPresses; - comp->addToDesktop (desktopFlags | defaultFlags, parentView); - - // (this workaround is because Wavelab provides a zero-size parent view..) - if ([parentView frame].size.height == 0) - [((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; - - comp->setVisible (true); - comp->toFront (false); - - [[parentView window] setAcceptsMouseMovedEvents: YES]; - return parentView; - } -} - -void VSTWindowUtilities::detachComponentFromWindowRefVST (Component* comp, void* window) -{ - JUCE_AUTORELEASEPOOL - { - comp->removeFromDesktop(); - [(id) window release]; - } -} - -void VSTWindowUtilities::setNativeHostWindowSizeVST (void* window, - Component* component, - int newWidth, - int newHeight) -{ - JUCE_AUTORELEASEPOOL - { - if (NSView* hostView = (NSView*) window) - { - const int dx = newWidth - component->getWidth(); - const int dy = newHeight - component->getHeight(); - - NSRect r = [hostView frame]; - r.size.width += dx; - r.size.height += dy; - r.origin.y -= dy; - [hostView setFrame: r]; - } - } -} - -} // namespace juce::detail - -#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp index dab46ad5ff..8d616052b8 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp @@ -23,4 +23,2562 @@ ============================================================================== */ -#include "AAX/juce_AAX_Wrapper.cpp" +#include +#include + +#if JucePlugin_Build_AAX + +#include +#include +#include + +#include + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4127 4512 4996) +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor", + "-Wsign-conversion", + "-Wextra-semi", + "-Wshift-sign-overflow", + "-Wpragma-pack", + "-Wzero-as-null-pointer-constant", + "-Winconsistent-missing-destructor-override", + "-Wfour-char-constants", + "-Wtautological-overlap-compare", + "-Wdeprecated-declarations") + +#include + +static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p4p0_REVISION, "JUCE requires AAX SDK version 2.4.0 or higher"); + +#define INITACFIDS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfour-char-constants") + +#undef check + +#include + +using namespace juce; + +#ifndef JucePlugin_AAX_Chunk_Identifier + #define JucePlugin_AAX_Chunk_Identifier 'juce' +#endif + +const int32_t juceChunkType = JucePlugin_AAX_Chunk_Identifier; + +//============================================================================== +namespace AAXClasses +{ + static int32 getAAXParamHash (AAX_CParamID paramID) noexcept + { + int32 result = 0; + + while (*paramID != 0) + result = (31 * result) + (*paramID++); + + return result; + } + + static void check (AAX_Result result) + { + jassertquiet (result == AAX_SUCCESS); + } + + // maps a channel index of an AAX format to an index of a juce format + struct AAXChannelStreamOrder + { + AAX_EStemFormat aaxStemFormat; + AudioChannelSet::ChannelType speakerOrder[10]; + }; + + static AAX_EStemFormat stemFormatForAmbisonicOrder (int order) + { + switch (order) + { + case 1: return AAX_eStemFormat_Ambi_1_ACN; + case 2: return AAX_eStemFormat_Ambi_2_ACN; + case 3: return AAX_eStemFormat_Ambi_3_ACN; + default: break; + } + + return AAX_eStemFormat_INT32_MAX; + } + + static AAXChannelStreamOrder aaxChannelOrder[] = + { + { AAX_eStemFormat_Mono, { AudioChannelSet::centre, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_Stereo, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_LCR, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::unknown, AudioChannelSet::unknown, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_LCRS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::centreSurround, AudioChannelSet::unknown, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_Quad, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::unknown, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_5_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_5_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_6_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, + AudioChannelSet::rightSurround, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_6_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, + AudioChannelSet::rightSurround, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_7_0_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, + AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_7_0_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_7_1_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, + AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_7_1_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_7_0_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, AudioChannelSet::unknown } }, + + { AAX_eStemFormat_7_1_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, + + { AAX_eStemFormat_None, { AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, + AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, + }; + + static AAX_EStemFormat aaxFormats[] = + { + AAX_eStemFormat_Mono, + AAX_eStemFormat_Stereo, + AAX_eStemFormat_LCR, + AAX_eStemFormat_LCRS, + AAX_eStemFormat_Quad, + AAX_eStemFormat_5_0, + AAX_eStemFormat_5_1, + AAX_eStemFormat_6_0, + AAX_eStemFormat_6_1, + AAX_eStemFormat_7_0_SDDS, + AAX_eStemFormat_7_1_SDDS, + AAX_eStemFormat_7_0_DTS, + AAX_eStemFormat_7_1_DTS, + AAX_eStemFormat_7_0_2, + AAX_eStemFormat_7_1_2, + AAX_eStemFormat_Ambi_1_ACN, + AAX_eStemFormat_Ambi_2_ACN, + AAX_eStemFormat_Ambi_3_ACN + }; + + static AAX_EStemFormat getFormatForAudioChannelSet (const AudioChannelSet& set, bool ignoreLayout) noexcept + { + // if the plug-in ignores layout, it is ok to convert between formats only by their numchannnels + if (ignoreLayout) + { + auto numChannels = set.size(); + + switch (numChannels) + { + case 0: return AAX_eStemFormat_None; + case 1: return AAX_eStemFormat_Mono; + case 2: return AAX_eStemFormat_Stereo; + case 3: return AAX_eStemFormat_LCR; + case 4: return AAX_eStemFormat_Quad; + case 5: return AAX_eStemFormat_5_0; + case 6: return AAX_eStemFormat_5_1; + case 7: return AAX_eStemFormat_7_0_DTS; + case 8: return AAX_eStemFormat_7_1_DTS; + case 9: return AAX_eStemFormat_7_0_2; + case 10: return AAX_eStemFormat_7_1_2; + default: break; + } + + // check for ambisonics support + auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; + auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); + + if (static_cast (ambisonicOrder) == sqrtMinusOne) + return stemFormatForAmbisonicOrder (ambisonicOrder); + + return AAX_eStemFormat_INT32_MAX; + } + + if (set == AudioChannelSet::disabled()) return AAX_eStemFormat_None; + if (set == AudioChannelSet::mono()) return AAX_eStemFormat_Mono; + if (set == AudioChannelSet::stereo()) return AAX_eStemFormat_Stereo; + if (set == AudioChannelSet::createLCR()) return AAX_eStemFormat_LCR; + if (set == AudioChannelSet::createLCRS()) return AAX_eStemFormat_LCRS; + if (set == AudioChannelSet::quadraphonic()) return AAX_eStemFormat_Quad; + if (set == AudioChannelSet::create5point0()) return AAX_eStemFormat_5_0; + if (set == AudioChannelSet::create5point1()) return AAX_eStemFormat_5_1; + if (set == AudioChannelSet::create6point0()) return AAX_eStemFormat_6_0; + if (set == AudioChannelSet::create6point1()) return AAX_eStemFormat_6_1; + if (set == AudioChannelSet::create7point0()) return AAX_eStemFormat_7_0_DTS; + if (set == AudioChannelSet::create7point1()) return AAX_eStemFormat_7_1_DTS; + if (set == AudioChannelSet::create7point0SDDS()) return AAX_eStemFormat_7_0_SDDS; + if (set == AudioChannelSet::create7point1SDDS()) return AAX_eStemFormat_7_1_SDDS; + if (set == AudioChannelSet::create7point0point2()) return AAX_eStemFormat_7_0_2; + if (set == AudioChannelSet::create7point1point2()) return AAX_eStemFormat_7_1_2; + + auto order = set.getAmbisonicOrder(); + if (order >= 0) + return stemFormatForAmbisonicOrder (order); + + return AAX_eStemFormat_INT32_MAX; + } + + static inline AudioChannelSet channelSetFromStemFormat (AAX_EStemFormat format, bool ignoreLayout) noexcept + { + if (! ignoreLayout) + { + switch (format) + { + case AAX_eStemFormat_None: return AudioChannelSet::disabled(); + case AAX_eStemFormat_Mono: return AudioChannelSet::mono(); + case AAX_eStemFormat_Stereo: return AudioChannelSet::stereo(); + case AAX_eStemFormat_LCR: return AudioChannelSet::createLCR(); + case AAX_eStemFormat_LCRS: return AudioChannelSet::createLCRS(); + case AAX_eStemFormat_Quad: return AudioChannelSet::quadraphonic(); + case AAX_eStemFormat_5_0: return AudioChannelSet::create5point0(); + case AAX_eStemFormat_5_1: return AudioChannelSet::create5point1(); + case AAX_eStemFormat_6_0: return AudioChannelSet::create6point0(); + case AAX_eStemFormat_6_1: return AudioChannelSet::create6point1(); + case AAX_eStemFormat_7_0_SDDS: return AudioChannelSet::create7point0SDDS(); + case AAX_eStemFormat_7_0_DTS: return AudioChannelSet::create7point0(); + case AAX_eStemFormat_7_1_SDDS: return AudioChannelSet::create7point1SDDS(); + case AAX_eStemFormat_7_1_DTS: return AudioChannelSet::create7point1(); + case AAX_eStemFormat_7_0_2: return AudioChannelSet::create7point0point2(); + case AAX_eStemFormat_7_1_2: return AudioChannelSet::create7point1point2(); + case AAX_eStemFormat_Ambi_1_ACN: return AudioChannelSet::ambisonic (1); + case AAX_eStemFormat_Ambi_2_ACN: return AudioChannelSet::ambisonic (2); + case AAX_eStemFormat_Ambi_3_ACN: return AudioChannelSet::ambisonic (3); + case AAX_eStemFormat_Reserved_1: + case AAX_eStemFormat_Reserved_2: + case AAX_eStemFormat_Reserved_3: + case AAX_eStemFormatNum: + case AAX_eStemFormat_Any: + case AAX_eStemFormat_INT32_MAX: + default: return AudioChannelSet::disabled(); + } + } + + return AudioChannelSet::discreteChannels (jmax (0, static_cast (AAX_STEM_FORMAT_CHANNEL_COUNT (format)))); + } + + static AAX_EMeterType getMeterTypeForCategory (AudioProcessorParameter::Category category) + { + switch (category) + { + case AudioProcessorParameter::inputMeter: return AAX_eMeterType_Input; + case AudioProcessorParameter::outputMeter: return AAX_eMeterType_Output; + case AudioProcessorParameter::compressorLimiterGainReductionMeter: return AAX_eMeterType_CLGain; + case AudioProcessorParameter::expanderGateGainReductionMeter: return AAX_eMeterType_EGGain; + case AudioProcessorParameter::analysisMeter: return AAX_eMeterType_Analysis; + case AudioProcessorParameter::genericParameter: + case AudioProcessorParameter::inputGain: + case AudioProcessorParameter::outputGain: + case AudioProcessorParameter::otherMeter: + default: return AAX_eMeterType_Other; + } + } + + static Colour getColourFromHighlightEnum (AAX_EHighlightColor colour) noexcept + { + switch (colour) + { + case AAX_eHighlightColor_Red: return Colours::red; + case AAX_eHighlightColor_Blue: return Colours::blue; + case AAX_eHighlightColor_Green: return Colours::green; + case AAX_eHighlightColor_Yellow: return Colours::yellow; + case AAX_eHighlightColor_Num: + default: jassertfalse; break; + } + + return Colours::black; + } + + static int juceChannelIndexToAax (int juceIndex, const AudioChannelSet& channelSet) + { + auto isAmbisonic = (channelSet.getAmbisonicOrder() >= 0); + auto currentLayout = getFormatForAudioChannelSet (channelSet, false); + int layoutIndex; + + if (isAmbisonic && currentLayout != AAX_eStemFormat_INT32_MAX) + return juceIndex; + + for (layoutIndex = 0; aaxChannelOrder[layoutIndex].aaxStemFormat != currentLayout; ++layoutIndex) + if (aaxChannelOrder[layoutIndex].aaxStemFormat == 0) return juceIndex; + + auto& channelOrder = aaxChannelOrder[layoutIndex]; + auto channelType = channelSet.getTypeOfChannel (static_cast (juceIndex)); + auto numSpeakers = numElementsInArray (channelOrder.speakerOrder); + + for (int i = 0; i < numSpeakers && channelOrder.speakerOrder[i] != 0; ++i) + if (channelOrder.speakerOrder[i] == channelType) + return i; + + return juceIndex; + } + + //============================================================================== + class JuceAAX_Processor; + + struct PluginInstanceInfo + { + PluginInstanceInfo (JuceAAX_Processor& p) : parameters (p) {} + + JuceAAX_Processor& parameters; + + JUCE_DECLARE_NON_COPYABLE (PluginInstanceInfo) + }; + + //============================================================================== + struct JUCEAlgorithmContext + { + float** inputChannels; + float** outputChannels; + int32_t* bufferSize; + int32_t* bypass; + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + AAX_IMIDINode* midiNodeIn; + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + AAX_IMIDINode* midiNodeOut; + #endif + + PluginInstanceInfo* pluginInstance; + int32_t* isPrepared; + float* const* meterTapBuffers; + int32_t* sideChainBuffers; + }; + + struct JUCEAlgorithmIDs + { + enum + { + inputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, inputChannels), + outputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, outputChannels), + bufferSize = AAX_FIELD_INDEX (JUCEAlgorithmContext, bufferSize), + bypass = AAX_FIELD_INDEX (JUCEAlgorithmContext, bypass), + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + midiNodeIn = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeIn), + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + midiNodeOut = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeOut), + #endif + + pluginInstance = AAX_FIELD_INDEX (JUCEAlgorithmContext, pluginInstance), + preparedFlag = AAX_FIELD_INDEX (JUCEAlgorithmContext, isPrepared), + + meterTapBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, meterTapBuffers), + + sideChainBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, sideChainBuffers) + }; + }; + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeIn; } + #else + static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext&) noexcept { return nullptr; } + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + AAX_IMIDINode* midiNodeOut; + static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeOut; } + #else + static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext&) noexcept { return nullptr; } + #endif + + //============================================================================== + class JuceAAX_Processor; + + class JuceAAX_GUI : public AAX_CEffectGUI, + public ModifierKeyProvider + { + public: + JuceAAX_GUI() = default; + ~JuceAAX_GUI() override { DeleteViewContainer(); } + + static AAX_IEffectGUI* AAX_CALLBACK Create() { return new JuceAAX_GUI(); } + + void CreateViewContents() override; + + void CreateViewContainer() override + { + CreateViewContents(); + + if (void* nativeViewToAttachTo = GetViewContainerPtr()) + { + #if JUCE_MAC + if (GetViewContainerType() == AAX_eViewContainer_Type_NSView) + #else + if (GetViewContainerType() == AAX_eViewContainer_Type_HWND) + #endif + { + component->setVisible (true); + component->addToDesktop (detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()), nativeViewToAttachTo); + + if (ModifierKeyReceiver* modReceiver = dynamic_cast (component->getPeer())) + modReceiver->setModifierKeyProvider (this); + } + } + } + + void DeleteViewContainer() override + { + if (component != nullptr) + { + JUCE_AUTORELEASEPOOL + { + if (auto* modReceiver = dynamic_cast (component->getPeer())) + modReceiver->removeModifierKeyProvider(); + + component->removeFromDesktop(); + component = nullptr; + } + } + } + + AAX_Result GetViewSize (AAX_Point* viewSize) const override + { + if (component != nullptr) + { + *viewSize = convertToHostBounds ({ (float) component->getHeight(), + (float) component->getWidth() }); + + return AAX_SUCCESS; + } + + return AAX_ERROR_NULL_OBJECT; + } + + AAX_Result ParameterUpdated (AAX_CParamID) override + { + return AAX_SUCCESS; + } + + AAX_Result SetControlHighlightInfo (AAX_CParamID paramID, AAX_CBoolean isHighlighted, AAX_EHighlightColor colour) override + { + if (component != nullptr && component->pluginEditor != nullptr) + { + auto index = getParamIndexFromID (paramID); + + if (index >= 0) + { + AudioProcessorEditor::ParameterControlHighlightInfo info; + info.parameterIndex = index; + info.isHighlighted = (isHighlighted != 0); + info.suggestedColour = getColourFromHighlightEnum (colour); + + component->pluginEditor->setControlHighlight (info); + } + + return AAX_SUCCESS; + } + + return AAX_ERROR_NULL_OBJECT; + } + + int getWin32Modifiers() const override + { + int modifierFlags = 0; + + if (auto* viewContainer = GetViewContainer()) + { + uint32 aaxViewMods = 0; + const_cast (viewContainer)->GetModifiers (&aaxViewMods); + + if ((aaxViewMods & AAX_eModifiers_Shift) != 0) modifierFlags |= ModifierKeys::shiftModifier; + if ((aaxViewMods & AAX_eModifiers_Alt ) != 0) modifierFlags |= ModifierKeys::altModifier; + } + + return modifierFlags; + } + + private: + //============================================================================== + int getParamIndexFromID (AAX_CParamID paramID) const noexcept; + AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept; + + //============================================================================== + static AAX_Point convertToHostBounds (AAX_Point pluginSize) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return pluginSize; + + return { pluginSize.vert * desktopScale, + pluginSize.horz * desktopScale }; + } + + //============================================================================== + struct ContentWrapperComponent : public Component + { + ContentWrapperComponent (JuceAAX_GUI& gui, AudioProcessor& plugin) + : owner (gui) + { + setOpaque (true); + setBroughtToFrontOnMouseClick (true); + + pluginEditor.reset (plugin.createEditorIfNeeded()); + addAndMakeVisible (pluginEditor.get()); + + if (pluginEditor != nullptr) + { + lastValidSize = pluginEditor->getLocalBounds(); + setBounds (lastValidSize); + pluginEditor->addMouseListener (this, true); + } + } + + ~ContentWrapperComponent() override + { + if (pluginEditor != nullptr) + { + PopupMenu::dismissAllActiveMenus(); + pluginEditor->removeMouseListener (this); + pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); + } + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + template + void callMouseMethod (const MouseEvent& e, MethodType method) + { + if (auto* vc = owner.GetViewContainer()) + { + auto parameterIndex = pluginEditor->getControlParameterIndex (*e.eventComponent); + + if (auto aaxParamID = owner.getAAXParamIDFromJuceIndex (parameterIndex)) + { + uint32_t mods = 0; + vc->GetModifiers (&mods); + + (vc->*method) (aaxParamID, mods); + } + } + } + + void mouseDown (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDown); } + void mouseUp (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseUp); } + void mouseDrag (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDrag); } + + void parentSizeChanged() override + { + resizeHostWindow(); + + if (pluginEditor != nullptr) + pluginEditor->repaint(); + } + + void childBoundsChanged (Component*) override + { + if (resizeHostWindow()) + { + setSize (pluginEditor->getWidth(), pluginEditor->getHeight()); + lastValidSize = getBounds(); + } + else + { + pluginEditor->setBoundsConstrained (pluginEditor->getBounds().withSize (lastValidSize.getWidth(), + lastValidSize.getHeight())); + } + } + + bool resizeHostWindow() + { + if (pluginEditor != nullptr) + { + auto newSize = convertToHostBounds ({ (float) pluginEditor->getHeight(), + (float) pluginEditor->getWidth() }); + + return owner.GetViewContainer()->SetViewSize (newSize) == AAX_SUCCESS; + } + + return false; + } + + std::unique_ptr pluginEditor; + JuceAAX_GUI& owner; + + #if JUCE_WINDOWS + detail::WindowsHooks hooks; + #endif + juce::Rectangle lastValidSize; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) + }; + + std::unique_ptr component; + ScopedJuceInitialiser_GUI libraryInitialiser; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAAX_GUI) + }; + + // Copied here, because not all versions of the AAX SDK define all of these values + enum JUCE_AAX_EFrameRate : std::underlying_type_t + { + JUCE_AAX_eFrameRate_Undeclared = 0, + JUCE_AAX_eFrameRate_24Frame = 1, + JUCE_AAX_eFrameRate_25Frame = 2, + JUCE_AAX_eFrameRate_2997NonDrop = 3, + JUCE_AAX_eFrameRate_2997DropFrame = 4, + JUCE_AAX_eFrameRate_30NonDrop = 5, + JUCE_AAX_eFrameRate_30DropFrame = 6, + JUCE_AAX_eFrameRate_23976 = 7, + JUCE_AAX_eFrameRate_47952 = 8, + JUCE_AAX_eFrameRate_48Frame = 9, + JUCE_AAX_eFrameRate_50Frame = 10, + JUCE_AAX_eFrameRate_5994NonDrop = 11, + JUCE_AAX_eFrameRate_5994DropFrame = 12, + JUCE_AAX_eFrameRate_60NonDrop = 13, + JUCE_AAX_eFrameRate_60DropFrame = 14, + JUCE_AAX_eFrameRate_100Frame = 15, + JUCE_AAX_eFrameRate_11988NonDrop = 16, + JUCE_AAX_eFrameRate_11988DropFrame = 17, + JUCE_AAX_eFrameRate_120NonDrop = 18, + JUCE_AAX_eFrameRate_120DropFrame = 19 + }; + + static void AAX_CALLBACK algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); + + static Array activeProcessors; + + //============================================================================== + class JuceAAX_Processor : public AAX_CEffectParameters, + public juce::AudioPlayHead, + public AudioProcessorListener, + private AsyncUpdater + { + public: + JuceAAX_Processor() + : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_AAX)) + { + inParameterChangedCallback = false; + + pluginInstance->setPlayHead (this); + pluginInstance->addListener (this); + + rebuildChannelMapArrays(); + + AAX_CEffectParameters::GetNumberOfChunks (&juceChunkIndex); + activeProcessors.add (this); + } + + ~JuceAAX_Processor() override + { + activeProcessors.removeAllInstancesOf (this); + } + + static AAX_CEffectParameters* AAX_CALLBACK Create() + { + if (PluginHostType::jucePlugInIsRunningInAudioSuiteFn == nullptr) + { + PluginHostType::jucePlugInIsRunningInAudioSuiteFn = [] (AudioProcessor& processor) + { + for (auto* p : activeProcessors) + if (&p->getPluginInstance() == &processor) + return p->isInAudioSuite(); + + return false; + }; + } + + return new JuceAAX_Processor(); + } + + AAX_Result Uninitialize() override + { + cancelPendingUpdate(); + juceParameters.clear(); + + if (isPrepared && pluginInstance != nullptr) + { + isPrepared = false; + processingSidechainChange = false; + + pluginInstance->releaseResources(); + } + + return AAX_CEffectParameters::Uninitialize(); + } + + AAX_Result EffectInit() override + { + cancelPendingUpdate(); + check (Controller()->GetSampleRate (&sampleRate)); + processingSidechainChange = false; + auto err = preparePlugin(); + + if (err != AAX_SUCCESS) + return err; + + addAudioProcessorParameters(); + + return AAX_SUCCESS; + } + + AAX_Result GetNumberOfChunks (int32_t* numChunks) const override + { + // The juceChunk is the last chunk. + *numChunks = juceChunkIndex + 1; + return AAX_SUCCESS; + } + + AAX_Result GetChunkIDFromIndex (int32_t index, AAX_CTypeID* chunkID) const override + { + if (index != juceChunkIndex) + return AAX_CEffectParameters::GetChunkIDFromIndex (index, chunkID); + + *chunkID = juceChunkType; + return AAX_SUCCESS; + } + + AAX_Result GetChunkSize (AAX_CTypeID chunkID, uint32_t* oSize) const override + { + if (chunkID != juceChunkType) + return AAX_CEffectParameters::GetChunkSize (chunkID, oSize); + + auto& chunkMemoryBlock = perThreadFilterData.get(); + + chunkMemoryBlock.data.reset(); + pluginInstance->getStateInformation (chunkMemoryBlock.data); + chunkMemoryBlock.isValid = true; + + *oSize = (uint32_t) chunkMemoryBlock.data.getSize(); + return AAX_SUCCESS; + } + + AAX_Result GetChunk (AAX_CTypeID chunkID, AAX_SPlugInChunk* oChunk) const override + { + if (chunkID != juceChunkType) + return AAX_CEffectParameters::GetChunk (chunkID, oChunk); + + auto& chunkMemoryBlock = perThreadFilterData.get(); + + if (! chunkMemoryBlock.isValid) + return 20700; // AAX_ERROR_PLUGIN_API_INVALID_THREAD + + oChunk->fSize = (int32_t) chunkMemoryBlock.data.getSize(); + chunkMemoryBlock.data.copyTo (oChunk->fData, 0, chunkMemoryBlock.data.getSize()); + chunkMemoryBlock.isValid = false; + + return AAX_SUCCESS; + } + + AAX_Result SetChunk (AAX_CTypeID chunkID, const AAX_SPlugInChunk* chunk) override + { + if (chunkID != juceChunkType) + return AAX_CEffectParameters::SetChunk (chunkID, chunk); + + pluginInstance->setStateInformation ((void*) chunk->fData, chunk->fSize); + + // Notify Pro Tools that the parameters were updated. + // Without it a bug happens in these circumstances: + // * A preset is saved with the RTAS version of the plugin (".tfx" preset format). + // * The preset is loaded in PT 10 using the AAX version. + // * The session is then saved, and closed. + // * The saved session is loaded, but acting as if the preset was never loaded. + // IMPORTANT! If the plugin doesn't manage its own bypass parameter, don't try + // to overwrite the bypass parameter value. + auto numParameters = juceParameters.getNumParameters(); + + for (int i = 0; i < numParameters; ++i) + if (auto* juceParam = juceParameters.getParamForIndex (i)) + if (juceParam != ownedBypassParameter.get()) + if (auto paramID = getAAXParamIDFromJuceIndex (i)) + SetParameterNormalizedValue (paramID, juceParam->getValue()); + + return AAX_SUCCESS; + } + + AAX_Result ResetFieldData (AAX_CFieldIndex fieldIndex, void* data, uint32_t dataSize) const override + { + switch (fieldIndex) + { + case JUCEAlgorithmIDs::pluginInstance: + { + auto numObjects = dataSize / sizeof (PluginInstanceInfo); + auto* objects = static_cast (data); + + jassert (numObjects == 1); // not sure how to handle more than one.. + + for (size_t i = 0; i < numObjects; ++i) + new (objects + i) PluginInstanceInfo (const_cast (*this)); + + break; + } + + case JUCEAlgorithmIDs::preparedFlag: + { + const_cast (this)->preparePlugin(); + + auto numObjects = dataSize / sizeof (uint32_t); + auto* objects = static_cast (data); + + for (size_t i = 0; i < numObjects; ++i) + objects[i] = 1; + + break; + } + + case JUCEAlgorithmIDs::meterTapBuffers: + { + // this is a dummy field only when there are no aaxMeters + jassert (aaxMeters.size() == 0); + + { + auto numObjects = dataSize / sizeof (float*); + auto* objects = static_cast (data); + + for (size_t i = 0; i < numObjects; ++i) + objects[i] = nullptr; + } + break; + } + } + + return AAX_SUCCESS; + } + + void setAudioProcessorParameter (AAX_CParamID paramID, double value) + { + if (auto* param = getParameterFromID (paramID)) + { + auto newValue = static_cast (value); + + if (newValue != param->getValue()) + { + param->setValue (newValue); + + inParameterChangedCallback = true; + param->sendValueChangedMessageToListeners (newValue); + } + } + } + + AAX_Result GetNumberOfChanges (int32_t* numChanges) const override + { + const auto result = AAX_CEffectParameters::GetNumberOfChanges (numChanges); + *numChanges += numSetDirtyCalls; + return result; + } + + AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override + { + auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); + setAudioProcessorParameter (paramID, value); + + return result; + } + + AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override + { + if (auto* param = getParameterFromID (paramID)) + { + if (! LegacyAudioParameter::isLegacy (param)) + { + *result = param->getValueForText (text.Get()); + return AAX_SUCCESS; + } + } + + return AAX_CEffectParameters::GetParameterValueFromString (paramID, result, text); + } + + AAX_Result GetParameterStringFromValue (AAX_CParamID paramID, double value, AAX_IString* result, int32_t maxLen) const override + { + if (auto* param = getParameterFromID (paramID)) + result->Set (param->getText ((float) value, maxLen).toRawUTF8()); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterNumberofSteps (AAX_CParamID paramID, int32_t* result) const + { + if (auto* param = getParameterFromID (paramID)) + *result = getSafeNumberOfParameterSteps (*param); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override + { + if (auto* param = getParameterFromID (paramID)) + *result = (double) param->getValue(); + else + *result = 0.0; + + return AAX_SUCCESS; + } + + AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override + { + if (auto* p = mParameterManager.GetParameterByID (paramID)) + p->SetValueWithFloat ((float) newValue); + + setAudioProcessorParameter (paramID, (float) newValue); + + return AAX_SUCCESS; + } + + AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override + { + if (auto* param = getParameterFromID (paramID)) + { + auto newValue = param->getValue() + (float) newDeltaValue; + + setAudioProcessorParameter (paramID, jlimit (0.0f, 1.0f, newValue)); + + if (auto* p = mParameterManager.GetParameterByID (paramID)) + p->SetValueWithFloat (newValue); + } + + return AAX_SUCCESS; + } + + AAX_Result GetParameterNameOfLength (AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override + { + if (auto* param = getParameterFromID (paramID)) + result->Set (param->getName (maxLen).toRawUTF8()); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override + { + if (auto* param = getParameterFromID (paramID)) + result->Set (param->getName (31).toRawUTF8()); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override + { + if (auto* param = getParameterFromID (paramID)) + *result = (double) param->getDefaultValue(); + else + *result = 0.0; + + jassert (*result >= 0 && *result <= 1.0f); + + return AAX_SUCCESS; + } + + AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } + + Optional getPosition() const override + { + PositionInfo info; + + const AAX_ITransport& transport = *Transport(); + + info.setBpm ([&] + { + double bpm = 0.0; + + return transport.GetCurrentTempo (&bpm) == AAX_SUCCESS ? makeOptional (bpm) : nullopt; + }()); + + const auto signature = [&] + { + int32_t num = 4, den = 4; + + return transport.GetCurrentMeter (&num, &den) == AAX_SUCCESS + ? makeOptional (TimeSignature { (int) num, (int) den }) + : nullopt; + }(); + + info.setTimeSignature (signature); + + info.setIsPlaying ([&] + { + bool isPlaying = false; + + return transport.IsTransportPlaying (&isPlaying) == AAX_SUCCESS && isPlaying; + }()); + + info.setIsRecording (recordingState.get().orFallback (false)); + + const auto optionalTimeInSamples = [&info, &transport] + { + int64_t timeInSamples = 0; + return ((! info.getIsPlaying() && transport.GetTimelineSelectionStartPosition (&timeInSamples) == AAX_SUCCESS) + || transport.GetCurrentNativeSampleLocation (&timeInSamples) == AAX_SUCCESS) + ? makeOptional (timeInSamples) + : nullopt; + }(); + + info.setTimeInSamples (optionalTimeInSamples); + info.setTimeInSeconds ((float) optionalTimeInSamples.orFallback (0) / sampleRate); + + const auto tickPosition = [&] + { + int64_t ticks = 0; + + return ((info.getIsPlaying() && transport.GetCustomTickPosition (&ticks, optionalTimeInSamples.orFallback (0))) == AAX_SUCCESS) + || transport.GetCurrentTickPosition (&ticks) == AAX_SUCCESS + ? makeOptional (ticks) + : nullopt; + }(); + + info.setPpqPosition (tickPosition.hasValue() ? makeOptional (static_cast (*tickPosition) / 960'000.0) : nullopt); + + bool isLooping = false; + int64_t loopStartTick = 0, loopEndTick = 0; + + if (transport.GetCurrentLoopPosition (&isLooping, &loopStartTick, &loopEndTick) == AAX_SUCCESS) + { + info.setIsLooping (isLooping); + info.setLoopPoints (LoopPoints { (double) loopStartTick / 960000.0, (double) loopEndTick / 960000.0 }); + } + + struct RateAndOffset + { + AAX_EFrameRate frameRate{}; + int64_t offset{}; + }; + + const auto timeCodeIfAvailable = [&]() -> std::optional + { + RateAndOffset result{}; + + if (transport.GetHDTimeCodeInfo (&result.frameRate, &result.offset) == AAX_SUCCESS) + return result; + + int32_t offset32{}; + + if (transport.GetTimeCodeInfo (&result.frameRate, &offset32) == AAX_SUCCESS) + { + result.offset = offset32; + return result; + } + + return {}; + }(); + + if (timeCodeIfAvailable.has_value()) + { + info.setFrameRate ([&]() -> Optional + { + switch ((JUCE_AAX_EFrameRate) timeCodeIfAvailable->frameRate) + { + case JUCE_AAX_eFrameRate_24Frame: return FrameRate().withBaseRate (24); + case JUCE_AAX_eFrameRate_23976: return FrameRate().withBaseRate (24).withPullDown(); + + case JUCE_AAX_eFrameRate_25Frame: return FrameRate().withBaseRate (25); + + case JUCE_AAX_eFrameRate_30NonDrop: return FrameRate().withBaseRate (30); + case JUCE_AAX_eFrameRate_30DropFrame: return FrameRate().withBaseRate (30).withDrop(); + case JUCE_AAX_eFrameRate_2997NonDrop: return FrameRate().withBaseRate (30).withPullDown(); + case JUCE_AAX_eFrameRate_2997DropFrame: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + + case JUCE_AAX_eFrameRate_48Frame: return FrameRate().withBaseRate (48); + case JUCE_AAX_eFrameRate_47952: return FrameRate().withBaseRate (48).withPullDown(); + + case JUCE_AAX_eFrameRate_50Frame: return FrameRate().withBaseRate (50); + + case JUCE_AAX_eFrameRate_60NonDrop: return FrameRate().withBaseRate (60); + case JUCE_AAX_eFrameRate_60DropFrame: return FrameRate().withBaseRate (60).withDrop(); + case JUCE_AAX_eFrameRate_5994NonDrop: return FrameRate().withBaseRate (60).withPullDown(); + case JUCE_AAX_eFrameRate_5994DropFrame: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + + case JUCE_AAX_eFrameRate_100Frame: return FrameRate().withBaseRate (100); + + case JUCE_AAX_eFrameRate_120NonDrop: return FrameRate().withBaseRate (120); + case JUCE_AAX_eFrameRate_120DropFrame: return FrameRate().withBaseRate (120).withDrop(); + case JUCE_AAX_eFrameRate_11988NonDrop: return FrameRate().withBaseRate (120).withPullDown(); + case JUCE_AAX_eFrameRate_11988DropFrame: return FrameRate().withBaseRate (120).withPullDown().withDrop(); + + case JUCE_AAX_eFrameRate_Undeclared: break; + } + + return {}; + }()); + } + + const auto offset = timeCodeIfAvailable.has_value() ? timeCodeIfAvailable->offset : int64_t{}; + const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; + info.setEditOriginTime (makeOptional (effectiveRate != 0.0 ? offset / effectiveRate : offset)); + + { + int32_t bars{}, beats{}; + int64_t displayTicks{}; + + if (optionalTimeInSamples.hasValue() + && transport.GetBarBeatPosition (&bars, &beats, &displayTicks, *optionalTimeInSamples) == AAX_SUCCESS) + { + info.setBarCount (bars); + + if (signature.hasValue()) + { + const auto ticksSinceBar = static_cast (((beats - 1) * 4 * 960'000) / signature->denominator) + displayTicks; + + if (tickPosition.hasValue() && ticksSinceBar <= tickPosition) + { + const auto barStartInTicks = static_cast (*tickPosition - ticksSinceBar); + info.setPpqPositionOfLastBarStart (barStartInTicks / 960'000.0); + } + } + } + } + + return info; + } + + void audioProcessorParameterChanged (AudioProcessor* /*processor*/, int parameterIndex, float newValue) override + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) + SetParameterNormalizedValue (paramID, (double) newValue); + } + + void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override + { + ++mNumPlugInChanges; + + if (details.parameterInfoChanged) + { + for (const auto* param : juceParameters) + if (auto* aaxParam = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (param->getParameterIndex()))) + syncParameterAttributes (aaxParam, param); + } + + if (details.latencyChanged) + check (Controller()->SetSignalLatency (processor->getLatencySamples())); + + if (details.nonParameterStateChanged) + ++numSetDirtyCalls; + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override + { + if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) + TouchParameter (paramID); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override + { + if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) + ReleaseParameter (paramID); + } + + AAX_Result NotificationReceived (AAX_CTypeID type, const void* data, uint32_t size) override + { + switch (type) + { + case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; + case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); break; + + case AAX_eNotificationEvent_ASProcessingState: + { + if (data != nullptr && size == sizeof (AAX_EProcessingState)) + { + const auto state = *static_cast (data); + const auto nonRealtime = state == AAX_eProcessingState_StartPass + || state == AAX_eProcessingState_BeginPassGroup; + pluginInstance->setNonRealtime (nonRealtime); + } + + break; + } + + case AAX_eNotificationEvent_TrackNameChanged: + if (data != nullptr) + { + AudioProcessor::TrackProperties props; + props.name = String::fromUTF8 (static_cast (data)->Get()); + + pluginInstance->updateTrackProperties (props); + } + break; + + case AAX_eNotificationEvent_SideChainBeingConnected: + case AAX_eNotificationEvent_SideChainBeingDisconnected: + { + processingSidechainChange = true; + sidechainDesired = (type == AAX_eNotificationEvent_SideChainBeingConnected); + updateSidechainState(); + break; + } + + case AAX_eNotificationEvent_TransportStateChanged: + if (data != nullptr) + { + const auto& info = *static_cast (data); + recordingState.set (info.mIsRecording); + } + break; + } + + return AAX_CEffectParameters::NotificationReceived (type, data, size); + } + + const float* getAudioBufferForInput (const float* const* inputs, int sidechain, int mainNumIns, int idx) const noexcept + { + jassert (idx < (mainNumIns + 1)); + + if (idx < mainNumIns) + return inputs[inputLayoutMap[idx]]; + + return (sidechain != -1 ? inputs[sidechain] : sideChainBuffer.data()); + } + + void process (const float* const* inputs, float* const* outputs, const int sideChainBufferIdx, + const int bufferSize, const bool bypass, + AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut, + float* const meterBuffers) + { + auto numIns = pluginInstance->getTotalNumInputChannels(); + auto numOuts = pluginInstance->getTotalNumOutputChannels(); + auto numMeters = aaxMeters.size(); + + const ScopedLock sl (pluginInstance->getCallbackLock()); + + bool isSuspended = [this, sideChainBufferIdx] + { + if (processingSidechainChange) + return true; + + bool processWantsSidechain = (sideChainBufferIdx != -1); + + if (hasSidechain && canDisableSidechain && (sidechainDesired != processWantsSidechain)) + { + sidechainDesired = processWantsSidechain; + processingSidechainChange = true; + triggerAsyncUpdate(); + return true; + } + + return pluginInstance->isSuspended(); + }(); + + if (isSuspended) + { + for (int i = 0; i < numOuts; ++i) + FloatVectorOperations::clear (outputs[i], bufferSize); + + if (meterBuffers != nullptr) + FloatVectorOperations::clear (meterBuffers, numMeters); + } + else + { + auto mainNumIns = pluginInstance->getMainBusNumInputChannels(); + auto sidechain = (pluginInstance->getChannelCountOfBus (true, 1) > 0 ? sideChainBufferIdx : -1); + auto numChans = jmax (numIns, numOuts); + + if (numChans == 0) + return; + + if (channelList.size() <= numChans) + channelList.insertMultiple (-1, nullptr, 1 + numChans - channelList.size()); + + float** channels = channelList.getRawDataPointer(); + + if (numOuts >= numIns) + { + for (int i = 0; i < numOuts; ++i) + channels[i] = outputs[outputLayoutMap[i]]; + + for (int i = 0; i < numIns; ++i) + memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); + + for (int i = numIns; i < numOuts; ++i) + zeromem (channels[i], (size_t) bufferSize * sizeof (float)); + + process (channels, numOuts, bufferSize, bypass, midiNodeIn, midiNodesOut); + } + else + { + for (int i = 0; i < numOuts; ++i) + channels[i] = outputs[outputLayoutMap[i]]; + + for (int i = 0; i < numOuts; ++i) + memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); + + for (int i = numOuts; i < numIns; ++i) + channels[i] = const_cast (getAudioBufferForInput (inputs, sidechain, mainNumIns, i)); + + process (channels, numIns, bufferSize, bypass, midiNodeIn, midiNodesOut); + } + + if (meterBuffers != nullptr) + for (int i = 0; i < numMeters; ++i) + meterBuffers[i] = aaxMeters[i]->getValue(); + } + } + + //============================================================================== + // In aax, the format of the aux and sidechain buses need to be fully determined + // by the format on the main buses. This function tried to provide such a mapping. + // Returns false if the in/out main layout is not supported + static bool fullBusesLayoutFromMainLayout (const AudioProcessor& p, + const AudioChannelSet& mainInput, const AudioChannelSet& mainOutput, + AudioProcessor::BusesLayout& fullLayout) + { + auto currentLayout = getDefaultLayout (p, true); + bool success = p.checkBusesLayoutSupported (currentLayout); + jassertquiet (success); + + auto numInputBuses = p.getBusCount (true); + auto numOutputBuses = p.getBusCount (false); + + if (auto* bus = p.getBus (true, 0)) + if (! bus->isLayoutSupported (mainInput, ¤tLayout)) + return false; + + if (auto* bus = p.getBus (false, 0)) + if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) + return false; + + // did this change the input again + if (numInputBuses > 0 && currentLayout.inputBuses.getReference (0) != mainInput) + return false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + + if (! AudioProcessor::containsLayout (currentLayout, configs)) + return false; + #endif + + bool foundValid = false; + { + auto onlyMains = currentLayout; + + for (int i = 1; i < numInputBuses; ++i) + onlyMains.inputBuses.getReference (i) = AudioChannelSet::disabled(); + + for (int i = 1; i < numOutputBuses; ++i) + onlyMains.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (p.checkBusesLayoutSupported (onlyMains)) + { + foundValid = true; + fullLayout = onlyMains; + } + } + + if (numInputBuses > 1) + { + // can the first bus be a sidechain or disabled, if not then we can't use this layout combination + if (auto* bus = p.getBus (true, 1)) + if (! bus->isLayoutSupported (AudioChannelSet::mono(), ¤tLayout) && ! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) + return foundValid; + + // can all the other inputs be disabled, if not then we can't use this layout combination + for (int i = 2; i < numInputBuses; ++i) + if (auto* bus = p.getBus (true, i)) + if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (true, 0)) + if (! bus->isLayoutSupported (mainInput, ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (false, 0)) + if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) + return foundValid; + + // recheck if the format is correct + if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) + || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) + return foundValid; + + auto& sidechainBus = currentLayout.inputBuses.getReference (1); + + if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) + return foundValid; + + for (int i = 2; i < numInputBuses; ++i) + if (! currentLayout.inputBuses.getReference (i).isDisabled()) + return foundValid; + } + + const bool hasSidechain = (numInputBuses > 1 && currentLayout.inputBuses.getReference (1) == AudioChannelSet::mono()); + + if (hasSidechain) + { + auto onlyMainsAndSidechain = currentLayout; + + for (int i = 1; i < numOutputBuses; ++i) + onlyMainsAndSidechain.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (p.checkBusesLayoutSupported (onlyMainsAndSidechain)) + { + foundValid = true; + fullLayout = onlyMainsAndSidechain; + } + } + + if (numOutputBuses > 1) + { + auto copy = currentLayout; + int maxAuxBuses = jmin (16, numOutputBuses); + + for (int i = 1; i < maxAuxBuses; ++i) + copy.outputBuses.getReference (i) = mainOutput; + + for (int i = maxAuxBuses; i < numOutputBuses; ++i) + copy.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (p.checkBusesLayoutSupported (copy)) + { + fullLayout = copy; + foundValid = true; + } + else + { + for (int i = 1; i < maxAuxBuses; ++i) + if (currentLayout.outputBuses.getReference (i).isDisabled()) + return foundValid; + + for (int i = maxAuxBuses; i < numOutputBuses; ++i) + if (auto* bus = p.getBus (false, i)) + if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (true, 0)) + if (! bus->isLayoutSupported (mainInput, ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (false, 0)) + if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) + return foundValid; + + if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) + || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) + return foundValid; + + if (numInputBuses > 1) + { + auto& sidechainBus = currentLayout.inputBuses.getReference (1); + + if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) + return foundValid; + } + + for (int i = maxAuxBuses; i < numOutputBuses; ++i) + if (! currentLayout.outputBuses.getReference (i).isDisabled()) + return foundValid; + + fullLayout = currentLayout; + foundValid = true; + } + } + + return foundValid; + } + + bool isInAudioSuite() + { + AAX_CBoolean res; + Controller()->GetIsAudioSuite (&res); + + return res > 0; + } + + private: + friend class JuceAAX_GUI; + friend void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); + + void process (float* const* channels, const int numChans, const int bufferSize, + const bool bypass, [[maybe_unused]] AAX_IMIDINode* midiNodeIn, [[maybe_unused]] AAX_IMIDINode* midiNodesOut) + { + AudioBuffer buffer (channels, numChans, bufferSize); + midiBuffer.clear(); + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + { + auto* midiStream = midiNodeIn->GetNodeBuffer(); + auto numMidiEvents = midiStream->mBufferSize; + + for (uint32_t i = 0; i < numMidiEvents; ++i) + { + auto& m = midiStream->mBuffer[i]; + jassert ((int) m.mTimestamp < bufferSize); + + midiBuffer.addEvent (m.mData, (int) m.mLength, + jlimit (0, (int) bufferSize - 1, (int) m.mTimestamp)); + } + } + #endif + + { + if (lastBufferSize != bufferSize) + { + lastBufferSize = bufferSize; + pluginInstance->setRateAndBufferSizeDetails (sampleRate, lastBufferSize); + + // we only call prepareToPlay here if the new buffer size is larger than + // the one used last time prepareToPlay was called. + // currently, this should never actually happen, because as of Pro Tools 12, + // the maximum possible value is 1024, and we call prepareToPlay with that + // value during initialisation. + if (bufferSize > maxBufferSize) + prepareProcessorWithSampleRateAndBufferSize (sampleRate, bufferSize); + } + + if (bypass && pluginInstance->getBypassParameter() == nullptr) + pluginInstance->processBlockBypassed (buffer, midiBuffer); + else + pluginInstance->processBlock (buffer, midiBuffer); + } + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + { + AAX_CMidiPacket packet; + packet.mIsImmediate = false; + + for (const auto metadata : midiBuffer) + { + jassert (isPositiveAndBelow (metadata.samplePosition, bufferSize)); + + if (metadata.numBytes <= 4) + { + packet.mTimestamp = (uint32_t) metadata.samplePosition; + packet.mLength = (uint32_t) metadata.numBytes; + memcpy (packet.mData, metadata.data, (size_t) metadata.numBytes); + + check (midiNodesOut->PostMIDIPacket (&packet)); + } + } + } + #endif + } + + bool isBypassPartOfRegularParemeters() const + { + auto& audioProcessor = getPluginInstance(); + + int n = juceParameters.getNumParameters(); + + if (auto* bypassParam = audioProcessor.getBypassParameter()) + for (int i = 0; i < n; ++i) + if (juceParameters.getParamForIndex (i) == bypassParam) + return true; + + return false; + } + + // Some older Pro Tools control surfaces (EUCON [PT version 12.4] and + // Avid S6 before version 2.1) cannot cope with a large number of + // parameter steps. + static int32_t getSafeNumberOfParameterSteps (const AudioProcessorParameter& param) + { + return jmin (param.getNumSteps(), 2048); + } + + void addAudioProcessorParameters() + { + auto& audioProcessor = getPluginInstance(); + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + const bool forceLegacyParamIDs = true; + #else + const bool forceLegacyParamIDs = false; + #endif + + auto bypassPartOfRegularParams = isBypassPartOfRegularParemeters(); + + juceParameters.update (audioProcessor, forceLegacyParamIDs); + + auto* bypassParameter = pluginInstance->getBypassParameter(); + + if (bypassParameter == nullptr) + { + ownedBypassParameter.reset (new AudioParameterBool (cDefaultMasterBypassID, "Master Bypass", false)); + bypassParameter = ownedBypassParameter.get(); + } + + if (! bypassPartOfRegularParams) + juceParameters.addNonOwning (bypassParameter); + + int parameterIndex = 0; + + for (auto* juceParam : juceParameters) + { + auto isBypassParameter = (juceParam == bypassParameter); + + auto category = juceParam->getCategory(); + auto paramID = isBypassParameter ? String (cDefaultMasterBypassID) + : juceParameters.getParamID (audioProcessor, parameterIndex); + + aaxParamIDs.add (paramID); + auto* aaxParamID = aaxParamIDs.getReference (parameterIndex++).toRawUTF8(); + + paramMap.set (AAXClasses::getAAXParamHash (aaxParamID), juceParam); + + // is this a meter? + if (((category & 0xffff0000) >> 16) == 2) + { + aaxMeters.add (juceParam); + continue; + } + + auto parameter = new AAX_CParameter (aaxParamID, + AAX_CString (juceParam->getName (31).toRawUTF8()), + juceParam->getDefaultValue(), + AAX_CLinearTaperDelegate(), + AAX_CNumberDisplayDelegate(), + juceParam->isAutomatable()); + + parameter->AddShortenedName (juceParam->getName (4).toRawUTF8()); + + auto parameterNumSteps = getSafeNumberOfParameterSteps (*juceParam); + parameter->SetNumberOfSteps ((uint32_t) parameterNumSteps); + + #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous + : AAX_eParameterType_Discrete); + #else + parameter->SetType (juceParam->isDiscrete() ? AAX_eParameterType_Discrete + : AAX_eParameterType_Continuous); + #endif + + parameter->SetOrientation (juceParam->isOrientationInverted() + ? (AAX_eParameterOrientation_RightMinLeftMax | AAX_eParameterOrientation_TopMinBottomMax + | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) + : (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax + | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax)); + + mParameterManager.AddParameter (parameter); + + if (isBypassParameter) + mPacketDispatcher.RegisterPacket (aaxParamID, JUCEAlgorithmIDs::bypass); + } + } + + bool getMainBusFormats (AudioChannelSet& inputSet, AudioChannelSet& outputSet) + { + auto& audioProcessor = getPluginInstance(); + + #if JucePlugin_IsMidiEffect + // MIDI effect plug-ins do not support any audio channels + jassert (audioProcessor.getTotalNumInputChannels() == 0 + && audioProcessor.getTotalNumOutputChannels() == 0); + + inputSet = outputSet = AudioChannelSet(); + return true; + #else + auto inputBuses = audioProcessor.getBusCount (true); + auto outputBuses = audioProcessor.getBusCount (false); + + AAX_EStemFormat inputStemFormat = AAX_eStemFormat_None; + check (Controller()->GetInputStemFormat (&inputStemFormat)); + + AAX_EStemFormat outputStemFormat = AAX_eStemFormat_None; + check (Controller()->GetOutputStemFormat (&outputStemFormat)); + + #if JucePlugin_IsSynth + if (inputBuses == 0) + inputStemFormat = AAX_eStemFormat_None; + #endif + + inputSet = (inputBuses > 0 ? channelSetFromStemFormat (inputStemFormat, false) : AudioChannelSet()); + outputSet = (outputBuses > 0 ? channelSetFromStemFormat (outputStemFormat, false) : AudioChannelSet()); + + if ((inputSet == AudioChannelSet::disabled() && inputStemFormat != AAX_eStemFormat_None) || (outputSet == AudioChannelSet::disabled() && outputStemFormat != AAX_eStemFormat_None) + || (inputSet != AudioChannelSet::disabled() && inputBuses == 0) || (outputSet != AudioChannelSet::disabled() && outputBuses == 0)) + return false; + + return true; + #endif + } + + AAX_Result preparePlugin() + { + auto& audioProcessor = getPluginInstance(); + auto oldLayout = audioProcessor.getBusesLayout(); + AudioChannelSet inputSet, outputSet; + + if (! getMainBusFormats (inputSet, outputSet)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + AudioProcessor::BusesLayout newLayout; + + if (! fullBusesLayoutFromMainLayout (audioProcessor, inputSet, outputSet, newLayout)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + hasSidechain = (newLayout.getNumChannels (true, 1) == 1); + + if (hasSidechain) + { + sidechainDesired = true; + + auto disabledSidechainLayout = newLayout; + disabledSidechainLayout.inputBuses.getReference (1) = AudioChannelSet::disabled(); + + canDisableSidechain = audioProcessor.checkBusesLayoutSupported (disabledSidechainLayout); + + if (canDisableSidechain && ! lastSideChainState) + { + sidechainDesired = false; + newLayout = disabledSidechainLayout; + } + } + + if (isInAudioSuite()) + { + // AudioSuite doesn't support multiple output buses + for (int i = 1; i < newLayout.outputBuses.size(); ++i) + newLayout.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (! audioProcessor.checkBusesLayoutSupported (newLayout)) + { + // your plug-in needs to support a single output bus if running in AudioSuite + jassertfalse; + + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + } + + const bool layoutChanged = (oldLayout != newLayout); + + if (layoutChanged) + { + if (! audioProcessor.setBusesLayout (newLayout)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + rebuildChannelMapArrays(); + } + + if (layoutChanged || (! isPrepared)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + prepareProcessorWithSampleRateAndBufferSize (sampleRate, lastBufferSize); + + midiBuffer.ensureSize (2048); + midiBuffer.clear(); + } + + check (Controller()->SetSignalLatency (audioProcessor.getLatencySamples())); + isPrepared = true; + + return AAX_SUCCESS; + } + + void rebuildChannelMapArrays() + { + auto& audioProcessor = getPluginInstance(); + + for (int dir = 0; dir < 2; ++dir) + { + bool isInput = (dir == 0); + auto& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; + layoutMap.clear(); + + auto numBuses = audioProcessor.getBusCount (isInput); + int chOffset = 0; + + for (int busIdx = 0; busIdx < numBuses; ++busIdx) + { + auto channelFormat = audioProcessor.getChannelLayoutOfBus (isInput, busIdx); + + if (channelFormat != AudioChannelSet::disabled()) + { + auto numChannels = channelFormat.size(); + + for (int ch = 0; ch < numChannels; ++ch) + layoutMap.add (juceChannelIndexToAax (ch, channelFormat) + chOffset); + + chOffset += numChannels; + } + } + } + } + + static void algorithmCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) + { + for (auto iter = instancesBegin; iter < instancesEnd; ++iter) + { + auto& i = **iter; + + int sideChainBufferIdx = i.pluginInstance->parameters.hasSidechain && i.sideChainBuffers != nullptr + ? static_cast (*i.sideChainBuffers) : -1; + + // sidechain index of zero is an invalid index + if (sideChainBufferIdx <= 0) + sideChainBufferIdx = -1; + + auto numMeters = i.pluginInstance->parameters.aaxMeters.size(); + float* const meterTapBuffers = (i.meterTapBuffers != nullptr && numMeters > 0 ? *i.meterTapBuffers : nullptr); + + i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, sideChainBufferIdx, + *(i.bufferSize), *(i.bypass) != 0, + getMidiNodeIn(i), getMidiNodeOut(i), + meterTapBuffers); + } + } + + void prepareProcessorWithSampleRateAndBufferSize (double sr, int bs) + { + maxBufferSize = jmax (maxBufferSize, bs); + + auto& audioProcessor = getPluginInstance(); + audioProcessor.setRateAndBufferSizeDetails (sr, maxBufferSize); + audioProcessor.prepareToPlay (sr, maxBufferSize); + sideChainBuffer.resize (static_cast (maxBufferSize)); + } + + //============================================================================== + void updateSidechainState() + { + if (! processingSidechainChange) + return; + + auto& audioProcessor = getPluginInstance(); + const auto sidechainActual = audioProcessor.getChannelCountOfBus (true, 1) > 0; + + if (hasSidechain && canDisableSidechain && sidechainDesired != sidechainActual) + { + lastSideChainState = sidechainDesired; + + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + if (auto* bus = audioProcessor.getBus (true, 1)) + bus->setCurrentLayout (lastSideChainState ? AudioChannelSet::mono() + : AudioChannelSet::disabled()); + + prepareProcessorWithSampleRateAndBufferSize (audioProcessor.getSampleRate(), maxBufferSize); + isPrepared = true; + } + + processingSidechainChange = false; + } + + void handleAsyncUpdate() override + { + updateSidechainState(); + } + + //============================================================================== + static AudioProcessor::CurveData::Type aaxCurveTypeToJUCE (AAX_CTypeID type) noexcept + { + switch (type) + { + case AAX_eCurveType_EQ: return AudioProcessor::CurveData::Type::EQ; + case AAX_eCurveType_Dynamics: return AudioProcessor::CurveData::Type::Dynamics; + case AAX_eCurveType_Reduction: return AudioProcessor::CurveData::Type::GainReduction; + default: break; + } + + return AudioProcessor::CurveData::Type::Unknown; + } + + uint32_t getAAXMeterIdForParamId (const String& paramID) const noexcept + { + int idx; + + for (idx = 0; idx < aaxMeters.size(); ++idx) + if (LegacyAudioParameter::getParamID (aaxMeters[idx], false) == paramID) + break; + + // you specified a parameter id in your curve but the parameter does not have the meter + // category + jassert (idx < aaxMeters.size()); + return 'Metr' + static_cast (idx); + } + + //============================================================================== + AAX_Result GetCurveData (AAX_CTypeID iCurveType, const float * iValues, uint32_t iNumValues, float * oValues ) const override + { + auto curveType = aaxCurveTypeToJUCE (iCurveType); + + if (curveType != AudioProcessor::CurveData::Type::Unknown) + { + auto& audioProcessor = getPluginInstance(); + auto curve = audioProcessor.getResponseCurve (curveType); + + if (curve.curve) + { + if (oValues != nullptr && iValues != nullptr) + { + for (uint32_t i = 0; i < iNumValues; ++i) + oValues[i] = curve.curve (iValues[i]); + } + + return AAX_SUCCESS; + } + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + AAX_Result GetCurveDataMeterIds (AAX_CTypeID iCurveType, uint32_t *oXMeterId, uint32_t *oYMeterId) const override + { + auto curveType = aaxCurveTypeToJUCE (iCurveType); + + if (curveType != AudioProcessor::CurveData::Type::Unknown) + { + auto& audioProcessor = getPluginInstance(); + auto curve = audioProcessor.getResponseCurve (curveType); + + if (curve.curve && curve.xMeterID.isNotEmpty() && curve.yMeterID.isNotEmpty()) + { + if (oXMeterId != nullptr) *oXMeterId = getAAXMeterIdForParamId (curve.xMeterID); + if (oYMeterId != nullptr) *oYMeterId = getAAXMeterIdForParamId (curve.yMeterID); + + return AAX_SUCCESS; + } + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + AAX_Result GetCurveDataDisplayRange (AAX_CTypeID iCurveType, float *oXMin, float *oXMax, float *oYMin, float *oYMax) const override + { + auto curveType = aaxCurveTypeToJUCE (iCurveType); + + if (curveType != AudioProcessor::CurveData::Type::Unknown) + { + auto& audioProcessor = getPluginInstance(); + auto curve = audioProcessor.getResponseCurve (curveType); + + if (curve.curve) + { + if (oXMin != nullptr) *oXMin = curve.xRange.getStart(); + if (oXMax != nullptr) *oXMax = curve.xRange.getEnd(); + if (oYMin != nullptr) *oYMin = curve.yRange.getStart(); + if (oYMax != nullptr) *oYMax = curve.yRange.getEnd(); + + return AAX_SUCCESS; + } + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + //============================================================================== + inline int getParamIndexFromID (AAX_CParamID paramID) const noexcept + { + if (auto* param = getParameterFromID (paramID)) + return LegacyAudioParameter::getParamIndex (getPluginInstance(), param); + + return -1; + } + + inline AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept + { + if (isPositiveAndBelow (index, aaxParamIDs.size())) + return aaxParamIDs.getReference (index).toRawUTF8(); + + return nullptr; + } + + AudioProcessorParameter* getParameterFromID (AAX_CParamID paramID) const noexcept + { + return paramMap [AAXClasses::getAAXParamHash (paramID)]; + } + + //============================================================================== + static AudioProcessor::BusesLayout getDefaultLayout (const AudioProcessor& p, bool enableAll) + { + AudioProcessor::BusesLayout defaultLayout; + + for (int dir = 0; dir < 2; ++dir) + { + bool isInput = (dir == 0); + auto numBuses = p.getBusCount (isInput); + auto& layouts = (isInput ? defaultLayout.inputBuses : defaultLayout.outputBuses); + + for (int i = 0; i < numBuses; ++i) + if (auto* bus = p.getBus (isInput, i)) + layouts.add (enableAll || bus->isEnabledByDefault() ? bus->getDefaultLayout() : AudioChannelSet()); + } + + return defaultLayout; + } + + static AudioProcessor::BusesLayout getDefaultLayout (AudioProcessor& p) + { + auto defaultLayout = getDefaultLayout (p, true); + + if (! p.checkBusesLayoutSupported (defaultLayout)) + defaultLayout = getDefaultLayout (p, false); + + // Your processor must support the default layout + jassert (p.checkBusesLayoutSupported (defaultLayout)); + return defaultLayout; + } + + void syncParameterAttributes (AAX_IParameter* aaxParam, const AudioProcessorParameter* juceParam) + { + if (juceParam == nullptr) + return; + + { + auto newName = juceParam->getName (31); + + if (aaxParam->Name() != newName.toRawUTF8()) + aaxParam->SetName (AAX_CString (newName.toRawUTF8())); + } + + { + auto newType = juceParam->isDiscrete() ? AAX_eParameterType_Discrete : AAX_eParameterType_Continuous; + + if (aaxParam->GetType() != newType) + aaxParam->SetType (newType); + } + + { + auto newNumSteps = static_cast (juceParam->getNumSteps()); + + if (aaxParam->GetNumberOfSteps() != newNumSteps) + aaxParam->SetNumberOfSteps (newNumSteps); + } + + { + auto defaultValue = juceParam->getDefaultValue(); + + if (! approximatelyEqual (static_cast (aaxParam->GetNormalizedDefaultValue()), defaultValue)) + aaxParam->SetNormalizedDefaultValue (defaultValue); + } + } + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + std::unique_ptr pluginInstance; + + static constexpr auto maxSamplesPerBlock = 1 << AAX_eAudioBufferLength_Max; + + bool isPrepared = false; + MidiBuffer midiBuffer; + Array channelList; + int32_t juceChunkIndex = 0, numSetDirtyCalls = 0; + AAX_CSampleRate sampleRate = 0; + int lastBufferSize = maxSamplesPerBlock, maxBufferSize = maxSamplesPerBlock; + bool hasSidechain = false, canDisableSidechain = false, lastSideChainState = false; + + /* Pro Tools 2021 sends TransportStateChanged on the main thread, but we read + the recording state on the audio thread. + I'm not sure whether Pro Tools ensures that these calls are mutually + exclusive, so to ensure there are no data races, we store the recording + state in an atomic int and convert it to/from an Optional as necessary. + */ + class RecordingState + { + public: + /* This uses Optional rather than std::optional for consistency with get() */ + void set (const Optional newState) + { + state.store (newState.hasValue() ? (flagValid | (*newState ? flagActive : 0)) + : 0, + std::memory_order_relaxed); + } + + /* PositionInfo::setIsRecording takes an Optional, so we use that type rather + than std::optional to avoid having to convert. + */ + Optional get() const + { + const auto loaded = state.load (std::memory_order_relaxed); + return ((loaded & flagValid) != 0) ? makeOptional ((loaded & flagActive) != 0) + : nullopt; + } + + private: + enum RecordingFlags + { + flagValid = 1 << 0, + flagActive = 1 << 1 + }; + + std::atomic state { 0 }; + }; + + RecordingState recordingState; + + std::atomic processingSidechainChange, sidechainDesired; + + std::vector sideChainBuffer; + Array inputLayoutMap, outputLayoutMap; + + Array aaxParamIDs; + HashMap paramMap; + LegacyAudioParametersWrapper juceParameters; + std::unique_ptr ownedBypassParameter; + + Array aaxMeters; + + struct ChunkMemoryBlock + { + juce::MemoryBlock data; + bool isValid; + }; + + // temporary filter data is generated in GetChunkSize + // and the size of the data returned. To avoid generating + // it again in GetChunk, we need to store it somewhere. + // However, as GetChunkSize and GetChunk can be called + // on different threads, we store it in thread dependent storage + // in a hash map with the thread id as a key. + mutable ThreadLocalValue perThreadFilterData; + CriticalSection perThreadDataLock; + + ThreadLocalValue inParameterChangedCallback; + + JUCE_DECLARE_NON_COPYABLE (JuceAAX_Processor) + }; + + //============================================================================== + void JuceAAX_GUI::CreateViewContents() + { + if (component == nullptr) + { + if (auto* params = dynamic_cast (GetEffectParameters())) + component.reset (new ContentWrapperComponent (*this, params->getPluginInstance())); + else + jassertfalse; + } + } + + int JuceAAX_GUI::getParamIndexFromID (AAX_CParamID paramID) const noexcept + { + if (auto* params = dynamic_cast (GetEffectParameters())) + return params->getParamIndexFromID (paramID); + + return -1; + } + + AAX_CParamID JuceAAX_GUI::getAAXParamIDFromJuceIndex (int index) const noexcept + { + if (auto* params = dynamic_cast (GetEffectParameters())) + return params->getAAXParamIDFromJuceIndex (index); + + return nullptr; + } + + //============================================================================== + struct AAXFormatConfiguration + { + AAXFormatConfiguration() noexcept {} + + AAXFormatConfiguration (AAX_EStemFormat inFormat, AAX_EStemFormat outFormat) noexcept + : inputFormat (inFormat), outputFormat (outFormat) {} + + AAX_EStemFormat inputFormat = AAX_eStemFormat_None, + outputFormat = AAX_eStemFormat_None; + + bool operator== (const AAXFormatConfiguration other) const noexcept + { + return inputFormat == other.inputFormat && outputFormat == other.outputFormat; + } + + bool operator< (const AAXFormatConfiguration other) const noexcept + { + return inputFormat == other.inputFormat ? (outputFormat < other.outputFormat) + : (inputFormat < other.inputFormat); + } + }; + + //============================================================================== + static int addAAXMeters (AudioProcessor& p, AAX_IEffectDescriptor& descriptor) + { + LegacyAudioParametersWrapper params; + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + const bool forceLegacyParamIDs = true; + #else + const bool forceLegacyParamIDs = false; + #endif + + params.update (p, forceLegacyParamIDs); + + int meterIdx = 0; + + for (auto* param : params) + { + auto category = param->getCategory(); + + // is this a meter? + if (((category & 0xffff0000) >> 16) == 2) + { + if (auto* meterProperties = descriptor.NewPropertyMap()) + { + meterProperties->AddProperty (AAX_eProperty_Meter_Type, getMeterTypeForCategory (category)); + meterProperties->AddProperty (AAX_eProperty_Meter_Orientation, AAX_eMeterOrientation_TopRight); + + descriptor.AddMeterDescription ('Metr' + static_cast (meterIdx++), + param->getName (1024).toRawUTF8(), meterProperties); + } + } + } + + return meterIdx; + } + + static void createDescriptor (AAX_IComponentDescriptor& desc, + const AudioProcessor::BusesLayout& fullLayout, + AudioProcessor& processor, + Array& pluginIds, + const int numMeters) + { + auto aaxInputFormat = getFormatForAudioChannelSet (fullLayout.getMainInputChannelSet(), false); + auto aaxOutputFormat = getFormatForAudioChannelSet (fullLayout.getMainOutputChannelSet(), false); + + #if JucePlugin_IsSynth + if (aaxInputFormat == AAX_eStemFormat_None) + aaxInputFormat = aaxOutputFormat; + #endif + + #if JucePlugin_IsMidiEffect + aaxInputFormat = aaxOutputFormat = AAX_eStemFormat_Mono; + #endif + + check (desc.AddAudioIn (JUCEAlgorithmIDs::inputChannels)); + check (desc.AddAudioOut (JUCEAlgorithmIDs::outputChannels)); + + check (desc.AddAudioBufferLength (JUCEAlgorithmIDs::bufferSize)); + check (desc.AddDataInPort (JUCEAlgorithmIDs::bypass, sizeof (int32_t))); + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeIn, AAX_eMIDINodeType_LocalInput, + JucePlugin_Name, 0xffff)); + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeOut, AAX_eMIDINodeType_LocalOutput, + JucePlugin_Name " Out", 0xffff)); + #endif + + check (desc.AddPrivateData (JUCEAlgorithmIDs::pluginInstance, sizeof (PluginInstanceInfo))); + check (desc.AddPrivateData (JUCEAlgorithmIDs::preparedFlag, sizeof (int32_t))); + + if (numMeters > 0) + { + HeapBlock meterIDs (static_cast (numMeters)); + + for (int i = 0; i < numMeters; ++i) + meterIDs[i] = 'Metr' + static_cast (i); + + check (desc.AddMeters (JUCEAlgorithmIDs::meterTapBuffers, meterIDs.getData(), static_cast (numMeters))); + } + else + { + // AAX does not allow there to be any gaps in the fields of the algorithm context structure + // so just add a dummy one here if there aren't any meters + check (desc.AddPrivateData (JUCEAlgorithmIDs::meterTapBuffers, sizeof (uintptr_t))); + } + + // Create a property map + AAX_IPropertyMap* const properties = desc.NewPropertyMap(); + jassert (properties != nullptr); + + properties->AddProperty (AAX_eProperty_ManufacturerID, JucePlugin_AAXManufacturerCode); + properties->AddProperty (AAX_eProperty_ProductID, JucePlugin_AAXProductId); + + #if JucePlugin_AAXDisableBypass + properties->AddProperty (AAX_eProperty_CanBypass, false); + #else + properties->AddProperty (AAX_eProperty_CanBypass, true); + #endif + + properties->AddProperty (AAX_eProperty_InputStemFormat, static_cast (aaxInputFormat)); + properties->AddProperty (AAX_eProperty_OutputStemFormat, static_cast (aaxOutputFormat)); + + // This value needs to match the RTAS wrapper's Type ID, so that + // the host knows that the RTAS/AAX plugins are equivalent. + const int32 pluginID = processor.getAAXPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), + fullLayout.getMainOutputChannelSet(), + false); + + // The plugin id generated from your AudioProcessor's getAAXPluginIDForMainBusConfig callback + // it not unique. Please fix your implementation! + jassert (! pluginIds.contains (pluginID)); + pluginIds.add (pluginID); + + properties->AddProperty (AAX_eProperty_PlugInID_Native, pluginID); + + #if ! JucePlugin_AAXDisableAudioSuite + properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, + processor.getAAXPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), + fullLayout.getMainOutputChannelSet(), + true)); + #endif + + #if JucePlugin_AAXDisableMultiMono + properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, false); + #else + properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, true); + #endif + + #if JucePlugin_AAXDisableDynamicProcessing + properties->AddProperty (AAX_eProperty_Constraint_AlwaysProcess, true); + #endif + + #if JucePlugin_AAXDisableDefaultSettingsChunks + properties->AddProperty (AAX_eProperty_Constraint_DoNotApplyDefaultSettings, true); + #endif + + #if JucePlugin_AAXDisableSaveRestore + properties->AddProperty (AAX_eProperty_SupportsSaveRestore, false); + #endif + + properties->AddProperty (AAX_eProperty_ObservesTransportState, true); + + if (fullLayout.getChannelSet (true, 1) == AudioChannelSet::mono()) + { + check (desc.AddSideChainIn (JUCEAlgorithmIDs::sideChainBuffers)); + properties->AddProperty (AAX_eProperty_SupportsSideChainInput, true); + } + else + { + // AAX does not allow there to be any gaps in the fields of the algorithm context structure + // so just add a dummy one here if there aren't any side chains + check (desc.AddPrivateData (JUCEAlgorithmIDs::sideChainBuffers, sizeof (uintptr_t))); + } + + auto maxAuxBuses = jmax (0, jmin (15, fullLayout.outputBuses.size() - 1)); + + // add the output buses + // This is incredibly dumb: the output bus format must be well defined + // for every main bus in/out format pair. This means that there cannot + // be two configurations with different aux formats but + // identical main bus in/out formats. + for (int busIdx = 1; busIdx < maxAuxBuses + 1; ++busIdx) + { + auto set = fullLayout.getChannelSet (false, busIdx); + + if (set.isDisabled()) + break; + + auto auxFormat = getFormatForAudioChannelSet (set, true); + + if (auxFormat != AAX_eStemFormat_INT32_MAX && auxFormat != AAX_eStemFormat_None) + { + auto& name = processor.getBus (false, busIdx)->getName(); + check (desc.AddAuxOutputStem (0, static_cast (auxFormat), name.toRawUTF8())); + } + } + + check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); + } + + static inline bool hostSupportsStemFormat (AAX_EStemFormat stemFormat, const AAX_IFeatureInfo* featureInfo) + { + if (featureInfo != nullptr) + { + AAX_ESupportLevel supportLevel; + + if (featureInfo->SupportLevel (supportLevel) == AAX_SUCCESS && supportLevel == AAX_eSupportLevel_ByProperty) + { + std::unique_ptr props (featureInfo->AcquireProperties()); + + // Due to a bug in ProTools 12.8, ProTools thinks that AAX_eStemFormat_Ambi_1_ACN is not supported + // To workaround this bug, check if ProTools supports AAX_eStemFormat_Ambi_2_ACN, and, if yes, + // we can safely assume that it will also support AAX_eStemFormat_Ambi_1_ACN + if (stemFormat == AAX_eStemFormat_Ambi_1_ACN) + stemFormat = AAX_eStemFormat_Ambi_2_ACN; + + if (props != nullptr && props->GetProperty ((AAX_EProperty) stemFormat, (AAX_CPropertyValue*) &supportLevel) != 0) + return (supportLevel == AAX_eSupportLevel_Supported); + } + } + + return (AAX_STEM_FORMAT_INDEX (stemFormat) <= 12); + } + + static void getPlugInDescription (AAX_IEffectDescriptor& descriptor, [[maybe_unused]] const AAX_IFeatureInfo* featureInfo) + { + auto plugin = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); + auto numInputBuses = plugin->getBusCount (true); + auto numOutputBuses = plugin->getBusCount (false); + + auto pluginNames = plugin->getAlternateDisplayNames(); + + pluginNames.insert (0, JucePlugin_Name); + + pluginNames.removeDuplicates (false); + + for (auto name : pluginNames) + descriptor.AddName (name.toRawUTF8()); + + descriptor.AddCategory (JucePlugin_AAXCategory); + + const int numMeters = addAAXMeters (*plugin, descriptor); + + #ifdef JucePlugin_AAXPageTableFile + // optional page table setting - define this macro in your project if you want + // to set this value - see Avid documentation for details about its format. + descriptor.AddResourceInfo (AAX_eResourceType_PageTable, JucePlugin_AAXPageTableFile); + #endif + + check (descriptor.AddProcPtr ((void*) JuceAAX_GUI::Create, kAAX_ProcPtrID_Create_EffectGUI)); + check (descriptor.AddProcPtr ((void*) JuceAAX_Processor::Create, kAAX_ProcPtrID_Create_EffectParameters)); + + Array pluginIds; + #if JucePlugin_IsMidiEffect + // MIDI effect plug-ins do not support any audio channels + jassert (numInputBuses == 0 && numOutputBuses == 0); + + if (auto* desc = descriptor.NewComponentDescriptor()) + { + createDescriptor (*desc, plugin->getBusesLayout(), *plugin, pluginIds, numMeters); + check (descriptor.AddComponent (desc)); + } + #else + const int numIns = numInputBuses > 0 ? numElementsInArray (aaxFormats) : 0; + const int numOuts = numOutputBuses > 0 ? numElementsInArray (aaxFormats) : 0; + + for (int inIdx = 0; inIdx < jmax (numIns, 1); ++inIdx) + { + auto aaxInFormat = numIns > 0 ? aaxFormats[inIdx] : AAX_eStemFormat_None; + auto inLayout = channelSetFromStemFormat (aaxInFormat, false); + + for (int outIdx = 0; outIdx < jmax (numOuts, 1); ++outIdx) + { + auto aaxOutFormat = numOuts > 0 ? aaxFormats[outIdx] : AAX_eStemFormat_None; + auto outLayout = channelSetFromStemFormat (aaxOutFormat, false); + + if (hostSupportsStemFormat (aaxInFormat, featureInfo) + && hostSupportsStemFormat (aaxOutFormat, featureInfo)) + { + AudioProcessor::BusesLayout fullLayout; + + if (! JuceAAX_Processor::fullBusesLayoutFromMainLayout (*plugin, inLayout, outLayout, fullLayout)) + continue; + + if (auto* desc = descriptor.NewComponentDescriptor()) + { + createDescriptor (*desc, fullLayout, *plugin, pluginIds, numMeters); + check (descriptor.AddComponent (desc)); + } + } + } + } + + // You don't have any supported layouts + jassert (pluginIds.size() > 0); + #endif + } +} // namespace AAXClasses + +void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) +{ + AAXClasses::JuceAAX_Processor::algorithmCallback (instancesBegin, instancesEnd); +} + +//============================================================================== +AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection*); +AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection* collection) +{ + ScopedJuceInitialiser_GUI libraryInitialiser; + + std::unique_ptr stemFormatFeatureInfo; + + if (const auto* hostDescription = collection->DescriptionHost()) + stemFormatFeatureInfo.reset (hostDescription->AcquireFeatureProperties (AAXATTR_ClientFeature_StemFormat)); + + if (auto* descriptor = collection->NewDescriptor()) + { + AAXClasses::getPlugInDescription (*descriptor, stemFormatFeatureInfo.get()); + collection->AddEffect (JUCE_STRINGIFY (JucePlugin_AAXIdentifier), descriptor); + + collection->SetManufacturerName (JucePlugin_Manufacturer); + collection->AddPackageName (JucePlugin_Desc); + collection->AddPackageName (JucePlugin_Name); + collection->SetPackageVersion (JucePlugin_VersionCode); + + return AAX_SUCCESS; + } + + return AAX_ERROR_NULL_OBJECT; +} + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +//============================================================================== +#if _MSC_VER || JUCE_MINGW +extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } +#endif + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm index 58f3b0d95e..fddac2e440 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm @@ -24,4 +24,4 @@ */ #define JUCE_INCLUDED_AAX_IN_MM 1 -#include "AAX/juce_AAX_Wrapper.cpp" +#include "juce_audio_plugin_client_AAX.cpp" diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp index a0b994c315..8eef0dc85d 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp @@ -25,7 +25,7 @@ #include -#if JucePlugin_Build_AAX && (JUCE_MAC || JUCE_WINDOWS) +#if JucePlugin_Build_AAX #include diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp index ec83d19af9..732b4d6388 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp @@ -23,4 +23,23 @@ ============================================================================== */ -#include "ARA/juce_ARA_Wrapper.cpp" +#include +#include + +#if JucePlugin_Enable_ARA + +#include +#include + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", "-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) + +#include +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm index edb490a8a0..40d85050ff 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm @@ -23,4 +23,2598 @@ ============================================================================== */ -#include "AU/juce_AU_Wrapper.mm" +#include +#include +#include + +#if JucePlugin_Build_AU + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshorten-64-to-32", + "-Wunused-parameter", + "-Wdeprecated-declarations", + "-Wsign-conversion", + "-Wconversion", + "-Woverloaded-virtual", + "-Wextra-semi", + "-Wcast-align", + "-Wshadow", + "-Wswitch-enum", + "-Wzero-as-null-pointer-constant", + "-Wnullable-to-nonnull-conversion", + "-Wgnu-zero-variadic-macro-arguments", + "-Wformat-pedantic", + "-Wdeprecated-anon-enum-enum-conversion") + +#include + +#include +#include +#include +#include +#include +#include "AudioUnitSDK/MusicDeviceBase.h" + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 + +#include + +#include +#include +#include +#include + +#if JucePlugin_Enable_ARA + #include + #include + #if ARA_SUPPORT_VERSION_1 + #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current JUCE ARA implementation" + #endif +#endif + +#include + +//============================================================================== +using namespace juce; + +static Array activePlugins, activeUIs; + +static const AudioUnitPropertyID juceFilterObjectPropertyID = 0x1a45ffe9; + +template <> struct ContainerDeletePolicy { static void destroy (const __CFString* o) { if (o != nullptr) CFRelease (o); } }; + +// make sure the audio processor is initialized before the AUBase class +struct AudioProcessorHolder +{ + AudioProcessorHolder (bool initialiseGUI) + { + if (initialiseGUI) + initialiseJuce_GUI(); + + juceFilter = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnit); + + // audio units do not have a notion of enabled or un-enabled buses + juceFilter->enableAllBuses(); + } + + std::unique_ptr juceFilter; +}; + +//============================================================================== +class JuceAU : public AudioProcessorHolder, + public ausdk::MusicDeviceBase, + public AudioProcessorListener, + public AudioProcessorParameter::Listener +{ +public: + JuceAU (AudioUnit component) + : AudioProcessorHolder (activePlugins.size() + activeUIs.size() == 0), + MusicDeviceBase (component, + (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true), + (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)) + { + inParameterChangedCallback = false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + juceFilter->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); + + for (int i = 0; i < numConfigs; ++i) + { + AUChannelInfo info; + + info.inChannels = configs[i][0]; + info.outChannels = configs[i][1]; + + channelInfo.add (info); + } + #else + channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); + #endif + + AddPropertyListener (kAudioUnitProperty_ContextName, auPropertyListenerDispatcher, this); + + totalInChannels = juceFilter->getTotalNumInputChannels(); + totalOutChannels = juceFilter->getTotalNumOutputChannels(); + + juceFilter->addListener (this); + + addParameters(); + + activePlugins.add (this); + + zerostruct (auEvent); + auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance(); + auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global; + auEvent.mArgument.mParameter.mElement = 0; + + zerostruct (midiCallback); + + CreateElements(); + + if (syncAudioUnitWithProcessor() != noErr) + jassertfalse; + } + + ~JuceAU() override + { + if (bypassParam != nullptr) + bypassParam->removeListener (this); + + deleteActiveEditors(); + juceFilter = nullptr; + clearPresetsArray(); + + jassert (activePlugins.contains (this)); + activePlugins.removeFirstMatchingValue (this); + + if (activePlugins.size() + activeUIs.size() == 0) + shutdownJuce_GUI(); + } + + //============================================================================== + ComponentResult Initialize() override + { + ComponentResult err; + + if ((err = syncProcessorWithAudioUnit()) != noErr) + return err; + + if ((err = MusicDeviceBase::Initialize()) != noErr) + return err; + + mapper.alloc (*juceFilter); + pulledSucceeded.calloc (static_cast (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true))); + + prepareToPlay(); + + return noErr; + } + + void Cleanup() override + { + MusicDeviceBase::Cleanup(); + + pulledSucceeded.free(); + mapper.release(); + + if (juceFilter != nullptr) + juceFilter->releaseResources(); + + audioBuffer.release(); + midiEvents.clear(); + incomingEvents.clear(); + prepared = false; + } + + ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) override + { + if (! prepared) + prepareToPlay(); + + if (juceFilter != nullptr) + juceFilter->reset(); + + return MusicDeviceBase::Reset (inScope, inElement); + } + + //============================================================================== + void prepareToPlay() + { + if (juceFilter != nullptr) + { + juceFilter->setRateAndBufferSizeDetails (getSampleRate(), (int) GetMaxFramesPerSlice()); + + audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (juceFilter.get()), (int) GetMaxFramesPerSlice() + 32); + juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); + + midiEvents.ensureSize (2048); + midiEvents.clear(); + incomingEvents.ensureSize (2048); + incomingEvents.clear(); + + prepared = true; + } + } + + //============================================================================== + bool BusCountWritable ([[maybe_unused]] AudioUnitScope scope) override + { + #ifdef JucePlugin_PreferredChannelConfigurations + return false; + #else + bool isInput; + + if (scopeToDirection (scope, isInput) != noErr) + return false; + + #if JucePlugin_IsMidiEffect + return false; + #elif JucePlugin_IsSynth + if (isInput) return false; + #endif + + const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + return (juceFilter->canAddBus (isInput) || (busCount > 0 && juceFilter->canRemoveBus (isInput))); + #endif + } + + OSStatus SetBusCount (AudioUnitScope scope, UInt32 count) override + { + OSStatus err = noErr; + bool isInput; + + if ((err = scopeToDirection (scope, isInput)) != noErr) + return err; + + if (count != (UInt32) AudioUnitHelpers::getBusCount (*juceFilter, isInput)) + { + #ifdef JucePlugin_PreferredChannelConfigurations + return kAudioUnitErr_PropertyNotWritable; + #else + const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + + if ((! juceFilter->canAddBus (isInput)) && ((busCount == 0) || (! juceFilter->canRemoveBus (isInput)))) + return kAudioUnitErr_PropertyNotWritable; + + // we need to already create the underlying elements so that we can change their formats + err = MusicDeviceBase::SetBusCount (scope, count); + + if (err != noErr) + return err; + + // however we do need to update the format tag: we need to do the same thing in SetFormat, for example + const int requestedNumBus = static_cast (count); + { + (isInput ? currentInputLayout : currentOutputLayout).resize (requestedNumBus); + + int busNr; + + for (busNr = (busCount - 1); busNr != (requestedNumBus - 1); busNr += (requestedNumBus > busCount ? 1 : -1)) + { + if (requestedNumBus > busCount) + { + if (! juceFilter->addBus (isInput)) + break; + + err = syncAudioUnitWithChannelSet (isInput, busNr, + juceFilter->getBus (isInput, busNr + 1)->getDefaultLayout()); + if (err != noErr) + break; + } + else + { + if (! juceFilter->removeBus (isInput)) + break; + } + } + + err = (busNr == (requestedNumBus - 1) ? (OSStatus) noErr : (OSStatus) kAudioUnitErr_FormatNotSupported); + } + + // was there an error? + if (err != noErr) + { + // restore bus state + const int newBusCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + for (int i = newBusCount; i != busCount; i += (busCount > newBusCount ? 1 : -1)) + { + if (busCount > newBusCount) + juceFilter->addBus (isInput); + else + juceFilter->removeBus (isInput); + } + + (isInput ? currentInputLayout : currentOutputLayout).resize (busCount); + MusicDeviceBase::SetBusCount (scope, static_cast (busCount)); + + return kAudioUnitErr_FormatNotSupported; + } + + // update total channel count + totalInChannels = juceFilter->getTotalNumInputChannels(); + totalOutChannels = juceFilter->getTotalNumOutputChannels(); + + addSupportedLayoutTagsForDirection (isInput); + + if (err != noErr) + return err; + #endif + } + + return noErr; + } + + UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) override + { + if (outInfo != nullptr) + *outInfo = channelInfo.getRawDataPointer(); + + return (UInt32) channelInfo.size(); + } + + //============================================================================== + ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + UInt32& outDataSize, + bool& outWritable) override + { + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case juceFilterObjectPropertyID: + outWritable = false; + outDataSize = sizeof (void*) * 2; + return noErr; + + case kAudioUnitProperty_OfflineRender: + outWritable = true; + outDataSize = sizeof (UInt32); + return noErr; + + case kMusicDeviceProperty_InstrumentCount: + outDataSize = sizeof (UInt32); + outWritable = false; + return noErr; + + case kAudioUnitProperty_CocoaUI: + outDataSize = sizeof (AudioUnitCocoaViewInfo); + outWritable = true; + return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_AudioUnitMIDIProtocol: + outDataSize = sizeof (SInt32); + outWritable = false; + return noErr; + + case kAudioUnitProperty_HostMIDIProtocol: + outDataSize = sizeof (SInt32); + outWritable = true; + return noErr; + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + case kAudioUnitProperty_MIDIOutputCallbackInfo: + outDataSize = sizeof (CFArrayRef); + outWritable = false; + return noErr; + + case kAudioUnitProperty_MIDIOutputCallback: + outDataSize = sizeof (AUMIDIOutputCallbackStruct); + outWritable = true; + return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_MIDIOutputEventListCallback: + outDataSize = sizeof (AUMIDIEventListBlock); + outWritable = true; + return noErr; + #endif + #endif + + case kAudioUnitProperty_ParameterStringFromValue: + outDataSize = sizeof (AudioUnitParameterStringFromValue); + outWritable = false; + return noErr; + + case kAudioUnitProperty_ParameterValueFromString: + outDataSize = sizeof (AudioUnitParameterValueFromString); + outWritable = false; + return noErr; + + case kAudioUnitProperty_BypassEffect: + outDataSize = sizeof (UInt32); + outWritable = true; + return noErr; + + case kAudioUnitProperty_SupportsMPE: + outDataSize = sizeof (UInt32); + outWritable = false; + return noErr; + + #if JucePlugin_Enable_ARA + case ARA::kAudioUnitProperty_ARAFactory: + outWritable = false; + outDataSize = sizeof (ARA::ARAAudioUnitFactory); + return noErr; + case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: + outWritable = false; + outDataSize = sizeof (ARA::ARAAudioUnitPlugInExtensionBinding); + return noErr; + #endif + + default: break; + } + } + + return MusicDeviceBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); + } + + ComponentResult GetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + void* outData) override + { + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case kAudioUnitProperty_ParameterClumpName: + + if (auto* clumpNameInfo = (AudioUnitParameterNameInfo*) outData) + { + if (juceFilter != nullptr) + { + auto clumpIndex = clumpNameInfo->inID - 1; + const auto* group = parameterGroups[(int) clumpIndex]; + auto name = group->getName(); + + while (group->getParent() != &juceFilter->getParameterTree()) + { + group = group->getParent(); + name = group->getName() + group->getSeparator() + name; + } + + clumpNameInfo->outName = name.toCFString(); + return noErr; + } + } + + // Failed to find a group corresponding to the clump ID. + jassertfalse; + break; + + //============================================================================== + #if JucePlugin_Enable_ARA + case ARA::kAudioUnitProperty_ARAFactory: + { + auto auFactory = static_cast (outData); + if (auFactory->inOutMagicNumber != ARA::kARAAudioUnitMagic) + return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics + + auFactory->outFactory = createARAFactory(); + return noErr; + } + + case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: + { + auto binding = static_cast (outData); + if (binding->inOutMagicNumber != ARA::kARAAudioUnitMagic) + return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics + + AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (juceFilter.get()); + binding->outPlugInExtension = araAudioProcessorExtension->bindToARA (binding->inDocumentControllerRef, binding->knownRoles, binding->assignedRoles); + if (binding->outPlugInExtension == nullptr) + return kAudioUnitErr_CannotDoInCurrentContext; // bindToARA() returns null if binding is already established + + return noErr; + } + #endif + + case juceFilterObjectPropertyID: + ((void**) outData)[0] = (void*) static_cast (juceFilter.get()); + ((void**) outData)[1] = (void*) this; + return noErr; + + case kAudioUnitProperty_OfflineRender: + *(UInt32*) outData = (juceFilter != nullptr && juceFilter->isNonRealtime()) ? 1 : 0; + return noErr; + + case kMusicDeviceProperty_InstrumentCount: + *(UInt32*) outData = 1; + return noErr; + + case kAudioUnitProperty_BypassEffect: + if (bypassParam != nullptr) + *(UInt32*) outData = (bypassParam->getValue() != 0.0f ? 1 : 0); + else + *(UInt32*) outData = isBypassed ? 1 : 0; + return noErr; + + case kAudioUnitProperty_SupportsMPE: + *(UInt32*) outData = (juceFilter != nullptr && juceFilter->supportsMPE()) ? 1 : 0; + return noErr; + + case kAudioUnitProperty_CocoaUI: + { + JUCE_AUTORELEASEPOOL + { + static JuceUICreationClass cls; + + // (NB: this may be the host's bundle, not necessarily the component's) + NSBundle* bundle = [NSBundle bundleForClass: cls.cls]; + + AudioUnitCocoaViewInfo* info = static_cast (outData); + info->mCocoaAUViewClass[0] = (CFStringRef) [juceStringToNS (class_getName (cls.cls)) retain]; + info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [bundle bundlePath]] retain]; + } + + return noErr; + } + + break; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_AudioUnitMIDIProtocol: + { + // This will become configurable in the future + *static_cast (outData) = kMIDIProtocol_1_0; + return noErr; + } + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + case kAudioUnitProperty_MIDIOutputCallbackInfo: + { + CFStringRef strs[1]; + strs[0] = CFSTR ("MIDI Callback"); + + CFArrayRef callbackArray = CFArrayCreate (nullptr, (const void**) strs, 1, &kCFTypeArrayCallBacks); + *(CFArrayRef*) outData = callbackArray; + return noErr; + } + #endif + + case kAudioUnitProperty_ParameterValueFromString: + { + if (AudioUnitParameterValueFromString* pv = (AudioUnitParameterValueFromString*) outData) + { + if (juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (pv->inParamID)) + { + const String text (String::fromCFString (pv->inString)); + + if (LegacyAudioParameter::isLegacy (param)) + pv->outValue = text.getFloatValue(); + else + pv->outValue = param->getValueForText (text) * getMaximumParameterValue (param); + + + return noErr; + } + } + } + } + break; + + case kAudioUnitProperty_ParameterStringFromValue: + { + if (AudioUnitParameterStringFromValue* pv = (AudioUnitParameterStringFromValue*) outData) + { + if (juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (pv->inParamID)) + { + const float value = (float) *(pv->inValue); + String text; + + if (LegacyAudioParameter::isLegacy (param)) + text = String (value); + else + text = param->getText (value / getMaximumParameterValue (param), 0); + + pv->outString = text.toCFString(); + + return noErr; + } + } + } + } + break; + + default: + break; + } + } + + return MusicDeviceBase::GetProperty (inID, inScope, inElement, outData); + } + + ComponentResult SetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + const void* inData, + UInt32 inDataSize) override + { + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + case kAudioUnitProperty_MIDIOutputCallback: + if (inDataSize < sizeof (AUMIDIOutputCallbackStruct)) + return kAudioUnitErr_InvalidPropertyValue; + + if (AUMIDIOutputCallbackStruct* callbackStruct = (AUMIDIOutputCallbackStruct*) inData) + midiCallback = *callbackStruct; + + return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_MIDIOutputEventListCallback: + { + if (inDataSize != sizeof (AUMIDIEventListBlock)) + return kAudioUnitErr_InvalidPropertyValue; + + midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast (inData)); + return noErr; + } + #endif + #endif + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_HostMIDIProtocol: + { + if (inDataSize != sizeof (SInt32)) + return kAudioUnitErr_InvalidPropertyValue; + + hostProtocol = *static_cast (inData); + return noErr; + } + #endif + + case kAudioUnitProperty_BypassEffect: + { + if (inDataSize < sizeof (UInt32)) + return kAudioUnitErr_InvalidPropertyValue; + + const bool newBypass = *((UInt32*) inData) != 0; + const bool currentlyBypassed = (bypassParam != nullptr ? (bypassParam->getValue() != 0.0f) : isBypassed); + + if (newBypass != currentlyBypassed) + { + if (bypassParam != nullptr) + bypassParam->setValueNotifyingHost (newBypass ? 1.0f : 0.0f); + else + isBypassed = newBypass; + + if (! currentlyBypassed && IsInitialized()) // turning bypass off and we're initialized + Reset (0, 0); + } + + return noErr; + } + + case kAudioUnitProperty_OfflineRender: + { + const auto shouldBeOffline = (*reinterpret_cast (inData) != 0); + + if (juceFilter != nullptr) + { + const auto isOffline = juceFilter->isNonRealtime(); + + if (isOffline != shouldBeOffline) + { + const ScopedLock sl (juceFilter->getCallbackLock()); + + juceFilter->setNonRealtime (shouldBeOffline); + + if (prepared) + juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); + } + } + + return noErr; + } + + case kAudioUnitProperty_AUHostIdentifier: + { + if (inDataSize < sizeof (AUHostVersionIdentifier)) + return kAudioUnitErr_InvalidPropertyValue; + + const auto* identifier = static_cast (inData); + PluginHostType::hostIdReportedByWrapper = String::fromCFString (identifier->hostName); + + return noErr; + } + + default: break; + } + } + + return MusicDeviceBase::SetProperty (inID, inScope, inElement, inData, inDataSize); + } + + //============================================================================== + ComponentResult SaveState (CFPropertyListRef* outData) override + { + ComponentResult err = MusicDeviceBase::SaveState (outData); + + if (err != noErr) + return err; + + jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); + + CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; + + if (juceFilter != nullptr) + { + juce::MemoryBlock state; + + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + juceFilter->getCurrentProgramStateInformation (state); + #else + juceFilter->getStateInformation (state); + #endif + + if (state.getSize() > 0) + { + CFUniquePtr ourState (CFDataCreate (kCFAllocatorDefault, (const UInt8*) state.getData(), (CFIndex) state.getSize())); + CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); + CFDictionarySetValue (dict, key.get(), ourState.get()); + } + } + + return noErr; + } + + ComponentResult RestoreState (CFPropertyListRef inData) override + { + const ScopedValueSetter scope { restoringState, true }; + + { + // Remove the data entry from the state to prevent the superclass loading the parameters + CFUniquePtr copyWithoutData (CFDictionaryCreateMutableCopy (nullptr, 0, (CFDictionaryRef) inData)); + CFDictionaryRemoveValue (copyWithoutData.get(), CFSTR (kAUPresetDataKey)); + ComponentResult err = MusicDeviceBase::RestoreState (copyWithoutData.get()); + + if (err != noErr) + return err; + } + + if (juceFilter != nullptr) + { + CFDictionaryRef dict = (CFDictionaryRef) inData; + CFDataRef data = nullptr; + + CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); + + bool valuePresent = CFDictionaryGetValueIfPresent (dict, key.get(), (const void**) &data); + + if (valuePresent) + { + if (data != nullptr) + { + const int numBytes = (int) CFDataGetLength (data); + const juce::uint8* const rawBytes = CFDataGetBytePtr (data); + + if (numBytes > 0) + { + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + juceFilter->setCurrentProgramStateInformation (rawBytes, numBytes); + #else + juceFilter->setStateInformation (rawBytes, numBytes); + #endif + } + } + } + } + + return noErr; + } + + //============================================================================== + bool busIgnoresLayout ([[maybe_unused]] bool isInput, [[maybe_unused]] int busNr) const + { + #ifdef JucePlugin_PreferredChannelConfigurations + return true; + #else + if (const AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNr)) + { + AudioChannelSet discreteRangeSet; + const int n = bus->getDefaultLayout().size(); + for (int i = 0; i < n; ++i) + discreteRangeSet.addChannel ((AudioChannelSet::ChannelType) (256 + i)); + + // if the audioprocessor supports this it cannot + // really be interested in the bus layouts + return bus->isLayoutSupported (discreteRangeSet); + } + + return true; + #endif + } + + UInt32 GetAudioChannelLayout (AudioUnitScope scope, + AudioUnitElement element, + AudioChannelLayout* outLayoutPtr, + bool& outWritable) override + { + outWritable = false; + + const auto info = getElementInfo (scope, element); + + if (info.error != noErr) + return 0; + + if (busIgnoresLayout (info.isInput, info.busNr)) + return 0; + + outWritable = true; + + const size_t sizeInBytes = sizeof (AudioChannelLayout) - sizeof (AudioChannelDescription); + + if (outLayoutPtr != nullptr) + { + zeromem (outLayoutPtr, sizeInBytes); + outLayoutPtr->mChannelLayoutTag = getCurrentLayout (info.isInput, info.busNr); + } + + return sizeInBytes; + } + + std::vector GetChannelLayoutTags (AudioUnitScope inScope, AudioUnitElement inElement) override + { + const auto info = getElementInfo (inScope, inElement); + + if (info.error != noErr) + return {}; + + if (busIgnoresLayout (info.isInput, info.busNr)) + return {}; + + return getSupportedBusLayouts (info.isInput, info.busNr); + } + + OSStatus SetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout) override + { + const auto info = getElementInfo (scope, element); + + if (info.error != noErr) + return info.error; + + if (busIgnoresLayout (info.isInput, info.busNr)) + return kAudioUnitErr_PropertyNotWritable; + + if (inLayout == nullptr) + return kAudioUnitErr_InvalidPropertyValue; + + auto& ioElement = IOElement (info.isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element); + + const AudioChannelSet newChannelSet = CoreAudioLayouts::fromCoreAudio (*inLayout); + const int currentNumChannels = static_cast (ioElement.NumberChannels()); + const int newChannelNum = newChannelSet.size(); + + if (currentNumChannels != newChannelNum) + return kAudioUnitErr_InvalidPropertyValue; + + // check if the new layout could be potentially set + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newChannelNum, configs)) + return kAudioUnitErr_FormatNotSupported; + #else + if (! juceFilter->getBus (info.isInput, info.busNr)->isLayoutSupported (newChannelSet)) + return kAudioUnitErr_FormatNotSupported; + #endif + + getCurrentLayout (info.isInput, info.busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet); + + return noErr; + } + + //============================================================================== + // When parameters are discrete we need to use integer values. + float getMaximumParameterValue ([[maybe_unused]] AudioProcessorParameter* juceParam) + { + #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + return 1.0f; + #else + return juceParam->isDiscrete() ? (float) (juceParam->getNumSteps() - 1) : 1.0f; + #endif + } + + ComponentResult GetParameterInfo (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) override + { + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inParameterID)) + { + outParameterInfo.unit = kAudioUnitParameterUnit_Generic; + outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable + | kAudioUnitParameterFlag_IsReadable + | kAudioUnitParameterFlag_HasCFNameString + | kAudioUnitParameterFlag_ValuesHaveStrings); + + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; + #endif + + const String name = param->getName (1024); + + // Set whether the param is automatable (unnamed parameters aren't allowed to be automated) + if (name.isEmpty() || ! param->isAutomatable()) + outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; + + const bool isParameterDiscrete = param->isDiscrete(); + + if (! isParameterDiscrete) + outParameterInfo.flags |= kAudioUnitParameterFlag_CanRamp; + + if (param->isMetaParameter()) + outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; + + auto parameterGroupHierarchy = juceFilter->getParameterTree().getGroupsForParameter (param); + + if (! parameterGroupHierarchy.isEmpty()) + { + outParameterInfo.flags |= kAudioUnitParameterFlag_HasClump; + outParameterInfo.clumpID = (UInt32) parameterGroups.indexOf (parameterGroupHierarchy.getLast()) + 1; + } + + // Is this a meter? + if ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2) + { + outParameterInfo.flags &= ~kAudioUnitParameterFlag_IsWritable; + outParameterInfo.flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; + outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; + } + else + { + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + if (isParameterDiscrete) + outParameterInfo.unit = param->isBoolean() ? kAudioUnitParameterUnit_Boolean + : kAudioUnitParameterUnit_Indexed; + #endif + } + + MusicDeviceBase::FillInParameterName (outParameterInfo, name.toCFString(), true); + + outParameterInfo.minValue = 0.0f; + outParameterInfo.maxValue = getMaximumParameterValue (param); + outParameterInfo.defaultValue = param->getDefaultValue() * getMaximumParameterValue (param); + jassert (outParameterInfo.defaultValue >= outParameterInfo.minValue + && outParameterInfo.defaultValue <= outParameterInfo.maxValue); + + return noErr; + } + } + + return kAudioUnitErr_InvalidParameter; + } + + ComponentResult GetParameterValueStrings (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + CFArrayRef *outStrings) override + { + if (outStrings == nullptr) + return noErr; + + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inParameterID)) + { + if (param->isDiscrete()) + { + auto index = LegacyAudioParameter::getParamIndex (*juceFilter, param); + + if (auto* valueStrings = parameterValueStringArrays[index]) + { + *outStrings = CFArrayCreate (nullptr, + (const void **) valueStrings->getRawDataPointer(), + valueStrings->size(), + nullptr); + + return noErr; + } + } + } + } + + return kAudioUnitErr_InvalidParameter; + } + + ComponentResult GetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32& outValue) override + { + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inID)) + { + const auto normValue = param->getValue(); + + outValue = normValue * getMaximumParameterValue (param); + return noErr; + } + } + + return MusicDeviceBase::GetParameter (inID, inScope, inElement, outValue); + } + + ComponentResult SetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32 inValue, + UInt32 inBufferOffsetInFrames) override + { + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inID)) + { + auto value = inValue / getMaximumParameterValue (param); + + if (value != param->getValue()) + { + inParameterChangedCallback = true; + param->setValueNotifyingHost (value); + } + + return noErr; + } + } + + return MusicDeviceBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); + } + + // No idea what this method actually does or what it should return. Current Apple docs say nothing about it. + // (Note that this isn't marked 'override' in case older versions of the SDK don't include it) + bool CanScheduleParameters() const override { return false; } + + //============================================================================== + bool SupportsTail() override { return true; } + Float64 GetTailTime() override { return juceFilter->getTailLengthSeconds(); } + + double getSampleRate() + { + if (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false) > 0) + return Output (0).GetStreamFormat().mSampleRate; + + return 44100.0; + } + + Float64 GetLatency() override + { + const double rate = getSampleRate(); + jassert (rate > 0); + #if JucePlugin_Enable_ARA + jassert (juceFilter->getLatencySamples() == 0 || ! dynamic_cast (juceFilter.get())->isBoundToARA()); + #endif + return rate > 0 ? juceFilter->getLatencySamples() / rate : 0; + } + + class ScopedPlayHead : private AudioPlayHead + { + public: + explicit ScopedPlayHead (JuceAU& juceAudioUnit) + : audioUnit (juceAudioUnit) + { + audioUnit.juceFilter->setPlayHead (this); + } + + ~ScopedPlayHead() override + { + audioUnit.juceFilter->setPlayHead (nullptr); + } + + private: + Optional getPosition() const override + { + PositionInfo info; + + info.setFrameRate ([this]() -> Optional + { + switch (audioUnit.lastTimeStamp.mSMPTETime.mType) + { + case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); + case kSMPTETimeType24: return FrameRate().withBaseRate (24); + case kSMPTETimeType25: return FrameRate().withBaseRate (25); + case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); + case kSMPTETimeType30: return FrameRate().withBaseRate (30); + case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); + case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + case kSMPTETimeType60: return FrameRate().withBaseRate (60); + case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); + case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); + case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + case kSMPTETimeType50: return FrameRate().withBaseRate (50); + default: break; + } + + return {}; + }()); + + double ppqPosition = 0.0; + double bpm = 0.0; + + if (audioUnit.CallHostBeatAndTempo (&ppqPosition, &bpm) == noErr) + { + info.setPpqPosition (ppqPosition); + info.setBpm (bpm); + } + + UInt32 outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat; + float num; + UInt32 den; + + if (audioUnit.CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, + &num, + &den, + &outCurrentMeasureDownBeat) == noErr) + { + info.setTimeSignature (TimeSignature { (int) num, (int) den }); + info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); + } + + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; + Boolean playing = false, looping = false, playchanged; + + const auto setTimeInSamples = [&] (auto timeInSamples) + { + info.setTimeInSamples ((int64) (timeInSamples + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / audioUnit.getSampleRate()); + }; + + if (audioUnit.CallHostTransportState (&playing, + &playchanged, + &outCurrentSampleInTimeLine, + &looping, + &outCycleStartBeat, + &outCycleEndBeat) == noErr) + { + info.setIsPlaying (playing); + info.setIsLooping (looping); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); + setTimeInSamples (outCurrentSampleInTimeLine); + } + else + { + // If the host doesn't support this callback, then use the sample time from lastTimeStamp + setTimeInSamples (audioUnit.lastTimeStamp.mSampleTime); + } + + info.setHostTimeNs ((audioUnit.lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0 + ? makeOptional (audioUnit.timeConversions.hostTimeToNanos (audioUnit.lastTimeStamp.mHostTime)) + : nullopt); + + return info; + } + + JuceAU& audioUnit; + }; + + //============================================================================== + void sendAUEvent (const AudioUnitEventType type, const int juceParamIndex) + { + if (restoringState) + return; + + auEvent.mEventType = type; + auEvent.mArgument.mParameter.mParameterID = getAUParameterIDForIndex (juceParamIndex); + AUEventListenerNotify (nullptr, nullptr, &auEvent); + } + + void audioProcessorParameterChanged (AudioProcessor*, int index, float /*newValue*/) override + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + sendAUEvent (kAudioUnitEvent_ParameterValueChange, index); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override + { + sendAUEvent (kAudioUnitEvent_BeginParameterChangeGesture, index); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override + { + sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); + } + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + audioProcessorChangedUpdater.update (details); + } + + //============================================================================== + // this will only ever be called by the bypass parameter + void parameterValueChanged (int, float) override + { + if (! restoringState) + PropertyChanged (kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0); + } + + void parameterGestureChanged (int, bool) override {} + + //============================================================================== + bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override + { + const auto info = getElementInfo (scope, element); + + return ((! IsInitialized()) && (info.error == noErr)); + } + + bool ValidFormat (AudioUnitScope inScope, + AudioUnitElement inElement, + const AudioStreamBasicDescription& inNewFormat) override + { + // DSP Quattro incorrectly uses global scope for the ValidFormat call + if (inScope == kAudioUnitScope_Global) + return ValidFormat (kAudioUnitScope_Input, inElement, inNewFormat) + || ValidFormat (kAudioUnitScope_Output, inElement, inNewFormat); + + const auto info = getElementInfo (inScope, inElement); + + if (info.error != noErr) + return false; + + if (info.kind == BusKind::wrapperOnly) + return true; + + const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); + const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); + + if (newNumChannels == oldNumChannels) + return true; + + if ([[maybe_unused]] AudioProcessor::Bus* bus = juceFilter->getBus (info.isInput, info.busNr)) + { + if (! MusicDeviceBase::ValidFormat (inScope, inElement, inNewFormat)) + return false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + return AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs); + #else + return bus->isNumberOfChannelsSupported (newNumChannels); + #endif + } + + return false; + } + + // AU requires us to override this for the sole reason that we need to find a default layout tag if the number of channels have changed + OSStatus ChangeStreamFormat (AudioUnitScope inScope, + AudioUnitElement inElement, + const AudioStreamBasicDescription& inPrevFormat, + const AudioStreamBasicDescription& inNewFormat) override + { + const auto info = getElementInfo (inScope, inElement); + + if (info.error != noErr) + return info.error; + + AudioChannelLayoutTag& currentTag = getCurrentLayout (info.isInput, info.busNr); + + const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); + const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs)) + return kAudioUnitErr_FormatNotSupported; + #endif + + // predict channel layout + const auto set = [&] + { + if (info.kind == BusKind::wrapperOnly) + return AudioChannelSet::discreteChannels (newNumChannels); + + if (newNumChannels != oldNumChannels) + return juceFilter->getBus (info.isInput, info.busNr)->supportedLayoutWithChannels (newNumChannels); + + return juceFilter->getChannelLayoutOfBus (info.isInput, info.busNr); + }(); + + if (set == AudioChannelSet()) + return kAudioUnitErr_FormatNotSupported; + + const auto err = MusicDeviceBase::ChangeStreamFormat (inScope, inElement, inPrevFormat, inNewFormat); + + if (err == noErr) + currentTag = CoreAudioLayouts::toCoreAudio (set); + + return err; + } + + //============================================================================== + ComponentResult Render (AudioUnitRenderActionFlags& ioActionFlags, + const AudioTimeStamp& inTimeStamp, + const UInt32 nFrames) override + { + lastTimeStamp = inTimeStamp; + + // prepare buffers + { + pullInputAudio (ioActionFlags, inTimeStamp, nFrames); + prepareOutputBuffers (nFrames); + audioBuffer.reset(); + } + + ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; + + const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + + // set buffer pointers to minimize copying + { + int chIdx = 0, numChannels = 0; + bool interleaved = false; + AudioBufferList* buffer = nullptr; + + // use output pointers + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + { + GetAudioBufferList (false, busIdx, buffer, interleaved, numChannels); + const int* outLayoutMap = mapper.get (false, busIdx); + + for (int ch = 0; ch < numChannels; ++ch) + audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); + } + + // use input pointers on remaining channels + for (int busIdx = 0; chIdx < totalInChannels;) + { + int channelIndexInBus = juceFilter->getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); + const bool badData = ! pulledSucceeded[busIdx]; + + if (! badData) + GetAudioBufferList (true, busIdx, buffer, interleaved, numChannels); + + const int* inLayoutMap = mapper.get (true, busIdx); + + const int n = juceFilter->getChannelCountOfBus (true, busIdx); + for (int ch = channelIndexInBus; ch < n; ++ch) + audioBuffer.setBuffer (chIdx++, interleaved || badData ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[ch]].mData)); + } + } + + // copy input + { + for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) + { + if (pulledSucceeded[busIdx]) + audioBuffer.set (busIdx, Input ((UInt32) busIdx).GetBufferList(), mapper.get (true, busIdx)); + else + audioBuffer.clearInputBus (busIdx, (int) nFrames); + } + + audioBuffer.clearUnusedChannels ((int) nFrames); + } + + // swap midi buffers + { + const ScopedLock sl (incomingMidiLock); + midiEvents.clear(); + incomingEvents.swapWith (midiEvents); + } + + // process audio + processBlock (audioBuffer.getBuffer (nFrames), midiEvents); + + // copy back + { + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + audioBuffer.get (busIdx, Output ((UInt32) busIdx).GetBufferList(), mapper.get (false, busIdx)); + } + + // process midi output + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + pushMidiOutput (nFrames); + #endif + + midiEvents.clear(); + + return noErr; + } + + //============================================================================== + ComponentResult StartNote (MusicDeviceInstrumentID, MusicDeviceGroupID, NoteInstanceID*, UInt32, const MusicDeviceNoteParams&) override { return noErr; } + ComponentResult StopNote (MusicDeviceGroupID, NoteInstanceID, UInt32) override { return noErr; } + + //============================================================================== + OSStatus HandleMIDIEvent ([[maybe_unused]] UInt8 inStatus, + [[maybe_unused]] UInt8 inChannel, + [[maybe_unused]] UInt8 inData1, + [[maybe_unused]] UInt8 inData2, + [[maybe_unused]] UInt32 inStartFrame) override + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + const juce::uint8 data[] = { (juce::uint8) (inStatus | inChannel), + (juce::uint8) inData1, + (juce::uint8) inData2 }; + + const ScopedLock sl (incomingMidiLock); + incomingEvents.addEvent (data, 3, (int) inStartFrame); + return noErr; + #else + return kAudioUnitErr_PropertyNotInUse; + #endif + } + + OSStatus HandleSysEx ([[maybe_unused]] const UInt8* inData, [[maybe_unused]] UInt32 inLength) override + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + const ScopedLock sl (incomingMidiLock); + incomingEvents.addEvent (inData, (int) inLength, 0); + return noErr; + #else + return kAudioUnitErr_PropertyNotInUse; + #endif + } + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + OSStatus MIDIEventList (UInt32 inOffsetSampleFrame, const struct MIDIEventList* list) override + { + const ScopedLock sl (incomingMidiLock); + + auto* packet = &list->packet[0]; + + for (uint32_t i = 0; i < list->numPackets; ++i) + { + toBytestreamDispatcher.dispatch (reinterpret_cast (packet->words), + reinterpret_cast (packet->words + packet->wordCount), + static_cast (packet->timeStamp + inOffsetSampleFrame), + [this] (const ump::BytestreamMidiView& message) + { + incomingEvents.addEvent (message.getMessage(), (int) message.timestamp); + }); + + packet = MIDIEventPacketNext (packet); + } + + return noErr; + } + #endif + + //============================================================================== + ComponentResult GetPresets (CFArrayRef* outData) const override + { + if (outData != nullptr) + { + const int numPrograms = juceFilter->getNumPrograms(); + + clearPresetsArray(); + presetsArray.insertMultiple (0, AUPreset(), numPrograms); + + CFMutableArrayRef presetsArrayRef = CFArrayCreateMutable (nullptr, numPrograms, nullptr); + + for (int i = 0; i < numPrograms; ++i) + { + String name (juceFilter->getProgramName(i)); + if (name.isEmpty()) + name = "Untitled"; + + AUPreset& p = presetsArray.getReference(i); + p.presetNumber = i; + p.presetName = name.toCFString(); + + CFArrayAppendValue (presetsArrayRef, &p); + } + + *outData = (CFArrayRef) presetsArrayRef; + } + + return noErr; + } + + OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset) override + { + const int numPrograms = juceFilter->getNumPrograms(); + const SInt32 chosenPresetNumber = (int) inNewFactoryPreset.presetNumber; + + if (chosenPresetNumber >= numPrograms) + return kAudioUnitErr_InvalidProperty; + + AUPreset chosenPreset; + chosenPreset.presetNumber = chosenPresetNumber; + chosenPreset.presetName = juceFilter->getProgramName (chosenPresetNumber).toCFString(); + + juceFilter->setCurrentProgram (chosenPresetNumber); + SetAFactoryPresetAsCurrent (chosenPreset); + + return noErr; + } + + //============================================================================== + class EditorCompHolder : public Component + { + public: + EditorCompHolder (AudioProcessorEditor* const editor) + { + addAndMakeVisible (editor); + + #if ! JucePlugin_EditorRequiresKeyboardFocus + setWantsKeyboardFocus (false); + #else + setWantsKeyboardFocus (true); + #endif + + setBounds (getSizeToContainChild()); + + lastBounds = getBounds(); + } + + ~EditorCompHolder() override + { + deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may + // have been transferred to another parent which takes over ownership. + } + + Rectangle getSizeToContainChild() + { + if (auto* editor = getChildComponent (0)) + return getLocalArea (editor, editor->getLocalBounds()); + + return {}; + } + + static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor) + { + auto* editorCompHolder = new EditorCompHolder (editor); + auto r = convertToHostBounds (makeNSRect (editorCompHolder->getSizeToContainChild())); + + static JuceUIViewClass cls; + auto* view = [[cls.createInstance() initWithFrame: r] autorelease]; + + JuceUIViewClass::setFilter (view, filter); + JuceUIViewClass::setAU (view, au); + JuceUIViewClass::setEditor (view, editorCompHolder); + + [view setHidden: NO]; + [view setPostsFrameChangedNotifications: YES]; + + [[NSNotificationCenter defaultCenter] addObserver: view + selector: @selector (applicationWillTerminate:) + name: NSApplicationWillTerminateNotification + object: nil]; + activeUIs.add (view); + + editorCompHolder->addToDesktop (detail::PluginUtilities::getDesktopFlags (editor), view); + editorCompHolder->setVisible (view); + + return view; + } + + void parentSizeChanged() override + { + resizeHostWindow(); + + if (auto* editor = getChildComponent (0)) + editor->repaint(); + } + + void childBoundsChanged (Component*) override + { + auto b = getSizeToContainChild(); + + if (lastBounds != b) + { + lastBounds = b; + setSize (jmax (32, b.getWidth()), jmax (32, b.getHeight())); + + resizeHostWindow(); + } + } + + bool keyPressed (const KeyPress&) override + { + if (detail::PluginUtilities::getHostType().isAbletonLive()) + { + static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event + NSTimeInterval eventTime = [[NSApp currentEvent] timestamp]; + + if (lastEventTime != eventTime) + { + lastEventTime = eventTime; + + NSView* view = (NSView*) getWindowHandle(); + NSView* hostView = [view superview]; + NSWindow* hostWindow = [hostView window]; + + [hostWindow makeFirstResponder: hostView]; + [hostView keyDown: (NSEvent*) [NSApp currentEvent]]; + [hostWindow makeFirstResponder: view]; + } + } + + return false; + } + + void resizeHostWindow() + { + [CATransaction begin]; + [CATransaction setValue:(id) kCFBooleanTrue forKey:kCATransactionDisableActions]; + + auto rect = convertToHostBounds (makeNSRect (lastBounds)); + auto* view = (NSView*) getWindowHandle(); + + auto superRect = [[view superview] frame]; + superRect.size.width = rect.size.width; + superRect.size.height = rect.size.height; + + [[view superview] setFrame: superRect]; + [view setFrame: rect]; + [CATransaction commit]; + + [view setNeedsDisplay: YES]; + } + + private: + Rectangle lastBounds; + + JUCE_DECLARE_NON_COPYABLE (EditorCompHolder) + }; + + void deleteActiveEditors() + { + for (int i = activeUIs.size(); --i >= 0;) + { + id ui = (id) activeUIs.getUnchecked(i); + + if (JuceUIViewClass::getAU (ui) == this) + JuceUIViewClass::deleteEditor (ui); + } + } + + //============================================================================== + struct JuceUIViewClass : public ObjCClass + { + JuceUIViewClass() : ObjCClass ("JUCEAUView_") + { + addIvar ("filter"); + addIvar ("au"); + addIvar ("editor"); + + addMethod (@selector (dealloc), dealloc); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate); + addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow); + addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow); + + registerClass(); + } + + static void deleteEditor (id self) + { + std::unique_ptr editorComp (getEditor (self)); + + if (editorComp != nullptr) + { + if (editorComp->getChildComponent(0) != nullptr + && activePlugins.contains (getAU (self))) // plugin may have been deleted before the UI + { + AudioProcessor* const filter = getIvar (self, "filter"); + filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); + } + + editorComp = nullptr; + setEditor (self, nullptr); + } + } + + static JuceAU* getAU (id self) { return getIvar (self, "au"); } + static EditorCompHolder* getEditor (id self) { return getIvar (self, "editor"); } + + static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); } + static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); } + static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); } + + private: + static void dealloc (id self, SEL) + { + if (activeUIs.contains (self)) + shutdown (self); + + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void applicationWillTerminate (id self, SEL, NSNotification*) + { + shutdown (self); + } + + static void shutdown (id self) + { + [[NSNotificationCenter defaultCenter] removeObserver: self]; + deleteEditor (self); + + jassert (activeUIs.contains (self)); + activeUIs.removeFirstMatchingValue (self); + + if (activePlugins.size() + activeUIs.size() == 0) + { + // there's some kind of component currently modal, but the host + // is trying to delete our plugin.. + jassert (Component::getCurrentlyModalComponent() == nullptr); + + shutdownJuce_GUI(); + } + } + + static void viewDidMoveToWindow (id self, SEL) + { + if (NSWindow* w = [(NSView*) self window]) + { + [w setAcceptsMouseMovedEvents: YES]; + + if (EditorCompHolder* const editorComp = getEditor (self)) + [w makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; + } + } + + static BOOL mouseDownCanMoveWindow (id, SEL) + { + return NO; + } + }; + + //============================================================================== + struct JuceUICreationClass : public ObjCClass + { + JuceUICreationClass() : ObjCClass ("JUCE_AUCocoaViewClass_") + { + addMethod (@selector (interfaceVersion), interfaceVersion); + addMethod (@selector (description), description); + addMethod (@selector (uiViewForAudioUnit:withSize:), uiViewForAudioUnit); + + addProtocol (@protocol (AUCocoaUIBase)); + + registerClass(); + } + + private: + static unsigned int interfaceVersion (id, SEL) { return 0; } + + static NSString* description (id, SEL) + { + return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; + } + + static NSView* uiViewForAudioUnit (id, SEL, AudioUnit inAudioUnit, NSSize) + { + void* pointers[2]; + UInt32 propertySize = sizeof (pointers); + + if (AudioUnitGetProperty (inAudioUnit, juceFilterObjectPropertyID, + kAudioUnitScope_Global, 0, pointers, &propertySize) == noErr) + { + if (AudioProcessor* filter = static_cast (pointers[0])) + if (AudioProcessorEditor* editorComp = filter->createEditorIfNeeded()) + { + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (editorComp) != nullptr); + // for proper view embedding, ARA plug-ins must be resizable + jassert (editorComp->isResizable()); + #endif + return EditorCompHolder::createViewFor (filter, static_cast (pointers[1]), editorComp); + } + } + + return nil; + } + }; + +private: + //============================================================================== + /* The call to AUBase::PropertyChanged may allocate hence the need for this class */ + class AudioProcessorChangedUpdater final : private AsyncUpdater + { + public: + explicit AudioProcessorChangedUpdater (JuceAU& o) : owner (o) {} + ~AudioProcessorChangedUpdater() override { cancelPendingUpdate(); } + + void update (const ChangeDetails& details) + { + int flags = 0; + + if (details.latencyChanged) + flags |= latencyChangedFlag; + + if (details.parameterInfoChanged) + flags |= parameterInfoChangedFlag; + + if (details.programChanged) + flags |= programChangedFlag; + + if (flags != 0) + { + callbackFlags.fetch_or (flags); + + if (MessageManager::getInstance()->isThisTheMessageThread()) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + } + } + + private: + void handleAsyncUpdate() override + { + const auto flags = callbackFlags.exchange (0); + + if ((flags & latencyChangedFlag) != 0) + owner.PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); + + if ((flags & parameterInfoChangedFlag) != 0) + { + owner.PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); + owner.PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); + } + + owner.PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); + + if ((flags & programChangedFlag) != 0) + { + owner.refreshCurrentPreset(); + owner.PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); + } + } + + JuceAU& owner; + + static constexpr int latencyChangedFlag = 1 << 0, + parameterInfoChangedFlag = 1 << 1, + programChangedFlag = 1 << 2; + + std::atomic callbackFlags { 0 }; + }; + + //============================================================================== + AudioUnitHelpers::CoreAudioBufferList audioBuffer; + MidiBuffer midiEvents, incomingEvents; + bool prepared = false, isBypassed = false, restoringState = false; + + //============================================================================== + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + static constexpr bool forceUseLegacyParamIDs = true; + #else + static constexpr bool forceUseLegacyParamIDs = false; + #endif + + //============================================================================== + LegacyAudioParametersWrapper juceParameters; + std::unordered_map paramMap; + Array auParamIDs; + Array parameterGroups; + + // Stores the parameter IDs in the order that they will be reported to the host. + std::vector cachedParameterList; + + //============================================================================== + // According to the docs, this is the maximum size of a MIDIPacketList. + static constexpr UInt32 packetListBytes = 65536; + + CoreAudioTimeConversions timeConversions; + AudioUnitEvent auEvent; + mutable Array presetsArray; + CriticalSection incomingMidiLock; + AUMIDIOutputCallbackStruct midiCallback; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + class ScopedMIDIEventListBlock + { + public: + ScopedMIDIEventListBlock() = default; + + ScopedMIDIEventListBlock (ScopedMIDIEventListBlock&& other) noexcept + : midiEventListBlock (std::exchange (other.midiEventListBlock, nil)) {} + + ScopedMIDIEventListBlock& operator= (ScopedMIDIEventListBlock&& other) noexcept + { + ScopedMIDIEventListBlock { std::move (other) }.swap (*this); + return *this; + } + + ~ScopedMIDIEventListBlock() + { + if (midiEventListBlock != nil) + [midiEventListBlock release]; + } + + static ScopedMIDIEventListBlock copy (AUMIDIEventListBlock b) + { + return ScopedMIDIEventListBlock { b }; + } + + explicit operator bool() const { return midiEventListBlock != nil; } + + void operator() (AUEventSampleTime eventSampleTime, uint8_t cable, const struct MIDIEventList * eventList) const + { + jassert (midiEventListBlock != nil); + midiEventListBlock (eventSampleTime, cable, eventList); + } + + private: + void swap (ScopedMIDIEventListBlock& other) noexcept + { + std::swap (other.midiEventListBlock, midiEventListBlock); + } + + explicit ScopedMIDIEventListBlock (AUMIDIEventListBlock b) : midiEventListBlock ([b copy]) {} + + AUMIDIEventListBlock midiEventListBlock = nil; + }; + + ScopedMIDIEventListBlock midiEventListBlock; + std::optional hostProtocol; + ump::ToUMP1Converter toUmp1Converter; + ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 }; + #endif + + AudioTimeStamp lastTimeStamp; + int totalInChannels, totalOutChannels; + HeapBlock pulledSucceeded; + HeapBlock packetList { packetListBytes, 1 }; + + ThreadLocalValue inParameterChangedCallback; + + AudioProcessorChangedUpdater audioProcessorChangedUpdater { *this }; + + //============================================================================== + Array channelInfo; + Array> supportedInputLayouts, supportedOutputLayouts; + Array currentInputLayout, currentOutputLayout; + + //============================================================================== + AudioUnitHelpers::ChannelRemapper mapper; + + //============================================================================== + OwnedArray> parameterValueStringArrays; + + //============================================================================== + AudioProcessorParameter* bypassParam = nullptr; + + //============================================================================== + static NSRect convertToHostBounds (NSRect pluginRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return pluginRect; + + return NSMakeRect (static_cast (pluginRect.origin.x * desktopScale), + static_cast (pluginRect.origin.y * desktopScale), + static_cast (pluginRect.size.width * desktopScale), + static_cast (pluginRect.size.height * desktopScale)); + } + + static NSRect convertFromHostBounds (NSRect hostRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return hostRect; + + return NSMakeRect (static_cast (hostRect.origin.x / desktopScale), + static_cast (hostRect.origin.y / desktopScale), + static_cast (hostRect.size.width / desktopScale), + static_cast (hostRect.size.height / desktopScale)); + } + + //============================================================================== + void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept + { + const unsigned int numInputBuses = GetScope (kAudioUnitScope_Input).GetNumberOfElements(); + + for (unsigned int i = 0; i < numInputBuses; ++i) + { + auto& input = Input (i); + + const bool succeeded = (input.PullInput (flags, timestamp, i, nFrames) == noErr); + + if ((flags & kAudioUnitRenderAction_OutputIsSilence) != 0 && succeeded) + AudioUnitHelpers::clearAudioBuffer (input.GetBufferList()); + + pulledSucceeded[i] = succeeded; + } + } + + void prepareOutputBuffers (const UInt32 nFrames) noexcept + { + const auto numProcessorBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + const auto numWrapperBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements(); + + for (UInt32 busIdx = 0; busIdx < numWrapperBuses; ++busIdx) + { + auto& output = Output (busIdx); + + if (output.WillAllocateBuffer()) + output.PrepareBuffer (nFrames); + + if (busIdx >= (UInt32) numProcessorBuses) + AudioUnitHelpers::clearAudioBuffer (output.GetBufferList()); + } + } + + void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept + { + const ScopedLock sl (juceFilter->getCallbackLock()); + const ScopedPlayHead playhead { *this }; + + if (juceFilter->isSuspended()) + { + buffer.clear(); + } + else if (bypassParam == nullptr && isBypassed) + { + juceFilter->processBlockBypassed (buffer, midiBuffer); + } + else + { + juceFilter->processBlock (buffer, midiBuffer); + } + } + + void pushMidiOutput ([[maybe_unused]] UInt32 nFrames) noexcept + { + if (midiEvents.isEmpty()) + return; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + if (@available (macOS 12.0, iOS 15.0, *)) + { + if (midiEventListBlock) + { + struct MIDIEventList stackList = {}; + MIDIEventPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0); + }; + + const auto send = [&] + { + midiEventListBlock (static_cast (lastTimeStamp.mSampleTime), 0, &stackList); + }; + + const auto add = [&] (const ump::View& view, int timeStamp) + { + static_assert (sizeof (uint32_t) == sizeof (UInt32) + && alignof (uint32_t) == alignof (UInt32), + "If this fails, the cast below will be broken too!"); + using List = struct MIDIEventList; + end = MIDIEventListAdd (&stackList, + sizeof (List::packet), + end, + (MIDITimeStamp) timeStamp, + view.size(), + reinterpret_cast (view.data())); + }; + + init(); + + for (const auto metadata : midiEvents) + { + toUmp1Converter.convert (ump::BytestreamMidiView (metadata), [&] (const ump::View& view) + { + add (view, metadata.samplePosition); + + if (end != nullptr) + return; + + send(); + init(); + add (view, metadata.samplePosition); + }); + + } + + send(); + + return; + } + } + #endif + + if (midiCallback.midiOutputCallback) + { + MIDIPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIPacketListInit (packetList); + }; + + const auto send = [&] + { + midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); + }; + + const auto add = [&] (const MidiMessageMetadata& metadata) + { + end = MIDIPacketListAdd (packetList, + packetListBytes, + end, + static_cast (metadata.samplePosition), + static_cast (metadata.numBytes), + metadata.data); + }; + + init(); + + for (const auto metadata : midiEvents) + { + jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); + + add (metadata); + + if (end == nullptr) + { + send(); + init(); + add (metadata); + + if (end == nullptr) + { + // If this is hit, the size of this midi packet exceeds the maximum size of + // a MIDIPacketList. Large SysEx messages should be broken up into smaller + // chunks. + jassertfalse; + init(); + } + } + } + + send(); + } + } + + void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) + { + auto* element = Element (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, static_cast (busIdx)).AsIOElement(); + jassert (element != nullptr); + + bufferList = &element->GetBufferList(); + + jassert (bufferList->mNumberBuffers > 0); + + interleaved = AudioUnitHelpers::isAudioBufferInterleaved (*bufferList); + numChannels = static_cast (interleaved ? bufferList->mBuffers[0].mNumberChannels : bufferList->mNumberBuffers); + } + + //============================================================================== + static OSStatus scopeToDirection (AudioUnitScope scope, bool& isInput) noexcept + { + isInput = (scope == kAudioUnitScope_Input); + + return (scope != kAudioUnitScope_Input + && scope != kAudioUnitScope_Output) + ? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; + } + + enum class BusKind + { + processor, + wrapperOnly, + }; + + struct ElementInfo + { + int busNr; + BusKind kind; + bool isInput; + OSStatus error; + }; + + ElementInfo getElementInfo (AudioUnitScope scope, AudioUnitElement element) noexcept + { + bool isInput = false; + OSStatus err; + + if ((err = scopeToDirection (scope, isInput)) != noErr) + return { {}, {}, {}, err }; + + const auto busIdx = static_cast (element); + + if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (*juceFilter, isInput))) + return { busIdx, BusKind::processor, isInput, noErr }; + + if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCountForWrapper (*juceFilter, isInput))) + return { busIdx, BusKind::wrapperOnly, isInput, noErr }; + + return { {}, {}, {}, kAudioUnitErr_InvalidElement }; + } + + OSStatus GetParameterList (AudioUnitScope inScope, AudioUnitParameterID* outParameterList, UInt32& outNumParameters) override + { + if (forceUseLegacyParamIDs || inScope != kAudioUnitScope_Global) + return MusicDeviceBase::GetParameterList (inScope, outParameterList, outNumParameters); + + outNumParameters = (UInt32) juceParameters.size(); + + if (outParameterList == nullptr) + return noErr; + + if (cachedParameterList.empty()) + { + struct ParamInfo + { + AudioUnitParameterID identifier; + int versionHint; + }; + + std::vector vec; + vec.reserve (juceParameters.size()); + + for (const auto* param : juceParameters) + vec.push_back ({ generateAUParameterID (*param), param->getVersionHint() }); + + std::sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.identifier < b.identifier; }); + std::stable_sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.versionHint < b.versionHint; }); + std::transform (vec.begin(), vec.end(), std::back_inserter (cachedParameterList), [] (auto x) { return x.identifier; }); + } + + std::copy (cachedParameterList.begin(), cachedParameterList.end(), outParameterList); + + return noErr; + } + + //============================================================================== + void addParameters() + { + parameterGroups = juceFilter->getParameterTree().getSubgroups (true); + + juceParameters.update (*juceFilter, forceUseLegacyParamIDs); + const int numParams = juceParameters.getNumParameters(); + + if (forceUseLegacyParamIDs) + { + Globals()->UseIndexedParameters (static_cast (numParams)); + } + else + { + for (auto* param : juceParameters) + { + const AudioUnitParameterID auParamID = generateAUParameterID (*param); + + // Consider yourself very unlucky if you hit this assertion. The hash codes of your + // parameter ids are not unique. + jassert (paramMap.find (static_cast (auParamID)) == paramMap.end()); + + auParamIDs.add (auParamID); + paramMap.emplace (static_cast (auParamID), param); + Globals()->SetParameter (auParamID, param->getValue()); + } + } + + #if JUCE_DEBUG + // Some hosts can't handle the huge numbers of discrete parameter values created when + // using the default number of steps. + for (auto* param : juceParameters) + if (param->isDiscrete()) + jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); + #endif + + parameterValueStringArrays.ensureStorageAllocated (numParams); + + for (auto* param : juceParameters) + { + OwnedArray* stringValues = nullptr; + + auto initialValue = param->getValue(); + bool paramIsLegacy = dynamic_cast (param) != nullptr; + + if (param->isDiscrete() && (! forceUseLegacyParamIDs)) + { + const auto numSteps = param->getNumSteps(); + stringValues = new OwnedArray(); + stringValues->ensureStorageAllocated (numSteps); + + const auto maxValue = getMaximumParameterValue (param); + + auto getTextValue = [param, paramIsLegacy] (float value) + { + if (paramIsLegacy) + { + param->setValue (value); + return param->getCurrentValueAsText(); + } + + return param->getText (value, 256); + }; + + for (int i = 0; i < numSteps; ++i) + { + auto value = (float) i / maxValue; + stringValues->add (CFStringCreateCopy (nullptr, (getTextValue (value).toCFString()))); + } + } + + if (paramIsLegacy) + param->setValue (initialValue); + + parameterValueStringArrays.add (stringValues); + } + + if ((bypassParam = juceFilter->getBypassParameter()) != nullptr) + bypassParam->addListener (this); + } + + //============================================================================== + static AudioUnitParameterID generateAUParameterID (const AudioProcessorParameter& param) + { + const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceUseLegacyParamIDs); + AudioUnitParameterID paramHash = static_cast (juceParamID.hashCode()); + + #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS + // studio one doesn't like negative parameters + paramHash &= ~(((AudioUnitParameterID) 1) << (sizeof (AudioUnitParameterID) * 8 - 1)); + #endif + + return forceUseLegacyParamIDs ? static_cast (juceParamID.getIntValue()) + : paramHash; + } + + inline AudioUnitParameterID getAUParameterIDForIndex (int paramIndex) const noexcept + { + return forceUseLegacyParamIDs ? static_cast (paramIndex) + : auParamIDs.getReference (paramIndex); + } + + AudioProcessorParameter* getParameterForAUParameterID (AudioUnitParameterID address) const noexcept + { + const auto index = static_cast (address); + + if (forceUseLegacyParamIDs) + return juceParameters.getParamForIndex (index); + + const auto iter = paramMap.find (index); + return iter != paramMap.end() ? iter->second : nullptr; + } + + //============================================================================== + OSStatus syncAudioUnitWithProcessor() + { + OSStatus err = noErr; + const auto numWrapperInputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true); + const auto numWrapperOutputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false); + + if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast (numWrapperInputs))) != noErr) + return err; + + if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast (numWrapperOutputs))) != noErr) + return err; + + addSupportedLayoutTags(); + + const auto numProcessorInputs = AudioUnitHelpers::getBusCount (*juceFilter, true); + const auto numProcessorOutputs = AudioUnitHelpers::getBusCount (*juceFilter, false); + + for (int i = 0; i < numProcessorInputs; ++i) + if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) + return err; + + for (int i = 0; i < numProcessorOutputs; ++i) + if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) + return err; + + return noErr; + } + + OSStatus syncProcessorWithAudioUnit() + { + const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + + const int numInputElements = static_cast (GetScope (kAudioUnitScope_Input). GetNumberOfElements()); + const int numOutputElements = static_cast (GetScope (kAudioUnitScope_Output).GetNumberOfElements()); + + AudioProcessor::BusesLayout requestedLayouts; + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = (isInput ? numInputBuses : numOutputBuses); + const int numAUElements = (isInput ? numInputElements : numOutputElements); + Array& requestedBuses = (isInput ? requestedLayouts.inputBuses : requestedLayouts.outputBuses); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + const auto* element = (busIdx < numAUElements ? &IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busIdx) : nullptr); + const int numChannels = (element != nullptr ? static_cast (element->NumberChannels()) : 0); + + AudioChannelLayoutTag currentLayoutTag = isInput ? currentInputLayout[busIdx] : currentOutputLayout[busIdx]; + const int tagNumChannels = currentLayoutTag & 0xffff; + + if (numChannels != tagNumChannels) + return kAudioUnitErr_FormatNotSupported; + + requestedBuses.add (CoreAudioLayouts::fromCoreAudio (currentLayoutTag)); + } + } + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioProcessor::containsLayout (requestedLayouts, configs)) + return kAudioUnitErr_FormatNotSupported; + #endif + + if (! AudioUnitHelpers::setBusesLayout (juceFilter.get(), requestedLayouts)) + return kAudioUnitErr_FormatNotSupported; + + // update total channel count + totalInChannels = juceFilter->getTotalNumInputChannels(); + totalOutChannels = juceFilter->getTotalNumOutputChannels(); + + return noErr; + } + + OSStatus syncAudioUnitWithChannelSet (bool isInput, int busNr, const AudioChannelSet& channelSet) + { + const int numChannels = channelSet.size(); + + getCurrentLayout (isInput, busNr) = CoreAudioLayouts::toCoreAudio (channelSet); + + // is this bus activated? + if (numChannels == 0) + return noErr; + + auto& element = IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busNr); + + element.SetName ((CFStringRef) juceStringToNS (juceFilter->getBus (isInput, busNr)->getName())); + + const auto streamDescription = ausdk::ASBD::CreateCommonFloat32 (getSampleRate(), (UInt32) numChannels); + return element.SetStreamFormat (streamDescription); + } + + //============================================================================== + void clearPresetsArray() const + { + for (int i = presetsArray.size(); --i >= 0;) + CFRelease (presetsArray.getReference(i).presetName); + + presetsArray.clear(); + } + + void refreshCurrentPreset() + { + // this will make the AU host re-read and update the current preset name + // in case it was changed here in the plug-in: + + const int currentProgramNumber = juceFilter->getCurrentProgram(); + const String currentProgramName = juceFilter->getProgramName (currentProgramNumber); + + AUPreset currentPreset; + currentPreset.presetNumber = currentProgramNumber; + currentPreset.presetName = currentProgramName.toCFString(); + + SetAFactoryPresetAsCurrent (currentPreset); + } + + //============================================================================== + std::vector& getSupportedBusLayouts (bool isInput, int bus) noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } + const std::vector& getSupportedBusLayouts (bool isInput, int bus) const noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } + AudioChannelLayoutTag& getCurrentLayout (bool isInput, int bus) noexcept { return (isInput ? currentInputLayout : currentOutputLayout).getReference (bus); } + AudioChannelLayoutTag getCurrentLayout (bool isInput, int bus) const noexcept { return (isInput ? currentInputLayout : currentOutputLayout)[bus]; } + + //============================================================================== + std::vector getSupportedLayoutTagsForBus (bool isInput, int busNum) const + { + std::set tags; + + if (AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNum)) + { + #ifndef JucePlugin_PreferredChannelConfigurations + auto& knownTags = CoreAudioLayouts::getKnownCoreAudioTags(); + + for (auto tag : knownTags) + if (bus->isLayoutSupported (CoreAudioLayouts::fromCoreAudio (tag))) + tags.insert (tag); + #endif + + // add discrete layout tags + int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); + + for (int ch = 0; ch < n; ++ch) + { + #ifdef JucePlugin_PreferredChannelConfigurations + const short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + if (AudioUnitHelpers::isLayoutSupported (*juceFilter, isInput, busNum, ch, configs)) + tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); + #else + if (bus->isLayoutSupported (AudioChannelSet::discreteChannels (ch))) + tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); + #endif + } + } + + return std::vector (tags.begin(), tags.end()); + } + + void addSupportedLayoutTagsForDirection (bool isInput) + { + auto& layouts = isInput ? supportedInputLayouts : supportedOutputLayouts; + layouts.clearQuick(); + auto numBuses = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + + for (int busNr = 0; busNr < numBuses; ++busNr) + layouts.add (getSupportedLayoutTagsForBus (isInput, busNr)); + } + + void addSupportedLayoutTags() + { + currentInputLayout.clear(); currentOutputLayout.clear(); + + currentInputLayout. resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true)); + currentOutputLayout.resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)); + + addSupportedLayoutTagsForDirection (true); + addSupportedLayoutTagsForDirection (false); + } + + static int maxChannelsToProbeFor() + { + return (detail::PluginUtilities::getHostType().isLogic() ? 8 : 64); + } + + //============================================================================== + void auPropertyListener (AudioUnitPropertyID propId, AudioUnitScope scope, AudioUnitElement) + { + if (scope == kAudioUnitScope_Global && propId == kAudioUnitProperty_ContextName + && juceFilter != nullptr && GetContextName() != nullptr) + { + AudioProcessor::TrackProperties props; + props.name = String::fromCFString (GetContextName()); + + juceFilter->updateTrackProperties (props); + } + } + + static void auPropertyListenerDispatcher (void* inRefCon, AudioUnit, AudioUnitPropertyID propId, + AudioUnitScope scope, AudioUnitElement element) + { + static_cast (inRefCon)->auPropertyListener (propId, scope, element); + } + + JUCE_DECLARE_NON_COPYABLE (JuceAU) +}; + +//============================================================================== +#if JucePlugin_ProducesMidiOutput || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + #define FACTORY_BASE_CLASS ausdk::AUMusicDeviceFactory +#else + #define FACTORY_BASE_CLASS ausdk::AUBaseFactory +#endif + +AUSDK_COMPONENT_ENTRY (FACTORY_BASE_CLASS, JuceAU) + +#define JUCE_AU_ENTRY_POINT_NAME JUCE_CONCAT (JucePlugin_AUExportPrefix, Factory) + + extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc); +AUSDK_EXPORT extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc) +{ + return JuceAUFactory (inDesc); +} + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm index 1f81ee0ab1..ef4ff9dea3 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm @@ -86,18 +86,18 @@ enum MIDICVStatus : unsigned int #endif -#include "AU/AudioUnitSDK/AUBase.cpp" -#include "AU/AudioUnitSDK/AUBuffer.cpp" -#include "AU/AudioUnitSDK/AUBufferAllocator.cpp" -#include "AU/AudioUnitSDK/AUEffectBase.cpp" -#include "AU/AudioUnitSDK/AUInputElement.cpp" -#include "AU/AudioUnitSDK/AUMIDIBase.cpp" -#include "AU/AudioUnitSDK/AUMIDIEffectBase.cpp" -#include "AU/AudioUnitSDK/AUOutputElement.cpp" -#include "AU/AudioUnitSDK/AUPlugInDispatch.cpp" -#include "AU/AudioUnitSDK/AUScopeElement.cpp" -#include "AU/AudioUnitSDK/ComponentBase.cpp" -#include "AU/AudioUnitSDK/MusicDeviceBase.cpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #undef verify #undef verify_noerr diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm index ec742fff7f..fef366b52f 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm @@ -23,4 +23,1991 @@ ============================================================================== */ -#include "AU/juce_AUv3_Wrapper.mm" +#include +#include + +#if JucePlugin_Build_AUv3 + +#if JUCE_MAC && ! (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + #error AUv3 needs Deployment Target OS X 10.11 or higher to compile +#endif + +#ifndef __OBJC2__ + #error AUv3 needs Objective-C 2 support (compile with 64-bit) +#endif + +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 + +#include +#include + +#import +#import +#import + +#include +#include +#include +#include +#include + +#define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) + +#if JUCE_IOS + #define JUCE_IOS_MAC_VIEW UIView +#else + #define JUCE_IOS_MAC_VIEW NSView +#endif + +#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) + +#include + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") + +using namespace juce; + +struct AudioProcessorHolder : public ReferenceCountedObject +{ + AudioProcessorHolder() = default; + explicit AudioProcessorHolder (std::unique_ptr p) : processor (std::move (p)) {} + AudioProcessor& operator*() noexcept { return *processor; } + AudioProcessor* operator->() noexcept { return processor.get(); } + AudioProcessor* get() noexcept { return processor.get(); } + + struct ViewConfig + { + double width; + double height; + bool hostHasMIDIController; + }; + + std::unique_ptr viewConfiguration; + + using Ptr = ReferenceCountedObjectPtr; + +private: + std::unique_ptr processor; + + AudioProcessorHolder& operator= (AudioProcessor*) = delete; + AudioProcessorHolder (AudioProcessorHolder&) = delete; + AudioProcessorHolder& operator= (AudioProcessorHolder&) = delete; +}; + +//============================================================================== +//=========================== The actual AudioUnit ============================= +//============================================================================== +class JuceAudioUnitv3 : public AudioProcessorListener, + public AudioPlayHead, + private AudioProcessorParameter::Listener +{ +public: + JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, + const AudioComponentDescription& descr, + AudioComponentInstantiationOptions options, + NSError** error) + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wobjc-method-access") + : au ([getClass().createInstance() initWithComponentDescription: descr + options: options + error: error + juceClass: this]), + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + processorHolder (processor) + { + init(); + } + + JuceAudioUnitv3 (AUAudioUnit* audioUnit, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**) + : au (audioUnit), + processorHolder (new AudioProcessorHolder (createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))) + { + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + initialiseJuce_GUI(); + + init(); + } + + ~JuceAudioUnitv3() override + { + auto& processor = getAudioProcessor(); + processor.removeListener (this); + + if (bypassParam != nullptr) + bypassParam->removeListener (this); + + removeEditor (processor); + } + + //============================================================================== + void init() + { + inParameterChangedCallback = false; + + AudioProcessor& processor = getAudioProcessor(); + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + processor.setPlayConfigDetails (configs[0][0], configs[0][1], kDefaultSampleRate, static_cast (maxFrames)); + + Array channelInfos; + + for (int i = 0; i < numConfigs; ++i) + { + AUChannelInfo channelInfo; + + channelInfo.inChannels = configs[i][0]; + channelInfo.outChannels = configs[i][1]; + + channelInfos.add (channelInfo); + } + #else + Array channelInfos = AudioUnitHelpers::getAUChannelInfo (processor); + #endif + + processor.setPlayHead (this); + + totalInChannels = processor.getTotalNumInputChannels(); + totalOutChannels = processor.getTotalNumOutputChannels(); + + { + channelCapabilities.reset ([[NSMutableArray alloc] init]); + + for (int i = 0; i < channelInfos.size(); ++i) + { + AUChannelInfo& info = channelInfos.getReference (i); + + [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.inChannels]]; + [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.outChannels]]; + } + } + + internalRenderBlock = CreateObjCBlock (this, &JuceAudioUnitv3::renderCallback); + + processor.setRateAndBufferSizeDetails (kDefaultSampleRate, static_cast (maxFrames)); + processor.prepareToPlay (kDefaultSampleRate, static_cast (maxFrames)); + processor.addListener (this); + + addParameters(); + addPresets(); + + addAudioUnitBusses (true); + addAudioUnitBusses (false); + } + + AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } + + //============================================================================== + void reset() + { + midiMessages.clear(); + lastTimeStamp.mSampleTime = std::numeric_limits::max(); + lastTimeStamp.mFlags = 0; + } + + //============================================================================== + AUAudioUnitPreset* getCurrentPreset() const + { + return factoryPresets.getAtIndex (getAudioProcessor().getCurrentProgram()); + } + + void setCurrentPreset (AUAudioUnitPreset* preset) + { + getAudioProcessor().setCurrentProgram (static_cast ([preset number])); + } + + NSArray* getFactoryPresets() const + { + return factoryPresets.get(); + } + + NSDictionary* getFullState() const + { + NSMutableDictionary* retval = [[NSMutableDictionary alloc] init]; + + { + auto* superRetval = ObjCMsgSendSuper*> (au, @selector (fullState)); + + if (superRetval != nullptr) + [retval addEntriesFromDictionary:superRetval]; + } + + juce::MemoryBlock state; + + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + getAudioProcessor().getCurrentProgramStateInformation (state); + #else + getAudioProcessor().getStateInformation (state); + #endif + + if (state.getSize() > 0) + { + NSData* ourState = [[NSData alloc] initWithBytes: state.getData() + length: state.getSize()]; + + NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; + + [retval setObject: ourState + forKey: nsKey]; + + [nsKey release]; + [ourState release]; + } + + return [retval autorelease]; + } + + void setFullState (NSDictionary* state) + { + if (state == nullptr) + return; + + NSMutableDictionary* modifiedState = [[NSMutableDictionary alloc] init]; + [modifiedState addEntriesFromDictionary: state]; + + NSString* nsPresetKey = [[NSString alloc] initWithUTF8String: kAUPresetDataKey]; + [modifiedState removeObjectForKey: nsPresetKey]; + [nsPresetKey release]; + + ObjCMsgSendSuper (au, @selector (setFullState:), state); + + NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; + NSObject* obj = [modifiedState objectForKey: nsKey]; + [nsKey release]; + + if (obj != nullptr) + { + if ([obj isKindOfClass:[NSData class]]) + { + NSData* data = reinterpret_cast (obj); + const int numBytes = static_cast ([data length]); + const juce::uint8* const rawBytes = reinterpret_cast< const juce::uint8* const> ([data bytes]); + + if (numBytes > 0) + { + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + getAudioProcessor().setCurrentProgramStateInformation (rawBytes, numBytes); + #else + getAudioProcessor().setStateInformation (rawBytes, numBytes); + #endif + } + } + } + + [modifiedState release]; + } + + AUParameterTree* getParameterTree() const + { + return paramTree.get(); + } + + NSArray* parametersForOverviewWithCount (int count) const + { + auto* retval = [[[NSMutableArray alloc] init] autorelease]; + + for (const auto& address : addressForIndex) + { + if (static_cast (count) <= [retval count]) + break; + + [retval addObject: [NSNumber numberWithUnsignedLongLong: address]]; + } + + return retval; + } + + //============================================================================== + NSTimeInterval getLatency() const + { + auto& p = getAudioProcessor(); + return p.getLatencySamples() / p.getSampleRate(); + } + + NSTimeInterval getTailTime() const + { + return getAudioProcessor().getTailLengthSeconds(); + } + + //============================================================================== + AUAudioUnitBusArray* getInputBusses() const { return inputBusses.get(); } + AUAudioUnitBusArray* getOutputBusses() const { return outputBusses.get(); } + NSArray* getChannelCapabilities() const { return channelCapabilities.get(); } + + bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) + { + const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); + const int busIdx = static_cast ([auBus index]); + const int newNumChannels = static_cast ([format channelCount]); + + AudioProcessor& processor = getAudioProcessor(); + + if ([[maybe_unused]] AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) + { + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) + return false; + #else + const AVAudioChannelLayout* layout = [format channelLayout]; + const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); + + if (layoutTag != 0) + { + AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); + + if (newLayout.size() != newNumChannels) + return false; + + if (! bus->isLayoutSupported (newLayout)) + return false; + } + else + { + if (! bus->isNumberOfChannelsSupported (newNumChannels)) + return false; + } + #endif + + return true; + } + + return false; + } + + //============================================================================== + int getVirtualMIDICableCount() const + { + #if JucePlugin_WantsMidiInput + return 1; + #else + return 0; + #endif + } + + bool getSupportsMPE() const + { + return getAudioProcessor().supportsMPE(); + } + + NSArray* getMIDIOutputNames() const + { + #if JucePlugin_ProducesMidiOutput + return @[@"MIDI Out"]; + #else + return @[]; + #endif + } + + //============================================================================== + AUInternalRenderBlock getInternalRenderBlock() const { return internalRenderBlock; } + bool getRenderingOffline() const { return getAudioProcessor().isNonRealtime(); } + void setRenderingOffline (bool offline) + { + auto& processor = getAudioProcessor(); + auto isCurrentlyNonRealtime = processor.isNonRealtime(); + + if (isCurrentlyNonRealtime != offline) + { + ScopedLock callbackLock (processor.getCallbackLock()); + + processor.setNonRealtime (offline); + processor.prepareToPlay (processor.getSampleRate(), processor.getBlockSize()); + } + } + + bool getShouldBypassEffect() const + { + if (bypassParam != nullptr) + return (bypassParam->getValue() != 0.0f); + + return (ObjCMsgSendSuper (au, @selector (shouldBypassEffect)) == YES); + } + + void setShouldBypassEffect (bool shouldBypass) + { + if (bypassParam != nullptr) + bypassParam->setValue (shouldBypass ? 1.0f : 0.0f); + + ObjCMsgSendSuper (au, @selector (setShouldBypassEffect:), shouldBypass ? YES : NO); + } + + //============================================================================== + NSString* getContextName() const { return juceStringToNS (contextName); } + void setContextName (NSString* str) + { + if (str != nullptr) + { + AudioProcessor::TrackProperties props; + props.name = nsStringToJuce (str); + + getAudioProcessor().updateTrackProperties (props); + } + } + + //============================================================================== + bool allocateRenderResourcesAndReturnError (NSError **outError) + { + AudioProcessor& processor = getAudioProcessor(); + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + if (ObjCMsgSendSuper (au, @selector (allocateRenderResourcesAndReturnError:), outError) == NO) + return false; + + if (outError != nullptr) + *outError = nullptr; + + AudioProcessor::BusesLayout layouts; + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); + Array& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses); + + AUAudioUnitBusArray* auBuses = (isInput ? [au inputBusses] : [au outputBusses]); + jassert ([auBuses count] == static_cast (n)); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) + { + AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast (busIdx)] format]; + + AudioChannelSet newLayout; + const AVAudioChannelLayout* layout = [format channelLayout]; + const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); + + if (layoutTag != 0) + newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); + else + newLayout = bus->supportedLayoutWithChannels (static_cast ([format channelCount])); + + if (newLayout.isDisabled()) + return false; + + channelSets.add (newLayout); + } + } + } + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioProcessor::containsLayout (layouts, configs)) + { + if (outError != nullptr) + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; + + return false; + } + #endif + + if (! AudioUnitHelpers::setBusesLayout (&getAudioProcessor(), layouts)) + { + if (outError != nullptr) + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; + + return false; + } + + totalInChannels = processor.getTotalNumInputChannels(); + totalOutChannels = processor.getTotalNumOutputChannels(); + + allocateBusBuffer (true); + allocateBusBuffer (false); + + mapper.alloc (processor); + + audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (&processor), static_cast (maxFrames)); + + auto sampleRate = [&] + { + for (auto* buffer : { inputBusses.get(), outputBusses.get() }) + if ([buffer count] > 0) + return [[[buffer objectAtIndexedSubscript: 0] format] sampleRate]; + + return 44100.0; + }(); + + processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); + processor.prepareToPlay (sampleRate, static_cast (maxFrames)); + + midiMessages.ensureSize (2048); + midiMessages.clear(); + + hostMusicalContextCallback = [au musicalContextBlock]; + hostTransportStateCallback = [au transportStateBlock]; + + if (@available (macOS 10.13, iOS 11.0, *)) + midiOutputEventBlock = [au MIDIOutputEventBlock]; + + reset(); + + return true; + } + + void deallocateRenderResources() + { + midiOutputEventBlock = nullptr; + + hostMusicalContextCallback = nullptr; + hostTransportStateCallback = nullptr; + + getAudioProcessor().releaseResources(); + audioBuffer.release(); + + inBusBuffers. clear(); + outBusBuffers.clear(); + + mapper.release(); + + ObjCMsgSendSuper (au, @selector (deallocateRenderResources)); + } + + //============================================================================== + struct ScopedKeyChange + { + ScopedKeyChange (AUAudioUnit* a, NSString* k) + : au (a), key (k) + { + [au willChangeValueForKey: key]; + } + + ~ScopedKeyChange() + { + [au didChangeValueForKey: key]; + } + + AUAudioUnit* au; + NSString* key; + }; + + //============================================================================== + void audioProcessorChanged ([[maybe_unused]] AudioProcessor* processor, const ChangeDetails& details) override + { + if (details.programChanged) + { + { + ScopedKeyChange scope (au, @"allParameterValues"); + addPresets(); + } + + { + ScopedKeyChange scope (au, @"currentPreset"); + } + } + + if (details.latencyChanged) + { + ScopedKeyChange scope (au, @"latency"); + } + + if (details.parameterInfoChanged) + { + ScopedKeyChange scope (au, @"parameterTree"); + auto nodes = createParameterNodes (processor->getParameterTree()); + installNewParameterTree (std::move (nodes.nodeArray)); + } + } + + void sendParameterEvent (int idx, const float* newValue, AUParameterAutomationEventType type) + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (auto* juceParam = juceParameters.getParamForIndex (idx)) + { + if (auto* param = [paramTree.get() parameterWithAddress: getAUParameterAddressForIndex (idx)]) + { + const auto value = (newValue != nullptr ? *newValue : juceParam->getValue()) * getMaximumParameterValue (*juceParam); + + if (@available (macOS 10.12, iOS 10.0, *)) + { + [param setValue: value + originator: editorObserverToken.get() + atHostTime: lastTimeStamp.mHostTime + eventType: type]; + } + else if (type == AUParameterAutomationEventTypeValue) + { + [param setValue: value originator: editorObserverToken.get()]; + } + } + } + } + + void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override + { + sendParameterEvent (idx, &newValue, AUParameterAutomationEventTypeValue); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int idx) override + { + sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeTouch); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int idx) override + { + sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeRelease); + } + + //============================================================================== + Optional getPosition() const override + { + PositionInfo info; + info.setTimeInSamples ((int64) (lastTimeStamp.mSampleTime + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); + + info.setFrameRate ([this] + { + switch (lastTimeStamp.mSMPTETime.mType) + { + case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); + case kSMPTETimeType24: return FrameRate().withBaseRate (24); + case kSMPTETimeType25: return FrameRate().withBaseRate (25); + case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); + case kSMPTETimeType30: return FrameRate().withBaseRate (30); + case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); + case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + case kSMPTETimeType60: return FrameRate().withBaseRate (60); + case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); + case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); + case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + case kSMPTETimeType50: return FrameRate().withBaseRate (50); + default: break; + } + + return FrameRate(); + }()); + + double num; + NSInteger den; + NSInteger outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat, bpm; + double ppqPosition; + + if (hostMusicalContextCallback != nullptr) + { + AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback; + + if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat)) + { + info.setTimeSignature (TimeSignature { (int) num, (int) den }); + info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); + info.setBpm (bpm); + info.setPpqPosition (ppqPosition); + } + } + + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; + AUHostTransportStateFlags flags; + + if (hostTransportStateCallback != nullptr) + { + AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback; + + if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) + { + info.setTimeInSamples ((int64) (outCurrentSampleInTimeLine + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); + info.setIsPlaying ((flags & AUHostTransportStateMoving) != 0); + info.setIsLooping ((flags & AUHostTransportStateCycling) != 0); + info.setIsRecording ((flags & AUHostTransportStateRecording) != 0); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); + } + } + + if ((lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) + info.setHostTimeNs (timeConversions.hostTimeToNanos (lastTimeStamp.mHostTime)); + + return info; + } + + //============================================================================== + static void removeEditor (AudioProcessor& processor) + { + ScopedLock editorLock (processor.getCallbackLock()); + + if (AudioProcessorEditor* editor = processor.getActiveEditor()) + { + processor.editorBeingDeleted (editor); + delete editor; + } + } + + AUAudioUnit* getAudioUnit() const { return au; } + +private: + struct Class : public ObjCClass + { + Class() : ObjCClass ("AUAudioUnit_") + { + addIvar ("cppObject"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (initWithComponentDescription:options:error:juceClass:), [] (id _self, + SEL, + AudioComponentDescription descr, + AudioComponentInstantiationOptions options, + NSError** error, + JuceAudioUnitv3* juceAU) + { + AUAudioUnit* self = _self; + + self = ObjCMsgSendSuper (self, @selector(initWithComponentDescription:options:error:), descr, options, error); + + setThis (self, juceAU); + return self; + }); + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + addMethod (@selector (initWithComponentDescription:options:error:), [] (id _self, + SEL, + AudioComponentDescription descr, + AudioComponentInstantiationOptions options, + NSError** error) + { + AUAudioUnit* self = _self; + + self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); + + auto* juceAU = JuceAudioUnitv3::create (self, descr, options, error); + + setThis (self, juceAU); + return self; + }); + + addMethod (@selector (dealloc), [] (id self, SEL) + { + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + WaitableEvent deletionEvent; + + struct AUDeleter : public CallbackMessage + { + AUDeleter (id selfToDelete, WaitableEvent& event) + : parentSelf (selfToDelete), parentDeletionEvent (event) + { + } + + void messageCallback() override + { + delete _this (parentSelf); + parentDeletionEvent.signal(); + } + + id parentSelf; + WaitableEvent& parentDeletionEvent; + }; + + (new AUDeleter (self, deletionEvent))->post(); + deletionEvent.wait (-1); + } + else + { + delete _this (self); + } + }); + + //============================================================================== + addMethod (@selector (reset), [] (id self, SEL) { return _this (self)->reset(); }); + + //============================================================================== + addMethod (@selector (currentPreset), [] (id self, SEL) { return _this (self)->getCurrentPreset(); }); + addMethod (@selector (setCurrentPreset:), [] (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); }); + addMethod (@selector (factoryPresets), [] (id self, SEL) { return _this (self)->getFactoryPresets(); }); + addMethod (@selector (fullState), [] (id self, SEL) { return _this (self)->getFullState(); }); + addMethod (@selector (setFullState:), [] (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); }); + addMethod (@selector (parameterTree), [] (id self, SEL) { return _this (self)->getParameterTree(); }); + addMethod (@selector (parametersForOverviewWithCount:), [] (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); }); + + //============================================================================== + addMethod (@selector (latency), [] (id self, SEL) { return _this (self)->getLatency(); }); + addMethod (@selector (tailTime), [] (id self, SEL) { return _this (self)->getTailTime(); }); + + //============================================================================== + addMethod (@selector (inputBusses), [] (id self, SEL) { return _this (self)->getInputBusses(); }); + addMethod (@selector (outputBusses), [] (id self, SEL) { return _this (self)->getOutputBusses(); }); + addMethod (@selector (channelCapabilities), [] (id self, SEL) { return _this (self)->getChannelCapabilities(); }); + addMethod (@selector (shouldChangeToFormat:forBus:), [] (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; }); + + //============================================================================== + addMethod (@selector (virtualMIDICableCount), [] (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); }); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (supportsMPE), [] (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; }); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + if (@available (macOS 10.13, iOS 11.0, *)) + addMethod (@selector (MIDIOutputNames), [] (id self, SEL) { return _this (self)->getMIDIOutputNames(); }); + + //============================================================================== + addMethod (@selector (internalRenderBlock), [] (id self, SEL) { return _this (self)->getInternalRenderBlock(); }); + addMethod (@selector (canProcessInPlace), [] (id, SEL) { return NO; }); + addMethod (@selector (isRenderingOffline), [] (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; }); + addMethod (@selector (setRenderingOffline:), [] (id self, SEL, BOOL renderingOffline) { return _this (self)->setRenderingOffline (renderingOffline); }); + addMethod (@selector (shouldBypassEffect), [] (id self, SEL) { return _this (self)->getShouldBypassEffect() ? YES : NO; }); + addMethod (@selector (setShouldBypassEffect:), [] (id self, SEL, BOOL shouldBypass) { return _this (self)->setShouldBypassEffect (shouldBypass); }); + addMethod (@selector (allocateRenderResourcesAndReturnError:), [] (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; }); + addMethod (@selector (deallocateRenderResources), [] (id self, SEL) { return _this (self)->deallocateRenderResources(); }); + + //============================================================================== + addMethod (@selector (contextName), [] (id self, SEL) { return _this (self)->getContextName(); }); + addMethod (@selector (setContextName:), [](id self, SEL, NSString* str) { return _this (self)->setContextName (str); }); + + //============================================================================== + if (@available (macOS 10.13, iOS 11.0, *)) + { + addMethod (@selector (supportedViewConfigurations:), [] (id self, SEL, NSArray* configs) + { + auto supportedViewIndices = [[NSMutableIndexSet alloc] init]; + auto n = [configs count]; + + if (auto* editor = _this (self)->getAudioProcessor().createEditorIfNeeded()) + { + // If you hit this assertion then your plug-in's editor is reporting that it doesn't support + // any host MIDI controller configurations! + jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); + + for (auto i = 0u; i < n; ++i) + { + if (auto viewConfiguration = [configs objectAtIndex: i]) + { + if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) + { + auto* constrainer = editor->getConstrainer(); + auto height = (int) [viewConfiguration height]; + auto width = (int) [viewConfiguration width]; + + const auto maxLimits = std::numeric_limits::max() / 2; + const Rectangle requestedBounds { width, height }; + auto modifiedBounds = requestedBounds; + constrainer->checkBounds (modifiedBounds, editor->getBounds().withZeroOrigin(), { maxLimits, maxLimits }, false, false, false, false); + + if (modifiedBounds == requestedBounds) + [supportedViewIndices addIndex: i]; + } + } + } + } + + return [supportedViewIndices autorelease]; + }); + + addMethod (@selector (selectViewConfiguration:), [] (id self, SEL, AUAudioUnitViewConfiguration* config) + { + _this (self)->processorHolder->viewConfiguration.reset (new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }); + }); + } + + registerClass(); + } + + //============================================================================== + static JuceAudioUnitv3* _this (id self) { return getIvar (self, "cppObject"); } + static void setThis (id self, JuceAudioUnitv3* cpp) { object_setInstanceVariable (self, "cppObject", cpp); } + }; + + static JuceAudioUnitv3* create (AUAudioUnit* audioUnit, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) + { + return new JuceAudioUnitv3 (audioUnit, descr, options, error); + } + + //============================================================================== + static Class& getClass() + { + static Class result; + return result; + } + + //============================================================================== + struct BusBuffer + { + BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) + : auBus (bus), + maxFrames (maxFramesPerBuffer), + numberOfChannels (static_cast ([[auBus format] channelCount])), + isInterleaved ([[auBus format] isInterleaved]) + { + alloc(); + } + + //============================================================================== + void alloc() + { + const int numBuffers = isInterleaved ? 1 : numberOfChannels; + int bytes = static_cast (sizeof (AudioBufferList)) + + ((numBuffers - 1) * static_cast (sizeof (::AudioBuffer))); + jassert (bytes > 0); + + bufferListStorage.calloc (static_cast (bytes)); + bufferList = reinterpret_cast (bufferListStorage.getData()); + + const int bufferChannels = isInterleaved ? numberOfChannels : 1; + scratchBuffer.setSize (numBuffers, bufferChannels * maxFrames); + } + + void dealloc() + { + bufferList = nullptr; + bufferListStorage.free(); + scratchBuffer.setSize (0, 0); + } + + //============================================================================== + int numChannels() const noexcept { return numberOfChannels; } + bool interleaved() const noexcept { return isInterleaved; } + AudioBufferList* get() const noexcept { return bufferList; } + + //============================================================================== + void prepare (UInt32 nFrames, const AudioBufferList* other = nullptr) noexcept + { + const int numBuffers = isInterleaved ? 1 : numberOfChannels; + const bool isCompatible = isCompatibleWith (other); + + bufferList->mNumberBuffers = static_cast (numBuffers); + + for (int i = 0; i < numBuffers; ++i) + { + const UInt32 bufferChannels = static_cast (isInterleaved ? numberOfChannels : 1); + bufferList->mBuffers[i].mNumberChannels = bufferChannels; + bufferList->mBuffers[i].mData = (isCompatible ? other->mBuffers[i].mData + : scratchBuffer.getWritePointer (i)); + bufferList->mBuffers[i].mDataByteSize = nFrames * bufferChannels * sizeof (float); + } + } + + //============================================================================== + bool isCompatibleWith (const AudioBufferList* other) const noexcept + { + if (other == nullptr) + return false; + + if (other->mNumberBuffers > 0) + { + const bool otherInterleaved = AudioUnitHelpers::isAudioBufferInterleaved (*other); + const int otherChannels = static_cast (otherInterleaved ? other->mBuffers[0].mNumberChannels + : other->mNumberBuffers); + + return otherInterleaved == isInterleaved + && numberOfChannels == otherChannels; + } + + return numberOfChannels == 0; + } + + private: + AUAudioUnitBus* auBus; + HeapBlock bufferListStorage; + AudioBufferList* bufferList = nullptr; + int maxFrames, numberOfChannels; + bool isInterleaved; + juce::AudioBuffer scratchBuffer; + }; + + class FactoryPresets + { + public: + using Presets = std::unique_ptr, NSObjectDeleter>; + + void set (Presets newPresets) + { + std::lock_guard lock (mutex); + std::swap (presets, newPresets); + } + + NSArray* get() const + { + std::lock_guard lock (mutex); + return presets.get(); + } + + AUAudioUnitPreset* getAtIndex (int index) const + { + std::lock_guard lock (mutex); + + if (index < (int) [presets.get() count]) + return [presets.get() objectAtIndex: (unsigned int) index]; + + return nullptr; + } + + private: + Presets presets; + mutable std::mutex mutex; + }; + + //============================================================================== + void addAudioUnitBusses (bool isInput) + { + std::unique_ptr, NSObjectDeleter> array ([[NSMutableArray alloc] init]); + AudioProcessor& processor = getAudioProcessor(); + const auto numWrapperBuses = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); + const auto numProcessorBuses = AudioUnitHelpers::getBusCount (processor, isInput); + + for (int i = 0; i < numWrapperBuses; ++i) + { + using AVAudioFormatPtr = std::unique_ptr; + + const auto audioFormat = [&]() -> AVAudioFormatPtr + { + const auto tag = i < numProcessorBuses ? CoreAudioLayouts::toCoreAudio (processor.getChannelLayoutOfBus (isInput, i)) + : kAudioChannelLayoutTag_Stereo; + const std::unique_ptr layout { [[AVAudioChannelLayout alloc] initWithLayoutTag: tag] }; + + if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate + channelLayout: layout.get()] }) + return format; + + const auto channels = i < numProcessorBuses ? processor.getChannelCountOfBus (isInput, i) + : 2; + + // According to the docs, this will fail if the number of channels is greater than 2. + if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate + channels: static_cast (channels)] }) + return format; + + jassertfalse; + return nullptr; + }(); + + using AUAudioUnitBusPtr = std::unique_ptr; + + const auto audioUnitBus = [&]() -> AUAudioUnitBusPtr + { + if (audioFormat != nullptr) + return AUAudioUnitBusPtr { [[AUAudioUnitBus alloc] initWithFormat: audioFormat.get() error: nullptr] }; + + jassertfalse; + return nullptr; + }(); + + if (audioUnitBus != nullptr) + [array.get() addObject: audioUnitBus.get()]; + } + + (isInput ? inputBusses : outputBusses).reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit: au + busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput) + busses: array.get()]); + } + + // When parameters are discrete we need to use integer values. + static float getMaximumParameterValue ([[maybe_unused]] const AudioProcessorParameter& juceParam) + { + #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + return 1.0f; + #else + return juceParam.isDiscrete() ? (float) (juceParam.getNumSteps() - 1) : 1.0f; + #endif + } + + static auto createParameter (const AudioProcessorParameter& parameter) + { + const String name (parameter.getName (512)); + + AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; + AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable + | kAudioUnitParameterFlag_IsReadable + | kAudioUnitParameterFlag_HasCFNameString + | kAudioUnitParameterFlag_ValuesHaveStrings); + + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; + #endif + + // Set whether the param is automatable (unnamed parameters aren't allowed to be automated). + if (name.isEmpty() || ! parameter.isAutomatable()) + flags |= kAudioUnitParameterFlag_NonRealTime; + + const bool isParameterDiscrete = parameter.isDiscrete(); + + if (! isParameterDiscrete) + flags |= kAudioUnitParameterFlag_CanRamp; + + if (parameter.isMetaParameter()) + flags |= kAudioUnitParameterFlag_IsGlobalMeta; + + std::unique_ptr valueStrings; + + // Is this a meter? + if (((parameter.getCategory() & 0xffff0000) >> 16) == 2) + { + flags &= ~kAudioUnitParameterFlag_IsWritable; + flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; + unit = kAudioUnitParameterUnit_LinearGain; + } + else + { + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + if (parameter.isDiscrete()) + { + unit = parameter.isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; + auto maxValue = getMaximumParameterValue (parameter); + auto numSteps = parameter.getNumSteps(); + + // Some hosts can't handle the huge numbers of discrete parameter values created when + // using the default number of steps. + jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); + + valueStrings.reset ([NSMutableArray new]); + + for (int i = 0; i < numSteps; ++i) + [valueStrings.get() addObject: juceStringToNS (parameter.getText ((float) i / maxValue, 0))]; + } + #endif + } + + const auto address = generateAUParameterAddress (parameter); + + auto getParameterIdentifier = [¶meter] + { + if (const auto* paramWithID = dynamic_cast (¶meter)) + return paramWithID->paramID; + + // This could clash if any groups have been given integer IDs! + return String (parameter.getParameterIndex()); + }; + + std::unique_ptr param; + + @try + { + // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h + param.reset([[AUParameterTree createParameterWithIdentifier: juceStringToNS (getParameterIdentifier()) + name: juceStringToNS (name) + address: address + min: 0.0f + max: getMaximumParameterValue (parameter) + unit: unit + unitName: nullptr + flags: flags + valueStrings: valueStrings.get() + dependentParameters: nullptr] + retain]); + } + + @catch (NSException* exception) + { + // Do you have duplicate identifiers in any of your groups or parameters, + // or do your identifiers have unusual characters in them? + jassertfalse; + } + + [param.get() setValue: parameter.getDefaultValue()]; + return param; + } + + struct NodeArrayResult + { + std::unique_ptr, NSObjectDeleter> nodeArray { [NSMutableArray new] }; + + void addParameter (const AudioProcessorParameter&, std::unique_ptr auParam) + { + [nodeArray.get() addObject: [auParam.get() retain]]; + } + + void addGroup (const AudioProcessorParameterGroup& group, const NodeArrayResult& r) + { + @try + { + // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h + [nodeArray.get() addObject: [[AUParameterTree createGroupWithIdentifier: juceStringToNS (group.getID()) + name: juceStringToNS (group.getName()) + children: r.nodeArray.get()] retain]]; + } + @catch (NSException* exception) + { + // Do you have duplicate identifiers in any of your groups or parameters, + // or do your identifiers have unusual characters in them? + jassertfalse; + } + } + }; + + struct AddressedNodeArrayResult + { + NodeArrayResult nodeArray; + std::map addressForIndex; + + void addParameter (const AudioProcessorParameter& juceParam, std::unique_ptr auParam) + { + const auto index = juceParam.getParameterIndex(); + const auto address = [auParam.get() address]; + + if (const auto iter = addressForIndex.find (index); iter == addressForIndex.cend()) + addressForIndex.emplace (index, address); + else + jassertfalse; // If you hit this assertion then you have put a parameter in two groups. + + nodeArray.addParameter (juceParam, std::move (auParam)); + } + + void addGroup (const AudioProcessorParameterGroup& group, const AddressedNodeArrayResult& r) + { + nodeArray.addGroup (group, r.nodeArray); + + [[maybe_unused]] const auto initialSize = addressForIndex.size(); + addressForIndex.insert (r.addressForIndex.begin(), r.addressForIndex.end()); + [[maybe_unused]] const auto finalSize = addressForIndex.size(); + + // If this is hit, the same parameter index exists in multiple groups. + jassert (finalSize == initialSize + r.addressForIndex.size()); + } + }; + + template + static Result createParameterNodes (const AudioProcessorParameterGroup& group) + { + Result result; + + for (auto* node : group) + { + if (auto* childGroup = node->getGroup()) + { + result.addGroup (*childGroup, createParameterNodes (*childGroup)); + } + else if (auto* juceParam = node->getParameter()) + { + result.addParameter (*juceParam, createParameter (*juceParam)); + } + else + { + // No group or parameter at this node! + jassertfalse; + } + } + + return result; + } + + void addParameters() + { + auto& processor = getAudioProcessor(); + juceParameters.update (processor, forceLegacyParamIDs); + + if ((bypassParam = processor.getBypassParameter()) != nullptr) + bypassParam->addListener (this); + + auto nodes = createParameterNodes (processor.getParameterTree()); + installNewParameterTree (std::move (nodes.nodeArray.nodeArray)); + + // When we first create the parameter tree, we also create structures to allow lookup by index/address. + // These structures are not rebuilt, i.e. we assume that the parameter addresses and indices are stable. + // These structures aren't modified after creation, so there should be no need to synchronize access to them. + + addressForIndex = [&] + { + std::vector addresses (static_cast (processor.getParameters().size())); + + for (size_t i = 0; i < addresses.size(); ++i) + { + if (const auto iter = nodes.addressForIndex.find (static_cast (i)); iter != nodes.addressForIndex.cend()) + addresses[i] = iter->second; + else + jassertfalse; // Somehow, there's a parameter missing... + } + + return addresses; + }(); + + #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS + indexForAddress = [&] + { + std::map indices; + + for (const auto& [index, address] : nodes.addressForIndex) + { + if (const auto iter = indices.find (address); iter == indices.cend()) + indices.emplace (address, index); + else + jassertfalse; // The parameter at index 'iter->first' has the same address as the parameter at index 'index' + } + + return indices; + }(); + #endif + } + + void installNewParameterTree (std::unique_ptr, NSObjectDeleter> topLevelNodes) + { + editorObserverToken.reset(); + + @try + { + // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h + paramTree.reset ([[AUParameterTree createTreeWithChildren: topLevelNodes.get()] retain]); + } + @catch (NSException* exception) + { + // Do you have duplicate identifiers in any of your groups or parameters, + // or do your identifiers have unusual characters in them? + jassertfalse; + } + + [paramTree.get() setImplementorValueObserver: ^(AUParameter* param, AUValue value) { this->valueChangedFromHost (param, value); }]; + [paramTree.get() setImplementorValueProvider: ^(AUParameter* param) { return this->getValue (param); }]; + [paramTree.get() setImplementorStringFromValueCallback: ^(AUParameter* param, const AUValue* value) { return this->stringFromValue (param, value); }]; + [paramTree.get() setImplementorValueFromStringCallback: ^(AUParameter* param, NSString* str) { return this->valueFromString (param, str); }]; + + if (getAudioProcessor().hasEditor()) + { + editorObserverToken = ObserverPtr ([paramTree.get() tokenByAddingParameterObserver: ^(AUParameterAddress, AUValue) + { + // this will have already been handled by valueChangedFromHost + }], + ObserverDestructor { paramTree.get() }); + } + } + + void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) + { + if (value != juceParam->getValue()) + { + juceParam->setValue (value); + + inParameterChangedCallback = true; + juceParam->sendValueChangedMessageToListeners (value); + } + } + + void addPresets() + { + FactoryPresets::Presets newPresets { [[NSMutableArray alloc] init] }; + + const int n = getAudioProcessor().getNumPrograms(); + + for (int idx = 0; idx < n; ++idx) + { + String name = getAudioProcessor().getProgramName (idx); + + std::unique_ptr preset ([[AUAudioUnitPreset alloc] init]); + [preset.get() setName: juceStringToNS (name)]; + [preset.get() setNumber: static_cast (idx)]; + + [newPresets.get() addObject: preset.get()]; + } + + factoryPresets.set (std::move (newPresets)); + } + + //============================================================================== + void allocateBusBuffer (bool isInput) + { + OwnedArray& busBuffers = isInput ? inBusBuffers : outBusBuffers; + busBuffers.clear(); + + const int n = AudioUnitHelpers::getBusCountForWrapper (getAudioProcessor(), isInput); + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + for (int busIdx = 0; busIdx < n; ++busIdx) + busBuffers.add (new BusBuffer ([(isInput ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: static_cast (busIdx)], + static_cast (maxFrames))); + } + + //============================================================================== + void processEvents (const AURenderEvent *__nullable realtimeEventListHead, [[maybe_unused]] int numParams, AUEventSampleTime startTime) + { + for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) + { + switch (event->head.eventType) + { + case AURenderEventMIDI: + { + const AUMIDIEvent& midiEvent = event->MIDI; + midiMessages.addEvent (midiEvent.data, midiEvent.length, static_cast (midiEvent.eventSampleTime - startTime)); + } + break; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case AURenderEventMIDIEventList: + { + const auto& list = event->MIDIEventsList.eventList; + auto* packet = &list.packet[0]; + + for (uint32_t i = 0; i < list.numPackets; ++i) + { + converter.dispatch (reinterpret_cast (packet->words), + reinterpret_cast (packet->words + packet->wordCount), + static_cast (packet->timeStamp - (MIDITimeStamp) startTime), + [this] (const ump::BytestreamMidiView& message) + { + midiMessages.addEvent (message.getMessage(), (int) message.timestamp); + }); + + packet = MIDIEventPacketNext (packet); + } + } + break; + #endif + + case AURenderEventParameter: + case AURenderEventParameterRamp: + { + const AUParameterEvent& paramEvent = event->parameter; + + if (auto* p = getJuceParameterForAUAddress (paramEvent.parameterAddress)) + { + auto normalisedValue = paramEvent.value / getMaximumParameterValue (*p); + setAudioProcessorParameter (p, normalisedValue); + } + } + break; + + case AURenderEventMIDISysEx: + default: + break; + } + } + } + + AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount, + NSInteger outputBusNumber, AudioBufferList* outputData, const AURenderEvent *__nullable realtimeEventListHead, + AURenderPullInputBlock __nullable pullInputBlock) + { + auto& processor = getAudioProcessor(); + jassert (static_cast (frameCount) <= getAudioProcessor().getBlockSize()); + + const auto numProcessorBusesOut = AudioUnitHelpers::getBusCount (processor, false); + + if (lastTimeStamp.mSampleTime != timestamp->mSampleTime) + { + // process params and incoming midi (only once for a given timestamp) + midiMessages.clear(); + + const int numParams = juceParameters.getNumParameters(); + processEvents (realtimeEventListHead, numParams, static_cast (timestamp->mSampleTime)); + + lastTimeStamp = *timestamp; + + const auto numWrapperBusesIn = AudioUnitHelpers::getBusCountForWrapper (processor, true); + const auto numWrapperBusesOut = AudioUnitHelpers::getBusCountForWrapper (processor, false); + const auto numProcessorBusesIn = AudioUnitHelpers::getBusCount (processor, true); + + // prepare buffers + { + for (int busIdx = 0; busIdx < numWrapperBusesOut; ++busIdx) + { + BusBuffer& busBuffer = *outBusBuffers[busIdx]; + const bool canUseDirectOutput = + (busIdx == outputBusNumber && outputData != nullptr && outputData->mNumberBuffers > 0); + + busBuffer.prepare (frameCount, canUseDirectOutput ? outputData : nullptr); + + if (numProcessorBusesOut <= busIdx) + AudioUnitHelpers::clearAudioBuffer (*busBuffer.get()); + } + + for (int busIdx = 0; busIdx < numWrapperBusesIn; ++busIdx) + { + BusBuffer& busBuffer = *inBusBuffers[busIdx]; + busBuffer.prepare (frameCount, busIdx < numWrapperBusesOut ? outBusBuffers[busIdx]->get() : nullptr); + } + + audioBuffer.reset(); + } + + // pull inputs + { + for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) + { + BusBuffer& busBuffer = *inBusBuffers[busIdx]; + AudioBufferList* buffer = busBuffer.get(); + + if (pullInputBlock == nullptr || pullInputBlock (actionFlags, timestamp, frameCount, busIdx, buffer) != noErr) + AudioUnitHelpers::clearAudioBuffer (*buffer); + + if (actionFlags != nullptr && (*actionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0) + AudioUnitHelpers::clearAudioBuffer (*buffer); + } + } + + // set buffer pointer to minimize copying + { + int chIdx = 0; + + for (int busIdx = 0; busIdx < numProcessorBusesOut; ++busIdx) + { + BusBuffer& busBuffer = *outBusBuffers[busIdx]; + AudioBufferList* buffer = busBuffer.get(); + + const bool interleaved = busBuffer.interleaved(); + const int numChannels = busBuffer.numChannels(); + + const int* outLayoutMap = mapper.get (false, busIdx); + + for (int ch = 0; ch < numChannels; ++ch) + audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); + } + + // use input pointers on remaining channels + + for (int busIdx = 0; chIdx < totalInChannels;) + { + const int channelOffset = processor.getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); + + BusBuffer& busBuffer = *inBusBuffers[busIdx]; + AudioBufferList* buffer = busBuffer.get(); + + const int* inLayoutMap = mapper.get (true, busIdx); + audioBuffer.setBuffer (chIdx++, busBuffer.interleaved() ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[channelOffset]].mData)); + } + } + + // copy input + { + for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) + audioBuffer.set (busIdx, *inBusBuffers[busIdx]->get(), mapper.get (true, busIdx)); + + audioBuffer.clearUnusedChannels ((int) frameCount); + } + + // process audio + processBlock (audioBuffer.getBuffer (frameCount), midiMessages); + + // send MIDI + #if JucePlugin_ProducesMidiOutput + if (@available (macOS 10.13, iOS 11.0, *)) + { + if (auto midiOut = midiOutputEventBlock) + for (const auto metadata : midiMessages) + if (isPositiveAndBelow (metadata.samplePosition, frameCount)) + midiOut ((int64_t) metadata.samplePosition + (int64_t) (timestamp->mSampleTime + 0.5), + 0, + metadata.numBytes, + metadata.data); + } + #endif + } + + // copy back + if (outputBusNumber < numProcessorBusesOut && outputData != nullptr) + audioBuffer.get ((int) outputBusNumber, *outputData, mapper.get (false, (int) outputBusNumber)); + + return noErr; + } + + void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept + { + auto& processor = getAudioProcessor(); + const ScopedLock sl (processor.getCallbackLock()); + + if (processor.isSuspended()) + buffer.clear(); + else if (bypassParam == nullptr && [au shouldBypassEffect]) + processor.processBlockBypassed (buffer, midiBuffer); + else + processor.processBlock (buffer, midiBuffer); + } + + //============================================================================== + void valueChangedFromHost (AUParameter* param, AUValue value) + { + if (param != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + { + auto normalisedValue = value / getMaximumParameterValue (*p); + setAudioProcessorParameter (p, normalisedValue); + } + } + } + + AUValue getValue (AUParameter* param) const + { + if (param != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + return p->getValue() * getMaximumParameterValue (*p); + } + + return 0; + } + + NSString* stringFromValue (AUParameter* param, const AUValue* value) + { + String text; + + if (param != nullptr && value != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + { + if (LegacyAudioParameter::isLegacy (p)) + text = String (*value); + else + text = p->getText (*value / getMaximumParameterValue (*p), 0); + } + } + + return juceStringToNS (text); + } + + AUValue valueFromString (AUParameter* param, NSString* str) + { + if (param != nullptr && str != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + { + const String text (nsStringToJuce (str)); + + if (LegacyAudioParameter::isLegacy (p)) + return text.getFloatValue(); + + return p->getValueForText (text) * getMaximumParameterValue (*p); + } + } + + return 0; + } + + //============================================================================== + // this is only ever called for the bypass parameter + void parameterValueChanged (int, float newValue) override + { + JuceAudioUnitv3::setShouldBypassEffect (newValue != 0.0f); + } + + void parameterGestureChanged (int, bool) override {} + + //============================================================================== + inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept + { + if (isPositiveAndBelow (paramIndex, addressForIndex.size())) + return addressForIndex[static_cast (paramIndex)]; + + return {}; + } + + inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept + { + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + return static_cast (address); + #else + if (const auto iter = indexForAddress.find (address); iter != indexForAddress.cend()) + return iter->second; + + return {}; + #endif + } + + static AUParameterAddress generateAUParameterAddress (const AudioProcessorParameter& param) + { + const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceLegacyParamIDs); + + return static_cast (forceLegacyParamIDs ? juceParamID.getIntValue() + : juceParamID.hashCode64()); + } + + AudioProcessorParameter* getJuceParameterForAUAddress (AUParameterAddress address) const noexcept + { + return juceParameters.getParamForIndex (getJuceParameterIndexForAUAddress (address)); + } + + //============================================================================== + static constexpr double kDefaultSampleRate = 44100.0; + + struct ObserverDestructor + { + void operator() (AUParameterObserverToken ptr) const + { + if (ptr != nullptr) + [tree removeParameterObserver: ptr]; + } + + AUParameterTree* tree; + }; + + using ObserverPtr = std::unique_ptr, ObserverDestructor>; + + AUAudioUnit* au; + AudioProcessorHolder::Ptr processorHolder; + + int totalInChannels, totalOutChannels; + + CoreAudioTimeConversions timeConversions; + std::unique_ptr inputBusses, outputBusses; + + #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS + std::map indexForAddress; + #endif + std::vector addressForIndex; + LegacyAudioParametersWrapper juceParameters; + + // to avoid recursion on parameter changes, we need to add an + // editor observer to do the parameter changes + std::unique_ptr paramTree; + ObserverPtr editorObserverToken; + + std::unique_ptr, NSObjectDeleter> channelCapabilities; + + FactoryPresets factoryPresets; + + ObjCBlock internalRenderBlock; + + AudioUnitHelpers::CoreAudioBufferList audioBuffer; + AudioUnitHelpers::ChannelRemapper mapper; + + OwnedArray inBusBuffers, outBusBuffers; + MidiBuffer midiMessages; + AUMIDIOutputEventBlock midiOutputEventBlock = nullptr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + ump::ToBytestreamDispatcher converter { 2048 }; + #endif + + ObjCBlock hostMusicalContextCallback; + ObjCBlock hostTransportStateCallback; + + AudioTimeStamp lastTimeStamp; + + String contextName; + + ThreadLocalValue inParameterChangedCallback; + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + static constexpr bool forceLegacyParamIDs = true; + #else + static constexpr bool forceLegacyParamIDs = false; + #endif + AudioProcessorParameter* bypassParam = nullptr; +}; + +#if JUCE_IOS +namespace juce +{ +struct UIViewPeerControllerReceiver +{ + virtual ~UIViewPeerControllerReceiver(); + virtual void setViewController (UIViewController*) = 0; +}; +} +#endif + +//============================================================================== +class JuceAUViewController +{ +public: + JuceAUViewController (AUViewController* p) + : myself (p) + { + initialiseJuce_GUI(); + } + + ~JuceAUViewController() + { + JUCE_ASSERT_MESSAGE_THREAD + + if (processorHolder.get() != nullptr) + JuceAudioUnitv3::removeEditor (getAudioProcessor()); + } + + //============================================================================== + void loadView() + { + JUCE_ASSERT_MESSAGE_THREAD + + if (auto p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3)) + { + processorHolder = new AudioProcessorHolder (std::move (p)); + auto& processor = getAudioProcessor(); + + if (processor.hasEditor()) + { + if (AudioProcessorEditor* editor = processor.createEditorIfNeeded()) + { + preferredSize = editor->getBounds(); + + JUCE_IOS_MAC_VIEW* view = [[[JUCE_IOS_MAC_VIEW alloc] initWithFrame: convertToCGRect (editor->getBounds())] autorelease]; + [myself setView: view]; + + #if JUCE_IOS + editor->setVisible (false); + #else + editor->setVisible (true); + #endif + + detail::PluginUtilities::addToDesktop (*editor, view); + + #if JUCE_IOS + if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) + [peerView setContentMode: UIViewContentModeTop]; + + if (auto* peer = dynamic_cast (editor->getPeer())) + peer->setViewController (myself); + #endif + } + } + } + } + + void viewDidLayoutSubviews() + { + if (auto holder = processorHolder.get()) + { + if ([myself view] != nullptr) + { + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + { + if (holder->viewConfiguration != nullptr) + editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); + + editor->setBounds (convertToRectInt ([[myself view] bounds])); + + if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) + { + #if JUCE_IOS + [peerView setNeedsDisplay]; + #else + [peerView setNeedsDisplay: YES]; + #endif + } + } + } + } + } + + void didReceiveMemoryWarning() + { + if (auto ptr = processorHolder.get()) + if (auto* processor = ptr->get()) + processor->memoryWarningReceived(); + } + + void viewDidAppear (bool) + { + if (processorHolder.get() != nullptr) + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + editor->setVisible (true); + } + + void viewDidDisappear (bool) + { + if (processorHolder.get() != nullptr) + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + editor->setVisible (false); + } + + CGSize getPreferredContentSize() const + { + return CGSizeMake (static_cast (preferredSize.getWidth()), + static_cast (preferredSize.getHeight())); + } + + //============================================================================== + AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) + { + const auto holder = [&] + { + if (auto initialisedHolder = processorHolder.get()) + return initialisedHolder; + + waitForExecutionOnMainThread ([this] { [myself view]; }); + return processorHolder.get(); + }(); + + if (holder == nullptr) + return nullptr; + + return [(new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit() autorelease]; + } + +private: + template + static void waitForExecutionOnMainThread (Callback&& callback) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + callback(); + return; + } + + std::promise promise; + + MessageManager::callAsync ([&] + { + callback(); + promise.set_value(); + }); + + promise.get_future().get(); + } + + // There's a chance that createAudioUnit will be called from a background + // thread while the processorHolder is being updated on the main thread. + class LockedProcessorHolder + { + public: + AudioProcessorHolder::Ptr get() const + { + const ScopedLock lock (mutex); + return holder; + } + + LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) + { + const ScopedLock lock (mutex); + holder = other; + return *this; + } + + private: + mutable CriticalSection mutex; + AudioProcessorHolder::Ptr holder; + }; + + //============================================================================== + AUViewController* myself; + LockedProcessorHolder processorHolder; + Rectangle preferredSize { 1, 1 }; + + //============================================================================== + AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } +}; + +//============================================================================== +// necessary glue code +@interface JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) : AUViewController +@end + +@implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) +{ + std::unique_ptr cpp; +} + +- (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp.reset (new JuceAUViewController (self)); return self; } +- (void) loadView { cpp->loadView(); } +- (AUAudioUnit *) createAudioUnitWithComponentDescription: (AudioComponentDescription) desc error: (NSError **) error { return cpp->createAudioUnit (desc, error); } +- (CGSize) preferredContentSize { return cpp->getPreferredContentSize(); } + +// NSViewController and UIViewController have slightly different names for this function +- (void) viewDidLayoutSubviews { cpp->viewDidLayoutSubviews(); } +- (void) viewDidLayout { cpp->viewDidLayoutSubviews(); } + +- (void) didReceiveMemoryWarning { cpp->didReceiveMemoryWarning(); } +#if JUCE_IOS +- (void) viewDidAppear: (BOOL) animated { cpp->viewDidAppear (animated); [super viewDidAppear:animated]; } +- (void) viewDidDisappear: (BOOL) animated { cpp->viewDidDisappear (animated); [super viewDidDisappear:animated]; } +#endif +@end + +//============================================================================== +#if JUCE_IOS +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +bool JUCE_CALLTYPE juce_isInterAppAudioConnected() { return false; } +void JUCE_CALLTYPE juce_switchToHostApplication() {} +Image JUCE_CALLTYPE juce_getIAAHostIcon (int) { return {}; } + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp index 717bb795d9..16d14a3365 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp @@ -23,4 +23,1793 @@ ============================================================================== */ -#include "LV2/juce_LV2_Client.cpp" +#if JucePlugin_Build_LV2 + +#ifndef _SCL_SECURE_NO_WARNINGS + #define _SCL_SECURE_NO_WARNINGS +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif + +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 + +#include +#include +#include +#include + +#include +#include + +#include "JuceLV2Defines.h" +#include + +#include + +#define JUCE_TURTLE_RECALL_URI "https://lv2-extensions.juce.com/turtle_recall" + +#ifndef JucePlugin_LV2URI + #error "You need to define the JucePlugin_LV2URI value! If you're using the Projucer/CMake, the definition will be written into JuceLV2Defines.h automatically." +#endif + +namespace juce +{ +namespace lv2_client +{ + +constexpr auto uriSeparator = ":"; +const auto JucePluginLV2UriUi = String (JucePlugin_LV2URI) + uriSeparator + "UI"; +const auto JucePluginLV2UriState = String (JucePlugin_LV2URI) + uriSeparator + "StateString"; +const auto JucePluginLV2UriProgram = String (JucePlugin_LV2URI) + uriSeparator + "Program"; + +static const LV2_Feature* findMatchingFeature (const LV2_Feature* const* features, const char* uri) +{ + for (auto feature = features; *feature != nullptr; ++feature) + if (std::strcmp ((*feature)->URI, uri) == 0) + return *feature; + + return nullptr; +} + +static bool hasFeature (const LV2_Feature* const* features, const char* uri) +{ + return findMatchingFeature (features, uri) != nullptr; +} + +template +Data findMatchingFeatureData (const LV2_Feature* const* features, const char* uri) +{ + if (const auto* feature = findMatchingFeature (features, uri)) + return static_cast (feature->data); + + return {}; +} + +static const LV2_Options_Option* findMatchingOption (const LV2_Options_Option* options, LV2_URID urid) +{ + for (auto option = options; option->value != nullptr; ++option) + if (option->key == urid) + return option; + + return nullptr; +} + +class ParameterStorage : private AudioProcessorListener +{ +public: + ParameterStorage (AudioProcessor& proc, LV2_URID_Map map) + : processor (proc), + mapFeature (map), + legacyParameters (proc, false) + { + processor.addListener (this); + } + + ~ParameterStorage() override + { + processor.removeListener (this); + } + + /* This is the string that will be used to uniquely identify the parameter. + + This string will be written into the plugin's manifest as an IRI, so it must be + syntactically valid. + + We escape this string rather than writing the user-defined parameter ID directly to avoid + writing a malformed manifest in the case that user IDs contain spaces or other reserved + characters. This should allow users to keep the same param IDs for all plugin formats. + */ + static String getIri (const AudioProcessorParameter& param) + { + const auto urlSanitised = URL::addEscapeChars (LegacyAudioParameter::getParamID (¶m, false), true); + const auto ttlSanitised = lv2_shared::sanitiseStringAsTtlName (urlSanitised); + + // If this is hit, the parameter ID could not be represented directly in the plugin ttl. + // We'll replace offending characters with '_'. + jassert (urlSanitised == ttlSanitised); + + return ttlSanitised; + } + + void setValueFromHost (LV2_URID urid, float value) noexcept + { + const auto it = uridToIndexMap.find (urid); + + if (it == uridToIndexMap.end()) + { + // No such parameter. + jassertfalse; + return; + } + + if (auto* param = legacyParameters.getParamForIndex ((int) it->second)) + { + const auto scaledValue = [&] + { + if (auto* rangedParam = dynamic_cast (param)) + return rangedParam->convertTo0to1 (value); + + return value; + }(); + + if (scaledValue != param->getValue()) + { + ScopedValueSetter scope (ignoreCallbacks, true); + param->setValueNotifyingHost (scaledValue); + } + } + } + + struct Options + { + bool parameterValue, gestureBegin, gestureEnd; + }; + + static constexpr auto newClientValue = 1 << 0, + gestureBegan = 1 << 1, + gestureEnded = 1 << 2; + + template + void forEachChangedParameter (Callback&& callback) + { + stateCache.ifSet ([this, &callback] (size_t parameterIndex, float, uint32_t bits) + { + const Options options { (bits & newClientValue) != 0, + (bits & gestureBegan) != 0, + (bits & gestureEnded) != 0 }; + + callback (*legacyParameters.getParamForIndex ((int) parameterIndex), + indexToUridMap[parameterIndex], + options); + }); + } + +private: + void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float value) override + { + if (! ignoreCallbacks) + stateCache.setValueAndBits ((size_t) parameterIndex, value, newClientValue); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override + { + if (! ignoreCallbacks) + stateCache.setBits ((size_t) parameterIndex, gestureBegan); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override + { + if (! ignoreCallbacks) + stateCache.setBits ((size_t) parameterIndex, gestureEnded); + } + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} + + AudioProcessor& processor; + const LV2_URID_Map mapFeature; + const LegacyAudioParametersWrapper legacyParameters; + const std::vector indexToUridMap = [&] + { + std::vector result; + + for (auto* param : legacyParameters) + { + jassert ((size_t) param->getParameterIndex() == result.size()); + + const auto uri = JucePlugin_LV2URI + String (uriSeparator) + getIri (*param); + const auto urid = mapFeature.map (mapFeature.handle, uri.toRawUTF8()); + result.push_back (urid); + } + + // If this is hit, some parameters have duplicate IDs. + // This may be because the IDs resolve to the same string when removing characters that + // are invalid in a TTL name. + jassert (std::set (result.begin(), result.end()).size() == result.size()); + + return result; + }(); + const std::map uridToIndexMap = [&] + { + std::map result; + size_t index = 0; + + for (const auto& urid : indexToUridMap) + result.emplace (urid, index++); + + return result; + }(); + FlaggedFloatCache<3> stateCache { (size_t) legacyParameters.getNumParameters() }; + bool ignoreCallbacks = false; + + JUCE_LEAK_DETECTOR (ParameterStorage) +}; + +enum class PortKind { seqInput, seqOutput, latencyOutput, freeWheelingInput, enabledInput }; + +struct PortIndices +{ + PortIndices (int numInputsIn, int numOutputsIn) + : numInputs (numInputsIn), numOutputs (numOutputsIn) {} + + int getPortIndexForAudioInput (int audioIndex) const noexcept + { + return audioIndex; + } + + int getPortIndexForAudioOutput (int audioIndex) const noexcept + { + return audioIndex + numInputs; + } + + int getPortIndexFor (PortKind p) const noexcept { return getMaxAudioPortIndex() + (int) p; } + + // Audio ports are numbered from 0 to numInputs + numOutputs + int getMaxAudioPortIndex() const noexcept { return numInputs + numOutputs; } + + int numInputs, numOutputs; +}; + +//============================================================================== +class PlayHead : public AudioPlayHead +{ +public: + PlayHead (LV2_URID_Map mapFeatureIn, double sampleRateIn) + : parser (mapFeatureIn), sampleRate (sampleRateIn) + { + } + + void invalidate() { info = nullopt; } + + void readNewInfo (const LV2_Atom_Event* event) + { + if (event->body.type != mLV2_ATOM__Object && event->body.type != mLV2_ATOM__Blank) + return; + + const auto* object = reinterpret_cast (&event->body); + + if (object->body.otype != mLV2_TIME__Position) + return; + + const LV2_Atom* atomFrame = nullptr; + const LV2_Atom* atomSpeed = nullptr; + const LV2_Atom* atomBar = nullptr; + const LV2_Atom* atomBeat = nullptr; + const LV2_Atom* atomBeatUnit = nullptr; + const LV2_Atom* atomBeatsPerBar = nullptr; + const LV2_Atom* atomBeatsPerMinute = nullptr; + + LV2_Atom_Object_Query query[] { { mLV2_TIME__frame, &atomFrame }, + { mLV2_TIME__speed, &atomSpeed }, + { mLV2_TIME__bar, &atomBar }, + { mLV2_TIME__beat, &atomBeat }, + { mLV2_TIME__beatUnit, &atomBeatUnit }, + { mLV2_TIME__beatsPerBar, &atomBeatsPerBar }, + { mLV2_TIME__beatsPerMinute, &atomBeatsPerMinute }, + LV2_ATOM_OBJECT_QUERY_END }; + + lv2_atom_object_query (object, query); + + info.emplace(); + + // Carla always seems to give us an integral 'beat' even though I'd expect + // it to be a floating-point value + + const auto numerator = parser.parseNumericAtom (atomBeatsPerBar); + const auto denominator = parser.parseNumericAtom (atomBeatUnit); + + if (numerator.hasValue() && denominator.hasValue()) + info->setTimeSignature (TimeSignature { (int) *numerator, (int) *denominator }); + + info->setBpm (parser.parseNumericAtom (atomBeatsPerMinute)); + info->setPpqPosition (parser.parseNumericAtom (atomBeat)); + info->setIsPlaying (parser.parseNumericAtom (atomSpeed).orFallback (0.0f) != 0.0f); + info->setBarCount (parser.parseNumericAtom (atomBar)); + + if (const auto parsed = parser.parseNumericAtom (atomFrame)) + { + info->setTimeInSamples (*parsed); + info->setTimeInSeconds ((double) *parsed / sampleRate); + } + } + + Optional getPosition() const override + { + return info; + } + +private: + lv2_shared::NumericAtomParser parser; + Optional info; + double sampleRate; + + #define X(str) const LV2_URID m##str = parser.map (str); + X (LV2_ATOM__Blank) + X (LV2_ATOM__Object) + X (LV2_TIME__Position) + X (LV2_TIME__beat) + X (LV2_TIME__beatUnit) + X (LV2_TIME__beatsPerBar) + X (LV2_TIME__beatsPerMinute) + X (LV2_TIME__frame) + X (LV2_TIME__speed) + X (LV2_TIME__bar) + #undef X + + JUCE_LEAK_DETECTOR (PlayHead) +}; + +//============================================================================== +class Ports +{ +public: + Ports (LV2_URID_Map map, int numInputsIn, int numOutputsIn) + : forge (map), + indices (numInputsIn, numOutputsIn), + mLV2_ATOM__Sequence (map.map (map.handle, LV2_ATOM__Sequence)) + { + audioBuffers.resize (static_cast (numInputsIn + numOutputsIn), nullptr); + } + + void connect (int port, void* data) + { + // The following is not UB _if_ data really points to an object with the expected type. + + if (port == indices.getPortIndexFor (PortKind::seqInput)) + { + inputData = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::seqOutput)) + { + outputData = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::latencyOutput)) + { + latency = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::freeWheelingInput)) + { + freeWheeling = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::enabledInput)) + { + enabled = static_cast (data); + } + else if (isPositiveAndBelow (port, indices.getMaxAudioPortIndex())) + { + audioBuffers[(size_t) port] = static_cast (data); + } + else + { + // This port was not declared! + jassertfalse; + } + } + + template + void forEachInputEvent (Callback&& callback) + { + if (inputData != nullptr && inputData->atom.type == mLV2_ATOM__Sequence) + for (const auto* event : lv2_shared::SequenceIterator { lv2_shared::SequenceWithSize { inputData } }) + callback (event); + } + + void prepareToWrite() + { + // Note: Carla seems to have a bug (verified with the eg-fifths plugin) where + // the output buffer size is incorrect on alternate calls. + forge.setBuffer (reinterpret_cast (outputData), outputData->atom.size); + } + + void writeLatency (int value) + { + if (latency != nullptr) + *latency = (float) value; + } + + const float* getBufferForAudioInput (int index) const noexcept + { + return audioBuffers[(size_t) indices.getPortIndexForAudioInput (index)]; + } + + float* getBufferForAudioOutput (int index) const noexcept + { + return audioBuffers[(size_t) indices.getPortIndexForAudioOutput (index)]; + } + + bool isFreeWheeling() const noexcept + { + if (freeWheeling != nullptr) + return *freeWheeling > 0.5f; + + return false; + } + + bool isEnabled() const noexcept + { + if (enabled != nullptr) + return *enabled > 0.5f; + + return true; + } + + lv2_shared::AtomForge forge; + PortIndices indices; + +private: + static constexpr auto numParamPorts = 3; + const LV2_Atom_Sequence* inputData = nullptr; + LV2_Atom_Sequence* outputData = nullptr; + float* latency = nullptr; + float* freeWheeling = nullptr; + float* enabled = nullptr; + std::vector audioBuffers; + const LV2_URID mLV2_ATOM__Sequence; + + JUCE_LEAK_DETECTOR (Ports) +}; + +class LV2PluginInstance : private AudioProcessorListener +{ +public: + LV2PluginInstance (double sampleRate, + int64_t maxBlockSize, + const char*, + LV2_URID_Map mapFeatureIn) + : mapFeature (mapFeatureIn), + playHead (mapFeature, sampleRate) + { + processor->addListener (this); + processor->setPlayHead (&playHead); + prepare (sampleRate, (int) maxBlockSize); + } + + void connect (uint32_t port, void* data) + { + ports.connect ((int) port, data); + } + + void activate() {} + + template + static void iterateAudioBuffer (AudioBuffer& ab, UnaryFunction fn) + { + float* const* sampleData = ab.getArrayOfWritePointers(); + + for (int c = ab.getNumChannels(); --c >= 0;) + for (int s = ab.getNumSamples(); --s >= 0;) + fn (sampleData[c][s]); + } + + static int countNaNs (AudioBuffer& ab) noexcept + { + int count = 0; + iterateAudioBuffer (ab, [&count] (float s) + { + if (std::isnan (s)) + ++count; + }); + + return count; + } + + void run (uint32_t numSteps) + { + // If this is hit, the host is trying to process more samples than it told us to prepare + jassert (static_cast (numSteps) <= processor->getBlockSize()); + + midi.clear(); + playHead.invalidate(); + audio.setSize (audio.getNumChannels(), static_cast (numSteps), true, false, true); + + ports.forEachInputEvent ([&] (const LV2_Atom_Event* event) + { + struct Callback + { + explicit Callback (LV2PluginInstance& s) : self (s) {} + + void setParameter (LV2_URID property, float value) const noexcept + { + self.parameters.setValueFromHost (property, value); + } + + // The host probably shouldn't send us 'touched' messages. + void gesture (LV2_URID, bool) const noexcept {} + + LV2PluginInstance& self; + }; + + patchSetHelper.processPatchSet (event, Callback { *this }); + + playHead.readNewInfo (event); + + if (event->body.type == mLV2_MIDI__MidiEvent) + midi.addEvent (event + 1, static_cast (event->body.size), static_cast (event->time.frames)); + }); + + processor->setNonRealtime (ports.isFreeWheeling()); + + for (auto i = 0, end = processor->getTotalNumInputChannels(); i < end; ++i) + audio.copyFrom (i, 0, ports.getBufferForAudioInput (i), audio.getNumSamples()); + + jassert (countNaNs (audio) == 0); + + { + const ScopedLock lock { processor->getCallbackLock() }; + + if (processor->isSuspended()) + { + for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) + { + const auto ptr = ports.getBufferForAudioOutput (i); + std::fill (ptr, ptr + numSteps, 0.0f); + } + } + else + { + const auto isEnabled = ports.isEnabled(); + + if (auto* param = processor->getBypassParameter()) + { + param->setValueNotifyingHost (isEnabled ? 0.0f : 1.0f); + processor->processBlock (audio, midi); + } + else if (isEnabled) + { + processor->processBlock (audio, midi); + } + else + { + processor->processBlockBypassed (audio, midi); + } + } + } + + for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) + { + const auto src = audio.getReadPointer (i); + const auto dst = ports.getBufferForAudioOutput (i); + + if (dst != nullptr) + std::copy (src, src + numSteps, dst); + } + + ports.prepareToWrite(); + auto* forge = ports.forge.get(); + lv2_shared::SequenceFrame sequence { forge, (uint32_t) 0 }; + + parameters.forEachChangedParameter ([&] (const AudioProcessorParameter& param, + LV2_URID paramUrid, + const ParameterStorage::Options& options) + { + const auto sendTouched = [&] (bool state) + { + // TODO Implement begin/end change gesture support once it's supported by LV2 + ignoreUnused (state); + }; + + if (options.gestureBegin) + sendTouched (true); + + if (options.parameterValue) + { + lv2_atom_forge_frame_time (forge, 0); + + lv2_shared::ObjectFrame object { forge, (uint32_t) 0, patchSetHelper.mLV2_PATCH__Set }; + + lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__property); + lv2_atom_forge_urid (forge, paramUrid); + + lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__value); + lv2_atom_forge_float (forge, [&] + { + if (auto* rangedParam = dynamic_cast (¶m)) + return rangedParam->convertFrom0to1 (rangedParam->getValue()); + + return param.getValue(); + }()); + } + + if (options.gestureEnd) + sendTouched (false); + }); + + if (shouldSendStateChange.exchange (false)) + { + lv2_atom_forge_frame_time (forge, 0); + lv2_shared::ObjectFrame { forge, (uint32_t) 0, mLV2_STATE__StateChanged }; + } + + for (const auto meta : midi) + { + const auto bytes = static_cast (meta.numBytes); + lv2_atom_forge_frame_time (forge, meta.samplePosition); + lv2_atom_forge_atom (forge, bytes, mLV2_MIDI__MidiEvent); + lv2_atom_forge_write (forge, meta.data, bytes); + } + + ports.writeLatency (processor->getLatencySamples()); + } + + void deactivate() {} + + LV2_State_Status store (LV2_State_Store_Function storeFn, + LV2_State_Handle handle, + uint32_t, + const LV2_Feature* const*) + { + MemoryBlock block; + processor->getStateInformation (block); + const auto text = block.toBase64Encoding(); + storeFn (handle, + mJucePluginLV2UriState, + text.toRawUTF8(), + text.getNumBytesAsUTF8() + 1, + mLV2_ATOM__String, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + return LV2_STATE_SUCCESS; + } + + LV2_State_Status retrieve (LV2_State_Retrieve_Function retrieveFn, + LV2_State_Handle handle, + uint32_t, + const LV2_Feature* const*) + { + size_t size = 0; + uint32_t type = 0; + uint32_t dataFlags = 0; + + // Try retrieving a port index (if this is a 'program' preset). + const auto* programData = retrieveFn (handle, mJucePluginLV2UriProgram, &size, &type, &dataFlags); + + if (programData != nullptr && type == mLV2_ATOM__Int && size == sizeof (int32_t)) + { + const auto programIndex = readUnaligned (programData); + processor->setCurrentProgram (programIndex); + return LV2_STATE_SUCCESS; + } + + // This doesn't seem to be a 'program' preset, try setting the full state from a string instead. + const auto* data = retrieveFn (handle, mJucePluginLV2UriState, &size, &type, &dataFlags); + + if (data == nullptr) + return LV2_STATE_ERR_NO_PROPERTY; + + if (type != mLV2_ATOM__String) + return LV2_STATE_ERR_BAD_TYPE; + + String text (static_cast (data), (size_t) size); + MemoryBlock block; + block.fromBase64Encoding (text); + processor->setStateInformation (block.getData(), (int) block.getSize()); + + return LV2_STATE_SUCCESS; + } + + std::unique_ptr createEditor() + { + return std::unique_ptr (processor->createEditorIfNeeded()); + } + + void editorBeingDeleted (AudioProcessorEditor* editor) + { + processor->editorBeingDeleted (editor); + } + + static std::unique_ptr createProcessorInstance() + { + auto result = createPluginFilterOfType (AudioProcessor::wrapperType_LV2); + + #if defined (JucePlugin_PreferredChannelConfigurations) + constexpr short channelConfigurations[][2] { JucePlugin_PreferredChannelConfigurations }; + + static_assert (numElementsInArray (channelConfigurations) > 0, + "JucePlugin_PreferredChannelConfigurations must contain at least one entry"); + static_assert (channelConfigurations[0][0] > 0 || channelConfigurations[0][1] > 0, + "JucePlugin_PreferredChannelConfigurations must have at least one input or output channel"); + result->setPlayConfigDetails (channelConfigurations[0][0], channelConfigurations[0][1], 44100.0, 1024); + + const auto desiredChannels = std::make_tuple (channelConfigurations[0][0], channelConfigurations[0][1]); + const auto actualChannels = std::make_tuple (result->getTotalNumInputChannels(), result->getTotalNumOutputChannels()); + + if (desiredChannels != actualChannels) + Logger::outputDebugString ("Failed to apply requested channel configuration!"); + #else + result->enableAllBuses(); + #endif + + return result; + } + +private: + void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + // Only check for non-parameter state here because: + // - Latency is automatically written every block. + // - There's no way for an LV2 plugin to report an internal program change. + // - Parameter info is hard-coded in the plugin's turtle description. + if (details.nonParameterStateChanged) + shouldSendStateChange = true; + } + + void prepare (double sampleRate, int maxBlockSize) + { + jassert (processor != nullptr); + processor->setRateAndBufferSizeDetails (sampleRate, maxBlockSize); + processor->prepareToPlay (sampleRate, maxBlockSize); + + const auto numChannels = jmax (processor->getTotalNumInputChannels(), + processor->getTotalNumOutputChannels()); + + midi.ensureSize (8192); + audio.setSize (numChannels, maxBlockSize); + audio.clear(); + } + + LV2_URID map (StringRef uri) const { return mapFeature.map (mapFeature.handle, uri); } + + ScopedJuceInitialiser_GUI scopedJuceInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + std::unique_ptr processor = createProcessorInstance(); + const LV2_URID_Map mapFeature; + ParameterStorage parameters { *processor, mapFeature }; + Ports ports { mapFeature, + processor->getTotalNumInputChannels(), + processor->getTotalNumOutputChannels() }; + lv2_shared::PatchSetHelper patchSetHelper { mapFeature, JucePlugin_LV2URI }; + PlayHead playHead; + MidiBuffer midi; + AudioBuffer audio; + std::atomic shouldSendStateChange { false }; + + #define X(str) const LV2_URID m##str = map (str); + X (JucePluginLV2UriProgram) + X (JucePluginLV2UriState) + X (LV2_ATOM__Int) + X (LV2_ATOM__String) + X (LV2_BUF_SIZE__maxBlockLength) + X (LV2_BUF_SIZE__sequenceSize) + X (LV2_MIDI__MidiEvent) + X (LV2_PATCH__Set) + X (LV2_STATE__StateChanged) + #undef X + + JUCE_LEAK_DETECTOR (LV2PluginInstance) +}; + +//============================================================================== +struct RecallFeature +{ + int (*doRecall) (const char*) = [] (const char* libraryPath) -> int + { + const ScopedJuceInitialiser_GUI scope; + const auto processor = LV2PluginInstance::createProcessorInstance(); + + const String pathString { CharPointer_UTF8 { libraryPath } }; + + const auto absolutePath = File::isAbsolutePath (pathString) ? File (pathString) + : File::getCurrentWorkingDirectory().getChildFile (pathString); + + const auto writers = { writeManifestTtl, writeDspTtl, writeUiTtl }; + + const auto wroteSuccessfully = [&processor, &absolutePath] (auto* fn) + { + const auto result = fn (*processor, absolutePath); + + if (! result.wasOk()) + std::cerr << result.getErrorMessage() << '\n'; + + return result.wasOk(); + }; + + return std::all_of (writers.begin(), writers.end(), wroteSuccessfully) ? 0 : 1; + }; + +private: + static String getPresetUri (int index) + { + return JucePlugin_LV2URI + String (uriSeparator) + "preset" + String (index + 1); + } + + static FileOutputStream openStream (const File& libraryPath, StringRef name) + { + return FileOutputStream { libraryPath.getSiblingFile (name + ".ttl") }; + } + + static Result prepareStream (FileOutputStream& stream) + { + if (const auto result = stream.getStatus(); ! result) + return result; + + stream.setPosition (0); + stream.truncate(); + return Result::ok(); + } + + static Result writeManifestTtl (AudioProcessor& proc, const File& libraryPath) + { + auto os = openStream (libraryPath, "manifest"); + + if (const auto result = prepareStream (os); ! result) + return result; + + os << "@prefix lv2: .\n" + "@prefix rdfs: .\n" + "@prefix pset: .\n" + "@prefix state: .\n" + "@prefix ui: .\n" + "@prefix xsd: .\n" + "\n" + "<" JucePlugin_LV2URI ">\n" + "\ta lv2:Plugin ;\n" + "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" + "\trdfs:seeAlso .\n"; + + if (proc.hasEditor()) + { + #if JUCE_MAC + #define JUCE_LV2_UI_KIND "CocoaUI" + #elif JUCE_WINDOWS + #define JUCE_LV2_UI_KIND "WindowsUI" + #elif JUCE_LINUX || JUCE_BSD + #define JUCE_LV2_UI_KIND "X11UI" + #else + #error "LV2 UI is unsupported on this platform" + #endif + + os << "\n" + "<" << JucePluginLV2UriUi << ">\n" + "\ta ui:" JUCE_LV2_UI_KIND " ;\n" + "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" + "\trdfs:seeAlso .\n" + "\n"; + } + + for (auto i = 0, end = proc.getNumPrograms(); i < end; ++i) + { + os << "<" << getPresetUri (i) << ">\n" + "\ta pset:Preset ;\n" + "\tlv2:appliesTo <" JucePlugin_LV2URI "> ;\n" + "\trdfs:label \"" << proc.getProgramName (i) << "\" ;\n" + "\tstate:state [ <" << JucePluginLV2UriProgram << "> \"" << i << "\"^^xsd:int ; ] .\n" + "\n"; + } + + return Result::ok(); + } + + static std::vector findAllSubgroupsDepthFirst (const AudioProcessorParameterGroup& group, + std::vector foundSoFar = {}) + { + foundSoFar.push_back (&group); + + for (auto* node : group) + { + if (auto* subgroup = node->getGroup()) + foundSoFar = findAllSubgroupsDepthFirst (*subgroup, std::move (foundSoFar)); + } + + return foundSoFar; + } + + using GroupSymbolMap = std::map; + + static String getFlattenedGroupSymbol (const AudioProcessorParameterGroup& group, String symbol = "") + { + if (auto* parent = group.getParent()) + return getFlattenedGroupSymbol (*parent, group.getID() + (symbol.isEmpty() ? "" : group.getSeparator() + symbol)); + + return symbol; + } + + static String getSymbolForGroup (const AudioProcessorParameterGroup& group) + { + const String allowedCharacters ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"); + const auto base = getFlattenedGroupSymbol (group); + + if (base.isEmpty()) + return {}; + + String copy; + + for (const auto character : base) + copy << String::charToString (allowedCharacters.containsChar (character) ? character : (juce_wchar) '_'); + + return "paramgroup_" + copy; + } + + static GroupSymbolMap getGroupsAndSymbols (const std::vector& groups) + { + std::set symbols; + GroupSymbolMap result; + + for (const auto& group : groups) + { + const auto symbol = [&] + { + const auto idealSymbol = getSymbolForGroup (*group); + + if (symbols.find (idealSymbol) == symbols.cend()) + return idealSymbol; + + for (auto i = 2; i < std::numeric_limits::max(); ++i) + { + const auto toTest = idealSymbol + "_" + String (i); + + if (symbols.find (toTest) == symbols.cend()) + return toTest; + } + + jassertfalse; + return String(); + }(); + + symbols.insert (symbol); + result.emplace (group, symbol); + } + + return result; + } + + template + static void visitAllParameters (const GroupSymbolMap& groups, Fn&& fn) + { + for (const auto& group : groups) + for (const auto* node : *group.first) + if (auto* param = node->getParameter()) + fn (group.second, *param); + } + + static Result writeDspTtl (AudioProcessor& proc, const File& libraryPath) + { + auto os = openStream (libraryPath, "dsp"); + + if (const auto result = prepareStream (os); ! result) + return result; + + os << "@prefix atom: .\n" + "@prefix bufs: .\n" + "@prefix doap: .\n" + "@prefix foaf: .\n" + "@prefix lv2: .\n" + "@prefix midi: .\n" + "@prefix opts: .\n" + "@prefix param: .\n" + "@prefix patch: .\n" + "@prefix pg: .\n" + "@prefix plug: <" JucePlugin_LV2URI << uriSeparator << "> .\n" + "@prefix pprop: .\n" + "@prefix rdfs: .\n" + "@prefix rdf: .\n" + "@prefix rsz: .\n" + "@prefix state: .\n" + "@prefix time: .\n" + "@prefix ui: .\n" + "@prefix units: .\n" + "@prefix urid: .\n" + "@prefix xsd: .\n" + "\n"; + + LegacyAudioParametersWrapper legacyParameters (proc, false); + + const auto allGroups = findAllSubgroupsDepthFirst (legacyParameters.getGroup()); + const auto groupsAndSymbols = getGroupsAndSymbols (allGroups); + + const auto parameterVisitor = [&] (const String& symbol, + const AudioProcessorParameter& param) + { + os << "plug:" << ParameterStorage::getIri (param) << "\n" + "\ta lv2:Parameter ;\n" + "\trdfs:label \"" << param.getName (1024) << "\" ;\n"; + + if (symbol.isNotEmpty()) + os << "\tpg:group plug:" << symbol << " ;\n"; + + os << "\trdfs:range atom:Float ;\n"; + + if (auto* rangedParam = dynamic_cast (¶m)) + { + os << "\tlv2:default " << rangedParam->convertFrom0to1 (rangedParam->getDefaultValue()) << " ;\n" + "\tlv2:minimum " << rangedParam->getNormalisableRange().start << " ;\n" + "\tlv2:maximum " << rangedParam->getNormalisableRange().end; + } + else + { + os << "\tlv2:default " << param.getDefaultValue() << " ;\n" + "\tlv2:minimum 0.0 ;\n" + "\tlv2:maximum 1.0"; + } + + // Avoid writing out loads of scale points for parameters with lots of steps + constexpr auto stepLimit = 1000; + const auto numSteps = param.getNumSteps(); + + if (param.isDiscrete() && 2 <= numSteps && numSteps < stepLimit) + { + os << "\t ;\n" + "\tlv2:portProperty lv2:enumeration " << (param.isBoolean() ? ", lv2:toggled " : "") << ";\n" + "\tlv2:scalePoint "; + + const auto maxIndex = numSteps - 1; + + for (int i = 0; i < numSteps; ++i) + { + const auto value = (float) i / (float) maxIndex; + const auto text = param.getText (value, 1024); + + os << (i != 0 ? ", " : "") << "[\n" + "\t\trdfs:label \"" << text << "\" ;\n" + "\t\trdf:value " << value << " ;\n" + "\t]"; + } + } + + os << " .\n\n"; + }; + + visitAllParameters (groupsAndSymbols, parameterVisitor); + + for (const auto& groupInfo : groupsAndSymbols) + { + if (groupInfo.second.isEmpty()) + continue; + + os << "plug:" << groupInfo.second << "\n" + "\ta pg:Group ;\n"; + + if (auto* parent = groupInfo.first->getParent()) + { + if (parent->getParent() != nullptr) + { + const auto it = groupsAndSymbols.find (parent); + + if (it != groupsAndSymbols.cend()) + os << "\tpg:subGroupOf plug:" << it->second << " ;\n"; + } + } + + os << "\tlv2:symbol \"" << groupInfo.second << "\" ;\n" + "\tlv2:name \"" << groupInfo.first->getName() << "\" .\n\n"; + } + + const auto getBaseBusName = [] (bool isInput) { return isInput ? "input_group_" : "output_group_"; }; + + for (const auto isInput : { true, false }) + { + const auto baseBusName = getBaseBusName (isInput); + const auto groupKind = isInput ? "InputGroup" : "OutputGroup"; + const auto busCount = proc.getBusCount (isInput); + + for (auto i = 0; i < busCount; ++i) + { + if (const auto* bus = proc.getBus (isInput, i)) + { + os << "plug:" << baseBusName << (i + 1) << "\n" + "\ta pg:" << groupKind << " ;\n" + "\tlv2:name \"" << bus->getName() << "\" ;\n" + "\tlv2:symbol \"" << baseBusName << (i + 1) << "\" .\n\n"; + } + } + } + + os << "<" JucePlugin_LV2URI ">\n"; + + if (proc.hasEditor()) + os << "\tui:ui <" << JucePluginLV2UriUi << "> ;\n"; + + const auto versionParts = StringArray::fromTokens (JucePlugin_VersionString, ".", ""); + + const auto getVersionOrZero = [&] (int indexFromBack) + { + const auto str = versionParts[versionParts.size() - indexFromBack]; + return str.isEmpty() ? 0 : str.getIntValue(); + }; + + const auto minorVersion = getVersionOrZero (2); + const auto microVersion = getVersionOrZero (1); + + os << "\ta " + #if JucePlugin_IsSynth + "lv2:InstrumentPlugin" + #else + "lv2:Plugin" + #endif + " ;\n" + "\tdoap:name \"" JucePlugin_Name "\" ;\n" + "\tdoap:description \"" JucePlugin_Desc "\" ;\n" + "\tlv2:minorVersion " << minorVersion << " ;\n" + "\tlv2:microVersion " << microVersion << " ;\n" + "\tdoap:maintainer [\n" + "\t\ta foaf:Person ;\n" + "\t\tfoaf:name \"" JucePlugin_Manufacturer "\" ;\n" + "\t\tfoaf:homepage <" JucePlugin_ManufacturerWebsite "> ;\n" + "\t\tfoaf:mbox <" JucePlugin_ManufacturerEmail "> ;\n" + "\t] ;\n" + "\tdoap:release [\n" + "\t\ta doap:Version ;\n" + "\t\tdoap:revision \"" JucePlugin_VersionString "\" ;\n" + "\t] ;\n" + "\tlv2:optionalFeature\n" + "\t\tlv2:hardRTCapable ;\n" + "\tlv2:extensionData\n" + "\t\tstate:interface ;\n" + "\tlv2:requiredFeature\n" + "\t\turid:map ,\n" + "\t\topts:options ,\n" + "\t\tbufs:boundedBlockLength ;\n"; + + for (const auto isInput : { true, false }) + { + const auto kind = isInput ? "mainInput" : "mainOutput"; + + if (proc.getBusCount (isInput) > 0) + os << "\tpg:" << kind << " plug:" << getBaseBusName (isInput) << "1 ;\n"; + } + + if (legacyParameters.size() != 0) + { + for (const auto header : { "writable", "readable" }) + { + os << "\tpatch:" << header; + + bool isFirst = true; + + for (const auto* param : legacyParameters) + { + os << (isFirst ? "" : " ,") << "\n\t\tplug:" << ParameterStorage::getIri (*param); + isFirst = false; + } + + os << " ;\n"; + } + } + + os << "\tlv2:port [\n"; + + const PortIndices indices (proc.getTotalNumInputChannels(), + proc.getTotalNumOutputChannels()); + + const auto designationMap = [&] + { + std::map result; + + for (const auto& pair : lv2_shared::channelDesignationMap) + result.emplace (pair.second, pair.first); + + return result; + }(); + + // TODO add support for specific audio group kinds + for (const auto isInput : { true, false }) + { + const auto baseBusName = getBaseBusName (isInput); + const auto portKind = isInput ? "InputPort" : "OutputPort"; + const auto portName = isInput ? "Audio In " : "Audio Out "; + const auto portSymbol = isInput ? "audio_in_" : "audio_out_"; + const auto busCount = proc.getBusCount (isInput); + + auto channelCounter = 0; + + for (auto busIndex = 0; busIndex < busCount; ++busIndex) + { + if (const auto* bus = proc.getBus (isInput, busIndex)) + { + const auto channelCount = bus->getNumberOfChannels(); + const auto optionalBus = ! bus->isEnabledByDefault(); + + for (auto channelIndex = 0; channelIndex < channelCount; ++channelIndex, ++channelCounter) + { + const auto portIndex = isInput ? indices.getPortIndexForAudioInput (channelCounter) + : indices.getPortIndexForAudioOutput (channelCounter); + + os << "\t\ta lv2:" << portKind << " , lv2:AudioPort ;\n" + "\t\tlv2:index " << portIndex << " ;\n" + "\t\tlv2:symbol \"" << portSymbol << (channelCounter + 1) << "\" ;\n" + "\t\tlv2:name \"" << portName << (channelCounter + 1) << "\" ;\n" + "\t\tpg:group plug:" << baseBusName << (busIndex + 1) << " ;\n"; + + if (optionalBus) + os << "\t\tlv2:portProperty lv2:connectionOptional ;\n"; + + const auto designation = bus->getCurrentLayout().getTypeOfChannel (channelIndex); + const auto it = designationMap.find (designation); + + if (it != designationMap.end()) + os << "\t\tlv2:designation <" << it->second << "> ;\n"; + + os << "\t] , [\n"; + } + } + } + } + + // In the event that the plugin decides to send all of its parameters in one go, + // we should ensure that the output buffer is large enough to accommodate, with some + // extra room for the sequence header, MIDI messages etc.. + const auto patchSetSizeBytes = 72; + const auto additionalSize = 8192; + const auto atomPortMinSize = proc.getParameters().size() * patchSetSizeBytes + additionalSize; + + os << "\t\ta lv2:InputPort , atom:AtomPort ;\n" + "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" + "\t\tatom:bufferType atom:Sequence ;\n" + "\t\tatom:supports\n"; + + #if ! JucePlugin_IsSynth && ! JucePlugin_IsMidiEffect + if (proc.acceptsMidi()) + #endif + os << "\t\t\tmidi:MidiEvent ,\n"; + + os << "\t\t\tpatch:Message ,\n" + "\t\t\ttime:Position ;\n" + "\t\tlv2:designation lv2:control ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqInput) << " ;\n" + "\t\tlv2:symbol \"in\" ;\n" + "\t\tlv2:name \"In\" ;\n" + "\t] , [\n" + "\t\ta lv2:OutputPort , atom:AtomPort ;\n" + "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" + "\t\tatom:bufferType atom:Sequence ;\n" + "\t\tatom:supports\n"; + + #if ! JucePlugin_IsMidiEffect + if (proc.producesMidi()) + #endif + os << "\t\t\tmidi:MidiEvent ,\n"; + + os << "\t\t\tpatch:Message ;\n" + "\t\tlv2:designation lv2:control ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqOutput) << " ;\n" + "\t\tlv2:symbol \"out\" ;\n" + "\t\tlv2:name \"Out\" ;\n" + "\t] , [\n" + "\t\ta lv2:OutputPort , lv2:ControlPort ;\n" + "\t\tlv2:designation lv2:latency ;\n" + "\t\tlv2:symbol \"latency\" ;\n" + "\t\tlv2:name \"Latency\" ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::latencyOutput) << " ;\n" + "\t\tlv2:portProperty lv2:reportsLatency , lv2:integer , lv2:connectionOptional , pprop:notOnGUI ;\n" + "\t\tunits:unit units:frame ;\n" + "\t] , [\n" + "\t\ta lv2:InputPort , lv2:ControlPort ;\n" + "\t\tlv2:designation lv2:freeWheeling ;\n" + "\t\tlv2:symbol \"freeWheeling\" ;\n" + "\t\tlv2:name \"Free Wheeling\" ;\n" + "\t\tlv2:default 0.0 ;\n" + "\t\tlv2:minimum 0.0 ;\n" + "\t\tlv2:maximum 1.0 ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::freeWheelingInput) << " ;\n" + "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" + "\t] , [\n" + "\t\ta lv2:InputPort , lv2:ControlPort ;\n" + "\t\tlv2:designation lv2:enabled ;\n" + "\t\tlv2:symbol \"enabled\" ;\n" + "\t\tlv2:name \"Enabled\" ;\n" + "\t\tlv2:default 1.0 ;\n" + "\t\tlv2:minimum 0.0 ;\n" + "\t\tlv2:maximum 1.0 ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::enabledInput) << " ;\n" + "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" + "\t] ;\n" + "\topts:supportedOption\n" + "\t\tbufs:maxBlockLength .\n"; + + return Result::ok(); + } + + static Result writeUiTtl (AudioProcessor& proc, const File& libraryPath) + { + if (! proc.hasEditor()) + return Result::ok(); + + auto os = openStream (libraryPath, "ui"); + + if (const auto result = prepareStream (os); ! result) + return result; + + const auto editorInstance = rawToUniquePtr (proc.createEditor()); + const auto resizeFeatureString = editorInstance->isResizable() ? "ui:resize" : "ui:noUserResize"; + + os << "@prefix lv2: .\n" + "@prefix opts: .\n" + "@prefix param: .\n" + "@prefix ui: .\n" + "@prefix urid: .\n" + "\n" + "<" << JucePluginLV2UriUi << ">\n" + "\tlv2:extensionData\n" + #if JUCE_LINUX || JUCE_BSD + "\t\tui:idleInterface ,\n" + #endif + "\t\topts:interface ,\n" + "\t\tui:noUserResize ,\n" // resize and noUserResize are always present in the extension data array + "\t\tui:resize ;\n" + "\n" + "\tlv2:requiredFeature\n" + #if JUCE_LINUX || JUCE_BSD + "\t\tui:idleInterface ,\n" + #endif + "\t\turid:map ,\n" + "\t\tui:parent ,\n" + "\t\t ;\n" + "\n" + "\tlv2:optionalFeature\n" + "\t\t" << resizeFeatureString << " ,\n" + "\t\topts:interface ,\n" + "\t\topts:options ;\n\n" + "\topts:supportedOption\n" + "\t\tui:scaleFactor ,\n" + "\t\tparam:sampleRate .\n"; + + return Result::ok(); + } + + JUCE_LEAK_DETECTOR (RecallFeature) +}; + +//============================================================================== +LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index) +{ + if (index != 0) + return nullptr; + + static const LV2_Descriptor descriptor + { + JucePlugin_LV2URI, // TODO some constexpr check that this is a valid URI in terms of RFC 3986 + [] (const LV2_Descriptor*, + double sampleRate, + const char* pathToBundle, + const LV2_Feature* const* features) -> LV2_Handle + { + const auto* mapFeature = findMatchingFeatureData (features, LV2_URID__map); + + if (mapFeature == nullptr) + { + // The host doesn't provide the 'urid map' feature + jassertfalse; + return nullptr; + } + + const auto boundedBlockLength = hasFeature (features, LV2_BUF_SIZE__boundedBlockLength); + + if (! boundedBlockLength) + { + // The host doesn't provide the 'bounded block length' feature + jassertfalse; + return nullptr; + } + + const auto* options = findMatchingFeatureData (features, LV2_OPTIONS__options); + + if (options == nullptr) + { + // The host doesn't provide the 'options' feature + jassertfalse; + return nullptr; + } + + const lv2_shared::NumericAtomParser parser { *mapFeature }; + const auto blockLengthUrid = mapFeature->map (mapFeature->handle, LV2_BUF_SIZE__maxBlockLength); + const auto blockSize = parser.parseNumericOption (findMatchingOption (options, blockLengthUrid)); + + if (! blockSize.hasValue()) + { + // The host doesn't specify a maximum block size + jassertfalse; + return nullptr; + } + + return new LV2PluginInstance { sampleRate, *blockSize, pathToBundle, *mapFeature }; + }, + [] (LV2_Handle instance, uint32_t port, void* data) + { + static_cast (instance)->connect (port, data); + }, + [] (LV2_Handle instance) { static_cast (instance)->activate(); }, + [] (LV2_Handle instance, uint32_t sampleCount) + { + static_cast (instance)->run (sampleCount); + }, + [] (LV2_Handle instance) { static_cast (instance)->deactivate(); }, + [] (LV2_Handle instance) + { + JUCE_AUTORELEASEPOOL + { + delete static_cast (instance); + } + }, + [] (const char* uri) -> const void* + { + const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; + + static RecallFeature recallFeature; + + static LV2_State_Interface stateInterface + { + [] (LV2_Handle instance, + LV2_State_Store_Function store, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) -> LV2_State_Status + { + return static_cast (instance)->store (store, handle, flags, features); + }, + [] (LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) -> LV2_State_Status + { + return static_cast (instance)->retrieve (retrieve, handle, flags, features); + } + }; + + static const LV2_Feature features[] { { JUCE_TURTLE_RECALL_URI, &recallFeature }, + { LV2_STATE__interface, &stateInterface } }; + + const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); + return it != std::end (features) ? it->data : nullptr; + } + }; + + return &descriptor; +} + +static Optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options) +{ + if (options == nullptr || symap == nullptr) + return {}; + + const lv2_shared::NumericAtomParser parser { *symap }; + const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); + const auto* scaleFactorOption = findMatchingOption (options, scaleFactorUrid); + return parser.parseNumericOption (scaleFactorOption); +} + +class LV2UIInstance : private Component, + private ComponentListener +{ +public: + LV2UIInstance (const char*, + const char*, + LV2UI_Write_Function writeFunctionIn, + LV2UI_Controller controllerIn, + LV2UI_Widget* widget, + LV2PluginInstance* pluginIn, + LV2UI_Widget parentIn, + const LV2_URID_Map* symapIn, + const LV2UI_Resize* resizeFeatureIn, + Optional scaleFactorIn) + : writeFunction (writeFunctionIn), + controller (controllerIn), + plugin (pluginIn), + parent (parentIn), + symap (symapIn), + resizeFeature (resizeFeatureIn), + scaleFactor (scaleFactorIn), + editor (plugin->createEditor()) + { + jassert (plugin != nullptr); + jassert (parent != nullptr); + jassert (editor != nullptr); + + if (editor == nullptr) + return; + + const auto bounds = getSizeToContainChild(); + setSize (bounds.getWidth(), bounds.getHeight()); + + addAndMakeVisible (*editor); + + setBroughtToFrontOnMouseClick (true); + setOpaque (true); + setVisible (false); + removeFromDesktop(); + addToDesktop (detail::PluginUtilities::getDesktopFlags (editor.get()), parent); + editor->addComponentListener (this); + + *widget = getWindowHandle(); + + setVisible (true); + + editor->setScaleFactor (getScaleFactor()); + requestResize(); + } + + ~LV2UIInstance() override + { + plugin->editorBeingDeleted (editor.get()); + } + + // This is called by the host when a parameter changes. + // We don't care, our UI will listen to the processor directly. + void portEvent (uint32_t, uint32_t, uint32_t, const void*) {} + + // Called when the host requests a resize + int resize (int width, int height) + { + const ScopedValueSetter scope (hostRequestedResize, true); + setSize (width, height); + return 0; + } + + // Called by the host to give us an opportunity to process UI events + void idleCallback() + { + #if JUCE_LINUX || JUCE_BSD + messageThread->processPendingEvents(); + #endif + } + + void resized() override + { + const ScopedValueSetter scope (hostRequestedResize, true); + + if (editor != nullptr) + { + const auto localArea = editor->getLocalArea (this, getLocalBounds()); + editor->setBoundsConstrained ({ localArea.getWidth(), localArea.getHeight() }); + } + } + + void paint (Graphics& g) override { g.fillAll (Colours::black); } + + uint32_t getOptions (LV2_Options_Option* options) + { + const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); + const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; + + for (auto* opt = options; opt->key != 0; ++opt) + { + if (opt->context != LV2_OPTIONS_INSTANCE || opt->subject != 0 || opt->key != scaleFactorUrid) + continue; + + if (scaleFactor.hasValue()) + { + opt->type = floatUrid; + opt->size = sizeof (float); + opt->value = &(*scaleFactor); + } + } + + return LV2_OPTIONS_SUCCESS; + } + + uint32_t setOptions (const LV2_Options_Option* options) + { + const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); + const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; + + for (auto* opt = options; opt->key != 0; ++opt) + { + if (opt->context != LV2_OPTIONS_INSTANCE + || opt->subject != 0 + || opt->key != scaleFactorUrid + || opt->type != floatUrid + || opt->size != sizeof (float)) + { + continue; + } + + scaleFactor = *static_cast (opt->value); + updateScale(); + } + + return LV2_OPTIONS_SUCCESS; + } + +private: + void updateScale() + { + editor->setScaleFactor (getScaleFactor()); + requestResize(); + } + + Rectangle getSizeToContainChild() const + { + if (editor != nullptr) + return getLocalArea (editor.get(), editor->getLocalBounds()); + + return {}; + } + + float getScaleFactor() const noexcept + { + return scaleFactor.hasValue() ? *scaleFactor : 1.0f; + } + + void componentMovedOrResized (Component&, bool, bool wasResized) override + { + if (! hostRequestedResize && wasResized) + requestResize(); + } + + void write (uint32_t portIndex, uint32_t bufferSize, uint32_t portProtocol, const void* data) + { + writeFunction (controller, portIndex, bufferSize, portProtocol, data); + } + + void requestResize() + { + if (editor == nullptr) + return; + + const auto bounds = getSizeToContainChild(); + + if (resizeFeature == nullptr) + return; + + if (auto* fn = resizeFeature->ui_resize) + fn (resizeFeature->handle, bounds.getWidth(), bounds.getHeight()); + + setSize (bounds.getWidth(), bounds.getHeight()); + repaint(); + } + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + LV2UI_Write_Function writeFunction; + LV2UI_Controller controller; + LV2PluginInstance* plugin; + LV2UI_Widget parent; + const LV2_URID_Map* symap = nullptr; + const LV2UI_Resize* resizeFeature = nullptr; + Optional scaleFactor; + std::unique_ptr editor; + bool hostRequestedResize = false; + + JUCE_LEAK_DETECTOR (LV2UIInstance) +}; + +LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor (uint32_t index) +{ + if (index != 0) + return nullptr; + + static const LV2UI_Descriptor descriptor + { + JucePluginLV2UriUi.toRawUTF8(), // TODO some constexpr check that this is a valid URI in terms of RFC 3986 + [] (const LV2UI_Descriptor*, + const char* pluginUri, + const char* bundlePath, + LV2UI_Write_Function writeFunction, + LV2UI_Controller controller, + LV2UI_Widget* widget, + const LV2_Feature* const* features) -> LV2UI_Handle + { + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + auto* plugin = findMatchingFeatureData (features, LV2_INSTANCE_ACCESS_URI); + + if (plugin == nullptr) + { + // No instance access + jassertfalse; + return nullptr; + } + + auto* parent = findMatchingFeatureData (features, LV2_UI__parent); + + if (parent == nullptr) + { + // No parent access + jassertfalse; + return nullptr; + } + + auto* resizeFeature = findMatchingFeatureData (features, LV2_UI__resize); + + const auto* symap = findMatchingFeatureData (features, LV2_URID__map); + const auto scaleFactor = findScaleFactor (symap, findMatchingFeatureData (features, LV2_OPTIONS__options)); + + return new LV2UIInstance { pluginUri, + bundlePath, + writeFunction, + controller, + widget, + plugin, + parent, + symap, + resizeFeature, + scaleFactor }; + }, + [] (LV2UI_Handle ui) + { + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + JUCE_AUTORELEASEPOOL + { + delete static_cast (ui); + } + }, + [] (LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) + { + JUCE_ASSERT_MESSAGE_THREAD + static_cast (ui)->portEvent (portIndex, bufferSize, format, buffer); + }, + [] (const char* uri) -> const void* + { + const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; + + static LV2UI_Resize resize { nullptr, [] (LV2UI_Feature_Handle handle, int width, int height) -> int + { + JUCE_ASSERT_MESSAGE_THREAD + return static_cast (handle)->resize (width, height); + } }; + + static LV2UI_Idle_Interface idle { [] (LV2UI_Handle handle) + { + static_cast (handle)->idleCallback(); + return 0; + } }; + + static LV2_Options_Interface options + { + [] (LV2_Handle handle, LV2_Options_Option* optionsIn) + { + return static_cast (handle)->getOptions (optionsIn); + }, + [] (LV2_Handle handle, const LV2_Options_Option* optionsIn) + { + return static_cast (handle)->setOptions (optionsIn); + } + }; + + // We'll always define noUserResize and idle in the extension data array, but we'll + // only declare them in the ui.ttl if the UI is actually non-resizable, or requires + // idle callbacks. + // Well-behaved hosts should check the ttl before trying to search the + // extension-data array. + static const LV2_Feature features[] { { LV2_UI__resize, &resize }, + { LV2_UI__noUserResize, nullptr }, + { LV2_UI__idleInterface, &idle }, + { LV2_OPTIONS__interface, &options } }; + + const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); + return it != std::end (features) ? it->data : nullptr; + } + }; + + return &descriptor; +} + +} +} + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp index e4c5c25bb0..1374cdf3c7 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp @@ -31,7 +31,152 @@ #error To compile AudioUnitv3 and/or Standalone plug-ins, you need to add the juce_audio_utils and juce_audio_devices modules! #endif -#include "Standalone/juce_StandaloneFilterApp.cpp" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +// You can set this flag in your build if you need to specify a different +// standalone JUCEApplication class for your app to use. If you don't +// set it then by default we'll just create a simple one as below. +#if ! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP + +#include + +namespace juce +{ + +//============================================================================== +class StandaloneFilterApp : public JUCEApplication +{ +public: + StandaloneFilterApp() + { + PropertiesFile::Options options; + + options.applicationName = getApplicationName(); + options.filenameSuffix = ".settings"; + options.osxLibrarySubFolder = "Application Support"; + #if JUCE_LINUX || JUCE_BSD + options.folderName = "~/.config"; + #else + options.folderName = ""; + #endif + + appProperties.setStorageParameters (options); + } + + const String getApplicationName() override { return CharPointer_UTF8 (JucePlugin_Name); } + const String getApplicationVersion() override { return JucePlugin_VersionString; } + bool moreThanOneInstanceAllowed() override { return true; } + void anotherInstanceStarted (const String&) override {} + + virtual StandaloneFilterWindow* createWindow() + { + #ifdef JucePlugin_PreferredChannelConfigurations + StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations }; + #endif + + return new StandaloneFilterWindow (getApplicationName(), + LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), + appProperties.getUserSettings(), + false, {}, nullptr + #ifdef JucePlugin_PreferredChannelConfigurations + , juce::Array (channels, juce::numElementsInArray (channels)) + #else + , {} + #endif + #if JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE + , false + #endif + ); + } + + //============================================================================== + void initialise (const String&) override + { + mainWindow.reset (createWindow()); + + #if JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE + Desktop::getInstance().setKioskModeComponent (mainWindow.get(), false); + #endif + + mainWindow->setVisible (true); + } + + void shutdown() override + { + mainWindow = nullptr; + appProperties.saveIfNeeded(); + } + + //============================================================================== + void systemRequestedQuit() override + { + if (mainWindow.get() != nullptr) + mainWindow->pluginHolder->savePluginState(); + + if (ModalComponentManager::getInstance()->cancelAllModalComponents()) + { + Timer::callAfterDelay (100, []() + { + if (auto app = JUCEApplicationBase::getInstance()) + app->systemRequestedQuit(); + }); + } + else + { + quit(); + } + } + +protected: + ApplicationProperties appProperties; + std::unique_ptr mainWindow; +}; + +} // namespace juce + +#if JucePlugin_Build_Standalone && JUCE_IOS + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +using namespace juce; + +bool JUCE_CALLTYPE juce_isInterAppAudioConnected() +{ + if (auto holder = StandalonePluginHolder::getInstance()) + return holder->isInterAppAudioConnected(); + + return false; +} + +void JUCE_CALLTYPE juce_switchToHostApplication() +{ + if (auto holder = StandalonePluginHolder::getInstance()) + holder->switchToHostApplication(); +} + +Image JUCE_CALLTYPE juce_getIAAHostIcon (int size) +{ + if (auto holder = StandalonePluginHolder::getInstance()) + return holder->getIAAHostIcon (size); + + return Image(); +} + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#endif + +#endif #if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP extern juce::JUCEApplicationBase* juce_CreateApplication(); diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp index 0667545141..c624322712 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp @@ -23,4 +23,752 @@ ============================================================================== */ -#include "Unity/juce_Unity_Wrapper.cpp" +#include + +#if JucePlugin_Build_Unity + +#include +#include + +#if JUCE_WINDOWS + #include +#endif + +#include + +//============================================================================== +namespace juce +{ + +typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); +extern createUnityPeerFunctionType juce_createUnityPeerFn; + +//============================================================================== +class UnityPeer : public ComponentPeer, + public AsyncUpdater +{ +public: + UnityPeer (Component& ed) + : ComponentPeer (ed, 0), + mouseWatcher (*this) + { + getEditor().setResizable (false, false); + } + + //============================================================================== + Rectangle getBounds() const override { return bounds; } + Point localToGlobal (Point relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } + Point globalToLocal (Point screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } + + using ComponentPeer::localToGlobal; + using ComponentPeer::globalToLocal; + + StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } + + void setBounds (const Rectangle& newBounds, bool) override + { + bounds = newBounds; + mouseWatcher.setBoundsToWatch (bounds); + } + + bool contains (Point localPos, bool) const override + { + if (isPositiveAndBelow (localPos.getX(), getBounds().getWidth()) + && isPositiveAndBelow (localPos.getY(), getBounds().getHeight())) + return true; + + return false; + } + + void handleAsyncUpdate() override + { + fillPixels(); + } + + //============================================================================== + AudioProcessorEditor& getEditor() { return *dynamic_cast (&getComponent()); } + + void setPixelDataHandle (uint8* handle, int width, int height) + { + pixelData = handle; + + textureWidth = width; + textureHeight = height; + + renderImage = Image (new UnityBitmapImage (pixelData, width, height)); + } + + // N.B. This is NOT an efficient way to do this and you shouldn't use this method in your own code. + // It works for our purposes here but a much more efficient way would be to use a GL texture. + void fillPixels() + { + if (pixelData == nullptr) + return; + + LowLevelGraphicsSoftwareRenderer renderer (renderImage); + renderer.addTransform (AffineTransform::verticalFlip ((float) getComponent().getHeight())); + + handlePaint (renderer); + + for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) + { + auto r = pixelData[i + 2]; + auto g = pixelData[i + 1]; + auto b = pixelData[i + 0]; + + pixelData[i + 0] = r; + pixelData[i + 1] = g; + pixelData[i + 2] = b; + } + } + + void forwardMouseEvent (Point position, ModifierKeys mods) + { + ModifierKeys::currentModifiers = mods; + + handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::defaultPressure, + juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); + } + + void forwardKeyPress (int code, String name, ModifierKeys mods) + { + ModifierKeys::currentModifiers = mods; + + handleKeyPress (getKeyPress (code, name)); + } + +private: + //============================================================================== + struct UnityBitmapImage : public ImagePixelData + { + UnityBitmapImage (uint8* data, int w, int h) + : ImagePixelData (Image::PixelFormat::ARGB, w, h), + imageData (data), + lineStride (width * pixelStride) + { + } + + std::unique_ptr createType() const override + { + return std::make_unique(); + } + + std::unique_ptr createLowLevelContext() override + { + return std::make_unique (Image (this)); + } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, [[maybe_unused]] Image::BitmapData::ReadWriteMode mode) override + { + const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; + bitmap.data = imageData + offset; + bitmap.size = (size_t) (lineStride * height) - offset; + bitmap.pixelFormat = pixelFormat; + bitmap.lineStride = lineStride; + bitmap.pixelStride = pixelStride; + } + + ImagePixelData::Ptr clone() override + { + auto im = new UnityBitmapImage (imageData, width, height); + + for (int i = 0; i < height; ++i) + memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); + + return im; + } + + uint8* imageData; + int pixelStride = 4, lineStride; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityBitmapImage) + }; + + //============================================================================== + struct MouseWatcher : public Timer + { + MouseWatcher (ComponentPeer& o) : owner (o) {} + + void timerCallback() override + { + auto pos = Desktop::getMousePosition(); + + if (boundsToWatch.contains (pos) && pos != lastMousePos) + { + auto ms = Desktop::getInstance().getMainMouseSource(); + + if (! ms.getCurrentModifiers().isLeftButtonDown()) + owner.handleMouseEvent (juce::MouseInputSource::mouse, owner.globalToLocal (pos.toFloat()), {}, + juce::MouseInputSource::defaultPressure, juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); + + lastMousePos = pos; + } + + } + + void setBoundsToWatch (Rectangle b) + { + if (boundsToWatch != b) + boundsToWatch = b; + + startTimer (250); + } + + ComponentPeer& owner; + Rectangle boundsToWatch; + Point lastMousePos; + }; + + //============================================================================== + KeyPress getKeyPress (int keyCode, String name) + { + if (keyCode >= 32 && keyCode <= 64) + return { keyCode, ModifierKeys::currentModifiers, juce::juce_wchar (keyCode) }; + + if (keyCode >= 91 && keyCode <= 122) + return { keyCode, ModifierKeys::currentModifiers, name[0] }; + + if (keyCode >= 256 && keyCode <= 265) + return { juce::KeyPress::numberPad0 + (keyCode - 256), ModifierKeys::currentModifiers, juce::String (keyCode - 256).getCharPointer()[0] }; + + if (keyCode == 8) return { juce::KeyPress::backspaceKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 127) return { juce::KeyPress::deleteKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 9) return { juce::KeyPress::tabKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 13) return { juce::KeyPress::returnKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 27) return { juce::KeyPress::escapeKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 32) return { juce::KeyPress::spaceKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 266) return { juce::KeyPress::numberPadDecimalPoint, ModifierKeys::currentModifiers, {} }; + if (keyCode == 267) return { juce::KeyPress::numberPadDivide, ModifierKeys::currentModifiers, {} }; + if (keyCode == 268) return { juce::KeyPress::numberPadMultiply, ModifierKeys::currentModifiers, {} }; + if (keyCode == 269) return { juce::KeyPress::numberPadSubtract, ModifierKeys::currentModifiers, {} }; + if (keyCode == 270) return { juce::KeyPress::numberPadAdd, ModifierKeys::currentModifiers, {} }; + if (keyCode == 272) return { juce::KeyPress::numberPadEquals, ModifierKeys::currentModifiers, {} }; + if (keyCode == 273) return { juce::KeyPress::upKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 274) return { juce::KeyPress::downKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 275) return { juce::KeyPress::rightKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 276) return { juce::KeyPress::leftKey, ModifierKeys::currentModifiers, {} }; + + return {}; + } + + //============================================================================== + Rectangle bounds; + MouseWatcher mouseWatcher; + + uint8* pixelData = nullptr; + int textureWidth, textureHeight; + Image renderImage; + + //============================================================================== + void setMinimised (bool) override {} + bool isMinimised() const override { return false; } + void setFullScreen (bool) override {} + bool isFullScreen() const override { return false; } + bool setAlwaysOnTop (bool) override { return false; } + void toFront (bool) override {} + void toBehind (ComponentPeer*) override {} + bool isFocused() const override { return true; } + void grabFocus() override {} + void* getNativeHandle() const override { return nullptr; } + OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } + BorderSize getFrameSize() const override { return {}; } + void setVisible (bool) override {} + void setTitle (const String&) override {} + void setIcon (const Image&) override {} + void textInputRequired (Point, TextInputTarget&) override {} + void setAlpha (float) override {} + void performAnyPendingRepaintsNow() override {} + void repaint (const Rectangle&) override {} + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) +}; + +static ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } + +//============================================================================== +class AudioProcessorUnityWrapper +{ +public: + AudioProcessorUnityWrapper (bool isTemporary) + { + pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_Unity); + + if (! isTemporary && pluginInstance->hasEditor()) + { + pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); + pluginInstanceEditor->setVisible (true); + detail::PluginUtilities::addToDesktop (*pluginInstanceEditor, nullptr); + } + + juceParameters.update (*pluginInstance, false); + } + + ~AudioProcessorUnityWrapper() + { + if (pluginInstanceEditor != nullptr) + { + pluginInstanceEditor->removeFromDesktop(); + + PopupMenu::dismissAllActiveMenus(); + pluginInstanceEditor->processor.editorBeingDeleted (pluginInstanceEditor.get()); + pluginInstanceEditor = nullptr; + } + } + + void create (UnityAudioEffectState* state) + { + // only supported in Unity plugin API > 1.0 + if (state->structSize >= sizeof (UnityAudioEffectState)) + samplesPerBlock = static_cast (state->dspBufferSize); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + jassertquiet (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + + pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], state->sampleRate, samplesPerBlock); + #else + pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); + #endif + + pluginInstance->prepareToPlay (state->sampleRate, samplesPerBlock); + + scratchBuffer.setSize (jmax (pluginInstance->getTotalNumInputChannels(), pluginInstance->getTotalNumOutputChannels()), samplesPerBlock); + } + + void release() + { + pluginInstance->releaseResources(); + } + + void reset() + { + pluginInstance->reset(); + } + + void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) + { + // If the plugin has a bypass parameter, set it to the current bypass state + if (auto* param = pluginInstance->getBypassParameter()) + if (isBypassed != (param->getValue() >= 0.5f)) + param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); + + for (int pos = 0; pos < bufferSize;) + { + auto max = jmin (bufferSize - pos, samplesPerBlock); + processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); + + pos += max; + } + } + + void declareParameters (UnityAudioEffectDefinition& definition) + { + static std::unique_ptr parametersPtr; + static int numParams = 0; + + if (parametersPtr == nullptr) + { + numParams = (int) juceParameters.size(); + + parametersPtr.reset (static_cast (std::calloc (static_cast (numParams), + sizeof (UnityAudioParameterDefinition)))); + + parameterDescriptions.clear(); + + for (int i = 0; i < numParams; ++i) + { + auto* parameter = juceParameters.getParamForIndex (i); + auto& paramDef = parametersPtr.get()[i]; + + const auto nameLength = (size_t) numElementsInArray (paramDef.name); + const auto unitLength = (size_t) numElementsInArray (paramDef.unit); + + parameter->getName ((int) nameLength - 1).copyToUTF8 (paramDef.name, nameLength); + + if (parameter->getLabel().isNotEmpty()) + parameter->getLabel().copyToUTF8 (paramDef.unit, unitLength); + + parameterDescriptions.add (parameter->getName (15)); + paramDef.description = parameterDescriptions[i].toRawUTF8(); + + paramDef.defaultVal = parameter->getDefaultValue(); + paramDef.min = 0.0f; + paramDef.max = 1.0f; + paramDef.displayScale = 1.0f; + paramDef.displayExponent = 1.0f; + } + } + + definition.numParameters = static_cast (numParams); + definition.parameterDefintions = parametersPtr.get(); + } + + void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValueNotifyingHost (value); } + float getParameter (int index) const noexcept { return juceParameters.getParamForIndex (index)->getValue(); } + + String getParameterString (int index) const noexcept + { + auto* param = juceParameters.getParamForIndex (index); + return param->getText (param->getValue(), 16); + } + + int getNumInputChannels() const noexcept { return pluginInstance->getTotalNumInputChannels(); } + int getNumOutputChannels() const noexcept { return pluginInstance->getTotalNumOutputChannels(); } + + bool hasEditor() const noexcept { return pluginInstance->hasEditor(); } + + UnityPeer& getEditorPeer() const + { + auto* peer = dynamic_cast (pluginInstanceEditor->getPeer()); + + jassert (peer != nullptr); + return *peer; + } + +private: + //============================================================================== + void processBuffers (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) + { + int ch; + for (ch = 0; ch < numInChannels; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + DstSampleType dstData (scratchBuffer.getWritePointer (ch)); + SrcSampleType srcData (inBuffer + ch, numInChannels); + dstData.convertSamples (srcData, bufferSize); + } + + for (; ch < numOutChannels; ++ch) + scratchBuffer.clear (ch, 0, bufferSize); + + { + const ScopedLock sl (pluginInstance->getCallbackLock()); + + if (pluginInstance->isSuspended()) + { + scratchBuffer.clear(); + } + else + { + MidiBuffer mb; + + if (isBypassed && pluginInstance->getBypassParameter() == nullptr) + pluginInstance->processBlockBypassed (scratchBuffer, mb); + else + pluginInstance->processBlock (scratchBuffer, mb); + } + } + + for (ch = 0; ch < numOutChannels; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + DstSampleType dstData (outBuffer + ch, numOutChannels); + SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); + dstData.convertSamples (srcData, bufferSize); + } + } + + //============================================================================== + std::unique_ptr pluginInstance; + std::unique_ptr pluginInstanceEditor; + + int samplesPerBlock = 1024; + StringArray parameterDescriptions; + + AudioBuffer scratchBuffer; + + LegacyAudioParametersWrapper juceParameters; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) +}; + +//============================================================================== +static HashMap& getWrapperMap() +{ + static HashMap wrapperMap; + return wrapperMap; +} + +static void onWrapperCreation (AudioProcessorUnityWrapper* wrapperToAdd) +{ + getWrapperMap().set (std::abs (Random::getSystemRandom().nextInt (65536)), wrapperToAdd); +} + +static void onWrapperDeletion (AudioProcessorUnityWrapper* wrapperToRemove) +{ + getWrapperMap().removeValue (wrapperToRemove); +} + +//============================================================================== +static UnityAudioEffectDefinition getEffectDefinition() +{ + const auto wrapper = std::make_unique (true); + const String originalName { JucePlugin_Name }; + const auto name = (! originalName.startsWithIgnoreCase ("audioplugin") ? "audioplugin_" : "") + originalName; + + UnityAudioEffectDefinition result{}; + name.copyToUTF8 (result.name, (size_t) numElementsInArray (result.name)); + + result.structSize = sizeof (UnityAudioEffectDefinition); + result.parameterStructSize = sizeof (UnityAudioParameterDefinition); + + result.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; + result.pluginVersion = JucePlugin_VersionCode; + + // effects must set this to 0, generators > 0 + result.channels = (wrapper->getNumInputChannels() != 0 ? 0 + : static_cast (wrapper->getNumOutputChannels())); + + wrapper->declareParameters (result); + + result.create = [] (UnityAudioEffectState* state) + { + auto* pluginInstance = new AudioProcessorUnityWrapper (false); + pluginInstance->create (state); + + state->effectData = pluginInstance; + + onWrapperCreation (pluginInstance); + + return 0; + }; + + result.release = [] (UnityAudioEffectState* state) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->release(); + + onWrapperDeletion (pluginInstance); + delete pluginInstance; + + if (getWrapperMap().size() == 0) + shutdownJuce_GUI(); + + return 0; + }; + + result.reset = [] (UnityAudioEffectState* state) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->reset(); + + return 0; + }; + + result.setPosition = [] (UnityAudioEffectState* state, unsigned int pos) + { + ignoreUnused (state, pos); + return 0; + }; + + result.process = [] (UnityAudioEffectState* state, + float* inBuffer, + float* outBuffer, + unsigned int bufferSize, + int numInChannels, + int numOutChannels) + { + auto* pluginInstance = state->getEffectData(); + + if (pluginInstance != nullptr) + { + auto isPlaying = ((state->flags & stateIsPlaying) != 0); + auto isMuted = ((state->flags & stateIsMuted) != 0); + auto isPaused = ((state->flags & stateIsPaused) != 0); + + const auto bypassed = ! isPlaying || (isMuted || isPaused); + pluginInstance->process (inBuffer, outBuffer, static_cast (bufferSize), numInChannels, numOutChannels, bypassed); + } + else + { + FloatVectorOperations::clear (outBuffer, static_cast (bufferSize) * numOutChannels); + } + + return 0; + }; + + result.setFloatParameter = [] (UnityAudioEffectState* state, int index, float value) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->setParameter (index, value); + + return 0; + }; + + result.getFloatParameter = [] (UnityAudioEffectState* state, int index, float* value, char* valueStr) + { + auto* pluginInstance = state->getEffectData(); + *value = pluginInstance->getParameter (index); + + pluginInstance->getParameterString (index).copyToUTF8 (valueStr, 15); + + return 0; + }; + + result.getFloatBuffer = [] (UnityAudioEffectState* state, const char* kind, float* buffer, int numSamples) + { + ignoreUnused (numSamples); + + const StringRef kindStr { kind }; + + if (kindStr == StringRef ("Editor")) + { + auto* pluginInstance = state->getEffectData(); + + buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; + } + else if (kindStr == StringRef ("ID")) + { + auto* pluginInstance = state->getEffectData(); + + for (HashMap::Iterator i (getWrapperMap()); i.next();) + { + if (i.getValue() == pluginInstance) + { + buffer[0] = (float) i.getKey(); + break; + } + } + + return 0; + } + else if (kindStr == StringRef ("Size")) + { + auto* pluginInstance = state->getEffectData(); + + auto& editor = pluginInstance->getEditorPeer().getEditor(); + + buffer[0] = (float) editor.getBounds().getWidth(); + buffer[1] = (float) editor.getBounds().getHeight(); + buffer[2] = (float) editor.getConstrainer()->getMinimumWidth(); + buffer[3] = (float) editor.getConstrainer()->getMinimumHeight(); + buffer[4] = (float) editor.getConstrainer()->getMaximumWidth(); + buffer[5] = (float) editor.getConstrainer()->getMaximumHeight(); + } + + return 0; + }; + + return result; +} + +} // namespace juce + +// From reading the example code, it seems that the triple indirection indicates +// an out-value of an array of pointers. That is, after calling this function, definitionsPtr +// should point to a pre-existing/static array of pointer-to-effect-definition. +UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) +{ + if (juce::getWrapperMap().size() == 0) + juce::initialiseJuce_GUI(); + + static std::once_flag flag; + std::call_once (flag, [] { juce::juce_createUnityPeerFn = juce::createUnityPeer; }); + + static auto definition = juce::getEffectDefinition(); + static UnityAudioEffectDefinition* definitions[] { &definition }; + *definitionsPtr = definitions; + + return 1; +} + +//============================================================================== +static juce::ModifierKeys unityModifiersToJUCE (UnityEventModifiers mods, bool mouseDown, int mouseButton = -1) +{ + int flags = 0; + + if (mouseDown) + { + if (mouseButton == 0) + flags |= juce::ModifierKeys::leftButtonModifier; + else if (mouseButton == 1) + flags |= juce::ModifierKeys::rightButtonModifier; + else if (mouseButton == 2) + flags |= juce::ModifierKeys::middleButtonModifier; + } + + if (mods == 0) + return flags; + + if ((mods & UnityEventModifiers::shift) != 0) flags |= juce::ModifierKeys::shiftModifier; + if ((mods & UnityEventModifiers::control) != 0) flags |= juce::ModifierKeys::ctrlModifier; + if ((mods & UnityEventModifiers::alt) != 0) flags |= juce::ModifierKeys::altModifier; + if ((mods & UnityEventModifiers::command) != 0) flags |= juce::ModifierKeys::commandModifier; + + return { flags }; +} + +//============================================================================== +static juce::AudioProcessorUnityWrapper* getWrapperChecked (int id) +{ + auto* wrapper = juce::getWrapperMap()[id]; + jassert (wrapper != nullptr); + + return wrapper; +} + +//============================================================================== +static void UNITY_INTERFACE_API onRenderEvent (int id) +{ + getWrapperChecked (id)->getEditorPeer().triggerAsyncUpdate(); +} + +UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback() +{ + return onRenderEvent; +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* data, int w, int h) +{ + getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast (data), w, h); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers unityMods, int button) +{ + getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers unityMods, int button) +{ + getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers unityMods) +{ + getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, false)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name) +{ + getWrapperChecked (id)->getEditorPeer().forwardKeyPress (code, name, unityModifiersToJUCE (mods, false)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h) +{ + getWrapperChecked (id)->getEditorPeer().getEditor().setBounds ({ (int) x, (int) y, (int) w, (int) h }); +} + +//============================================================================== +#if JUCE_WINDOWS + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + + extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) + { + if (reason == DLL_PROCESS_ATTACH) + juce::Process::setCurrentModuleInstanceHandle (instance); + + return true; + } + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp index befb88f108..487b356b6f 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp @@ -23,4 +23,2186 @@ ============================================================================== */ -#include "VST/juce_VST_Wrapper.cpp" +#include +#include +#include + +#if JucePlugin_Build_VST + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) + +#include +#include + +#if JucePlugin_VersionCode < 0x010000 // Major < 0 + + #if (JucePlugin_VersionCode & 0x00FF00) > (9 * 0x100) // check if Minor number exceeds 9 + JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'minor' exceeding 9") + #endif + + #if (JucePlugin_VersionCode & 0xFF) > 9 // check if Bugfix number exceeds 9 + JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'bugfix' exceeding 9") + #endif + +#elif JucePlugin_VersionCode >= 0x650000 // Major >= 101 + + #if (JucePlugin_VersionCode & 0x00FF00) > (99 * 0x100) // check if Minor number exceeds 99 + JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'minor' exceeding 99") + #endif + + #if (JucePlugin_VersionCode & 0xFF) > 99 // check if Bugfix number exceeds 99 + JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'bugfix' exceeding 99") + #endif + +#endif + +#ifdef PRAGMA_ALIGN_SUPPORTED + #undef PRAGMA_ALIGN_SUPPORTED + #define PRAGMA_ALIGN_SUPPORTED 1 +#endif + +#if ! JUCE_MSVC && ! defined (__cdecl) + #define __cdecl +#endif + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion", + "-Wshadow", + "-Wdeprecated-register", + "-Wdeprecated-declarations", + "-Wunused-parameter", + "-Wdeprecated-writable-strings", + "-Wnon-virtual-dtor", + "-Wzero-as-null-pointer-constant") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) + +#define VST_FORCE_DEPRECATED 0 + +namespace Vst2 +{ +// If the following files cannot be found then you are probably trying to build +// a VST2 plug-in or a VST2-compatible VST3 plug-in. To do this you must have a +// VST2 SDK in your header search paths or use the "VST (Legacy) SDK Folder" +// field in the Projucer. The VST2 SDK can be obtained from the +// vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK or JUCE version 5.3.2. You +// also need a VST2 license from Steinberg to distribute VST2 plug-ins. +#include "pluginterfaces/vst2.x/aeffect.h" +#include "pluginterfaces/vst2.x/aeffectx.h" +} + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +//============================================================================== +#if JUCE_MSVC + #pragma pack (push, 8) +#endif + +#define JUCE_VSTINTERFACE_H_INCLUDED 1 +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 + +#include + +using namespace juce; + +#include +#include +#include + +#include +#include + +#ifdef JUCE_MSVC + #pragma pack (pop) +#endif + +#undef MemoryBlock + +class JuceVSTWrapper; +static bool recursionCheck = false; + +namespace juce +{ + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + JUCE_API double getScaleFactorForWindow (HWND); + #endif +} + +//============================================================================== +#if JUCE_WINDOWS + +namespace +{ + // Returns the actual container window, unlike GetParent, which can also return a separate owner window. + static HWND getWindowParent (HWND w) noexcept { return GetAncestor (w, GA_PARENT); } + + static HWND findMDIParentOf (HWND w) + { + const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); + + while (w != nullptr) + { + auto parent = getWindowParent (w); + + if (parent == nullptr) + break; + + TCHAR windowType[32] = { 0 }; + GetClassName (parent, windowType, 31); + + if (String (windowType).equalsIgnoreCase ("MDIClient")) + return parent; + + RECT windowPos, parentPos; + GetWindowRect (w, &windowPos); + GetWindowRect (parent, &parentPos); + + auto dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); + auto dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); + + if (dw > 100 || dh > 100) + break; + + w = parent; + + if (dw == 2 * frameThickness) + break; + } + + return w; + } + + static int numActivePlugins = 0; + static bool messageThreadIsDefinitelyCorrect = false; +} + +#endif + +//============================================================================== +// Ableton Live host specific commands +struct AbletonLiveHostSpecific +{ + enum + { + KCantBeSuspended = (1 << 2) + }; + + uint32 magic; // 'AbLi' + int cmd; // 5 = realtime properties + size_t commandSize; // sizeof (int) + int flags; // KCantBeSuspended = (1 << 2) +}; + +//============================================================================== +/** + This is an AudioEffectX object that holds and wraps our AudioProcessor... +*/ +class JuceVSTWrapper : public AudioProcessorListener, + public AudioPlayHead, + private Timer, + private AudioProcessorParameter::Listener +{ +private: + //============================================================================== + template + struct VstTempBuffers + { + VstTempBuffers() {} + ~VstTempBuffers() { release(); } + + void release() noexcept + { + for (auto* c : tempChannels) + delete[] c; + + tempChannels.clear(); + } + + HeapBlock channels; + Array tempChannels; // see note in processReplacing() + juce::AudioBuffer processTempBuffer; + }; + + /** Use the same names as the VST SDK. */ + struct VstOpCodeArguments + { + int32 index; + pointer_sized_int value; + void* ptr; + float opt; + }; + +public: + //============================================================================== + JuceVSTWrapper (Vst2::audioMasterCallback cb, std::unique_ptr af) + : hostCallback (cb), + processor (std::move (af)) + { + inParameterChangedCallback = false; + + // VST-2 does not support disabling buses: so always enable all of them + processor->enableAllBuses(); + + findMaxTotalChannels (maxNumInChannels, maxNumOutChannels); + + // You must at least have some channels + jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); + + if (processor->isMidiEffect()) + maxNumInChannels = maxNumOutChannels = 2; + + #ifdef JucePlugin_PreferredChannelConfigurations + processor->setPlayConfigDetails (maxNumInChannels, maxNumOutChannels, 44100.0, 1024); + #endif + + processor->setRateAndBufferSizeDetails (0, 0); + processor->setPlayHead (this); + processor->addListener (this); + + if (auto* juceParam = processor->getBypassParameter()) + juceParam->addListener (this); + + juceParameters.update (*processor, false); + + memset (&vstEffect, 0, sizeof (vstEffect)); + vstEffect.magic = 0x56737450 /* 'VstP' */; + vstEffect.dispatcher = (Vst2::AEffectDispatcherProc) dispatcherCB; + vstEffect.process = nullptr; + vstEffect.setParameter = (Vst2::AEffectSetParameterProc) setParameterCB; + vstEffect.getParameter = (Vst2::AEffectGetParameterProc) getParameterCB; + vstEffect.numPrograms = jmax (1, processor->getNumPrograms()); + vstEffect.numParams = juceParameters.getNumParameters(); + vstEffect.numInputs = maxNumInChannels; + vstEffect.numOutputs = maxNumOutChannels; + vstEffect.initialDelay = processor->getLatencySamples(); + vstEffect.object = this; + vstEffect.uniqueID = JucePlugin_VSTUniqueID; + + #ifdef JucePlugin_VSTChunkStructureVersion + vstEffect.version = JucePlugin_VSTChunkStructureVersion; + #else + vstEffect.version = JucePlugin_VersionCode; + #endif + + vstEffect.processReplacing = (Vst2::AEffectProcessProc) processReplacingCB; + vstEffect.processDoubleReplacing = (Vst2::AEffectProcessDoubleProc) processDoubleReplacingCB; + + vstEffect.flags |= Vst2::effFlagsHasEditor; + + vstEffect.flags |= Vst2::effFlagsCanReplacing; + if (processor->supportsDoublePrecisionProcessing()) + vstEffect.flags |= Vst2::effFlagsCanDoubleReplacing; + + vstEffect.flags |= Vst2::effFlagsProgramChunks; + + #if JucePlugin_IsSynth + vstEffect.flags |= Vst2::effFlagsIsSynth; + #else + if (processor->getTailLengthSeconds() == 0.0) + vstEffect.flags |= Vst2::effFlagsNoSoundInStop; + #endif + + #if JUCE_WINDOWS + ++numActivePlugins; + #endif + } + + ~JuceVSTWrapper() override + { + JUCE_AUTORELEASEPOOL + { + #if JUCE_LINUX || JUCE_BSD + MessageManagerLock mmLock; + #endif + + stopTimer(); + deleteEditor (false); + + hasShutdown = true; + + processor = nullptr; + + jassert (editorComp == nullptr); + + deleteTempChannels(); + + #if JUCE_WINDOWS + if (--numActivePlugins == 0) + messageThreadIsDefinitelyCorrect = false; + #endif + } + } + + Vst2::AEffect* getAEffect() noexcept { return &vstEffect; } + + template + void internalProcessReplacing (FloatType** inputs, FloatType** outputs, + int32 numSamples, VstTempBuffers& tmpBuffers) + { + const bool isMidiEffect = processor->isMidiEffect(); + + if (firstProcessCallback) + { + firstProcessCallback = false; + + // if this fails, the host hasn't called resume() before processing + jassert (isProcessing); + + // (tragically, some hosts actually need this, although it's stupid to have + // to do it here.) + if (! isProcessing) + resume(); + + processor->setNonRealtime (isProcessLevelOffline()); + + #if JUCE_WINDOWS + if (detail::PluginUtilities::getHostType().isWavelab()) + { + int priority = GetThreadPriority (GetCurrentThread()); + + if (priority <= THREAD_PRIORITY_NORMAL && priority >= THREAD_PRIORITY_LOWEST) + processor->setNonRealtime (true); + } + #endif + } + + #if JUCE_DEBUG && ! (JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect) + const int numMidiEventsComingIn = midiEvents.getNumEvents(); + #endif + + { + const int numIn = processor->getTotalNumInputChannels(); + const int numOut = processor->getTotalNumOutputChannels(); + + const ScopedLock sl (processor->getCallbackLock()); + + if (processor->isSuspended()) + { + for (int i = 0; i < numOut; ++i) + if (outputs[i] != nullptr) + FloatVectorOperations::clear (outputs[i], numSamples); + } + else + { + updateCallbackContextInfo(); + + int i; + for (i = 0; i < numOut; ++i) + { + auto* chan = tmpBuffers.tempChannels.getUnchecked(i); + + if (chan == nullptr) + { + chan = outputs[i]; + + bool bufferPointerReusedForOtherChannels = false; + + for (int j = i; --j >= 0;) + { + if (outputs[j] == chan) + { + bufferPointerReusedForOtherChannels = true; + break; + } + } + + // if some output channels are disabled, some hosts supply the same buffer + // for multiple channels or supply a nullptr - this buggers up our method + // of copying the inputs over the outputs, so we need to create unique temp + // buffers in this case.. + if (bufferPointerReusedForOtherChannels || chan == nullptr) + { + chan = new FloatType [(size_t) blockSize * 2]; + tmpBuffers.tempChannels.set (i, chan); + } + } + + if (i < numIn) + { + if (chan != inputs[i]) + memcpy (chan, inputs[i], (size_t) numSamples * sizeof (FloatType)); + } + else + { + FloatVectorOperations::clear (chan, numSamples); + } + + tmpBuffers.channels[i] = chan; + } + + for (; i < numIn; ++i) + tmpBuffers.channels[i] = inputs[i]; + + { + const int numChannels = jmax (numIn, numOut); + AudioBuffer chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); + + if (isBypassed && processor->getBypassParameter() == nullptr) + processor->processBlockBypassed (chans, midiEvents); + else + processor->processBlock (chans, midiEvents); + } + + // copy back any temp channels that may have been used.. + for (i = 0; i < numOut; ++i) + if (auto* chan = tmpBuffers.tempChannels.getUnchecked(i)) + if (auto* dest = outputs[i]) + memcpy (dest, chan, (size_t) numSamples * sizeof (FloatType)); + } + } + + if (! midiEvents.isEmpty()) + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + auto numEvents = midiEvents.getNumEvents(); + + outgoingEvents.ensureSize (numEvents); + outgoingEvents.clear(); + + for (const auto metadata : midiEvents) + { + jassert (metadata.samplePosition >= 0 && metadata.samplePosition < numSamples); + + outgoingEvents.addEvent (metadata.data, metadata.numBytes, metadata.samplePosition); + } + + // Send VST events to the host. + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterProcessEvents, 0, 0, outgoingEvents.events, 0); + #elif JUCE_DEBUG + /* This assertion is caused when you've added some events to the + midiMessages array in your processBlock() method, which usually means + that you're trying to send them somewhere. But in this case they're + getting thrown away. + + If your plugin does want to send midi messages, you'll need to set + the JucePlugin_ProducesMidiOutput macro to 1 in your + JucePluginCharacteristics.h file. + + If you don't want to produce any midi output, then you should clear the + midiMessages array at the end of your processBlock() method, to + indicate that you don't want any of the events to be passed through + to the output. + */ + jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); + #endif + + midiEvents.clear(); + } + } + + void processReplacing (float** inputs, float** outputs, int32 sampleFrames) + { + jassert (! processor->isUsingDoublePrecision()); + internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); + } + + static void processReplacingCB (Vst2::AEffect* vstInterface, float** inputs, float** outputs, int32 sampleFrames) + { + getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); + } + + void processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames) + { + jassert (processor->isUsingDoublePrecision()); + internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); + } + + static void processDoubleReplacingCB (Vst2::AEffect* vstInterface, double** inputs, double** outputs, int32 sampleFrames) + { + getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); + } + + //============================================================================== + void resume() + { + if (processor != nullptr) + { + isProcessing = true; + + auto numInAndOutChannels = static_cast (vstEffect.numInputs + vstEffect.numOutputs); + floatTempBuffers .channels.calloc (numInAndOutChannels); + doubleTempBuffers.channels.calloc (numInAndOutChannels); + + auto currentRate = sampleRate; + auto currentBlockSize = blockSize; + + firstProcessCallback = true; + + processor->setNonRealtime (isProcessLevelOffline()); + processor->setRateAndBufferSizeDetails (currentRate, currentBlockSize); + + deleteTempChannels(); + + processor->prepareToPlay (currentRate, currentBlockSize); + + midiEvents.ensureSize (2048); + midiEvents.clear(); + + vstEffect.initialDelay = processor->getLatencySamples(); + + /** If this plug-in is a synth or it can receive midi events we need to tell the + host that we want midi. In the SDK this method is marked as deprecated, but + some hosts rely on this behaviour. + */ + if (vstEffect.flags & Vst2::effFlagsIsSynth || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect) + { + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterWantMidi, 0, 1, nullptr, 0); + } + + if (detail::PluginUtilities::getHostType().isAbletonLive() + && hostCallback != nullptr + && processor->getTailLengthSeconds() == std::numeric_limits::infinity()) + { + AbletonLiveHostSpecific hostCmd; + + hostCmd.magic = 0x41624c69; // 'AbLi' + hostCmd.cmd = 5; + hostCmd.commandSize = sizeof (int); + hostCmd.flags = AbletonLiveHostSpecific::KCantBeSuspended; + + hostCallback (&vstEffect, Vst2::audioMasterVendorSpecific, 0, 0, &hostCmd, 0.0f); + } + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + outgoingEvents.ensureSize (512); + #endif + } + } + + void suspend() + { + if (processor != nullptr) + { + processor->releaseResources(); + outgoingEvents.freeEvents(); + + isProcessing = false; + floatTempBuffers.channels.free(); + doubleTempBuffers.channels.free(); + + deleteTempChannels(); + } + } + + void updateCallbackContextInfo() + { + const Vst2::VstTimeInfo* ti = nullptr; + + if (hostCallback != nullptr) + { + int32 flags = Vst2::kVstPpqPosValid | Vst2::kVstTempoValid + | Vst2::kVstBarsValid | Vst2::kVstCyclePosValid + | Vst2::kVstTimeSigValid | Vst2::kVstSmpteValid + | Vst2::kVstClockValid | Vst2::kVstNanosValid; + + auto result = hostCallback (&vstEffect, Vst2::audioMasterGetTime, 0, flags, nullptr, 0); + ti = reinterpret_cast (result); + } + + if (ti == nullptr || ti->sampleRate <= 0) + { + currentPosition.reset(); + return; + } + + auto& info = currentPosition.emplace(); + info.setBpm ((ti->flags & Vst2::kVstTempoValid) != 0 ? makeOptional (ti->tempo) : nullopt); + + info.setTimeSignature ((ti->flags & Vst2::kVstTimeSigValid) != 0 ? makeOptional (TimeSignature { ti->timeSigNumerator, ti->timeSigDenominator }) + : nullopt); + + info.setTimeInSamples ((int64) (ti->samplePos + 0.5)); + info.setTimeInSeconds (ti->samplePos / ti->sampleRate); + info.setPpqPosition ((ti->flags & Vst2::kVstPpqPosValid) != 0 ? makeOptional (ti->ppqPos) : nullopt); + info.setPpqPositionOfLastBarStart ((ti->flags & Vst2::kVstBarsValid) != 0 ? makeOptional (ti->barStartPos) : nullopt); + + if ((ti->flags & Vst2::kVstSmpteValid) != 0) + { + info.setFrameRate ([&]() -> Optional + { + switch (ti->smpteFrameRate) + { + case Vst2::kVstSmpte24fps: return FrameRate().withBaseRate (24); + case Vst2::kVstSmpte239fps: return FrameRate().withBaseRate (24).withPullDown(); + + case Vst2::kVstSmpte25fps: return FrameRate().withBaseRate (25); + case Vst2::kVstSmpte249fps: return FrameRate().withBaseRate (25).withPullDown(); + + case Vst2::kVstSmpte30fps: return FrameRate().withBaseRate (30); + case Vst2::kVstSmpte30dfps: return FrameRate().withBaseRate (30).withDrop(); + case Vst2::kVstSmpte2997fps: return FrameRate().withBaseRate (30).withPullDown(); + case Vst2::kVstSmpte2997dfps: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + + case Vst2::kVstSmpte60fps: return FrameRate().withBaseRate (60); + case Vst2::kVstSmpte599fps: return FrameRate().withBaseRate (60).withPullDown(); + + case Vst2::kVstSmpteFilm16mm: + case Vst2::kVstSmpteFilm35mm: return FrameRate().withBaseRate (24); + } + + return nullopt; + }()); + + const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; + info.setEditOriginTime (effectiveRate != 0.0 ? makeOptional (ti->smpteOffset / (80.0 * effectiveRate)) : nullopt); + } + + info.setIsRecording ((ti->flags & Vst2::kVstTransportRecording) != 0); + info.setIsPlaying ((ti->flags & (Vst2::kVstTransportRecording | Vst2::kVstTransportPlaying)) != 0); + info.setIsLooping ((ti->flags & Vst2::kVstTransportCycleActive) != 0); + + info.setLoopPoints ((ti->flags & Vst2::kVstCyclePosValid) != 0 ? makeOptional (LoopPoints { ti->cycleStartPos, ti->cycleEndPos }) + : nullopt); + + info.setHostTimeNs ((ti->flags & Vst2::kVstNanosValid) != 0 ? makeOptional ((uint64_t) ti->nanoSeconds) : nullopt); + } + + //============================================================================== + Optional getPosition() const override + { + return currentPosition; + } + + //============================================================================== + float getParameter (int32 index) const + { + if (auto* param = juceParameters.getParamForIndex (index)) + return param->getValue(); + + return 0.0f; + } + + static float getParameterCB (Vst2::AEffect* vstInterface, int32 index) + { + return getWrapper (vstInterface)->getParameter (index); + } + + void setParameter (int32 index, float value) + { + if (auto* param = juceParameters.getParamForIndex (index)) + setValueAndNotifyIfChanged (*param, value); + } + + static void setParameterCB (Vst2::AEffect* vstInterface, int32 index, float value) + { + getWrapper (vstInterface)->setParameter (index, value); + } + + void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterAutomate, index, 0, nullptr, newValue); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override + { + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterBeginEdit, index, 0, nullptr, 0); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override + { + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterEndEdit, index, 0, nullptr, 0); + } + + void parameterValueChanged (int, float newValue) override + { + // this can only come from the bypass parameter + isBypassed = (newValue >= 0.5f); + } + + void parameterGestureChanged (int, bool) override {} + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + hostChangeUpdater.update (details); + } + + bool getPinProperties (Vst2::VstPinProperties& properties, bool direction, int index) const + { + if (processor->isMidiEffect()) + return false; + + int channelIdx, busIdx; + + // fill with default + properties.flags = 0; + properties.label[0] = 0; + properties.shortLabel[0] = 0; + properties.arrangementType = Vst2::kSpeakerArrEmpty; + + if ((channelIdx = processor->getOffsetInBusBufferForAbsoluteChannelIndex (direction, index, busIdx)) >= 0) + { + auto& bus = *processor->getBus (direction, busIdx); + auto& channelSet = bus.getCurrentLayout(); + auto channelType = channelSet.getTypeOfChannel (channelIdx); + + properties.flags = Vst2::kVstPinIsActive | Vst2::kVstPinUseSpeaker; + properties.arrangementType = SpeakerMappings::channelSetToVstArrangementType (channelSet); + String label = bus.getName(); + + #ifdef JucePlugin_PreferredChannelConfigurations + label += " " + String (channelIdx); + #else + if (channelSet.size() > 1) + label += " " + AudioChannelSet::getAbbreviatedChannelTypeName (channelType); + #endif + + label.copyToUTF8 (properties.label, (size_t) (Vst2::kVstMaxLabelLen + 1)); + label.copyToUTF8 (properties.shortLabel, (size_t) (Vst2::kVstMaxShortLabelLen + 1)); + + if (channelType == AudioChannelSet::left + || channelType == AudioChannelSet::leftSurround + || channelType == AudioChannelSet::leftCentre + || channelType == AudioChannelSet::leftSurroundSide + || channelType == AudioChannelSet::topFrontLeft + || channelType == AudioChannelSet::topRearLeft + || channelType == AudioChannelSet::leftSurroundRear + || channelType == AudioChannelSet::wideLeft) + properties.flags |= Vst2::kVstPinIsStereo; + + return true; + } + + return false; + } + + //============================================================================== + void timerCallback() override + { + if (shouldDeleteEditor) + { + shouldDeleteEditor = false; + deleteEditor (true); + } + + { + ScopedLock lock (stateInformationLock); + + if (chunkMemoryTime > 0 + && chunkMemoryTime < juce::Time::getApproximateMillisecondCounter() - 2000 + && ! recursionCheck) + { + chunkMemory.reset(); + chunkMemoryTime = 0; + } + } + } + + void setHasEditorFlag (bool shouldSetHasEditor) + { + auto hasEditor = (vstEffect.flags & Vst2::effFlagsHasEditor) != 0; + + if (shouldSetHasEditor == hasEditor) + return; + + if (shouldSetHasEditor) + vstEffect.flags |= Vst2::effFlagsHasEditor; + else + vstEffect.flags &= ~Vst2::effFlagsHasEditor; + } + + void createEditorComp() + { + if (hasShutdown || processor == nullptr) + return; + + if (editorComp == nullptr) + { + if (auto* ed = processor->createEditorIfNeeded()) + { + setHasEditorFlag (true); + editorComp.reset (new EditorCompWrapper (*this, *ed, editorScaleFactor)); + } + else + { + setHasEditorFlag (false); + } + } + + shouldDeleteEditor = false; + } + + void deleteEditor (bool canDeleteLaterIfModal) + { + JUCE_AUTORELEASEPOOL + { + PopupMenu::dismissAllActiveMenus(); + + jassert (! recursionCheck); + ScopedValueSetter svs (recursionCheck, true, false); + + if (editorComp != nullptr) + { + if (auto* modalComponent = Component::getCurrentlyModalComponent()) + { + modalComponent->exitModalState (0); + + if (canDeleteLaterIfModal) + { + shouldDeleteEditor = true; + return; + } + } + + editorComp->detachHostWindow(); + + if (auto* ed = editorComp->getEditorComp()) + processor->editorBeingDeleted (ed); + + editorComp = nullptr; + + // there's some kind of component currently modal, but the host + // is trying to delete our plugin. You should try to avoid this happening.. + jassert (Component::getCurrentlyModalComponent() == nullptr); + } + } + } + + pointer_sized_int dispatcher (int32 opCode, VstOpCodeArguments args) + { + if (hasShutdown) + return 0; + + switch (opCode) + { + case Vst2::effOpen: return handleOpen (args); + case Vst2::effClose: return handleClose (args); + case Vst2::effSetProgram: return handleSetCurrentProgram (args); + case Vst2::effGetProgram: return handleGetCurrentProgram (args); + case Vst2::effSetProgramName: return handleSetCurrentProgramName (args); + case Vst2::effGetProgramName: return handleGetCurrentProgramName (args); + case Vst2::effGetParamLabel: return handleGetParameterLabel (args); + case Vst2::effGetParamDisplay: return handleGetParameterText (args); + case Vst2::effGetParamName: return handleGetParameterName (args); + case Vst2::effSetSampleRate: return handleSetSampleRate (args); + case Vst2::effSetBlockSize: return handleSetBlockSize (args); + case Vst2::effMainsChanged: return handleResumeSuspend (args); + case Vst2::effEditGetRect: return handleGetEditorBounds (args); + case Vst2::effEditOpen: return handleOpenEditor (args); + case Vst2::effEditClose: return handleCloseEditor (args); + case Vst2::effIdentify: return (pointer_sized_int) ByteOrder::bigEndianInt ("NvEf"); + case Vst2::effGetChunk: return handleGetData (args); + case Vst2::effSetChunk: return handleSetData (args); + case Vst2::effProcessEvents: return handlePreAudioProcessingEvents (args); + case Vst2::effCanBeAutomated: return handleIsParameterAutomatable (args); + case Vst2::effString2Parameter: return handleParameterValueForText (args); + case Vst2::effGetProgramNameIndexed: return handleGetProgramName (args); + case Vst2::effGetInputProperties: return handleGetInputPinProperties (args); + case Vst2::effGetOutputProperties: return handleGetOutputPinProperties (args); + case Vst2::effGetPlugCategory: return handleGetPlugInCategory (args); + case Vst2::effSetSpeakerArrangement: return handleSetSpeakerConfiguration (args); + case Vst2::effSetBypass: return handleSetBypass (args); + case Vst2::effGetEffectName: return handleGetPlugInName (args); + case Vst2::effGetProductString: return handleGetPlugInName (args); + case Vst2::effGetVendorString: return handleGetManufacturerName (args); + case Vst2::effGetVendorVersion: return handleGetManufacturerVersion (args); + case Vst2::effVendorSpecific: return handleManufacturerSpecific (args); + case Vst2::effCanDo: return handleCanPlugInDo (args); + case Vst2::effGetTailSize: return handleGetTailSize (args); + case Vst2::effKeysRequired: return handleKeyboardFocusRequired (args); + case Vst2::effGetVstVersion: return handleGetVstInterfaceVersion (args); + case Vst2::effGetCurrentMidiProgram: return handleGetCurrentMidiProgram (args); + case Vst2::effGetSpeakerArrangement: return handleGetSpeakerConfiguration (args); + case Vst2::effSetTotalSampleToProcess: return handleSetNumberOfSamplesToProcess (args); + case Vst2::effSetProcessPrecision: return handleSetSampleFloatType (args); + case Vst2::effGetNumMidiInputChannels: return handleGetNumMidiInputChannels(); + case Vst2::effGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels(); + case Vst2::effEditIdle: return handleEditIdle(); + default: return 0; + } + } + + static pointer_sized_int dispatcherCB (Vst2::AEffect* vstInterface, int32 opCode, int32 index, + pointer_sized_int value, void* ptr, float opt) + { + auto* wrapper = getWrapper (vstInterface); + VstOpCodeArguments args = { index, value, ptr, opt }; + + if (opCode == Vst2::effClose) + { + wrapper->dispatcher (opCode, args); + delete wrapper; + return 1; + } + + return wrapper->dispatcher (opCode, args); + } + + //============================================================================== + // A component to hold the AudioProcessorEditor, and cope with some housekeeping + // chores when it changes or repaints. + struct EditorCompWrapper : public Component + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + , public Timer + #endif + { + EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor, [[maybe_unused]] float initialScale) + : wrapper (w) + { + editor.setOpaque (true); + #if ! JUCE_MAC + editor.setScaleFactor (initialScale); + #endif + addAndMakeVisible (editor); + + auto editorBounds = getSizeToContainChild(); + setSize (editorBounds.getWidth(), editorBounds.getHeight()); + + #if JUCE_WINDOWS + if (! detail::PluginUtilities::getHostType().isReceptor()) + addMouseListener (this, true); + #endif + + setOpaque (true); + } + + ~EditorCompWrapper() override + { + deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may + // have been transferred to another parent which takes over ownership. + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + void getEditorBounds (Vst2::ERect& bounds) + { + auto editorBounds = getSizeToContainChild(); + bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); + } + + void attachToHost (VstOpCodeArguments args) + { + setVisible (false); + + const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (getEditorComp()); + + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + addToDesktop (desktopFlags, args.ptr); + hostWindow = (HostWindowType) args.ptr; + + #if JUCE_LINUX || JUCE_BSD + X11Symbols::getInstance()->xReparentWindow (display, + (Window) getWindowHandle(), + (HostWindowType) hostWindow, + 0, 0); + // The host is likely to attempt to move/resize the window directly after this call, + // and we need to ensure that the X server knows that our window has been attached + // before that happens. + X11Symbols::getInstance()->xFlush (display); + #elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + checkHostWindowScaleFactor (true); + startTimer (500); + #endif + #elif JUCE_MAC + hostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (this, desktopFlags, args.ptr); + #endif + + setVisible (true); + } + + void detachHostWindow() + { + #if JUCE_MAC + if (hostWindow != nullptr) + detail::VSTWindowUtilities::detachComponentFromWindowRefVST (this, hostWindow); + #endif + + hostWindow = {}; + } + + AudioProcessorEditor* getEditorComp() const noexcept + { + return dynamic_cast (getChildComponent (0)); + } + + void resized() override + { + if (auto* pluginEditor = getEditorComp()) + { + if (! resizingParent) + { + auto newBounds = getLocalBounds(); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); + } + + lastBounds = newBounds; + } + + updateWindowSize(); + } + } + + void parentSizeChanged() override + { + updateWindowSize(); + repaint(); + } + + void childBoundsChanged (Component*) override + { + if (resizingChild) + return; + + auto newBounds = getSizeToContainChild(); + + if (newBounds != lastBounds) + { + updateWindowSize(); + lastBounds = newBounds; + } + } + + juce::Rectangle getSizeToContainChild() + { + if (auto* pluginEditor = getEditorComp()) + return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); + + return {}; + } + + void resizeHostWindow (juce::Rectangle bounds) + { + auto rect = convertToHostBounds ({ 0, 0, (int16) bounds.getHeight(), (int16) bounds.getWidth() }); + const auto newWidth = rect.right - rect.left; + const auto newHeight = rect.bottom - rect.top; + + bool sizeWasSuccessful = false; + + if (auto host = wrapper.hostCallback) + { + auto status = host (wrapper.getAEffect(), Vst2::audioMasterCanDo, 0, 0, const_cast ("sizeWindow"), 0); + + if (status == (pointer_sized_int) 1 || detail::PluginUtilities::getHostType().isAbletonLive()) + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + + sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::audioMasterSizeWindow, + newWidth, newHeight, nullptr, 0) != 0); + } + } + + // some hosts don't support the sizeWindow call, so do it manually.. + if (! sizeWasSuccessful) + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + + #if JUCE_MAC + detail::VSTWindowUtilities::setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight); + #elif JUCE_LINUX || JUCE_BSD + // (Currently, all linux hosts support sizeWindow, so this should never need to happen) + setSize (newWidth, newHeight); + #else + int dw = 0; + int dh = 0; + const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); + + HWND w = (HWND) getWindowHandle(); + + while (w != nullptr) + { + HWND parent = getWindowParent (w); + + if (parent == nullptr) + break; + + TCHAR windowType [32] = { 0 }; + GetClassName (parent, windowType, 31); + + if (String (windowType).equalsIgnoreCase ("MDIClient")) + break; + + RECT windowPos, parentPos; + GetWindowRect (w, &windowPos); + GetWindowRect (parent, &parentPos); + + if (w != (HWND) getWindowHandle()) + SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); + + dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); + dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); + + w = parent; + + if (dw == 2 * frameThickness) + break; + + if (dw > 100 || dh > 100) + w = nullptr; + } + + if (w != nullptr) + SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); + #endif + } + + #if JUCE_LINUX || JUCE_BSD + X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), + static_cast (rect.right - rect.left), + static_cast (rect.bottom - rect.top)); + #endif + } + + void setContentScaleFactor (float scale) + { + if (auto* pluginEditor = getEditorComp()) + { + auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + + pluginEditor->setScaleFactor (scale); + pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); + } + + lastBounds = getSizeToContainChild(); + updateWindowSize(); + } + } + + #if JUCE_WINDOWS + void mouseDown (const MouseEvent&) override + { + broughtToFront(); + } + + void broughtToFront() override + { + // for hosts like nuendo, need to also pop the MDI container to the + // front when our comp is clicked on. + if (! isCurrentlyBlockedByAnotherModalComponent()) + if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) + SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + void checkHostWindowScaleFactor (bool force = false) + { + auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); + + if (force || (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, wrapper.editorScaleFactor))) + wrapper.handleSetContentScaleFactor (hostWindowScale, force); + } + + void timerCallback() override + { + checkHostWindowScaleFactor(); + } + #endif + #endif + + private: + void updateWindowSize() + { + if (! resizingParent + && getEditorComp() != nullptr + && hostWindow != HostWindowType{}) + { + const auto editorBounds = getSizeToContainChild(); + resizeHostWindow (editorBounds); + + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + + // setSize() on linux causes renoise and energyxt to fail. + // We'll resize our peer during resizeHostWindow() instead. + #if ! (JUCE_LINUX || JUCE_BSD) + setSize (editorBounds.getWidth(), editorBounds.getHeight()); + #endif + + if (auto* p = getPeer()) + p->updateBounds(); + } + + #if JUCE_MAC + resizeHostWindow (editorBounds); // (doing this a second time seems to be necessary in tracktion) + #endif + } + } + + //============================================================================== + static Vst2::ERect convertToHostBounds (const Vst2::ERect& rect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return rect; + + return { (int16) roundToInt (rect.top * desktopScale), + (int16) roundToInt (rect.left * desktopScale), + (int16) roundToInt (rect.bottom * desktopScale), + (int16) roundToInt (rect.right * desktopScale) }; + } + + //============================================================================== + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostEventLoop; + #endif + + //============================================================================== + JuceVSTWrapper& wrapper; + bool resizingChild = false, resizingParent = false; + + juce::Rectangle lastBounds; + + #if JUCE_LINUX || JUCE_BSD + using HostWindowType = ::Window; + ::Display* display = XWindowSystem::getInstance()->getDisplay(); + #elif JUCE_WINDOWS + using HostWindowType = HWND; + detail::WindowsHooks hooks; + #else + using HostWindowType = void*; + #endif + + HostWindowType hostWindow = {}; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) + }; + + //============================================================================== +private: + struct HostChangeUpdater : private AsyncUpdater + { + explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} + ~HostChangeUpdater() override { cancelPendingUpdate(); } + + void update (const ChangeDetails& details) + { + if (details.latencyChanged) + { + owner.vstEffect.initialDelay = owner.processor->getLatencySamples(); + callbackBits |= audioMasterIOChangedBit; + } + + if (details.parameterInfoChanged || details.programChanged) + callbackBits |= audioMasterUpdateDisplayBit; + + triggerAsyncUpdate(); + } + + private: + void handleAsyncUpdate() override + { + const auto callbacksToFire = callbackBits.exchange (0); + + if (auto* callback = owner.hostCallback) + { + struct FlagPair + { + Vst2::AudioMasterOpcodesX opcode; + int bit; + }; + + constexpr FlagPair pairs[] { { Vst2::audioMasterUpdateDisplay, audioMasterUpdateDisplayBit }, + { Vst2::audioMasterIOChanged, audioMasterIOChangedBit } }; + + for (const auto& pair : pairs) + if ((callbacksToFire & pair.bit) != 0) + callback (&owner.vstEffect, pair.opcode, 0, 0, nullptr, 0); + } + } + + static constexpr auto audioMasterUpdateDisplayBit = 1 << 0; + static constexpr auto audioMasterIOChangedBit = 1 << 1; + + JuceVSTWrapper& owner; + std::atomic callbackBits { 0 }; + }; + + static JuceVSTWrapper* getWrapper (Vst2::AEffect* v) noexcept { return static_cast (v->object); } + + bool isProcessLevelOffline() + { + return hostCallback != nullptr + && (int32) hostCallback (&vstEffect, Vst2::audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0) == 4; + } + + static int32 convertHexVersionToDecimal (const unsigned int hexVersion) + { + #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY + return (int32) hexVersion; + #else + // Currently, only Cubase displays the version number to the user + // We are hoping here that when other DAWs start to display the version + // number, that they do so according to yfede's encoding table in the link + // below. If not, then this code will need an if (isSteinberg()) in the + // future. + int major = (hexVersion >> 16) & 0xff; + int minor = (hexVersion >> 8) & 0xff; + int bugfix = hexVersion & 0xff; + + // for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867 + + // Encoding B + if (major < 1) + return major * 1000 + minor * 100 + bugfix * 10; + + // Encoding E + if (major > 100) + return major * 10000000 + minor * 100000 + bugfix * 1000; + + // Encoding D + return static_cast (hexVersion); + #endif + } + + //============================================================================== + #if JUCE_WINDOWS + // Workarounds for hosts which attempt to open editor windows on a non-GUI thread.. (Grrrr...) + static void checkWhetherMessageThreadIsCorrect() + { + auto host = detail::PluginUtilities::getHostType(); + + if (host.isWavelab() || host.isCubaseBridged() || host.isPremiere()) + { + if (! messageThreadIsDefinitelyCorrect) + { + MessageManager::getInstance()->setCurrentThreadAsMessageThread(); + + struct MessageThreadCallback : public CallbackMessage + { + MessageThreadCallback (bool& tr) : triggered (tr) {} + void messageCallback() override { triggered = true; } + + bool& triggered; + }; + + (new MessageThreadCallback (messageThreadIsDefinitelyCorrect))->post(); + } + } + } + #else + static void checkWhetherMessageThreadIsCorrect() {} + #endif + + void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) + { + if (param.getValue() == newValue) + return; + + inParameterChangedCallback = true; + param.setValueNotifyingHost (newValue); + } + + //============================================================================== + template + void deleteTempChannels (VstTempBuffers& tmpBuffers) + { + tmpBuffers.release(); + + if (processor != nullptr) + tmpBuffers.tempChannels.insertMultiple (0, nullptr, vstEffect.numInputs + + vstEffect.numOutputs); + } + + void deleteTempChannels() + { + deleteTempChannels (floatTempBuffers); + deleteTempChannels (doubleTempBuffers); + } + + //============================================================================== + void findMaxTotalChannels (int& maxTotalIns, int& maxTotalOuts) + { + #ifdef JucePlugin_PreferredChannelConfigurations + int configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + maxTotalIns = maxTotalOuts = 0; + + for (auto& config : configs) + { + maxTotalIns = jmax (maxTotalIns, config[0]); + maxTotalOuts = jmax (maxTotalOuts, config[1]); + } + #else + auto numInputBuses = processor->getBusCount (true); + auto numOutputBuses = processor->getBusCount (false); + + if (numInputBuses > 1 || numOutputBuses > 1) + { + maxTotalIns = maxTotalOuts = 0; + + for (int i = 0; i < numInputBuses; ++i) + maxTotalIns += processor->getChannelCountOfBus (true, i); + + for (int i = 0; i < numOutputBuses; ++i) + maxTotalOuts += processor->getChannelCountOfBus (false, i); + } + else + { + maxTotalIns = numInputBuses > 0 ? processor->getBus (true, 0)->getMaxSupportedChannels (64) : 0; + maxTotalOuts = numOutputBuses > 0 ? processor->getBus (false, 0)->getMaxSupportedChannels (64) : 0; + } + #endif + } + + bool pluginHasSidechainsOrAuxs() const { return (processor->getBusCount (true) > 1 || processor->getBusCount (false) > 1); } + + //============================================================================== + /** Host to plug-in calls. */ + + pointer_sized_int handleOpen (VstOpCodeArguments) + { + // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. + setHasEditorFlag (processor->hasEditor()); + + return 0; + } + + pointer_sized_int handleClose (VstOpCodeArguments) + { + // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. + stopTimer(); + + if (MessageManager::getInstance()->isThisTheMessageThread()) + deleteEditor (false); + + return 0; + } + + pointer_sized_int handleSetCurrentProgram (VstOpCodeArguments args) + { + if (processor != nullptr && isPositiveAndBelow ((int) args.value, processor->getNumPrograms())) + processor->setCurrentProgram ((int) args.value); + + return 0; + } + + pointer_sized_int handleGetCurrentProgram (VstOpCodeArguments) + { + return (processor != nullptr && processor->getNumPrograms() > 0 ? processor->getCurrentProgram() : 0); + } + + pointer_sized_int handleSetCurrentProgramName (VstOpCodeArguments args) + { + if (processor != nullptr && processor->getNumPrograms() > 0) + processor->changeProgramName (processor->getCurrentProgram(), (char*) args.ptr); + + return 0; + } + + pointer_sized_int handleGetCurrentProgramName (VstOpCodeArguments args) + { + if (processor != nullptr && processor->getNumPrograms() > 0) + processor->getProgramName (processor->getCurrentProgram()).copyToUTF8 ((char*) args.ptr, 24 + 1); + + return 0; + } + + pointer_sized_int handleGetParameterLabel (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + param->getLabel().copyToUTF8 ((char*) args.ptr, 24 + 1); + } + + return 0; + } + + pointer_sized_int handleGetParameterText (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + param->getCurrentValueAsText().copyToUTF8 ((char*) args.ptr, 24 + 1); + } + + return 0; + } + + pointer_sized_int handleGetParameterName (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + param->getName (32).copyToUTF8 ((char*) args.ptr, 32 + 1); + } + + return 0; + } + + pointer_sized_int handleSetSampleRate (VstOpCodeArguments args) + { + sampleRate = args.opt; + return 0; + } + + pointer_sized_int handleSetBlockSize (VstOpCodeArguments args) + { + blockSize = (int32) args.value; + return 0; + } + + pointer_sized_int handleResumeSuspend (VstOpCodeArguments args) + { + if (args.value) + resume(); + else + suspend(); + + return 0; + } + + pointer_sized_int handleGetEditorBounds (VstOpCodeArguments args) + { + checkWhetherMessageThreadIsCorrect(); + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + createEditorComp(); + + if (editorComp != nullptr) + { + editorComp->getEditorBounds (editorRect); + *((Vst2::ERect**) args.ptr) = &editorRect; + return (pointer_sized_int) &editorRect; + } + + return 0; + } + + pointer_sized_int handleOpenEditor (VstOpCodeArguments args) + { + checkWhetherMessageThreadIsCorrect(); + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + jassert (! recursionCheck); + + startTimerHz (4); // performs misc housekeeping chores + + deleteEditor (true); + createEditorComp(); + + if (editorComp != nullptr) + { + editorComp->attachToHost (args); + return 1; + } + + return 0; + } + + pointer_sized_int handleCloseEditor (VstOpCodeArguments) + { + checkWhetherMessageThreadIsCorrect(); + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + + deleteEditor (true); + + return 0; + } + + pointer_sized_int handleGetData (VstOpCodeArguments args) + { + if (processor == nullptr) + return 0; + + auto data = (void**) args.ptr; + bool onlyStoreCurrentProgramData = (args.index != 0); + + MemoryBlock block; + + if (onlyStoreCurrentProgramData) + processor->getCurrentProgramStateInformation (block); + else + processor->getStateInformation (block); + + // IMPORTANT! Don't call getStateInfo while holding this lock! + const ScopedLock lock (stateInformationLock); + + chunkMemory = std::move (block); + *data = (void*) chunkMemory.getData(); + + // because the chunk is only needed temporarily by the host (or at least you'd + // hope so) we'll give it a while and then free it in the timer callback. + chunkMemoryTime = juce::Time::getApproximateMillisecondCounter(); + + return (int32) chunkMemory.getSize(); + } + + pointer_sized_int handleSetData (VstOpCodeArguments args) + { + if (processor != nullptr) + { + void* data = args.ptr; + int32 byteSize = (int32) args.value; + bool onlyRestoreCurrentProgramData = (args.index != 0); + + { + const ScopedLock lock (stateInformationLock); + + chunkMemory.reset(); + chunkMemoryTime = 0; + } + + if (byteSize > 0 && data != nullptr) + { + if (onlyRestoreCurrentProgramData) + processor->setCurrentProgramStateInformation (data, byteSize); + else + processor->setStateInformation (data, byteSize); + } + } + + return 0; + } + + pointer_sized_int handlePreAudioProcessingEvents ([[maybe_unused]] VstOpCodeArguments args) + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + VSTMidiEventList::addEventsToMidiBuffer ((Vst2::VstEvents*) args.ptr, midiEvents); + return 1; + #else + return 0; + #endif + } + + pointer_sized_int handleIsParameterAutomatable (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + const bool isMeter = ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2); + return (param->isAutomatable() && (! isMeter) ? 1 : 0); + } + + return 0; + } + + pointer_sized_int handleParameterValueForText (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + if (! LegacyAudioParameter::isLegacy (param)) + { + setValueAndNotifyIfChanged (*param, param->getValueForText (String::fromUTF8 ((char*) args.ptr))); + return 1; + } + } + + return 0; + } + + pointer_sized_int handleGetProgramName (VstOpCodeArguments args) + { + if (processor != nullptr && isPositiveAndBelow (args.index, processor->getNumPrograms())) + { + processor->getProgramName (args.index).copyToUTF8 ((char*) args.ptr, 24 + 1); + return 1; + } + + return 0; + } + + pointer_sized_int handleGetInputPinProperties (VstOpCodeArguments args) + { + return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, true, args.index)) ? 1 : 0; + } + + pointer_sized_int handleGetOutputPinProperties (VstOpCodeArguments args) + { + return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, false, args.index)) ? 1 : 0; + } + + pointer_sized_int handleGetPlugInCategory (VstOpCodeArguments) + { + return Vst2::JucePlugin_VSTCategory; + } + + pointer_sized_int handleSetSpeakerConfiguration (VstOpCodeArguments args) + { + auto* pluginInput = reinterpret_cast (args.value); + auto* pluginOutput = reinterpret_cast (args.ptr); + + if (processor->isMidiEffect()) + return 0; + + auto numIns = processor->getBusCount (true); + auto numOuts = processor->getBusCount (false); + + if (pluginInput != nullptr && pluginInput->type >= 0) + { + // inconsistent request? + if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput).size() != pluginInput->numChannels) + return 0; + } + + if (pluginOutput != nullptr && pluginOutput->type >= 0) + { + // inconsistent request? + if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput).size() != pluginOutput->numChannels) + return 0; + } + + if (pluginInput != nullptr && pluginInput->numChannels > 0 && numIns == 0) + return 0; + + if (pluginOutput != nullptr && pluginOutput->numChannels > 0 && numOuts == 0) + return 0; + + auto layouts = processor->getBusesLayout(); + + if (pluginInput != nullptr && pluginInput-> numChannels >= 0 && numIns > 0) + layouts.getChannelSet (true, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput); + + if (pluginOutput != nullptr && pluginOutput->numChannels >= 0 && numOuts > 0) + layouts.getChannelSet (false, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + if (! AudioProcessor::containsLayout (layouts, configs)) + return 0; + #endif + + return processor->setBusesLayout (layouts) ? 1 : 0; + } + + pointer_sized_int handleSetBypass (VstOpCodeArguments args) + { + isBypassed = args.value != 0; + + if (auto* param = processor->getBypassParameter()) + param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); + + return 1; + } + + pointer_sized_int handleGetPlugInName (VstOpCodeArguments args) + { + String (JucePlugin_Name).copyToUTF8 ((char*) args.ptr, 64 + 1); + return 1; + } + + pointer_sized_int handleGetManufacturerName (VstOpCodeArguments args) + { + String (JucePlugin_Manufacturer).copyToUTF8 ((char*) args.ptr, 64 + 1); + return 1; + } + + pointer_sized_int handleGetManufacturerVersion (VstOpCodeArguments) + { + return convertHexVersionToDecimal (JucePlugin_VersionCode); + } + + pointer_sized_int handleManufacturerSpecific (VstOpCodeArguments args) + { + if (detail::PluginUtilities::handleManufacturerSpecificVST2Opcode (args.index, args.value, args.ptr, args.opt)) + return 1; + + if (args.index == (int32) ByteOrder::bigEndianInt ("PreS") + && args.value == (int32) ByteOrder::bigEndianInt ("AeCs")) + return handleSetContentScaleFactor (args.opt); + + if (args.index == Vst2::effGetParamDisplay) + return handleCockosGetParameterText (args.value, args.ptr, args.opt); + + if (auto callbackHandler = dynamic_cast (processor.get())) + return callbackHandler->handleVstManufacturerSpecific (args.index, args.value, args.ptr, args.opt); + + return 0; + } + + pointer_sized_int handleCanPlugInDo (VstOpCodeArguments args) + { + auto text = (const char*) args.ptr; + auto matches = [=] (const char* s) { return strcmp (text, s) == 0; }; + + if (matches ("receiveVstEvents") + || matches ("receiveVstMidiEvent") + || matches ("receiveVstMidiEvents")) + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + return 1; + #else + return -1; + #endif + } + + if (matches ("sendVstEvents") + || matches ("sendVstMidiEvent") + || matches ("sendVstMidiEvents")) + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + return 1; + #else + return -1; + #endif + } + + if (matches ("receiveVstTimeInfo") + || matches ("conformsToWindowRules") + || matches ("supportsViewDpiScaling") + || matches ("bypass")) + { + return 1; + } + + // This tells Wavelab to use the UI thread to invoke open/close, + // like all other hosts do. + if (matches ("openCloseAnyThread")) + return -1; + + if (matches ("MPE")) + return processor->supportsMPE() ? 1 : 0; + + #if JUCE_MAC + if (matches ("hasCockosViewAsConfig")) + { + return (int32) 0xbeef0000; + } + #endif + + if (matches ("hasCockosExtensions")) + return (int32) 0xbeef0000; + + if (auto callbackHandler = dynamic_cast (processor.get())) + return callbackHandler->handleVstPluginCanDo (args.index, args.value, args.ptr, args.opt); + + return 0; + } + + pointer_sized_int handleGetTailSize (VstOpCodeArguments) + { + if (processor != nullptr) + { + int32 result; + + auto tailSeconds = processor->getTailLengthSeconds(); + + if (tailSeconds == std::numeric_limits::infinity()) + result = std::numeric_limits::max(); + else + result = static_cast (tailSeconds * sampleRate); + + return result; // Vst2 expects an int32 upcasted to a intptr_t here + } + + return 0; + } + + pointer_sized_int handleKeyboardFocusRequired (VstOpCodeArguments) + { + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6326) + return (JucePlugin_EditorRequiresKeyboardFocus != 0) ? 1 : 0; + JUCE_END_IGNORE_WARNINGS_MSVC + } + + pointer_sized_int handleGetVstInterfaceVersion (VstOpCodeArguments) + { + return kVstVersion; + } + + pointer_sized_int handleGetCurrentMidiProgram (VstOpCodeArguments) + { + return -1; + } + + pointer_sized_int handleGetSpeakerConfiguration (VstOpCodeArguments args) + { + auto** pluginInput = reinterpret_cast (args.value); + auto** pluginOutput = reinterpret_cast (args.ptr); + + if (pluginHasSidechainsOrAuxs() || processor->isMidiEffect()) + return false; + + auto inputLayout = processor->getChannelLayoutOfBus (true, 0); + auto outputLayout = processor->getChannelLayoutOfBus (false, 0); + + const auto speakerBaseSize = offsetof (Vst2::VstSpeakerArrangement, speakers); + + cachedInArrangement .malloc (speakerBaseSize + (static_cast (inputLayout. size()) * sizeof (Vst2::VstSpeakerProperties)), 1); + cachedOutArrangement.malloc (speakerBaseSize + (static_cast (outputLayout.size()) * sizeof (Vst2::VstSpeakerProperties)), 1); + + *pluginInput = cachedInArrangement. getData(); + *pluginOutput = cachedOutArrangement.getData(); + + SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (true, 0), **pluginInput); + SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (false, 0), **pluginOutput); + + return 1; + } + + pointer_sized_int handleSetNumberOfSamplesToProcess (VstOpCodeArguments args) + { + return args.value; + } + + pointer_sized_int handleSetSampleFloatType (VstOpCodeArguments args) + { + if (! isProcessing) + { + if (processor != nullptr) + { + processor->setProcessingPrecision ((args.value == Vst2::kVstProcessPrecision64 + && processor->supportsDoublePrecisionProcessing()) + ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision); + + return 1; + } + } + + return 0; + } + + pointer_sized_int handleSetContentScaleFactor ([[maybe_unused]] float scale, [[maybe_unused]] bool force = false) + { + checkWhetherMessageThreadIsCorrect(); + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + + #if ! JUCE_MAC + if (force || ! approximatelyEqual (scale, editorScaleFactor)) + { + editorScaleFactor = scale; + + if (editorComp != nullptr) + editorComp->setContentScaleFactor (editorScaleFactor); + } + #endif + + return 1; + } + + pointer_sized_int handleCockosGetParameterText (pointer_sized_int paramIndex, + void* dest, + float value) + { + if (processor != nullptr && dest != nullptr) + { + if (auto* param = juceParameters.getParamForIndex ((int) paramIndex)) + { + if (! LegacyAudioParameter::isLegacy (param)) + { + String text (param->getText (value, 1024)); + memcpy (dest, text.toRawUTF8(), ((size_t) text.length()) + 1); + return 0xbeef; + } + } + } + + return 0; + } + + //============================================================================== + pointer_sized_int handleGetNumMidiInputChannels() + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + #ifdef JucePlugin_VSTNumMidiInputs + return JucePlugin_VSTNumMidiInputs; + #else + return 16; + #endif + #else + return 0; + #endif + } + + pointer_sized_int handleGetNumMidiOutputChannels() + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + #ifdef JucePlugin_VSTNumMidiOutputs + return JucePlugin_VSTNumMidiOutputs; + #else + return 16; + #endif + #else + return 0; + #endif + } + + pointer_sized_int handleEditIdle() + { + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + hostDrivenEventLoop->processPendingEvents(); + #endif + + return 0; + } + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + Vst2::audioMasterCallback hostCallback; + std::unique_ptr processor; + double sampleRate = 44100.0; + int32 blockSize = 1024; + Vst2::AEffect vstEffect; + CriticalSection stateInformationLock; + juce::MemoryBlock chunkMemory; + uint32 chunkMemoryTime = 0; + float editorScaleFactor = 1.0f; + std::unique_ptr editorComp; + Vst2::ERect editorRect; + MidiBuffer midiEvents; + VSTMidiEventList outgoingEvents; + Optional currentPosition; + + LegacyAudioParametersWrapper juceParameters; + + bool isProcessing = false, isBypassed = false, hasShutdown = false; + bool firstProcessCallback = true, shouldDeleteEditor = false; + + VstTempBuffers floatTempBuffers; + VstTempBuffers doubleTempBuffers; + int maxNumInChannels = 0, maxNumOutChannels = 0; + + HeapBlock cachedInArrangement, cachedOutArrangement; + + ThreadLocalValue inParameterChangedCallback; + + HostChangeUpdater hostChangeUpdater { *this }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) +}; + + +//============================================================================== +namespace +{ + Vst2::AEffect* pluginEntryPoint (Vst2::audioMasterCallback audioMaster) + { + JUCE_AUTORELEASEPOOL + { + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #endif + + try + { + if (audioMaster (nullptr, Vst2::audioMasterVersion, 0, 0, nullptr, 0) != 0) + { + std::unique_ptr processor { createPluginFilterOfType (AudioProcessor::wrapperType_VST) }; + auto* processorPtr = processor.get(); + auto* wrapper = new JuceVSTWrapper (audioMaster, std::move (processor)); + auto* aEffect = wrapper->getAEffect(); + + if (auto* callbackHandler = dynamic_cast (processorPtr)) + { + callbackHandler->handleVstHostCallbackAvailable ([audioMaster, aEffect] (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) + { + return audioMaster (aEffect, opcode, index, value, ptr, opt); + }); + } + + return aEffect; + } + } + catch (...) + {} + } + + return nullptr; + } +} + +#if ! JUCE_WINDOWS + #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) +#endif + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +//============================================================================== +// Mac startup code.. +#if JUCE_MAC + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + +//============================================================================== +// Linux startup code.. +#elif JUCE_LINUX || JUCE_BSD + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) asm ("main"); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) + { + return VSTPluginMain (audioMaster); + } + + // don't put initialiseJuce_GUI or shutdownJuce_GUI in these... it will crash! + __attribute__((constructor)) void myPluginInit() {} + __attribute__((destructor)) void myPluginFini() {} + +//============================================================================== +// Win32 startup code.. +#else + + extern "C" __declspec (dllexport) Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + + #if ! defined (JUCE_64BIT) && JUCE_MSVC // (can't compile this on win64, but it's not needed anyway with VST2.4) + extern "C" __declspec (dllexport) int main (Vst2::audioMasterCallback audioMaster) + { + return (int) pluginEntryPoint (audioMaster); + } + #endif + + extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) + { + if (reason == DLL_PROCESS_ATTACH) + Process::setCurrentModuleInstanceHandle (instance); + + return true; + } +#endif + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +JUCE_END_IGNORE_WARNINGS_MSVC + +#endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_utils.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.mm similarity index 92% rename from modules/juce_audio_plugin_client/juce_audio_plugin_client_utils.cpp rename to modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.mm index c8769ab172..aca798a028 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_utils.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.mm @@ -23,4 +23,4 @@ ============================================================================== */ -#include "detail/juce_PluginUtilities.cpp" +#include "juce_audio_plugin_client_VST2.cpp" diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index 100cc0cf12..9e19247d97 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -23,4 +23,4259 @@ ============================================================================== */ -#include "VST3/juce_VST3_Wrapper.cpp" +#include +#include + +//============================================================================== +#if JucePlugin_Build_VST3 + +JUCE_BEGIN_NO_SANITIZE ("vptr") + +#if JUCE_PLUGINHOST_VST3 + #if JUCE_MAC + #include + #endif + #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY + #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 +#endif + +#include + +#undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifndef JUCE_VST3_CAN_REPLACE_VST2 + #define JUCE_VST3_CAN_REPLACE_VST2 1 +#endif + +#if JUCE_VST3_CAN_REPLACE_VST2 + + #if ! JUCE_MSVC && ! defined (__cdecl) + #define __cdecl + #endif + + namespace Vst2 + { + struct AEffect; + #include "pluginterfaces/vst2.x/vstfxstore.h" + } + +#endif + +#ifndef JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + #if JucePlugin_WantsMidiInput + #define JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS 1 + #endif +#endif + +#if JUCE_LINUX || JUCE_BSD + #include + #include +#endif + +#if JUCE_MAC + #include +#endif + +//============================================================================== +#if JucePlugin_Enable_ARA + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wpragma-pack") + #include + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #if ARA_SUPPORT_VERSION_1 + #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current implementation" + #endif + + DEF_CLASS_IID(ARA::IPlugInEntryPoint) + DEF_CLASS_IID(ARA::IPlugInEntryPoint2) + DEF_CLASS_IID(ARA::IMainFactory) +#endif + +namespace juce +{ + +using namespace Steinberg; + +//============================================================================== +#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + double getScaleFactorForWindow (HWND); +#endif + +//============================================================================== +#if JUCE_LINUX || JUCE_BSD + +enum class HostMessageThreadAttached { no, yes }; + +class HostMessageThreadState +{ +public: + template + void setStateWithAction (HostMessageThreadAttached stateIn, Callback&& action) + { + const std::lock_guard lock { m }; + state = stateIn; + action(); + } + + void assertHostMessageThread() + { + const std::lock_guard lock { m }; + + if (state == HostMessageThreadAttached::no) + return; + + JUCE_ASSERT_MESSAGE_THREAD + } + +private: + HostMessageThreadAttached state = HostMessageThreadAttached::no; + std::mutex m; +}; + +class EventHandler final : public Steinberg::Linux::IEventHandler, + private LinuxEventLoopInternal::Listener +{ +public: + EventHandler() + { + LinuxEventLoopInternal::registerLinuxEventLoopListener (*this); + } + + ~EventHandler() override + { + jassert (hostRunLoops.empty()); + + LinuxEventLoopInternal::deregisterLinuxEventLoopListener (*this); + + if (! messageThread->isRunning()) + hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::no, + [this]() { messageThread->start(); }); + } + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + return testFor (*this, targetIID, UniqueBase{}).extract (obj); + } + + void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override + { + updateCurrentMessageThread(); + LinuxEventLoopInternal::invokeEventLoopCallbackForFd (fd); + } + + //============================================================================== + void registerHandlerForFrame (IPlugFrame* plugFrame) + { + if (auto* runLoop = getRunLoopFromFrame (plugFrame)) + { + refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.insert (runLoop); }); + updateCurrentMessageThread(); + } + } + + void unregisterHandlerForFrame (IPlugFrame* plugFrame) + { + if (auto* runLoop = getRunLoopFromFrame (plugFrame)) + refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.erase (runLoop); }); + } + + /* Asserts if it can be established that the calling thread is different from the host's message + thread. + + On Linux this can only be determined if the host has already registered its run loop. Until + then JUCE messages are serviced by a background thread internal to the plugin. + */ + static void assertHostMessageThread() + { + hostMessageThreadState.assertHostMessageThread(); + } + +private: + //============================================================================== + /* Connects all known FDs to a single host event loop instance. */ + class AttachedEventLoop + { + public: + AttachedEventLoop() = default; + + AttachedEventLoop (Steinberg::Linux::IRunLoop* loopIn, Steinberg::Linux::IEventHandler* handlerIn) + : loop (loopIn), handler (handlerIn) + { + for (auto& fd : LinuxEventLoopInternal::getRegisteredFds()) + loop->registerEventHandler (handler, fd); + } + + AttachedEventLoop (AttachedEventLoop&& other) noexcept + { + swap (other); + } + + AttachedEventLoop& operator= (AttachedEventLoop&& other) noexcept + { + swap (other); + return *this; + } + + AttachedEventLoop (const AttachedEventLoop&) = delete; + AttachedEventLoop& operator= (const AttachedEventLoop&) = delete; + + ~AttachedEventLoop() + { + if (loop == nullptr) + return; + + loop->unregisterEventHandler (handler); + } + + private: + void swap (AttachedEventLoop& other) + { + std::swap (other.loop, loop); + std::swap (other.handler, handler); + } + + Steinberg::Linux::IRunLoop* loop = nullptr; + Steinberg::Linux::IEventHandler* handler = nullptr; + }; + + //============================================================================== + static Steinberg::Linux::IRunLoop* getRunLoopFromFrame (IPlugFrame* plugFrame) + { + Steinberg::Linux::IRunLoop* runLoop = nullptr; + + if (plugFrame != nullptr) + plugFrame->queryInterface (Steinberg::Linux::IRunLoop::iid, (void**) &runLoop); + + jassert (runLoop != nullptr); + return runLoop; + } + + void updateCurrentMessageThread() + { + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + if (messageThread->isRunning()) + messageThread->stop(); + + hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::yes, + [] { MessageManager::getInstance()->setCurrentThreadAsMessageThread(); }); + } + } + + void fdCallbacksChanged() override + { + // The set of active FDs has changed, so deregister from the current event loop and then + // re-register the current set of FDs. + refreshAttachedEventLoop ([]{}); + } + + /* Deregisters from any attached event loop, updates the set of known event loops, and then + attaches all FDs to the first known event loop. + + The same event loop instance is shared between all plugin instances. Every time an event + loop is added or removed, this function should be called to register all FDs with a + suitable event loop. + + Note that there's no API to deregister a single FD for a given event loop. Instead, we must + deregister all FDs, and then register all known FDs again. + */ + template + void refreshAttachedEventLoop (Callback&& modifyKnownRunLoops) + { + // Deregister the old event loop. + // It's important to call the destructor from the old attached loop before calling the + // constructor of the new attached loop. + attachedEventLoop = AttachedEventLoop(); + + modifyKnownRunLoops(); + + // If we still know about an extant event loop, attach to it. + if (hostRunLoops.begin() != hostRunLoops.end()) + attachedEventLoop = AttachedEventLoop (*hostRunLoops.begin(), this); + } + + SharedResourcePointer messageThread; + + std::atomic refCount { 1 }; + + std::multiset hostRunLoops; + AttachedEventLoop attachedEventLoop; + + static HostMessageThreadState hostMessageThreadState; + + //============================================================================== + JUCE_DECLARE_NON_MOVEABLE (EventHandler) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler) +}; + +HostMessageThreadState EventHandler::hostMessageThreadState; + +#endif + +static void assertHostMessageThread() +{ + #if JUCE_LINUX || JUCE_BSD + EventHandler::assertHostMessageThread(); + #else + JUCE_ASSERT_MESSAGE_THREAD + #endif +} + +//============================================================================== +class InParameterChangedCallbackSetter +{ +public: + explicit InParameterChangedCallbackSetter (bool& ref) + : inner ([&]() -> auto& { jassert (! ref); return ref; }(), true, false) {} + +private: + ScopedValueSetter inner; +}; + +template +static QueryInterfaceResult queryAdditionalInterfaces (AudioProcessor* processor, + const TUID targetIID, + Member&& member) +{ + if (processor == nullptr) + return {}; + + void* obj = nullptr; + + if (auto* extensions = dynamic_cast (processor)) + { + const auto result = (extensions->*member) (targetIID, &obj); + return { result, obj }; + } + + return {}; +} + +static tresult extractResult (const QueryInterfaceResult& userInterface, + const InterfaceResultWithDeferredAddRef& juceInterface, + void** obj) +{ + if (userInterface.isOk() && juceInterface.isOk()) + { + // If you hit this assertion, you've provided a custom implementation of an interface + // that JUCE implements already. As a result, your plugin may not behave correctly. + // Consider removing your custom implementation. + jassertfalse; + + return userInterface.extract (obj); + } + + if (userInterface.isOk()) + return userInterface.extract (obj); + + return juceInterface.extract (obj); +} + +//============================================================================== +class JuceAudioProcessor : public Vst::IUnitInfo +{ +public: + explicit JuceAudioProcessor (AudioProcessor* source) noexcept + : audioProcessor (source) + { + setupParameters(); + } + + virtual ~JuceAudioProcessor() = default; + + AudioProcessor* get() const noexcept { return audioProcessor.get(); } + + JUCE_DECLARE_VST3_COM_QUERY_METHODS + JUCE_DECLARE_VST3_COM_REF_METHODS + + //============================================================================== + enum InternalParameters + { + paramPreset = 0x70727374, // 'prst' + paramMidiControllerOffset = 0x6d636d00, // 'mdm*' + paramBypass = 0x62797073 // 'byps' + }; + + //============================================================================== + Steinberg::int32 PLUGIN_API getUnitCount() override + { + return parameterGroups.size() + 1; + } + + tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override + { + if (unitIndex == 0) + { + info.id = Vst::kRootUnitId; + info.parentUnitId = Vst::kNoParentUnitId; + info.programListId = getProgramListCount() > 0 + ? static_cast (programParamID) + : Vst::kNoProgramListId; + + toString128 (info.name, TRANS ("Root Unit")); + + return kResultTrue; + } + + if (auto* group = parameterGroups[unitIndex - 1]) + { + info.id = JuceAudioProcessor::getUnitID (group); + info.parentUnitId = JuceAudioProcessor::getUnitID (group->getParent()); + info.programListId = Vst::kNoProgramListId; + + toString128 (info.name, group->getName()); + + return kResultTrue; + } + + return kResultFalse; + } + + Steinberg::int32 PLUGIN_API getProgramListCount() override + { + if (audioProcessor->getNumPrograms() > 0) + return 1; + + return 0; + } + + tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override + { + if (listIndex == 0) + { + info.id = static_cast (programParamID); + info.programCount = static_cast (audioProcessor->getNumPrograms()); + + toString128 (info.name, TRANS ("Factory Presets")); + + return kResultTrue; + } + + jassertfalse; + zerostruct (info); + return kResultFalse; + } + + tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override + { + if (listId == static_cast (programParamID) + && isPositiveAndBelow ((int) programIndex, audioProcessor->getNumPrograms())) + { + toString128 (name, audioProcessor->getProgramName ((int) programIndex)); + return kResultTrue; + } + + jassertfalse; + toString128 (name, juce::String()); + return kResultFalse; + } + + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override { return kNotImplemented; } + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override { return kNotImplemented; } + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override { return kNotImplemented; } + tresult PLUGIN_API selectUnit (Vst::UnitID) override { return kNotImplemented; } + tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } + Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } + + tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection, Steinberg::int32, Steinberg::int32, Vst::UnitID& unitId) override + { + unitId = Vst::kRootUnitId; + return kResultOk; + } + + //============================================================================== + inline Vst::ParamID getVSTParamIDForIndex (int paramIndex) const noexcept + { + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + return static_cast (paramIndex); + #else + return vstParamIDs.getReference (paramIndex); + #endif + } + + AudioProcessorParameter* getParamForVSTParamID (Vst::ParamID paramID) const noexcept + { + return paramMap[static_cast (paramID)]; + } + + AudioProcessorParameter* getBypassParameter() const noexcept + { + return getParamForVSTParamID (bypassParamID); + } + + AudioProcessorParameter* getProgramParameter() const noexcept + { + return getParamForVSTParamID (programParamID); + } + + static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) + { + if (group == nullptr || group->getParent() == nullptr) + return Vst::kRootUnitId; + + // From the VST3 docs (also applicable to unit IDs!): + // Up to 2^31 parameters can be exported with id range [0, 2147483648] + // (the range [2147483649, 429496729] is reserved for host application). + auto unitID = group->getID().hashCode() & 0x7fffffff; + + // If you hit this assertion then your group ID is hashing to a value + // reserved by the VST3 SDK. Please use a different group ID. + jassert (unitID != Vst::kRootUnitId); + + return unitID; + } + + const Array& getParamIDs() const noexcept { return vstParamIDs; } + Vst::ParamID getBypassParamID() const noexcept { return bypassParamID; } + Vst::ParamID getProgramParamID() const noexcept { return programParamID; } + bool isBypassRegularParameter() const noexcept { return bypassIsRegularParameter; } + + int findCacheIndexForParamID (Vst::ParamID paramID) const noexcept { return vstParamIDs.indexOf (paramID); } + + void setParameterValue (Steinberg::int32 paramIndex, float value) + { + cachedParamValues.set (paramIndex, value); + } + + template + void forAllChangedParameters (Callback&& callback) + { + cachedParamValues.ifSet ([&] (Steinberg::int32 index, float value) + { + callback (cachedParamValues.getParamID (index), value); + }); + } + + bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } + + //============================================================================== + static const FUID iid; + +private: + //============================================================================== + void setupParameters() + { + parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); + + #if JUCE_DEBUG + auto allGroups = parameterGroups; + allGroups.add (&audioProcessor->getParameterTree()); + std::unordered_set unitIDs; + + for (auto* group : allGroups) + { + auto insertResult = unitIDs.insert (getUnitID (group)); + + // If you hit this assertion then either a group ID is not unique or + // you are very unlucky and a hashed group ID is not unique + jassert (insertResult.second); + } + #endif + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + const bool forceLegacyParamIDs = true; + #else + const bool forceLegacyParamIDs = false; + #endif + + juceParameters.update (*audioProcessor, forceLegacyParamIDs); + auto numParameters = juceParameters.getNumParameters(); + + bool vst3WrapperProvidedBypassParam = false; + auto* bypassParameter = audioProcessor->getBypassParameter(); + + if (bypassParameter == nullptr) + { + vst3WrapperProvidedBypassParam = true; + ownedBypassParameter.reset (new AudioParameterBool ("byps", "Bypass", false)); + bypassParameter = ownedBypassParameter.get(); + } + + // if the bypass parameter is not part of the exported parameters that the plug-in supports + // then add it to the end of the list as VST3 requires the bypass parameter to be exported! + bypassIsRegularParameter = juceParameters.contains (audioProcessor->getBypassParameter()); + + if (! bypassIsRegularParameter) + juceParameters.addNonOwning (bypassParameter); + + int i = 0; + for (auto* juceParam : juceParameters) + { + bool isBypassParameter = (juceParam == bypassParameter); + + Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast (i++) + : generateVSTParamIDForParam (juceParam); + + if (isBypassParameter) + { + // we need to remain backward compatible with the old bypass id + if (vst3WrapperProvidedBypassParam) + { + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6240) + vstParamID = static_cast ((isUsingManagedParameters() && ! forceLegacyParamIDs) ? paramBypass : numParameters); + JUCE_END_IGNORE_WARNINGS_MSVC + } + + bypassParamID = vstParamID; + } + + vstParamIDs.add (vstParamID); + paramMap.set (static_cast (vstParamID), juceParam); + } + + auto numPrograms = audioProcessor->getNumPrograms(); + + if (numPrograms > 1) + { + ownedProgramParameter = std::make_unique ("juceProgramParameter", "Program", + 0, numPrograms - 1, + audioProcessor->getCurrentProgram()); + + juceParameters.addNonOwning (ownedProgramParameter.get()); + + if (forceLegacyParamIDs) + programParamID = static_cast (i++); + + vstParamIDs.add (programParamID); + paramMap.set (static_cast (programParamID), ownedProgramParameter.get()); + } + + cachedParamValues = CachedParamValues { { vstParamIDs.begin(), vstParamIDs.end() } }; + } + + Vst::ParamID generateVSTParamIDForParam (const AudioProcessorParameter* param) + { + auto juceParamID = LegacyAudioParameter::getParamID (param, false); + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + return static_cast (juceParamID.getIntValue()); + #else + auto paramHash = static_cast (juceParamID.hashCode()); + + #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS + // studio one doesn't like negative parameters + paramHash &= ~(((Vst::ParamID) 1) << (sizeof (Vst::ParamID) * 8 - 1)); + #endif + + return paramHash; + #endif + } + + //============================================================================== + Array vstParamIDs; + CachedParamValues cachedParamValues; + Vst::ParamID bypassParamID = 0, programParamID = static_cast (paramPreset); + bool bypassIsRegularParameter = false; + + //============================================================================== + std::atomic refCount { 0 }; + std::unique_ptr audioProcessor; + + //============================================================================== + LegacyAudioParametersWrapper juceParameters; + HashMap paramMap; + std::unique_ptr ownedBypassParameter, ownedProgramParameter; + Array parameterGroups; + + JuceAudioProcessor() = delete; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor) +}; + +class JuceVST3Component; + +static thread_local bool inParameterChangedCallback = false; + +static void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) +{ + if (param.getValue() == newValue) + return; + + const InParameterChangedCallbackSetter scopedSetter { inParameterChangedCallback }; + param.setValueNotifyingHost (newValue); +} + +//============================================================================== +class JuceVST3EditController : public Vst::EditController, + public Vst::IMidiMapping, + public Vst::IUnitInfo, + public Vst::ChannelContext::IInfoListener, + #if JucePlugin_Enable_ARA + public Presonus::IPlugInViewEmbedding, + #endif + public AudioProcessorListener, + private ComponentRestarter::Listener +{ +public: + explicit JuceVST3EditController (Vst::IHostApplication* host) + { + if (host != nullptr) + host->queryInterface (FUnknown::iid, (void**) &hostContext); + + blueCatPatchwork |= isBlueCatHost (host); + } + + //============================================================================== + static const FUID iid; + + //============================================================================== + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Winconsistent-missing-override") + + REFCOUNT_METHODS (ComponentBase) + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto userProvidedInterface = queryAdditionalInterfaces (getPluginInstance(), + targetIID, + &VST3ClientExtensions::queryIEditController); + + const auto juceProvidedInterface = queryInterfaceInternal (targetIID); + + return extractResult (userProvidedInterface, juceProvidedInterface, obj); + } + + //============================================================================== + tresult PLUGIN_API initialize (FUnknown* context) override + { + if (hostContext != context) + hostContext = context; + + blueCatPatchwork |= isBlueCatHost (context); + + return kResultTrue; + } + + tresult PLUGIN_API terminate() override + { + if (auto* pluginInstance = getPluginInstance()) + pluginInstance->removeListener (this); + + audioProcessor = nullptr; + + return EditController::terminate(); + } + + //============================================================================== + struct Param : public Vst::Parameter + { + Param (JuceVST3EditController& editController, AudioProcessorParameter& p, + Vst::ParamID vstParamID, Vst::UnitID vstUnitID, + bool isBypassParameter) + : owner (editController), param (p) + { + info.id = vstParamID; + info.unitId = vstUnitID; + + updateParameterInfo(); + + info.stepCount = (Steinberg::int32) 0; + + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + if (param.isDiscrete()) + #endif + { + const int numSteps = param.getNumSteps(); + info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); + } + + info.defaultNormalizedValue = param.getDefaultValue(); + jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); + + // Is this a meter? + if ((((unsigned int) param.getCategory() & 0xffff0000) >> 16) == 2) + info.flags = Vst::ParameterInfo::kIsReadOnly; + else + info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; + + if (isBypassParameter) + info.flags |= Vst::ParameterInfo::kIsBypass; + + valueNormalized = info.defaultNormalizedValue; + } + + bool updateParameterInfo() + { + auto updateParamIfChanged = [] (Vst::String128& paramToUpdate, const String& newValue) + { + if (juce::toString (paramToUpdate) == newValue) + return false; + + toString128 (paramToUpdate, newValue); + return true; + }; + + auto anyUpdated = updateParamIfChanged (info.title, param.getName (128)); + anyUpdated |= updateParamIfChanged (info.shortTitle, param.getName (8)); + anyUpdated |= updateParamIfChanged (info.units, param.getLabel()); + + return anyUpdated; + } + + bool setNormalized (Vst::ParamValue v) override + { + v = jlimit (0.0, 1.0, v); + + if (v != valueNormalized) + { + valueNormalized = v; + + // Only update the AudioProcessor here if we're not playing, + // otherwise we get parallel streams of parameter value updates + // during playback + if (! owner.vst3IsPlaying) + setValueAndNotifyIfChanged (param, (float) v); + + changed(); + return true; + } + + return false; + } + + void toString (Vst::ParamValue value, Vst::String128 result) const override + { + if (LegacyAudioParameter::isLegacy (¶m)) + // remain backward-compatible with old JUCE code + toString128 (result, param.getCurrentValueAsText()); + else + toString128 (result, param.getText ((float) value, 128)); + } + + bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override + { + if (! LegacyAudioParameter::isLegacy (¶m)) + { + outValueNormalized = param.getValueForText (getStringFromVstTChars (text)); + return true; + } + + return false; + } + + static String getStringFromVstTChars (const Vst::TChar* text) + { + return juce::String (juce::CharPointer_UTF16 (reinterpret_cast (text))); + } + + Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v; } + Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v; } + + private: + JuceVST3EditController& owner; + AudioProcessorParameter& param; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param) + }; + + //============================================================================== + struct ProgramChangeParameter : public Vst::Parameter + { + ProgramChangeParameter (AudioProcessor& p, Vst::ParamID vstParamID) + : owner (p) + { + jassert (owner.getNumPrograms() > 1); + + info.id = vstParamID; + toString128 (info.title, "Program"); + toString128 (info.shortTitle, "Program"); + toString128 (info.units, ""); + info.stepCount = owner.getNumPrograms() - 1; + info.defaultNormalizedValue = static_cast (owner.getCurrentProgram()) + / static_cast (info.stepCount); + info.unitId = Vst::kRootUnitId; + info.flags = Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kCanAutomate; + } + + ~ProgramChangeParameter() override = default; + + bool setNormalized (Vst::ParamValue v) override + { + const auto programValue = getProgramValueFromNormalised (v); + + if (programValue != owner.getCurrentProgram()) + owner.setCurrentProgram (programValue); + + if (valueNormalized != v) + { + valueNormalized = v; + changed(); + + return true; + } + + return false; + } + + void toString (Vst::ParamValue value, Vst::String128 result) const override + { + toString128 (result, owner.getProgramName (roundToInt (value * info.stepCount))); + } + + bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override + { + auto paramValueString = getStringFromVstTChars (text); + auto n = owner.getNumPrograms(); + + for (int i = 0; i < n; ++i) + { + if (paramValueString == owner.getProgramName (i)) + { + outValueNormalized = static_cast (i) / info.stepCount; + return true; + } + } + + return false; + } + + static String getStringFromVstTChars (const Vst::TChar* text) + { + return String (CharPointer_UTF16 (reinterpret_cast (text))); + } + + Steinberg::int32 getProgramValueFromNormalised (Vst::ParamValue v) const + { + return jmin (info.stepCount, (Steinberg::int32) (v * (info.stepCount + 1))); + } + + Vst::ParamValue toPlain (Vst::ParamValue v) const override { return getProgramValueFromNormalised (v); } + Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v / info.stepCount; } + + private: + AudioProcessor& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) + }; + + //============================================================================== + tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override + { + if (auto* instance = getPluginInstance()) + { + if (list != nullptr) + { + AudioProcessor::TrackProperties trackProperties; + + { + Vst::String128 channelName; + if (list->getString (Vst::ChannelContext::kChannelNameKey, channelName, sizeof (channelName)) == kResultTrue) + trackProperties.name = toString (channelName); + } + + { + int64 colour; + if (list->getInt (Vst::ChannelContext::kChannelColorKey, colour) == kResultTrue) + trackProperties.colour = Colour (Vst::ChannelContext::GetRed ((uint32) colour), Vst::ChannelContext::GetGreen ((uint32) colour), + Vst::ChannelContext::GetBlue ((uint32) colour), Vst::ChannelContext::GetAlpha ((uint32) colour)); + } + + + + if (MessageManager::getInstance()->isThisTheMessageThread()) + instance->updateTrackProperties (trackProperties); + else + MessageManager::callAsync ([trackProperties, instance] + { instance->updateTrackProperties (trackProperties); }); + } + } + + return kResultOk; + } + + //============================================================================== + #if JucePlugin_Enable_ARA + Steinberg::TBool PLUGIN_API isViewEmbeddingSupported() override + { + if (auto* pluginInstance = getPluginInstance()) + return (Steinberg::TBool) dynamic_cast (pluginInstance)->isEditorView(); + return (Steinberg::TBool) false; + } + + Steinberg::tresult PLUGIN_API setViewIsEmbedded (Steinberg::IPlugView* /*view*/, Steinberg::TBool /*embedded*/) override + { + return kResultOk; + } + #endif + + //============================================================================== + tresult PLUGIN_API setComponentState (IBStream* stream) override + { + // As an IEditController member, the host should only call this from the message thread. + assertHostMessageThread(); + + if (auto* pluginInstance = getPluginInstance()) + { + for (auto vstParamId : audioProcessor->getParamIDs()) + { + auto paramValue = [&] + { + if (vstParamId == audioProcessor->getProgramParamID()) + return EditController::plainParamToNormalized (audioProcessor->getProgramParamID(), + pluginInstance->getCurrentProgram()); + + return (double) audioProcessor->getParamForVSTParamID (vstParamId)->getValue(); + }(); + + setParamNormalized (vstParamId, paramValue); + } + } + + if (auto* handler = getComponentHandler()) + handler->restartComponent (Vst::kParamValuesChanged); + + return Vst::EditController::setComponentState (stream); + } + + void setAudioProcessor (JuceAudioProcessor* audioProc) + { + if (audioProcessor != audioProc) + installAudioProcessor (audioProc); + } + + tresult PLUGIN_API connect (IConnectionPoint* other) override + { + if (other != nullptr && audioProcessor == nullptr) + { + auto result = ComponentBase::connect (other); + + if (! audioProcessor.loadFrom (other)) + sendIntMessage ("JuceVST3EditController", (Steinberg::int64) (pointer_sized_int) this); + else + installAudioProcessor (audioProcessor); + + return result; + } + + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API getMidiControllerAssignment ([[maybe_unused]] Steinberg::int32 busIndex, + [[maybe_unused]] Steinberg::int16 channel, + [[maybe_unused]] Vst::CtrlNumber midiControllerNumber, + [[maybe_unused]] Vst::ParamID& resultID) override + { + #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + resultID = midiControllerToParameter[channel][midiControllerNumber]; + return kResultTrue; // Returning false makes some hosts stop asking for further MIDI Controller Assignments + #else + return kResultFalse; + #endif + } + + // Converts an incoming parameter index to a MIDI controller: + bool getMidiControllerForParameter (Vst::ParamID index, int& channel, int& ctrlNumber) + { + auto mappedIndex = static_cast (index - parameterToMidiControllerOffset); + + if (isPositiveAndBelow (mappedIndex, numElementsInArray (parameterToMidiController))) + { + auto& mc = parameterToMidiController[mappedIndex]; + + if (mc.channel != -1 && mc.ctrlNumber != -1) + { + channel = jlimit (1, 16, mc.channel + 1); + ctrlNumber = mc.ctrlNumber; + return true; + } + } + + return false; + } + + inline bool isMidiControllerParamID (Vst::ParamID paramID) const noexcept + { + return (paramID >= parameterToMidiControllerOffset + && isPositiveAndBelow (paramID - parameterToMidiControllerOffset, + static_cast (numElementsInArray (parameterToMidiController)))); + } + + //============================================================================== + Steinberg::int32 PLUGIN_API getUnitCount() override + { + if (audioProcessor != nullptr) + return audioProcessor->getUnitCount(); + + jassertfalse; + return 1; + } + + tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override + { + if (audioProcessor != nullptr) + return audioProcessor->getUnitInfo (unitIndex, info); + + jassertfalse; + if (unitIndex == 0) + { + info.id = Vst::kRootUnitId; + info.parentUnitId = Vst::kNoParentUnitId; + info.programListId = Vst::kNoProgramListId; + + toString128 (info.name, TRANS ("Root Unit")); + + return kResultTrue; + } + + zerostruct (info); + return kResultFalse; + } + + Steinberg::int32 PLUGIN_API getProgramListCount() override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramListCount(); + + jassertfalse; + return 0; + } + + tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramListInfo (listIndex, info); + + jassertfalse; + zerostruct (info); + return kResultFalse; + } + + tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramName (listId, programIndex, name); + + jassertfalse; + toString128 (name, juce::String()); + return kResultFalse; + } + + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Vst::CString attributeId, Vst::String128 attributeValue) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramInfo (listId, programIndex, attributeId, attributeValue); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override + { + if (audioProcessor != nullptr) + return audioProcessor->hasProgramPitchNames (listId, programIndex); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Steinberg::int16 midiPitch, Vst::String128 name) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramPitchName (listId, programIndex, midiPitch, name); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override + { + if (audioProcessor != nullptr) + return audioProcessor->selectUnit (unitId); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, + Steinberg::IBStream* data) override + { + if (audioProcessor != nullptr) + return audioProcessor->setUnitProgramData (listOrUnitId, programIndex, data); + + jassertfalse; + return kResultFalse; + } + + Vst::UnitID PLUGIN_API getSelectedUnit() override + { + if (audioProcessor != nullptr) + return audioProcessor->getSelectedUnit(); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, + Steinberg::int32 channel, Vst::UnitID& unitId) override + { + if (audioProcessor != nullptr) + return audioProcessor->getUnitByBus (type, dir, busIndex, channel, unitId); + + jassertfalse; + return kResultFalse; + } + + //============================================================================== + IPlugView* PLUGIN_API createView (const char* name) override + { + if (auto* pluginInstance = getPluginInstance()) + { + const auto mayCreateEditor = pluginInstance->hasEditor() + && name != nullptr + && std::strcmp (name, Vst::ViewType::kEditor) == 0 + && (pluginInstance->getActiveEditor() == nullptr + || detail::PluginUtilities::getHostType().isAdobeAudition() + || detail::PluginUtilities::getHostType().isPremiere()); + + if (mayCreateEditor) + return new JuceVST3Editor (*this, *audioProcessor); + } + + return nullptr; + } + + //============================================================================== + void beginGesture (Vst::ParamID vstParamId) + { + if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) + beginEdit (vstParamId); + } + + void endGesture (Vst::ParamID vstParamId) + { + if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) + endEdit (vstParamId); + } + + void paramChanged (Steinberg::int32 parameterIndex, Vst::ParamID vstParamId, double newValue) + { + if (inParameterChangedCallback || inSetState) + return; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + // NB: Cubase has problems if performEdit is called without setParamNormalized + EditController::setParamNormalized (vstParamId, newValue); + performEdit (vstParamId, newValue); + } + else + { + audioProcessor->setParameterValue (parameterIndex, (float) newValue); + } + } + + //============================================================================== + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override + { + beginGesture (audioProcessor->getVSTParamIDForIndex (index)); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override + { + endGesture (audioProcessor->getVSTParamIDForIndex (index)); + } + + void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override + { + paramChanged (index, audioProcessor->getVSTParamIDForIndex (index), newValue); + } + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + int32 flags = 0; + + if (details.parameterInfoChanged) + { + for (int32 i = 0; i < parameters.getParameterCount(); ++i) + if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) + if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) + flags |= Vst::kParamTitlesChanged; + } + + if (auto* pluginInstance = getPluginInstance()) + { + if (details.programChanged) + { + const auto programParameterId = audioProcessor->getProgramParamID(); + + if (audioProcessor->getParamForVSTParamID (programParameterId) != nullptr) + { + const auto currentProgram = pluginInstance->getCurrentProgram(); + const auto paramValue = roundToInt (EditController::normalizedParamToPlain (programParameterId, + EditController::getParamNormalized (programParameterId))); + + if (currentProgram != paramValue) + { + beginGesture (programParameterId); + paramChanged (audioProcessor->findCacheIndexForParamID (programParameterId), + programParameterId, + EditController::plainParamToNormalized (programParameterId, currentProgram)); + endGesture (programParameterId); + + flags |= Vst::kParamValuesChanged; + } + } + } + + auto latencySamples = pluginInstance->getLatencySamples(); + + #if JucePlugin_Enable_ARA + jassert (latencySamples == 0 || ! dynamic_cast (pluginInstance)->isBoundToARA()); + #endif + + if (details.latencyChanged && latencySamples != lastLatencySamples) + { + flags |= Vst::kLatencyChanged; + lastLatencySamples = latencySamples; + } + } + + if (details.nonParameterStateChanged) + flags |= pluginShouldBeMarkedDirtyFlag; + + if (inSetupProcessing) + flags &= Vst::kLatencyChanged; + + componentRestarter.restart (flags); + } + + //============================================================================== + AudioProcessor* getPluginInstance() const noexcept + { + if (audioProcessor != nullptr) + return audioProcessor->get(); + + return nullptr; + } + + static constexpr auto pluginShouldBeMarkedDirtyFlag = 1 << 16; + +private: + bool isBlueCatHost (FUnknown* context) const + { + // We can't use the normal PluginHostType mechanism here because that will give us the name + // of the host process. However, this plugin instance might be loaded in an instance of + // the BlueCat PatchWork host, which might itself be a plugin. + + VSTComSmartPtr host; + host.loadFrom (context); + + if (host == nullptr) + return false; + + Vst::String128 name; + + if (host->getName (name) != kResultOk) + return false; + + const auto hostName = toString (name); + return hostName.contains ("Blue Cat's VST3 Host"); + } + + friend class JuceVST3Component; + friend struct Param; + + //============================================================================== + VSTComSmartPtr audioProcessor; + + struct MidiController + { + int channel = -1, ctrlNumber = -1; + }; + + ComponentRestarter componentRestarter { *this }; + + enum { numMIDIChannels = 16 }; + Vst::ParamID parameterToMidiControllerOffset; + MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; + Vst::ParamID midiControllerToParameter[numMIDIChannels][Vst::kCountCtrlNumber]; + + void restartComponentOnMessageThread (int32 flags) override + { + if ((flags & pluginShouldBeMarkedDirtyFlag) != 0) + setDirty (true); + + flags &= ~pluginShouldBeMarkedDirtyFlag; + + if (auto* handler = componentHandler) + handler->restartComponent (flags); + } + + //============================================================================== + struct OwnedParameterListener : public AudioProcessorParameter::Listener + { + OwnedParameterListener (JuceVST3EditController& editController, + AudioProcessorParameter& parameter, + Vst::ParamID paramID, + int cacheIndex) + : owner (editController), + vstParamID (paramID), + parameterIndex (cacheIndex) + { + // We shouldn't be using an OwnedParameterListener for parameters that have + // been added directly to the AudioProcessor. We observe those via the + // normal audioProcessorParameterChanged mechanism. + jassert (parameter.getParameterIndex() == -1); + // The parameter must have a non-negative index in the parameter cache. + jassert (parameterIndex >= 0); + parameter.addListener (this); + } + + void parameterValueChanged (int, float newValue) override + { + owner.paramChanged (parameterIndex, vstParamID, newValue); + } + + void parameterGestureChanged (int, bool gestureIsStarting) override + { + if (gestureIsStarting) + owner.beginGesture (vstParamID); + else + owner.endGesture (vstParamID); + } + + JuceVST3EditController& owner; + const Vst::ParamID vstParamID = Vst::kNoParamId; + const int parameterIndex = -1; + }; + + std::vector> ownedParameterListeners; + + //============================================================================== + bool inSetState = false; + std::atomic vst3IsPlaying { false }, + inSetupProcessing { false }; + + int lastLatencySamples = 0; + bool blueCatPatchwork = isBlueCatHost (hostContext.get()); + + #if ! JUCE_MAC + float lastScaleFactorReceived = 1.0f; + #endif + + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + SharedBase{}, + UniqueBase{}, + #if JucePlugin_Enable_ARA + UniqueBase{}, + #endif + SharedBase{}); + + if (result.isOk()) + return result; + + if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) + return { kResultOk, audioProcessor.get() }; + + return {}; + } + + void installAudioProcessor (const VSTComSmartPtr& newAudioProcessor) + { + audioProcessor = newAudioProcessor; + + if (auto* extensions = dynamic_cast (audioProcessor->get())) + { + extensions->setIComponentHandler (componentHandler); + extensions->setIHostApplication (hostContext.get()); + } + + if (auto* pluginInstance = getPluginInstance()) + { + lastLatencySamples = pluginInstance->getLatencySamples(); + + pluginInstance->addListener (this); + + // as the bypass is not part of the regular parameters we need to listen for it explicitly + if (! audioProcessor->isBypassRegularParameter()) + { + const auto paramID = audioProcessor->getBypassParamID(); + ownedParameterListeners.push_back (std::make_unique (*this, + *audioProcessor->getParamForVSTParamID (paramID), + paramID, + audioProcessor->findCacheIndexForParamID (paramID))); + } + + if (parameters.getParameterCount() <= 0) + { + auto n = audioProcessor->getParamIDs().size(); + + for (int i = 0; i < n; ++i) + { + auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); + + if (vstParamID == audioProcessor->getProgramParamID()) + continue; + + auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); + auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); + auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); + + parameters.addParameter (new Param (*this, *juceParam, vstParamID, unitID, + (vstParamID == audioProcessor->getBypassParamID()))); + } + + const auto programParamId = audioProcessor->getProgramParamID(); + + if (auto* programParam = audioProcessor->getParamForVSTParamID (programParamId)) + { + ownedParameterListeners.push_back (std::make_unique (*this, + *programParam, + programParamId, + audioProcessor->findCacheIndexForParamID (programParamId))); + + parameters.addParameter (new ProgramChangeParameter (*pluginInstance, audioProcessor->getProgramParamID())); + } + } + + #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + parameterToMidiControllerOffset = static_cast (audioProcessor->isUsingManagedParameters() ? JuceAudioProcessor::paramMidiControllerOffset + : parameters.getParameterCount()); + + initialiseMidiControllerMappings(); + #endif + + audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); + } + } + + void initialiseMidiControllerMappings() + { + for (int c = 0, p = 0; c < numMIDIChannels; ++c) + { + for (int i = 0; i < Vst::kCountCtrlNumber; ++i, ++p) + { + midiControllerToParameter[c][i] = static_cast (p) + parameterToMidiControllerOffset; + parameterToMidiController[p].channel = c; + parameterToMidiController[p].ctrlNumber = i; + + parameters.addParameter (new Vst::Parameter (toString ("MIDI CC " + String (c) + "|" + String (i)), + static_cast (p) + parameterToMidiControllerOffset, nullptr, 0, 0, + 0, Vst::kRootUnitId)); + } + } + } + + void sendIntMessage (const char* idTag, const Steinberg::int64 value) + { + jassert (hostContext != nullptr); + + if (auto* message = allocateMessage()) + { + const FReleaser releaser (message); + message->setMessageID (idTag); + message->getAttributes()->setInt (idTag, value); + sendMessage (message); + } + } + + class EditorContextMenu : public HostProvidedContextMenu + { + public: + EditorContextMenu (AudioProcessorEditor& editorIn, + VSTComSmartPtr contextMenuIn) + : editor (editorIn), contextMenu (contextMenuIn) {} + + PopupMenu getEquivalentPopupMenu() const override + { + using MenuItem = Steinberg::Vst::IContextMenuItem; + using MenuTarget = Steinberg::Vst::IContextMenuTarget; + + struct Submenu + { + PopupMenu menu; + String name; + bool enabled; + }; + + std::vector menuStack (1); + + for (int32_t i = 0, end = contextMenu->getItemCount(); i < end; ++i) + { + MenuItem item{}; + MenuTarget* target = nullptr; + contextMenu->getItem (i, item, &target); + + if ((item.flags & MenuItem::kIsGroupStart) == MenuItem::kIsGroupStart) + { + menuStack.push_back ({ PopupMenu{}, + toString (item.name), + (item.flags & MenuItem::kIsDisabled) == 0 }); + } + else if ((item.flags & MenuItem::kIsGroupEnd) == MenuItem::kIsGroupEnd) + { + const auto back = menuStack.back(); + menuStack.pop_back(); + + if (menuStack.empty()) + { + // malformed menu + jassertfalse; + return {}; + } + + menuStack.back().menu.addSubMenu (back.name, back.menu, back.enabled); + } + else if ((item.flags & MenuItem::kIsSeparator) == MenuItem::kIsSeparator) + { + menuStack.back().menu.addSeparator(); + } + else + { + VSTComSmartPtr ownedTarget (target); + const auto tag = item.tag; + menuStack.back().menu.addItem (toString (item.name), + (item.flags & MenuItem::kIsDisabled) == 0, + (item.flags & MenuItem::kIsChecked) != 0, + [ownedTarget, tag] { ownedTarget->executeMenuItem (tag); }); + } + } + + if (menuStack.size() != 1) + { + // malformed menu + jassertfalse; + return {}; + } + + return menuStack.back().menu; + } + + void showNativeMenu (Point pos) const override + { + const auto scaled = pos * Component::getApproximateScaleFactorForComponent (&editor); + contextMenu->popup (scaled.x, scaled.y); + } + + private: + AudioProcessorEditor& editor; + VSTComSmartPtr contextMenu; + }; + + class EditorHostContext : public AudioProcessorEditorHostContext + { + public: + EditorHostContext (JuceAudioProcessor& processorIn, + AudioProcessorEditor& editorIn, + Steinberg::Vst::IComponentHandler* handler, + Steinberg::IPlugView* viewIn) + : processor (processorIn), editor (editorIn), componentHandler (handler), view (viewIn) {} + + std::unique_ptr getContextMenuForParameter (const AudioProcessorParameter* parameter) const override + { + if (componentHandler == nullptr || view == nullptr) + return {}; + + Steinberg::FUnknownPtr handler (componentHandler); + + if (handler == nullptr) + return {}; + + const auto idToUse = parameter != nullptr ? processor.getVSTParamIDForIndex (parameter->getParameterIndex()) : 0; + const auto menu = VSTComSmartPtr (handler->createContextMenu (view, &idToUse)); + return std::make_unique (editor, menu); + } + + private: + JuceAudioProcessor& processor; + AudioProcessorEditor& editor; + Steinberg::Vst::IComponentHandler* componentHandler = nullptr; + Steinberg::IPlugView* view = nullptr; + }; + + //============================================================================== + class JuceVST3Editor : public Vst::EditorView, + public Steinberg::IPlugViewContentScaleSupport, + private Timer + { + public: + JuceVST3Editor (JuceVST3EditController& ec, JuceAudioProcessor& p) + : EditorView (&ec, nullptr), + owner (&ec), + pluginInstance (*p.get()) + { + createContentWrapperComponentIfNeeded(); + + #if JUCE_MAC + if (detail::PluginUtilities::getHostType().type == PluginHostType::SteinbergCubase10) + cubase10Workaround.reset (new Cubase10WindowResizeWorkaround (*this)); + #endif + } + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto result = testFor (*this, targetIID, UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + return Vst::EditorView::queryInterface (targetIID, obj); + } + + REFCOUNT_METHODS (Vst::EditorView) + + //============================================================================== + tresult PLUGIN_API isPlatformTypeSupported (FIDString type) override + { + if (type != nullptr && pluginInstance.hasEditor()) + { + #if JUCE_WINDOWS + if (strcmp (type, kPlatformTypeHWND) == 0) + #elif JUCE_MAC + if (strcmp (type, kPlatformTypeNSView) == 0 || strcmp (type, kPlatformTypeHIView) == 0) + #elif JUCE_LINUX || JUCE_BSD + if (strcmp (type, kPlatformTypeX11EmbedWindowID) == 0) + #endif + return kResultTrue; + } + + return kResultFalse; + } + + tresult PLUGIN_API attached (void* parent, FIDString type) override + { + if (parent == nullptr || isPlatformTypeSupported (type) == kResultFalse) + return kResultFalse; + + #if JUCE_LINUX || JUCE_BSD + eventHandler->registerHandlerForFrame (plugFrame); + #endif + + systemWindow = parent; + + createContentWrapperComponentIfNeeded(); + + const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()); + + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + // If the plugin was last opened at a particular scale, try to reapply that scale here. + // Note that we do this during attach(), rather than in JuceVST3Editor(). During the + // constructor, we don't have a host plugFrame, so + // ContentWrapperComponent::resizeHostWindow() won't do anything, and the content + // wrapper component will be left at the wrong size. + applyScaleFactor (StoredScaleFactor{}.withInternal (owner->lastScaleFactorReceived)); + + // Check the host scale factor *before* calling addToDesktop, so that the initial + // window size during addToDesktop is correct for the current platform scale factor. + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + component->checkHostWindowScaleFactor(); + #endif + + component->setOpaque (true); + component->addToDesktop (desktopFlags, systemWindow); + component->setVisible (true); + + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + component->startTimer (500); + #endif + + #else + macHostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (component.get(), desktopFlags, parent); + #endif + + component->resizeHostWindow(); + attachedToParent(); + + // Life's too short to faff around with wave lab + if (detail::PluginUtilities::getHostType().isWavelab()) + startTimer (200); + + return kResultTrue; + } + + tresult PLUGIN_API removed() override + { + if (component != nullptr) + { + #if JUCE_WINDOWS + component->removeFromDesktop(); + #elif JUCE_MAC + if (macHostWindow != nullptr) + { + detail::VSTWindowUtilities::detachComponentFromWindowRefVST (component.get(), macHostWindow); + macHostWindow = nullptr; + } + #endif + + component = nullptr; + } + + #if JUCE_LINUX || JUCE_BSD + eventHandler->unregisterHandlerForFrame (plugFrame); + #endif + + return CPluginView::removed(); + } + + tresult PLUGIN_API onSize (ViewRect* newSize) override + { + if (newSize != nullptr) + { + rect = convertFromHostBounds (*newSize); + + if (component != nullptr) + { + component->setSize (rect.getWidth(), rect.getHeight()); + + #if JUCE_MAC + if (cubase10Workaround != nullptr) + { + cubase10Workaround->triggerAsyncUpdate(); + } + else + #endif + { + if (auto* peer = component->getPeer()) + peer->updateBounds(); + } + } + + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getSize (ViewRect* size) override + { + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + if (detail::PluginUtilities::getHostType().isAbletonLive() && systemWindow == nullptr) + return kResultFalse; + #endif + + if (size != nullptr && component != nullptr) + { + auto editorBounds = component->getSizeToContainChild(); + + *size = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); + return kResultTrue; + } + + return kResultFalse; + } + + tresult PLUGIN_API canResize() override + { + if (component != nullptr) + if (auto* editor = component->pluginEditor.get()) + if (editor->isResizable()) + return kResultTrue; + + return kResultFalse; + } + + tresult PLUGIN_API checkSizeConstraint (ViewRect* rectToCheck) override + { + if (rectToCheck != nullptr && component != nullptr) + { + if (auto* editor = component->pluginEditor.get()) + { + if (canResize() == kResultFalse) + { + // Ableton Live will call checkSizeConstraint even if the view returns false + // from canResize. Set the out param to an appropriate size for the editor + // and return. + auto constrainedRect = component->getLocalArea (editor, editor->getLocalBounds()) + .getSmallestIntegerContainer(); + + *rectToCheck = convertFromHostBounds (*rectToCheck); + rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); + rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); + *rectToCheck = convertToHostBounds (*rectToCheck); + } + else if (auto* constrainer = editor->getConstrainer()) + { + *rectToCheck = convertFromHostBounds (*rectToCheck); + + auto editorBounds = editor->getLocalArea (component.get(), + Rectangle::leftTopRightBottom (rectToCheck->left, rectToCheck->top, + rectToCheck->right, rectToCheck->bottom).toFloat()); + + auto minW = (float) constrainer->getMinimumWidth(); + auto maxW = (float) constrainer->getMaximumWidth(); + auto minH = (float) constrainer->getMinimumHeight(); + auto maxH = (float) constrainer->getMaximumHeight(); + + auto width = jlimit (minW, maxW, editorBounds.getWidth()); + auto height = jlimit (minH, maxH, editorBounds.getHeight()); + + auto aspectRatio = (float) constrainer->getFixedAspectRatio(); + + if (aspectRatio != 0.0) + { + bool adjustWidth = (width / height > aspectRatio); + + if (detail::PluginUtilities::getHostType().type == PluginHostType::SteinbergCubase9) + { + auto currentEditorBounds = editor->getBounds().toFloat(); + + if (currentEditorBounds.getWidth() == width && currentEditorBounds.getHeight() != height) + adjustWidth = true; + else if (currentEditorBounds.getHeight() == height && currentEditorBounds.getWidth() != width) + adjustWidth = false; + } + + if (adjustWidth) + { + width = height * aspectRatio; + + if (width > maxW || width < minW) + { + width = jlimit (minW, maxW, width); + height = width / aspectRatio; + } + } + else + { + height = width / aspectRatio; + + if (height > maxH || height < minH) + { + height = jlimit (minH, maxH, height); + width = height * aspectRatio; + } + } + } + + auto constrainedRect = component->getLocalArea (editor, Rectangle (width, height)) + .getSmallestIntegerContainer(); + + rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); + rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); + + *rectToCheck = convertToHostBounds (*rectToCheck); + } + } + + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API setContentScaleFactor ([[maybe_unused]] const Steinberg::IPlugViewContentScaleSupport::ScaleFactor factor) override + { + #if ! JUCE_MAC + const auto scaleToApply = [&] + { + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + // Cubase 10 only sends integer scale factors, so correct this for fractional scales + if (detail::PluginUtilities::getHostType().type != PluginHostType::SteinbergCubase10) + return factor; + + const auto hostWindowScale = (Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getScaleFactorForWindow (static_cast (systemWindow)); + + if (hostWindowScale <= 0.0 || approximatelyEqual (factor, hostWindowScale)) + return factor; + + return hostWindowScale; + #else + return factor; + #endif + }(); + + applyScaleFactor (scaleFactor.withHost (scaleToApply)); + + return kResultTrue; + #else + return kResultFalse; + #endif + } + + private: + void timerCallback() override + { + stopTimer(); + + ViewRect viewRect; + getSize (&viewRect); + onSize (&viewRect); + } + + static ViewRect convertToHostBounds (ViewRect pluginRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return pluginRect; + + return { roundToInt ((float) pluginRect.left * desktopScale), + roundToInt ((float) pluginRect.top * desktopScale), + roundToInt ((float) pluginRect.right * desktopScale), + roundToInt ((float) pluginRect.bottom * desktopScale) }; + } + + static ViewRect convertFromHostBounds (ViewRect hostRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return hostRect; + + return { roundToInt ((float) hostRect.left / desktopScale), + roundToInt ((float) hostRect.top / desktopScale), + roundToInt ((float) hostRect.right / desktopScale), + roundToInt ((float) hostRect.bottom / desktopScale) }; + } + + //============================================================================== + struct ContentWrapperComponent : public Component + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + , public Timer + #endif + { + ContentWrapperComponent (JuceVST3Editor& editor) : owner (editor) + { + setOpaque (true); + setBroughtToFrontOnMouseClick (true); + } + + ~ContentWrapperComponent() override + { + if (pluginEditor != nullptr) + { + PopupMenu::dismissAllActiveMenus(); + pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); + } + } + + void createEditor (AudioProcessor& plugin) + { + pluginEditor.reset (plugin.createEditorIfNeeded()); + + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (pluginEditor.get()) != nullptr); + // for proper view embedding, ARA plug-ins must be resizable + jassert (pluginEditor->isResizable()); + #endif + + if (pluginEditor != nullptr) + { + editorHostContext = std::make_unique (*owner.owner->audioProcessor, + *pluginEditor, + owner.owner->getComponentHandler(), + &owner); + + pluginEditor->setHostContext (editorHostContext.get()); + #if ! JUCE_MAC + pluginEditor->setScaleFactor (owner.scaleFactor.get()); + #endif + + addAndMakeVisible (pluginEditor.get()); + pluginEditor->setTopLeftPosition (0, 0); + + lastBounds = getSizeToContainChild(); + + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + setBounds (lastBounds); + } + + resizeHostWindow(); + } + else + { + // if hasEditor() returns true then createEditorIfNeeded has to return a valid editor + jassertfalse; + } + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + juce::Rectangle getSizeToContainChild() + { + if (pluginEditor != nullptr) + return getLocalArea (pluginEditor.get(), pluginEditor->getLocalBounds()); + + return {}; + } + + void childBoundsChanged (Component*) override + { + if (resizingChild) + return; + + auto newBounds = getSizeToContainChild(); + + if (newBounds != lastBounds) + { + resizeHostWindow(); + + #if JUCE_LINUX || JUCE_BSD + if (detail::PluginUtilities::getHostType().isBitwigStudio()) + repaint(); + #endif + + lastBounds = newBounds; + } + } + + void resized() override + { + if (pluginEditor != nullptr) + { + if (! resizingParent) + { + auto newBounds = getLocalBounds(); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); + } + + lastBounds = newBounds; + } + } + } + + void parentSizeChanged() override + { + if (pluginEditor != nullptr) + { + resizeHostWindow(); + pluginEditor->repaint(); + } + } + + void resizeHostWindow() + { + if (pluginEditor != nullptr) + { + if (owner.plugFrame != nullptr) + { + auto editorBounds = getSizeToContainChild(); + auto newSize = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); + + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + owner.plugFrame->resizeView (&owner, &newSize); + } + + auto host = detail::PluginUtilities::getHostType(); + + #if JUCE_MAC + if (host.isWavelab() || host.isReaper() || owner.owner->blueCatPatchwork) + #else + if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio() || owner.owner->blueCatPatchwork) + #endif + setBounds (editorBounds.withPosition (0, 0)); + } + } + } + + void setEditorScaleFactor (float scale) + { + if (pluginEditor != nullptr) + { + auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + + pluginEditor->setScaleFactor (scale); + pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); + } + + lastBounds = getSizeToContainChild(); + + resizeHostWindow(); + repaint(); + } + } + + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + void checkHostWindowScaleFactor() + { + const auto estimatedScale = (float) getScaleFactorForWindow (static_cast (owner.systemWindow)); + + if (estimatedScale > 0.0) + owner.applyScaleFactor (owner.scaleFactor.withInternal (estimatedScale)); + } + + void timerCallback() override + { + checkHostWindowScaleFactor(); + } + #endif + + std::unique_ptr pluginEditor; + + private: + JuceVST3Editor& owner; + std::unique_ptr editorHostContext; + Rectangle lastBounds; + bool resizingChild = false, resizingParent = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) + }; + + void createContentWrapperComponentIfNeeded() + { + if (component == nullptr) + { + #if JUCE_LINUX || JUCE_BSD + const MessageManagerLock mmLock; + #endif + + component.reset (new ContentWrapperComponent (*this)); + component->createEditor (pluginInstance); + } + } + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + SharedResourcePointer eventHandler; + #endif + + VSTComSmartPtr owner; + AudioProcessor& pluginInstance; + + #if JUCE_LINUX || JUCE_BSD + struct MessageManagerLockedDeleter + { + template + void operator() (ObjectType* object) const noexcept + { + const MessageManagerLock mmLock; + delete object; + } + }; + + std::unique_ptr component; + #else + std::unique_ptr component; + #endif + + friend struct ContentWrapperComponent; + + #if JUCE_MAC + void* macHostWindow = nullptr; + + // On macOS Cubase 10 resizes the host window after calling onSize() resulting in the peer + // bounds being a step behind the plug-in. Calling updateBounds() asynchronously seems to fix things... + struct Cubase10WindowResizeWorkaround : public AsyncUpdater + { + Cubase10WindowResizeWorkaround (JuceVST3Editor& o) : owner (o) {} + + void handleAsyncUpdate() override + { + if (owner.component != nullptr) + if (auto* peer = owner.component->getPeer()) + peer->updateBounds(); + } + + JuceVST3Editor& owner; + }; + + std::unique_ptr cubase10Workaround; + #else + class StoredScaleFactor + { + public: + StoredScaleFactor withHost (float x) const { return withMember (*this, &StoredScaleFactor::host, x); } + StoredScaleFactor withInternal (float x) const { return withMember (*this, &StoredScaleFactor::internal, x); } + float get() const { return host.value_or (internal); } + + private: + std::optional host; + float internal = 1.0f; + }; + + void applyScaleFactor (const StoredScaleFactor newFactor) + { + const auto previous = std::exchange (scaleFactor, newFactor).get(); + + if (previous == scaleFactor.get()) + return; + + if (owner != nullptr) + owner->lastScaleFactorReceived = scaleFactor.get(); + + if (component != nullptr) + { + #if JUCE_LINUX || JUCE_BSD + const MessageManagerLock mmLock; + #endif + component->setEditorScaleFactor (scaleFactor.get()); + } + } + + StoredScaleFactor scaleFactor; + + #if JUCE_WINDOWS + detail::WindowsHooks hooks; + #endif + + #endif + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Editor) + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) +}; + + +//============================================================================== +#if JucePlugin_Enable_ARA + class JuceARAFactory : public ARA::IMainFactory + { + public: + JuceARAFactory() = default; + virtual ~JuceARAFactory() = default; + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const ::Steinberg::TUID targetIID, void** obj) override + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + if (doUIDsMatch (targetIID, JuceARAFactory::iid)) + { + addRef(); + *obj = this; + return kResultOk; + } + + *obj = nullptr; + return kNoInterface; + } + + //---from ARA::IMainFactory------- + const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE + { + return createARAFactory(); + } + static const FUID iid; + + private: + //============================================================================== + std::atomic refCount { 1 }; + }; +#endif + +//============================================================================== +class JuceVST3Component : public Vst::IComponent, + public Vst::IAudioProcessor, + public Vst::IUnitInfo, + public Vst::IConnectionPoint, + public Vst::IProcessContextRequirements, + #if JucePlugin_Enable_ARA + public ARA::IPlugInEntryPoint, + public ARA::IPlugInEntryPoint2, + #endif + public AudioPlayHead +{ +public: + JuceVST3Component (Vst::IHostApplication* h) + : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_VST3).release()), + host (h) + { + inParameterChangedCallback = false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + [[maybe_unused]] const int numConfigs = numElementsInArray (configs); + + jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + + pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); + #endif + + // VST-3 requires your default layout to be non-discrete! + // For example, your default layout must be mono, stereo, quadrophonic + // and not AudioChannelSet::discreteChannels (2) etc. + jassert (checkBusFormatsAreNotDiscrete()); + + comPluginInstance = VSTComSmartPtr { new JuceAudioProcessor (pluginInstance) }; + + zerostruct (processContext); + + processSetup.maxSamplesPerBlock = 1024; + processSetup.processMode = Vst::kRealtime; + processSetup.sampleRate = 44100.0; + processSetup.symbolicSampleSize = Vst::kSample32; + + pluginInstance->setPlayHead (this); + + // Constructing the underlying static object involves dynamic allocation. + // This call ensures that the construction won't happen on the audio thread. + detail::PluginUtilities::getHostType(); + } + + ~JuceVST3Component() override + { + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = false; + + if (pluginInstance != nullptr) + if (pluginInstance->getPlayHead() == this) + pluginInstance->setPlayHead (nullptr); + } + + //============================================================================== + AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } + + //============================================================================== + static const FUID iid; + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto userProvidedInterface = queryAdditionalInterfaces (&getPluginInstance(), + targetIID, + &VST3ClientExtensions::queryIAudioProcessor); + + const auto juceProvidedInterface = queryInterfaceInternal (targetIID); + + return extractResult (userProvidedInterface, juceProvidedInterface, obj); + } + + enum class CallPrepareToPlay { no, yes }; + + //============================================================================== + tresult PLUGIN_API initialize (FUnknown* hostContext) override + { + if (host != hostContext) + host.loadFrom (hostContext); + + processContext.sampleRate = processSetup.sampleRate; + preparePlugin (processSetup.sampleRate, (int) processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); + + return kResultTrue; + } + + tresult PLUGIN_API terminate() override + { + getPluginInstance().releaseResources(); + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API connect (IConnectionPoint* other) override + { + if (other != nullptr && juceVST3EditController == nullptr) + juceVST3EditController.loadFrom (other); + + return kResultTrue; + } + + tresult PLUGIN_API disconnect (IConnectionPoint*) override + { + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = false; + + juceVST3EditController = {}; + return kResultTrue; + } + + tresult PLUGIN_API notify (Vst::IMessage* message) override + { + if (message != nullptr && juceVST3EditController == nullptr) + { + Steinberg::int64 value = 0; + + if (message->getAttributes()->getInt ("JuceVST3EditController", value) == kResultTrue) + { + juceVST3EditController = VSTComSmartPtr { (JuceVST3EditController*) (pointer_sized_int) value }; + + if (juceVST3EditController != nullptr) + juceVST3EditController->setAudioProcessor (comPluginInstance); + else + jassertfalse; + } + } + + return kResultTrue; + } + + tresult PLUGIN_API getControllerClassId (TUID classID) override + { + memcpy (classID, JuceVST3EditController::iid, sizeof (TUID)); + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API setActive (TBool state) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + const auto willBeActive = (state != 0); + + active = false; + // Some hosts may call setBusArrangements in response to calls made during prepareToPlay + // or releaseResources. Specifically, Wavelab 11.1 calls setBusArrangements in the same + // call stack when the AudioProcessor calls setLatencySamples inside prepareToPlay. + // In order for setBusArrangements to return successfully, the plugin must not be activated + // until after prepareToPlay has completely finished. + const ScopeGuard scope { [&] { active = willBeActive; } }; + + if (willBeActive) + { + const auto sampleRate = processSetup.sampleRate > 0.0 + ? processSetup.sampleRate + : getPluginInstance().getSampleRate(); + + const auto bufferSize = processSetup.maxSamplesPerBlock > 0 + ? (int) processSetup.maxSamplesPerBlock + : getPluginInstance().getBlockSize(); + + preparePlugin (sampleRate, bufferSize, CallPrepareToPlay::yes); + } + else + { + getPluginInstance().releaseResources(); + } + + return kResultOk; + } + + tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } + tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } + + //============================================================================== + bool isBypassed() const + { + if (auto* bypassParam = comPluginInstance->getBypassParameter()) + return bypassParam->getValue() >= 0.5f; + + return false; + } + + void setBypassed (bool shouldBeBypassed) + { + if (auto* bypassParam = comPluginInstance->getBypassParameter()) + setValueAndNotifyIfChanged (*bypassParam, shouldBeBypassed ? 1.0f : 0.0f); + } + + //============================================================================== + void writeJucePrivateStateInformation (MemoryOutputStream& out) + { + if (pluginInstance->getBypassParameter() == nullptr) + { + ValueTree privateData (kJucePrivateDataIdentifier); + + // for now we only store the bypass value + privateData.setProperty ("Bypass", var (isBypassed()), nullptr); + privateData.writeToStream (out); + } + } + + void setJucePrivateStateInformation (const void* data, int sizeInBytes) + { + if (pluginInstance->getBypassParameter() == nullptr) + { + if (comPluginInstance->getBypassParameter() != nullptr) + { + auto privateData = ValueTree::readFromData (data, static_cast (sizeInBytes)); + setBypassed (static_cast (privateData.getProperty ("Bypass", var (false)))); + } + } + } + + void getStateInformation (MemoryBlock& destData) + { + pluginInstance->getStateInformation (destData); + + // With bypass support, JUCE now needs to store private state data. + // Put this at the end of the plug-in state and add a few null characters + // so that plug-ins built with older versions of JUCE will hopefully ignore + // this data. Additionally, we need to add some sort of magic identifier + // at the very end of the private data so that JUCE has some sort of + // way to figure out if the data was stored with a newer JUCE version. + MemoryOutputStream extraData; + + extraData.writeInt64 (0); + writeJucePrivateStateInformation (extraData); + auto privateDataSize = (int64) (extraData.getDataSize() - sizeof (int64)); + extraData.writeInt64 (privateDataSize); + extraData << kJucePrivateDataIdentifier; + + // write magic string + destData.append (extraData.getData(), extraData.getDataSize()); + } + + void setStateInformation (const void* data, int sizeAsInt) + { + bool unusedState = false; + auto& flagToSet = juceVST3EditController != nullptr ? juceVST3EditController->inSetState : unusedState; + const ScopedValueSetter scope (flagToSet, true); + + auto size = (uint64) sizeAsInt; + + // Check if this data was written with a newer JUCE version + // and if it has the JUCE private data magic code at the end + auto jucePrivDataIdentifierSize = std::strlen (kJucePrivateDataIdentifier); + + if ((size_t) size >= jucePrivDataIdentifierSize + sizeof (int64)) + { + auto buffer = static_cast (data); + + String magic (CharPointer_UTF8 (buffer + size - jucePrivDataIdentifierSize), + CharPointer_UTF8 (buffer + size)); + + if (magic == kJucePrivateDataIdentifier) + { + // found a JUCE private data section + uint64 privateDataSize; + + std::memcpy (&privateDataSize, + buffer + ((size_t) size - jucePrivDataIdentifierSize - sizeof (uint64)), + sizeof (uint64)); + + privateDataSize = ByteOrder::swapIfBigEndian (privateDataSize); + size -= privateDataSize + jucePrivDataIdentifierSize + sizeof (uint64); + + if (privateDataSize > 0) + setJucePrivateStateInformation (buffer + size, static_cast (privateDataSize)); + + size -= sizeof (uint64); + } + } + + if (size > 0) + pluginInstance->setStateInformation (data, static_cast (size)); + } + + //============================================================================== + #if JUCE_VST3_CAN_REPLACE_VST2 + bool loadVST2VstWBlock (const char* data, int size) + { + jassert (ByteOrder::bigEndianInt ("VstW") == htonl ((uint32) readUnaligned (data))); + jassert (1 == htonl ((uint32) readUnaligned (data + 8))); // version should be 1 according to Steinberg's docs + + auto headerLen = (int) htonl ((uint32) readUnaligned (data + 4)) + 8; + return loadVST2CcnKBlock (data + headerLen, size - headerLen); + } + + bool loadVST2CcnKBlock (const char* data, int size) + { + auto* bank = reinterpret_cast (data); + + jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); + jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); + jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); + jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); + + setStateInformation (bank->content.data.chunk, + jmin ((int) (size - (bank->content.data.chunk - data)), + (int) htonl ((uint32) bank->content.data.size))); + return true; + } + + bool loadVST3PresetFile (const char* data, int size) + { + if (size < 48) + return false; + + // At offset 4 there's a little-endian version number which seems to typically be 1 + // At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" + auto chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); + jassert (memcmp (data + chunkListOffset, "List", 4) == 0); + auto entryCount = (int) ByteOrder::littleEndianInt (data + chunkListOffset + 4); + jassert (entryCount > 0); + + for (int i = 0; i < entryCount; ++i) + { + auto entryOffset = chunkListOffset + 8 + 20 * i; + + if (entryOffset + 20 > size) + return false; + + if (memcmp (data + entryOffset, "Comp", 4) == 0) + { + // "Comp" entries seem to contain the data. + auto chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); + auto chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); + + if (static_cast (chunkOffset + chunkSize) > static_cast (size)) + { + jassertfalse; + return false; + } + + loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); + } + } + + return true; + } + + bool loadVST2CompatibleState (const char* data, int size) + { + if (size < 4) + return false; + + auto header = htonl ((uint32) readUnaligned (data)); + + if (header == ByteOrder::bigEndianInt ("VstW")) + return loadVST2VstWBlock (data, size); + + if (header == ByteOrder::bigEndianInt ("CcnK")) + return loadVST2CcnKBlock (data, size); + + if (memcmp (data, "VST3", 4) == 0) + { + // In Cubase 5, when loading VST3 .vstpreset files, + // we get the whole content of the files to load. + // In Cubase 7 we get just the contents within and + // we go directly to the loadVST2VstW codepath instead. + return loadVST3PresetFile (data, size); + } + + return false; + } + #endif + + void loadStateData (const void* data, int size) + { + #if JUCE_VST3_CAN_REPLACE_VST2 + if (loadVST2CompatibleState ((const char*) data, size)) + return; + #endif + setStateInformation (data, size); + } + + bool readFromMemoryStream (IBStream* state) + { + FUnknownPtr s (state); + Steinberg::int64 size = 0; + + if (s != nullptr + && s->getStreamSize (size) == kResultOk + && size > 0 + && size < 1024 * 1024 * 100) // (some hosts seem to return junk for the size) + { + MemoryBlock block (static_cast (size)); + + // turns out that Cubase 9 might give you the incorrect stream size :-( + Steinberg::int32 bytesRead = 1; + int len; + + for (len = 0; bytesRead > 0 && len < static_cast (block.getSize()); len += bytesRead) + if (state->read (block.getData(), static_cast (block.getSize()), &bytesRead) != kResultOk) + break; + + if (len == 0) + return false; + + block.setSize (static_cast (len)); + + // Adobe Audition CS6 hack to avoid trying to use corrupted streams: + if (detail::PluginUtilities::getHostType().isAdobeAudition()) + if (block.getSize() >= 5 && memcmp (block.getData(), "VC2!E", 5) == 0) + return false; + + loadStateData (block.getData(), (int) block.getSize()); + return true; + } + + return false; + } + + bool readFromUnknownStream (IBStream* state) + { + MemoryOutputStream allData; + + { + const size_t bytesPerBlock = 4096; + HeapBlock buffer (bytesPerBlock); + + for (;;) + { + Steinberg::int32 bytesRead = 0; + auto status = state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead); + + if (bytesRead <= 0 || (status != kResultTrue && ! detail::PluginUtilities::getHostType().isWavelab())) + break; + + allData.write (buffer, static_cast (bytesRead)); + } + } + + const size_t dataSize = allData.getDataSize(); + + if (dataSize <= 0 || dataSize >= 0x7fffffff) + return false; + + loadStateData (allData.getData(), (int) dataSize); + return true; + } + + tresult PLUGIN_API setState (IBStream* state) override + { + // The VST3 spec requires that this function is called from the UI thread. + // If this assertion fires, your host is misbehaving! + assertHostMessageThread(); + + if (state == nullptr) + return kInvalidArgument; + + FUnknownPtr stateRefHolder (state); // just in case the caller hasn't properly ref-counted the stream object + + if (state->seek (0, IBStream::kIBSeekSet, nullptr) == kResultTrue) + { + if (! detail::PluginUtilities::getHostType().isFruityLoops() && readFromMemoryStream (state)) + return kResultTrue; + + if (readFromUnknownStream (state)) + return kResultTrue; + } + + return kResultFalse; + } + + #if JUCE_VST3_CAN_REPLACE_VST2 + static tresult writeVST2Header (IBStream* state, bool bypassed) + { + auto writeVST2IntToState = [state] (uint32 n) + { + auto t = (int32) htonl (n); + return state->write (&t, 4); + }; + + auto status = writeVST2IntToState (ByteOrder::bigEndianInt ("VstW")); + + if (status == kResultOk) status = writeVST2IntToState (8); // header size + if (status == kResultOk) status = writeVST2IntToState (1); // version + if (status == kResultOk) status = writeVST2IntToState (bypassed ? 1 : 0); // bypass + + return status; + } + #endif + + tresult PLUGIN_API getState (IBStream* state) override + { + if (state == nullptr) + return kInvalidArgument; + + MemoryBlock mem; + getStateInformation (mem); + + #if JUCE_VST3_CAN_REPLACE_VST2 + tresult status = writeVST2Header (state, isBypassed()); + + if (status != kResultOk) + return status; + + const int bankBlockSize = 160; + Vst2::fxBank bank; + + zerostruct (bank); + bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); + bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); + bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); + bank.version = (int32) htonl (2); + bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); + bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); + bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); + + status = state->write (&bank, bankBlockSize); + + if (status != kResultOk) + return status; + #endif + + return state->write (mem.getData(), (Steinberg::int32) mem.getSize()); + } + + //============================================================================== + Steinberg::int32 PLUGIN_API getUnitCount() override { return comPluginInstance->getUnitCount(); } + tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override { return comPluginInstance->getUnitInfo (unitIndex, info); } + Steinberg::int32 PLUGIN_API getProgramListCount() override { return comPluginInstance->getProgramListCount(); } + tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override { return comPluginInstance->getProgramListInfo (listIndex, info); } + tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override { return comPluginInstance->getProgramName (listId, programIndex, name); } + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Vst::CString attributeId, Vst::String128 attributeValue) override { return comPluginInstance->getProgramInfo (listId, programIndex, attributeId, attributeValue); } + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override { return comPluginInstance->hasProgramPitchNames (listId, programIndex); } + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Steinberg::int16 midiPitch, Vst::String128 name) override { return comPluginInstance->getProgramPitchName (listId, programIndex, midiPitch, name); } + tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override { return comPluginInstance->selectUnit (unitId); } + tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, + Steinberg::IBStream* data) override { return comPluginInstance->setUnitProgramData (listOrUnitId, programIndex, data); } + Vst::UnitID PLUGIN_API getSelectedUnit() override { return comPluginInstance->getSelectedUnit(); } + tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, + Steinberg::int32 channel, Vst::UnitID& unitId) override { return comPluginInstance->getUnitByBus (type, dir, busIndex, channel, unitId); } + + //============================================================================== + Optional getPosition() const override + { + PositionInfo info; + info.setTimeInSamples (jmax ((juce::int64) 0, processContext.projectTimeSamples)); + info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processContext.sampleRate); + info.setIsRecording ((processContext.state & Vst::ProcessContext::kRecording) != 0); + info.setIsPlaying ((processContext.state & Vst::ProcessContext::kPlaying) != 0); + info.setIsLooping ((processContext.state & Vst::ProcessContext::kCycleActive) != 0); + + info.setBpm ((processContext.state & Vst::ProcessContext::kTempoValid) != 0 + ? makeOptional (processContext.tempo) + : nullopt); + + info.setTimeSignature ((processContext.state & Vst::ProcessContext::kTimeSigValid) != 0 + ? makeOptional (TimeSignature { processContext.timeSigNumerator, processContext.timeSigDenominator }) + : nullopt); + + info.setLoopPoints ((processContext.state & Vst::ProcessContext::kCycleValid) != 0 + ? makeOptional (LoopPoints { processContext.cycleStartMusic, processContext.cycleEndMusic }) + : nullopt); + + info.setPpqPosition ((processContext.state & Vst::ProcessContext::kProjectTimeMusicValid) != 0 + ? makeOptional (processContext.projectTimeMusic) + : nullopt); + + info.setPpqPositionOfLastBarStart ((processContext.state & Vst::ProcessContext::kBarPositionValid) != 0 + ? makeOptional (processContext.barPositionMusic) + : nullopt); + + info.setFrameRate ((processContext.state & Vst::ProcessContext::kSmpteValid) != 0 + ? makeOptional (FrameRate().withBaseRate ((int) processContext.frameRate.framesPerSecond) + .withDrop ((processContext.frameRate.flags & Vst::FrameRate::kDropRate) != 0) + .withPullDown ((processContext.frameRate.flags & Vst::FrameRate::kPullDownRate) != 0)) + : nullopt); + + info.setEditOriginTime (info.getFrameRate().hasValue() + ? makeOptional ((double) processContext.smpteOffsetSubframes / (80.0 * info.getFrameRate()->getEffectiveRate())) + : nullopt); + + info.setHostTimeNs ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0 + ? makeOptional ((uint64_t) processContext.systemTime) + : nullopt); + + return info; + } + + //============================================================================== + int getNumAudioBuses (bool isInput) const + { + int busCount = pluginInstance->getBusCount (isInput); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = numElementsInArray (configs); + + bool hasOnlyZeroChannels = true; + + for (int i = 0; i < numConfigs && hasOnlyZeroChannels == true; ++i) + if (configs[i][isInput ? 0 : 1] != 0) + hasOnlyZeroChannels = false; + + busCount = jmin (busCount, hasOnlyZeroChannels ? 0 : 1); + #endif + + return busCount; + } + + //============================================================================== + Steinberg::int32 PLUGIN_API getBusCount (Vst::MediaType type, Vst::BusDirection dir) override + { + if (type == Vst::kAudio) + return getNumAudioBuses (dir == Vst::kInput); + + if (type == Vst::kEvent) + { + #if JucePlugin_WantsMidiInput + if (dir == Vst::kInput) + return 1; + #endif + + #if JucePlugin_ProducesMidiOutput + if (dir == Vst::kOutput) + return 1; + #endif + } + + return 0; + } + + tresult PLUGIN_API getBusInfo (Vst::MediaType type, Vst::BusDirection dir, + Steinberg::int32 index, Vst::BusInfo& info) override + { + if (type == Vst::kAudio) + { + if (index < 0 || index >= getNumAudioBuses (dir == Vst::kInput)) + return kResultFalse; + + if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) + { + info.mediaType = Vst::kAudio; + info.direction = dir; + info.channelCount = bus->getLastEnabledLayout().size(); + + [[maybe_unused]] const auto lastEnabledVst3Layout = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); + jassert (lastEnabledVst3Layout.has_value() && info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (*lastEnabledVst3Layout)); + toString128 (info.name, bus->getName()); + + info.busType = [&] + { + const auto isFirstBus = (index == 0); + + if (dir == Vst::kInput) + { + if (isFirstBus) + { + if (auto* extensions = dynamic_cast (pluginInstance)) + return extensions->getPluginHasMainInput() ? Vst::kMain : Vst::kAux; + + return Vst::kMain; + } + + return Vst::kAux; + } + + #if JucePlugin_IsSynth + return Vst::kMain; + #else + return isFirstBus ? Vst::kMain : Vst::kAux; + #endif + }(); + + #ifdef JucePlugin_PreferredChannelConfigurations + info.flags = Vst::BusInfo::kDefaultActive; + #else + info.flags = (bus->isEnabledByDefault()) ? Vst::BusInfo::kDefaultActive : 0; + #endif + + return kResultTrue; + } + } + + if (type == Vst::kEvent) + { + info.flags = Vst::BusInfo::kDefaultActive; + + #if JucePlugin_WantsMidiInput + if (dir == Vst::kInput && index == 0) + { + info.mediaType = Vst::kEvent; + info.direction = dir; + + #ifdef JucePlugin_VSTNumMidiInputs + info.channelCount = JucePlugin_VSTNumMidiInputs; + #else + info.channelCount = 16; + #endif + + toString128 (info.name, TRANS("MIDI Input")); + info.busType = Vst::kMain; + return kResultTrue; + } + #endif + + #if JucePlugin_ProducesMidiOutput + if (dir == Vst::kOutput && index == 0) + { + info.mediaType = Vst::kEvent; + info.direction = dir; + + #ifdef JucePlugin_VSTNumMidiOutputs + info.channelCount = JucePlugin_VSTNumMidiOutputs; + #else + info.channelCount = 16; + #endif + + toString128 (info.name, TRANS("MIDI Output")); + info.busType = Vst::kMain; + return kResultTrue; + } + #endif + } + + zerostruct (info); + return kResultFalse; + } + + tresult PLUGIN_API activateBus (Vst::MediaType type, + Vst::BusDirection dir, + Steinberg::int32 index, + TBool state) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + // The host is misbehaving! The plugin must be deactivated before setting new arrangements. + jassert (! active); + + if (type == Vst::kEvent) + { + #if JucePlugin_WantsMidiInput + if (index == 0 && dir == Vst::kInput) + { + isMidiInputBusEnabled = (state != 0); + return kResultTrue; + } + #endif + + #if JucePlugin_ProducesMidiOutput + if (index == 0 && dir == Vst::kOutput) + { + isMidiOutputBusEnabled = (state != 0); + return kResultTrue; + } + #endif + + return kResultFalse; + } + + if (type == Vst::kAudio) + { + const auto numInputBuses = getNumAudioBuses (true); + const auto numOutputBuses = getNumAudioBuses (false); + + if (! isPositiveAndBelow (index, dir == Vst::kInput ? numInputBuses : numOutputBuses)) + return kResultFalse; + + // The host is allowed to enable/disable buses as it sees fit, so the plugin needs to be + // able to handle any set of enabled/disabled buses, including layouts for which + // AudioProcessor::isBusesLayoutSupported would return false. + // Our strategy is to keep track of the layout that the host last requested, and to + // attempt to apply that layout directly. + // If the layout isn't supported by the processor, we'll try enabling all the buses + // instead. + // If the host enables a bus that the processor refused to enable, then we'll ignore + // that bus (and return silence for output buses). If the host disables a bus that the + // processor refuses to disable, the wrapper will provide the processor with silence for + // input buses, and ignore the contents of output buses. + // Note that some hosts (old bitwig and cakewalk) may incorrectly call this function + // when the plugin is in an activated state. + if (dir == Vst::kInput) + bufferMapper.setInputBusHostActive ((size_t) index, state != 0); + else + bufferMapper.setOutputBusHostActive ((size_t) index, state != 0); + + AudioProcessor::BusesLayout desiredLayout; + + for (auto i = 0; i < numInputBuses; ++i) + desiredLayout.inputBuses.add (bufferMapper.getRequestedLayoutForInputBus ((size_t) i)); + + for (auto i = 0; i < numOutputBuses; ++i) + desiredLayout.outputBuses.add (bufferMapper.getRequestedLayoutForOutputBus ((size_t) i)); + + const auto prev = pluginInstance->getBusesLayout(); + + const auto busesLayoutSupported = [&] + { + #ifdef JucePlugin_PreferredChannelConfigurations + struct ChannelPair + { + short ins, outs; + + auto tie() const { return std::tie (ins, outs); } + bool operator== (ChannelPair x) const { return tie() == x.tie(); } + }; + + const auto countChannels = [] (auto& range) + { + return std::accumulate (range.begin(), range.end(), 0, [] (auto acc, auto set) + { + return acc + set.size(); + }); + }; + + const auto toShort = [] (int x) + { + jassert (0 <= x && x <= std::numeric_limits::max()); + return (short) x; + }; + + const ChannelPair requested { toShort (countChannels (desiredLayout.inputBuses)), + toShort (countChannels (desiredLayout.outputBuses)) }; + const ChannelPair configs[] = { JucePlugin_PreferredChannelConfigurations }; + return std::find (std::begin (configs), std::end (configs), requested) != std::end (configs); + #else + return pluginInstance->checkBusesLayoutSupported (desiredLayout); + #endif + }(); + + if (busesLayoutSupported) + pluginInstance->setBusesLayout (desiredLayout); + else + pluginInstance->enableAllBuses(); + + bufferMapper.updateActiveClientBuses (pluginInstance->getBusesLayout()); + + return kResultTrue; + } + + return kResultFalse; + } + + bool checkBusFormatsAreNotDiscrete() + { + auto numInputBuses = pluginInstance->getBusCount (true); + auto numOutputBuses = pluginInstance->getBusCount (false); + + for (int i = 0; i < numInputBuses; ++i) + { + auto layout = pluginInstance->getChannelLayoutOfBus (true, i); + + if (layout.isDiscreteLayout() && ! layout.isDisabled()) + return false; + } + + for (int i = 0; i < numOutputBuses; ++i) + { + auto layout = pluginInstance->getChannelLayoutOfBus (false, i); + + if (layout.isDiscreteLayout() && ! layout.isDisabled()) + return false; + } + + return true; + } + + tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, + Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + if (active) + { + // The host is misbehaving! The plugin must be deactivated before setting new arrangements. + jassertfalse; + return kResultFalse; + } + + auto numInputBuses = pluginInstance->getBusCount (true); + auto numOutputBuses = pluginInstance->getBusCount (false); + + if (numIns > numInputBuses || numOuts > numOutputBuses) + return kResultFalse; + + // see the following documentation to understand the correct way to react to this callback + // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 + + const auto toLayoutsArray = [] (auto begin, auto end) -> std::optional> + { + Array result; + + for (auto it = begin; it != end; ++it) + { + const auto set = getChannelSetForSpeakerArrangement (*it); + + if (! set.has_value()) + return {}; + + result.add (*set); + } + + return result; + }; + + const auto optionalRequestedLayout = [&]() -> std::optional + { + const auto ins = toLayoutsArray (inputs, inputs + numIns); + const auto outs = toLayoutsArray (outputs, outputs + numOuts); + + if (! ins.has_value() || ! outs.has_value()) + return {}; + + AudioProcessor::BusesLayout result; + result.inputBuses = *ins; + result.outputBuses = *outs; + return result; + }(); + + if (! optionalRequestedLayout.has_value()) + return kResultFalse; + + const auto& requestedLayout = *optionalRequestedLayout; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + if (! AudioProcessor::containsLayout (requestedLayout, configs)) + return kResultFalse; + #endif + + if (pluginInstance->checkBusesLayoutSupported (requestedLayout)) + { + if (! pluginInstance->setBusesLayoutWithoutEnabling (requestedLayout)) + return kResultFalse; + + bufferMapper.updateFromProcessor (*pluginInstance); + return kResultTrue; + } + + // apply layout changes in reverse order as Steinberg says we should prioritize main buses + const auto nextBest = [this, numInputBuses, numOutputBuses, &requestedLayout] + { + auto layout = pluginInstance->getBusesLayout(); + + for (auto busIdx = jmax (numInputBuses, numOutputBuses) - 1; busIdx >= 0; --busIdx) + for (const auto isInput : { true, false }) + if (auto* bus = pluginInstance->getBus (isInput, busIdx)) + bus->isLayoutSupported (requestedLayout.getChannelSet (isInput, busIdx), &layout); + + return layout; + }(); + + if (pluginInstance->setBusesLayoutWithoutEnabling (nextBest)) + bufferMapper.updateFromProcessor (*pluginInstance); + + return kResultFalse; + } + + tresult PLUGIN_API getBusArrangement (Vst::BusDirection dir, Steinberg::int32 index, Vst::SpeakerArrangement& arr) override + { + if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) + { + if (const auto arrangement = getVst3SpeakerArrangement (bus->getLastEnabledLayout())) + { + arr = *arrangement; + return kResultTrue; + } + + // There's a bus here, but we can't represent its layout in terms of VST3 speakers! + jassertfalse; + } + + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override + { + return (symbolicSampleSize == Vst::kSample32 + || (getPluginInstance().supportsDoublePrecisionProcessing() + && symbolicSampleSize == Vst::kSample64)) ? kResultTrue : kResultFalse; + } + + Steinberg::uint32 PLUGIN_API getLatencySamples() override + { + return (Steinberg::uint32) jmax (0, getPluginInstance().getLatencySamples()); + } + + tresult PLUGIN_API setupProcessing (Vst::ProcessSetup& newSetup) override + { + ScopedInSetupProcessingSetter inSetupProcessingSetter (juceVST3EditController); + + if (canProcessSampleSize (newSetup.symbolicSampleSize) != kResultTrue) + return kResultFalse; + + processSetup = newSetup; + processContext.sampleRate = processSetup.sampleRate; + + getPluginInstance().setProcessingPrecision (newSetup.symbolicSampleSize == Vst::kSample64 + ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision); + getPluginInstance().setNonRealtime (newSetup.processMode == Vst::kOffline); + + preparePlugin (processSetup.sampleRate, processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); + + return kResultTrue; + } + + tresult PLUGIN_API setProcessing (TBool state) override + { + if (! state) + getPluginInstance().reset(); + + return kResultTrue; + } + + Steinberg::uint32 PLUGIN_API getTailSamples() override + { + auto tailLengthSeconds = getPluginInstance().getTailLengthSeconds(); + + if (tailLengthSeconds <= 0.0 || processSetup.sampleRate <= 0.0) + return Vst::kNoTail; + + if (tailLengthSeconds == std::numeric_limits::infinity()) + return Vst::kInfiniteTail; + + return (Steinberg::uint32) roundToIntAccurate (tailLengthSeconds * processSetup.sampleRate); + } + + //============================================================================== + void processParameterChanges (Vst::IParameterChanges& paramChanges) + { + jassert (pluginInstance != nullptr); + + struct ParamChangeInfo + { + Steinberg::int32 offsetSamples = 0; + double value = 0.0; + }; + + const auto getPointFromQueue = [] (Steinberg::Vst::IParamValueQueue* queue, Steinberg::int32 index) + { + ParamChangeInfo result; + return queue->getPoint (index, result.offsetSamples, result.value) == kResultTrue + ? makeOptional (result) + : nullopt; + }; + + const auto numParamsChanged = paramChanges.getParameterCount(); + + for (Steinberg::int32 i = 0; i < numParamsChanged; ++i) + { + if (auto* paramQueue = paramChanges.getParameterData (i)) + { + const auto vstParamID = paramQueue->getParameterId(); + const auto numPoints = paramQueue->getPointCount(); + + #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) + { + for (Steinberg::int32 point = 0; point < numPoints; ++point) + { + if (const auto change = getPointFromQueue (paramQueue, point)) + addParameterChangeToMidiBuffer (change->offsetSamples, vstParamID, change->value); + } + } + else + #endif + if (const auto change = getPointFromQueue (paramQueue, numPoints - 1)) + { + if (auto* param = comPluginInstance->getParamForVSTParamID (vstParamID)) + setValueAndNotifyIfChanged (*param, (float) change->value); + } + } + } + } + + void addParameterChangeToMidiBuffer (const Steinberg::int32 offsetSamples, const Vst::ParamID id, const double value) + { + // If the parameter is mapped to a MIDI CC message then insert it into the midiBuffer. + int channel, ctrlNumber; + + if (juceVST3EditController->getMidiControllerForParameter (id, channel, ctrlNumber)) + { + if (ctrlNumber == Vst::kAfterTouch) + midiBuffer.addEvent (MidiMessage::channelPressureChange (channel, + jlimit (0, 127, (int) (value * 128.0))), offsetSamples); + else if (ctrlNumber == Vst::kPitchBend) + midiBuffer.addEvent (MidiMessage::pitchWheel (channel, + jlimit (0, 0x3fff, (int) (value * 0x4000))), offsetSamples); + else + midiBuffer.addEvent (MidiMessage::controllerEvent (channel, + jlimit (0, 127, ctrlNumber), + jlimit (0, 127, (int) (value * 128.0))), offsetSamples); + } + } + + tresult PLUGIN_API process (Vst::ProcessData& data) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + if (pluginInstance == nullptr) + return kResultFalse; + + if ((processSetup.symbolicSampleSize == Vst::kSample64) != pluginInstance->isUsingDoublePrecision()) + return kResultFalse; + + if (data.processContext != nullptr) + { + processContext = *data.processContext; + + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; + } + else + { + zerostruct (processContext); + + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = false; + } + + midiBuffer.clear(); + + if (data.inputParameterChanges != nullptr) + processParameterChanges (*data.inputParameterChanges); + + #if JucePlugin_WantsMidiInput + if (isMidiInputBusEnabled && data.inputEvents != nullptr) + MidiEventList::toMidiBuffer (midiBuffer, *data.inputEvents); + #endif + + if (detail::PluginUtilities::getHostType().isWavelab()) + { + const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; + const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; + + if ((pluginInstance->getTotalNumInputChannels() + pluginInstance->getTotalNumOutputChannels()) > 0 + && (numInputChans + numOutputChans) == 0) + return kResultFalse; + } + + // If all of these are zero, the host is attempting to flush parameters without processing audio. + if (data.numSamples != 0 || data.numInputs != 0 || data.numOutputs != 0) + { + if (processSetup.symbolicSampleSize == Vst::kSample32) processAudio (data); + else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio (data); + else jassertfalse; + } + + if (auto* changes = data.outputParameterChanges) + { + comPluginInstance->forAllChangedParameters ([&] (Vst::ParamID paramID, float value) + { + Steinberg::int32 queueIndex = 0; + + if (auto* queue = changes->addParameterData (paramID, queueIndex)) + { + Steinberg::int32 pointIndex = 0; + queue->addPoint (0, value, pointIndex); + } + }); + } + + #if JucePlugin_ProducesMidiOutput + if (isMidiOutputBusEnabled && data.outputEvents != nullptr) + MidiEventList::pluginToHostEventList (*data.outputEvents, midiBuffer); + #endif + + return kResultTrue; + } + +private: + /* FL's Patcher implements the VST3 specification incorrectly, calls process() before/during + setActive(). + */ + class [[nodiscard]] FLStudioDIYSpecificationEnforcementLock + { + public: + explicit FLStudioDIYSpecificationEnforcementLock (CriticalSection& mutex) + { + static const auto lockRequired = PluginHostType().isFruityLoops(); + + if (lockRequired) + lock.emplace (mutex); + } + + private: + std::optional lock; + }; + + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + #if JucePlugin_Enable_ARA + UniqueBase{}, + UniqueBase{}, + #endif + SharedBase{}); + + if (result.isOk()) + return result; + + if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) + return { kResultOk, comPluginInstance.get() }; + + return {}; + } + + //============================================================================== + struct ScopedInSetupProcessingSetter + { + ScopedInSetupProcessingSetter (JuceVST3EditController* c) + : controller (c) + { + if (controller != nullptr) + controller->inSetupProcessing = true; + } + + ~ScopedInSetupProcessingSetter() + { + if (controller != nullptr) + controller->inSetupProcessing = false; + } + + private: + JuceVST3EditController* controller = nullptr; + }; + + //============================================================================== + template + void processAudio (Vst::ProcessData& data) + { + ClientRemappedBuffer remappedBuffer { bufferMapper, data }; + auto& buffer = remappedBuffer.buffer; + + jassert ((int) buffer.getNumChannels() == jmax (pluginInstance->getTotalNumInputChannels(), + pluginInstance->getTotalNumOutputChannels())); + + { + const ScopedLock sl (pluginInstance->getCallbackLock()); + + pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); + + #if JUCE_DEBUG && ! JucePlugin_ProducesMidiOutput + const int numMidiEventsComingIn = midiBuffer.getNumEvents(); + #endif + + if (pluginInstance->isSuspended()) + { + buffer.clear(); + } + else + { + // processBlockBypassed should only ever be called if the AudioProcessor doesn't + // return a valid parameter from getBypassParameter + if (pluginInstance->getBypassParameter() == nullptr && comPluginInstance->getBypassParameter()->getValue() >= 0.5f) + pluginInstance->processBlockBypassed (buffer, midiBuffer); + else + pluginInstance->processBlock (buffer, midiBuffer); + } + + #if JUCE_DEBUG && (! JucePlugin_ProducesMidiOutput) + /* This assertion is caused when you've added some events to the + midiMessages array in your processBlock() method, which usually means + that you're trying to send them somewhere. But in this case they're + getting thrown away. + + If your plugin does want to send MIDI messages, you'll need to set + the JucePlugin_ProducesMidiOutput macro to 1 in your + JucePluginCharacteristics.h file. + + If you don't want to produce any MIDI output, then you should clear the + midiMessages array at the end of your processBlock() method, to + indicate that you don't want any of the events to be passed through + to the output. + */ + jassert (midiBuffer.getNumEvents() <= numMidiEventsComingIn); + #endif + } + } + + //============================================================================== + Steinberg::uint32 PLUGIN_API getProcessContextRequirements() override + { + return kNeedSystemTime + | kNeedContinousTimeSamples + | kNeedProjectTimeMusic + | kNeedBarPositionMusic + | kNeedCycleMusic + | kNeedSamplesToNextClock + | kNeedTempo + | kNeedTimeSignature + | kNeedChord + | kNeedFrameRate + | kNeedTransportState; + } + + void preparePlugin (double sampleRate, int bufferSize, CallPrepareToPlay callPrepareToPlay) + { + auto& p = getPluginInstance(); + + p.setRateAndBufferSizeDetails (sampleRate, bufferSize); + + if (callPrepareToPlay == CallPrepareToPlay::yes) + p.prepareToPlay (sampleRate, bufferSize); + + midiBuffer.ensureSize (2048); + midiBuffer.clear(); + + bufferMapper.updateFromProcessor (p); + bufferMapper.prepare (bufferSize); + } + + //============================================================================== + #if JucePlugin_Enable_ARA + const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE + { + return createARAFactory(); + } + + const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentController (ARA::ARADocumentControllerRef /*controllerRef*/) SMTG_OVERRIDE + { + ARA_VALIDATE_API_STATE (false && "call is deprecated in ARA 2, host must not call this"); + return nullptr; + } + + const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentControllerWithRoles (ARA::ARADocumentControllerRef documentControllerRef, + ARA::ARAPlugInInstanceRoleFlags knownRoles, ARA::ARAPlugInInstanceRoleFlags assignedRoles) SMTG_OVERRIDE + { + AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (pluginInstance); + return araAudioProcessorExtension->bindToARA (documentControllerRef, knownRoles, assignedRoles); + } + #endif + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + std::atomic refCount { 1 }; + AudioProcessor* pluginInstance = nullptr; + + #if JUCE_LINUX || JUCE_BSD + template + struct LockedVSTComSmartPtr + { + LockedVSTComSmartPtr() = default; + LockedVSTComSmartPtr (const VSTComSmartPtr& ptrIn) : ptr (ptrIn) {} + LockedVSTComSmartPtr (const LockedVSTComSmartPtr&) = default; + LockedVSTComSmartPtr& operator= (const LockedVSTComSmartPtr&) = default; + + ~LockedVSTComSmartPtr() + { + const MessageManagerLock mmLock; + ptr = {}; + } + + T* operator->() const { return ptr.operator->(); } + T* get() const noexcept { return ptr.get(); } + operator T*() const noexcept { return ptr.get(); } + + template + bool loadFrom (Args&&... args) { return ptr.loadFrom (std::forward (args)...); } + + private: + VSTComSmartPtr ptr; + }; + + LockedVSTComSmartPtr host; + LockedVSTComSmartPtr comPluginInstance; + LockedVSTComSmartPtr juceVST3EditController; + #else + VSTComSmartPtr host; + VSTComSmartPtr comPluginInstance; + VSTComSmartPtr juceVST3EditController; + #endif + + /** + Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, + this object needs to be copied on every call to process() to be up-to-date... + */ + Vst::ProcessContext processContext; + Vst::ProcessSetup processSetup; + + MidiBuffer midiBuffer; + ClientBufferMapper bufferMapper; + + bool active = false; + + #if JucePlugin_WantsMidiInput + std::atomic isMidiInputBusEnabled { true }; + #endif + #if JucePlugin_ProducesMidiOutput + std::atomic isMidiOutputBusEnabled { true }; + #endif + + static const char* kJucePrivateDataIdentifier; + CriticalSection flStudioDIYSpecificationEnforcementMutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Component) +}; + +const char* JuceVST3Component::kJucePrivateDataIdentifier = "JUCEPrivateData"; + +//============================================================================== +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4310) +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wall") + +DECLARE_CLASS_IID (JuceAudioProcessor, 0x0101ABAB, 0xABCDEF01, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) +DEF_CLASS_IID (JuceAudioProcessor) + +#if JUCE_VST3_CAN_REPLACE_VST2 + static FUID getFUIDForVST2ID (bool forControllerUID) + { + TUID uuid; + detail::PluginUtilities::getUUIDForVST2ID (forControllerUID, (uint8*) uuid); + return FUID (uuid); + } + const Steinberg::FUID JuceVST3Component ::iid (getFUIDForVST2ID (false)); + const Steinberg::FUID JuceVST3EditController::iid (getFUIDForVST2ID (true)); +#else + DECLARE_CLASS_IID (JuceVST3EditController, 0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) + DEF_CLASS_IID (JuceVST3EditController) + + DECLARE_CLASS_IID (JuceVST3Component, 0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) + DEF_CLASS_IID (JuceVST3Component) +#endif + +#if JucePlugin_Enable_ARA + DECLARE_CLASS_IID (JuceARAFactory, 0xABCDEF01, 0xA1B2C3D4, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) + DEF_CLASS_IID (JuceARAFactory) +#endif + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +//============================================================================== +bool initModule(); +bool initModule() +{ + return true; +} + +bool shutdownModule(); +bool shutdownModule() +{ + return true; +} + +#undef JUCE_EXPORTED_FUNCTION + +#if JUCE_WINDOWS + #define JUCE_EXPORTED_FUNCTION +#else + #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) +#endif + +#if JUCE_WINDOWS + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + + extern "C" __declspec (dllexport) bool InitDll() { return initModule(); } + extern "C" __declspec (dllexport) bool ExitDll() { return shutdownModule(); } + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#elif JUCE_LINUX || JUCE_BSD + void* moduleHandle = nullptr; + int moduleEntryCounter = 0; + + JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle); + JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle) + { + if (++moduleEntryCounter == 1) + { + moduleHandle = sharedLibraryHandle; + return initModule(); + } + + return true; + } + + JUCE_EXPORTED_FUNCTION bool ModuleExit(); + JUCE_EXPORTED_FUNCTION bool ModuleExit() + { + if (--moduleEntryCounter == 0) + { + moduleHandle = nullptr; + return shutdownModule(); + } + + return true; + } +#elif JUCE_MAC + CFBundleRef globalBundleInstance = nullptr; + juce::uint32 numBundleRefs = 0; + juce::Array bundleRefs; + + enum { MaxPathLength = 2048 }; + char modulePath[MaxPathLength] = { 0 }; + void* moduleHandle = nullptr; + + JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref); + JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref) + { + if (ref != nullptr) + { + ++numBundleRefs; + CFRetain (ref); + + bundleRefs.add (ref); + + if (moduleHandle == nullptr) + { + globalBundleInstance = ref; + moduleHandle = ref; + + CFUniquePtr tempURL (CFBundleCopyBundleURL (ref)); + CFURLGetFileSystemRepresentation (tempURL.get(), true, (UInt8*) modulePath, MaxPathLength); + } + } + + return initModule(); + } + + JUCE_EXPORTED_FUNCTION bool bundleExit(); + JUCE_EXPORTED_FUNCTION bool bundleExit() + { + if (shutdownModule()) + { + if (--numBundleRefs == 0) + { + for (int i = 0; i < bundleRefs.size(); ++i) + CFRelease (bundleRefs.getUnchecked (i)); + + bundleRefs.clear(); + } + + return true; + } + + return false; + } +#endif + +//============================================================================== +/** This typedef represents VST3's createInstance() function signature */ +using CreateFunction = FUnknown* (*)(Vst::IHostApplication*); + +static FUnknown* createComponentInstance (Vst::IHostApplication* host) +{ + return static_cast (new JuceVST3Component (host)); +} + +static FUnknown* createControllerInstance (Vst::IHostApplication* host) +{ + return static_cast (new JuceVST3EditController (host)); +} + +#if JucePlugin_Enable_ARA + static FUnknown* createARAFactoryInstance (Vst::IHostApplication* /*host*/) + { + return static_cast (new JuceARAFactory()); + } +#endif + +//============================================================================== +struct JucePluginFactory; +static JucePluginFactory* globalFactory = nullptr; + +//============================================================================== +struct JucePluginFactory : public IPluginFactory3 +{ + JucePluginFactory() + : factoryInfo (JucePlugin_Manufacturer, JucePlugin_ManufacturerWebsite, + JucePlugin_ManufacturerEmail, Vst::kDefaultFactoryFlags) + { + } + + virtual ~JucePluginFactory() + { + if (globalFactory == this) + globalFactory = nullptr; + } + + //============================================================================== + bool registerClass (const PClassInfo2& info, CreateFunction createFunction) + { + if (createFunction == nullptr) + { + jassertfalse; + return false; + } + + auto entry = std::make_unique (info, createFunction); + entry->infoW.fromAscii (info); + + classes.push_back (std::move (entry)); + + return true; + } + + //============================================================================== + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + jassertfalse; // Something new? + *obj = nullptr; + return kNotImplemented; + } + + //============================================================================== + Steinberg::int32 PLUGIN_API countClasses() override + { + return (Steinberg::int32) classes.size(); + } + + tresult PLUGIN_API getFactoryInfo (PFactoryInfo* info) override + { + if (info == nullptr) + return kInvalidArgument; + + memcpy (info, &factoryInfo, sizeof (PFactoryInfo)); + return kResultOk; + } + + tresult PLUGIN_API getClassInfo (Steinberg::int32 index, PClassInfo* info) override + { + return getPClassInfo (index, info); + } + + tresult PLUGIN_API getClassInfo2 (Steinberg::int32 index, PClassInfo2* info) override + { + return getPClassInfo (index, info); + } + + tresult PLUGIN_API getClassInfoUnicode (Steinberg::int32 index, PClassInfoW* info) override + { + if (info != nullptr) + { + if (auto& entry = classes[(size_t) index]) + { + memcpy (info, &entry->infoW, sizeof (PClassInfoW)); + return kResultOk; + } + } + + return kInvalidArgument; + } + + tresult PLUGIN_API createInstance (FIDString cid, FIDString sourceIid, void** obj) override + { + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + *obj = nullptr; + + TUID tuid; + memcpy (tuid, sourceIid, sizeof (TUID)); + + #if VST_VERSION >= 0x030608 + auto sourceFuid = FUID::fromTUID (tuid); + #else + FUID sourceFuid; + sourceFuid = tuid; + #endif + + if (cid == nullptr || sourceIid == nullptr || ! sourceFuid.isValid()) + { + jassertfalse; // The host you're running in has severe implementation issues! + return kInvalidArgument; + } + + TUID iidToQuery; + sourceFuid.toTUID (iidToQuery); + + for (auto& entry : classes) + { + if (doUIDsMatch (entry->infoW.cid, cid)) + { + if (auto* instance = entry->createFunction (host)) + { + const FReleaser releaser (instance); + + if (instance->queryInterface (iidToQuery, obj) == kResultOk) + return kResultOk; + } + + break; + } + } + + return kNoInterface; + } + + tresult PLUGIN_API setHostContext (FUnknown* context) override + { + host.loadFrom (context); + + if (host != nullptr) + { + Vst::String128 name; + host->getName (name); + + return kResultTrue; + } + + return kNotImplemented; + } + +private: + //============================================================================== + std::atomic refCount { 1 }; + const PFactoryInfo factoryInfo; + VSTComSmartPtr host; + + //============================================================================== + struct ClassEntry + { + ClassEntry() noexcept {} + + ClassEntry (const PClassInfo2& info, CreateFunction fn) noexcept + : info2 (info), createFunction (fn) {} + + PClassInfo2 info2; + PClassInfoW infoW; + CreateFunction createFunction = {}; + bool isUnicode = false; + + private: + JUCE_DECLARE_NON_COPYABLE (ClassEntry) + }; + + std::vector> classes; + + //============================================================================== + template + tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) + { + if (info != nullptr) + { + zerostruct (*info); + + if (auto& entry = classes[(size_t) index]) + { + if (entry->isUnicode) + return kResultFalse; + + memcpy (info, (PClassInfoType*) &entry->info2, sizeof (PClassInfoType)); + return kResultOk; + } + } + + jassertfalse; + return kInvalidArgument; + } + + //============================================================================== + // no leak detector here to prevent it firing on shutdown when running in hosts that + // don't release the factory object correctly... + JUCE_DECLARE_NON_COPYABLE (JucePluginFactory) +}; + +} // namespace juce + +//============================================================================== +#ifndef JucePlugin_Vst3ComponentFlags + #if JucePlugin_IsSynth + #define JucePlugin_Vst3ComponentFlags Vst::kSimpleModeSupported + #else + #define JucePlugin_Vst3ComponentFlags 0 + #endif +#endif + +#ifndef JucePlugin_Vst3Category + #if JucePlugin_IsSynth + #define JucePlugin_Vst3Category Vst::PlugType::kInstrumentSynth + #else + #define JucePlugin_Vst3Category Vst::PlugType::kFx + #endif +#endif + +using namespace juce; + +//============================================================================== +// The VST3 plugin entry point. +extern "C" SMTG_EXPORT_SYMBOL IPluginFactory* PLUGIN_API GetPluginFactory() +{ + #if (JUCE_MSVC || (JUCE_WINDOWS && JUCE_CLANG)) && JUCE_32BIT + // Cunning trick to force this function to be exported. Life's too short to + // faff around creating .def files for this kind of thing. + // Unnecessary for 64-bit builds because those don't use decorated function names. + #pragma comment(linker, "/EXPORT:GetPluginFactory=_GetPluginFactory@0") + #endif + + if (globalFactory == nullptr) + { + globalFactory = new JucePluginFactory(); + + static const PClassInfo2 componentClass (JuceVST3Component::iid, + PClassInfo::kManyInstances, + kVstAudioEffectClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString); + + globalFactory->registerClass (componentClass, createComponentInstance); + + static const PClassInfo2 controllerClass (JuceVST3EditController::iid, + PClassInfo::kManyInstances, + kVstComponentControllerClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString); + + globalFactory->registerClass (controllerClass, createControllerInstance); + + #if JucePlugin_Enable_ARA + static const PClassInfo2 araFactoryClass (JuceARAFactory::iid, + PClassInfo::kManyInstances, + kARAMainFactoryClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString); + + globalFactory->registerClass (araFactoryClass, createARAFactoryInstance); + #endif + } + else + { + globalFactory->addRef(); + } + + return dynamic_cast (globalFactory); +} + +//============================================================================== +#if JUCE_WINDOWS +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +JUCE_END_NO_SANITIZE + +#endif //JucePlugin_Build_VST3 diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST_utils.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm similarity index 86% rename from modules/juce_audio_plugin_client/juce_audio_plugin_client_VST_utils.mm rename to modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm index 682e9eb4a5..54b145434e 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST_utils.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm @@ -23,6 +23,4 @@ ============================================================================== */ -#if JucePlugin_Build_VST || JucePlugin_Build_VST3 - #include "detail/juce_VSTWindowUtilities.mm" -#endif +#include "juce_audio_plugin_client_VST3.cpp" diff --git a/modules/juce_events/native/juce_win32_Messaging.cpp b/modules/juce_events/native/juce_win32_Messaging.cpp index 9ac232394e..b918b5610f 100644 --- a/modules/juce_events/native/juce_win32_Messaging.cpp +++ b/modules/juce_events/native/juce_win32_Messaging.cpp @@ -27,7 +27,7 @@ extern HWND juce_messageWindowHandle; namespace detail { - #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client bool isRunningInUnity(); #else constexpr bool isRunningInUnity() { return false; } diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index d9d844c978..cbb71b7bda 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -50,7 +50,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type") #endif void juce_repeatLastProcessPriority(); -bool juce_isRunningInWine(); using CheckEventBlockedByModalComps = bool (*) (const MSG&); extern CheckEventBlockedByModalComps isEventBlockedByModalComps;