From edd677706b9817159cfd4abb02c5c8f518e91709 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 16 Jan 2019 13:19:51 +0100 Subject: [PATCH] Revert "Delete juce_audio_processors" This reverts commit b1a15e0cd69c602ba20eff060e290f571e4f0e89. Signed-off-by: falkTX --- source/modules/juce_audio_processors/Makefile | 135 + .../format/juce_AudioPluginFormat.cpp | 214 ++ .../format/juce_AudioPluginFormat.h | 168 + .../format/juce_AudioPluginFormatManager.cpp | 183 + .../format/juce_AudioPluginFormatManager.h | 138 + .../format_types/juce_AU_Shared.h | 535 +++ .../format_types/juce_AudioUnitPluginFormat.h | 79 + .../juce_AudioUnitPluginFormat.mm | 2463 ++++++++++++++ .../format_types/juce_LADSPAPluginFormat.cpp | 718 ++++ .../format_types/juce_LADSPAPluginFormat.h | 70 + .../format_types/juce_VST3Common.h | 633 ++++ .../format_types/juce_VST3Headers.h | 186 + .../format_types/juce_VST3PluginFormat.cpp | 2983 +++++++++++++++++ .../format_types/juce_VST3PluginFormat.h | 71 + .../format_types/juce_VSTCommon.h | 298 ++ .../format_types/juce_VSTInterface.h | 485 +++ .../format_types/juce_VSTMidiEventList.h | 191 ++ .../format_types/juce_VSTPluginFormat.cpp | 2972 ++++++++++++++++ .../format_types/juce_VSTPluginFormat.h | 132 + .../juce_audio_processors.cpp | 185 + .../juce_audio_processors.h | 124 + .../processors/juce_AudioPluginInstance.h | 89 + .../processors/juce_AudioProcessor.cpp | 1464 ++++++++ .../processors/juce_AudioProcessor.h | 1641 +++++++++ .../processors/juce_AudioProcessorEditor.cpp | 189 ++ .../processors/juce_AudioProcessorEditor.h | 209 ++ .../processors/juce_AudioProcessorGraph.cpp | 1702 ++++++++++ .../processors/juce_AudioProcessorGraph.h | 411 +++ .../processors/juce_AudioProcessorListener.h | 107 + .../processors/juce_AudioProcessorParameter.h | 197 ++ .../juce_GenericAudioProcessorEditor.cpp | 192 ++ .../juce_GenericAudioProcessorEditor.h | 58 + .../processors/juce_PluginDescription.cpp | 151 + .../processors/juce_PluginDescription.h | 156 + .../scanning/juce_KnownPluginList.cpp | 587 ++++ .../scanning/juce_KnownPluginList.h | 225 ++ .../scanning/juce_PluginDirectoryScanner.cpp | 136 + .../scanning/juce_PluginDirectoryScanner.h | 131 + .../scanning/juce_PluginListComponent.cpp | 593 ++++ .../scanning/juce_PluginListComponent.h | 132 + .../utilities/juce_AudioParameterBool.h | 71 + .../utilities/juce_AudioParameterChoice.h | 86 + .../utilities/juce_AudioParameterFloat.h | 87 + .../utilities/juce_AudioParameterInt.h | 84 + .../juce_AudioProcessorParameterWithID.h | 69 + .../juce_AudioProcessorParameters.cpp | 175 + .../juce_AudioProcessorValueTreeState.cpp | 576 ++++ .../juce_AudioProcessorValueTreeState.h | 238 ++ 48 files changed, 22719 insertions(+) create mode 100644 source/modules/juce_audio_processors/Makefile create mode 100644 source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp create mode 100644 source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h create mode 100644 source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp create mode 100644 source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_AU_Shared.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm create mode 100644 source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp create mode 100644 source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VST3Common.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VST3Headers.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp create mode 100644 source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTCommon.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTInterface.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h create mode 100644 source/modules/juce_audio_processors/juce_audio_processors.cpp create mode 100644 source/modules/juce_audio_processors/juce_audio_processors.h create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessor.h create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h create mode 100644 source/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h create mode 100644 source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp create mode 100644 source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.h create mode 100644 source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp create mode 100644 source/modules/juce_audio_processors/processors/juce_PluginDescription.h create mode 100644 source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp create mode 100644 source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h create mode 100644 source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp create mode 100644 source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h create mode 100644 source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp create mode 100644 source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp create mode 100644 source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h diff --git a/source/modules/juce_audio_processors/Makefile b/source/modules/juce_audio_processors/Makefile new file mode 100644 index 000000000..c246b23ec --- /dev/null +++ b/source/modules/juce_audio_processors/Makefile @@ -0,0 +1,135 @@ +#!/usr/bin/make -f +# Makefile for juce_audio_processors # +# ---------------------------------- # +# Created by falkTX +# + +CWD=../.. +MODULENAME=juce_audio_processors +include ../Makefile.mk + +# ---------------------------------------------------------------------------------------------------------------------------- + +BUILD_CXX_FLAGS += $(JUCE_AUDIO_PROCESSORS_FLAGS) -I$(CWD)/includes/ladspa -I$(CWD)/includes/vst2 -I$(CWD)/includes/vst3 -I.. + +ifeq ($(CARLA_VESTIGE_HEADER),true) +BUILD_CXX_FLAGS += -DVESTIGE_HEADER +else +# needed by vst3 +BUILD_CXX_FLAGS += -w +ifeq ($(DEBUG),true) +BUILD_CXX_FLAGS += -DDEVELOPMENT -D_DEBUG +else +BUILD_CXX_FLAGS += -DRELEASE +endif +# needed by vst3 on mingw +ifeq ($(WIN32),true) +BUILD_CXX_FLAGS += -D_NATIVE_WCHAR_T_DEFINED -D__wchar_t=wchar_t -fpermissive +endif +endif + +# ---------------------------------------------------------------------------------------------------------------------------- + +ifeq ($(MACOS),true) +OBJS = $(OBJDIR)/$(MODULENAME).mm.o +OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o +OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o +else +OBJS = $(OBJDIR)/$(MODULENAME).cpp.o +OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o +OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o +endif +OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o +OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o + +# ---------------------------------------------------------------------------------------------------------------------------- + +all: $(MODULEDIR)/$(MODULENAME).a +posix32: $(MODULEDIR)/$(MODULENAME).posix32.a +posix64: $(MODULEDIR)/$(MODULENAME).posix64.a +win32: $(MODULEDIR)/$(MODULENAME).win32.a +win64: $(MODULEDIR)/$(MODULENAME).win64.a + +# ---------------------------------------------------------------------------------------------------------------------------- + +clean: + rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a + +debug: + $(MAKE) DEBUG=true + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(MODULEDIR)/$(MODULENAME).a: $(OBJS) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).posix32.a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).posix64.a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).win32.a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).win64.a" + @rm -f $@ + @$(AR) crs $@ $^ + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ + +$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (32bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ + +$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (64bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ + +$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (32bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ + +$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (64bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ + +# ---------------------------------------------------------------------------------------------------------------------------- + +-include $(OBJS:%.o=%.d) +-include $(OBJS_posix32:%.o=%.d) +-include $(OBJS_posix64:%.o=%.d) +-include $(OBJS_win32:%.o=%.d) +-include $(OBJS_win64:%.o=%.d) + +# ---------------------------------------------------------------------------------------------------------------------------- diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp new file mode 100644 index 000000000..8b33d4d04 --- /dev/null +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp @@ -0,0 +1,214 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +namespace AudioPluginFormatHelpers +{ + struct CallbackInvoker + { + struct InvokeOnMessageThread : public CallbackMessage + { + InvokeOnMessageThread (AudioPluginInstance* inInstance, const String& inError, + AudioPluginFormat::InstantiationCompletionCallback* inCompletion, + CallbackInvoker* invoker) + : instance (inInstance), error (inError), compCallback (inCompletion), owner (invoker) + {} + + void messageCallback() override { compCallback->completionCallback (instance, error); } + + //============================================================================== + AudioPluginInstance* instance; + String error; + ScopedPointer compCallback; + ScopedPointer owner; + }; + + //============================================================================== + CallbackInvoker (AudioPluginFormat::InstantiationCompletionCallback* cc) : completion (cc) + {} + + void completionCallback (AudioPluginInstance* instance, const String& error) + { + (new InvokeOnMessageThread (instance, error, completion, this))->post(); + } + + static void staticCompletionCallback (void* userData, AudioPluginInstance* instance, const String& error) + { + reinterpret_cast (userData)->completionCallback (instance, error); + } + + //============================================================================== + AudioPluginFormat::InstantiationCompletionCallback* completion; + }; +} + +AudioPluginFormat::AudioPluginFormat() noexcept {} +AudioPluginFormat::~AudioPluginFormat() {} + +AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, + double initialSampleRate, + int initialBufferSize) +{ + String errorMessage; + return createInstanceFromDescription (desc, initialSampleRate, initialBufferSize, errorMessage); +} + +//============================================================================== +struct EventSignaler : public AudioPluginFormat::InstantiationCompletionCallback +{ + EventSignaler (WaitableEvent& inEvent, AudioPluginInstance*& inInstance, String& inErrorMessage) + : event (inEvent), outInstance (inInstance), outErrorMessage (inErrorMessage) + {} + + void completionCallback (AudioPluginInstance* newInstance, const String& result) override + { + outInstance = newInstance; + outErrorMessage = result; + event.signal(); + } + + static void staticCompletionCallback (void* userData, AudioPluginInstance* pluginInstance, const String& error) + { + reinterpret_cast (userData)->completionCallback (pluginInstance, error); + } + + WaitableEvent& event; + AudioPluginInstance*& outInstance; + String& outErrorMessage; + + JUCE_DECLARE_NON_COPYABLE (EventSignaler) +}; + +AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, + double initialSampleRate, + int initialBufferSize, + String& errorMessage) +{ + if (MessageManager::getInstance()->isThisTheMessageThread() + && requiresUnblockedMessageThreadDuringCreation(desc)) + { + errorMessage = NEEDS_TRANS ("This plug-in cannot be instantiated synchronously"); + return nullptr; + } + + WaitableEvent waitForCreation; + AudioPluginInstance* instance = nullptr; + + ScopedPointer eventSignaler (new EventSignaler (waitForCreation, instance, errorMessage)); + + if (! MessageManager::getInstance()->isThisTheMessageThread()) + createPluginInstanceAsync (desc, initialSampleRate, initialBufferSize, eventSignaler.release()); + else + createPluginInstance (desc, initialSampleRate, initialBufferSize, + eventSignaler, EventSignaler::staticCompletionCallback); + + + waitForCreation.wait(); + + return instance; +} + +void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback) +{ + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + createPluginInstanceOnMessageThread (description, initialSampleRate, initialBufferSize, callback); + return; + } + + //============================================================================== + struct InvokeOnMessageThread : public CallbackMessage + { + InvokeOnMessageThread (AudioPluginFormat* myself, + const PluginDescription& descriptionParam, + double initialSampleRateParam, + int initialBufferSizeParam, + AudioPluginFormat::InstantiationCompletionCallback* callbackParam) + : owner (myself), descr (descriptionParam), sampleRate (initialSampleRateParam), + bufferSize (initialBufferSizeParam), call (callbackParam) + {} + + void messageCallback() override + { + owner->createPluginInstanceOnMessageThread (descr, sampleRate, bufferSize, call); + } + + AudioPluginFormat* owner; + PluginDescription descr; + double sampleRate; + int bufferSize; + AudioPluginFormat::InstantiationCompletionCallback* call; + }; + + (new InvokeOnMessageThread (this, description, initialSampleRate, initialBufferSize, callback))->post(); +} + +void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function f) +{ + struct CallbackInvoker : public AudioPluginFormat::InstantiationCompletionCallback + { + CallbackInvoker (std::function inCompletion) + : completion (inCompletion) + {} + + void completionCallback (AudioPluginInstance* instance, const String& error) override + { + completion (instance, error); + } + + std::function completion; + }; + + createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, new CallbackInvoker (f)); +} + +void AudioPluginFormat::createPluginInstanceOnMessageThread (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback) +{ + jassert (callback != nullptr); + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + //============================================================================== + + + //============================================================================== + AudioPluginFormatHelpers::CallbackInvoker* completion = new AudioPluginFormatHelpers::CallbackInvoker (callback); + + createPluginInstance (description, initialSampleRate, initialBufferSize, completion, + AudioPluginFormatHelpers::CallbackInvoker::staticCompletionCallback); +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h new file mode 100644 index 000000000..6eb08eb32 --- /dev/null +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h @@ -0,0 +1,168 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. + + @see AudioPluginFormatManager +*/ +class JUCE_API AudioPluginFormat +{ +public: + //============================================================================== + struct JUCE_API InstantiationCompletionCallback + { + virtual ~InstantiationCompletionCallback() {} + virtual void completionCallback (AudioPluginInstance* instance, const String& error) = 0; + + JUCE_LEAK_DETECTOR (InstantiationCompletionCallback) + }; + + //============================================================================== + /** Destructor. */ + virtual ~AudioPluginFormat(); + + //============================================================================== + /** Returns the format name. + E.g. "VST", "AudioUnit", etc. + */ + virtual String getName() const = 0; + + /** This tries to create descriptions for all the plugin types available in + a binary module file. + + The file will be some kind of DLL or bundle. + + Normally there will only be one type returned, but some plugins + (e.g. VST shells) can use a single DLL to create a set of different plugin + subtypes, so in that case, each subtype is returned as a separate object. + */ + virtual void findAllTypesForFile (OwnedArray& results, + const String& fileOrIdentifier) = 0; + + /** Tries to recreate a type from a previously generated PluginDescription. + @see AudioPluginFormatManager::createInstance + */ + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, + double initialSampleRate, + int initialBufferSize); + + /** Same as above but with the possibility of returning an error message. + + @see AudioPluginFormatManager::createInstance + */ + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, + double initialSampleRate, + int initialBufferSize, + String& errorMessage); + + /** Tries to recreate a type from a previously generated PluginDescription. + + @see AudioPluginFormatManager::createInstanceAsync + */ + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + InstantiationCompletionCallback* completionCallback); + + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function completionCallback); + + /** Should do a quick check to see if this file or directory might be a plugin of + this format. + + This is for searching for potential files, so it shouldn't actually try to + load the plugin or do anything time-consuming. + */ + virtual bool fileMightContainThisPluginType (const String& fileOrIdentifier) = 0; + + /** Returns a readable version of the name of the plugin that this identifier refers to. */ + virtual String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) = 0; + + /** Returns true if this plugin's version or date has changed and it should be re-checked. */ + virtual bool pluginNeedsRescanning (const PluginDescription&) = 0; + + /** Checks whether this plugin could possibly be loaded. + It doesn't actually need to load it, just to check whether the file or component + still exists. + */ + virtual bool doesPluginStillExist (const PluginDescription&) = 0; + + /** Returns true if this format needs to run a scan to find its list of plugins. */ + virtual bool canScanForPlugins() const = 0; + + /** Searches a suggested set of directories for any plugins in this format. + The path might be ignored, e.g. by AUs, which are found by the OS rather + than manually. + + @param directoriesToSearch This specifies which directories shall be + searched for plug-ins. + @param recursive Should the search recursively traverse folders. + @param allowPluginsWhichRequireAsynchronousInstantiation + If this is false then plug-ins which require + asynchronous creation will be excluded. + */ + virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, + bool recursive, + bool allowPluginsWhichRequireAsynchronousInstantiation = false) = 0; + + /** Returns the typical places to look for this kind of plugin. + + Note that if this returns no paths, it means that the format doesn't search in + files or folders, e.g. AudioUnits. + */ + virtual FileSearchPath getDefaultLocationsToSearch() = 0; + +protected: + //============================================================================== + friend class AudioPluginFormatManager; + + AudioPluginFormat() noexcept; + + /** Implementors must override this function. This is guaranteed to be called on + the message thread. You may call the callback on any thread. + */ + virtual void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) = 0; + + virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept = 0; + +private: + /** @internal */ + void createPluginInstanceOnMessageThread (const PluginDescription&, double rate, int size, + AudioPluginFormat::InstantiationCompletionCallback*); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp new file mode 100644 index 000000000..ceedf1705 --- /dev/null +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp @@ -0,0 +1,183 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +namespace PluginFormatManagerHelpers +{ + struct ErrorCallbackOnMessageThread : public CallbackMessage + { + ErrorCallbackOnMessageThread (const String& inError, + AudioPluginFormat::InstantiationCompletionCallback* inCallback) + : error (inError), callback (inCallback) + { + } + + void messageCallback() override { callback->completionCallback (nullptr, error); } + + String error; + ScopedPointer callback; + }; + + struct ErrorLambdaOnMessageThread : public CallbackMessage + { + ErrorLambdaOnMessageThread (const String& inError, + std::function f) + : error (inError), lambda (f) + { + } + + void messageCallback() override { lambda (nullptr, error); } + + String error; + std::function lambda; + }; +} + +AudioPluginFormatManager::AudioPluginFormatManager() {} +AudioPluginFormatManager::~AudioPluginFormatManager() {} + +//============================================================================== +void AudioPluginFormatManager::addDefaultFormats() +{ + #if JUCE_DEBUG + // you should only call this method once! + for (int i = formats.size(); --i >= 0;) + { + #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) + jassert (dynamic_cast (formats[i]) == nullptr); + #endif + + #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) + jassert (dynamic_cast (formats[i]) == nullptr); + #endif + + #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) + jassert (dynamic_cast (formats[i]) == nullptr); + #endif + + #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX + jassert (dynamic_cast (formats[i]) == nullptr); + #endif + } + #endif + + #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) + formats.add (new AudioUnitPluginFormat()); + #endif + + #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) + formats.add (new VSTPluginFormat()); + #endif + + #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) + formats.add (new VST3PluginFormat()); + #endif + + #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX + formats.add (new LADSPAPluginFormat()); + #endif +} + +int AudioPluginFormatManager::getNumFormats() +{ + return formats.size(); +} + +AudioPluginFormat* AudioPluginFormatManager::getFormat (const int index) +{ + return formats [index]; +} + +void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) +{ + formats.add (format); +} + +AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, + int blockSize, String& errorMessage) const +{ + if (AudioPluginFormat* format = findFormatForDescription (description, errorMessage)) + return format->createInstanceFromDescription (description, rate, blockSize, errorMessage); + + return nullptr; +} + +void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback) +{ + String error; + + if (AudioPluginFormat* format = findFormatForDescription (description, error)) + return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, callback); + + (new PluginFormatManagerHelpers::ErrorCallbackOnMessageThread (error, callback))->post(); +} + +void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function f) +{ + String error; + + if (AudioPluginFormat* format = findFormatForDescription (description, error)) + return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, f); + + (new PluginFormatManagerHelpers::ErrorLambdaOnMessageThread (error, f))->post(); +} + +AudioPluginFormat* AudioPluginFormatManager::findFormatForDescription (const PluginDescription& description, String& errorMessage) const +{ + errorMessage = String(); + + for (int i = 0; i < formats.size(); ++i) + { + AudioPluginFormat* format; + + if ((format = formats.getUnchecked (i))->getName() == description.pluginFormatName + && format->fileMightContainThisPluginType (description.fileOrIdentifier)) + return format; + } + + errorMessage = NEEDS_TRANS ("No compatible plug-in format exists for this plug-in"); + + return nullptr; +} + +bool AudioPluginFormatManager::doesPluginStillExist (const PluginDescription& description) const +{ + for (int i = 0; i < formats.size(); ++i) + if (formats.getUnchecked(i)->getName() == description.pluginFormatName) + return formats.getUnchecked(i)->doesPluginStillExist (description); + + return false; +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h new file mode 100644 index 000000000..495c22291 --- /dev/null +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h @@ -0,0 +1,138 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This maintains a list of known AudioPluginFormats. + + @see AudioPluginFormat +*/ +class JUCE_API AudioPluginFormatManager +{ +public: + //============================================================================== + AudioPluginFormatManager(); + + /** Destructor. */ + ~AudioPluginFormatManager(); + + //============================================================================== + /** Adds any formats that it knows about, e.g. VST. + */ + void addDefaultFormats(); + + //============================================================================== + /** Returns the number of types of format that are available. + + Use getFormat() to get one of them. + */ + int getNumFormats(); + + /** Returns one of the available formats. + + @see getNumFormats + */ + AudioPluginFormat* getFormat (int index); + + //============================================================================== + /** Adds a format to the list. + + The object passed in will be owned and deleted by the manager. + */ + void addFormat (AudioPluginFormat* format); + + + //============================================================================== + /** Tries to load the type for this description, by trying all the formats + that this manager knows about. + + The caller is responsible for deleting the object that is returned. + + If it can't load the plugin, it returns nullptr and leaves a message in the + errorMessage string. + + If you intend to instantiate a AudioUnit v3 plug-in then you must either + use the non-blocking asynchrous version below - or call this method from a + thread other than the message thread and without blocking the message + thread. + */ + AudioPluginInstance* createPluginInstance (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + String& errorMessage) const; + + /** Tries to asynchronously load the type for this description, by trying + all the formats that this manager knows about. + + The caller must supply a callback object which will be called when + the instantantiation has completed. + + If it can't load the plugin then the callback function will be called + passing a nullptr as the instance argument along with an error message. + + The callback function will be called on the message thread so the caller + must not block the message thread. + + The callback object will be deleted automatically after it has been + invoked. + + The caller is responsible for deleting the instance that is passed to + the callback function. + + If you intend to instantiate a AudioUnit v3 plug-in then you must use + this non-blocking asynchrous version - or call the synchrous method + from an auxiliary thread. + */ + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback); + + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function completionCallback); + + /** Checks that the file or component for this plugin actually still exists. + + (This won't try to load the plugin) + */ + bool doesPluginStillExist (const PluginDescription& description) const; + +private: + //============================================================================== + //@internal + AudioPluginFormat* findFormatForDescription (const PluginDescription&, String& errorMessage) const; + + OwnedArray formats; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h b/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h new file mode 100644 index 000000000..434326ff2 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -0,0 +1,535 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +// This macro can be set if you need to override this internal name for some reason.. +#ifndef JUCE_STATE_DICTIONARY_KEY + #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" +#endif + +namespace juce +{ + +struct AudioUnitHelpers +{ + class ChannelRemapper + { + public: + ChannelRemapper (AudioProcessor& p) : processor (p), inputLayoutMap (nullptr), outputLayoutMap (nullptr) {} + ~ChannelRemapper() {} + + void alloc() + { + const int numInputBuses = AudioUnitHelpers::getBusCount (&processor, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (&processor, false); + + initializeChannelMapArray (true, numInputBuses); + initializeChannelMapArray (false, numOutputBuses); + + for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) + fillLayoutChannelMaps (true, busIdx); + + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + fillLayoutChannelMaps (false, busIdx); + } + + void release() + { + inputLayoutMap = outputLayoutMap = nullptr; + inputLayoutMapPtrStorage.free(); + outputLayoutMapPtrStorage.free(); + inputLayoutMapStorage.free(); + outputLayoutMapStorage.free(); + } + + inline const int* get (bool input, int bus) const noexcept { return (input ? inputLayoutMap : outputLayoutMap) [bus]; } + + private: + //============================================================================== + AudioProcessor& processor; + HeapBlock inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; + HeapBlock inputLayoutMapStorage, outputLayoutMapStorage; + int** inputLayoutMap; + int** outputLayoutMap; + + //============================================================================== + void initializeChannelMapArray (bool isInput, const int numBuses) + { + HeapBlock& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; + HeapBlock& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; + int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; + + const int totalInChannels = processor.getTotalNumInputChannels(); + const int totalOutChannels = processor.getTotalNumOutputChannels(); + + layoutMapPtrStorage.calloc (static_cast (numBuses)); + layoutMapStorage.calloc (static_cast (isInput ? totalInChannels : totalOutChannels)); + + layoutMap = layoutMapPtrStorage. get(); + + int ch = 0; + for (int busIdx = 0; busIdx < numBuses; ++busIdx) + { + layoutMap[busIdx] = layoutMapStorage.get() + ch; + ch += processor.getChannelCountOfBus (isInput, busIdx); + } + } + + void fillLayoutChannelMaps (bool isInput, int busNr) + { + int* layoutMap = (isInput ? inputLayoutMap : outputLayoutMap)[busNr]; + auto channelFormat = processor.getChannelLayoutOfBus (isInput, busNr); + AudioChannelLayout coreAudioLayout; + + zerostruct (coreAudioLayout); + coreAudioLayout.mChannelLayoutTag = CoreAudioLayouts::toCoreAudio (channelFormat); + + const int numChannels = channelFormat.size(); + auto coreAudioChannels = CoreAudioLayouts::getCoreAudioLayoutChannels (coreAudioLayout); + + for (int i = 0; i < numChannels; ++i) + layoutMap[i] = coreAudioChannels.indexOf (channelFormat.getTypeOfChannel (i)); + } + }; + + //============================================================================== + class CoreAudioBufferList + { + public: + CoreAudioBufferList() { reset(); } + + //============================================================================== + void prepare (int inChannels, int outChannels, int maxFrames) + { + const int numChannels = jmax (inChannels, outChannels); + + scratch.setSize (numChannels, maxFrames); + channels.calloc (static_cast (numChannels)); + + reset(); + } + + void release() + { + scratch.setSize (0, 0); + channels.free(); + } + + void reset() noexcept + { + pushIdx = 0; + popIdx = 0; + zeromem (channels.get(), sizeof(float*) * static_cast (scratch.getNumChannels())); + } + + //============================================================================== + float* setBuffer (const int idx, float* ptr = nullptr) noexcept + { + jassert (idx < scratch.getNumChannels()); + return (channels [idx] = uniqueBuffer (idx, ptr)); + } + + //============================================================================== + float* push() noexcept + { + jassert (pushIdx < scratch.getNumChannels()); + return channels [pushIdx++]; + } + + void push (AudioBufferList& bufferList, const int* channelMap) noexcept + { + jassert (pushIdx < scratch.getNumChannels()); + + if (bufferList.mNumberBuffers > 0) + { + const UInt32 n = bufferList.mBuffers [0].mDataByteSize / + (bufferList.mBuffers [0].mNumberChannels * sizeof (float)); + const bool isInterleaved = isAudioBufferInterleaved (bufferList); + const int numChannels = static_cast (isInterleaved ? bufferList.mBuffers [0].mNumberChannels + : bufferList.mNumberBuffers); + + for (int ch = 0; ch < numChannels; ++ch) + { + float* data = push(); + + int mappedChannel = channelMap [ch]; + if (isInterleaved || static_cast (bufferList.mBuffers [mappedChannel].mData) != data) + copyAudioBuffer (bufferList, mappedChannel, n, data); + } + } + } + + //============================================================================== + float* pop() noexcept + { + jassert (popIdx < scratch.getNumChannels()); + return channels[popIdx++]; + } + + void pop (AudioBufferList& buffer, const int* channelMap) noexcept + { + if (buffer.mNumberBuffers > 0) + { + const UInt32 n = buffer.mBuffers [0].mDataByteSize / (buffer.mBuffers [0].mNumberChannels * sizeof (float)); + const bool isInterleaved = isAudioBufferInterleaved (buffer); + const int numChannels = static_cast (isInterleaved ? buffer.mBuffers [0].mNumberChannels : buffer.mNumberBuffers); + + for (int ch = 0; ch < numChannels; ++ch) + { + int mappedChannel = channelMap [ch]; + float* nextBuffer = pop(); + + if (nextBuffer == buffer.mBuffers [mappedChannel].mData && ! isInterleaved) + continue; // no copying necessary + + if (buffer.mBuffers [mappedChannel].mData == nullptr && ! isInterleaved) + buffer.mBuffers [mappedChannel].mData = nextBuffer; + else + copyAudioBuffer (nextBuffer, mappedChannel, n, buffer); + } + } + } + + //============================================================================== + AudioSampleBuffer& getBuffer (UInt32 frames) noexcept + { + jassert (pushIdx == scratch.getNumChannels()); + + #if JUCE_DEBUG + for (int i = 0; i < pushIdx; ++i) + jassert (channels [i] != nullptr); + #endif + + mutableBuffer.setDataToReferTo (channels, pushIdx, static_cast (frames)); + return mutableBuffer; + } + + private: + float* uniqueBuffer (int idx, float* buffer) noexcept + { + if (buffer == nullptr) + return scratch.getWritePointer (idx); + + for (int ch = 0; ch < idx; ++ch) + if (buffer == channels[ch]) + return scratch.getWritePointer (idx); + + return buffer; + } + + //============================================================================== + AudioSampleBuffer scratch; + AudioSampleBuffer mutableBuffer; + + HeapBlock channels; + int pushIdx, popIdx; + }; + + static bool isAudioBufferInterleaved (const AudioBufferList& audioBuffer) noexcept + { + return (audioBuffer.mNumberBuffers == 1 && audioBuffer.mBuffers[0].mNumberChannels > 1); + } + + static void clearAudioBuffer (const AudioBufferList& audioBuffer) noexcept + { + for (unsigned int ch = 0; ch < audioBuffer.mNumberBuffers; ++ch) + zeromem (audioBuffer.mBuffers[ch].mData, audioBuffer.mBuffers[ch].mDataByteSize); + } + + static void copyAudioBuffer (const AudioBufferList& audioBuffer, const int channel, const UInt32 size, float* dst) noexcept + { + if (! isAudioBufferInterleaved (audioBuffer)) + { + jassert (channel < static_cast (audioBuffer.mNumberBuffers)); + jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); + + memcpy (dst, audioBuffer.mBuffers[channel].mData, size * sizeof (float)); + } + else + { + const int numChannels = static_cast (audioBuffer.mBuffers[0].mNumberChannels); + const UInt32 n = static_cast (numChannels) * size; + const float* src = static_cast (audioBuffer.mBuffers[0].mData); + + jassert (channel < numChannels); + jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); + + for (const float* inData = src; inData < (src + n); inData += numChannels) + *dst++ = inData[channel]; + } + } + + static void copyAudioBuffer (const float *src, const int channel, const UInt32 size, AudioBufferList& audioBuffer) noexcept + { + if (! isAudioBufferInterleaved (audioBuffer)) + { + jassert (channel < static_cast (audioBuffer.mNumberBuffers)); + jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); + + memcpy (audioBuffer.mBuffers[channel].mData, src, size * sizeof (float)); + } + else + { + const int numChannels = static_cast (audioBuffer.mBuffers[0].mNumberChannels); + const UInt32 n = static_cast (numChannels) * size; + float* dst = static_cast (audioBuffer.mBuffers[0].mData); + + jassert (channel < numChannels); + jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); + + for (float* outData = dst; outData < (dst + n); outData += numChannels) + outData[channel] = *src++; + } + } + + template + static bool isLayoutSupported (const AudioProcessor& processor, + bool isInput, int busIdx, + int numChannels, + const short (&channelLayoutList) [numLayouts][2], + bool hasLayoutMap = true) + { + if (const AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) + { + if (! bus->isNumberOfChannelsSupported (numChannels)) + return false; + + if (! hasLayoutMap) + return true; + + const int numConfigs = sizeof (channelLayoutList) / sizeof (short[2]); + + for (int i = 0; i < numConfigs; ++i) + { + if (channelLayoutList[i][isInput ? 0 : 1] == numChannels) + return true; + } + } + + return false; + } + + static Array getAUChannelInfo (const AudioProcessor& processor) + { + Array channelInfo; + + auto hasMainInputBus = (AudioUnitHelpers::getBusCount (&processor, true) > 0); + auto hasMainOutputBus = (AudioUnitHelpers::getBusCount (&processor, false) > 0); + + if ((! hasMainInputBus) && (! hasMainOutputBus)) + { + // midi effect plug-in: no audio + AUChannelInfo info; + info.inChannels = 0; + info.outChannels = 0; + + return { &info, 1 }; + } + + auto layout = processor.getBusesLayout(); + auto maxNumChanToCheckFor = 9; + + auto defaultInputs = processor.getChannelCountOfBus (true, 0); + auto defaultOutputs = processor.getChannelCountOfBus (false, 0); + + SortedSet supportedChannels; + + // add the current configuration + if (defaultInputs != 0 || defaultOutputs != 0) + supportedChannels.add ((defaultInputs << 16) | defaultOutputs); + + for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) + { + auto inLayout = layout; + + if (auto* inBus = processor.getBus (true, 0)) + if (! isNumberOfChannelsSupported (inBus, inChanNum, inLayout)) + continue; + + for (auto outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) + { + auto outLayout = inLayout; + + if (auto* outBus = processor.getBus (false, 0)) + if (! isNumberOfChannelsSupported (outBus, outChanNum, outLayout)) + continue; + + supportedChannels.add (((hasMainInputBus ? outLayout.getMainInputChannels() : 0) << 16) + | (hasMainOutputBus ? outLayout.getMainOutputChannels() : 0)); + } + } + + auto hasInOutMismatch = false; + + for (auto supported : supportedChannels) + { + auto numInputs = (supported >> 16) & 0xffff; + auto numOutputs = (supported >> 0) & 0xffff; + + if (numInputs != numOutputs) + { + hasInOutMismatch = true; + break; + } + } + + auto hasUnsupportedInput = ! hasMainInputBus, hasUnsupportedOutput = ! hasMainOutputBus; + + for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) + { + auto channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); + + if (! supportedChannels.contains (channelConfiguration)) + { + hasUnsupportedInput = true; + break; + } + } + + for (auto outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) + { + auto channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; + + if (! supportedChannels.contains (channelConfiguration)) + { + hasUnsupportedOutput = true; + break; + } + } + + for (auto supported : supportedChannels) + { + auto numInputs = (supported >> 16) & 0xffff; + auto numOutputs = (supported >> 0) & 0xffff; + + AUChannelInfo info; + + // see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html + info.inChannels = static_cast (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); + info.outChannels = static_cast (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); + + if (info.inChannels == -2 && info.outChannels == -2) + info.inChannels = -1; + + int j; + for (j = 0; j < channelInfo.size(); ++j) + if (info.inChannels == channelInfo.getReference (j).inChannels + && info.outChannels == channelInfo.getReference (j).outChannels) + break; + + if (j >= channelInfo.size()) + channelInfo.add (info); + } + + return channelInfo; + } + + static bool isNumberOfChannelsSupported (const AudioProcessor::Bus* b, int numChannels, AudioProcessor::BusesLayout& inOutCurrentLayout) + { + auto potentialSets = AudioChannelSet::channelSetsWithNumberOfChannels (static_cast (numChannels)); + + for (auto set : potentialSets) + { + auto copy = inOutCurrentLayout; + + if (b->isLayoutSupported (set, ©)) + { + inOutCurrentLayout = copy; + return true; + } + } + + return false; + } + + //============================================================================== + static int getBusCount (const AudioProcessor* juceFilter, bool isInput) + { + int busCount = juceFilter->getBusCount (isInput); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + 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; + } + + static bool setBusesLayout (AudioProcessor* juceFilter, const AudioProcessor::BusesLayout& requestedLayouts) + { + #ifdef JucePlugin_PreferredChannelConfigurations + AudioProcessor::BusesLayout copy (requestedLayouts); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + + const int actualBuses = juceFilter->getBusCount (isInput); + const int auNumBuses = getBusCount (juceFilter, isInput); + Array& buses = (isInput ? copy.inputBuses : copy.outputBuses); + + for (int i = auNumBuses; i < actualBuses; ++i) + buses.add (AudioChannelSet::disabled()); + } + + return juceFilter->setBusesLayout (copy); + #else + return juceFilter->setBusesLayout (requestedLayouts); + #endif + } + + static AudioProcessor::BusesLayout getBusesLayout (const AudioProcessor* juceFilter) + { + #ifdef JucePlugin_PreferredChannelConfigurations + AudioProcessor::BusesLayout layout = juceFilter->getBusesLayout(); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + + const int actualBuses = juceFilter->getBusCount (isInput); + const int auNumBuses = getBusCount (juceFilter, isInput); + auto& buses = (isInput ? layout.inputBuses : layout.outputBuses); + + for (int i = auNumBuses; i < actualBuses; ++i) + buses.removeLast(); + } + + return layout; + #else + return juceFilter->getBusesLayout(); + #endif + } +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h new file mode 100644 index 000000000..a4d0e1b16 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +#if (JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS)) || DOXYGEN + +//============================================================================== +/** + Implements a plugin format manager for AudioUnits. +*/ +class JUCE_API AudioUnitPluginFormat : public AudioPluginFormat +{ +public: + //============================================================================== + AudioUnitPluginFormat(); + ~AudioUnitPluginFormat(); + + //============================================================================== + String getName() const override { return "AudioUnit"; } + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; + bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; + String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; + bool pluginNeedsRescanning (const PluginDescription&) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; + bool doesPluginStillExist (const PluginDescription&) override; + FileSearchPath getDefaultLocationsToSearch() override; + bool canScanForPlugins() const override { return true; } + +private: + //============================================================================== + void createPluginInstance (const PluginDescription&, + double initialSampleRate, + int initialBufferSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) +}; + +#endif + +//============================================================================== +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 +enum +{ + /** Custom AudioUnit property used to indicate MPE support */ + kAudioUnitProperty_SupportsMPE = 58 +}; +#endif + +} diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm new file mode 100644 index 000000000..65b364950 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -0,0 +1,2463 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) + +#if JUCE_MAC +#include +#include +#include +#endif + +#include + +#if JUCE_SUPPORT_CARBON + #include +#endif + +#ifndef JUCE_SUPPORTS_AUv3 + #if JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES && __OBJC2__ \ + && ((defined (MAC_OS_X_VERSION_MIN_REQUIRED) && defined (MAC_OS_X_VERSION_10_11) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) \ + || (defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && defined (__IPHONE_9_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0))) + #define JUCE_SUPPORTS_AUv3 1 + #else + #define JUCE_SUPPORTS_AUv3 0 + #endif +#endif + +#if JUCE_SUPPORTS_AUv3 + #include +#endif + +#include "../../juce_audio_basics/native/juce_mac_CoreAudioLayouts.h" +#include "../../juce_audio_devices/native/juce_MidiDataConcatenator.h" +#include "juce_AU_Shared.h" + +namespace juce +{ + +// Change this to disable logging of various activities +#ifndef AU_LOGGING + #define AU_LOGGING 1 +#endif + +#if AU_LOGGING + #define JUCE_AU_LOG(a) Logger::writeToLog(a); +#else + #define JUCE_AU_LOG(a) +#endif + +namespace AudioUnitFormatHelpers +{ + #if JUCE_DEBUG + static ThreadLocalValue insideCallback; + #endif + + String osTypeToString (OSType type) noexcept + { + const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), + (juce_wchar) ((type >> 16) & 0xff), + (juce_wchar) ((type >> 8) & 0xff), + (juce_wchar) (type & 0xff) }; + return String (s, 4); + } + + OSType stringToOSType (String s) + { + if (s.trim().length() >= 4) // (to avoid trimming leading spaces) + s = s.trim(); + + s += " "; + + return (((OSType) (unsigned char) s[0]) << 24) + | (((OSType) (unsigned char) s[1]) << 16) + | (((OSType) (unsigned char) s[2]) << 8) + | ((OSType) (unsigned char) s[3]); + } + + static const char* auIdentifierPrefix = "AudioUnit:"; + + String createPluginIdentifier (const AudioComponentDescription& desc) + { + String s (auIdentifierPrefix); + + if (desc.componentType == kAudioUnitType_MusicDevice) + s << "Synths/"; + else if (desc.componentType == kAudioUnitType_MusicEffect + || desc.componentType == kAudioUnitType_Effect) + s << "Effects/"; + else if (desc.componentType == kAudioUnitType_Generator) + s << "Generators/"; + else if (desc.componentType == kAudioUnitType_Panner) + s << "Panners/"; + else if (desc.componentType == kAudioUnitType_Mixer) + s << "Mixers/"; + + s << osTypeToString (desc.componentType) << "," + << osTypeToString (desc.componentSubType) << "," + << osTypeToString (desc.componentManufacturer); + + return s; + } + + void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) + { + CFStringRef cfName; + if (AudioComponentCopyName (comp, &cfName) == noErr) + { + name = String::fromCFString (cfName); + CFRelease (cfName); + } + + if (name.containsChar (':')) + { + manufacturer = name.upToFirstOccurrenceOf (":", false, false).trim(); + name = name.fromFirstOccurrenceOf (":", false, false).trim(); + } + + if (name.isEmpty()) + name = ""; + } + + bool getComponentDescFromIdentifier (const String& fileOrIdentifier, AudioComponentDescription& desc, + String& name, String& version, String& manufacturer) + { + if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) + { + String s (fileOrIdentifier.substring (jmax (fileOrIdentifier.lastIndexOfChar (':'), + fileOrIdentifier.lastIndexOfChar ('/')) + 1)); + + StringArray tokens; + tokens.addTokens (s, ",", StringRef()); + tokens.removeEmptyStrings(); + + if (tokens.size() == 3) + { + zerostruct (desc); + desc.componentType = stringToOSType (tokens[0]); + desc.componentSubType = stringToOSType (tokens[1]); + desc.componentManufacturer = stringToOSType (tokens[2]); + + if (AudioComponent comp = AudioComponentFindNext (0, &desc)) + { + getNameAndManufacturer (comp, name, manufacturer); + + if (manufacturer.isEmpty()) + manufacturer = tokens[2]; + + if (version.isEmpty()) + { + UInt32 versionNum; + + if (AudioComponentGetVersion (comp, &versionNum) == noErr) + { + version << (int) (versionNum >> 16) << "." + << (int) ((versionNum >> 8) & 0xff) << "." + << (int) (versionNum & 0xff); + } + } + + return true; + } + } + } + + return false; + } + + bool getComponentDescFromFile (const String& fileOrIdentifier, AudioComponentDescription& desc, + String& name, String& version, String& manufacturer) + { + zerostruct (desc); + + #if JUCE_IOS + ignoreUnused (fileOrIdentifier, name, version, manufacturer); + + return false; + #else + const File file (fileOrIdentifier); + if (! file.hasFileExtension (".component") && ! file.hasFileExtension (".appex")) + return false; + + const char* const utf8 = fileOrIdentifier.toUTF8(); + + if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, + (CFIndex) strlen (utf8), file.isDirectory())) + { + CFBundleRef bundleRef = CFBundleCreate (kCFAllocatorDefault, url); + CFRelease (url); + + if (bundleRef != 0) + { + CFTypeRef bundleName = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); + + if (bundleName != 0 && CFGetTypeID (bundleName) == CFStringGetTypeID()) + name = String::fromCFString ((CFStringRef) bundleName); + + if (name.isEmpty()) + name = file.getFileNameWithoutExtension(); + + CFTypeRef versionString = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleVersion")); + + if (versionString != 0 && CFGetTypeID (versionString) == CFStringGetTypeID()) + version = String::fromCFString ((CFStringRef) versionString); + + CFTypeRef manuString = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleGetInfoString")); + + if (manuString != 0 && CFGetTypeID (manuString) == CFStringGetTypeID()) + manufacturer = String::fromCFString ((CFStringRef) manuString); + + const ResFileRefNum resFileId = CFBundleOpenBundleResourceMap (bundleRef); + UseResFile (resFileId); + + const OSType thngType = stringToOSType ("thng"); + + for (ResourceIndex i = 1; i <= Count1Resources (thngType); ++i) + { + if (Handle h = Get1IndResource (thngType, i)) + { + HLock (h); + const uint32* const types = (const uint32*) *h; + + if (types[0] == kAudioUnitType_MusicDevice + || types[0] == kAudioUnitType_MusicEffect + || types[0] == kAudioUnitType_Effect + || types[0] == kAudioUnitType_Generator + || types[0] == kAudioUnitType_Panner + || types[0] == kAudioUnitType_Mixer) + { + desc.componentType = types[0]; + desc.componentSubType = types[1]; + desc.componentManufacturer = types[2]; + + if (AudioComponent comp = AudioComponentFindNext (0, &desc)) + getNameAndManufacturer (comp, name, manufacturer); + + break; + } + + HUnlock (h); + ReleaseResource (h); + } + } + + CFBundleCloseBundleResourceMap (bundleRef, resFileId); + CFRelease (bundleRef); + } + } + + return desc.componentType != 0 && desc.componentSubType != 0; + #endif + } + + const char* getCategory (OSType type) noexcept + { + switch (type) + { + case kAudioUnitType_Effect: + case kAudioUnitType_MusicEffect: return "Effect"; + case kAudioUnitType_MusicDevice: return "Synth"; + case kAudioUnitType_Generator: return "Generator"; + case kAudioUnitType_Panner: return "Panner"; + case kAudioUnitType_Mixer: return "Mixer"; + default: break; + } + + return nullptr; + } +} + +//============================================================================== +class AudioUnitPluginWindowCarbon; +class AudioUnitPluginWindowCocoa; + +//============================================================================== +class AudioUnitPluginInstance : public AudioPluginInstance +{ +public: + AudioUnitPluginInstance (AudioComponentInstance au) + : AudioPluginInstance (getBusesProperties (au)), + auComponent (AudioComponentInstanceGetComponent (au)), + wantsMidiMessages (false), + producesMidiMessages (false), + wasPlaying (false), + prepared (false), + isAUv3 (false), + currentBuffer (nullptr), + audioUnit (au), + #if JUCE_MAC + eventListenerRef (0), + #endif + midiConcatenator (2048) + { + using namespace AudioUnitFormatHelpers; + + AudioComponentGetDescription (auComponent, &componentDesc); + + #if JUCE_SUPPORTS_AUv3 + isAUv3 = ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + #endif + + wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice + || componentDesc.componentType == kAudioUnitType_MusicEffect; + + AudioComponentDescription ignore; + getComponentDescFromIdentifier (createPluginIdentifier (componentDesc), ignore, pluginName, version, manufacturer); + updateSupportedLayouts(); + } + + ~AudioUnitPluginInstance() + { + const ScopedLock sl (lock); + + #if JUCE_DEBUG + // this indicates that some kind of recursive call is getting triggered that's + // deleting this plugin while it's still under construction. + jassert (AudioUnitFormatHelpers::insideCallback.get() == 0); + #endif + + if (audioUnit != nullptr) + { + struct AUDeleter : public CallbackMessage + { + AUDeleter (AudioUnitPluginInstance& inInstance, WaitableEvent& inEvent) + : auInstance (inInstance), completionSignal (inEvent) + {} + + void messageCallback() override + { + auInstance.cleanup(); + completionSignal.signal(); + } + + AudioUnitPluginInstance& auInstance; + WaitableEvent& completionSignal; + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cleanup(); + } + else + { + WaitableEvent completionEvent; + (new AUDeleter (*this, completionEvent))->post(); + completionEvent.wait(); + } + } + } + + // called from the destructer above + void cleanup() + { + #if JUCE_MAC + if (eventListenerRef != 0) + { + AUListenerDispose (eventListenerRef); + eventListenerRef = 0; + } + #endif + + if (prepared) + releaseResources(); + + AudioComponentInstanceDispose (audioUnit); + audioUnit = nullptr; + } + + bool initialise (double rate, int blockSize) + { + producesMidiMessages = canProduceMidiOutput(); + setRateAndBufferSizeDetails (rate, blockSize); + setLatencySamples (0); + refreshParameterList(); + createPluginCallbacks(); + + return true; + } + + //============================================================================== + bool canAddBus (bool isInput) const override { return isBusCountWritable (isInput); } + bool canRemoveBus (bool isInput) const override { return isBusCountWritable (isInput); } + + bool canApplyBusCountChange (bool isInput, bool isAdding, BusProperties& outProperties) override + { + int currentCount = getBusCount (isInput); + int newCount = currentCount + (isAdding ? 1 : -1); + AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + + if (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &newCount, sizeof (newCount)) == noErr) + { + getBusProperties (isInput, currentCount, outProperties.busName, outProperties.defaultLayout); + outProperties.isActivatedByDefault = true; + updateSupportedLayouts(); + + return true; + } + + return false; + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + if (layouts == getBusesLayout()) + return true; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const Array& requestedLayouts = (isInput ? layouts.inputBuses : layouts.outputBuses); + const Array& oppositeRequestedLayouts = (isInput ? layouts.outputBuses : layouts.inputBuses); + const Array >& supported = (isInput ? supportedInLayouts : supportedOutLayouts); + const int n = getBusCount (isInput); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + const AudioChannelSet& requested = requestedLayouts.getReference (busIdx); + const int oppositeBusIdx = jmin (getBusCount (! isInput) - 1, busIdx); + const bool hasOppositeBus = (oppositeBusIdx >= 0); + const AudioChannelSet oppositeRequested = (hasOppositeBus ? oppositeRequestedLayouts.getReference (oppositeBusIdx) : AudioChannelSet()); + const Array& possible = supported.getReference (busIdx); + + if (requested.isDisabled()) + return false; + + if (possible.size() > 0 && ! possible.contains (requested)) + return false; + + int i; + for (i = 0; i < numChannelInfos; ++i) + { + const AUChannelInfo& info = channelInfos[i]; + const SInt16& thisChannels = (isInput ? info.inChannels : info.outChannels); + const SInt16& opChannels = (isInput ? info.outChannels : info.inChannels); + + // this bus + if (thisChannels == 0) continue; + else if (thisChannels > 0 && requested.size() != thisChannels) continue; + else if (thisChannels < -2 && requested.size() > (thisChannels * -1)) continue; + + // opposite bus + if (opChannels == 0 && hasOppositeBus) continue; + else if (opChannels > 0 && oppositeRequested.size() != opChannels) continue; + else if (opChannels < -2 && oppositeRequested.size() > (opChannels * -1)) continue; + + // both buses + if (thisChannels == -2 && opChannels == -2) continue; + if (thisChannels == -1 && opChannels == -1) + { + int numOppositeBuses = getBusCount (! isInput); + int j; + for (j = 0; j < numOppositeBuses; ++j) + if (requested.size() != oppositeRequestedLayouts.getReference (j).size()) + break; + + if (j < numOppositeBuses) continue; + } + + break; + } + + if (i >= numChannelInfos) + return false; + } + } + + return true; + } + + bool syncBusLayouts (const BusesLayout& layouts, bool isInitialized, bool& layoutHasChanged) const + { + layoutHasChanged = false; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getBusCount (isInput); + + if (getElementCount (scope) != n && isBusCountWritable (isInput)) + { + OSStatus err; + UInt32 newCount = static_cast (n); + layoutHasChanged = true; + + err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &newCount, sizeof (newCount)); + jassert (err == noErr); + } + + for (int i = 0; i < n; ++i) + { + Float64 sampleRate; + UInt32 sampleRateSize = sizeof (sampleRate); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); + + const AudioChannelSet& set = layouts.getChannelSet (isInput, i); + const int requestedNumChannels = set.size(); + + { + AudioStreamBasicDescription stream; + UInt32 dataSize = sizeof (stream); + OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, &dataSize); + if (err != noErr || dataSize < sizeof (stream)) + return false; + + const int actualNumChannels = static_cast (stream.mChannelsPerFrame); + + if (actualNumChannels != requestedNumChannels) + { + layoutHasChanged = true; + zerostruct (stream); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + stream.mSampleRate = sampleRate; + stream.mFormatID = kAudioFormatLinearPCM; + stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; + stream.mFramesPerPacket = 1; + stream.mBytesPerPacket = 4; + stream.mBytesPerFrame = 4; + stream.mBitsPerChannel = 32; + stream.mChannelsPerFrame = static_cast (requestedNumChannels); + + err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, sizeof (stream)); + if (err != noErr) return false; + } + } + + if (! set.isDiscreteLayout()) + { + const AudioChannelLayoutTag requestedTag = CoreAudioLayouts::toCoreAudio (set); + + AudioChannelLayout layout; + const UInt32 minDataSize = sizeof (layout) - sizeof (AudioChannelDescription); + UInt32 dataSize = minDataSize; + + AudioChannelLayoutTag actualTag = kAudioChannelLayoutTag_Unknown; + OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (i), &layout, &dataSize); + bool supportsLayouts = (err == noErr && dataSize >= minDataSize); + + if (supportsLayouts) + { + const UInt32 expectedSize = + minDataSize + (sizeof (AudioChannelDescription) * layout.mNumberChannelDescriptions); + + HeapBlock layoutBuffer; + layoutBuffer.malloc (1, expectedSize); + dataSize = expectedSize; + + err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, + static_cast (i), layoutBuffer.get(), &dataSize); + + if (err != noErr || dataSize < expectedSize) + return false; + + // try to convert the layout into a tag + actualTag = CoreAudioLayouts::toCoreAudio (CoreAudioLayouts::fromCoreAudio (layout)); + } + + if (actualTag != requestedTag) + { + zerostruct (layout); + layout.mChannelLayoutTag = requestedTag; + + err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (i), &layout, minDataSize); + + // only bail out if the plug-in claims to support layouts + // See AudioUnit headers on kAudioUnitProperty_AudioChannelLayout + if (err != noErr && supportsLayouts && isInitialized) + return false; + } + } + } + } + + return true; + } + + bool canApplyBusesLayout (const BusesLayout& layouts) const override + { + // You cannot call setBusesLayout when the AudioProcessor is processing. + // Call releaseResources first! + jassert (! prepared); + + bool layoutHasChanged = false; + + if (! syncBusLayouts (layouts, false, layoutHasChanged)) + return false; + + // did anything actually change + if (layoutHasChanged) + { + bool success = (AudioUnitInitialize (audioUnit) == noErr); + + // Some plug-ins require the LayoutTag to be set after initialization + if (success) + success = syncBusLayouts (layouts, true, layoutHasChanged); + + AudioUnitUninitialize (audioUnit); + + if (! success) + // make sure that the layout is back to it's original state + syncBusLayouts (getBusesLayout(), false, layoutHasChanged); + + return success; + } + + return true; + } + + //============================================================================== + // AudioPluginInstance methods: + + void fillInPluginDescription (PluginDescription& desc) const override + { + desc.name = pluginName; + desc.descriptiveName = pluginName; + desc.fileOrIdentifier = AudioUnitFormatHelpers::createPluginIdentifier (componentDesc); + desc.uid = ((int) componentDesc.componentType) + ^ ((int) componentDesc.componentSubType) + ^ ((int) componentDesc.componentManufacturer); + desc.lastFileModTime = Time(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); + desc.pluginFormatName = "AudioUnit"; + desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); + desc.manufacturerName = manufacturer; + desc.version = version; + desc.numInputChannels = getTotalNumInputChannels(); + desc.numOutputChannels = getTotalNumOutputChannels(); + desc.isInstrument = (componentDesc.componentType == kAudioUnitType_MusicDevice); + } + + void* getPlatformSpecificData() override { return audioUnit; } + const String getName() const override { return pluginName; } + + double getTailLengthSeconds() const override + { + Float64 tail = 0; + UInt32 tailSize = sizeof (tail); + + if (audioUnit != nullptr) + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_TailTime, kAudioUnitScope_Global, + 0, &tail, &tailSize); + + return tail; + } + + bool acceptsMidi() const override { return wantsMidiMessages; } + bool producesMidi() const override { return producesMidiMessages; } + + //============================================================================== + // AudioProcessor methods: + + void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override + { + if (audioUnit != nullptr) + { + releaseResources(); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getBusCount (isInput); + + for (int i = 0; i < n; ++i) + { + Float64 sampleRate; + UInt32 sampleRateSize = sizeof (sampleRate); + const Float64 sr = newSampleRate; + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); + + if (sampleRate != sr) + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sr, sizeof (sr)); + + if (isInput) + { + AURenderCallbackStruct info; + zerostruct (info); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + + info.inputProcRefCon = this; + info.inputProc = renderGetInputCallback; + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, + static_cast (i), &info, sizeof (info)); + } + else + { + outputBufferList.add (new AUBuffer (static_cast (getChannelCountOfBus (false, i)))); + } + } + } + + UInt32 frameSize = (UInt32) estimatedSamplesPerBlock; + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, + &frameSize, sizeof (frameSize)); + + setRateAndBufferSizeDetails ((double) newSampleRate, estimatedSamplesPerBlock); + + updateLatency(); + + zerostruct (timeStamp); + timeStamp.mSampleTime = 0; + timeStamp.mHostTime = GetCurrentHostTime (0, newSampleRate, isAUv3); + timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; + + currentBuffer = nullptr; + wasPlaying = false; + + resetBuses(); + + bool ignore; + + if (! syncBusLayouts (getBusesLayout(), false, ignore)) + return; + + prepared = (AudioUnitInitialize (audioUnit) == noErr); + + if (prepared) + { + if (! syncBusLayouts (getBusesLayout(), true, ignore)) + { + prepared = false; + AudioUnitUninitialize (audioUnit); + } + } + } + } + + void releaseResources() override + { + if (prepared) + { + AudioUnitUninitialize (audioUnit); + resetBuses(); + AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); + + outputBufferList.clear(); + currentBuffer = nullptr; + prepared = false; + } + + incomingMidi.clear(); + } + + void resetBuses() + { + for (int i = 0; i < getBusCount (true); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Input, static_cast (i)); + for (int i = 0; i < getBusCount (false); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, static_cast (i)); + } + + void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override + { + const int numSamples = buffer.getNumSamples(); + + if (prepared) + { + timeStamp.mHostTime = GetCurrentHostTime (numSamples, getSampleRate(), isAUv3); + + int chIdx = 0; + const int numOutputBuses = getBusCount (false); + for (int i = 0; i < numOutputBuses; ++i) + { + if (AUBuffer* buf = outputBufferList[i]) + { + AudioBufferList& abl = *buf; + + for (AudioUnitElement j = 0; j < abl.mNumberBuffers; ++j) + { + abl.mBuffers[j].mNumberChannels = 1; + abl.mBuffers[j].mDataByteSize = (UInt32) (sizeof (float) * (size_t) numSamples); + abl.mBuffers[j].mData = buffer.getWritePointer (chIdx++); + } + } + } + + currentBuffer = &buffer; + + if (wantsMidiMessages) + { + const uint8* midiEventData; + int midiEventSize, midiEventPosition; + + for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (midiEventData, midiEventSize, midiEventPosition);) + { + if (midiEventSize <= 3) + MusicDeviceMIDIEvent (audioUnit, + midiEventData[0], midiEventData[1], midiEventData[2], + (UInt32) midiEventPosition); + else + MusicDeviceSysEx (audioUnit, midiEventData, (UInt32) midiEventSize); + } + + midiMessages.clear(); + } + + + for (int i = 0; i < numOutputBuses; ++i) + { + AudioUnitRenderActionFlags flags = 0; + + if (AUBuffer* buf = outputBufferList[i]) + AudioUnitRender (audioUnit, &flags, &timeStamp, static_cast (i), (UInt32) numSamples, buf->bufferList.get()); + } + + timeStamp.mSampleTime += numSamples; + } + else + { + // Plugin not working correctly, so just bypass.. + for (int i = getTotalNumOutputChannels(); --i >= 0;) + buffer.clear (i, 0, buffer.getNumSamples()); + } + + if (producesMidiMessages) + { + const ScopedLock sl (midiInLock); + midiMessages.swapWith (incomingMidi); + incomingMidi.clear(); + } + } + + //============================================================================== + bool hasEditor() const override { return true; } + AudioProcessorEditor* createEditor() override; + + static AudioProcessor::BusesProperties getBusesProperties (AudioComponentInstance comp) + { + AudioProcessor::BusesProperties busProperties; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = getElementCount (comp, isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); + + for (int i = 0; i < n; ++i) + { + String busName; + AudioChannelSet currentLayout; + + getBusProperties (comp, isInput, i, busName, currentLayout); + jassert (! currentLayout.isDisabled()); + + busProperties.addBus (isInput, busName, currentLayout, true); + } + } + + return busProperties; + } + + //============================================================================== + const String getInputChannelName (int index) const override + { + if (isPositiveAndBelow (index, getTotalNumInputChannels())) + return "Input " + String (index + 1); + + return {}; + } + + const String getOutputChannelName (int index) const override + { + if (isPositiveAndBelow (index, getTotalNumOutputChannels())) + return "Output " + String (index + 1); + + return {}; + } + + bool isInputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumInputChannels()); } + bool isOutputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } + + //============================================================================== + int getNumParameters() override { return parameters.size(); } + + float getParameter (int index) override + { + const ScopedLock sl (lock); + + AudioUnitParameterValue value = 0; + + if (audioUnit != nullptr) + { + if (const ParamInfo* p = parameters[index]) + { + AudioUnitGetParameter (audioUnit, + p->paramID, + kAudioUnitScope_Global, 0, + &value); + + value = (value - p->minValue) / (p->maxValue - p->minValue); + } + } + + return value; + } + + void setParameter (int index, float newValue) override + { + const ScopedLock sl (lock); + + if (audioUnit != nullptr) + { + if (const ParamInfo* p = parameters[index]) + { + AudioUnitSetParameter (audioUnit, p->paramID, kAudioUnitScope_Global, 0, + p->minValue + (p->maxValue - p->minValue) * newValue, 0); + + sendParameterChangeEvent (index); + } + } + } + + void sendParameterChangeEvent (int index) + { + #if JUCE_MAC + jassert (audioUnit != nullptr); + + const ParamInfo& p = *parameters.getUnchecked (index); + + AudioUnitEvent ev; + ev.mEventType = kAudioUnitEvent_ParameterValueChange; + ev.mArgument.mParameter.mAudioUnit = audioUnit; + ev.mArgument.mParameter.mParameterID = p.paramID; + ev.mArgument.mParameter.mScope = kAudioUnitScope_Global; + ev.mArgument.mParameter.mElement = 0; + + AUEventListenerNotify (nullptr, nullptr, &ev); + #else + ignoreUnused (index); + #endif + } + + void sendAllParametersChangedEvents() + { + #if JUCE_MAC + jassert (audioUnit != nullptr); + + AudioUnitParameter param; + param.mAudioUnit = audioUnit; + param.mParameterID = kAUParameterListener_AnyParameter; + + AUParameterListenerNotify (nullptr, nullptr, ¶m); + #endif + } + + const String getParameterName (int index) override + { + if (auto* p = parameters[index]) + return p->name; + + return {}; + } + + const String getParameterText (int index) override { return String (getParameter (index)); } + + int getParameterNumSteps (int index) override + { + if (auto* p = parameters[index]) + return p->numSteps; + + return AudioProcessor::getDefaultNumParameterSteps(); + } + + bool isParameterDiscrete (int index) const override + { + if (auto* p = parameters[index]) + return p->discrete; + + return false; + } + + bool isParameterAutomatable (int index) const override + { + if (auto* p = parameters[index]) + return p->automatable; + + return false; + } + + //============================================================================== + int getNumPrograms() override + { + CFArrayRef presets; + UInt32 sz = sizeof (CFArrayRef); + int num = 0; + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, + kAudioUnitScope_Global, 0, &presets, &sz) == noErr) + { + num = (int) CFArrayGetCount (presets); + CFRelease (presets); + } + + return num; + } + + int getCurrentProgram() override + { + AUPreset current; + current.presetNumber = 0; + UInt32 sz = sizeof (AUPreset); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_PresentPreset, + kAudioUnitScope_Global, 0, ¤t, &sz); + + return current.presetNumber; + } + + void setCurrentProgram (int newIndex) override + { + AUPreset current; + current.presetNumber = newIndex; + current.presetName = CFSTR(""); + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_PresentPreset, + kAudioUnitScope_Global, 0, ¤t, sizeof (AUPreset)); + + sendAllParametersChangedEvents(); + } + + const String getProgramName (int index) override + { + String s; + CFArrayRef presets; + UInt32 sz = sizeof (CFArrayRef); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_FactoryPresets, + kAudioUnitScope_Global, 0, &presets, &sz) == noErr) + { + for (CFIndex i = 0; i < CFArrayGetCount (presets); ++i) + { + if (const AUPreset* p = (const AUPreset*) CFArrayGetValueAtIndex (presets, i)) + { + if (p->presetNumber == index) + { + s = String::fromCFString (p->presetName); + break; + } + } + } + + CFRelease (presets); + } + + return s; + } + + void changeProgramName (int /*index*/, const String& /*newName*/) override + { + jassertfalse; // xxx not implemented! + } + + //============================================================================== + void updateTrackProperties (const TrackProperties& properties) override + { + if (properties.name.isNotEmpty()) + { + CFStringRef contextName = properties.name.toCFString(); + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ContextName, kAudioUnitScope_Global, + 0, &contextName, sizeof (CFStringRef)); + + CFRelease (contextName); + } + } + + //============================================================================== + void getStateInformation (MemoryBlock& destData) override + { + getCurrentProgramStateInformation (destData); + } + + void getCurrentProgramStateInformation (MemoryBlock& destData) override + { + CFPropertyListRef propertyList = 0; + UInt32 sz = sizeof (CFPropertyListRef); + + if (AudioUnitGetProperty (audioUnit, + kAudioUnitProperty_ClassInfo, + kAudioUnitScope_Global, + 0, &propertyList, &sz) == noErr) + { + CFWriteStreamRef stream = CFWriteStreamCreateWithAllocatedBuffers (kCFAllocatorDefault, kCFAllocatorDefault); + CFWriteStreamOpen (stream); + + CFIndex bytesWritten = CFPropertyListWriteToStream (propertyList, stream, kCFPropertyListBinaryFormat_v1_0, 0); + CFWriteStreamClose (stream); + + CFDataRef data = (CFDataRef) CFWriteStreamCopyProperty (stream, kCFStreamPropertyDataWritten); + + destData.setSize ((size_t) bytesWritten); + destData.copyFrom (CFDataGetBytePtr (data), 0, destData.getSize()); + CFRelease (data); + + CFRelease (stream); + CFRelease (propertyList); + } + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + setCurrentProgramStateInformation (data, sizeInBytes); + } + + void setCurrentProgramStateInformation (const void* data, int sizeInBytes) override + { + CFReadStreamRef stream = CFReadStreamCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) data, + sizeInBytes, kCFAllocatorNull); + CFReadStreamOpen (stream); + + CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0; + CFPropertyListRef propertyList = CFPropertyListCreateFromStream (kCFAllocatorDefault, stream, 0, + kCFPropertyListImmutable, &format, 0); + CFRelease (stream); + + if (propertyList != 0) + { + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, + 0, &propertyList, sizeof (propertyList)); + + sendAllParametersChangedEvents(); + + CFRelease (propertyList); + } + } + + void refreshParameterList() override + { + parameters.clear(); + paramIDToIndex.clear(); + + if (audioUnit != nullptr) + { + UInt32 paramListSize = 0; + AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, + 0, ¶mListSize, nullptr); + + if (paramListSize > 0) + { + const size_t numParams = paramListSize / sizeof (int); + + HeapBlock ids; + ids.calloc (numParams); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, + 0, ids, ¶mListSize); + + for (size_t i = 0; i < numParams; ++i) + { + AudioUnitParameterInfo info; + UInt32 sz = sizeof (info); + + if (AudioUnitGetProperty (audioUnit, + kAudioUnitProperty_ParameterInfo, + kAudioUnitScope_Global, + ids[i], &info, &sz) == noErr) + { + ParamInfo* const param = new ParamInfo(); + parameters.add (param); + param->paramID = ids[i]; + paramIDToIndex.getReference (ids[i]) = i; + param->minValue = info.minValue; + param->maxValue = info.maxValue; + param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; + param->discrete = (info.unit == kAudioUnitParameterUnit_Indexed); + param->numSteps = param->discrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(); + + if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) + { + param->name = String::fromCFString (info.cfNameString); + + if ((info.flags & kAudioUnitParameterFlag_CFNameRelease) != 0) + CFRelease (info.cfNameString); + } + else + { + param->name = String (info.name, sizeof (info.name)); + } + } + } + } + } + } + + void updateLatency() + { + Float64 latencySecs = 0.0; + UInt32 latencySize = sizeof (latencySecs); + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, + 0, &latencySecs, &latencySize); + + setLatencySamples (roundToInt (latencySecs * getSampleRate())); + } + + void handleIncomingMidiMessage (void*, const MidiMessage& message) + { + const ScopedLock sl (midiInLock); + incomingMidi.addEvent (message, 0); + } + + void handlePartialSysexMessage (void*, const uint8*, int, double) {} + +private: + //============================================================================== + friend class AudioUnitPluginWindowCarbon; + friend class AudioUnitPluginWindowCocoa; + friend class AudioUnitPluginFormat; + + AudioComponentDescription componentDesc; + AudioComponent auComponent; + String pluginName, manufacturer, version; + String fileOrIdentifier; + CriticalSection lock; + bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared, isAUv3; + + struct AUBuffer + { + AUBuffer (size_t numBuffers) + { + bufferList.calloc (1, (sizeof (AudioBufferList) - sizeof (::AudioBuffer)) + (sizeof (::AudioBuffer) * numBuffers)); + AudioBufferList& buffer = *bufferList.get(); + + buffer.mNumberBuffers = static_cast (numBuffers); + } + + operator AudioBufferList&() + { + return *bufferList.get(); + } + + HeapBlock bufferList; + }; + + OwnedArray outputBufferList; + AudioTimeStamp timeStamp; + AudioSampleBuffer* currentBuffer; + Array > supportedInLayouts, supportedOutLayouts; + + int numChannelInfos; + HeapBlock channelInfos; + + AudioUnit audioUnit; + #if JUCE_MAC + AUEventListenerRef eventListenerRef; + #endif + + struct ParamInfo + { + UInt32 paramID; + String name; + AudioUnitParameterValue minValue, maxValue; + bool automatable, discrete; + int numSteps; + }; + + OwnedArray parameters; + HashMap paramIDToIndex; + + MidiDataConcatenator midiConcatenator; + CriticalSection midiInLock; + MidiBuffer incomingMidi; + + void createPluginCallbacks() + { + if (audioUnit != nullptr) + { + #if JUCE_MAC + if (producesMidiMessages) + { + AUMIDIOutputCallbackStruct info; + zerostruct (info); + + info.userData = this; + info.midiOutputCallback = renderMidiOutputCallback; + + producesMidiMessages = (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallback, + kAudioUnitScope_Global, 0, &info, sizeof (info)) == noErr); + } + #endif + + { + HostCallbackInfo info; + zerostruct (info); + + info.hostUserData = this; + info.beatAndTempoProc = getBeatAndTempoCallback; + info.musicalTimeLocationProc = getMusicalTimeLocationCallback; + info.transportStateProc = getTransportStateCallback; + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, + kAudioUnitScope_Global, 0, &info, sizeof (info)); + } + #if JUCE_MAC + AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), + kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); + + for (int i = 0; i < parameters.size(); ++i) + { + AudioUnitEvent event; + event.mArgument.mParameter.mAudioUnit = audioUnit; + event.mArgument.mParameter.mParameterID = parameters.getUnchecked(i)->paramID; + event.mArgument.mParameter.mScope = kAudioUnitScope_Global; + event.mArgument.mParameter.mElement = 0; + + event.mEventType = kAudioUnitEvent_ParameterValueChange; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + + event.mEventType = kAudioUnitEvent_BeginParameterChangeGesture; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + + event.mEventType = kAudioUnitEvent_EndParameterChangeGesture; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + } + + addPropertyChangeListener (kAudioUnitProperty_PresentPreset); + addPropertyChangeListener (kAudioUnitProperty_ParameterList); + addPropertyChangeListener (kAudioUnitProperty_Latency); + #endif + } + } + + #if JUCE_MAC + void addPropertyChangeListener (AudioUnitPropertyID type) const + { + + AudioUnitEvent event; + event.mEventType = kAudioUnitEvent_PropertyChange; + event.mArgument.mProperty.mPropertyID = type; + event.mArgument.mProperty.mAudioUnit = audioUnit; + event.mArgument.mProperty.mScope = kAudioUnitScope_Global; + event.mArgument.mProperty.mElement = 0; + AUEventListenerAddEventType (eventListenerRef, nullptr, &event); + } + + void eventCallback (const AudioUnitEvent& event, AudioUnitParameterValue newValue) + { + int paramIndex = -1; + + if (event.mEventType == kAudioUnitEvent_ParameterValueChange + || event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture + || event.mEventType == kAudioUnitEvent_EndParameterChangeGesture) + { + auto paramID = event.mArgument.mParameter.mParameterID; + + if (! paramIDToIndex.contains (paramID)) + return; + + paramIndex = static_cast (paramIDToIndex [paramID]); + + if (! isPositiveAndBelow (paramIndex, parameters.size())) + return; + } + + switch (event.mEventType) + { + case kAudioUnitEvent_ParameterValueChange: + { + auto& p = *parameters.getUnchecked (paramIndex); + sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); + } + break; + + case kAudioUnitEvent_BeginParameterChangeGesture: + beginParameterChangeGesture (paramIndex); + break; + + case kAudioUnitEvent_EndParameterChangeGesture: + endParameterChangeGesture (paramIndex); + break; + + default: + if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) + updateHostDisplay(); + else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) + sendAllParametersChangedEvents(); + else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) + updateLatency(); + + break; + } + } + + static void eventListenerCallback (void* userRef, void*, const AudioUnitEvent* event, + UInt64, AudioUnitParameterValue value) + { + jassert (event != nullptr); + static_cast (userRef)->eventCallback (*event, value); + } + #endif + + //============================================================================== + OSStatus renderGetInput (AudioUnitRenderActionFlags*, + const AudioTimeStamp*, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) const + { + if (currentBuffer != nullptr) + { + // if this ever happens, might need to add extra handling + jassert (inNumberFrames == (UInt32) currentBuffer->getNumSamples()); + AudioSampleBuffer buffer = + (static_cast (inBusNumber) < getBusCount (true) + ? getBusBuffer (*currentBuffer, true, static_cast (inBusNumber)) + : AudioSampleBuffer()); + + for (int i = 0; i < static_cast (ioData->mNumberBuffers); ++i) + { + if (i < buffer.getNumChannels()) + { + memcpy (ioData->mBuffers[i].mData, + buffer.getReadPointer (i), + sizeof (float) * inNumberFrames); + } + else + { + zeromem (ioData->mBuffers[i].mData, + sizeof (float) * inNumberFrames); + } + } + } + + return noErr; + } + + OSStatus renderMidiOutput (const MIDIPacketList* pktlist) + { + if (pktlist != nullptr && pktlist->numPackets) + { + const double time = Time::getMillisecondCounterHiRes() * 0.001; + const MIDIPacket* packet = &pktlist->packet[0]; + + for (UInt32 i = 0; i < pktlist->numPackets; ++i) + { + midiConcatenator.pushMidiData (packet->data, (int) packet->length, time, (void*) nullptr, *this); + packet = MIDIPacketNext (packet); + } + } + + return noErr; + } + + template + static void setIfNotNull (Type1* p, Type2 value) noexcept + { + if (p != nullptr) *p = value; + } + + OSStatus getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const + { + AudioPlayHead* const ph = getPlayHead(); + AudioPlayHead::CurrentPositionInfo result; + + if (ph != nullptr && ph->getCurrentPosition (result)) + { + setIfNotNull (outCurrentBeat, result.ppqPosition); + setIfNotNull (outCurrentTempo, result.bpm); + } + else + { + setIfNotNull (outCurrentBeat, 0); + setIfNotNull (outCurrentTempo, 120.0); + } + + return noErr; + } + + OSStatus getMusicalTimeLocation (UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, + UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const + { + if (AudioPlayHead* const ph = getPlayHead()) + { + AudioPlayHead::CurrentPositionInfo result; + + if (ph->getCurrentPosition (result)) + { + setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx + setIfNotNull (outTimeSig_Numerator, (UInt32) result.timeSigNumerator); + setIfNotNull (outTimeSig_Denominator, (UInt32) result.timeSigDenominator); + setIfNotNull (outCurrentMeasureDownBeat, result.ppqPositionOfLastBarStart); //xxx wrong + return noErr; + } + } + + setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); + setIfNotNull (outTimeSig_Numerator, (UInt32) 4); + setIfNotNull (outTimeSig_Denominator, (UInt32) 4); + setIfNotNull (outCurrentMeasureDownBeat, 0); + return noErr; + } + + OSStatus getTransportState (Boolean* outIsPlaying, Boolean* outTransportStateChanged, + Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, + Float64* outCycleStartBeat, Float64* outCycleEndBeat) + { + if (AudioPlayHead* const ph = getPlayHead()) + { + AudioPlayHead::CurrentPositionInfo result; + + if (ph->getCurrentPosition (result)) + { + setIfNotNull (outIsPlaying, result.isPlaying); + + if (outTransportStateChanged != nullptr) + { + *outTransportStateChanged = result.isPlaying != wasPlaying; + wasPlaying = result.isPlaying; + } + + setIfNotNull (outCurrentSampleInTimeLine, result.timeInSamples); + setIfNotNull (outIsCycling, result.isLooping); + setIfNotNull (outCycleStartBeat, result.ppqLoopStart); + setIfNotNull (outCycleEndBeat, result.ppqLoopEnd); + return noErr; + } + } + + setIfNotNull (outIsPlaying, false); + setIfNotNull (outTransportStateChanged, false); + setIfNotNull (outCurrentSampleInTimeLine, 0); + setIfNotNull (outIsCycling, false); + setIfNotNull (outCycleStartBeat, 0.0); + setIfNotNull (outCycleEndBeat, 0.0); + return noErr; + } + + //============================================================================== + static OSStatus renderGetInputCallback (void* hostRef, AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, + UInt32 inNumberFrames, AudioBufferList* ioData) + { + return static_cast (hostRef) + ->renderGetInput (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); + } + + static OSStatus renderMidiOutputCallback (void* hostRef, const AudioTimeStamp*, UInt32 /*midiOutNum*/, + const MIDIPacketList* pktlist) + { + return static_cast (hostRef)->renderMidiOutput (pktlist); + } + + static OSStatus getBeatAndTempoCallback (void* hostRef, Float64* outCurrentBeat, Float64* outCurrentTempo) + { + return static_cast (hostRef)->getBeatAndTempo (outCurrentBeat, outCurrentTempo); + } + + static OSStatus getMusicalTimeLocationCallback (void* hostRef, UInt32* outDeltaSampleOffsetToNextBeat, + Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, + Float64* outCurrentMeasureDownBeat) + { + return static_cast (hostRef) + ->getMusicalTimeLocation (outDeltaSampleOffsetToNextBeat, outTimeSig_Numerator, + outTimeSig_Denominator, outCurrentMeasureDownBeat); + } + + static OSStatus getTransportStateCallback (void* hostRef, Boolean* outIsPlaying, Boolean* outTransportStateChanged, + Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, + Float64* outCycleStartBeat, Float64* outCycleEndBeat) + { + return static_cast (hostRef) + ->getTransportState (outIsPlaying, outTransportStateChanged, outCurrentSampleInTimeLine, + outIsCycling, outCycleStartBeat, outCycleEndBeat); + } + + //============================================================================== + static inline UInt64 GetCurrentHostTime (int numSamples, double sampleRate, bool isAUv3) noexcept + { + #if ! JUCE_IOS + if (! isAUv3) + return AudioGetCurrentHostTime(); + #else + ignoreUnused (isAUv3); + #endif + + UInt64 currentTime = mach_absolute_time(); + static mach_timebase_info_data_t sTimebaseInfo = {0, 0}; + + if (sTimebaseInfo.denom == 0) + mach_timebase_info (&sTimebaseInfo); + + double bufferNanos = static_cast (numSamples) * 1.0e9 / sampleRate; + UInt64 bufferTicks = static_cast (std::ceil (bufferNanos * (static_cast (sTimebaseInfo.denom) / static_cast (sTimebaseInfo.numer)))); + currentTime += bufferTicks; + + return currentTime; + } + + bool isBusCountWritable (bool isInput) const noexcept + { + UInt32 countSize; + Boolean writable; + OSStatus err; + AudioUnitScope scope = (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); + + err = AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &countSize, &writable); + + return (err == noErr && writable != 0 && countSize == sizeof (UInt32)); + } + + //============================================================================== + int getElementCount (AudioUnitScope scope) const noexcept + { + return static_cast (getElementCount (audioUnit, scope)); + } + + static int getElementCount (AudioUnit comp, AudioUnitScope scope) noexcept + { + UInt32 count; + UInt32 countSize = sizeof (count); + + OSStatus err = AudioUnitGetProperty (comp, kAudioUnitProperty_ElementCount, scope, 0, &count, &countSize); + jassert (err == noErr); + ignoreUnused (err); + + return static_cast (count); + } + + //============================================================================== + void getBusProperties (bool isInput, int busIdx, String& busName, AudioChannelSet& currentLayout) const + { + getBusProperties (audioUnit, isInput, busIdx, busName, currentLayout); + } + + static void getBusProperties (AudioUnit comp, bool isInput, int busIdx, String& busName, AudioChannelSet& currentLayout) + { + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + busName = (isInput ? "Input #" : "Output #") + String (busIdx + 1); + + { + CFStringRef busNameCF = nullptr; + UInt32 propertySize = sizeof (busNameCF); + + if (AudioUnitGetProperty (comp, kAudioUnitProperty_ElementName, scope, static_cast (busIdx), &busNameCF, &propertySize) == noErr + && busNameCF != nullptr) + { + busName = nsStringToJuce ((NSString*) busNameCF); + CFRelease (busNameCF); + } + + { + AudioChannelLayout auLayout; + propertySize = sizeof (auLayout); + + if (AudioUnitGetProperty (comp, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (busIdx), &auLayout, &propertySize) == noErr) + currentLayout = CoreAudioLayouts::fromCoreAudio (auLayout); + } + + if (currentLayout.isDisabled()) + { + AudioStreamBasicDescription descr; + propertySize = sizeof (descr); + + if (AudioUnitGetProperty (comp, kAudioUnitProperty_StreamFormat, scope, static_cast (busIdx), &descr, &propertySize) == noErr) + currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); + } + } + } + + //============================================================================== + void numBusesChanged() override + { + updateSupportedLayouts(); + } + + void updateSupportedLayouts() + { + supportedInLayouts.clear(); + supportedOutLayouts.clear(); + numChannelInfos = 0; + channelInfos.free(); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getElementCount (scope); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + Array supported; + AudioChannelSet currentLayout; + + { + AudioChannelLayout auLayout; + UInt32 propertySize = sizeof (auLayout); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (busIdx), &auLayout, &propertySize) == noErr) + currentLayout = CoreAudioLayouts::fromCoreAudio (auLayout); + } + + if (currentLayout.isDisabled()) + { + AudioStreamBasicDescription descr; + UInt32 propertySize = sizeof (descr); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (busIdx), &descr, &propertySize) == noErr) + currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); + } + + supported.clear(); + { + UInt32 propertySize = 0; + Boolean writable; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, static_cast (busIdx), &propertySize, &writable) == noErr + && propertySize > 0) + { + const size_t numElements = propertySize / sizeof (AudioChannelLayoutTag); + HeapBlock layoutTags (numElements); + propertySize = static_cast (sizeof (AudioChannelLayoutTag) * numElements); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, + static_cast (busIdx), layoutTags.get(), &propertySize) == noErr) + { + for (int j = 0; j < static_cast (numElements); ++j) + { + const AudioChannelLayoutTag tag = layoutTags[j]; + + if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) + { + AudioChannelLayout caLayout; + + caLayout.mChannelLayoutTag = tag; + supported.addIfNotAlreadyThere (CoreAudioLayouts::fromCoreAudio (caLayout)); + } + } + + if (supported.size() > 0) + supported.addIfNotAlreadyThere (currentLayout); + } + } + } + + (isInput ? supportedInLayouts : supportedOutLayouts).add (supported); + } + } + + { + UInt32 propertySize = 0; + Boolean writable; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr + && propertySize > 0) + { + numChannelInfos = propertySize / sizeof (AUChannelInfo); + channelInfos.malloc (static_cast (numChannelInfos)); + propertySize = static_cast (sizeof (AUChannelInfo) * static_cast (numChannelInfos)); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, channelInfos.get(), &propertySize) != noErr) + numChannelInfos = 0; + } + else + { + numChannelInfos = 1; + channelInfos.malloc (static_cast (numChannelInfos)); + channelInfos.get()->inChannels = -1; + channelInfos.get()->outChannels = -1; + } + } + } + + bool canProduceMidiOutput() + { + #if JUCE_MAC + UInt32 dataSize = 0; + Boolean isWritable = false; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo, + kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr + && dataSize != 0) + { + CFArrayRef midiArray; + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo, + kAudioUnitScope_Global, 0, &midiArray, &dataSize) == noErr) + { + bool result = (CFArrayGetCount (midiArray) > 0); + CFRelease (midiArray); + return result; + } + } + + return false; + #else + return false; + #endif + } + + bool supportsMPE() const override + { + UInt32 dataSize = 0; + Boolean isWritable = false; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportsMPE, + kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr + && dataSize == sizeof (UInt32)) + { + UInt32 result = 0; + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportsMPE, + kAudioUnitScope_Global, 0, &result, &dataSize) == noErr) + { + return result > 0; + } + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstance) +}; + +//============================================================================== +class AudioUnitPluginWindowCocoa : public AudioProcessorEditor +{ +public: + AudioUnitPluginWindowCocoa (AudioUnitPluginInstance& p, bool createGenericViewIfNeeded) + : AudioProcessorEditor (&p), + plugin (p), waitingForViewCallback (false) + { + addAndMakeVisible (wrapper); + + #if JUCE_SUPPORTS_AUv3 + viewControllerCallback = + CreateObjCBlock (this, &AudioUnitPluginWindowCocoa::requestViewControllerCallback); + #endif + + setOpaque (true); + setVisible (true); + setSize (100, 100); + + createView (createGenericViewIfNeeded); + } + + ~AudioUnitPluginWindowCocoa() + { + if (wrapper.getView() != nil) + { + wrapper.setVisible (false); + removeChildComponent (&wrapper); + wrapper.setView (nil); + plugin.editorBeingDeleted (this); + } + } + + #if JUCE_SUPPORTS_AUv3 + void embedViewController (JUCE_IOS_MAC_VIEW* pluginView, const CGSize& size) + { + wrapper.setView (pluginView); + waitingForViewCallback = false; + + #if JUCE_MAC + ignoreUnused (size); + if (pluginView != nil) + wrapper.resizeToFitView(); + #else + [pluginView setBounds: CGRectMake (0.f, 0.f, static_cast (size.width), static_cast (size.height))]; + wrapper.setSize (static_cast (size.width), static_cast (size.height)); + #endif + } + #endif + + bool isValid() const { return wrapper.getView() != nil || waitingForViewCallback; } + + void paint (Graphics& g) override + { + g.fillAll (Colours::white); + } + + void resized() override + { + wrapper.setSize (getWidth(), getHeight()); + } + + void childBoundsChanged (Component*) override + { + setSize (wrapper.getWidth(), wrapper.getHeight()); + } + +private: + + AudioUnitPluginInstance& plugin; + AutoResizingNSViewComponent wrapper; + + #if JUCE_SUPPORTS_AUv3 + typedef void (^ViewControllerCallbackBlock)(AUViewControllerBase *); + ObjCBlock viewControllerCallback; + #endif + + bool waitingForViewCallback; + + bool createView (const bool createGenericViewIfNeeded) + { + JUCE_IOS_MAC_VIEW* pluginView = nil; + UInt32 dataSize = 0; + Boolean isWritable = false; + + #if JUCE_MAC + if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, + 0, &dataSize, &isWritable) == noErr + && dataSize != 0 + && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, + 0, &dataSize, &isWritable) == noErr) + { + HeapBlock info; + info.calloc (dataSize, 1); + + if (AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, + 0, info, &dataSize) == noErr) + { + NSString* viewClassName = (NSString*) (info->mCocoaAUViewClass[0]); + CFStringRef path = CFURLCopyPath (info->mCocoaAUViewBundleLocation); + NSString* unescapedPath = (NSString*) CFURLCreateStringByReplacingPercentEscapes (0, path, CFSTR ("")); + CFRelease (path); + NSBundle* viewBundle = [NSBundle bundleWithPath: [unescapedPath autorelease]]; + Class viewClass = [viewBundle classNamed: viewClassName]; + + if ([viewClass conformsToProtocol: @protocol (AUCocoaUIBase)] + && [viewClass instancesRespondToSelector: @selector (interfaceVersion)] + && [viewClass instancesRespondToSelector: @selector (uiViewForAudioUnit: withSize:)]) + { + id factory = [[[viewClass alloc] init] autorelease]; + pluginView = [factory uiViewForAudioUnit: plugin.audioUnit + withSize: NSMakeSize (getWidth(), getHeight())]; + } + + for (int i = (dataSize - sizeof (CFURLRef)) / sizeof (CFStringRef); --i >= 0;) + CFRelease (info->mCocoaAUViewClass[i]); + + CFRelease (info->mCocoaAUViewBundleLocation); + } + } + #endif + + dataSize = 0; + isWritable = false; + + #if JUCE_SUPPORTS_AUv3 + if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, + 0, &dataSize, &isWritable) == noErr + && dataSize == sizeof (ViewControllerCallbackBlock) + && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, + 0, &dataSize, &isWritable) == noErr) + { + waitingForViewCallback = true; + ViewControllerCallbackBlock callback; + callback = viewControllerCallback; + + ViewControllerCallbackBlock* info = &callback; + + if (noErr == AudioUnitSetProperty (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, info, dataSize)) + return true; + + waitingForViewCallback = false; + } + #endif + + #if JUCE_MAC + if (createGenericViewIfNeeded && (pluginView == nil)) + { + { + // This forces CoreAudio.component to be loaded, otherwise the AUGenericView will assert + AudioComponentDescription desc; + String name, version, manufacturer; + AudioUnitFormatHelpers::getComponentDescFromIdentifier ("AudioUnit:Output/auou,genr,appl", + desc, name, version, manufacturer); + } + + pluginView = [[AUGenericView alloc] initWithAudioUnit: plugin.audioUnit]; + } + #else + ignoreUnused (createGenericViewIfNeeded); + #endif + + wrapper.setView (pluginView); + + if (pluginView != nil) + wrapper.resizeToFitView(); + + return pluginView != nil; + } + + #if JUCE_SUPPORTS_AUv3 + void requestViewControllerCallback (AUViewControllerBase* controller) + { + auto nsSize = [controller preferredContentSize]; + auto viewSize = CGSizeMake (nsSize.width, nsSize.height); + + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + struct AsyncViewControllerCallback : public CallbackMessage + { + AudioUnitPluginWindowCocoa* owner; + JUCE_IOS_MAC_VIEW* controllerView; + CGSize size; + + AsyncViewControllerCallback (AudioUnitPluginWindowCocoa* plugInWindow, JUCE_IOS_MAC_VIEW* inView, + const CGSize& preferredSize) + : owner (plugInWindow), controllerView ([inView retain]), size (preferredSize) + {} + + void messageCallback() override + { + owner->embedViewController (controllerView, size); + [controllerView release]; + } + }; + + (new AsyncViewControllerCallback (this, [controller view], viewSize))->post(); + } + else + { + embedViewController ([controller view], viewSize); + } + } + #endif +}; + +#if JUCE_SUPPORT_CARBON + +//============================================================================== +class AudioUnitPluginWindowCarbon : public AudioProcessorEditor +{ +public: + AudioUnitPluginWindowCarbon (AudioUnitPluginInstance& p) + : AudioProcessorEditor (&p), + plugin (p), + audioComponent (nullptr), + viewComponent (nullptr) + { + addAndMakeVisible (innerWrapper = new InnerWrapperComponent (*this)); + + setOpaque (true); + setVisible (true); + setSize (400, 300); + + UInt32 propertySize; + if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_GetUIComponentList, + kAudioUnitScope_Global, 0, &propertySize, NULL) == noErr + && propertySize > 0) + { + HeapBlock views (propertySize / sizeof (AudioComponentDescription)); + + if (AudioUnitGetProperty (plugin.audioUnit, kAudioUnitProperty_GetUIComponentList, + kAudioUnitScope_Global, 0, &views[0], &propertySize) == noErr) + { + audioComponent = AudioComponentFindNext (nullptr, &views[0]); + } + } + } + + ~AudioUnitPluginWindowCarbon() + { + innerWrapper = nullptr; + + if (isValid()) + plugin.editorBeingDeleted (this); + } + + bool isValid() const noexcept { return audioComponent != nullptr; } + + //============================================================================== + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + void resized() override + { + if (innerWrapper != nullptr) + innerWrapper->setSize (getWidth(), getHeight()); + } + + //============================================================================== + bool keyStateChanged (bool) override { return false; } + bool keyPressed (const KeyPress&) override { return false; } + + //============================================================================== + AudioUnit getAudioUnit() const { return plugin.audioUnit; } + + AudioUnitCarbonView getViewComponent() + { + if (viewComponent == nullptr && audioComponent != nullptr) + AudioComponentInstanceNew (audioComponent, &viewComponent); + + return viewComponent; + } + + void closeViewComponent() + { + if (viewComponent != nullptr) + { + JUCE_AU_LOG ("Closing AU GUI: " + plugin.getName()); + + AudioComponentInstanceDispose (viewComponent); + viewComponent = nullptr; + } + } + +private: + //============================================================================== + AudioUnitPluginInstance& plugin; + AudioComponent audioComponent; + AudioUnitCarbonView viewComponent; + + //============================================================================== + class InnerWrapperComponent : public CarbonViewWrapperComponent + { + public: + InnerWrapperComponent (AudioUnitPluginWindowCarbon& w) : owner (w) {} + + ~InnerWrapperComponent() + { + deleteWindow(); + } + + HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) override + { + JUCE_AU_LOG ("Opening AU GUI: " + owner.plugin.getName()); + + AudioUnitCarbonView carbonView = owner.getViewComponent(); + + if (carbonView == 0) + return 0; + + Float32Point pos = { 0, 0 }; + Float32Point size = { 250, 200 }; + HIViewRef pluginView = 0; + + AudioUnitCarbonViewCreate (carbonView, owner.getAudioUnit(), windowRef, rootView, + &pos, &size, (ControlRef*) &pluginView); + + return pluginView; + } + + void removeView (HIViewRef) override + { + owner.closeViewComponent(); + } + + private: + AudioUnitPluginWindowCarbon& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InnerWrapperComponent) + }; + + friend class InnerWrapperComponent; + ScopedPointer innerWrapper; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginWindowCarbon) +}; + +#endif + +//============================================================================== +AudioProcessorEditor* AudioUnitPluginInstance::createEditor() +{ + ScopedPointer w (new AudioUnitPluginWindowCocoa (*this, false)); + + if (! static_cast (w.get())->isValid()) + w = nullptr; + + #if JUCE_SUPPORT_CARBON + if (w == nullptr) + { + w = new AudioUnitPluginWindowCarbon (*this); + + if (! static_cast (w.get())->isValid()) + w = nullptr; + } + #endif + + if (w == nullptr) + w = new AudioUnitPluginWindowCocoa (*this, true); // use AUGenericView as a fallback + + return w.release(); +} + + +//============================================================================== +//============================================================================== +AudioUnitPluginFormat::AudioUnitPluginFormat() +{ +} + +AudioUnitPluginFormat::~AudioUnitPluginFormat() +{ +} + +void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray& results, + const String& fileOrIdentifier) +{ + if (! fileMightContainThisPluginType (fileOrIdentifier)) + return; + + PluginDescription desc; + desc.fileOrIdentifier = fileOrIdentifier; + desc.uid = 0; + + if (MessageManager::getInstance()->isThisTheMessageThread() + && requiresUnblockedMessageThreadDuringCreation (desc)) + return; + + try + { + ScopedPointer createdInstance (createInstanceFromDescription (desc, 44100.0, 512)); + + if (AudioUnitPluginInstance* auInstance = dynamic_cast (createdInstance.get())) + results.add (new PluginDescription (auInstance->getPluginDescription())); + } + catch (...) + { + // crashed while loading... + } +} + +void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, + double rate, + int blockSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) +{ + using namespace AudioUnitFormatHelpers; + + if (fileMightContainThisPluginType (desc.fileOrIdentifier)) + { + + String pluginName, version, manufacturer; + AudioComponentDescription componentDesc; + AudioComponent auComponent; + String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description"); + + if ((! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) + && (! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer))) + { + callback (userData, nullptr, errMessage); + return; + } + + if ((auComponent = AudioComponentFindNext (0, &componentDesc)) == nullptr) + { + callback (userData, nullptr, errMessage); + return; + } + + if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr) + { + callback (userData, nullptr, errMessage); + return; + } + + struct AUAsyncInitializationCallback + { + #if JUCE_SUPPORTS_AUv3 + typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus); + #endif + + AUAsyncInitializationCallback (double inSampleRate, int inFramesPerBuffer, + void* inUserData, void (*inOriginalCallback) (void*, AudioPluginInstance*, const String&)) + : sampleRate (inSampleRate), framesPerBuffer (inFramesPerBuffer), + passUserData (inUserData), originalCallback (inOriginalCallback) + { + #if JUCE_SUPPORTS_AUv3 + block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion); + #endif + } + + #if JUCE_SUPPORTS_AUv3 + AUCompletionCallbackBlock getBlock() noexcept { return block; } + #endif + + void completion (AudioComponentInstance audioUnit, OSStatus err) + { + if (err == noErr) + { + ScopedPointer instance (new AudioUnitPluginInstance (audioUnit)); + + if (instance->initialise (sampleRate, framesPerBuffer)) + originalCallback (passUserData, instance.release(), StringRef()); + else + originalCallback (passUserData, nullptr, + NEEDS_TRANS ("Unable to initialise the AudioUnit plug-in")); + } + else + { + String errMsg = NEEDS_TRANS ("An OS error occurred during initialisation of the plug-in (XXX)"); + originalCallback (passUserData, nullptr, errMsg.replace ("XXX", String (err))); + } + + delete this; + } + + double sampleRate; + int framesPerBuffer; + void* passUserData; + void (*originalCallback) (void*, AudioPluginInstance*, const String&); + + #if JUCE_SUPPORTS_AUv3 + ObjCBlock block; + #endif + }; + + AUAsyncInitializationCallback* callbackBlock + = new AUAsyncInitializationCallback (rate, blockSize, userData, callback); + + #if JUCE_SUPPORTS_AUv3 + //============================================================================== + bool isAUv3 = ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + + if (isAUv3) + { + AudioComponentInstantiate (auComponent, kAudioComponentInstantiation_LoadOutOfProcess, + callbackBlock->getBlock()); + + return; + } + #endif // JUCE_SUPPORTS_AUv3 + + AudioComponentInstance audioUnit; + OSStatus err = AudioComponentInstanceNew(auComponent, &audioUnit); + callbackBlock->completion (err != noErr ? nullptr : audioUnit, err); + } + else + { + callback (userData, nullptr, NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in")); + } +} + +bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const noexcept +{ + #if JUCE_SUPPORTS_AUv3 + String pluginName, version, manufacturer; + AudioComponentDescription componentDesc; + + if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, + pluginName, version, manufacturer) + || AudioUnitFormatHelpers::getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, + pluginName, version, manufacturer)) + { + if (AudioComponent auComp = AudioComponentFindNext (0, &componentDesc)) + if (AudioComponentGetDescription (auComp, &componentDesc) == noErr) + return ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + } + #else + ignoreUnused (desc); + #endif + + return false; +} + +StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath&, bool /*recursive*/, bool allowPluginsWhichRequireAsynchronousInstantiation) +{ + StringArray result; + AudioComponent comp = nullptr; + + for (;;) + { + AudioComponentDescription desc; + zerostruct (desc); + + comp = AudioComponentFindNext (comp, &desc); + + if (comp == nullptr) + break; + + if (AudioComponentGetDescription (comp, &desc) != noErr) + continue; + + if (desc.componentType == kAudioUnitType_MusicDevice + || desc.componentType == kAudioUnitType_MusicEffect + || desc.componentType == kAudioUnitType_Effect + || desc.componentType == kAudioUnitType_Generator + || desc.componentType == kAudioUnitType_Panner + || desc.componentType == kAudioUnitType_Mixer) + { + ignoreUnused (allowPluginsWhichRequireAsynchronousInstantiation); + + #if JUCE_SUPPORTS_AUv3 + bool isAUv3 = ((desc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + + if (allowPluginsWhichRequireAsynchronousInstantiation || ! isAUv3) + #endif + result.add (AudioUnitFormatHelpers::createPluginIdentifier (desc)); + } + } + + return result; +} + +bool AudioUnitPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) +{ + AudioComponentDescription desc; + + String name, version, manufacturer; + if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer)) + return AudioComponentFindNext (nullptr, &desc) != nullptr; + + const File f (File::createFileWithoutCheckingPath (fileOrIdentifier)); + + return (f.hasFileExtension (".component") || f.hasFileExtension (".appex")) + && f.isDirectory(); +} + +String AudioUnitPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) +{ + AudioComponentDescription desc; + String name, version, manufacturer; + AudioUnitFormatHelpers::getComponentDescFromIdentifier (fileOrIdentifier, desc, name, version, manufacturer); + + if (name.isEmpty()) + name = fileOrIdentifier; + + return name; +} + +bool AudioUnitPluginFormat::pluginNeedsRescanning (const PluginDescription& desc) +{ + AudioComponentDescription newDesc; + String name, version, manufacturer; + + return ! (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, newDesc, + name, version, manufacturer) + && version == desc.version); +} + +bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc) +{ + if (desc.fileOrIdentifier.startsWithIgnoreCase (AudioUnitFormatHelpers::auIdentifierPrefix)) + return fileMightContainThisPluginType (desc.fileOrIdentifier); + + return File (desc.fileOrIdentifier).exists(); +} + +FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() +{ + return {}; +} + +#undef JUCE_AU_LOG + +} // namespace juce + +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp new file mode 100644 index 000000000..85f307ca2 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -0,0 +1,718 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 JUCE_PLUGINHOST_LADSPA && JUCE_LINUX + +#include "ladspa.h" + +namespace juce +{ + +static int shellLADSPAUIDToCreate = 0; +static int insideLADSPACallback = 0; + +#define JUCE_LADSPA_LOGGING 1 + +#if JUCE_LADSPA_LOGGING + #define JUCE_LADSPA_LOG(x) Logger::writeToLog (x); +#else + #define JUCE_LADSPA_LOG(x) +#endif + +//============================================================================== +class LADSPAModuleHandle : public ReferenceCountedObject +{ +public: + LADSPAModuleHandle (const File& f) + : file (f), moduleMain (nullptr) + { + getActiveModules().add (this); + } + + ~LADSPAModuleHandle() + { + getActiveModules().removeFirstMatchingValue (this); + close(); + } + + typedef ReferenceCountedObjectPtr Ptr; + + static Array & getActiveModules() + { + static Array activeModules; + return activeModules; + } + + static LADSPAModuleHandle* findOrCreateModule (const File& file) + { + for (int i = getActiveModules().size(); --i >= 0;) + { + LADSPAModuleHandle* const module = getActiveModules().getUnchecked(i); + + if (module->file == file) + return module; + } + + ++insideLADSPACallback; + shellLADSPAUIDToCreate = 0; + + JUCE_LADSPA_LOG ("Loading LADSPA module: " + file.getFullPathName()); + + ScopedPointer m (new LADSPAModuleHandle (file)); + + if (! m->open()) + m = nullptr; + + --insideLADSPACallback; + + return m.release(); + } + + File file; + LADSPA_Descriptor_Function moduleMain; + +private: + DynamicLibrary module; + + bool open() + { + module.open (file.getFullPathName()); + moduleMain = (LADSPA_Descriptor_Function) module.getFunction ("ladspa_descriptor"); + return moduleMain != nullptr; + } + + void close() + { + module.close(); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAModuleHandle) +}; + + +//============================================================================== +class LADSPAPluginInstance : public AudioPluginInstance +{ +public: + LADSPAPluginInstance (const LADSPAModuleHandle::Ptr& m) + : module (m), plugin (nullptr), handle (nullptr), + initialised (false), tempBuffer (1, 1) + { + ++insideLADSPACallback; + + name = module->file.getFileNameWithoutExtension(); + + JUCE_LADSPA_LOG ("Creating LADSPA instance: " + name); + + if (module->moduleMain != nullptr) + { + plugin = module->moduleMain (shellLADSPAUIDToCreate); + + if (plugin == nullptr) + { + JUCE_LADSPA_LOG ("Cannot find any valid descriptor in shared library"); + --insideLADSPACallback; + return; + } + } + else + { + JUCE_LADSPA_LOG ("Cannot find any valid plugin in shared library"); + --insideLADSPACallback; + return; + } + + const double sampleRate = getSampleRate() > 0 ? getSampleRate() : 44100.0; + + handle = plugin->instantiate (plugin, (uint32) sampleRate); + + --insideLADSPACallback; + } + + ~LADSPAPluginInstance() + { + const ScopedLock sl (lock); + + jassert (insideLADSPACallback == 0); + + if (handle != nullptr && plugin != nullptr && plugin->cleanup != nullptr) + plugin->cleanup (handle); + + initialised = false; + module = nullptr; + plugin = nullptr; + handle = nullptr; + } + + void initialise (double initialSampleRate, int initialBlockSize) + { + setPlayConfigDetails (inputs.size(), outputs.size(), initialSampleRate, initialBlockSize); + + if (initialised || plugin == nullptr || handle == nullptr) + return; + + JUCE_LADSPA_LOG ("Initialising LADSPA: " + name); + + initialised = true; + + inputs.clear(); + outputs.clear(); + parameters.clear(); + + for (unsigned int i = 0; i < plugin->PortCount; ++i) + { + const LADSPA_PortDescriptor portDesc = plugin->PortDescriptors[i]; + + if ((portDesc & LADSPA_PORT_CONTROL) != 0) + parameters.add (i); + + if ((portDesc & LADSPA_PORT_AUDIO) != 0) + { + if ((portDesc & LADSPA_PORT_INPUT) != 0) inputs.add (i); + if ((portDesc & LADSPA_PORT_OUTPUT) != 0) outputs.add (i); + } + } + + parameterValues.calloc (parameters.size()); + + for (int i = 0; i < parameters.size(); ++i) + plugin->connect_port (handle, parameters[i], &(parameterValues[i].scaled)); + + setPlayConfigDetails (inputs.size(), outputs.size(), initialSampleRate, initialBlockSize); + + setCurrentProgram (0); + setLatencySamples (0); + + // Some plugins crash if this doesn't happen: + if (plugin->activate != nullptr) plugin->activate (handle); + if (plugin->deactivate != nullptr) plugin->deactivate (handle); + } + + //============================================================================== + // AudioPluginInstance methods: + + void fillInPluginDescription (PluginDescription& desc) const + { + desc.name = getName(); + desc.fileOrIdentifier = module->file.getFullPathName(); + desc.uid = getUID(); + desc.lastFileModTime = module->file.getLastModificationTime(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); + desc.pluginFormatName = "LADSPA"; + desc.category = getCategory(); + desc.manufacturerName = plugin != nullptr ? String (plugin->Maker) : String(); + desc.version = getVersion(); + desc.numInputChannels = getTotalNumInputChannels(); + desc.numOutputChannels = getTotalNumOutputChannels(); + desc.isInstrument = false; + } + + const String getName() const + { + if (plugin != nullptr && plugin->Label != nullptr) + return plugin->Label; + + return name; + } + + int getUID() const + { + if (plugin != nullptr && plugin->UniqueID != 0) + return (int) plugin->UniqueID; + + return module->file.hashCode(); + } + + String getVersion() const { return LADSPA_VERSION; } + String getCategory() const { return "Effect"; } + + bool acceptsMidi() const { return false; } + bool producesMidi() const { return false; } + + double getTailLengthSeconds() const { return 0.0; } + + //============================================================================== + void prepareToPlay (double newSampleRate, int samplesPerBlockExpected) + { + setLatencySamples (0); + + initialise (newSampleRate, samplesPerBlockExpected); + + if (initialised) + { + tempBuffer.setSize (jmax (1, outputs.size()), samplesPerBlockExpected); + + // dodgy hack to force some plugins to initialise the sample rate.. + if (getNumParameters() > 0) + { + const float old = getParameter (0); + setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); + setParameter (0, old); + } + + if (plugin->activate != nullptr) + plugin->activate (handle); + } + } + + void releaseResources() + { + if (handle != nullptr && plugin->deactivate != nullptr) + plugin->deactivate (handle); + + tempBuffer.setSize (1, 1); + } + + void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) + { + const int numSamples = buffer.getNumSamples(); + + if (initialised && plugin != nullptr && handle != nullptr) + { + for (int i = 0; i < inputs.size(); ++i) + plugin->connect_port (handle, inputs[i], + i < buffer.getNumChannels() ? buffer.getWritePointer (i) : nullptr); + + if (plugin->run != nullptr) + { + for (int i = 0; i < outputs.size(); ++i) + plugin->connect_port (handle, outputs.getUnchecked(i), + i < buffer.getNumChannels() ? buffer.getWritePointer (i) : nullptr); + + plugin->run (handle, numSamples); + return; + } + + if (plugin->run_adding != nullptr) + { + tempBuffer.setSize (outputs.size(), numSamples); + tempBuffer.clear(); + + for (int i = 0; i < outputs.size(); ++i) + plugin->connect_port (handle, outputs.getUnchecked(i), tempBuffer.getWritePointer (i)); + + plugin->run_adding (handle, numSamples); + + for (int i = 0; i < outputs.size(); ++i) + if (i < buffer.getNumChannels()) + buffer.copyFrom (i, 0, tempBuffer, i, 0, numSamples); + + return; + } + + jassertfalse; // no callback to use? + } + + for (int i = getTotalNumInputChannels(), e = getTotalNumOutputChannels(); i < e; ++i) + buffer.clear (i, 0, numSamples); + } + + bool isInputChannelStereoPair (int index) const { return isPositiveAndBelow (index, getTotalNumInputChannels()); } + bool isOutputChannelStereoPair (int index) const { return isPositiveAndBelow (index, getTotalNumOutputChannels()); } + + const String getInputChannelName (const int index) const + { + if (isPositiveAndBelow (index, getTotalNumInputChannels())) + return String (plugin->PortNames [inputs [index]]).trim(); + + return {}; + } + + const String getOutputChannelName (const int index) const + { + if (isPositiveAndBelow (index, getTotalNumInputChannels())) + return String (plugin->PortNames [outputs [index]]).trim(); + + return {}; + } + + //============================================================================== + int getNumParameters() { return handle != nullptr ? parameters.size() : 0; } + + bool isParameterAutomatable (int index) const + { + return plugin != nullptr + && (plugin->PortDescriptors [parameters[index]] & LADSPA_PORT_INPUT) != 0; + } + + float getParameter (int index) + { + if (plugin != nullptr && isPositiveAndBelow (index, parameters.size())) + { + const ScopedLock sl (lock); + return parameterValues[index].unscaled; + } + + return 0.0f; + } + + void setParameter (int index, float newValue) + { + if (plugin != nullptr && isPositiveAndBelow (index, parameters.size())) + { + const ScopedLock sl (lock); + + ParameterValue& p = parameterValues[index]; + + if (p.unscaled != newValue) + p = ParameterValue (getNewParamScaled (plugin->PortRangeHints [parameters[index]], newValue), newValue); + } + } + + const String getParameterName (int index) + { + if (plugin != nullptr) + { + jassert (isPositiveAndBelow (index, parameters.size())); + return String (plugin->PortNames [parameters [index]]).trim(); + } + + return {}; + } + + const String getParameterText (int index) + { + if (plugin != nullptr) + { + jassert (index >= 0 && index < parameters.size()); + + const LADSPA_PortRangeHint& hint = plugin->PortRangeHints [parameters [index]]; + + if (LADSPA_IS_HINT_INTEGER (hint.HintDescriptor)) + return String ((int) parameterValues[index].scaled); + + return String (parameterValues[index].scaled, 4); + } + + return {}; + } + + //============================================================================== + int getNumPrograms() { return 0; } + int getCurrentProgram() { return 0; } + + void setCurrentProgram (int newIndex) + { + if (plugin != nullptr) + for (int i = 0; i < parameters.size(); ++i) + parameterValues[i] = getParamValue (plugin->PortRangeHints [parameters[i]]); + } + + const String getProgramName (int index) + { + // XXX + return {}; + } + + void changeProgramName (int index, const String& newName) + { + // XXX + } + + //============================================================================== + void getStateInformation (MemoryBlock& destData) + { + destData.setSize (sizeof (float) * getNumParameters()); + destData.fillWith (0); + + float* const p = (float*) ((char*) destData.getData()); + for (int i = 0; i < getNumParameters(); ++i) + p[i] = getParameter(i); + } + + void getCurrentProgramStateInformation (MemoryBlock& destData) + { + getStateInformation (destData); + } + + void setStateInformation (const void* data, int sizeInBytes) + { + const float* p = static_cast (data); + + for (int i = 0; i < getNumParameters(); ++i) + setParameter (i, p[i]); + } + + void setCurrentProgramStateInformation (const void* data, int sizeInBytes) + { + setStateInformation (data, sizeInBytes); + } + + bool hasEditor() const + { + return false; + } + + AudioProcessorEditor* createEditor() + { + return nullptr; + } + + bool isValid() const + { + return handle != nullptr; + } + + LADSPAModuleHandle::Ptr module; + const LADSPA_Descriptor* plugin; + +private: + LADSPA_Handle handle; + String name; + CriticalSection lock; + bool initialised; + AudioSampleBuffer tempBuffer; + Array inputs, outputs, parameters; + + struct ParameterValue + { + inline ParameterValue() noexcept : scaled (0), unscaled (0) {} + inline ParameterValue (float s, float u) noexcept : scaled (s), unscaled (u) {} + + float scaled, unscaled; + }; + + HeapBlock parameterValues; + + //============================================================================== + static float scaledValue (float low, float high, float alpha, bool useLog) noexcept + { + if (useLog && low > 0 && high > 0) + return expf (logf (low) * (1.0f - alpha) + logf (high) * alpha); + + return low + (high - low) * alpha; + } + + static float toIntIfNecessary (const LADSPA_PortRangeHintDescriptor& desc, float value) + { + return LADSPA_IS_HINT_INTEGER (desc) ? ((float) (int) value) : value; + } + + float getNewParamScaled (const LADSPA_PortRangeHint& hint, float newValue) const + { + const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; + + if (LADSPA_IS_HINT_TOGGLED (desc)) + return (newValue < 0.5f) ? 0.0f : 1.0f; + + const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f; + const float lower = hint.LowerBound * scale; + const float upper = hint.UpperBound * scale; + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) + return toIntIfNecessary (desc, scaledValue (lower, upper, newValue, LADSPA_IS_HINT_LOGARITHMIC (desc))); + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) return toIntIfNecessary (desc, newValue); + if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc)) return toIntIfNecessary (desc, newValue * upper); + + return 0.0f; + } + + ParameterValue getParamValue (const LADSPA_PortRangeHint& hint) const + { + const LADSPA_PortRangeHintDescriptor& desc = hint.HintDescriptor; + + if (LADSPA_IS_HINT_HAS_DEFAULT (desc)) + { + if (LADSPA_IS_HINT_DEFAULT_0 (desc)) return ParameterValue(); + if (LADSPA_IS_HINT_DEFAULT_1 (desc)) return ParameterValue (1.0f, 1.0f); + if (LADSPA_IS_HINT_DEFAULT_100 (desc)) return ParameterValue (100.0f, 0.5f); + if (LADSPA_IS_HINT_DEFAULT_440 (desc)) return ParameterValue (440.0f, 0.5f); + + const float scale = LADSPA_IS_HINT_SAMPLE_RATE (desc) ? (float) getSampleRate() : 1.0f; + const float lower = hint.LowerBound * scale; + const float upper = hint.UpperBound * scale; + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc) && LADSPA_IS_HINT_DEFAULT_MINIMUM (desc)) return ParameterValue (lower, 0.0f); + if (LADSPA_IS_HINT_BOUNDED_ABOVE (desc) && LADSPA_IS_HINT_DEFAULT_MAXIMUM (desc)) return ParameterValue (upper, 1.0f); + + if (LADSPA_IS_HINT_BOUNDED_BELOW (desc)) + { + const bool useLog = LADSPA_IS_HINT_LOGARITHMIC (desc); + + if (LADSPA_IS_HINT_DEFAULT_LOW (desc)) return ParameterValue (scaledValue (lower, upper, 0.25f, useLog), 0.25f); + if (LADSPA_IS_HINT_DEFAULT_MIDDLE (desc)) return ParameterValue (scaledValue (lower, upper, 0.50f, useLog), 0.50f); + if (LADSPA_IS_HINT_DEFAULT_HIGH (desc)) return ParameterValue (scaledValue (lower, upper, 0.75f, useLog), 0.75f); + } + } + + return ParameterValue(); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginInstance) +}; + + +//============================================================================== +//============================================================================== +LADSPAPluginFormat::LADSPAPluginFormat() {} +LADSPAPluginFormat::~LADSPAPluginFormat() {} + +void LADSPAPluginFormat::findAllTypesForFile (OwnedArray & results, + const String& fileOrIdentifier) +{ + if (! fileMightContainThisPluginType (fileOrIdentifier)) + return; + + PluginDescription desc; + desc.fileOrIdentifier = fileOrIdentifier; + desc.uid = 0; + + ScopedPointer instance (dynamic_cast (createInstanceFromDescription (desc, 44100.0, 512))); + + if (instance == nullptr || ! instance->isValid()) + return; + + instance->initialise (44100.0, 512); + + instance->fillInPluginDescription (desc); + + if (instance->module->moduleMain != nullptr) + { + for (int uid = 0;; ++uid) + { + if (const LADSPA_Descriptor* plugin = instance->module->moduleMain (uid)) + { + desc.uid = uid; + desc.name = plugin->Name != nullptr ? plugin->Name : "Unknown"; + + if (! arrayContainsPlugin (results, desc)) + results.add (new PluginDescription (desc)); + } + else + { + break; + } + } + } +} + +void LADSPAPluginFormat::createPluginInstance (const PluginDescription& desc, + double sampleRate, int blockSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) +{ + ScopedPointer result; + + + if (fileMightContainThisPluginType (desc.fileOrIdentifier)) + { + File file (desc.fileOrIdentifier); + + const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); + file.getParentDirectory().setAsCurrentWorkingDirectory(); + + const LADSPAModuleHandle::Ptr module (LADSPAModuleHandle::findOrCreateModule (file)); + + if (module != nullptr) + { + shellLADSPAUIDToCreate = desc.uid; + + result = new LADSPAPluginInstance (module); + + if (result->plugin != nullptr && result->isValid()) + result->initialise (sampleRate, blockSize); + else + result = nullptr; + } + + previousWorkingDirectory.setAsCurrentWorkingDirectory(); + } + + String errorMsg; + + if (result == nullptr) + errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "LADSPA"); + + callback (userData, result.release(), errorMsg); +} + +bool LADSPAPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept +{ + return false; +} + +bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) +{ + const File f (File::createFileWithoutCheckingPath (fileOrIdentifier)); + return f.existsAsFile() && f.hasFileExtension (".so"); +} + +String LADSPAPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) +{ + return fileOrIdentifier; +} + +bool LADSPAPluginFormat::pluginNeedsRescanning (const PluginDescription& desc) +{ + return File (desc.fileOrIdentifier).getLastModificationTime() != desc.lastFileModTime; +} + +bool LADSPAPluginFormat::doesPluginStillExist (const PluginDescription& desc) +{ + return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); +} + +StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) +{ + StringArray results; + + for (int j = 0; j < directoriesToSearch.getNumPaths(); ++j) + recursiveFileSearch (results, directoriesToSearch[j], recursive); + + return results; +} + +void LADSPAPluginFormat::recursiveFileSearch (StringArray& results, const File& dir, const bool recursive) +{ + DirectoryIterator iter (dir, false, "*", File::findFilesAndDirectories); + + while (iter.next()) + { + const File f (iter.getFile()); + bool isPlugin = false; + + if (fileMightContainThisPluginType (f.getFullPathName())) + { + isPlugin = true; + results.add (f.getFullPathName()); + } + + if (recursive && (! isPlugin) && f.isDirectory()) + recursiveFileSearch (results, f, true); + } +} + +FileSearchPath LADSPAPluginFormat::getDefaultLocationsToSearch() +{ + return FileSearchPath (SystemStats::getEnvironmentVariable ("LADSPA_PATH", + "/usr/lib/ladspa;/usr/local/lib/ladspa;~/.ladspa") + .replace (":", ";")); +} + +} // namespace juce + +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h new file mode 100644 index 000000000..7002c5181 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h @@ -0,0 +1,70 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +#if (JUCE_PLUGINHOST_LADSPA && JUCE_LINUX) || DOXYGEN + +//============================================================================== +/** + Implements a plugin format manager for LADSPA plugins. +*/ +class JUCE_API LADSPAPluginFormat : public AudioPluginFormat +{ +public: + LADSPAPluginFormat(); + ~LADSPAPluginFormat(); + + //============================================================================== + String getName() const override { return "LADSPA"; } + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; + bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; + String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; + bool pluginNeedsRescanning (const PluginDescription&) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; + bool doesPluginStillExist (const PluginDescription&) override; + FileSearchPath getDefaultLocationsToSearch() override; + bool canScanForPlugins() const override { return true; } + +private: + //============================================================================== + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + +private: + void recursiveFileSearch (StringArray&, const File&, bool recursive); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginFormat) +}; + + +#endif + +} diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h new file mode 100644 index 000000000..7ac3608ae --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -0,0 +1,633 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +#define JUCE_DECLARE_VST3_COM_REF_METHODS \ + Steinberg::uint32 PLUGIN_API addRef() override { return (Steinberg::uint32) ++refCount; } \ + Steinberg::uint32 PLUGIN_API release() override { const int r = --refCount; if (r == 0) delete this; return (Steinberg::uint32) r; } + +#define JUCE_DECLARE_VST3_COM_QUERY_METHODS \ + Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID, void** obj) override \ + { \ + jassertfalse; \ + *obj = nullptr; \ + return Steinberg::kNotImplemented; \ + } + +static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexcept +{ + return std::memcmp (a, b, sizeof (Steinberg::TUID)) == 0; +} + +#define TEST_FOR_AND_RETURN_IF_VALID(iidToTest, ClassType) \ + if (doUIDsMatch (iidToTest, ClassType::iid)) \ + { \ + addRef(); \ + *obj = dynamic_cast (this); \ + return Steinberg::kResultOk; \ + } + +#define TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(iidToTest, CommonClassType, SourceClassType) \ + if (doUIDsMatch (iidToTest, CommonClassType::iid)) \ + { \ + addRef(); \ + *obj = (CommonClassType*) static_cast (this); \ + return Steinberg::kResultOk; \ + } + +//============================================================================== +inline juce::String toString (const Steinberg::char8* string) noexcept { return juce::String (string); } +inline juce::String toString (const Steinberg::char16* string) noexcept { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); } + +// NB: The casts are handled by a Steinberg::UString operator +inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast (string)); } +inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast (string)); } + +inline void toString128 (Steinberg::Vst::String128 result, const char* source) +{ + Steinberg::UString (result, 128).fromAscii (source); +} + +inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) +{ + Steinberg::UString (result, 128).fromAscii (source.toUTF8()); +} + +inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept +{ + return reinterpret_cast (source.toUTF16().getAddress()); +} + +#if JUCE_WINDOWS + static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeHWND; +#else + static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeNSView; +#endif + + +//============================================================================== +static inline Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg::Vst::IAudioProcessor* processor, + bool isInput, int busIndex) +{ + Steinberg::Vst::SpeakerArrangement arrangement = Steinberg::Vst::SpeakerArr::kEmpty; + + if (processor != nullptr) + processor->getBusArrangement (isInput ? Steinberg::Vst::kInput : Steinberg::Vst::kOutput, + (Steinberg::int32) busIndex, arrangement); + + return arrangement; +} + +/** For the sake of simplicity, there can only be 1 arrangement type per channel count. + i.e.: 4 channels == k31Cine OR k40Cine +*/ +static inline Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numChannels) noexcept +{ + using namespace Steinberg::Vst::SpeakerArr; + + switch (numChannels) + { + case 0: return kEmpty; + case 1: return kMono; + case 2: return kStereo; + case 3: return k30Cine; + case 4: return k31Cine; + case 5: return k50; + case 6: return k51; + case 7: return k61Cine; + case 8: return k71CineFullFront; + case 9: return k90; + case 10: return k91; + case 11: return k101; + case 12: return k111; + case 13: return k130; + case 14: return k131; + case 24: return (Steinberg::Vst::SpeakerArrangement) 1929904127; // k222 + default: break; + } + + jassert (numChannels >= 0); + + juce::BigInteger bi; + bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true); + return (Steinberg::Vst::SpeakerArrangement) bi.toInt64(); +} + +static inline Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept +{ + using namespace Steinberg::Vst; + + switch (type) + { + case AudioChannelSet::left: return kSpeakerL; + case AudioChannelSet::right: return kSpeakerR; + case AudioChannelSet::centre: return (set == AudioChannelSet::mono() ? kSpeakerM : kSpeakerC); + + case AudioChannelSet::LFE: return kSpeakerLfe; + case AudioChannelSet::leftSurround: return kSpeakerLs; + case AudioChannelSet::rightSurround: return kSpeakerRs; + case AudioChannelSet::leftCentre: return kSpeakerLc; + case AudioChannelSet::rightCentre: return kSpeakerRc; + case AudioChannelSet::centreSurround: return kSpeakerCs; + case AudioChannelSet::leftSurroundSide: return (1 << 26); /* kSpeakerLcs */ + case AudioChannelSet::rightSurroundSide: return (1 << 27); /* kSpeakerRcs */ + case AudioChannelSet::topMiddle: return (1 << 11); /* kSpeakerTm */ + case AudioChannelSet::topFrontLeft: return kSpeakerTfl; + case AudioChannelSet::topFrontCentre: return kSpeakerTfc; + case AudioChannelSet::topFrontRight: return kSpeakerTfr; + case AudioChannelSet::topRearLeft: return kSpeakerTrl; + case AudioChannelSet::topRearCentre: return kSpeakerTrc; + case AudioChannelSet::topRearRight: return kSpeakerTrr; + case AudioChannelSet::LFE2: return kSpeakerLfe2; + case AudioChannelSet::leftSurroundRear: return kSpeakerSl; + case AudioChannelSet::rightSurroundRear: return kSpeakerSr; + case AudioChannelSet::wideLeft: return kSpeakerPl; + case AudioChannelSet::wideRight: return kSpeakerPr; + case AudioChannelSet::ambisonicW: return (1 << 20); /* kSpeakerW */ + case AudioChannelSet::ambisonicX: return (1 << 21); /* kSpeakerX */ + case AudioChannelSet::ambisonicY: return (1 << 22); /* kSpeakerY */ + case AudioChannelSet::ambisonicZ: return (1 << 23); /* kSpeakerZ */ + case AudioChannelSet::topSideLeft: return (1 << 24); /* kSpeakerTsl */ + case AudioChannelSet::topSideRight: return (1 << 25); /* kSpeakerTsr */ + + case AudioChannelSet::discreteChannel0: return kSpeakerM; + default: + break; + } + + + switch (static_cast (type)) + { + case (int) AudioChannelSet::discreteChannel0 + 3: return (1 << 28); /* kSpeakerBfl */ + case (int) AudioChannelSet::discreteChannel0 + 4: return (1 << 29); /* kSpeakerBfc */ + case (int) AudioChannelSet::discreteChannel0 + 5: return (1 << 30); /* kSpeakerBfr */ + default: + break; + } + + auto channelIndex = static_cast (type) - (static_cast (AudioChannelSet::discreteChannel0) + 6ull); + return (1ull << (channelIndex + 33ull /* last speaker in vst layout + 1 */)); +} + +static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept +{ + using namespace Steinberg::Vst; + + switch (type) + { + case kSpeakerL: return AudioChannelSet::left; + case kSpeakerR: return AudioChannelSet::right; + case kSpeakerC: return AudioChannelSet::centre; + case kSpeakerLfe: return AudioChannelSet::LFE; + case kSpeakerLs: return AudioChannelSet::leftSurround; + case kSpeakerRs: return AudioChannelSet::rightSurround; + case kSpeakerLc: return AudioChannelSet::leftCentre; + case kSpeakerRc: return AudioChannelSet::rightCentre; + case kSpeakerCs: return AudioChannelSet::centreSurround; + case kSpeakerSl: return AudioChannelSet::leftSurroundRear; + case kSpeakerSr: return AudioChannelSet::rightSurroundRear; + case (1 << 11): return AudioChannelSet::topMiddle; /* kSpeakerTm */ + case kSpeakerTfl: return AudioChannelSet::topFrontLeft; + case kSpeakerTfc: return AudioChannelSet::topFrontCentre; + case kSpeakerTfr: return AudioChannelSet::topFrontRight; + case kSpeakerTrl: return AudioChannelSet::topRearLeft; + case kSpeakerTrc: return AudioChannelSet::topRearCentre; + case kSpeakerTrr: return AudioChannelSet::topRearRight; + case kSpeakerLfe2: return AudioChannelSet::LFE2; + case (1 << 19): return ((arr & kSpeakerC) != 0 ? AudioChannelSet::discreteChannel0 : AudioChannelSet::centre); + case (1 << 20): return AudioChannelSet::ambisonicW; /* kSpeakerW */ + case (1 << 21): return AudioChannelSet::ambisonicX; /* kSpeakerX */ + case (1 << 22): return AudioChannelSet::ambisonicY; /* kSpeakerY */ + case (1 << 23): return AudioChannelSet::ambisonicZ; /* kSpeakerZ */ + case (1 << 24): return AudioChannelSet::topSideLeft; /* kSpeakerTsl */ + case (1 << 25): return AudioChannelSet::topSideRight; /* kSpeakerTsr */ + case (1 << 26): return AudioChannelSet::leftSurroundSide; /* kSpeakerLcs */ + case (1 << 27): return AudioChannelSet::rightSurroundSide; /* kSpeakerRcs */ + case (1 << 28): return static_cast ((int)AudioChannelSet::discreteChannel0 + 3); /* kSpeakerBfl */ + case (1 << 29): return static_cast ((int)AudioChannelSet::discreteChannel0 + 4); /* kSpeakerBfc */ + case (1 << 30): return static_cast ((int)AudioChannelSet::discreteChannel0 + 5); /* kSpeakerBfr */ + case kSpeakerPl: return AudioChannelSet::wideLeft; + case kSpeakerPr: return AudioChannelSet::wideRight; + default: break; + } + + auto channelType = BigInteger (static_cast (type)).findNextSetBit (0); + + // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum + jassert (channelType >= 33); + + return static_cast (static_cast (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33)); +} + +static inline Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept +{ + using namespace Steinberg::Vst::SpeakerArr; + + if (channels == AudioChannelSet::disabled()) return kEmpty; + else if (channels == AudioChannelSet::mono()) return kMono; + else if (channels == AudioChannelSet::stereo()) return kStereo; + else if (channels == AudioChannelSet::createLCR()) return k30Cine; + else if (channels == AudioChannelSet::createLRS()) return k30Music; + else if (channels == AudioChannelSet::createLCRS()) return k40Cine; + else if (channels == AudioChannelSet::create5point0()) return k50; + else if (channels == AudioChannelSet::create5point1()) return k51; + else if (channels == AudioChannelSet::create6point0()) return k60Cine; + else if (channels == AudioChannelSet::create6point1()) return k61Cine; + else if (channels == AudioChannelSet::create6point0Music()) return k60Music; + else if (channels == AudioChannelSet::create6point1Music()) return k61Music; + else if (channels == AudioChannelSet::create7point0()) return k70Music; + else if (channels == AudioChannelSet::create7point0SDDS()) return k70Cine; + else if (channels == AudioChannelSet::create7point1()) return k71CineSideFill; + else if (channels == AudioChannelSet::create7point1SDDS()) return k71Cine; + else if (channels == AudioChannelSet::ambisonic()) return kBFormat; + else if (channels == AudioChannelSet::quadraphonic()) return k40Music; + else if (channels == AudioChannelSet::create7point0point2()) return k71_2 & ~(Steinberg::Vst::kSpeakerLfe); + else if (channels == AudioChannelSet::create7point1point2()) return k71_2; + + Steinberg::Vst::SpeakerArrangement result = 0; + + Array types (channels.getChannelTypes()); + + for (int i = 0; i < types.size(); ++i) + result |= getSpeakerType (channels, types.getReference(i)); + + return result; +} + +static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept +{ + using namespace Steinberg::Vst::SpeakerArr; + + if (arr == kEmpty) return AudioChannelSet::disabled(); + else if (arr == kMono) return AudioChannelSet::mono(); + else if (arr == kStereo) return AudioChannelSet::stereo(); + else if (arr == k30Cine) return AudioChannelSet::createLCR(); + else if (arr == k30Music) return AudioChannelSet::createLRS(); + else if (arr == k40Cine) return AudioChannelSet::createLCRS(); + else if (arr == k50) return AudioChannelSet::create5point0(); + else if (arr == k51) return AudioChannelSet::create5point1(); + else if (arr == k60Cine) return AudioChannelSet::create6point0(); + else if (arr == k61Cine) return AudioChannelSet::create6point1(); + else if (arr == k60Music) return AudioChannelSet::create6point0Music(); + else if (arr == k61Music) return AudioChannelSet::create6point1Music(); + else if (arr == k70Music) return AudioChannelSet::create7point0(); + else if (arr == k70Cine) return AudioChannelSet::create7point0SDDS(); + else if (arr == k71CineSideFill) return AudioChannelSet::create7point1(); + else if (arr == k71Cine) return AudioChannelSet::create7point1SDDS(); + else if (arr == kBFormat) return AudioChannelSet::ambisonic(); + else if (arr == k40Music) return AudioChannelSet::quadraphonic(); + else if (arr == k71_2) return AudioChannelSet::create7point1point2(); + else if (arr == (k71_2 & ~(Steinberg::Vst::kSpeakerLfe))) return AudioChannelSet::create7point0point2(); + + AudioChannelSet result; + + BigInteger vstChannels (static_cast (arr)); + for (auto bit = vstChannels.findNextSetBit (0); bit != -1; bit = vstChannels.findNextSetBit (bit + 1)) + { + AudioChannelSet::ChannelType channelType = getChannelType (arr, 1ull << static_cast (bit)); + if (channelType != AudioChannelSet::unknown) + result.addChannel (channelType); + } + + // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum + jassert (result.size() == vstChannels.countNumberOfSetBits()); + + return result; +} + +//============================================================================== +template +class ComSmartPtr +{ +public: + ComSmartPtr() noexcept : source (nullptr) {} + ComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept : source (object) { if (source != nullptr && autoAddRef) source->addRef(); } + ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } + ~ComSmartPtr() { if (source != nullptr) source->release(); } + + operator ObjectType*() const noexcept { return source; } + ObjectType* get() const noexcept { return source; } + ObjectType& operator*() const noexcept { return *source; } + ObjectType* operator->() const noexcept { return source; } + + ComSmartPtr& operator= (const ComSmartPtr& other) { return operator= (other.source); } + + ComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf) + { + ComSmartPtr p (newObjectToTakePossessionOf); + std::swap (p.source, source); + return *this; + } + + bool operator== (ObjectType* const other) noexcept { return source == other; } + bool operator!= (ObjectType* const other) noexcept { return source != other; } + + bool loadFrom (Steinberg::FUnknown* o) + { + *this = nullptr; + return o != nullptr && o->queryInterface (ObjectType::iid, (void**) &source) == Steinberg::kResultOk; + } + + bool loadFrom (Steinberg::IPluginFactory* factory, const Steinberg::TUID& uuid) + { + jassert (factory != nullptr); + *this = nullptr; + return factory->createInstance (uuid, ObjectType::iid, (void**) &source) == Steinberg::kResultOk; + } + +private: + ObjectType* source; +}; + +//============================================================================== +class MidiEventList : public Steinberg::Vst::IEventList +{ +public: + MidiEventList() {} + virtual ~MidiEventList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + //============================================================================== + void clear() + { + events.clearQuick(); + } + + Steinberg::int32 PLUGIN_API getEventCount() override + { + return (Steinberg::int32) events.size(); + } + + // NB: This has to cope with out-of-range indexes from some plugins. + Steinberg::tresult PLUGIN_API getEvent (Steinberg::int32 index, Steinberg::Vst::Event& e) override + { + if (isPositiveAndBelow ((int) index, events.size())) + { + e = events.getReference ((int) index); + return Steinberg::kResultTrue; + } + + return Steinberg::kResultFalse; + } + + Steinberg::tresult PLUGIN_API addEvent (Steinberg::Vst::Event& e) override + { + events.add (e); + return Steinberg::kResultTrue; + } + + //============================================================================== + static void toMidiBuffer (MidiBuffer& result, Steinberg::Vst::IEventList& eventList) + { + const int32 numEvents = eventList.getEventCount(); + + for (Steinberg::int32 i = 0; i < numEvents; ++i) + { + Steinberg::Vst::Event e; + + if (eventList.getEvent (i, e) == Steinberg::kResultOk) + { + switch (e.type) + { + case Steinberg::Vst::Event::kNoteOnEvent: + result.addEvent (MidiMessage::noteOn (createSafeChannel (e.noteOn.channel), + createSafeNote (e.noteOn.pitch), + (Steinberg::uint8) denormaliseToMidiValue (e.noteOn.velocity)), + e.sampleOffset); + break; + + case Steinberg::Vst::Event::kNoteOffEvent: + result.addEvent (MidiMessage::noteOff (createSafeChannel (e.noteOff.channel), + createSafeNote (e.noteOff.pitch), + (Steinberg::uint8) denormaliseToMidiValue (e.noteOff.velocity)), + e.sampleOffset); + break; + + case Steinberg::Vst::Event::kPolyPressureEvent: + result.addEvent (MidiMessage::aftertouchChange (createSafeChannel (e.polyPressure.channel), + createSafeNote (e.polyPressure.pitch), + denormaliseToMidiValue (e.polyPressure.pressure)), + e.sampleOffset); + break; + + case Steinberg::Vst::Event::kDataEvent: + result.addEvent (MidiMessage::createSysExMessage (e.data.bytes, (int) e.data.size), + e.sampleOffset); + break; + + default: + break; + } + } + } + } + + static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer) + { + MidiBuffer::Iterator iterator (midiBuffer); + MidiMessage msg; + int midiEventPosition = 0; + + enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once + int numEvents = 0; + + while (iterator.getNextEvent (msg, midiEventPosition)) + { + if (++numEvents > maxNumEvents) + break; + + Steinberg::Vst::Event e = { 0 }; + + if (msg.isNoteOn()) + { + e.type = Steinberg::Vst::Event::kNoteOnEvent; + e.noteOn.channel = createSafeChannel (msg.getChannel()); + e.noteOn.pitch = createSafeNote (msg.getNoteNumber()); + e.noteOn.velocity = normaliseMidiValue (msg.getVelocity()); + e.noteOn.length = 0; + e.noteOn.tuning = 0.0f; + e.noteOn.noteId = -1; + } + else if (msg.isNoteOff()) + { + e.type = Steinberg::Vst::Event::kNoteOffEvent; + e.noteOff.channel = createSafeChannel (msg.getChannel()); + e.noteOff.pitch = createSafeNote (msg.getNoteNumber()); + e.noteOff.velocity = normaliseMidiValue (msg.getVelocity()); + e.noteOff.tuning = 0.0f; + e.noteOff.noteId = -1; + } + else if (msg.isSysEx()) + { + e.type = Steinberg::Vst::Event::kDataEvent; + e.data.bytes = msg.getSysExData(); + e.data.size = (uint32) msg.getSysExDataSize(); + e.data.type = Steinberg::Vst::DataEvent::kMidiSysEx; + } + else if (msg.isAftertouch()) + { + e.type = Steinberg::Vst::Event::kPolyPressureEvent; + e.polyPressure.channel = createSafeChannel (msg.getChannel()); + e.polyPressure.pitch = createSafeNote (msg.getNoteNumber()); + e.polyPressure.pressure = normaliseMidiValue (msg.getAfterTouchValue()); + } + else + { + continue; + } + + e.busIndex = 0; + e.sampleOffset = midiEventPosition; + + result.addEvent (e); + } + } + +private: + Array events; + Atomic refCount; + + static Steinberg::int16 createSafeChannel (int channel) noexcept { return (Steinberg::int16) jlimit (0, 15, channel - 1); } + static int createSafeChannel (Steinberg::int16 channel) noexcept { return (int) jlimit (1, 16, channel + 1); } + + static Steinberg::int16 createSafeNote (int note) noexcept { return (Steinberg::int16) jlimit (0, 127, note); } + static int createSafeNote (Steinberg::int16 note) noexcept { return jlimit (0, 127, (int) note); } + + static float normaliseMidiValue (int value) noexcept { return jlimit (0.0f, 1.0f, (float) value / 127.0f); } + static int denormaliseToMidiValue (float value) noexcept { return roundToInt (jlimit (0.0f, 127.0f, value * 127.0f)); } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) +}; + +//============================================================================== +template +struct VST3BufferExchange +{ + typedef Array Bus; + typedef Array BusMap; + + static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw) { vstBuffers.channelBuffers32 = raw; } + static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; } + + /** Assigns a series of AudioSampleBuffer's channels to an AudioBusBuffers' + + @warning For speed, does not check the channel count and offsets + according to the AudioSampleBuffer + */ + static void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, + Bus& bus, + AudioBuffer& buffer, + int numChannels, int channelStartOffset, + int sampleOffset = 0) + { + const int channelEnd = numChannels + channelStartOffset; + jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); + + bus.clearQuick(); + + for (int i = channelStartOffset; i < channelEnd; ++i) + bus.add (buffer.getWritePointer (i, sampleOffset)); + + assignRawPointer (vstBuffers, (numChannels > 0 ? bus.getRawDataPointer() : nullptr)); + vstBuffers.numChannels = numChannels; + vstBuffers.silenceFlags = 0; + } + + static void mapArrangementToBuses (int& channelIndexOffset, int index, + Array& result, + BusMap& busMapToUse, const AudioChannelSet& arrangement, + AudioBuffer& source) + { + const int numChansForBus = arrangement.size(); + + if (index >= result.size()) + result.add (Steinberg::Vst::AudioBusBuffers()); + + if (index >= busMapToUse.size()) + busMapToUse.add (Bus()); + + associateBufferTo (result.getReference (index), + busMapToUse.getReference (index), + source, numChansForBus, channelIndexOffset); + + channelIndexOffset += numChansForBus; + } + + static inline void mapBufferToBuses (Array& result, BusMap& busMapToUse, + const Array& arrangements, + AudioBuffer& source) + { + int channelIndexOffset = 0; + + for (int i = 0; i < arrangements.size(); ++i) + mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, + arrangements.getUnchecked (i), source); + } + + static inline void mapBufferToBuses (Array& result, + Steinberg::Vst::IAudioProcessor& processor, + BusMap& busMapToUse, bool isInput, int numBuses, + AudioBuffer& source) + { + int channelIndexOffset = 0; + + for (int i = 0; i < numBuses; ++i) + mapArrangementToBuses (channelIndexOffset, i, + result, busMapToUse, + getArrangementForBus (&processor, isInput, i), + source); + } +}; + +template +struct VST3FloatAndDoubleBusMapCompositeHelper {}; + +struct VST3FloatAndDoubleBusMapComposite +{ + VST3BufferExchange::BusMap floatVersion; + VST3BufferExchange::BusMap doubleVersion; + + template + inline typename VST3BufferExchange::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper::get (*this); } +}; + + +template <> struct VST3FloatAndDoubleBusMapCompositeHelper +{ + static inline VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; } +}; + +template <> struct VST3FloatAndDoubleBusMapCompositeHelper +{ + static inline VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; } +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h new file mode 100644 index 000000000..6a68bc210 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h @@ -0,0 +1,186 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +// Wow, those Steinberg guys really don't worry too much about compiler warnings. +#if _MSC_VER + #pragma warning (disable: 4505) + #pragma warning (push, 0) + #pragma warning (disable: 4702) +#elif __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wnon-virtual-dtor" + #pragma clang diagnostic ignored "-Wreorder" + #pragma clang diagnostic ignored "-Wunsequenced" + #pragma clang diagnostic ignored "-Wint-to-pointer-cast" + #pragma clang diagnostic ignored "-Wunused-parameter" + #pragma clang diagnostic ignored "-Wconversion" + #pragma clang diagnostic ignored "-Woverloaded-virtual" + #pragma clang diagnostic ignored "-Wshadow" + #pragma clang diagnostic ignored "-Wdeprecated-register" + #pragma clang diagnostic ignored "-Wunused-function" + #pragma clang diagnostic ignored "-Wsign-conversion" + #pragma clang diagnostic ignored "-Wsign-compare" + #pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #pragma clang diagnostic ignored "-Wextra-semi" +#endif + +#undef DEVELOPMENT +#define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values + +/* These files come with the Steinberg VST3 SDK - to get them, you'll need to + visit the Steinberg website and agree to whatever is currently required to + get them. + + Then, you'll need to make sure your include path contains your "VST3 SDK" + directory (or whatever you've named it on your machine). The Projucer has + a special box for setting this path. +*/ +#if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#else + #if JUCE_MINGW + #define _set_abort_behavior(...) + #endif + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + +//============================================================================== +namespace Steinberg +{ + /** Missing IIDs */ + DEF_CLASS_IID (IPluginBase) + DEF_CLASS_IID (IPlugView) + DEF_CLASS_IID (IPlugFrame) + DEF_CLASS_IID (IBStream) + DEF_CLASS_IID (IPluginFactory) + DEF_CLASS_IID (IPluginFactory2) + DEF_CLASS_IID (IPluginFactory3) + DEF_CLASS_IID (IPlugViewContentScaleSupport) +} +#endif //JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY + +#if _MSC_VER + #pragma warning (pop) +#elif __clang__ + #pragma clang diagnostic pop +#endif + +#if JUCE_WINDOWS + #include +#endif + +//============================================================================== +#undef ASSERT +#undef WARNING +#undef PRINTSYSERROR +#undef DEBUGSTR +#undef DBPRT0 +#undef DBPRT1 +#undef DBPRT2 +#undef DBPRT3 +#undef DBPRT4 +#undef DBPRT5 +#undef min +#undef max +#undef MIN +#undef MAX +#undef calloc +#undef free +#undef malloc +#undef realloc +#undef NEW +#undef NEWVEC +#undef VERIFY +#undef VERIFY_IS +#undef VERIFY_NOT +#undef META_CREATE_FUNC +#undef CLASS_CREATE_FUNC +#undef SINGLE_CREATE_FUNC +#undef _META_CLASS +#undef _META_CLASS_IFACE +#undef _META_CLASS_SINGLE +#undef META_CLASS +#undef META_CLASS_IFACE +#undef META_CLASS_SINGLE +#undef SINGLETON +#undef OBJ_METHODS +#undef QUERY_INTERFACE +#undef LICENCE_UID +#undef BEGIN_FACTORY +#undef DEF_CLASS +#undef DEF_CLASS1 +#undef DEF_CLASS2 +#undef DEF_CLASS_W +#undef END_FACTORY diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp new file mode 100644 index 000000000..48ade86b5 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -0,0 +1,2983 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) + +#include +#include "juce_VST3Headers.h" +#include "juce_VST3Common.h" + +namespace juce +{ + +using namespace Steinberg; + +//============================================================================== +struct VST3Classes +{ + +#ifndef JUCE_VST3_DEBUGGING + #define JUCE_VST3_DEBUGGING 0 +#endif + +#if JUCE_VST3_DEBUGGING + #define VST3_DBG(a) Logger::writeToLog (a); +#else + #define VST3_DBG(a) +#endif + +#if JUCE_DEBUG +static int warnOnFailure (int result) noexcept +{ + const char* message = "Unknown result!"; + + switch (result) + { + case kResultOk: return result; + case kNotImplemented: message = "kNotImplemented"; break; + case kNoInterface: message = "kNoInterface"; break; + case kResultFalse: message = "kResultFalse"; break; + case kInvalidArgument: message = "kInvalidArgument"; break; + case kInternalError: message = "kInternalError"; break; + case kNotInitialized: message = "kNotInitialized"; break; + case kOutOfMemory: message = "kOutOfMemory"; break; + default: break; + } + + DBG (message); + return result; +} +#else + #define warnOnFailure(x) x +#endif + +//============================================================================== +static int getHashForTUID (const TUID& tuid) noexcept +{ + int value = 0; + + for (int i = 0; i < numElementsInArray (tuid); ++i) + value = (value * 31) + tuid[i]; + + return value; +} + +template +static void fillDescriptionWith (PluginDescription& description, ObjectType& object) +{ + description.version = toString (object.version).trim(); + description.category = toString (object.subCategories).trim(); + + if (description.manufacturerName.trim().isEmpty()) + description.manufacturerName = toString (object.vendor).trim(); +} + +static void createPluginDescription (PluginDescription& description, + const File& pluginFile, const String& company, const String& name, + const PClassInfo& info, PClassInfo2* info2, PClassInfoW* infoW, + int numInputs, int numOutputs) +{ + description.fileOrIdentifier = pluginFile.getFullPathName(); + description.lastFileModTime = pluginFile.getLastModificationTime(); + description.lastInfoUpdateTime = Time::getCurrentTime(); + description.manufacturerName = company; + description.name = name; + description.descriptiveName = name; + description.pluginFormatName = "VST3"; + description.numInputChannels = numInputs; + description.numOutputChannels = numOutputs; + description.uid = getHashForTUID (info.cid); + + if (infoW != nullptr) fillDescriptionWith (description, *infoW); + else if (info2 != nullptr) fillDescriptionWith (description, *info2); + + if (description.category.isEmpty()) + description.category = toString (info.category).trim(); + + description.isInstrument = description.category.containsIgnoreCase ("Instrument"); // This seems to be the only way to find that out! ARGH! +} + +static int getNumSingleDirectionBusesFor (Vst::IComponent* component, + bool checkInputs, + bool checkAudioChannels) +{ + jassert (component != nullptr); + + return (int) component->getBusCount (checkAudioChannels ? Vst::kAudio : Vst::kEvent, + checkInputs ? Vst::kInput : Vst::kOutput); +} + +/** Gives the total number of channels for a particular type of bus direction and media type */ +static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, + bool checkInputs, + bool checkAudioChannels) +{ + jassert (component != nullptr); + + const Vst::BusDirections direction = checkInputs ? Vst::kInput : Vst::kOutput; + const Vst::MediaTypes mediaType = checkAudioChannels ? Vst::kAudio : Vst::kEvent; + const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); + + int numChannels = 0; + + for (Steinberg::int32 i = numBuses; --i >= 0;) + { + Vst::BusInfo busInfo; + warnOnFailure (component->getBusInfo (mediaType, direction, i, busInfo)); + numChannels += ((busInfo.flags & Vst::BusInfo::kDefaultActive) != 0 ? (int) busInfo.channelCount : 0); + } + + return numChannels; +} + +static void setStateForAllBusesOfType (Vst::IComponent* component, + bool state, + bool activateInputs, + bool activateAudioChannels) +{ + jassert (component != nullptr); + + const Vst::BusDirections direction = activateInputs ? Vst::kInput : Vst::kOutput; + const Vst::MediaTypes mediaType = activateAudioChannels ? Vst::kAudio : Vst::kEvent; + const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); + + for (Steinberg::int32 i = numBuses; --i >= 0;) + warnOnFailure (component->activateBus (mediaType, direction, i, state)); +} + +//============================================================================== +/** Assigns a complete AudioSampleBuffer's channels to an AudioBusBuffers' */ +static void associateWholeBufferTo (Vst::AudioBusBuffers& vstBuffers, AudioSampleBuffer& buffer) noexcept +{ + vstBuffers.channelBuffers32 = buffer.getArrayOfWritePointers(); + vstBuffers.numChannels = buffer.getNumChannels(); + vstBuffers.silenceFlags = 0; +} + +//============================================================================== +static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, double sampleRate) +{ + jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK + + using namespace Vst; + + zerostruct (context); + context.sampleRate = sampleRate; + auto& fr = context.frameRate; + + if (playHead != nullptr) + { + AudioPlayHead::CurrentPositionInfo position; + playHead->getCurrentPosition (position); + + context.projectTimeSamples = position.timeInSamples; //Must always be valid, as stated by the VST3 SDK + context.projectTimeMusic = position.timeInSeconds; //Does not always need to be valid... + context.tempo = position.bpm; + context.timeSigNumerator = position.timeSigNumerator; + context.timeSigDenominator = position.timeSigDenominator; + context.barPositionMusic = position.ppqPositionOfLastBarStart; + context.cycleStartMusic = position.ppqLoopStart; + context.cycleEndMusic = position.ppqLoopEnd; + + switch (position.frameRate) + { + case AudioPlayHead::fps23976: fr.framesPerSecond = 24; fr.flags = FrameRate::kPullDownRate; break; + case AudioPlayHead::fps24: fr.framesPerSecond = 24; fr.flags = 0; break; + case AudioPlayHead::fps25: fr.framesPerSecond = 25; fr.flags = 0; break; + case AudioPlayHead::fps2997: fr.framesPerSecond = 30; fr.flags = FrameRate::kPullDownRate; break; + case AudioPlayHead::fps2997drop: fr.framesPerSecond = 30; fr.flags = FrameRate::kPullDownRate | FrameRate::kDropRate; break; + case AudioPlayHead::fps30: fr.framesPerSecond = 30; fr.flags = 0; break; + case AudioPlayHead::fps30drop: fr.framesPerSecond = 30; fr.flags = FrameRate::kDropRate; break; + case AudioPlayHead::fps60: fr.framesPerSecond = 60; fr.flags = 0; break; + case AudioPlayHead::fps60drop: fr.framesPerSecond = 60; fr.flags = FrameRate::kDropRate; break; + case AudioPlayHead::fpsUnknown: break; + default: jassertfalse; break; // New frame rate? + } + + if (position.isPlaying) context.state |= ProcessContext::kPlaying; + if (position.isRecording) context.state |= ProcessContext::kRecording; + if (position.isLooping) context.state |= ProcessContext::kCycleActive; + } + else + { + context.tempo = 120.0; + context.timeSigNumerator = 4; + context.timeSigDenominator = 4; + fr.framesPerSecond = 30; + fr.flags = 0; + } + + if (context.projectTimeMusic >= 0.0) context.state |= ProcessContext::kProjectTimeMusicValid; + if (context.barPositionMusic >= 0.0) context.state |= ProcessContext::kBarPositionValid; + if (context.tempo > 0.0) context.state |= ProcessContext::kTempoValid; + if (context.frameRate.framesPerSecond > 0) context.state |= ProcessContext::kSmpteValid; + + if (context.cycleStartMusic >= 0.0 + && context.cycleEndMusic > 0.0 + && context.cycleEndMusic > context.cycleStartMusic) + { + context.state |= ProcessContext::kCycleValid; + } + + if (context.timeSigNumerator > 0 && context.timeSigDenominator > 0) + context.state |= ProcessContext::kTimeSigValid; +} + +//============================================================================== +/** Get a list of speaker arrangements as per their speaker names + + (e.g.: 2 regular channels, aliased as 'kStringStereoS', is "L R") +*/ +static StringArray getSpeakerArrangements() +{ + using namespace Vst::SpeakerArr; + + const Vst::CString arrangements[] = + { + kStringMonoS, kStringStereoS, kStringStereoRS, kStringStereoCS, + kStringStereoSS, kStringStereoCLfeS, kString30CineS, kString30MusicS, + kString31CineS, kString31MusicS, kString40CineS, kString40MusicS, + kString41CineS, kString41MusicS, kString50S, kString51S, + kString60CineS, kString60MusicS, kString61CineS, kString61MusicS, + kString70CineS, kString70MusicS, kString71CineS, kString71MusicS, + kString80CineS, kString80MusicS, kString81CineS, kString81MusicS, + kString80CubeS, kStringBFormat1stOrderS, kString71CineTopCenterS, + kString71CineCenterHighS, kString71CineFrontHighS, kString71CineSideHighS, + kString71CineFullRearS, kString90S, kString91S, + kString100S, kString101S, kString110S, kString111S, + kString130S, kString131S, kString102S, kString122S, + nullptr + }; + + return StringArray (arrangements); +} + +/** Get a list of speaker arrangements as per their named configurations + + (e.g.: 2 regular channels, aliased as 'kStringStereoS', is "L R") +*/ +static StringArray getNamedSpeakerArrangements() +{ + using namespace Vst::SpeakerArr; + + const Vst::CString arrangements[] = + { + kStringEmpty, kStringMono, kStringStereo, kStringStereoR, + kStringStereoC, kStringStereoSide, kStringStereoCLfe, kString30Cine, + kString30Music, kString31Cine, kString31Music, kString40Cine, + kString40Music, kString41Cine, kString41Music, kString50, + kString51, kString60Cine, kString60Music, kString61Cine, + kString61Music, kString70Cine, kString70Music, kString71Cine, + kString71Music, kString71CineTopCenter, kString71CineCenterHigh, + kString71CineFrontHigh, kString71CineSideHigh, kString71CineFullRear, + kString80Cine, kString80Music, kString80Cube, kString81Cine, + kString81Music, kString102, kString122, kString90, + kString91, kString100, kString101, kString110, + kString111, kString130, kString131, + nullptr + }; + + return StringArray (arrangements); +} + +static Vst::SpeakerArrangement getSpeakerArrangementFrom (const String& string) +{ + return Vst::SpeakerArr::getSpeakerArrangementFromString (string.toUTF8()); +} + +//============================================================================== +static StringArray getPluginEffectCategories() +{ + using namespace Vst::PlugType; + + const Vst::CString categories[] = + { + kFxAnalyzer, kFxDelay, kFxDistortion, kFxDynamics, + kFxEQ, kFxFilter, kFx, kFxInstrument, + kFxInstrumentExternal, kFxSpatial, kFxGenerator, kFxMastering, + kFxModulation, kFxPitchShift, kFxRestoration, kFxReverb, + kFxSurround, kFxTools, kSpatial, kSpatialFx, + nullptr + }; + + return StringArray (categories); +} + +static StringArray getPluginInstrumentCategories() +{ + using namespace Vst::PlugType; + + const Vst::CString categories[] = + { + kInstrumentSynthSampler, kInstrumentDrum, + kInstrumentSampler, kInstrumentSynth, + kInstrumentExternal, kFxInstrument, + kFxInstrumentExternal, kFxSpatial, + kFxGenerator, + nullptr + }; + + return StringArray (categories); +} + +//============================================================================== +struct VST3PluginInstance; + +struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 + public Vst::IComponentHandler2, // From VST V3.1.0 (a very well named class, of course!) + public Vst::IComponentHandler3, // From VST V3.5.0 (also very well named!) + public Vst::IContextMenuTarget, + public Vst::IHostApplication, + public Vst::IUnitHandler +{ + VST3HostContext() + { + appName = File::getSpecialLocation (File::currentApplicationFile).getFileNameWithoutExtension(); + attributeList = new AttributeList (this); + } + + virtual ~VST3HostContext() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + + FUnknown* getFUnknown() { return static_cast (this); } + + static bool hasFlag (Steinberg::int32 source, Steinberg::int32 flag) noexcept + { + return (source & flag) == flag; + } + + //============================================================================== + tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override + { + if (plugin != nullptr) + { + auto index = getIndexOfParamID (paramID); + + if (index < 0) + return kResultFalse; + + plugin->beginParameterChangeGesture (index); + } + + return kResultTrue; + } + + tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override + { + if (plugin != nullptr) + { + auto index = getIndexOfParamID (paramID); + + if (index < 0) + return kResultFalse; + + plugin->sendParamChangeMessageToListeners (index, (float) valueNormalized); + + { + Steinberg::int32 eventIndex; + plugin->inputParameterChanges->addParameterData (paramID, eventIndex)->addPoint (0, valueNormalized, eventIndex); + } + + // did the plug-in already update the parameter internally + if (plugin->editController->getParamNormalized (paramID) != (float) valueNormalized) + return plugin->editController->setParamNormalized (paramID, valueNormalized); + } + + return kResultTrue; + } + + tresult PLUGIN_API endEdit (Vst::ParamID paramID) override + { + if (plugin != nullptr) + { + auto index = getIndexOfParamID (paramID); + + if (index < 0) + return kResultFalse; + + plugin->endParameterChangeGesture (index); + } + + return kResultTrue; + } + + tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override + { + if (plugin != nullptr) + { + if (hasFlag (flags, Vst::kReloadComponent)) + plugin->reset(); + + if (hasFlag (flags, Vst::kIoChanged)) + { + auto sampleRate = plugin->getSampleRate(); + auto blockSize = plugin->getBlockSize(); + + plugin->prepareToPlay (sampleRate >= 8000 ? sampleRate : 44100.0, + blockSize > 0 ? blockSize : 1024); + } + + if (hasFlag (flags, Vst::kLatencyChanged)) + if (plugin->processor != nullptr) + plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); + + plugin->updateHostDisplay(); + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API setDirty (TBool) override + { + return kResultFalse; + } + + tresult PLUGIN_API requestOpenEditor (FIDString name) override + { + ignoreUnused (name); + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API startGroupEdit() override + { + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API finishGroupEdit() override + { + jassertfalse; + return kResultFalse; + } + + void setPlugin (VST3PluginInstance* instance) + { + jassert (plugin == nullptr); + plugin = instance; + } + + //============================================================================== + struct ContextMenu : public Vst::IContextMenu + { + ContextMenu (VST3PluginInstance& pluginInstance) : owner (pluginInstance) {} + virtual ~ContextMenu() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + Steinberg::int32 PLUGIN_API getItemCount() override { return (Steinberg::int32) items.size(); } + + tresult PLUGIN_API addItem (const Item& item, IContextMenuTarget* target) override + { + jassert (target != nullptr); + + ItemAndTarget newItem; + newItem.item = item; + newItem.target = target; + + items.add (newItem); + return kResultOk; + } + + tresult PLUGIN_API removeItem (const Item& toRemove, IContextMenuTarget* target) override + { + for (int i = items.size(); --i >= 0;) + { + auto& item = items.getReference(i); + + if (item.item.tag == toRemove.tag && item.target == target) + items.remove (i); + } + + return kResultOk; + } + + tresult PLUGIN_API getItem (Steinberg::int32 tag, Item& result, IContextMenuTarget** target) override + { + for (int i = 0; i < items.size(); ++i) + { + auto& item = items.getReference(i); + + if (item.item.tag == tag) + { + result = item.item; + + if (target != nullptr) + *target = item.target; + + return kResultTrue; + } + } + + zerostruct (result); + return kResultFalse; + } + + tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override + { + Array subItemStack; + OwnedArray menuStack; + PopupMenu* topLevelMenu = menuStack.add (new PopupMenu()); + + for (int i = 0; i < items.size(); ++i) + { + auto& item = items.getReference (i).item; + auto* menuToUse = menuStack.getLast(); + + if (hasFlag (item.flags, Item::kIsGroupStart & ~Item::kIsDisabled)) + { + subItemStack.add (&item); + menuStack.add (new PopupMenu()); + } + else if (hasFlag (item.flags, Item::kIsGroupEnd)) + { + if (auto* subItem = subItemStack.getLast()) + { + if (auto* m = menuStack [menuStack.size() - 2]) + m->addSubMenu (toString (subItem->name), *menuToUse, + ! hasFlag (subItem->flags, Item::kIsDisabled), + nullptr, + hasFlag (subItem->flags, Item::kIsChecked)); + + menuStack.removeLast (1); + subItemStack.removeLast (1); + } + } + else if (hasFlag (item.flags, Item::kIsSeparator)) + { + menuToUse->addSeparator(); + } + else + { + menuToUse->addItem (item.tag != 0 ? (int) item.tag : (int) zeroTagReplacement, + toString (item.name), + ! hasFlag (item.flags, Item::kIsDisabled), + hasFlag (item.flags, Item::kIsChecked)); + } + } + + PopupMenu::Options options; + + if (auto* ed = owner.getActiveEditor()) + options = options.withTargetScreenArea (ed->getScreenBounds().translated ((int) x, (int) y).withSize (1, 1)); + + #if JUCE_MODAL_LOOPS_PERMITTED + // Unfortunately, Steinberg's docs explicitly say this should be modal.. + handleResult (topLevelMenu->showMenu (options)); + #else + topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr (this))); + #endif + + return kResultOk; + } + + #if ! JUCE_MODAL_LOOPS_PERMITTED + static void menuFinished (int modalResult, ComSmartPtr menu) { menu->handleResult (modalResult); } + #endif + + private: + enum { zeroTagReplacement = 0x7fffffff }; + + Atomic refCount; + VST3PluginInstance& owner; + + struct ItemAndTarget + { + Item item; + ComSmartPtr target; + }; + + Array items; + + void handleResult (int result) + { + if (result == 0) + return; + + if (result == zeroTagReplacement) + result = 0; + + for (int i = 0; i < items.size(); ++i) + { + auto& item = items.getReference(i); + + if ((int) item.item.tag == result) + { + if (item.target != nullptr) + item.target->executeMenuItem ((Steinberg::int32) result); + + break; + } + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContextMenu) + }; + + Vst::IContextMenu* PLUGIN_API createContextMenu (IPlugView*, const Vst::ParamID*) override + { + if (plugin != nullptr) + return new ContextMenu (*plugin); + + return nullptr; + } + + tresult PLUGIN_API executeMenuItem (Steinberg::int32) override + { + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API getName (Vst::String128 name) override + { + Steinberg::String str (appName.toUTF8()); + str.copyTo (name, 0, 127); + return kResultOk; + } + + tresult PLUGIN_API createInstance (TUID cid, TUID iid, void** obj) override + { + *obj = nullptr; + + if (! doUIDsMatch (cid, iid)) + { + jassertfalse; + return kInvalidArgument; + } + + if (doUIDsMatch (cid, Vst::IMessage::iid) && doUIDsMatch (iid, Vst::IMessage::iid)) + { + ComSmartPtr m (new Message (attributeList)); + messageQueue.add (m); + m->addRef(); + *obj = m; + return kResultOk; + } + else if (doUIDsMatch (cid, Vst::IAttributeList::iid) && doUIDsMatch (iid, Vst::IAttributeList::iid)) + { + ComSmartPtr l (new AttributeList (this)); + l->addRef(); + *obj = l; + return kResultOk; + } + + jassertfalse; + return kNotImplemented; + } + + //============================================================================== + tresult PLUGIN_API notifyUnitSelection (Vst::UnitID) override + { + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) override + { + if (plugin != nullptr) + plugin->syncProgramNames(); + + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override + { + if (doUIDsMatch (iid, Vst::IAttributeList::iid)) + { + *obj = attributeList.get(); + return kResultOk; + } + + TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IComponentHandler) + TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IComponentHandler2) + TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IComponentHandler3) + TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IContextMenuTarget) + TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IHostApplication) + TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IUnitHandler) + TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (iid, FUnknown, Vst::IComponentHandler) + + *obj = nullptr; + return kNotImplemented; + } + +private: + //============================================================================== + VST3PluginInstance* plugin = nullptr; + Atomic refCount; + String appName; + + typedef std::map ParamMapType; + ParamMapType paramToIndexMap; + + int getIndexOfParamID (Vst::ParamID paramID) + { + if (plugin == nullptr || plugin->editController == nullptr) + return -1; + + auto result = getMappedParamID (paramID); + + if (result < 0) + { + auto numParams = plugin->editController->getParameterCount(); + + for (int i = 0; i < numParams; ++i) + { + Vst::ParameterInfo paramInfo; + plugin->editController->getParameterInfo (i, paramInfo); + paramToIndexMap[paramInfo.id] = i; + } + + result = getMappedParamID (paramID); + } + + return result; + } + + int getMappedParamID (Vst::ParamID paramID) + { + auto it = paramToIndexMap.find (paramID); + return it != paramToIndexMap.end() ? it->second : -1; + } + + //============================================================================== + struct Message : public Vst::IMessage + { + Message (Vst::IAttributeList* list) + : attributeList (list) + { + } + + Message (Vst::IAttributeList* list, FIDString id) + : attributeList (list), messageId (toString (id)) + { + } + + Message (Vst::IAttributeList* list, FIDString id, const var& v) + : value (v), attributeList (list), messageId (toString (id)) + { + } + + virtual ~Message() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + FIDString PLUGIN_API getMessageID() override { return messageId.toRawUTF8(); } + void PLUGIN_API setMessageID (FIDString id) override { messageId = toString (id); } + Vst::IAttributeList* PLUGIN_API getAttributes() override { return attributeList; } + + var value; + + private: + ComSmartPtr attributeList; + String messageId; + Atomic refCount; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) + }; + + Array, CriticalSection> messageQueue; + + //============================================================================== + struct AttributeList : public Vst::IAttributeList + { + AttributeList (VST3HostContext* o) : owner (o) {} + virtual ~AttributeList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + //============================================================================== + tresult PLUGIN_API setInt (AttrID id, Steinberg::int64 value) override + { + addMessageToQueue (id, value); + return kResultTrue; + } + + tresult PLUGIN_API setFloat (AttrID id, double value) override + { + addMessageToQueue (id, value); + return kResultTrue; + } + + tresult PLUGIN_API setString (AttrID id, const Vst::TChar* string) override + { + addMessageToQueue (id, toString (string)); + return kResultTrue; + } + + tresult PLUGIN_API setBinary (AttrID id, const void* data, Steinberg::uint32 size) override + { + jassert (size >= 0 && (data != nullptr || size == 0)); + addMessageToQueue (id, MemoryBlock (data, (size_t) size)); + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API getInt (AttrID id, Steinberg::int64& result) override + { + jassert (id != nullptr); + + if (findMessageOnQueueWithID (id, result)) + return kResultTrue; + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getFloat (AttrID id, double& result) override + { + jassert (id != nullptr); + + if (findMessageOnQueueWithID (id, result)) + return kResultTrue; + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getString (AttrID id, Vst::TChar* result, Steinberg::uint32 length) override + { + jassert (id != nullptr); + + String stringToFetch; + if (findMessageOnQueueWithID (id, stringToFetch)) + { + Steinberg::String str (stringToFetch.toRawUTF8()); + str.copyTo (result, 0, (Steinberg::int32) jmin (length, (Steinberg::uint32) std::numeric_limits::max())); + + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getBinary (AttrID id, const void*& data, Steinberg::uint32& size) override + { + jassert (id != nullptr); + + for (auto&& m : owner->messageQueue) + { + if (std::strcmp (m->getMessageID(), id) == 0) + { + if (auto* binaryData = m->value.getBinaryData()) + { + data = binaryData->getData(); + size = (Steinberg::uint32) binaryData->getSize(); + return kResultTrue; + } + } + } + + return kResultFalse; + } + + private: + VST3HostContext* owner; + Atomic refCount; + + //============================================================================== + template + void addMessageToQueue (AttrID id, const Type& value) + { + jassert (id != nullptr); + + for (auto&& m : owner->messageQueue) + { + if (std::strcmp (m->getMessageID(), id) == 0) + { + m->value = value; + return; + } + } + + owner->messageQueue.add (ComSmartPtr (new Message (this, id, value))); + } + + template + bool findMessageOnQueueWithID (AttrID id, Type& value) + { + jassert (id != nullptr); + + for (auto&& m : owner->messageQueue) + { + if (std::strcmp (m->getMessageID(), id) == 0) + { + value = m->value; + return true; + } + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) + }; + + ComSmartPtr attributeList; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext) +}; + +//============================================================================== +struct DescriptionFactory +{ + DescriptionFactory (VST3HostContext* host, IPluginFactory* pluginFactory) + : vst3HostContext (host), factory (pluginFactory) + { + jassert (pluginFactory != nullptr); + } + + virtual ~DescriptionFactory() {} + + Result findDescriptionsAndPerform (const File& file) + { + StringArray foundNames; + PFactoryInfo factoryInfo; + factory->getFactoryInfo (&factoryInfo); + auto companyName = toString (factoryInfo.vendor).trim(); + + Result result (Result::ok()); + + auto numClasses = factory->countClasses(); + + for (Steinberg::int32 i = 0; i < numClasses; ++i) + { + PClassInfo info; + factory->getClassInfo (i, &info); + + if (std::strcmp (info.category, kVstAudioEffectClass) != 0) + continue; + + const String name (toString (info.name).trim()); + + if (foundNames.contains (name, true)) + continue; + + ScopedPointer info2; + ScopedPointer infoW; + + { + ComSmartPtr pf2; + ComSmartPtr pf3; + + if (pf2.loadFrom (factory)) + { + info2 = new PClassInfo2(); + pf2->getClassInfo2 (i, info2); + } + + if (pf3.loadFrom (factory)) + { + infoW = new PClassInfoW(); + pf3->getClassInfoUnicode (i, infoW); + } + } + + foundNames.add (name); + + PluginDescription desc; + + { + ComSmartPtr component; + + if (component.loadFrom (factory, info.cid)) + { + if (component->initialize (vst3HostContext->getFUnknown()) == kResultOk) + { + auto numInputs = getNumSingleDirectionChannelsFor (component, true, true); + auto numOutputs = getNumSingleDirectionChannelsFor (component, false, true); + + createPluginDescription (desc, file, companyName, name, + info, info2, infoW, numInputs, numOutputs); + + component->terminate(); + } + else + { + jassertfalse; + } + } + else + { + jassertfalse; + } + } + + result = performOnDescription (desc); + + if (result.failed()) + break; + } + + return result; + } + + virtual Result performOnDescription (PluginDescription&) = 0; + +private: + ComSmartPtr vst3HostContext; + ComSmartPtr factory; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionFactory) +}; + +struct MatchingDescriptionFinder : public DescriptionFactory +{ + MatchingDescriptionFinder (VST3HostContext* h, IPluginFactory* f, const PluginDescription& desc) + : DescriptionFactory (h, f), description (desc) + { + } + + static const char* getSuccessString() noexcept { return "Found Description"; } + + Result performOnDescription (PluginDescription& desc) + { + if (description.isDuplicateOf (desc)) + return Result::fail (getSuccessString()); + + return Result::ok(); + } + +private: + const PluginDescription& description; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MatchingDescriptionFinder) +}; + +struct DescriptionLister : public DescriptionFactory +{ + DescriptionLister (VST3HostContext* host, IPluginFactory* pluginFactory) + : DescriptionFactory (host, pluginFactory) + { + } + + Result performOnDescription (PluginDescription& desc) + { + list.add (new PluginDescription (desc)); + return Result::ok(); + } + + OwnedArray list; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionLister) +}; + +//============================================================================== +struct DLLHandle +{ + DLLHandle (const String& modulePath) + { + if (modulePath.trim().isNotEmpty()) + open (modulePath); + } + + ~DLLHandle() + { + typedef bool (PLUGIN_API *ExitModuleFn) (); + + #if JUCE_WINDOWS + releaseFactory(); + + if (auto exitFn = (ExitModuleFn) getFunction ("ExitDll")) + exitFn(); + + library.close(); + + #else + if (bundleRef != nullptr) + { + releaseFactory(); + + if (auto exitFn = (ExitModuleFn) getFunction ("bundleExit")) + exitFn(); + + CFRelease (bundleRef); + bundleRef = nullptr; + } + #endif + } + + void open (const PluginDescription& description) + { + #if JUCE_WINDOWS + jassert (description.fileOrIdentifier.isNotEmpty()); + jassert (File (description.fileOrIdentifier).existsAsFile()); + library.open (description.fileOrIdentifier); + #else + open (description.fileOrIdentifier); + #endif + } + + /** @note The factory should begin with a refCount of 1, + so don't increment the reference count + (ie: don't use a ComSmartPtr in here)! + Its lifetime will be handled by this DllHandle, + when such will be destroyed. + + @see releaseFactory + */ + IPluginFactory* JUCE_CALLTYPE getPluginFactory() + { + if (factory == nullptr) + if (auto proc = (GetFactoryProc) getFunction ("GetPluginFactory")) + factory = proc(); + + // The plugin NEEDS to provide a factory to be able to be called a VST3! + // Most likely you are trying to load a 32-bit VST3 from a 64-bit host + // or vice versa. + jassert (factory != nullptr); + return factory; + } + + void* getFunction (const char* functionName) + { + #if JUCE_WINDOWS + return library.getFunction (functionName); + #else + if (bundleRef == nullptr) + return nullptr; + + CFStringRef name = String (functionName).toCFString(); + void* fn = CFBundleGetFunctionPointerForName (bundleRef, name); + CFRelease (name); + return fn; + #endif + } + +private: + IPluginFactory* factory = nullptr; + + void releaseFactory() + { + if (factory != nullptr) + factory->release(); + } + + #if JUCE_WINDOWS + DynamicLibrary library; + + bool open (const String& filePath) + { + if (library.open (filePath)) + { + typedef bool (PLUGIN_API *InitModuleProc) (); + + if (auto proc = (InitModuleProc) getFunction ("InitDll")) + { + if (proc()) + return true; + } + else + { + return true; + } + + library.close(); + } + + return false; + } + + #else + CFBundleRef bundleRef; + + bool open (const String& filePath) + { + const File file (filePath); + const char* const utf8 = file.getFullPathName().toRawUTF8(); + + if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, (CFIndex) std::strlen (utf8), file.isDirectory())) + { + bundleRef = CFBundleCreate (kCFAllocatorDefault, url); + CFRelease (url); + + if (bundleRef != nullptr) + { + CFErrorRef error = nullptr; + + if (CFBundleLoadExecutableAndReturnError (bundleRef, &error)) + { + typedef bool (*BundleEntryProc)(CFBundleRef); + + if (auto proc = (BundleEntryProc) getFunction ("bundleEntry")) + { + if (proc (bundleRef)) + return true; + } + else + { + return true; + } + } + + if (error != nullptr) + { + if (CFStringRef failureMessage = CFErrorCopyFailureReason (error)) + { + DBG (String::fromCFString (failureMessage)); + CFRelease (failureMessage); + } + + CFRelease (error); + } + + CFRelease (bundleRef); + bundleRef = nullptr; + } + } + + return false; + } + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DLLHandle) +}; + +//============================================================================== +struct VST3ModuleHandle : public ReferenceCountedObject +{ + explicit VST3ModuleHandle (const File& pluginFile) : file (pluginFile) + { + getActiveModules().add (this); + } + + ~VST3ModuleHandle() + { + getActiveModules().removeFirstMatchingValue (this); + } + + /** + Since there is no apparent indication if a VST3 plugin is a shell or not, + we're stuck iterating through a VST3's factory, creating a description + for every housed plugin. + */ + static bool getAllDescriptionsForFile (OwnedArray& results, + const String& fileOrIdentifier) + { + DLLHandle tempModule (fileOrIdentifier); + + ComSmartPtr pluginFactory (tempModule.getPluginFactory()); + + if (pluginFactory != nullptr) + { + ComSmartPtr host (new VST3HostContext()); + DescriptionLister lister (host, pluginFactory); + auto result = lister.findDescriptionsAndPerform (File (fileOrIdentifier)); + + results.addCopiesOf (lister.list); + + return result.wasOk(); + } + + jassertfalse; + return false; + } + + //============================================================================== + typedef ReferenceCountedObjectPtr Ptr; + + static VST3ModuleHandle::Ptr findOrCreateModule (const File& file, const PluginDescription& description) + { + for (auto* module : getActiveModules()) + // VST3s are basically shells, you must therefore check their name along with their file: + if (module->file == file && module->name == description.name) + return module; + + VST3ModuleHandle::Ptr m (new VST3ModuleHandle (file)); + + if (! m->open (file, description)) + m = nullptr; + + return m; + } + + //============================================================================== + IPluginFactory* getPluginFactory() { return dllHandle->getPluginFactory(); } + + File file; + String name; + +private: + ScopedPointer dllHandle; + + //============================================================================== + static Array& getActiveModules() + { + static Array activeModules; + return activeModules; + } + + //============================================================================== + bool open (const File& f, const PluginDescription& description) + { + dllHandle = new DLLHandle (f.getFullPathName()); + + ComSmartPtr pluginFactory (dllHandle->getPluginFactory()); + + if (pluginFactory != nullptr) + { + ComSmartPtr host (new VST3HostContext()); + MatchingDescriptionFinder finder (host, pluginFactory, description); + + auto result = finder.findDescriptionsAndPerform (f); + + if (result.getErrorMessage() == MatchingDescriptionFinder::getSuccessString() || + result.getErrorMessage().isEmpty()) + { + name = description.name; + return true; + } + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) +}; + +//============================================================================== +struct VST3PluginWindow : public AudioProcessorEditor, + public ComponentMovementWatcher, + public IPlugFrame +{ + VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) + : AudioProcessorEditor (owner), + ComponentMovementWatcher (this), + view (pluginView, false) + { + setSize (10, 10); + setOpaque (true); + setVisible (true); + + warnOnFailure (view->setFrame (this)); + resizeToFit(); + } + + ~VST3PluginWindow() + { + warnOnFailure (view->removed()); + warnOnFailure (view->setFrame (nullptr)); + + processor.editorBeingDeleted (this); + + #if JUCE_MAC + embeddedComponent.setView (nullptr); + #endif + + view = nullptr; + } + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override + { + view->onWheel (wheel.deltaY); + } + + void focusGained (FocusChangeType) override { view->onFocus (true); } + void focusLost (FocusChangeType) override { view->onFocus (false); } + + /** It seems that most, if not all, plugins do their own keyboard hooks, + but IPlugView does have a set of keyboard related methods... + */ + bool keyStateChanged (bool /*isKeyDown*/) override { return true; } + bool keyPressed (const KeyPress& /*key*/) override { return true; } + + //============================================================================== + void componentMovedOrResized (bool, bool wasResized) override + { + if (recursiveResize) + return; + + auto* topComp = getTopLevelComponent(); + + if (topComp->getPeer() != nullptr) + { + #if JUCE_WINDOWS + auto pos = topComp->getLocalPoint (this, Point()); + #endif + + recursiveResize = true; + + ViewRect rect; + + if (wasResized && view->canResize() == kResultTrue) + { + rect.right = (Steinberg::int32) getWidth(); + rect.bottom = (Steinberg::int32) getHeight(); + view->checkSizeConstraint (&rect); + + auto w = (int) rect.getWidth(); + auto h = (int) rect.getHeight(); + setSize (w, h); + + #if JUCE_WINDOWS + SetWindowPos (pluginHandle, 0, + pos.x, pos.y, w, h, + isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); + #elif JUCE_MAC + embeddedComponent.setBounds (getLocalBounds()); + #endif + + view->onSize (&rect); + } + else + { + warnOnFailure (view->getSize (&rect)); + + #if JUCE_WINDOWS + SetWindowPos (pluginHandle, 0, + pos.x, pos.y, rect.getWidth(), rect.getHeight(), + isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); + #elif JUCE_MAC + embeddedComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); + #endif + } + + // Some plugins don't update their cursor correctly when mousing out the window + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); + + recursiveResize = false; + } + } + + void componentPeerChanged() override {} + + void componentVisibilityChanged() override + { + attachPluginWindow(); + componentMovedOrResized (true, true); + } + + void resizeToFit() + { + ViewRect rect; + warnOnFailure (view->getSize (&rect)); + resizeWithRect (*this, rect); + } + + tresult PLUGIN_API resizeView (IPlugView* incomingView, ViewRect* newSize) override + { + if (incomingView != nullptr + && newSize != nullptr + && incomingView == view) + { + resizeWithRect (embeddedComponent, *newSize); + setSize (embeddedComponent.getWidth(), embeddedComponent.getHeight()); + return kResultTrue; + } + + jassertfalse; + return kInvalidArgument; + } + + void setScaleFactor (float newScale) override + { + Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; + view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface); + + if (scaleInterface != nullptr) + { + scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) newScale); + scaleInterface->release(); + resizeToFit(); + } + } + +private: + //============================================================================== + Atomic refCount { 1 }; + ComSmartPtr view; + + #if JUCE_WINDOWS + struct ChildComponent : public Component + { + ChildComponent() {} + void paint (Graphics& g) override { g.fillAll (Colours::cornflowerblue); } + using Component::createNewPeer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent) + }; + + ChildComponent embeddedComponent; + ScopedPointer peer; + typedef HWND HandleFormat; + #elif JUCE_MAC + AutoResizingNSViewComponentWithParent embeddedComponent; + typedef NSView* HandleFormat; + #else + Component embeddedComponent; + typedef void* HandleFormat; + #endif + + HandleFormat pluginHandle = {}; + bool recursiveResize = false; + + //============================================================================== + static void resizeWithRect (Component& comp, const ViewRect& rect) + { + comp.setBounds ((int) rect.left, (int) rect.top, + jmax (10, std::abs ((int) rect.getWidth())), + jmax (10, std::abs ((int) rect.getHeight()))); + } + + void attachPluginWindow() + { + if (pluginHandle == nullptr) + { + #if JUCE_WINDOWS + if (auto* topComp = getTopLevelComponent()) + peer = embeddedComponent.createNewPeer (0, topComp->getWindowHandle()); + else + peer = nullptr; + + if (peer != nullptr) + pluginHandle = (HandleFormat) peer->getNativeHandle(); + #elif JUCE_MAC + embeddedComponent.setBounds (getLocalBounds()); + addAndMakeVisible (embeddedComponent); + pluginHandle = (NSView*) embeddedComponent.getView(); + jassert (pluginHandle != nil); + #endif + + if (pluginHandle != nullptr) + warnOnFailure (view->attached (pluginHandle, defaultVST3WindowType)); + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) +}; + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4996) // warning about overriding deprecated methods +#endif + +//============================================================================== +struct VST3ComponentHolder +{ + VST3ComponentHolder (const VST3ModuleHandle::Ptr& m) : module (m) + { + host = new VST3HostContext(); + } + + ~VST3ComponentHolder() + { + terminate(); + + component = nullptr; + host = nullptr; + factory = nullptr; + module = nullptr; + } + + // transfers ownership to the plugin instance! + AudioPluginInstance* createPluginInstance(); + + bool fetchController (ComSmartPtr& editController) + { + if (! isComponentInitialised && ! initialise()) + return false; + + // Get the IEditController: + TUID controllerCID = { 0 }; + + if (component->getControllerClassId (controllerCID) == kResultTrue && FUID (controllerCID).isValid()) + editController.loadFrom (factory, controllerCID); + + if (editController == nullptr) + { + // Try finding the IEditController the long way around: + auto numClasses = factory->countClasses(); + + for (Steinberg::int32 i = 0; i < numClasses; ++i) + { + PClassInfo classInfo; + factory->getClassInfo (i, &classInfo); + + if (std::strcmp (classInfo.category, kVstComponentControllerClass) == 0) + editController.loadFrom (factory, classInfo.cid); + } + } + + if (editController == nullptr) + editController.loadFrom (component); + + return (editController != nullptr); + } + + //============================================================================== + void fillInPluginDescription (PluginDescription& description) const + { + jassert (module != nullptr && isComponentInitialised); + + PFactoryInfo factoryInfo; + factory->getFactoryInfo (&factoryInfo); + + auto classIdx = getClassIndex (module->name); + + if (classIdx >= 0) + { + PClassInfo info; + bool success = (factory->getClassInfo (classIdx, &info) == kResultOk); + ignoreUnused (success); + jassert (success); + + ComSmartPtr pf2; + ComSmartPtr pf3; + + ScopedPointer info2; + ScopedPointer infoW; + + if (pf2.loadFrom (factory)) + { + info2 = new PClassInfo2(); + pf2->getClassInfo2 (classIdx, info2); + } + else + { + info2 = nullptr; + } + + if (pf3.loadFrom (factory)) + { + pf3->setHostContext (host->getFUnknown()); + infoW = new PClassInfoW(); + pf3->getClassInfoUnicode (classIdx, infoW); + } + else + { + infoW = nullptr; + } + + Vst::BusInfo bus; + int totalNumInputChannels = 0, totalNumOutputChannels = 0; + + int n = component->getBusCount(Vst::kAudio, Vst::kInput); + for (int i = 0; i < n; ++i) + if (component->getBusInfo (Vst::kAudio, Vst::kInput, i, bus) == kResultOk) + totalNumInputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); + + n = component->getBusCount(Vst::kAudio, Vst::kOutput); + for (int i = 0; i < n; ++i) + if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) + totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); + + createPluginDescription (description, module->file, + factoryInfo.vendor, module->name, + info, info2, infoW, + totalNumInputChannels, + totalNumOutputChannels); + + return; + } + + jassertfalse; + } + + //============================================================================== + bool initialise() + { + if (isComponentInitialised) return true; + + #if JUCE_WINDOWS + // On Windows it's highly advisable to create your plugins using the message thread, + // because many plugins need a chance to create HWNDs that will get their messages + // delivered by the main message thread, and that's not possible from a background thread. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #endif + + factory = ComSmartPtr (module->getPluginFactory()); + + int classIdx; + if ((classIdx = getClassIndex (module->name)) < 0) + return false; + + PClassInfo info; + if (factory->getClassInfo (classIdx, &info) != kResultOk) + return false; + + if (! component.loadFrom (factory, info.cid) || component == nullptr) + return false; + + if (warnOnFailure (component->initialize (host->getFUnknown())) != kResultOk) + return false; + + isComponentInitialised = true; + + return true; + } + + void terminate() + { + if (isComponentInitialised) component->terminate(); + isComponentInitialised = false; + } + + //============================================================================== + int getClassIndex (const String& className) const + { + PClassInfo info; + const Steinberg::int32 numClasses = factory->countClasses(); + + for (Steinberg::int32 j = 0; j < numClasses; ++j) + if (factory->getClassInfo (j, &info) == kResultOk + && std::strcmp (info.category, kVstAudioEffectClass) == 0 + && toString (info.name).trim() == className) + return j; + + return -1; + } + + //============================================================================== + VST3ModuleHandle::Ptr module; + ComSmartPtr factory; + ComSmartPtr host; + ComSmartPtr component; + + bool isComponentInitialised = false; +}; + +//============================================================================== +struct VST3PluginInstance : public AudioPluginInstance +{ + VST3PluginInstance (VST3ComponentHolder* componentHolder) + : AudioPluginInstance (getBusProperties (componentHolder->component)), + holder (componentHolder), + inputParameterChanges (new ParamValueQueueList()), + outputParameterChanges (new ParamValueQueueList()), + midiInputs (new MidiEventList()), + midiOutputs (new MidiEventList()) + { + holder->host->setPlugin (this); + } + + ~VST3PluginInstance() + { + jassert (getActiveEditor() == nullptr); // You must delete any editors before deleting the plugin instance! + + releaseResources(); + + if (editControllerConnection != nullptr && componentConnection != nullptr) + { + editControllerConnection->disconnect (componentConnection); + componentConnection->disconnect (editControllerConnection); + } + + editController->setComponentHandler (nullptr); + + if (isControllerInitialised) editController->terminate(); + holder->terminate(); + + componentConnection = nullptr; + editControllerConnection = nullptr; + unitData = nullptr; + unitInfo = nullptr; + programListData = nullptr; + componentHandler2 = nullptr; + componentHandler = nullptr; + processor = nullptr; + editController2 = nullptr; + editController = nullptr; + } + + bool initialise() + { + #if JUCE_WINDOWS + // On Windows it's highly advisable to create your plugins using the message thread, + // because many plugins need a chance to create HWNDs that will get their messages + // delivered by the main message thread, and that's not possible from a background thread. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #endif + + if (! holder->initialise()) + return false; + + if (! isControllerInitialised) + { + if (! holder->fetchController (editController)) + return false; + } + + // (May return an error if the plugin combines the IComponent and IEditController implementations) + editController->initialize (holder->host->getFUnknown()); + + isControllerInitialised = true; + editController->setComponentHandler (holder->host); + grabInformationObjects(); + interconnectComponentAndController(); + synchroniseStates(); + syncProgramNames(); + setupIO(); + return true; + } + + void* getPlatformSpecificData() override { return holder->component; } + void refreshParameterList() override {} + + //============================================================================== + const String getName() const override + { + VST3ModuleHandle::Ptr& module = holder->module; + return module != nullptr ? module->name : String(); + } + + void repopulateArrangements (Array& inputArrangements, Array& outputArrangements) const + { + inputArrangements.clearQuick(); + outputArrangements.clearQuick(); + + auto numInputAudioBuses = getBusCount (true); + auto numOutputAudioBuses = getBusCount (false); + + for (int i = 0; i < numInputAudioBuses; ++i) + inputArrangements.add (getArrangementForBus (processor, true, i)); + + for (int i = 0; i < numOutputAudioBuses; ++i) + outputArrangements.add (getArrangementForBus (processor, false, i)); + } + + void processorLayoutsToArrangements (Array& inputArrangements, Array& outputArrangements) + { + inputArrangements.clearQuick(); + outputArrangements.clearQuick(); + + auto numInputBuses = getBusCount (true); + auto numOutputBuses = getBusCount (false); + + for (int i = 0; i < numInputBuses; ++i) + inputArrangements.add (getVst3SpeakerArrangement (getBus (true, i)->getLastEnabledLayout())); + + for (int i = 0; i < numOutputBuses; ++i) + outputArrangements.add (getVst3SpeakerArrangement (getBus (false, i)->getLastEnabledLayout())); + } + + void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override + { + // Avoid redundantly calling things like setActive, which can be a heavy-duty call for some plugins: + if (isActive + && getSampleRate() == newSampleRate + && getBlockSize() == estimatedSamplesPerBlock) + return; + + using namespace Vst; + + ProcessSetup setup; + setup.symbolicSampleSize = isUsingDoublePrecision() ? kSample64 : kSample32; + setup.maxSamplesPerBlock = estimatedSamplesPerBlock; + setup.sampleRate = newSampleRate; + setup.processMode = isNonRealtime() ? kOffline : kRealtime; + + warnOnFailure (processor->setupProcessing (setup)); + + holder->initialise(); + editController->setComponentHandler (holder->host); + + + Array inputArrangements, outputArrangements; + processorLayoutsToArrangements (inputArrangements, outputArrangements); + + warnOnFailure (processor->setBusArrangements (inputArrangements.getRawDataPointer(), inputArrangements.size(), + outputArrangements.getRawDataPointer(), outputArrangements.size())); + + Array actualInArr, actualOutArr; + repopulateArrangements (actualInArr, actualOutArr); + + jassert (actualInArr == inputArrangements && actualOutArr == outputArrangements); + + // Needed for having the same sample rate in processBlock(); some plugins need this! + setRateAndBufferSizeDetails (newSampleRate, estimatedSamplesPerBlock); + + auto numInputBuses = getBusCount (true); + auto numOutputBuses = getBusCount (false); + + for (int i = 0; i < numInputBuses; ++i) + warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kInput, i, getBus (true, i)->isEnabled() ? 1 : 0)); + + for (int i = 0; i < numOutputBuses; ++i) + warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kOutput, i, getBus (false, i)->isEnabled() ? 1 : 0)); + + setLatencySamples (jmax (0, (int) processor->getLatencySamples())); + cachedBusLayouts = getBusesLayout(); + + warnOnFailure (holder->component->setActive (true)); + warnOnFailure (processor->setProcessing (true)); + + isActive = true; + } + + void releaseResources() override + { + if (! isActive) + return; // Avoids redundantly calling things like setActive + + isActive = false; + + setStateForAllMidiBuses (false); + + if (processor != nullptr) + warnOnFailure (processor->setProcessing (false)); + + if (holder->component != nullptr) + warnOnFailure (holder->component->setActive (false)); + } + + bool supportsDoublePrecisionProcessing() const override + { + return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (! isUsingDoublePrecision()); + + if (isActive && processor != nullptr) + processAudio (buffer, midiMessages, Vst::kSample32); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + + if (isActive && processor != nullptr) + processAudio (buffer, midiMessages, Vst::kSample64); + } + + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, + Vst::SymbolicSampleSizes sampleSize) + { + using namespace Vst; + auto numSamples = buffer.getNumSamples(); + + auto numInputAudioBuses = getBusCount (true); + auto numOutputAudioBuses = getBusCount (false); + + ProcessData data; + data.processMode = isNonRealtime() ? kOffline : kRealtime; + data.symbolicSampleSize = sampleSize; + data.numInputs = numInputAudioBuses; + data.numOutputs = numOutputAudioBuses; + data.inputParameterChanges = inputParameterChanges; + data.outputParameterChanges = outputParameterChanges; + data.numSamples = (Steinberg::int32) numSamples; + + updateTimingInformation (data, getSampleRate()); + + for (int i = getTotalNumInputChannels(); i < buffer.getNumChannels(); ++i) + buffer.clear (i, 0, numSamples); + + associateTo (data, buffer); + associateTo (data, midiMessages); + + processor->process (data); + + MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); + + inputParameterChanges->clearAllQueues(); + } + + //============================================================================== + bool canAddBus (bool) const override { return false; } + bool canRemoveBus (bool) const override { return false; } + + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + // if the processor is not active, we ask the underlying plug-in if the + // layout is actually supported + if (! isActive) + return canApplyBusesLayout (layouts); + + // not much we can do to check the layout while the audio processor is running + // Let's at least check if it is a VST3 compatible layout + for (int dir = 0; dir < 2; ++dir) + { + bool isInput = (dir == 0); + auto n = getBusCount (isInput); + + for (int i = 0; i < n; ++i) + if (getChannelLayoutOfBus (isInput, i).isDiscreteLayout()) + return false; + } + + return true; + } + + bool syncBusLayouts (const BusesLayout& layouts) const + { + for (int dir = 0; dir < 2; ++dir) + { + bool isInput = (dir == 0); + auto n = getBusCount (isInput); + const Vst::BusDirection vstDir = (isInput ? Vst::kInput : Vst::kOutput); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + const bool isEnabled = (! layouts.getChannelSet (isInput, busIdx).isDisabled()); + + if (holder->component->activateBus (Vst::kAudio, vstDir, busIdx, (isEnabled ? 1 : 0)) != kResultOk) + return false; + } + } + + Array inputArrangements, outputArrangements; + + for (int i = 0; i < layouts.inputBuses.size(); ++i) + { + const auto& requested = layouts.getChannelSet (true, i); + inputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested)); + } + + for (int i = 0; i < layouts.outputBuses.size(); ++i) + { + const auto& requested = layouts.getChannelSet (false, i); + outputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (false, i)->getLastEnabledLayout() : requested)); + } + + if (processor->setBusArrangements (inputArrangements.getRawDataPointer(), inputArrangements.size(), + outputArrangements.getRawDataPointer(), outputArrangements.size()) != kResultTrue) + return false; + + // check if the layout matches the request + Array actualIn, actualOut; + repopulateArrangements (actualIn, actualOut); + + return (actualIn == inputArrangements && actualOut == outputArrangements); + } + + bool canApplyBusesLayout (const BusesLayout& layouts) const override + { + // someone tried to change the layout while the AudioProcessor is running + // call releaseResources first! + jassert (! isActive); + + bool result = syncBusLayouts (layouts); + + // didn't succeed? Make sure it's back in it's original state + if (! result) + syncBusLayouts (getBusesLayout()); + + return result; + } + + //============================================================================== + void updateTrackProperties (const TrackProperties& properties) override + { + if (trackInfoListener != nullptr) + { + ComSmartPtr l (new TrackPropertiesAttributeList (properties)); + trackInfoListener->setChannelContextInfos (l); + } + } + + struct TrackPropertiesAttributeList : public Vst::IAttributeList + { + TrackPropertiesAttributeList (const TrackProperties& properties) : props (properties) {} + virtual ~TrackPropertiesAttributeList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID queryIid, void** obj) override + { + TEST_FOR_AND_RETURN_IF_VALID (queryIid, Vst::IAttributeList) + TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (queryIid, FUnknown, Vst::IAttributeList) + + *obj = nullptr; + return kNotImplemented; + } + + tresult PLUGIN_API setInt (AttrID, Steinberg::int64) override { return kOutOfMemory; } + tresult PLUGIN_API setFloat (AttrID, double) override { return kOutOfMemory; } + tresult PLUGIN_API setString (AttrID, const Vst::TChar*) override { return kOutOfMemory; } + tresult PLUGIN_API setBinary (AttrID, const void*, Steinberg::uint32) override { return kOutOfMemory; } + tresult PLUGIN_API getFloat (AttrID, double&) override { return kResultFalse; } + tresult PLUGIN_API getBinary (AttrID, const void*&, Steinberg::uint32&) override { return kResultFalse; } + + tresult PLUGIN_API getString (AttrID id, Vst::TChar* string, Steinberg::uint32 size) override + { + if (! std::strcmp (id, Vst::ChannelContext::kChannelNameKey)) + { + Steinberg::String str (props.name.toRawUTF8()); + str.copyTo (string, 0, (Steinberg::int32) jmin (size, (Steinberg::uint32) std::numeric_limits::max())); + + return kResultTrue; + } + + return kResultFalse; + } + + tresult PLUGIN_API getInt (AttrID id, Steinberg::int64& value) override + { + if (! std::strcmp (Vst::ChannelContext::kChannelNameLengthKey, id)) value = props.name.length(); + else if (! std::strcmp (Vst::ChannelContext::kChannelColorKey, id)) value = static_cast (props.colour.getARGB()); + else return kResultFalse; + + return kResultTrue; + } + + Atomic refCount; + TrackProperties props; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrackPropertiesAttributeList) + }; + + //============================================================================== + String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const + { + auto numBuses = getNumSingleDirectionBusesFor (holder->component, forInput, forAudioChannel); + int numCountedChannels = 0; + + for (int i = 0; i < numBuses; ++i) + { + auto busInfo = getBusInfo (forInput, forAudioChannel, i); + + numCountedChannels += busInfo.channelCount; + + if (channelIndex < numCountedChannels) + return toString (busInfo.name); + } + + return {}; + } + + const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, true, true); } + const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, false, true); } + + bool isInputChannelStereoPair (int channelIndex) const override + { + int busIdx; + return getOffsetInBusBufferForAbsoluteChannelIndex (true, channelIndex, busIdx) >= 0 + && getBusInfo (true, true, busIdx).channelCount == 2; + } + + bool isOutputChannelStereoPair (int channelIndex) const override + { + int busIdx; + return getOffsetInBusBufferForAbsoluteChannelIndex (false, channelIndex, busIdx) >= 0 + && getBusInfo (false, true, busIdx).channelCount == 2; + } + + bool acceptsMidi() const override { return getBusInfo (true, false).channelCount > 0; } + bool producesMidi() const override { return getBusInfo (false, false).channelCount > 0; } + + //============================================================================== + /** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */ + double getTailLengthSeconds() const override + { + if (processor != nullptr) + { + auto sampleRate = getSampleRate(); + + if (sampleRate > 0.0) + return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate; + } + + return 0.0; + } + + //============================================================================== + AudioProcessorEditor* createEditor() override + { + if (auto* view = tryCreatingView()) + return new VST3PluginWindow (this, view); + + return nullptr; + } + + bool hasEditor() const override + { + // (if possible, avoid creating a second instance of the editor, because that crashes some plugins) + if (getActiveEditor() != nullptr) + return true; + + ComSmartPtr view (tryCreatingView(), false); + return view != nullptr; + } + + //============================================================================== + int getNumParameters() override + { + if (editController != nullptr) + return (int) editController->getParameterCount(); + + return 0; + } + + const String getParameterName (int parameterIndex) override + { + return toString (getParameterInfoForIndex (parameterIndex).title); + } + + const String getParameterText (int parameterIndex) override + { + if (editController != nullptr) + { + auto id = getParameterInfoForIndex (parameterIndex).id; + + Vst::String128 result; + warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result)); + + return toString (result); + } + + return {}; + } + + int getParameterNumSteps (int parameterIndex) override + { + if (editController != nullptr) + { + const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; + + if (numSteps > 0) + return numSteps; + } + + return AudioProcessor::getDefaultNumParameterSteps(); + } + + bool isParameterDiscrete (int parameterIndex) const override + { + if (editController != nullptr) + { + const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; + return numSteps > 0; + } + + return false; + } + + float getParameter (int parameterIndex) override + { + if (editController != nullptr) + { + auto id = getParameterInfoForIndex (parameterIndex).id; + return (float) editController->getParamNormalized (id); + } + + return 0.0f; + } + + void setParameter (int parameterIndex, float newValue) override + { + if (editController != nullptr) + { + auto paramID = getParameterInfoForIndex (parameterIndex).id; + editController->setParamNormalized (paramID, (double) newValue); + + Steinberg::int32 index; + inputParameterChanges->addParameterData (paramID, index)->addPoint (0, newValue, index); + } + } + + //============================================================================== + int getNumPrograms() override { return programNames.size(); } + const String getProgramName (int index) override { return programNames[index]; } + int getCurrentProgram() override { return jmax (0, (int) editController->getParamNormalized (programParameterID) * (programNames.size() - 1)); } + void changeProgramName (int, const String&) override {} + + void setCurrentProgram (int program) override + { + if (programNames.size() > 0 && editController != nullptr) + { + auto value = static_cast (program) / static_cast (programNames.size()); + + editController->setParamNormalized (programParameterID, value); + Steinberg::int32 index; + inputParameterChanges->addParameterData (programParameterID, index)->addPoint (0, value, index); + } + } + + //============================================================================== + void reset() override + { + if (holder->component != nullptr && processor != nullptr) + { + processor->setProcessing (false); + holder->component->setActive (false); + + holder->component->setActive (true); + processor->setProcessing (true); + } + } + + //============================================================================== + void getStateInformation (MemoryBlock& destData) override + { + XmlElement state ("VST3PluginState"); + + appendStateFrom (state, holder->component, "IComponent"); + appendStateFrom (state, editController, "IEditController"); + + AudioProcessor::copyXmlToBinary (state, destData); + } + + void setStateInformation (const void* data, int sizeInBytes) override + { + ScopedPointer head (AudioProcessor::getXmlFromBinary (data, sizeInBytes)); + + if (head != nullptr) + { + ComSmartPtr componentStream (createMemoryStreamForState (*head, "IComponent")); + + if (componentStream != nullptr && holder->component != nullptr) + holder->component->setState (componentStream); + + if (editController != nullptr) + { + if (componentStream != nullptr) + { + int64 result; + componentStream->seek (0, IBStream::kIBSeekSet, &result); + editController->setComponentState (componentStream); + } + + ComSmartPtr controllerStream = createMemoryStreamForState (*head, "IEditController"); + + if (controllerStream != nullptr) + editController->setState (controllerStream); + } + } + } + + //============================================================================== + void fillInPluginDescription (PluginDescription& description) const override + { + holder->fillInPluginDescription (description); + } + + /** @note Not applicable to VST3 */ + void getCurrentProgramStateInformation (MemoryBlock& destData) override + { + destData.setSize (0, true); + } + + /** @note Not applicable to VST3 */ + void setCurrentProgramStateInformation (const void* data, int sizeInBytes) override + { + ignoreUnused (data, sizeInBytes); + } + + //============================================================================== + // NB: this class and its subclasses must be public to avoid problems in + // DLL builds under MSVC. + struct ParamValueQueueList : public Vst::IParameterChanges + { + ParamValueQueueList() {} + virtual ~ParamValueQueueList() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + Steinberg::int32 PLUGIN_API getParameterCount() override { return numQueuesUsed; } + Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32 index) override { return isPositiveAndBelow (static_cast (index), numQueuesUsed) ? queues[(int) index] : nullptr; } + + Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID& id, Steinberg::int32& index) override + { + for (int i = numQueuesUsed; --i >= 0;) + { + if (queues.getUnchecked (i)->getParameterId() == id) + { + index = (Steinberg::int32) i; + return queues.getUnchecked (i); + } + } + + index = numQueuesUsed++; + ParamValueQueue* valueQueue = (index < queues.size() ? queues[index] + : queues.add (new ParamValueQueue())); + + valueQueue->clear(); + valueQueue->setParamID (id); + + return valueQueue; + } + + void clearAllQueues() noexcept + { + numQueuesUsed = 0; + } + + struct ParamValueQueue : public Vst::IParamValueQueue + { + ParamValueQueue() + { + points.ensureStorageAllocated (1024); + } + + virtual ~ParamValueQueue() {} + + void setParamID (Vst::ParamID pID) noexcept { paramID = pID; } + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + Steinberg::Vst::ParamID PLUGIN_API getParameterId() override { return paramID; } + Steinberg::int32 PLUGIN_API getPointCount() override { return (Steinberg::int32) points.size(); } + + Steinberg::tresult PLUGIN_API getPoint (Steinberg::int32 index, + Steinberg::int32& sampleOffset, + Steinberg::Vst::ParamValue& value) override + { + const ScopedLock sl (pointLock); + + if (isPositiveAndBelow ((int) index, points.size())) + { + ParamPoint e (points.getUnchecked ((int) index)); + sampleOffset = e.sampleOffset; + value = e.value; + return kResultTrue; + } + + sampleOffset = -1; + value = 0.0; + return kResultFalse; + } + + Steinberg::tresult PLUGIN_API addPoint (Steinberg::int32 sampleOffset, + Steinberg::Vst::ParamValue value, + Steinberg::int32& index) override + { + ParamPoint p = { sampleOffset, value }; + + const ScopedLock sl (pointLock); + index = (Steinberg::int32) points.size(); + points.add (p); + return kResultTrue; + } + + void clear() noexcept + { + const ScopedLock sl (pointLock); + points.clearQuick(); + } + + private: + struct ParamPoint + { + Steinberg::int32 sampleOffset; + Steinberg::Vst::ParamValue value; + }; + + Atomic refCount; + Vst::ParamID paramID = static_cast (-1); + Array points; + CriticalSection pointLock; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParamValueQueue) + }; + + Atomic refCount; + OwnedArray queues; + int numQueuesUsed = 0; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParamValueQueueList) + }; + +private: + //============================================================================== + ScopedPointer holder; + + friend VST3HostContext; + + // Information objects: + String company; + ScopedPointer info; + ScopedPointer info2; + ScopedPointer infoW; + + // Rudimentary interfaces: + ComSmartPtr editController; + ComSmartPtr editController2; + ComSmartPtr processor; + ComSmartPtr componentHandler; + ComSmartPtr componentHandler2; + ComSmartPtr unitInfo; + ComSmartPtr unitData; + ComSmartPtr programListData; + ComSmartPtr componentConnection, editControllerConnection; + ComSmartPtr trackInfoListener; + + /** The number of IO buses MUST match that of the plugin, + even if there aren't enough channels to process, + as very poorly specified by the Steinberg SDK + */ + VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap; + Array inputBuses, outputBuses; + AudioProcessor::BusesLayout cachedBusLayouts; + + StringArray programNames; + Vst::ParamID programParameterID = (Vst::ParamID) -1; + + //============================================================================== + template + static void appendStateFrom (XmlElement& head, ComSmartPtr& object, const String& identifier) + { + if (object != nullptr) + { + Steinberg::MemoryStream stream; + + if (object->getState (&stream) == kResultTrue) + { + MemoryBlock info (stream.getData(), (size_t) stream.getSize()); + head.createNewChildElement (identifier)->addTextElement (info.toBase64Encoding()); + } + } + } + + static Steinberg::MemoryStream* createMemoryStreamForState (XmlElement& head, StringRef identifier) + { + Steinberg::MemoryStream* stream = nullptr; + + if (auto* state = head.getChildByName (identifier)) + { + MemoryBlock mem; + + if (mem.fromBase64Encoding (state->getAllSubText())) + { + stream = new Steinberg::MemoryStream(); + stream->setSize ((TSize) mem.getSize()); + mem.copyTo (stream->getData(), 0, mem.getSize()); + } + } + + return stream; + } + + ComSmartPtr inputParameterChanges, outputParameterChanges; + ComSmartPtr midiInputs, midiOutputs; + Vst::ProcessContext timingInfo; //< Only use this in processBlock()! + bool isControllerInitialised = false, isActive = false; + + //============================================================================== + /** Some plugins need to be "connected" to intercommunicate between their implemented classes */ + void interconnectComponentAndController() + { + componentConnection.loadFrom (holder->component); + editControllerConnection.loadFrom (editController); + + if (componentConnection != nullptr && editControllerConnection != nullptr) + { + warnOnFailure (componentConnection->connect (editControllerConnection)); + warnOnFailure (editControllerConnection->connect (componentConnection)); + } + } + + void synchroniseStates() + { + Steinberg::MemoryStream stream; + + if (holder->component->getState (&stream) == kResultTrue) + if (stream.seek (0, Steinberg::IBStream::kIBSeekSet, nullptr) == kResultTrue) + warnOnFailure (editController->setComponentState (&stream)); + } + + void grabInformationObjects() + { + processor.loadFrom (holder->component); + unitInfo.loadFrom (holder->component); + programListData.loadFrom (holder->component); + unitData.loadFrom (holder->component); + editController2.loadFrom (holder->component); + componentHandler.loadFrom (holder->component); + componentHandler2.loadFrom (holder->component); + trackInfoListener.loadFrom (holder->component); + + if (processor == nullptr) processor.loadFrom (editController); + if (unitInfo == nullptr) unitInfo.loadFrom (editController); + if (programListData == nullptr) programListData.loadFrom (editController); + if (unitData == nullptr) unitData.loadFrom (editController); + if (editController2 == nullptr) editController2.loadFrom (editController); + if (componentHandler == nullptr) componentHandler.loadFrom (editController); + if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); + if (trackInfoListener == nullptr) trackInfoListener.loadFrom (editController); + } + + void setStateForAllMidiBuses (bool newState) + { + setStateForAllBusesOfType (holder->component, newState, true, false); // Activate/deactivate MIDI inputs + setStateForAllBusesOfType (holder->component, newState, false, false); // Activate/deactivate MIDI outputs + } + + void setupIO() + { + setStateForAllMidiBuses (true); + + Vst::ProcessSetup setup; + setup.symbolicSampleSize = Vst::kSample32; + setup.maxSamplesPerBlock = 1024; + setup.sampleRate = 44100.0; + setup.processMode = Vst::kRealtime; + + warnOnFailure (processor->setupProcessing (setup)); + + cachedBusLayouts = getBusesLayout(); + setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock); + } + + static AudioProcessor::BusesProperties getBusProperties (ComSmartPtr& component) + { + AudioProcessor::BusesProperties busProperties; + ComSmartPtr processor; + processor.loadFrom (component.get()); + + for (int dirIdx = 0; dirIdx < 2; ++dirIdx) + { + const bool isInput = (dirIdx == 0); + const Vst::BusDirection dir = (isInput ? Vst::kInput : Vst::kOutput); + const int numBuses = component->getBusCount (Vst::kAudio, dir); + + for (int i = 0; i < numBuses; ++i) + { + Vst::BusInfo info; + + if (component->getBusInfo (Vst::kAudio, dir, (Steinberg::int32) i, info) != kResultOk) + continue; + + if (info.channelCount == 0) + continue; + + AudioChannelSet layout = AudioChannelSet::discreteChannels (info.channelCount); + + Vst::SpeakerArrangement arr; + if (processor != nullptr && processor->getBusArrangement (dir, i, arr) == kResultOk) + layout = getChannelSetForSpeakerArrangement (arr); + + busProperties.addBus (isInput, toString (info.name), layout, + (info.flags & Vst::BusInfo::kDefaultActive) != 0); + } + } + + return busProperties; + } + + //============================================================================== + Vst::BusInfo getBusInfo (bool forInput, bool forAudio, int index = 0) const + { + Vst::BusInfo busInfo; + busInfo.mediaType = forAudio ? Vst::kAudio : Vst::kEvent; + busInfo.direction = forInput ? Vst::kInput : Vst::kOutput; + busInfo.channelCount = 0; + + holder->component->getBusInfo (busInfo.mediaType, busInfo.direction, + (Steinberg::int32) index, busInfo); + return busInfo; + } + + //============================================================================== + /** @note An IPlugView, when first created, should start with a ref-count of 1! */ + IPlugView* tryCreatingView() const + { + IPlugView* v = editController->createView (Vst::ViewType::kEditor); + + if (v == nullptr) v = editController->createView (nullptr); + if (v == nullptr) editController->queryInterface (IPlugView::iid, (void**) &v); + + return v; + } + + //============================================================================== + template + void associateTo (Vst::ProcessData& destination, AudioBuffer& buffer) + { + VST3BufferExchange::mapBufferToBuses (inputBuses, inputBusMap.get(), cachedBusLayouts.inputBuses, buffer); + VST3BufferExchange::mapBufferToBuses (outputBuses, outputBusMap.get(), cachedBusLayouts.outputBuses, buffer); + + destination.inputs = inputBuses.getRawDataPointer(); + destination.outputs = outputBuses.getRawDataPointer(); + } + + void associateTo (Vst::ProcessData& destination, MidiBuffer& midiBuffer) + { + midiInputs->clear(); + midiOutputs->clear(); + + MidiEventList::toEventList (*midiInputs, midiBuffer); + + destination.inputEvents = midiInputs; + destination.outputEvents = midiOutputs; + } + + void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate) + { + toProcessContext (timingInfo, getPlayHead(), processSampleRate); + destination.processContext = &timingInfo; + } + + Vst::ParameterInfo getParameterInfoForIndex (int index) const + { + Vst::ParameterInfo paramInfo = { 0 }; + + if (processor != nullptr) + editController->getParameterInfo (index, paramInfo); + + return paramInfo; + } + + Vst::ProgramListInfo getProgramListInfo (int index) const + { + Vst::ProgramListInfo paramInfo = { 0 }; + + if (unitInfo != nullptr) + unitInfo->getProgramListInfo (index, paramInfo); + + return paramInfo; + } + + void syncProgramNames() + { + programNames.clear(); + + if (processor == nullptr || editController == nullptr) + return; + + Vst::UnitID programUnitID; + Vst::ParameterInfo paramInfo = { 0 }; + + { + int idx, num = editController->getParameterCount(); + for (idx = 0; idx < num; ++idx) + if (editController->getParameterInfo (idx, paramInfo) == kResultOk + && (paramInfo.flags & Steinberg::Vst::ParameterInfo::kIsProgramChange) != 0) + break; + + if (idx >= num) + return; + + programParameterID = paramInfo.id; + programUnitID = paramInfo.unitId; + } + + if (unitInfo != nullptr) + { + Vst::UnitInfo uInfo = { 0 }; + const int unitCount = unitInfo->getUnitCount(); + + for (int idx = 0; idx < unitCount; ++idx) + { + if (unitInfo->getUnitInfo(idx, uInfo) == kResultOk + && uInfo.id == programUnitID) + { + const int programListCount = unitInfo->getProgramListCount(); + + for (int j = 0; j < programListCount; ++j) + { + Vst::ProgramListInfo programListInfo = { 0 }; + + if (unitInfo->getProgramListInfo (j, programListInfo) == kResultOk + && programListInfo.id == uInfo.programListId) + { + Vst::String128 name; + + for (int k = 0; k < programListInfo.programCount; ++k) + if (unitInfo->getProgramName (programListInfo.id, k, name) == kResultOk) + programNames.add (toString (name)); + + return; + } + } + + break; + } + } + } + + if (editController != nullptr + && paramInfo.stepCount > 0) + { + auto numPrograms = paramInfo.stepCount + 1; + + for (int i = 0; i < numPrograms; ++i) + { + auto valueNormalized = static_cast (i) / static_cast (paramInfo.stepCount); + + Vst::String128 programName; + if (editController->getParamStringByValue (paramInfo.id, valueNormalized, programName) == kResultOk) + programNames.add (toString (programName)); + } + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginInstance) +}; + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +}; + +//============================================================================== +AudioPluginInstance* VST3Classes::VST3ComponentHolder::createPluginInstance() +{ + if (! initialise()) + return nullptr; + + auto* plugin = new VST3PluginInstance (this); + host->setPlugin (plugin); + return plugin; +} + + +//============================================================================== +VST3PluginFormat::VST3PluginFormat() {} +VST3PluginFormat::~VST3PluginFormat() {} + +void VST3PluginFormat::findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) +{ + if (fileMightContainThisPluginType (fileOrIdentifier)) + VST3Classes::VST3ModuleHandle::getAllDescriptionsForFile (results, fileOrIdentifier); +} + +void VST3PluginFormat::createPluginInstance (const PluginDescription& description, double, int, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) +{ + ScopedPointer result; + + if (fileMightContainThisPluginType (description.fileOrIdentifier)) + { + File file (description.fileOrIdentifier); + + auto previousWorkingDirectory = File::getCurrentWorkingDirectory(); + file.getParentDirectory().setAsCurrentWorkingDirectory(); + + if (const VST3Classes::VST3ModuleHandle::Ptr module = VST3Classes::VST3ModuleHandle::findOrCreateModule (file, description)) + { + ScopedPointer holder = new VST3Classes::VST3ComponentHolder (module); + + if (holder->initialise()) + { + result = new VST3Classes::VST3PluginInstance (holder.release()); + + if (! result->initialise()) + result = nullptr; + } + } + + previousWorkingDirectory.setAsCurrentWorkingDirectory(); + } + + String errorMsg; + + if (result == nullptr) + errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "VST-3"); + + callback (userData, result.release(), errorMsg); +} + +bool VST3PluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept +{ + return false; +} + +bool VST3PluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) +{ + auto f = File::createFileWithoutCheckingPath (fileOrIdentifier); + + return f.hasFileExtension (".vst3") + #if JUCE_MAC + && f.exists(); + #else + && f.existsAsFile(); + #endif +} + +String VST3PluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) +{ + return fileOrIdentifier; //Impossible to tell because every VST3 is a type of shell... +} + +bool VST3PluginFormat::pluginNeedsRescanning (const PluginDescription& description) +{ + return File (description.fileOrIdentifier).getLastModificationTime() != description.lastFileModTime; +} + +bool VST3PluginFormat::doesPluginStillExist (const PluginDescription& description) +{ + return File (description.fileOrIdentifier).exists(); +} + +StringArray VST3PluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) +{ + StringArray results; + + for (int i = 0; i < directoriesToSearch.getNumPaths(); ++i) + recursiveFileSearch (results, directoriesToSearch[i], recursive); + + return results; +} + +void VST3PluginFormat::recursiveFileSearch (StringArray& results, const File& directory, const bool recursive) +{ + DirectoryIterator iter (directory, false, "*", File::findFilesAndDirectories); + + while (iter.next()) + { + auto f = iter.getFile(); + bool isPlugin = false; + + if (fileMightContainThisPluginType (f.getFullPathName())) + { + isPlugin = true; + results.add (f.getFullPathName()); + } + + if (recursive && (! isPlugin) && f.isDirectory()) + recursiveFileSearch (results, f, true); + } +} + +FileSearchPath VST3PluginFormat::getDefaultLocationsToSearch() +{ + #if JUCE_WINDOWS + auto programFiles = File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName(); + return FileSearchPath (programFiles + "\\Common Files\\VST3"); + #elif JUCE_MAC + return FileSearchPath ("/Library/Audio/Plug-Ins/VST3;~/Library/Audio/Plug-Ins/VST3"); + #else + return FileSearchPath(); + #endif +} + +} // namespace juce + +#endif // JUCE_PLUGINHOST_VST3 diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h new file mode 100644 index 000000000..4c3f140b5 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h @@ -0,0 +1,71 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +#if (JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS)) || DOXYGEN + +/** + Implements a plugin format for VST3s. +*/ +class JUCE_API VST3PluginFormat : public AudioPluginFormat +{ +public: + /** Constructor */ + VST3PluginFormat(); + + /** Destructor */ + ~VST3PluginFormat(); + + //============================================================================== + String getName() const override { return "VST3"; } + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; + bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; + String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; + bool pluginNeedsRescanning (const PluginDescription&) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; + bool doesPluginStillExist (const PluginDescription&) override; + FileSearchPath getDefaultLocationsToSearch() override; + bool canScanForPlugins() const override { return true; } + +private: + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + +private: + //============================================================================== + void recursiveFileSearch (StringArray&, const File&, bool recursive); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) +}; + +#endif // JUCE_PLUGINHOST_VST3 + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTCommon.h b/source/modules/juce_audio_processors/format_types/juce_VSTCommon.h new file mode 100644 index 000000000..50494a748 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTCommon.h @@ -0,0 +1,298 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace) +{ + struct Mapping + { + int32 vst2; + ChannelType channels[13]; + + bool matches (const Array& chans) const noexcept + { + const int n = sizeof (channels) / sizeof (ChannelType); + + for (int i = 0; i < n; ++i) + { + if (channels[i] == unknown) return (i == chans.size()); + if (i == chans.size()) return (channels[i] == unknown); + + if (channels[i] != chans.getUnchecked(i)) + return false; + } + + return true; + } + }; + + static AudioChannelSet vstArrangementTypeToChannelSet (int32 arr, int fallbackNumChannels) + { + if (arr == vstSpeakerConfigTypeEmpty) return AudioChannelSet::disabled(); + else if (arr == vstSpeakerConfigTypeMono) return AudioChannelSet::mono(); + else if (arr == vstSpeakerConfigTypeLR) return AudioChannelSet::stereo(); + else if (arr == vstSpeakerConfigTypeLRC) return AudioChannelSet::createLCR(); + else if (arr == vstSpeakerConfigTypeLRS) return AudioChannelSet::createLRS(); + else if (arr == vstSpeakerConfigTypeLRCS) return AudioChannelSet::createLCRS(); + else if (arr == vstSpeakerConfigTypeLRCLsRs) return AudioChannelSet::create5point0(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRs) return AudioChannelSet::create5point1(); + else if (arr == vstSpeakerConfigTypeLRCLsRsCs) return AudioChannelSet::create6point0(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRsCs) return AudioChannelSet::create6point1(); + else if (arr == vstSpeakerConfigTypeLRLsRsSlSr) return AudioChannelSet::create6point0Music(); + else if (arr == vstSpeakerConfigTypeLRLfeLsRsSlSr) return AudioChannelSet::create6point1Music(); + else if (arr == vstSpeakerConfigTypeLRCLsRsSlSr) return AudioChannelSet::create7point0(); + else if (arr == vstSpeakerConfigTypeLRCLsRsLcRc) return AudioChannelSet::create7point0SDDS(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRsSlSr) return AudioChannelSet::create7point1(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRsLcRc) return AudioChannelSet::create7point1SDDS(); + else if (arr == vstSpeakerConfigTypeLRLsRs) return AudioChannelSet::quadraphonic(); + + for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) + { + if (m->vst2 == arr) + { + AudioChannelSet s; + + for (int i = 0; m->channels[i] != 0; ++i) + s.addChannel (m->channels[i]); + + return s; + } + } + + return AudioChannelSet::discreteChannels (fallbackNumChannels); + } + + static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerConfiguration& arr) + { + return vstArrangementTypeToChannelSet (arr.type, arr.numberOfChannels); + } + + static int32 channelSetToVstArrangementType (AudioChannelSet channels) + { + if (channels == AudioChannelSet::disabled()) return vstSpeakerConfigTypeEmpty; + else if (channels == AudioChannelSet::mono()) return vstSpeakerConfigTypeMono; + else if (channels == AudioChannelSet::stereo()) return vstSpeakerConfigTypeLR; + else if (channels == AudioChannelSet::createLCR()) return vstSpeakerConfigTypeLRC; + else if (channels == AudioChannelSet::createLRS()) return vstSpeakerConfigTypeLRS; + else if (channels == AudioChannelSet::createLCRS()) return vstSpeakerConfigTypeLRCS; + else if (channels == AudioChannelSet::create5point0()) return vstSpeakerConfigTypeLRCLsRs; + else if (channels == AudioChannelSet::create5point1()) return vstSpeakerConfigTypeLRCLfeLsRs; + else if (channels == AudioChannelSet::create6point0()) return vstSpeakerConfigTypeLRCLsRsCs; + else if (channels == AudioChannelSet::create6point1()) return vstSpeakerConfigTypeLRCLfeLsRsCs; + else if (channels == AudioChannelSet::create6point0Music()) return vstSpeakerConfigTypeLRLsRsSlSr; + else if (channels == AudioChannelSet::create6point1Music()) return vstSpeakerConfigTypeLRLfeLsRsSlSr; + else if (channels == AudioChannelSet::create7point0()) return vstSpeakerConfigTypeLRCLsRsSlSr; + else if (channels == AudioChannelSet::create7point0SDDS()) return vstSpeakerConfigTypeLRCLsRsLcRc; + else if (channels == AudioChannelSet::create7point1()) return vstSpeakerConfigTypeLRCLfeLsRsSlSr; + else if (channels == AudioChannelSet::create7point1SDDS()) return vstSpeakerConfigTypeLRCLfeLsRsLcRc; + else if (channels == AudioChannelSet::quadraphonic()) return vstSpeakerConfigTypeLRLsRs; + + Array chans (channels.getChannelTypes()); + + if (channels == AudioChannelSet::disabled()) + return vstSpeakerConfigTypeEmpty; + + for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) + if (m->matches (chans)) + return m->vst2; + + return vstSpeakerConfigTypeUser; + } + + class VstSpeakerConfigurationHolder + { + public: + VstSpeakerConfigurationHolder () { clear(); } + VstSpeakerConfigurationHolder (const VstSpeakerConfiguration& vstConfig) { operator= (vstConfig); } + VstSpeakerConfigurationHolder (const VstSpeakerConfigurationHolder& other) { operator= (other.get()); } + VstSpeakerConfigurationHolder (VstSpeakerConfigurationHolder&& other) : storage (static_cast&&> (other.storage)) { other.clear(); } + + VstSpeakerConfigurationHolder (const AudioChannelSet& channels) + { + auto numberOfChannels = channels.size(); + VstSpeakerConfiguration& dst = *allocate (numberOfChannels); + + dst.type = channelSetToVstArrangementType (channels); + dst.numberOfChannels = numberOfChannels; + + for (int i = 0; i < dst.numberOfChannels; ++i) + { + VstIndividualSpeakerInfo& speaker = dst.speakers[i]; + + zeromem (&speaker, sizeof (VstIndividualSpeakerInfo)); + speaker.type = getSpeakerType (channels.getTypeOfChannel (i)); + } + } + + VstSpeakerConfigurationHolder& operator= (const VstSpeakerConfigurationHolder& vstConfig) { return operator=(vstConfig.get()); } + VstSpeakerConfigurationHolder& operator= (const VstSpeakerConfiguration& vstConfig) + { + VstSpeakerConfiguration& dst = *allocate (vstConfig.numberOfChannels); + + dst.type = vstConfig.type; + dst.numberOfChannels = vstConfig.numberOfChannels; + + for (int i = 0; i < dst.numberOfChannels; ++i) + dst.speakers[i] = vstConfig.speakers[i]; + + return *this; + } + + VstSpeakerConfigurationHolder& operator= (VstSpeakerConfigurationHolder && vstConfig) + { + storage = static_cast&&> (vstConfig.storage); + vstConfig.clear(); + + return *this; + } + + const VstSpeakerConfiguration& get() const { return *storage.get(); } + + private: + JUCE_LEAK_DETECTOR (VstSpeakerConfigurationHolder) + + HeapBlock storage; + + VstSpeakerConfiguration* allocate (int numChannels) + { + auto arrangementSize = sizeof (VstSpeakerConfiguration) + + sizeof (VstIndividualSpeakerInfo) * static_cast (jmax (8, numChannels) - 8); + + storage.malloc (1, arrangementSize); + return storage.get(); + } + + void clear() + { + VstSpeakerConfiguration& dst = *allocate (0); + + dst.type = vstSpeakerConfigTypeEmpty; + dst.numberOfChannels = 0; + } + }; + + static const Mapping* getMappings() noexcept + { + static const Mapping mappings[] = + { + { vstSpeakerConfigTypeMono, { centre, unknown } }, + { vstSpeakerConfigTypeLR, { left, right, unknown } }, + { vstSpeakerConfigTypeLsRs, { leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLcRc, { leftCentre, rightCentre, unknown } }, + { vstSpeakerConfigTypeSlSr, { leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeCLfe, { centre, LFE, unknown } }, + { vstSpeakerConfigTypeLRC, { left, right, centre, unknown } }, + { vstSpeakerConfigTypeLRS, { left, right, surround, unknown } }, + { vstSpeakerConfigTypeLRCLfe, { left, right, centre, LFE, unknown } }, + { vstSpeakerConfigTypeLRLfeS, { left, right, LFE, surround, unknown } }, + { vstSpeakerConfigTypeLRCS, { left, right, centre, surround, unknown } }, + { vstSpeakerConfigTypeLRLsRs, { left, right, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLfeS, { left, right, centre, LFE, surround, unknown } }, + { vstSpeakerConfigTypeLRLfeLsRs, { left, right, LFE, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLsRs, { left, right, centre, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRs, { left, right, centre, LFE, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLsRsCs, { left, right, centre, leftSurround, rightSurround, surround, unknown } }, + { vstSpeakerConfigTypeLRLsRsSlSr, { left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsCs, { left, right, centre, LFE, leftSurround, rightSurround, surround, unknown } }, + { vstSpeakerConfigTypeLRLfeLsRsSlSr, { left, right, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLsRsLcRc, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, + { vstSpeakerConfigTypeLRCLsRsSlSr, { left, right, centre, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsLcRc, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLsRsLcRcCs, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, + { vstSpeakerConfigTypeLRCLsRsCsSlSr, { left, right, centre, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, LFE2, unknown } }, + { vstSpeakerConfigTypeEmpty, { unknown } } + }; + + return mappings; + } + + static inline int32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept + { + switch (type) + { + case AudioChannelSet::left: return vstIndividualSpeakerTypeLeft; + case AudioChannelSet::right: return vstIndividualSpeakerTypeRight; + case AudioChannelSet::centre: return vstIndividualSpeakerTypeCentre; + case AudioChannelSet::LFE: return vstIndividualSpeakerTypeLFE; + case AudioChannelSet::leftSurround: return vstIndividualSpeakerTypeLeftSurround; + case AudioChannelSet::rightSurround: return vstIndividualSpeakerTypeRightSurround; + case AudioChannelSet::leftCentre: return vstIndividualSpeakerTypeLeftCentre; + case AudioChannelSet::rightCentre: return vstIndividualSpeakerTypeRightCentre; + case AudioChannelSet::surround: return vstIndividualSpeakerTypeSurround; + case AudioChannelSet::leftSurroundRear: return vstIndividualSpeakerTypeLeftRearSurround; + case AudioChannelSet::rightSurroundRear: return vstIndividualSpeakerTypeRightRearSurround; + case AudioChannelSet::topMiddle: return vstIndividualSpeakerTypeTopMiddle; + case AudioChannelSet::topFrontLeft: return vstIndividualSpeakerTypeTopFrontLeft; + case AudioChannelSet::topFrontCentre: return vstIndividualSpeakerTypeTopFrontCentre; + case AudioChannelSet::topFrontRight: return vstIndividualSpeakerTypeTopFrontRight; + case AudioChannelSet::topRearLeft: return vstIndividualSpeakerTypeTopRearLeft; + case AudioChannelSet::topRearCentre: return vstIndividualSpeakerTypeTopRearCentre; + case AudioChannelSet::topRearRight: return vstIndividualSpeakerTypeTopRearRight; + case AudioChannelSet::LFE2: return vstIndividualSpeakerTypeLFE2; + default: break; + } + + return 0; + } + + static inline AudioChannelSet::ChannelType getChannelType (int32 type) noexcept + { + switch (type) + { + case vstIndividualSpeakerTypeLeft: return AudioChannelSet::left; + case vstIndividualSpeakerTypeRight: return AudioChannelSet::right; + case vstIndividualSpeakerTypeCentre: return AudioChannelSet::centre; + case vstIndividualSpeakerTypeLFE: return AudioChannelSet::LFE; + case vstIndividualSpeakerTypeLeftSurround: return AudioChannelSet::leftSurround; + case vstIndividualSpeakerTypeRightSurround: return AudioChannelSet::rightSurround; + case vstIndividualSpeakerTypeLeftCentre: return AudioChannelSet::leftCentre; + case vstIndividualSpeakerTypeRightCentre: return AudioChannelSet::rightCentre; + case vstIndividualSpeakerTypeSurround: return AudioChannelSet::surround; + case vstIndividualSpeakerTypeLeftRearSurround: return AudioChannelSet::leftSurroundRear; + case vstIndividualSpeakerTypeRightRearSurround: return AudioChannelSet::rightSurroundRear; + case vstIndividualSpeakerTypeTopMiddle: return AudioChannelSet::topMiddle; + case vstIndividualSpeakerTypeTopFrontLeft: return AudioChannelSet::topFrontLeft; + case vstIndividualSpeakerTypeTopFrontCentre: return AudioChannelSet::topFrontCentre; + case vstIndividualSpeakerTypeTopFrontRight: return AudioChannelSet::topFrontRight; + case vstIndividualSpeakerTypeTopRearLeft: return AudioChannelSet::topRearLeft; + case vstIndividualSpeakerTypeTopRearCentre: return AudioChannelSet::topRearCentre; + case vstIndividualSpeakerTypeTopRearRight: return AudioChannelSet::topRearRight; + case vstIndividualSpeakerTypeLFE2: return AudioChannelSet::LFE2; + default: break; + } + + return AudioChannelSet::unknown; + } +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/source/modules/juce_audio_processors/format_types/juce_VSTInterface.h new file mode 100644 index 000000000..4d31c312a --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTInterface.h @@ -0,0 +1,485 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +#define JUCE_VSTINTERFACE_H_INCLUDED + +using namespace juce; + +#if JUCE_MSVC + #define VSTINTERFACECALL __cdecl + #pragma pack(push) + #pragma pack(8) +#elif JUCE_MAC || JUCE_IOS + #define VSTINTERFACECALL + #if JUCE_64BIT + #pragma options align=power + #else + #pragma options align=mac68k + #endif +#else + #define VSTINTERFACECALL + #pragma pack(push, 8) +#endif + +const int32 juceVstInterfaceVersion = 2400; +const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. + +//============================================================================== +struct VstEffectInterface +{ + int32 interfaceIdentifier; + pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); + void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); + void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); + float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); + int32 numPrograms; + int32 numParameters; + int32 numInputChannels; + int32 numOutputChannels; + int32 flags; + pointer_sized_int hostSpace1; + pointer_sized_int hostSpace2; + int32 latency; + int32 deprecated1; + int32 deprecated2; + float deprecated3; + void* effectPointer; + void* userPointer; + int32 plugInIdentifier; + int32 plugInVersion; + void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); + void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); + char emptySpace[56]; +}; + +typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); + +enum VstEffectInterfaceFlags +{ + vstEffectFlagHasEditor = 1, + vstEffectFlagInplaceAudio = 16, + vstEffectFlagDataInChunks = 32, + vstEffectFlagIsSynth = 256, + vstEffectFlagInplaceDoubleAudio = 4096 +}; + +//============================================================================== +enum VstHostToPlugInOpcodes +{ + plugInOpcodeOpen, + plugInOpcodeClose, + plugInOpcodeSetCurrentProgram, + plugInOpcodeGetCurrentProgram, + plugInOpcodeSetCurrentProgramName, + plugInOpcodeGetCurrentProgramName, + plugInOpcodeGetParameterLabel, + plugInOpcodeGetParameterText, + plugInOpcodeGetParameterName, + plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, + plugInOpcodeSetBlockSize, + plugInOpcodeResumeSuspend, + plugInOpcodeGetEditorBounds, + plugInOpcodeOpenEditor, + plugInOpcodeCloseEditor, + plugInOpcodeDrawEditor, + plugInOpcodeGetMouse, + plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, + plugInOpcodeeffEditorTop, + plugInOpcodeSleepEditor, + plugInOpcodeIdentify, + plugInOpcodeGetData, + plugInOpcodeSetData, + plugInOpcodePreAudioProcessingEvents, + plugInOpcodeIsParameterAutomatable, + plugInOpcodeParameterValueForText, + plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, + plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, + plugInOpcodeConnectOutput, + plugInOpcodeGetInputPinProperties, + plugInOpcodeGetOutputPinProperties, + plugInOpcodeGetPlugInCategory, + plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, + plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, + plugInOpcodeGetPlugInName, + plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, + plugInOpcodeGetManufacturerProductName, + plugInOpcodeGetManufacturerVersion, + plugInOpcodeManufacturerSpecific, + plugInOpcodeCanPlugInDo, + plugInOpcodeGetTailSize, + plugInOpcodeIdle, + plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, + plugInOpcodeGetVstInterfaceVersion, + plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, + plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, + plugInOpcodeNextPlugInUniqueID, + plugInOpcodeStartProcess, + plugInOpcodeStopProcess, + plugInOpcodeSetNumberOfSamplesToProcess, + plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, + pluginOpcodeGetNumMidiInputChannels, + pluginOpcodeGetNumMidiOutputChannels, + plugInOpcodeMaximum = pluginOpcodeGetNumMidiOutputChannels +}; + + +enum VstPlugInToHostOpcodes +{ + hostOpcodeParameterChanged, + hostOpcodeVstVersion, + hostOpcodeCurrentId, + hostOpcodeIdle, + hostOpcodePinConnected, + hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, + hostOpcodeGetTimingInfo, + hostOpcodePreAudioProcessingEvents, + hostOpcodeSetTime, + hostOpcodeTempoAt, + hostOpcodeGetNumberOfAutomatableParameters, + hostOpcodeGetParameterInterval, + hostOpcodeIOModified, + hostOpcodeNeedsIdle, + hostOpcodeWindowSize, + hostOpcodeGetSampleRate, + hostOpcodeGetBlockSize, + hostOpcodeGetInputLatency, + hostOpcodeGetOutputLatency, + hostOpcodeGetPreviousPlugIn, + hostOpcodeGetNextPlugIn, + hostOpcodeWillReplace, + hostOpcodeGetCurrentAudioProcessingLevel, + hostOpcodeGetAutomationState, + hostOpcodeOfflineStart, + hostOpcodeOfflineReadSource, + hostOpcodeOfflineWrite, + hostOpcodeOfflineGetCurrentPass, + hostOpcodeOfflineGetCurrentMetaPass, + hostOpcodeSetOutputSampleRate, + hostOpcodeGetOutputSpeakerConfiguration, + hostOpcodeGetManufacturerName, + hostOpcodeGetProductName, + hostOpcodeGetManufacturerVersion, + hostOpcodeManufacturerSpecific, + hostOpcodeSetIcon, + hostOpcodeCanHostDo, + hostOpcodeGetLanguage, + hostOpcodeOpenEditorWindow, + hostOpcodeCloseEditorWindow, + hostOpcodeGetDirectory, + hostOpcodeUpdateView, + hostOpcodeParameterChangeGestureBegin, + hostOpcodeParameterChangeGestureEnd, +}; + +//============================================================================== +enum VstProcessingSampleType +{ + vstProcessingSampleTypeFloat, + vstProcessingSampleTypeDouble +}; + +//============================================================================== +// These names must be identical to the Steinberg SDK so JUCE users can set +// exactly what they want. +enum VstPlugInCategory +{ + kPlugCategUnknown, + kPlugCategEffect, + kPlugCategSynth, + kPlugCategAnalysis, + kPlugCategMastering, + kPlugCategSpacializer, + kPlugCategRoomFx, + kPlugSurroundFx, + kPlugCategRestoration, + kPlugCategOfflineProcess, + kPlugCategShell, + kPlugCategGenerator +}; + +//============================================================================== +struct VstEditorBounds +{ + int16 upper; + int16 leftmost; + int16 lower; + int16 rightmost; +}; + +//============================================================================== +enum VstMaxStringLengths +{ + vstMaxNameLength = 64, + vstMaxParameterOrPinLabelLength = 64, + vstMaxParameterOrPinShortLabelLength = 8, + vstMaxCategoryLength = 24, + vstMaxManufacturerStringLength = 64, + vstMaxPlugInNameStringLength = 64 +}; + +//============================================================================== +struct VstPinInfo +{ + char text[vstMaxParameterOrPinLabelLength]; + int32 flags; + int32 configurationType; + char shortText[vstMaxParameterOrPinShortLabelLength]; + char unused[48]; +}; + +enum VstPinInfoFlags +{ + vstPinInfoFlagIsActive = 1, + vstPinInfoFlagIsStereo = 2, + vstPinInfoFlagValid = 4 +}; + +//============================================================================== +struct VstEvent +{ + int32 type; + int32 size; + int32 sampleOffset; + int32 flags; + char content[16]; +}; + +enum VstEventTypes +{ + vstMidiEventType = 1, + vstSysExEventType = 6 +}; + +struct VstEventBlock +{ + int32 numberOfEvents; + pointer_sized_int future; + VstEvent* events[2]; +}; + +struct VstMidiEvent +{ + int32 type; + int32 size; + int32 sampleOffset; + int32 flags; + int32 noteSampleLength; + int32 noteSampleOffset; + char midiData[4]; + char tuning; + char noteVelocityOff; + char future1; + char future2; +}; + +enum VstMidiEventFlags +{ + vstMidiEventIsRealtime = 1 +}; + +struct VstSysExEvent +{ + int32 type; + int32 size; + int32 offsetSamples; + int32 flags; + int32 sysExDumpSize; + pointer_sized_int future1; + char* sysExDump; + pointer_sized_int future2; +}; + +//============================================================================== +struct VstTimingInformation +{ + double samplePosition; + double sampleRate; + double systemTimeNanoseconds; + double musicalPosition; + double tempoBPM; + double lastBarPosition; + double loopStartPosition; + double loopEndPosition; + int32 timeSignatureNumerator; + int32 timeSignatureDenominator; + int32 smpteOffset; + int32 smpteRate; + int32 samplesToNearestClock; + int32 flags; +}; + +enum VstTimingInformationFlags +{ + vstTimingInfoFlagTransportChanged = 1, + vstTimingInfoFlagCurrentlyPlaying = 2, + vstTimingInfoFlagLoopActive = 4, + vstTimingInfoFlagCurrentlyRecording = 8, + vstTimingInfoFlagAutomationWriteModeActive = 64, + vstTimingInfoFlagAutomationReadModeActive = 128, + vstTimingInfoFlagNanosecondsValid = 256, + vstTimingInfoFlagMusicalPositionValid = 512, + vstTimingInfoFlagTempoValid = 1024, + vstTimingInfoFlagLastBarPositionValid = 2048, + vstTimingInfoFlagLoopPositionValid = 4096, + vstTimingInfoFlagTimeSignatureValid = 8192, + vstTimingInfoFlagSmpteValid = 16384, + vstTimingInfoFlagNearestClockValid = 32768 +}; + +//============================================================================== +enum VstSmpteRates +{ + vstSmpteRateFps24, + vstSmpteRateFps25, + vstSmpteRateFps2997, + vstSmpteRateFps30, + vstSmpteRateFps2997drop, + vstSmpteRateFps30drop, + + vstSmpteRate16mmFilm, + vstSmpteRate35mmFilm, + + vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, + vstSmpteRateFps249 , + vstSmpteRateFps599, + vstSmpteRateFps60 +}; + +//============================================================================== +struct VstIndividualSpeakerInfo +{ + float azimuthalAngle; + float elevationAngle; + float radius; + float reserved; + char label[vstMaxNameLength]; + int32 type; + char unused[28]; +}; + +enum VstIndividualSpeakerType +{ + vstIndividualSpeakerTypeUndefined = 0x7fffffff, + vstIndividualSpeakerTypeMono = 0, + vstIndividualSpeakerTypeLeft, + vstIndividualSpeakerTypeRight, + vstIndividualSpeakerTypeCentre, + vstIndividualSpeakerTypeLFE, + vstIndividualSpeakerTypeLeftSurround, + vstIndividualSpeakerTypeRightSurround, + vstIndividualSpeakerTypeLeftCentre, + vstIndividualSpeakerTypeRightCentre, + vstIndividualSpeakerTypeSurround, + vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, + vstIndividualSpeakerTypeLeftRearSurround, + vstIndividualSpeakerTypeRightRearSurround, + vstIndividualSpeakerTypeTopMiddle, + vstIndividualSpeakerTypeTopFrontLeft, + vstIndividualSpeakerTypeTopFrontCentre, + vstIndividualSpeakerTypeTopFrontRight, + vstIndividualSpeakerTypeTopRearLeft, + vstIndividualSpeakerTypeTopRearCentre, + vstIndividualSpeakerTypeTopRearRight, + vstIndividualSpeakerTypeLFE2 +}; + +struct VstSpeakerConfiguration +{ + int32 type; + int32 numberOfChannels; + VstIndividualSpeakerInfo speakers[8]; +}; + +enum VstSpeakerConfigurationType +{ + vstSpeakerConfigTypeUser = -2, + vstSpeakerConfigTypeEmpty = -1, + vstSpeakerConfigTypeMono = 0, + vstSpeakerConfigTypeLR, + vstSpeakerConfigTypeLsRs, + vstSpeakerConfigTypeLcRc, + vstSpeakerConfigTypeSlSr, + vstSpeakerConfigTypeCLfe, + vstSpeakerConfigTypeLRC, + vstSpeakerConfigTypeLRS, + vstSpeakerConfigTypeLRCLfe, + vstSpeakerConfigTypeLRLfeS, + vstSpeakerConfigTypeLRCS, + vstSpeakerConfigTypeLRLsRs, + vstSpeakerConfigTypeLRCLfeS, + vstSpeakerConfigTypeLRLfeLsRs, + vstSpeakerConfigTypeLRCLsRs, + vstSpeakerConfigTypeLRCLfeLsRs, + vstSpeakerConfigTypeLRCLsRsCs, + vstSpeakerConfigTypeLRLsRsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsCs, + vstSpeakerConfigTypeLRLfeLsRsSlSr, + vstSpeakerConfigTypeLRCLsRsLcRc, + vstSpeakerConfigTypeLRCLsRsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsLcRc, + vstSpeakerConfigTypeLRCLfeLsRsSlSr, + vstSpeakerConfigTypeLRCLsRsLcRcCs, + vstSpeakerConfigTypeLRCLsRsCsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, + vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 +}; + +#if JUCE_BIG_ENDIAN + #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (a | (((uint32) b) << 8) | (((uint32) c) << 16) | (((uint32) d) << 24)) +#else + #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (d | (((uint32) c) << 8) | (((uint32) b) << 16) | (((uint32) a) << 24)) +#endif + +enum PresonusExtensionConstants +{ + presonusVendorID = JUCE_MULTICHAR_CONSTANT ('P', 'r', 'e', 'S'), + presonusSetContentScaleFactor = JUCE_MULTICHAR_CONSTANT ('A', 'e', 'C', 's') +}; + +//============================================================================== +struct vst2FxBank +{ + int32 magic1; + int32 size; + int32 magic2; + int32 version1; + int32 fxID; + int32 version2; + int32 elements; + int32 current; + char shouldBeZero[124]; + int32 chunkSize; + char chunk[1]; +}; + +#if JUCE_MSVC + #pragma pack(pop) +#elif JUCE_MAC || JUCE_IOS + #pragma options align=reset +#else + #pragma pack(pop) +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h b/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h new file mode 100644 index 000000000..6665cb9ae --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h @@ -0,0 +1,191 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +// NB: this must come first, *before* the header-guard. +#ifdef JUCE_VSTINTERFACE_H_INCLUDED + +namespace juce +{ + +//============================================================================== +/** Holds a set of VSTMidiEvent objects and makes it easy to add + events to the list. + + This is used by both the VST hosting code and the plugin wrapper. +*/ +class VSTMidiEventList +{ +public: + //============================================================================== + VSTMidiEventList() + : numEventsUsed (0), numEventsAllocated (0) + { + } + + ~VSTMidiEventList() + { + freeEvents(); + } + + //============================================================================== + void clear() + { + numEventsUsed = 0; + + if (events != nullptr) + events->numberOfEvents = 0; + } + + void addEvent (const void* const midiData, const int numBytes, const int frameOffset) + { + ensureSize (numEventsUsed + 1); + + VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); + events->numberOfEvents = ++numEventsUsed; + + if (numBytes <= 4) + { + if (e->type == vstSysExEventType) + { + delete[] (((VstSysExEvent*) e)->sysExDump); + e->type = vstMidiEventType; + e->size = sizeof (VstMidiEvent); + e->noteSampleLength = 0; + e->noteSampleOffset = 0; + e->tuning = 0; + e->noteVelocityOff = 0; + } + + e->sampleOffset = frameOffset; + memcpy (e->midiData, midiData, (size_t) numBytes); + } + else + { + VstSysExEvent* const se = (VstSysExEvent*) e; + + if (se->type == vstSysExEventType) + delete[] se->sysExDump; + + se->sysExDump = new char [(size_t) numBytes]; + memcpy (se->sysExDump, midiData, (size_t) numBytes); + + se->type = vstSysExEventType; + se->size = sizeof (VstSysExEvent); + se->offsetSamples = frameOffset; + se->flags = 0; + se->sysExDumpSize = numBytes; + se->future1 = 0; + se->future2 = 0; + } + } + + //============================================================================== + // Handy method to pull the events out of an event buffer supplied by the host + // or plugin. + static void addEventsToMidiBuffer (const VstEventBlock* events, MidiBuffer& dest) + { + for (int i = 0; i < events->numberOfEvents; ++i) + { + const VstEvent* const e = events->events[i]; + + if (e != nullptr) + { + if (e->type == vstMidiEventType) + { + dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, + 4, e->sampleOffset); + } + else if (e->type == vstSysExEventType) + { + dest.addEvent ((const juce::uint8*) ((const VstSysExEvent*) e)->sysExDump, + (int) ((const VstSysExEvent*) e)->sysExDumpSize, + e->sampleOffset); + } + } + } + } + + //============================================================================== + void ensureSize (int numEventsNeeded) + { + if (numEventsNeeded > numEventsAllocated) + { + numEventsNeeded = (numEventsNeeded + 32) & ~31; + + const size_t size = 20 + sizeof (VstEvent*) * (size_t) numEventsNeeded; + + if (events == nullptr) + events.calloc (size, 1); + else + events.realloc (size, 1); + + for (int i = numEventsAllocated; i < numEventsNeeded; ++i) + events->events[i] = allocateVSTEvent(); + + numEventsAllocated = numEventsNeeded; + } + } + + void freeEvents() + { + if (events != nullptr) + { + for (int i = numEventsAllocated; --i >= 0;) + freeVSTEvent (events->events[i]); + + events.free(); + numEventsUsed = 0; + numEventsAllocated = 0; + } + } + + //============================================================================== + HeapBlock events; + +private: + int numEventsUsed, numEventsAllocated; + + static VstEvent* allocateVSTEvent() + { + auto e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstSysExEvent) ? sizeof (VstMidiEvent) + : sizeof (VstSysExEvent)); + e->type = vstMidiEventType; + e->size = sizeof (VstMidiEvent); + return e; + } + + static void freeVSTEvent (VstEvent* e) + { + if (e->type == vstSysExEventType) + delete[] (((VstSysExEvent*) e)->sysExDump); + + std::free (e); + } +}; + +} // namespace juce + +#endif // JUCE_VSTINTERFACE_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp new file mode 100644 index 000000000..ff225ee2a --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -0,0 +1,2972 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 JUCE_PLUGINHOST_VST + +//============================================================================== +#undef PRAGMA_ALIGN_SUPPORTED + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4996) +#elif ! JUCE_MINGW + #define __cdecl +#endif + +namespace Vst2 +{ + #include "juce_VSTInterface.h" +} + +using namespace Vst2; + +#include "juce_VSTCommon.h" + +#if JUCE_MSVC + #pragma warning (pop) + #pragma warning (disable: 4355) // ("this" used in initialiser list warning) +#endif + +#include "juce_VSTMidiEventList.h" + +#if JUCE_MINGW + #ifndef WM_APPCOMMAND + #define WM_APPCOMMAND 0x0319 + #endif +#elif ! JUCE_WINDOWS + static void _fpreset() {} + static void _clearfp() {} +#endif + +#ifndef JUCE_VST_WRAPPER_LOAD_CUSTOM_MAIN + #define JUCE_VST_WRAPPER_LOAD_CUSTOM_MAIN +#endif + +#ifndef JUCE_VST_WRAPPER_INVOKE_MAIN + #define JUCE_VST_WRAPPER_INVOKE_MAIN effect = module->moduleMain (&audioMaster); +#endif + +//============================================================================== +namespace juce +{ + +//============================================================================== +namespace +{ + const int fxbVersionNum = 1; + + struct fxProgram + { + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxCk' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numParams; + char prgName[28]; + float params[1]; // variable no. of parameters + }; + + struct fxSet + { + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxBk' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numPrograms; + char future[128]; + fxProgram programs[1]; // variable no. of programs + }; + + struct fxChunkSet + { + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxCh', 'FPCh', or 'FBCh' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numPrograms; + char future[128]; + int32 chunkSize; + char chunk[8]; // variable + }; + + struct fxProgramSet + { + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxCh', 'FPCh', or 'FBCh' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numPrograms; + char name[28]; + int32 chunkSize; + char chunk[8]; // variable + }; + + // Compares a magic value in either endianness. + static bool compareMagic (int32 magic, const char* name) noexcept + { + return magic == (int32) ByteOrder::littleEndianInt (name) + || magic == (int32) ByteOrder::bigEndianInt (name); + } + + static int32 fxbName (const char* name) noexcept { return (int32) ByteOrder::littleEndianInt (name); } + static int32 fxbSwap (int32 x) noexcept { return (int32) ByteOrder::swapIfLittleEndian ((uint32) x); } + + static float fxbSwapFloat (const float x) noexcept + { + #ifdef JUCE_LITTLE_ENDIAN + union { uint32 asInt; float asFloat; } n; + n.asFloat = x; + n.asInt = ByteOrder::swap (n.asInt); + return n.asFloat; + #else + return x; + #endif + } +} + +//============================================================================== +namespace +{ + static double getVSTHostTimeNanoseconds() noexcept + { + #if JUCE_WINDOWS + return timeGetTime() * 1000000.0; + #elif JUCE_LINUX || JUCE_IOS || JUCE_ANDROID + timeval micro; + gettimeofday (µ, 0); + return micro.tv_usec * 1000.0; + #elif JUCE_MAC + UnsignedWide micro; + Microseconds (µ); + return micro.lo * 1000.0; + #endif + } + + static int shellUIDToCreate = 0; + static int insideVSTCallback = 0; + + struct IdleCallRecursionPreventer + { + IdleCallRecursionPreventer() : isMessageThread (MessageManager::getInstance()->isThisTheMessageThread()) + { + if (isMessageThread) + ++insideVSTCallback; + } + + ~IdleCallRecursionPreventer() + { + if (isMessageThread) + --insideVSTCallback; + } + + const bool isMessageThread; + JUCE_DECLARE_NON_COPYABLE (IdleCallRecursionPreventer) + }; + + #if JUCE_MAC + static bool makeFSRefFromPath (FSRef* destFSRef, const String& path) + { + return FSPathMakeRef (reinterpret_cast (path.toRawUTF8()), destFSRef, 0) == noErr; + } + #endif +} + +//============================================================================== +typedef VstEffectInterface* (VSTINTERFACECALL *MainCall) (VstHostCallback); +static pointer_sized_int VSTINTERFACECALL audioMaster (VstEffectInterface*, int32, int32, pointer_sized_int, void*, float); + +//============================================================================== +// Change this to disable logging of various VST activities +#ifndef VST_LOGGING + #define VST_LOGGING 1 +#endif + +#if VST_LOGGING + #define JUCE_VST_LOG(a) Logger::writeToLog(a); +#else + #define JUCE_VST_LOG(a) +#endif + +//============================================================================== +#if JUCE_LINUX + +namespace +{ + typedef void (*EventProcPtr) (XEvent* ev); + + Window getChildWindow (Window windowToCheck) + { + Window rootWindow, parentWindow; + Window* childWindows; + unsigned int numChildren = 0; + + { + ScopedXDisplay xDisplay; + XQueryTree (xDisplay.display, windowToCheck, &rootWindow, &parentWindow, &childWindows, &numChildren); + } + + if (numChildren > 0) + return childWindows [0]; + + return 0; + } +} + +#endif + +//============================================================================== +struct ModuleHandle : public ReferenceCountedObject +{ + File file; + MainCall moduleMain, customMain = {}; + String pluginName; + ScopedPointer vstXml; + + typedef ReferenceCountedObjectPtr Ptr; + + static Array& getActiveModules() + { + static Array activeModules; + return activeModules; + } + + //============================================================================== + static Ptr findOrCreateModule (const File& file) + { + for (auto* module : getActiveModules()) + if (module->file == file) + return module; + + const IdleCallRecursionPreventer icrp; + shellUIDToCreate = 0; + _fpreset(); + + JUCE_VST_LOG ("Attempting to load VST: " + file.getFullPathName()); + + Ptr m = new ModuleHandle (file, nullptr); + + if (m->open()) + { + _fpreset(); + return m; + } + + return {}; + } + + //============================================================================== + ModuleHandle (const File& f, MainCall customMainCall) + : file (f), moduleMain (customMainCall) + { + getActiveModules().add (this); + + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS || JUCE_ANDROID + fullParentDirectoryPathName = f.getParentDirectory().getFullPathName(); + #elif JUCE_MAC + FSRef ref; + makeFSRefFromPath (&ref, f.getParentDirectory().getFullPathName()); + FSGetCatalogInfo (&ref, kFSCatInfoNone, 0, 0, &parentDirFSSpec, 0); + #endif + } + + ~ModuleHandle() + { + getActiveModules().removeFirstMatchingValue (this); + close(); + } + + //============================================================================== + #if ! JUCE_MAC + String fullParentDirectoryPathName; + #endif + + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_ANDROID + DynamicLibrary module; + + bool open() + { + if (moduleMain != nullptr) + return true; + + pluginName = file.getFileNameWithoutExtension(); + + module.open (file.getFullPathName()); + + moduleMain = (MainCall) module.getFunction ("VSTPluginMain"); + + if (moduleMain == nullptr) + moduleMain = (MainCall) module.getFunction ("main"); + + JUCE_VST_WRAPPER_LOAD_CUSTOM_MAIN + + if (moduleMain != nullptr) + { + vstXml = XmlDocument::parse (file.withFileExtension ("vstxml")); + + #if JUCE_WINDOWS + if (vstXml == nullptr) + vstXml = XmlDocument::parse (getDLLResource (file, "VSTXML", 1)); + #endif + } + + return moduleMain != nullptr; + } + + void close() + { + _fpreset(); // (doesn't do any harm) + + module.close(); + } + + void closeEffect (VstEffectInterface* eff) + { + eff->dispatchFunction (eff, plugInOpcodeClose, 0, 0, 0, 0); + } + + #if JUCE_WINDOWS + static String getDLLResource (const File& dllFile, const String& type, int resID) + { + DynamicLibrary dll (dllFile.getFullPathName()); + auto dllModule = (HMODULE) dll.getNativeHandle(); + + if (dllModule != INVALID_HANDLE_VALUE) + { + if (auto res = FindResource (dllModule, MAKEINTRESOURCE (resID), type.toWideCharPointer())) + { + if (auto hGlob = LoadResource (dllModule, res)) + { + auto* data = static_cast (LockResource (hGlob)); + return String::fromUTF8 (data, SizeofResource (dllModule, res)); + } + } + } + + return {}; + } + #endif + #else + Handle resHandle = {}; + CFBundleRef bundleRef = {}; + + #if JUCE_MAC + CFBundleRefNum resFileId = {}; + FSSpec parentDirFSSpec; + #endif + + bool open() + { + if (moduleMain != nullptr) + return true; + + bool ok = false; + + if (file.hasFileExtension (".vst")) + { + auto* utf8 = file.getFullPathName().toRawUTF8(); + + if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8, + (CFIndex) strlen (utf8), file.isDirectory())) + { + bundleRef = CFBundleCreate (kCFAllocatorDefault, url); + CFRelease (url); + + if (bundleRef != 0) + { + if (CFBundleLoadExecutable (bundleRef)) + { + moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("main_macho")); + + if (moduleMain == nullptr) + moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("VSTPluginMain")); + + JUCE_VST_WRAPPER_LOAD_CUSTOM_MAIN + + if (moduleMain != nullptr) + { + if (CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName"))) + { + if (CFGetTypeID (name) == CFStringGetTypeID()) + { + char buffer[1024]; + + if (CFStringGetCString ((CFStringRef) name, buffer, sizeof (buffer), CFStringGetSystemEncoding())) + pluginName = buffer; + } + } + + if (pluginName.isEmpty()) + pluginName = file.getFileNameWithoutExtension(); + + #if JUCE_MAC + resFileId = CFBundleOpenBundleResourceMap (bundleRef); + #endif + + ok = true; + + Array vstXmlFiles; + file + #if JUCE_MAC + .getChildFile ("Contents") + .getChildFile ("Resources") + #endif + .findChildFiles (vstXmlFiles, File::findFiles, false, "*.vstxml"); + + if (vstXmlFiles.size() > 0) + vstXml = XmlDocument::parse (vstXmlFiles.getReference(0)); + } + } + + if (! ok) + { + CFBundleUnloadExecutable (bundleRef); + CFRelease (bundleRef); + bundleRef = 0; + } + } + } + } + + return ok; + } + + void close() + { + if (bundleRef != 0) + { + #if JUCE_MAC + CFBundleCloseBundleResourceMap (bundleRef, resFileId); + #endif + + if (CFGetRetainCount (bundleRef) == 1) + CFBundleUnloadExecutable (bundleRef); + + if (CFGetRetainCount (bundleRef) > 0) + CFRelease (bundleRef); + } + } + + void closeEffect (VstEffectInterface* eff) + { + eff->dispatchFunction (eff, plugInOpcodeClose, 0, 0, 0, 0); + } + + #endif + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleHandle) +}; + +static const int defaultVSTSampleRateValue = 44100; +static const int defaultVSTBlockSizeValue = 512; + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4996) // warning about overriding deprecated methods +#endif + +//============================================================================== +//============================================================================== +struct VSTPluginInstance : public AudioPluginInstance, + private Timer, + private AsyncUpdater +{ + VSTPluginInstance (const ModuleHandle::Ptr& mh, const BusesProperties& ioConfig, VstEffectInterface* effect, + double sampleRateToUse, int blockSizeToUse) + : AudioPluginInstance (ioConfig), + vstEffect (effect), + vstModule (mh), + name (mh->pluginName) + { + setRateAndBufferSizeDetails (sampleRateToUse, blockSizeToUse); + } + + ~VSTPluginInstance() + { + if (vstEffect != nullptr && vstEffect->interfaceIdentifier == juceVstInterfaceIdentifier) + { + struct VSTDeleter : public CallbackMessage + { + VSTDeleter (VSTPluginInstance& inInstance, WaitableEvent& inEvent) + : vstInstance (inInstance), completionSignal (inEvent) + {} + + void messageCallback() override + { + vstInstance.cleanup(); + completionSignal.signal(); + } + + VSTPluginInstance& vstInstance; + WaitableEvent& completionSignal; + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cleanup(); + } + else + { + WaitableEvent completionEvent; + (new VSTDeleter (*this, completionEvent))->post(); + completionEvent.wait(); + } + } + } + + void cleanup() + { + if (vstEffect != nullptr && vstEffect->interfaceIdentifier == juceVstInterfaceIdentifier) + { + #if JUCE_MAC + if (vstModule->resFileId != 0) + UseResFile (vstModule->resFileId); + #endif + + // Must delete any editors before deleting the plugin instance! + jassert (getActiveEditor() == 0); + + _fpreset(); // some dodgy plugs fuck around with this + + vstModule->closeEffect (vstEffect); + } + + vstModule = nullptr; + vstEffect = nullptr; + } + + static VSTPluginInstance* create (const ModuleHandle::Ptr& newModule, + double initialSampleRate, + int initialBlockSize) + { + if (auto* newEffect = constructEffect (newModule)) + { + newEffect->hostSpace2 = 0; + + newEffect->dispatchFunction (newEffect, plugInOpcodeIdentify, 0, 0, 0, 0); + + auto blockSize = jmax (32, initialBlockSize); + + newEffect->dispatchFunction (newEffect, plugInOpcodeSetSampleRate, 0, 0, 0, static_cast (initialSampleRate)); + newEffect->dispatchFunction (newEffect, plugInOpcodeSetBlockSize, 0, blockSize, 0, 0); + + newEffect->dispatchFunction (newEffect, plugInOpcodeOpen, 0, 0, 0, 0); + BusesProperties ioConfig = queryBusIO (newEffect); + + return new VSTPluginInstance (newModule, ioConfig, newEffect, initialSampleRate, blockSize); + } + + return nullptr; + } + + //============================================================================== + void fillInPluginDescription (PluginDescription& desc) const override + { + desc.name = name; + + { + char buffer[512] = { 0 }; + dispatch (plugInOpcodeGetPlugInName, 0, 0, buffer, 0); + + desc.descriptiveName = String::createStringFromData (buffer, (int) sizeof (buffer)).trim(); + + if (desc.descriptiveName.isEmpty()) + desc.descriptiveName = name; + } + + desc.fileOrIdentifier = vstModule->file.getFullPathName(); + desc.uid = getUID(); + desc.lastFileModTime = vstModule->file.getLastModificationTime(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); + desc.pluginFormatName = "VST"; + desc.category = getCategory(); + + { + char buffer[512] = { 0 }; + dispatch (plugInOpcodeGetManufacturerName, 0, 0, buffer, 0); + desc.manufacturerName = String::createStringFromData (buffer, (int) sizeof (buffer)).trim(); + } + + desc.version = getVersion(); + desc.numInputChannels = getTotalNumInputChannels(); + desc.numOutputChannels = getTotalNumOutputChannels(); + desc.isInstrument = (vstEffect != nullptr && (vstEffect->flags & vstEffectFlagIsSynth) != 0); + } + + bool initialiseEffect (double initialSampleRate, int initialBlockSize) + { + if (vstEffect != nullptr) + { + vstEffect->hostSpace2 = (pointer_sized_int) (pointer_sized_int) this; + initialise (initialSampleRate, initialBlockSize); + return true; + } + + return false; + } + + void initialise (double initialSampleRate, int initialBlockSize) + { + if (initialised || vstEffect == nullptr) + return; + + #if JUCE_WINDOWS + // On Windows it's highly advisable to create your plugins using the message thread, + // because many plugins need a chance to create HWNDs that will get their + // messages delivered by the main message thread, and that's not possible from + // a background thread. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #endif + + JUCE_VST_LOG ("Initialising VST: " + vstModule->pluginName + " (" + getVersion() + ")"); + initialised = true; + + setRateAndBufferSizeDetails (initialSampleRate, initialBlockSize); + + dispatch (plugInOpcodeIdentify, 0, 0, 0, 0); + + if (getSampleRate() > 0) + dispatch (plugInOpcodeSetSampleRate, 0, 0, 0, (float) getSampleRate()); + + if (getBlockSize() > 0) + dispatch (plugInOpcodeSetBlockSize, 0, jmax (32, getBlockSize()), 0, 0); + + dispatch (plugInOpcodeOpen, 0, 0, 0, 0); + + setRateAndBufferSizeDetails (getSampleRate(), getBlockSize()); + + if (getNumPrograms() > 1) + setCurrentProgram (0); + else + dispatch (plugInOpcodeSetCurrentProgram, 0, 0, 0, 0); + + for (int i = vstEffect->numInputChannels; --i >= 0;) dispatch (plugInOpcodeConnectInput, i, 1, 0, 0); + for (int i = vstEffect->numOutputChannels; --i >= 0;) dispatch (plugInOpcodeConnectOutput, i, 1, 0, 0); + + if (getVstCategory() != kPlugCategShell) // (workaround for Waves 5 plugins which crash during this call) + updateStoredProgramNames(); + + wantsMidiMessages = pluginCanDo ("receiveVstMidiEvent") > 0; + + #if JUCE_MAC && JUCE_SUPPORT_CARBON + usesCocoaNSView = ((unsigned int) pluginCanDo ("hasCockosViewAsConfig") & 0xffff0000ul) == 0xbeef0000ul; + #endif + + setLatencySamples (vstEffect->latency); + } + + void* getPlatformSpecificData() override { return vstEffect; } + + const String getName() const override + { + if (vstEffect != nullptr) + { + char buffer[512] = { 0 }; + + if (dispatch (plugInOpcodeGetManufacturerProductName, 0, 0, buffer, 0) != 0) + { + String productName = String::createStringFromData (buffer, (int) sizeof (buffer)); + + if (productName.isNotEmpty()) + return productName; + } + } + + return name; + } + + int getUID() const + { + int uid = vstEffect != nullptr ? vstEffect->plugInIdentifier : 0; + + if (uid == 0) + uid = vstModule->file.hashCode(); + + return uid; + } + + double getTailLengthSeconds() const override + { + if (vstEffect == nullptr) + return 0.0; + + auto sampleRate = getSampleRate(); + + if (sampleRate <= 0) + return 0.0; + + return dispatch (plugInOpcodeGetTailSize, 0, 0, 0, 0) / sampleRate; + } + + bool acceptsMidi() const override { return wantsMidiMessages; } + bool producesMidi() const override { return pluginCanDo ("sendVstMidiEvent") > 0; } + bool supportsMPE() const override { return pluginCanDo ("MPE") > 0; } + + VstPlugInCategory getVstCategory() const noexcept { return (VstPlugInCategory) dispatch (plugInOpcodeGetPlugInCategory, 0, 0, 0, 0); } + + int pluginCanDo (const char* text) const { return (int) dispatch (plugInOpcodeCanPlugInDo, 0, 0, (void*) text, 0); } + + //============================================================================== + void prepareToPlay (double rate, int samplesPerBlockExpected) override + { + setRateAndBufferSizeDetails (rate, samplesPerBlockExpected); + + SpeakerMappings::VstSpeakerConfigurationHolder inArr (getChannelLayoutOfBus (true, 0)); + SpeakerMappings::VstSpeakerConfigurationHolder outArr (getChannelLayoutOfBus (false, 0)); + + dispatch (plugInOpcodeSetSpeakerConfiguration, 0, (pointer_sized_int) &inArr.get(), (void*) &outArr.get(), 0.0f); + + vstHostTime.tempoBPM = 120.0; + vstHostTime.timeSignatureNumerator = 4; + vstHostTime.timeSignatureDenominator = 4; + vstHostTime.sampleRate = rate; + vstHostTime.samplePosition = 0; + vstHostTime.flags = vstTimingInfoFlagNanosecondsValid + | vstTimingInfoFlagAutomationWriteModeActive + | vstTimingInfoFlagAutomationReadModeActive; + + initialise (rate, samplesPerBlockExpected); + + if (initialised) + { + wantsMidiMessages = wantsMidiMessages || (pluginCanDo ("receiveVstMidiEvent") > 0); + + if (wantsMidiMessages) + midiEventsToSend.ensureSize (256); + else + midiEventsToSend.freeEvents(); + + incomingMidi.clear(); + + dispatch (plugInOpcodeSetSampleRate, 0, 0, 0, (float) rate); + dispatch (plugInOpcodeSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); + + if (supportsDoublePrecisionProcessing()) + { + int32 vstPrecision = isUsingDoublePrecision() ? vstProcessingSampleTypeDouble + : vstProcessingSampleTypeFloat; + + dispatch (plugInOpcodeSetSampleFloatType, 0, (pointer_sized_int) vstPrecision, 0, 0); + } + + auto maxChannels = jmax (1, jmax (vstEffect->numInputChannels, vstEffect->numOutputChannels)); + + tmpBufferFloat .setSize (maxChannels, samplesPerBlockExpected); + tmpBufferDouble.setSize (maxChannels, samplesPerBlockExpected); + + channelBufferFloat .calloc (static_cast (maxChannels)); + channelBufferDouble.calloc (static_cast (maxChannels)); + + outOfPlaceBuffer.setSize (jmax (1, vstEffect->numOutputChannels), samplesPerBlockExpected); + + if (! isPowerOn) + setPower (true); + + // dodgy hack to force some plugins to initialise the sample rate.. + if ((! hasEditor()) && getNumParameters() > 0) + { + auto old = getParameter (0); + setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); + setParameter (0, old); + } + + dispatch (plugInOpcodeStartProcess, 0, 0, 0, 0); + + setLatencySamples (vstEffect->latency); + } + } + + void releaseResources() override + { + if (initialised) + { + dispatch (plugInOpcodeStopProcess, 0, 0, 0, 0); + setPower (false); + } + + channelBufferFloat.free(); + tmpBufferFloat.setSize (0, 0); + + channelBufferDouble.free(); + tmpBufferDouble.setSize (0, 0); + + outOfPlaceBuffer.setSize (1, 1); + incomingMidi.clear(); + + midiEventsToSend.freeEvents(); + } + + void reset() override + { + if (isPowerOn) + { + setPower (false); + setPower (true); + } + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (! isUsingDoublePrecision()); + processAudio (buffer, midiMessages, tmpBufferFloat, channelBufferFloat); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + processAudio (buffer, midiMessages, tmpBufferDouble, channelBufferDouble); + } + + bool supportsDoublePrecisionProcessing() const override + { + return ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0 + && (vstEffect->flags & vstEffectFlagInplaceDoubleAudio) != 0); + } + + //============================================================================== + bool canAddBus (bool) const override { return false; } + bool canRemoveBus (bool) const override { return false; } + + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + auto numInputBuses = getBusCount (true); + auto numOutputBuses = getBusCount (false); + + // it's not possible to change layout if there are sidechains/aux buses + if (numInputBuses > 1 || numOutputBuses > 1) + return (layouts == getBusesLayout()); + + return (layouts.getNumChannels (true, 0) <= vstEffect->numInputChannels + && layouts.getNumChannels (false, 0) <= vstEffect->numOutputChannels); + } + + //============================================================================== + #if JUCE_IOS || JUCE_ANDROID + bool hasEditor() const override { return false; } + #else + bool hasEditor() const override { return vstEffect != nullptr && (vstEffect->flags & vstEffectFlagHasEditor) != 0; } + #endif + + AudioProcessorEditor* createEditor() override; + + //============================================================================== + const String getInputChannelName (int index) const override + { + if (isValidChannel (index, true)) + { + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetInputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return String (pinProps.text, sizeof (pinProps.text)); + } + + return {}; + } + + bool isInputChannelStereoPair (int index) const override + { + if (! isValidChannel (index, true)) + return false; + + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetInputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return (pinProps.flags & vstPinInfoFlagIsStereo) != 0; + + return true; + } + + const String getOutputChannelName (int index) const override + { + if (isValidChannel (index, false)) + { + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetOutputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return String (pinProps.text, sizeof (pinProps.text)); + } + + return {}; + } + + bool isOutputChannelStereoPair (int index) const override + { + if (! isValidChannel (index, false)) + return false; + + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetOutputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return (pinProps.flags & vstPinInfoFlagIsStereo) != 0; + + return true; + } + + bool isValidChannel (int index, bool isInput) const noexcept + { + return isPositiveAndBelow (index, isInput ? getTotalNumInputChannels() + : getTotalNumOutputChannels()); + } + + //============================================================================== + int getNumParameters() override { return vstEffect != nullptr ? vstEffect->numParameters : 0; } + + float getParameter (int index) override + { + if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters)) + { + const ScopedLock sl (lock); + return vstEffect->getParameterValueFunction (vstEffect, index); + } + + return 0.0f; + } + + void setParameter (int index, float newValue) override + { + if (vstEffect != nullptr && isPositiveAndBelow (index, vstEffect->numParameters)) + { + const ScopedLock sl (lock); + + if (vstEffect->getParameterValueFunction (vstEffect, index) != newValue) + vstEffect->setParameterValueFunction (vstEffect, index, newValue); + } + } + + const String getParameterName (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterName); } + const String getParameterText (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterText); } + String getParameterLabel (int index) const override { return getTextForOpcode (index, plugInOpcodeGetParameterLabel); } + + bool isParameterAutomatable (int index) const override + { + if (vstEffect != nullptr) + { + jassert (index >= 0 && index < vstEffect->numParameters); + return dispatch (plugInOpcodeIsParameterAutomatable, index, 0, 0, 0) != 0; + } + + return false; + } + + //============================================================================== + int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; } + + // NB: some plugs return negative numbers from this function. + int getCurrentProgram() override { return (int) dispatch (plugInOpcodeGetCurrentProgram, 0, 0, 0, 0); } + + void setCurrentProgram (int newIndex) override + { + if (getNumPrograms() > 0 && newIndex != getCurrentProgram()) + dispatch (plugInOpcodeSetCurrentProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); + } + + const String getProgramName (int index) override + { + if (index >= 0) + { + if (index == getCurrentProgram()) + return getCurrentProgramName(); + + if (vstEffect != nullptr) + { + char nm[264] = { 0 }; + + if (dispatch (plugInOpcodeGetProgramName, jlimit (0, getNumPrograms(), index), -1, nm, 0) != 0) + return String::fromUTF8 (nm).trim(); + } + } + + return programNames [index]; + } + + void changeProgramName (int index, const String& newName) override + { + if (index >= 0 && index == getCurrentProgram()) + { + if (getNumPrograms() > 0 && newName != getCurrentProgramName()) + dispatch (plugInOpcodeSetCurrentProgramName, 0, 0, (void*) newName.substring (0, 24).toRawUTF8(), 0.0f); + } + else + { + jassertfalse; // xxx not implemented! + } + } + + //============================================================================== + void getStateInformation (MemoryBlock& mb) override { saveToFXBFile (mb, true); } + void getCurrentProgramStateInformation (MemoryBlock& mb) override { saveToFXBFile (mb, false); } + + void setStateInformation (const void* data, int size) override { loadFromFXBFile (data, (size_t) size); } + void setCurrentProgramStateInformation (const void* data, int size) override { loadFromFXBFile (data, (size_t) size); } + + //============================================================================== + void timerCallback() override + { + if (dispatch (plugInOpcodeIdle, 0, 0, 0, 0) == 0) + stopTimer(); + } + + void handleAsyncUpdate() override + { + // indicates that something about the plugin has changed.. + updateHostDisplay(); + } + + pointer_sized_int handleCallback (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) + { + switch (opcode) + { + case hostOpcodeParameterChanged: sendParamChangeMessageToListeners (index, opt); break; + case hostOpcodePreAudioProcessingEvents: handleMidiFromPlugin ((const VstEventBlock*) ptr); break; + case hostOpcodeGetTimingInfo: return getVSTTime(); + case hostOpcodeIdle: handleIdle(); break; + case hostOpcodeWindowSize: setWindowSize (index, (int) value); return 1; + case hostOpcodeUpdateView: triggerAsyncUpdate(); break; + case hostOpcodeIOModified: setLatencySamples (vstEffect->latency); break; + case hostOpcodeNeedsIdle: startTimer (50); break; + + case hostOpcodeGetSampleRate: return (pointer_sized_int) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); + case hostOpcodeGetBlockSize: return (pointer_sized_int) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); + case hostOpcodePlugInWantsMidi: wantsMidiMessages = true; break; + case hostOpcodeGetDirectory: return getVstDirectory(); + + case hostOpcodeTempoAt: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getTempoAt ((int64) value) : 0); + case hostOpcodeGetAutomationState: return (pointer_sized_int) (extraFunctions != nullptr ? extraFunctions->getAutomationState() : 0); + + case hostOpcodeParameterChangeGestureBegin: beginParameterChangeGesture (index); break; + case hostOpcodeParameterChangeGestureEnd: endParameterChangeGesture (index); break; + + case hostOpcodePinConnected: return isValidChannel (index, value == 0) ? 0 : 1; // (yes, 0 = true) + case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0; + + // none of these are handled (yet)... + case hostOpcodeSetTime: + case hostOpcodeGetParameterInterval: + case hostOpcodeGetInputLatency: + case hostOpcodeGetOutputLatency: + case hostOpcodeGetPreviousPlugIn: + case hostOpcodeGetNextPlugIn: + case hostOpcodeWillReplace: + case hostOpcodeOfflineStart: + case hostOpcodeOfflineReadSource: + case hostOpcodeOfflineWrite: + case hostOpcodeOfflineGetCurrentPass: + case hostOpcodeOfflineGetCurrentMetaPass: + case hostOpcodeGetOutputSpeakerConfiguration: + case hostOpcodeManufacturerSpecific: + case hostOpcodeSetIcon: + case hostOpcodeGetLanguage: + case hostOpcodeOpenEditorWindow: + case hostOpcodeCloseEditorWindow: + break; + + default: + return handleGeneralCallback (opcode, index, value, ptr, opt); + } + + return 0; + } + + // handles non plugin-specific callbacks.. + static pointer_sized_int handleGeneralCallback (int32 opcode, int32 /*index*/, pointer_sized_int /*value*/, void* ptr, float /*opt*/) + { + switch (opcode) + { + case hostOpcodeCanHostDo: return handleCanDo ((const char*) ptr); + case hostOpcodeVstVersion: return 2400; + case hostOpcodeCurrentId: return shellUIDToCreate; + case hostOpcodeGetNumberOfAutomatableParameters: return 0; + case hostOpcodeGetAutomationState: return 1; + case hostOpcodeGetManufacturerVersion: return 0x0101; + + case hostOpcodeGetManufacturerName: + case hostOpcodeGetProductName: return getHostName ((char*) ptr); + + case hostOpcodeGetSampleRate: return (pointer_sized_int) defaultVSTSampleRateValue; + case hostOpcodeGetBlockSize: return (pointer_sized_int) defaultVSTBlockSizeValue; + case hostOpcodeSetOutputSampleRate: return 0; + + default: + DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); + break; + } + + return 0; + } + + //============================================================================== + pointer_sized_int dispatch (int opcode, int index, pointer_sized_int value, void* const ptr, float opt) const + { + pointer_sized_int result = 0; + + if (vstEffect != nullptr) + { + const ScopedLock sl (lock); + const IdleCallRecursionPreventer icrp; + + try + { + #if JUCE_MAC + auto oldResFile = CurResFile(); + + if (vstModule->resFileId != 0) + UseResFile (vstModule->resFileId); + #endif + + result = vstEffect->dispatchFunction (vstEffect, opcode, index, value, ptr, opt); + + #if JUCE_MAC + auto newResFile = CurResFile(); + + if (newResFile != oldResFile) // avoid confusing the parent app's resource file with the plug-in's + { + vstModule->resFileId = newResFile; + UseResFile (oldResFile); + } + #endif + } + catch (...) + {} + } + + return result; + } + + bool loadFromFXBFile (const void* const data, const size_t dataSize) + { + if (dataSize < 28) + return false; + + auto set = (const fxSet*) data; + + if ((! compareMagic (set->chunkMagic, "CcnK")) || fxbSwap (set->version) > fxbVersionNum) + return false; + + if (compareMagic (set->fxMagic, "FxBk")) + { + // bank of programs + if (fxbSwap (set->numPrograms) >= 0) + { + auto oldProg = getCurrentProgram(); + auto numParams = fxbSwap (((const fxProgram*) (set->programs))->numParams); + auto progLen = (int) sizeof (fxProgram) + (numParams - 1) * (int) sizeof (float); + + for (int i = 0; i < fxbSwap (set->numPrograms); ++i) + { + if (i != oldProg) + { + auto prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); + + if (((const char*) prog) - ((const char*) set) >= (ssize_t) dataSize) + return false; + + if (fxbSwap (set->numPrograms) > 0) + setCurrentProgram (i); + + if (! restoreProgramSettings (prog)) + return false; + } + } + + if (fxbSwap (set->numPrograms) > 0) + setCurrentProgram (oldProg); + + auto prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); + + if (((const char*) prog) - ((const char*) set) >= (ssize_t) dataSize) + return false; + + if (! restoreProgramSettings (prog)) + return false; + } + } + else if (compareMagic (set->fxMagic, "FxCk")) + { + // single program + auto prog = (const fxProgram*) data; + + if (! compareMagic (prog->chunkMagic, "CcnK")) + return false; + + changeProgramName (getCurrentProgram(), prog->prgName); + + for (int i = 0; i < fxbSwap (prog->numParams); ++i) + setParameter (i, fxbSwapFloat (prog->params[i])); + } + else if (compareMagic (set->fxMagic, "FBCh")) + { + // non-preset chunk + auto cset = (const fxChunkSet*) data; + + if ((size_t) fxbSwap (cset->chunkSize) + sizeof (fxChunkSet) - 8 > (size_t) dataSize) + return false; + + setChunkData (cset->chunk, fxbSwap (cset->chunkSize), false); + } + else if (compareMagic (set->fxMagic, "FPCh")) + { + // preset chunk + auto cset = (const fxProgramSet*) data; + + if ((size_t) fxbSwap (cset->chunkSize) + sizeof (fxProgramSet) - 8 > (size_t) dataSize) + return false; + + setChunkData (cset->chunk, fxbSwap (cset->chunkSize), true); + + changeProgramName (getCurrentProgram(), cset->name); + } + else + { + return false; + } + + return true; + } + + bool saveToFXBFile (MemoryBlock& dest, bool isFXB, int maxSizeMB = 128) + { + auto numPrograms = getNumPrograms(); + auto numParams = getNumParameters(); + + if (usesChunks()) + { + MemoryBlock chunk; + getChunkData (chunk, ! isFXB, maxSizeMB); + + if (isFXB) + { + auto totalLen = sizeof (fxChunkSet) + chunk.getSize() - 8; + dest.setSize (totalLen, true); + + auto set = (fxChunkSet*) dest.getData(); + set->chunkMagic = fxbName ("CcnK"); + set->byteSize = 0; + set->fxMagic = fxbName ("FBCh"); + set->version = fxbSwap (fxbVersionNum); + set->fxID = fxbSwap (getUID()); + set->fxVersion = fxbSwap (getVersionNumber()); + set->numPrograms = fxbSwap (numPrograms); + set->chunkSize = fxbSwap ((int32) chunk.getSize()); + + chunk.copyTo (set->chunk, 0, chunk.getSize()); + } + else + { + auto totalLen = sizeof (fxProgramSet) + chunk.getSize() - 8; + dest.setSize (totalLen, true); + + auto set = (fxProgramSet*) dest.getData(); + set->chunkMagic = fxbName ("CcnK"); + set->byteSize = 0; + set->fxMagic = fxbName ("FPCh"); + set->version = fxbSwap (fxbVersionNum); + set->fxID = fxbSwap (getUID()); + set->fxVersion = fxbSwap (getVersionNumber()); + set->numPrograms = fxbSwap (numPrograms); + set->chunkSize = fxbSwap ((int32) chunk.getSize()); + + getCurrentProgramName().copyToUTF8 (set->name, sizeof (set->name) - 1); + chunk.copyTo (set->chunk, 0, chunk.getSize()); + } + } + else + { + if (isFXB) + { + auto progLen = (int) sizeof (fxProgram) + (numParams - 1) * (int) sizeof (float); + auto len = (sizeof (fxSet) - sizeof (fxProgram)) + (size_t) (progLen * jmax (1, numPrograms)); + dest.setSize (len, true); + + auto set = (fxSet*) dest.getData(); + set->chunkMagic = fxbName ("CcnK"); + set->byteSize = 0; + set->fxMagic = fxbName ("FxBk"); + set->version = fxbSwap (fxbVersionNum); + set->fxID = fxbSwap (getUID()); + set->fxVersion = fxbSwap (getVersionNumber()); + set->numPrograms = fxbSwap (numPrograms); + + MemoryBlock oldSettings; + createTempParameterStore (oldSettings); + + auto oldProgram = getCurrentProgram(); + + if (oldProgram >= 0) + setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); + + for (int i = 0; i < numPrograms; ++i) + { + if (i != oldProgram) + { + setCurrentProgram (i); + setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); + } + } + + if (oldProgram >= 0) + setCurrentProgram (oldProgram); + + restoreFromTempParameterStore (oldSettings); + } + else + { + dest.setSize (sizeof (fxProgram) + (size_t) ((numParams - 1) * (int) sizeof (float)), true); + setParamsInProgramBlock ((fxProgram*) dest.getData()); + } + } + + return true; + } + + bool usesChunks() const noexcept { return vstEffect != nullptr && (vstEffect->flags & vstEffectFlagDataInChunks) != 0; } + + bool getChunkData (MemoryBlock& mb, bool isPreset, int maxSizeMB) const + { + if (usesChunks()) + { + void* data = nullptr; + auto bytes = (size_t) dispatch (plugInOpcodeGetData, isPreset ? 1 : 0, 0, &data, 0.0f); + + if (data != nullptr && bytes <= (size_t) maxSizeMB * 1024 * 1024) + { + mb.setSize (bytes); + mb.copyFrom (data, 0, bytes); + + return true; + } + } + + return false; + } + + bool setChunkData (const void* data, const int size, bool isPreset) + { + if (size > 0 && usesChunks()) + { + dispatch (plugInOpcodeSetData, isPreset ? 1 : 0, size, (void*) data, 0.0f); + + if (! isPreset) + updateStoredProgramNames(); + + return true; + } + + return false; + } + + VstEffectInterface* vstEffect; + ModuleHandle::Ptr vstModule; + + ScopedPointer extraFunctions; + bool usesCocoaNSView = false; + +private: + String name; + CriticalSection lock; + bool wantsMidiMessages = false, initialised = false, isPowerOn = false; + mutable StringArray programNames; + AudioBuffer outOfPlaceBuffer; + + CriticalSection midiInLock; + MidiBuffer incomingMidi; + VSTMidiEventList midiEventsToSend; + VstTimingInformation vstHostTime; + + AudioBuffer tmpBufferFloat; + HeapBlock channelBufferFloat; + + AudioBuffer tmpBufferDouble; + HeapBlock channelBufferDouble; + + static pointer_sized_int handleCanDo (const char* name) + { + static const char* canDos[] = { "supplyIdle", + "sendVstEvents", + "sendVstMidiEvent", + "sendVstTimeInfo", + "receiveVstEvents", + "receiveVstMidiEvent", + "supportShell", + "sizeWindow", + "shellCategory" }; + + for (int i = 0; i < numElementsInArray (canDos); ++i) + if (strcmp (canDos[i], name) == 0) + return 1; + + return 0; + } + + static pointer_sized_int getHostName (char* name) + { + String hostName ("Carla Plugin Host"); + + if (auto* app = JUCEApplicationBase::getInstance()) + hostName = app->getApplicationName(); + + hostName.copyToUTF8 (name, (size_t) jmin (vstMaxManufacturerStringLength, vstMaxPlugInNameStringLength) - 1); + return 1; + } + + pointer_sized_int getVSTTime() noexcept + { + #if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4311) + #endif + + return (pointer_sized_int) &vstHostTime; + + #if JUCE_MSVC + #pragma warning (pop) + #endif + } + + void handleIdle() + { + if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) + { + const IdleCallRecursionPreventer icrp; + + #if JUCE_MAC + if (getActiveEditor() != nullptr) + dispatch (plugInOpcodeEditorIdle, 0, 0, 0, 0); + #endif + + Timer::callPendingTimersSynchronously(); + handleUpdateNowIfNeeded(); + + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + if (auto* p = ComponentPeer::getPeer(i)) + p->performAnyPendingRepaintsNow(); + } + } + + void setWindowSize (int width, int height) + { + if (auto* ed = getActiveEditor()) + { + #if JUCE_LINUX + const MessageManagerLock mmLock; + #endif + ed->setSize (width, height); + } + } + + //============================================================================== + static VstEffectInterface* constructEffect (const ModuleHandle::Ptr& module) + { + VstEffectInterface* effect = nullptr; + try + { + const IdleCallRecursionPreventer icrp; + _fpreset(); + + JUCE_VST_LOG ("Creating VST instance: " + module->pluginName); + + #if JUCE_MAC + if (module->resFileId != 0) + UseResFile (module->resFileId); + #endif + + { + JUCE_VST_WRAPPER_INVOKE_MAIN + } + + if (effect != nullptr && effect->interfaceIdentifier == juceVstInterfaceIdentifier) + { + jassert (effect->hostSpace2 == 0); + jassert (effect->effectPointer != 0); + + _fpreset(); // some dodgy plugs mess around with this + } + else + { + effect = nullptr; + } + } + catch (...) + {} + + return effect; + } + + static BusesProperties queryBusIO (VstEffectInterface* effect) + { + BusesProperties returnValue; + + if (effect->numInputChannels == 0 && effect->numOutputChannels == 0) + return returnValue; + + // Workaround for old broken JUCE plug-ins which would return an invalid + // speaker arrangement if the host didn't ask for a specific arrangement + // beforehand. + // Check if the plug-in reports any default layouts. If it doesn't, then + // try setting a default layout compatible with the number of pins this + // plug-in is reporting. + if (! pluginHasDefaultChannelLayouts (effect)) + { + SpeakerMappings::VstSpeakerConfigurationHolder canonicalIn (AudioChannelSet::canonicalChannelSet (effect->numInputChannels)); + SpeakerMappings::VstSpeakerConfigurationHolder canonicalOut (AudioChannelSet::canonicalChannelSet (effect->numOutputChannels)); + + effect->dispatchFunction (effect, plugInOpcodeSetSpeakerConfiguration, 0, + (pointer_sized_int) &canonicalIn.get(), (void*) &canonicalOut.get(), 0.0f); + } + + HeapBlock inArrBlock (1, true), outArrBlock (1, true); + + auto* inArr = inArrBlock.get(); + auto* outArr = outArrBlock.get(); + + if (! getSpeakerArrangementWrapper (effect, inArr, outArr)) + inArr = outArr = nullptr; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int opcode = (isInput ? plugInOpcodeGetInputPinProperties : plugInOpcodeGetOutputPinProperties); + const int maxChannels = (isInput ? effect->numInputChannels : effect->numOutputChannels); + const VstSpeakerConfiguration* arr = (isInput ? inArr : outArr); + bool busAdded = false; + + VstPinInfo pinProps; + AudioChannelSet layout; + + for (int ch = 0; ch < maxChannels; ch += layout.size()) + { + if (effect->dispatchFunction (effect, opcode, ch, 0, &pinProps, 0.0f) == 0) + break; + + if ((pinProps.flags & vstPinInfoFlagValid) != 0) + { + layout = SpeakerMappings::vstArrangementTypeToChannelSet (pinProps.configurationType, 0); + + if (layout.isDisabled()) + break; + } + else if (arr == nullptr) + { + layout = ((pinProps.flags & vstPinInfoFlagIsStereo) != 0 ? AudioChannelSet::stereo() : AudioChannelSet::mono()); + } + else + break; + + busAdded = true; + returnValue.addBus (isInput, pinProps.text, layout, true); + } + + // no buses? + if (! busAdded && maxChannels > 0) + { + String busName = (isInput ? "Input" : "Output"); + + if (effect->dispatchFunction (effect, opcode, 0, 0, &pinProps, 0.0f) != 0) + busName = pinProps.text; + + if (arr != nullptr) + layout = SpeakerMappings::vstArrangementTypeToChannelSet (*arr); + else + layout = AudioChannelSet::canonicalChannelSet (maxChannels); + + returnValue.addBus (isInput, busName, layout, true); + } + } + + return returnValue; + } + + static bool pluginHasDefaultChannelLayouts (VstEffectInterface* effect) + { + HeapBlock inArrBlock (1, true), outArrBlock (1, true); + + auto* inArr = inArrBlock.get(); + auto* outArr = outArrBlock.get(); + + if (getSpeakerArrangementWrapper (effect, inArr, outArr)) + return true; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int opcode = (isInput ? plugInOpcodeGetInputPinProperties : plugInOpcodeGetOutputPinProperties); + const int maxChannels = (isInput ? effect->numInputChannels : effect->numOutputChannels); + + int channels = 1; + + for (int ch = 0; ch < maxChannels; ch += channels) + { + VstPinInfo pinProps; + + if (effect->dispatchFunction (effect, opcode, ch, 0, &pinProps, 0.0f) == 0) + return false; + + if ((pinProps.flags & vstPinInfoFlagValid) != 0) + return true; + + channels = (pinProps.flags & vstPinInfoFlagIsStereo) != 0 ? 2 : 1; + } + } + + return false; + } + + static bool getSpeakerArrangementWrapper (VstEffectInterface* effect, + VstSpeakerConfiguration* inArr, + VstSpeakerConfiguration* outArr) + { + // Workaround: unfortunately old JUCE VST-2 plug-ins had a bug and would crash if + // you try to get the speaker arrangement when there are no input channels present. + // Hopefully, one day (when there are no more old JUCE plug-ins around), we can + // commment out the next two lines. + if (effect->numInputChannels == 0) + return false; + + return (effect->dispatchFunction (effect, plugInOpcodeGetSpeakerArrangement, 0, + reinterpret_cast (&inArr), &outArr, 0.0f) != 0); + } + + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, + AudioBuffer& tmpBuffer, + HeapBlock& channelBuffer) + { + auto numSamples = buffer.getNumSamples(); + auto numChannels = buffer.getNumChannels(); + + if (initialised) + { + if (auto* currentPlayHead = getPlayHead()) + { + AudioPlayHead::CurrentPositionInfo position; + + if (currentPlayHead->getCurrentPosition (position)) + { + + vstHostTime.samplePosition = (double) position.timeInSamples; + vstHostTime.tempoBPM = position.bpm; + vstHostTime.timeSignatureNumerator = position.timeSigNumerator; + vstHostTime.timeSignatureDenominator = position.timeSigDenominator; + vstHostTime.musicalPosition = position.ppqPosition; + vstHostTime.lastBarPosition = position.ppqPositionOfLastBarStart; + vstHostTime.flags |= vstTimingInfoFlagTempoValid + | vstTimingInfoFlagTimeSignatureValid + | vstTimingInfoFlagMusicalPositionValid + | vstTimingInfoFlagLastBarPositionValid; + + int32 newTransportFlags = 0; + if (position.isPlaying) newTransportFlags |= vstTimingInfoFlagCurrentlyPlaying; + if (position.isRecording) newTransportFlags |= vstTimingInfoFlagCurrentlyRecording; + + if (newTransportFlags != (vstHostTime.flags & (vstTimingInfoFlagCurrentlyPlaying + | vstTimingInfoFlagCurrentlyRecording))) + vstHostTime.flags = (vstHostTime.flags & ~(vstTimingInfoFlagCurrentlyPlaying | vstTimingInfoFlagCurrentlyRecording)) | newTransportFlags | vstTimingInfoFlagTransportChanged; + else + vstHostTime.flags &= ~vstTimingInfoFlagTransportChanged; + + switch (position.frameRate) + { + case AudioPlayHead::fps24: setHostTimeFrameRate (vstSmpteRateFps24, 24.0, position.timeInSeconds); break; + case AudioPlayHead::fps25: setHostTimeFrameRate (vstSmpteRateFps25, 25.0, position.timeInSeconds); break; + case AudioPlayHead::fps30: setHostTimeFrameRate (vstSmpteRateFps30, 30.0, position.timeInSeconds); break; + case AudioPlayHead::fps60: setHostTimeFrameRate (vstSmpteRateFps60, 60.0, position.timeInSeconds); break; + + case AudioPlayHead::fps23976: setHostTimeFrameRateDrop (vstSmpteRateFps239, 24.0, position.timeInSeconds); break; + case AudioPlayHead::fps2997: setHostTimeFrameRateDrop (vstSmpteRateFps2997, 30.0, position.timeInSeconds); break; + case AudioPlayHead::fps2997drop: setHostTimeFrameRateDrop (vstSmpteRateFps2997drop, 30.0, position.timeInSeconds); break; + case AudioPlayHead::fps30drop: setHostTimeFrameRateDrop (vstSmpteRateFps30drop, 30.0, position.timeInSeconds); break; + case AudioPlayHead::fps60drop: setHostTimeFrameRateDrop (vstSmpteRateFps599, 60.0, position.timeInSeconds); break; + default: break; + } + + if (position.isLooping) + { + vstHostTime.loopStartPosition = position.ppqLoopStart; + vstHostTime.loopEndPosition = position.ppqLoopEnd; + vstHostTime.flags |= (vstTimingInfoFlagLoopPositionValid | vstTimingInfoFlagLoopActive); + } + else + { + vstHostTime.flags &= ~(vstTimingInfoFlagLoopPositionValid | vstTimingInfoFlagLoopActive); + } + } + } + + vstHostTime.systemTimeNanoseconds = getVSTHostTimeNanoseconds(); + + if (wantsMidiMessages) + { + midiEventsToSend.clear(); + midiEventsToSend.ensureSize (1); + + MidiBuffer::Iterator iter (midiMessages); + const uint8* midiData; + int numBytesOfMidiData, samplePosition; + + while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) + midiEventsToSend.addEvent (midiData, numBytesOfMidiData, + jlimit (0, numSamples - 1, samplePosition)); + + vstEffect->dispatchFunction (vstEffect, plugInOpcodePreAudioProcessingEvents, 0, 0, midiEventsToSend.events, 0); + } + + _clearfp(); + + // always ensure that the buffer is at least as large as the maximum number of channels + auto maxChannels = jmax (vstEffect->numInputChannels, vstEffect->numOutputChannels); + auto channels = channelBuffer.get(); + + if (numChannels < maxChannels) + { + if (numSamples > tmpBuffer.getNumSamples()) + tmpBuffer.setSize (tmpBuffer.getNumChannels(), numSamples); + + tmpBuffer.clear(); + } + + for (int ch = 0; ch < maxChannels; ++ch) + channels[ch] = (ch < numChannels ? buffer.getWritePointer (ch) : tmpBuffer.getWritePointer (ch)); + + { + AudioBuffer processBuffer (channels, maxChannels, numSamples); + + invokeProcessFunction (processBuffer, numSamples); + } + } + else + { + // Not initialised, so just bypass.. + for (int i = getTotalNumOutputChannels(); --i >= 0;) + buffer.clear (i, 0, buffer.getNumSamples()); + } + + { + // copy any incoming midi.. + const ScopedLock sl (midiInLock); + + midiMessages.swapWith (incomingMidi); + incomingMidi.clear(); + } + } + + //============================================================================== + inline void invokeProcessFunction (AudioBuffer& buffer, int32 sampleFrames) + { + if ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0) + { + vstEffect->processAudioInplaceFunction (vstEffect, buffer.getArrayOfWritePointers(), + buffer.getArrayOfWritePointers(), sampleFrames); + } + else + { + outOfPlaceBuffer.setSize (vstEffect->numOutputChannels, sampleFrames); + outOfPlaceBuffer.clear(); + + vstEffect->processAudioFunction (vstEffect, buffer.getArrayOfWritePointers(), + outOfPlaceBuffer.getArrayOfWritePointers(), sampleFrames); + + for (int i = vstEffect->numOutputChannels; --i >= 0;) + buffer.copyFrom (i, 0, outOfPlaceBuffer.getReadPointer (i), sampleFrames); + } + } + + inline void invokeProcessFunction (AudioBuffer& buffer, int32 sampleFrames) + { + vstEffect->processDoubleAudioInplaceFunction (vstEffect, buffer.getArrayOfWritePointers(), + buffer.getArrayOfWritePointers(), sampleFrames); + } + + //============================================================================== + void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept + { + vstHostTime.flags |= vstTimingInfoFlagSmpteValid; + vstHostTime.smpteRate = (int32) frameRateIndex; + vstHostTime.smpteOffset = (int32) (currentTime * 80.0 * frameRate + 0.5); + } + + void setHostTimeFrameRateDrop (long frameRateIndex, double frameRate, double currentTime) noexcept + { + setHostTimeFrameRate (frameRateIndex, frameRate * 1000.0 / 1001.0, currentTime); + } + + bool restoreProgramSettings (const fxProgram* const prog) + { + if (compareMagic (prog->chunkMagic, "CcnK") + && compareMagic (prog->fxMagic, "FxCk")) + { + changeProgramName (getCurrentProgram(), prog->prgName); + + for (int i = 0; i < fxbSwap (prog->numParams); ++i) + setParameter (i, fxbSwapFloat (prog->params[i])); + + return true; + } + + return false; + } + + String getTextForOpcode (const int index, const VstHostToPlugInOpcodes opcode) const + { + if (vstEffect == nullptr) + return {}; + + jassert (index >= 0 && index < vstEffect->numParameters); + char nm[256] = { 0 }; + dispatch (opcode, index, 0, nm, 0); + return String::createStringFromData (nm, (int) sizeof (nm)).trim(); + } + + String getCurrentProgramName() + { + String progName; + + if (vstEffect != nullptr) + { + { + char nm[256] = { 0 }; + dispatch (plugInOpcodeGetCurrentProgramName, 0, 0, nm, 0); + progName = String::createStringFromData (nm, (int) sizeof (nm)).trim(); + } + + const int index = getCurrentProgram(); + + if (index >= 0 && programNames[index].isEmpty()) + { + while (programNames.size() < index) + programNames.add (String()); + + programNames.set (index, progName); + } + } + + return progName; + } + + void setParamsInProgramBlock (fxProgram* prog) + { + auto numParams = getNumParameters(); + + prog->chunkMagic = fxbName ("CcnK"); + prog->byteSize = 0; + prog->fxMagic = fxbName ("FxCk"); + prog->version = fxbSwap (fxbVersionNum); + prog->fxID = fxbSwap (getUID()); + prog->fxVersion = fxbSwap (getVersionNumber()); + prog->numParams = fxbSwap (numParams); + + getCurrentProgramName().copyToUTF8 (prog->prgName, sizeof (prog->prgName) - 1); + + for (int i = 0; i < numParams; ++i) + prog->params[i] = fxbSwapFloat (getParameter (i)); + } + + void updateStoredProgramNames() + { + if (vstEffect != nullptr && getNumPrograms() > 0) + { + char nm[256] = { 0 }; + + // only do this if the plugin can't use indexed names.. + if (dispatch (plugInOpcodeGetProgramName, 0, -1, nm, 0) == 0) + { + auto oldProgram = getCurrentProgram(); + MemoryBlock oldSettings; + createTempParameterStore (oldSettings); + + for (int i = 0; i < getNumPrograms(); ++i) + { + setCurrentProgram (i); + getCurrentProgramName(); // (this updates the list) + } + + setCurrentProgram (oldProgram); + restoreFromTempParameterStore (oldSettings); + } + } + } + + void handleMidiFromPlugin (const VstEventBlock* events) + { + if (events != nullptr) + { + const ScopedLock sl (midiInLock); + VSTMidiEventList::addEventsToMidiBuffer (events, incomingMidi); + } + } + + //============================================================================== + void createTempParameterStore (MemoryBlock& dest) + { + dest.setSize (64 + 4 * (size_t) getNumParameters()); + dest.fillWith (0); + + getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); + + auto p = (float*) (((char*) dest.getData()) + 64); + + for (int i = 0; i < getNumParameters(); ++i) + p[i] = getParameter(i); + } + + void restoreFromTempParameterStore (const MemoryBlock& m) + { + changeProgramName (getCurrentProgram(), (const char*) m.getData()); + + auto p = (float*) (((char*) m.getData()) + 64); + + for (int i = 0; i < getNumParameters(); ++i) + setParameter (i, p[i]); + } + + pointer_sized_int getVstDirectory() const + { + #if JUCE_MAC + return (pointer_sized_int) (void*) &vstModule->parentDirFSSpec; + #else + return (pointer_sized_int) (pointer_sized_uint) vstModule->fullParentDirectoryPathName.toRawUTF8(); + #endif + } + + //============================================================================== + int getVersionNumber() const noexcept { return vstEffect != nullptr ? vstEffect->plugInVersion : 0; } + + String getVersion() const + { + auto v = (unsigned int) dispatch (plugInOpcodeGetManufacturerVersion, 0, 0, 0, 0); + + String s; + + if (v == 0 || (int) v == -1) + v = (unsigned int) getVersionNumber(); + + if (v != 0) + { + int versionBits[32]; + int n = 0; + + for (auto vv = v; vv != 0; vv /= 10) + versionBits [n++] = vv % 10; + + if (n > 4) // if the number ends up silly, it's probably encoded as hex instead of decimal.. + { + n = 0; + + for (auto vv = v; vv != 0; vv >>= 8) + versionBits [n++] = vv & 255; + } + + while (n > 1 && versionBits [n - 1] == 0) + --n; + + s << 'V'; + + while (n > 0) + { + s << versionBits [--n]; + + if (n > 0) + s << '.'; + } + } + + return s; + } + + const char* getCategory() const + { + switch (getVstCategory()) + { + case kPlugCategEffect: return "Effect"; + case kPlugCategSynth: return "Synth"; + case kPlugCategAnalysis: return "Analysis"; + case kPlugCategMastering: return "Mastering"; + case kPlugCategSpacializer: return "Spacial"; + case kPlugCategRoomFx: return "Reverb"; + case kPlugSurroundFx: return "Surround"; + case kPlugCategRestoration: return "Restoration"; + case kPlugCategGenerator: return "Tone generation"; + default: break; + } + + return nullptr; + } + + void setPower (const bool on) + { + dispatch (plugInOpcodeResumeSuspend, 0, on ? 1 : 0, 0, 0); + isPowerOn = on; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance) +}; + +//============================================================================== +#if ! (JUCE_IOS || JUCE_ANDROID) +struct VSTPluginWindow; +static Array activeVSTWindows; + +//============================================================================== +struct VSTPluginWindow : public AudioProcessorEditor, + #if ! JUCE_MAC + private ComponentMovementWatcher, + #endif + private Timer +{ +public: + VSTPluginWindow (VSTPluginInstance& plug) + : AudioProcessorEditor (&plug), + #if ! JUCE_MAC + ComponentMovementWatcher (this), + #endif + plugin (plug) + { + #if JUCE_LINUX + pluginWindow = None; + display = XWindowSystem::getInstance()->displayRef(); + + #elif JUCE_MAC + ignoreUnused (recursiveResize, pluginRefusesToResize, alreadyInside); + + #if JUCE_SUPPORT_CARBON + if (! plug.usesCocoaNSView) + addAndMakeVisible (carbonWrapper = new CarbonWrapperComponent (*this)); + else + #endif + addAndMakeVisible (cocoaWrapper = new AutoResizingNSViewComponentWithParent()); + #endif + + activeVSTWindows.add (this); + + setSize (1, 1); + setOpaque (true); + setVisible (true); + } + + ~VSTPluginWindow() + { + closePluginWindow(); + + #if JUCE_MAC + #if JUCE_SUPPORT_CARBON + carbonWrapper = nullptr; + #endif + cocoaWrapper = nullptr; + #elif JUCE_LINUX + display = XWindowSystem::getInstance()->displayUnref(); + #endif + + activeVSTWindows.removeFirstMatchingValue (this); + plugin.editorBeingDeleted (this); + } + + //============================================================================== + #if ! JUCE_MAC + void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override + { + if (recursiveResize) + return; + + auto* topComp = getTopLevelComponent(); + + if (topComp->getPeer() != nullptr) + { + auto pos = topComp->getLocalPoint (this, Point()); + + recursiveResize = true; + + #if JUCE_WINDOWS + if (pluginHWND != 0) + MoveWindow (pluginHWND, pos.getX(), pos.getY(), getWidth(), getHeight(), TRUE); + #elif JUCE_LINUX + if (pluginWindow != 0) + { + XMoveResizeWindow (display, pluginWindow, + pos.getX(), pos.getY(), + (unsigned int) getWidth(), + (unsigned int) getHeight()); + + XMapRaised (display, pluginWindow); + XFlush (display); + } + #endif + + recursiveResize = false; + } + } + + void componentVisibilityChanged() override + { + if (isShowing()) + openPluginWindow(); + else if (! shouldAvoidDeletingWindow()) + closePluginWindow(); + + componentMovedOrResized (true, true); + } + + void componentPeerChanged() override + { + closePluginWindow(); + openPluginWindow(); + + #if JUCE_LINUX + componentMovedOrResized (true, true); + #endif + } + #endif + + #if JUCE_MAC + void visibilityChanged() override + { + if (cocoaWrapper != nullptr) + { + if (isVisible()) + openPluginWindow ((NSView*) cocoaWrapper->getView()); + else + closePluginWindow(); + } + } + + void childBoundsChanged (Component*) override + { + if (cocoaWrapper != nullptr) + { + auto w = cocoaWrapper->getWidth(); + auto h = cocoaWrapper->getHeight(); + + if (w != getWidth() || h != getHeight()) + setSize (w, h); + } + } + #endif + + //============================================================================== + bool keyStateChanged (bool) override { return pluginWantsKeys; } + bool keyPressed (const juce::KeyPress&) override { return pluginWantsKeys; } + + //============================================================================== + #if JUCE_MAC + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + #else + void paint (Graphics& g) override + { + if (isOpen) + { + #if JUCE_LINUX + if (pluginWindow != 0) + { + auto clip = g.getClipBounds(); + + XClearArea (display, pluginWindow, + clip.getX(), clip.getY(), + static_cast (clip.getWidth()), + static_cast (clip.getHeight()), + True); + } + #endif + } + else + { + g.fillAll (Colours::black); + } + } + #endif + + //============================================================================== + void timerCallback() override + { + if (isShowing()) + { + #if JUCE_WINDOWS + if (--sizeCheckCount <= 0) + { + sizeCheckCount = 10; + checkPluginWindowSize(); + } + #endif + + static bool reentrantGuard = false; + + if (! reentrantGuard) + { + reentrantGuard = true; + plugin.dispatch (plugInOpcodeEditorIdle, 0, 0, 0, 0); + reentrantGuard = false; + } + + #if JUCE_LINUX + if (pluginWindow == 0) + { + updatePluginWindowHandle(); + + if (pluginWindow != 0) + componentMovedOrResized (true, true); + } + #endif + } + } + + //============================================================================== + void mouseDown (const MouseEvent& e) override + { + ignoreUnused (e); + + #if JUCE_WINDOWS || JUCE_LINUX + toFront (true); + #endif + } + + void broughtToFront() override + { + activeVSTWindows.removeFirstMatchingValue (this); + activeVSTWindows.add (this); + + #if JUCE_MAC + dispatch (plugInOpcodeeffEditorTop, 0, 0, 0, 0); + #endif + } + + void setScaleFactor (float newScale) override + { + scaleFactor = newScale; + dispatch (plugInOpcodeManufacturerSpecific, presonusVendorID, + presonusSetContentScaleFactor, nullptr, newScale); + } + + void sendScaleFactorIfNotUnity() + { + if (scaleFactor != 1.0f) + setScaleFactor (scaleFactor); + } + + //============================================================================== +private: + VSTPluginInstance& plugin; + float scaleFactor = 1.0f; + bool isOpen = false, recursiveResize = false; + bool pluginWantsKeys = false, pluginRefusesToResize = false, alreadyInside = false; + + #if JUCE_WINDOWS + HWND pluginHWND = {}; + void* originalWndProc = {}; + int sizeCheckCount = 0; + #elif JUCE_LINUX + ::Display* display; + Window pluginWindow; + #endif + + // This is a workaround for old Mackie plugins that crash if their + // window is deleted more than once. + bool shouldAvoidDeletingWindow() const + { + return plugin.getPluginDescription() + .manufacturerName.containsIgnoreCase ("Loud Technologies"); + } + + // This is an old workaround for some plugins that need a repaint when their + // windows are first created, but it breaks some Izotope plugins.. + bool shouldRepaintCarbonWindowWhenCreated() + { + return ! plugin.getName().containsIgnoreCase ("izotope"); + } + + //============================================================================== +#if JUCE_MAC + void openPluginWindow (void* parentWindow) + { + if (isOpen || parentWindow == 0) + return; + + isOpen = true; + + VstEditorBounds* rect = nullptr; + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeOpenEditor, 0, 0, parentWindow, 0); + sendScaleFactorIfNotUnity(); + + // do this before and after like in the steinberg example + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeGetCurrentProgram, 0, 0, 0, 0); // also in steinberg code + + // Install keyboard hooks + pluginWantsKeys = (dispatch (plugInOpcodeKeyboardFocusRequired, 0, 0, 0, 0) == 0); + + // double-check it's not too tiny + int w = 250, h = 150; + + if (rect != nullptr) + { + w = rect->rightmost - rect->leftmost; + h = rect->lower - rect->upper; + + if (w == 0 || h == 0) + { + w = 250; + h = 150; + } + } + + w = jmax (w, 32); + h = jmax (h, 32); + + setSize (w, h); + + startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); + repaint(); + } + +#else + void openPluginWindow() + { + if (isOpen || getWindowHandle() == 0) + return; + + JUCE_VST_LOG ("Opening VST UI: " + plugin.getName()); + isOpen = true; + + VstEditorBounds* rect = nullptr; + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeOpenEditor, 0, 0, getWindowHandle(), 0); + sendScaleFactorIfNotUnity(); + + // do this before and after like in the steinberg example + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeGetCurrentProgram, 0, 0, 0, 0); // also in steinberg code + + // Install keyboard hooks + pluginWantsKeys = (dispatch (plugInOpcodeKeyboardFocusRequired, 0, 0, 0, 0) == 0); + + #if JUCE_WINDOWS + originalWndProc = 0; + pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); + + if (pluginHWND == 0) + { + isOpen = false; + setSize (300, 150); + return; + } + + #pragma warning (push) + #pragma warning (disable: 4244) + + if (! pluginWantsKeys) + { + originalWndProc = (void*) GetWindowLongPtr (pluginHWND, GWLP_WNDPROC); + SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) vstHookWndProc); + } + + #pragma warning (pop) + + RECT r; + GetWindowRect (pluginHWND, &r); + int w = r.right - r.left; + int h = r.bottom - r.top; + + if (rect != nullptr) + { + const int rw = rect->rightmost - rect->leftmost; + const int rh = rect->lower - rect->upper; + + if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h) + || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) + { + // very dodgy logic to decide which size is right. + if (abs (rw - w) > 350 || abs (rh - h) > 350) + { + SetWindowPos (pluginHWND, 0, + 0, 0, rw, rh, + SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + + GetWindowRect (pluginHWND, &r); + + w = r.right - r.left; + h = r.bottom - r.top; + + pluginRefusesToResize = (w != rw) || (h != rh); + + w = rw; + h = rh; + } + } + } + + #elif JUCE_LINUX + updatePluginWindowHandle(); + + int w = 250, h = 150; + + if (rect != nullptr) + { + w = rect->rightmost - rect->leftmost; + h = rect->lower - rect->upper; + + if (w == 0 || h == 0) + { + w = 250; + h = 150; + } + } + + if (pluginWindow != 0) + XMapRaised (display, pluginWindow); + #endif + + // double-check it's not too tiny + w = jmax (w, 32); + h = jmax (h, 32); + + setSize (w, h); + + #if JUCE_WINDOWS + checkPluginWindowSize(); + #endif + + startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); + repaint(); + } +#endif + + //============================================================================== + void closePluginWindow() + { + if (isOpen) + { + // You shouldn't end up hitting this assertion unless the host is trying to do GUI + // cleanup on a non-GUI thread.. If it does that, bad things could happen in here.. + jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + JUCE_VST_LOG ("Closing VST UI: " + plugin.getName()); + isOpen = false; + dispatch (plugInOpcodeCloseEditor, 0, 0, 0, 0); + stopTimer(); + + #if JUCE_WINDOWS + #pragma warning (push) + #pragma warning (disable: 4244) + if (originalWndProc != 0 && pluginHWND != 0 && IsWindow (pluginHWND)) + SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc); + #pragma warning (pop) + + originalWndProc = 0; + pluginHWND = 0; + #elif JUCE_LINUX + pluginWindow = 0; + #endif + } + } + + //============================================================================== + pointer_sized_int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) + { + return plugin.dispatch (opcode, index, value, ptr, opt); + } + + //============================================================================== + #if JUCE_WINDOWS + void checkPluginWindowSize() + { + RECT r; + GetWindowRect (pluginHWND, &r); + auto w = r.right - r.left; + auto h = r.bottom - r.top; + + if (isShowing() && w > 0 && h > 0 + && (w != getWidth() || h != getHeight()) + && ! pluginRefusesToResize) + { + setSize (w, h); + sizeCheckCount = 0; + } + } + + // hooks to get keyboard events from VST windows.. + static LRESULT CALLBACK vstHookWndProc (HWND hW, UINT message, WPARAM wParam, LPARAM lParam) + { + for (int i = activeVSTWindows.size(); --i >= 0;) + { + Component::SafePointer w (activeVSTWindows[i]); + + if (w != nullptr && w->pluginHWND == hW) + { + if (message == WM_CHAR + || message == WM_KEYDOWN + || message == WM_SYSKEYDOWN + || message == WM_KEYUP + || message == WM_SYSKEYUP + || message == WM_APPCOMMAND) + { + SendMessage ((HWND) w->getTopLevelComponent()->getWindowHandle(), + message, wParam, lParam); + } + + if (w != nullptr) // (may have been deleted in SendMessage callback) + return CallWindowProc ((WNDPROC) w->originalWndProc, + (HWND) w->pluginHWND, + message, wParam, lParam); + } + } + + return DefWindowProc (hW, message, wParam, lParam); + } + #endif + + #if JUCE_LINUX + void updatePluginWindowHandle() + { + pluginWindow = getChildWindow ((Window) getWindowHandle()); + } + #endif + + //============================================================================== +#if JUCE_MAC + #if JUCE_SUPPORT_CARBON + struct CarbonWrapperComponent : public CarbonViewWrapperComponent + { + CarbonWrapperComponent (VSTPluginWindow& w) : owner (w) + { + keepPluginWindowWhenHidden = w.shouldAvoidDeletingWindow(); + setRepaintsChildHIViewWhenCreated (w.shouldRepaintCarbonWindowWhenCreated()); + } + + ~CarbonWrapperComponent() + { + deleteWindow(); + } + + HIViewRef attachView (WindowRef windowRef, HIViewRef /*rootView*/) override + { + owner.openPluginWindow (windowRef); + return {}; + } + + void removeView (HIViewRef) override + { + if (owner.isOpen) + { + owner.isOpen = false; + owner.dispatch (plugInOpcodeCloseEditor, 0, 0, 0, 0); + owner.dispatch (plugInOpcodeSleepEditor, 0, 0, 0, 0); + } + } + + bool getEmbeddedViewSize (int& w, int& h) override + { + VstEditorBounds* rect = nullptr; + owner.dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + w = rect->rightmost - rect->leftmost; + h = rect->lower - rect->upper; + return true; + } + + void handleMouseDown (int x, int y) override + { + if (! alreadyInside) + { + alreadyInside = true; + getTopLevelComponent()->toFront (true); + owner.dispatch (plugInOpcodeGetMouse, x, y, 0, 0); + alreadyInside = false; + } + else + { + PostEvent (::mouseDown, 0); + } + } + + void handlePaint() override + { + if (auto* peer = getPeer()) + { + auto pos = peer->globalToLocal (getScreenPosition()); + VstEditorBounds r; + r.leftmost = (int16) pos.getX(); + r.upper = (int16) pos.getY(); + r.rightmost = (int16) (r.leftmost + getWidth()); + r.lower = (int16) (r.upper + getHeight()); + + owner.dispatch (plugInOpcodeDrawEditor, 0, 0, &r, 0); + } + } + + private: + VSTPluginWindow& owner; + bool alreadyInside = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CarbonWrapperComponent) + }; + + friend struct CarbonWrapperComponent; + ScopedPointer carbonWrapper; + #endif + + ScopedPointer cocoaWrapper; + + void resized() override + { + #if JUCE_SUPPORT_CARBON + if (carbonWrapper != nullptr) + carbonWrapper->setSize (getWidth(), getHeight()); + #endif + + if (cocoaWrapper != nullptr) + cocoaWrapper->setSize (getWidth(), getHeight()); + } +#endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow) +}; +#endif +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +AudioProcessorEditor* VSTPluginInstance::createEditor() +{ + #if JUCE_IOS || JUCE_ANDROID + return nullptr; + #else + return hasEditor() ? new VSTPluginWindow (*this) + : nullptr; + #endif +} + +//============================================================================== +// entry point for all callbacks from the plugin +static pointer_sized_int VSTINTERFACECALL audioMaster (VstEffectInterface* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) +{ + if (effect != nullptr) + if (auto* instance = (VSTPluginInstance*) (effect->hostSpace2)) + return instance->handleCallback (opcode, index, value, ptr, opt); + + return VSTPluginInstance::handleGeneralCallback (opcode, index, value, ptr, opt); +} + +//============================================================================== +VSTPluginFormat::VSTPluginFormat() {} +VSTPluginFormat::~VSTPluginFormat() {} + +static VSTPluginInstance* createAndUpdateDesc (VSTPluginFormat& format, PluginDescription& desc) +{ + if (auto* p = format.createInstanceFromDescription (desc, 44100.0, 512)) + { + if (auto* instance = dynamic_cast (p)) + { + #if JUCE_MAC + if (instance->vstModule->resFileId != 0) + UseResFile (instance->vstModule->resFileId); + #endif + + instance->fillInPluginDescription (desc); + return instance; + } + + jassertfalse; + } + + return nullptr; +} + +void VSTPluginFormat::findAllTypesForFile (OwnedArray& results, + const String& fileOrIdentifier) +{ + if (! fileMightContainThisPluginType (fileOrIdentifier)) + return; + + PluginDescription desc; + desc.fileOrIdentifier = fileOrIdentifier; + desc.uid = 0; + + ScopedPointer instance (createAndUpdateDesc (*this, desc)); + + if (instance == nullptr) + return; + + if (instance->getVstCategory() != kPlugCategShell) + { + // Normal plugin... + results.add (new PluginDescription (desc)); + + instance->dispatch (plugInOpcodeOpen, 0, 0, 0, 0); + } + else + { + // It's a shell plugin, so iterate all the subtypes... + for (;;) + { + char shellEffectName [256] = { 0 }; + auto uid = (int) instance->dispatch (plugInOpcodeNextPlugInUniqueID, 0, 0, shellEffectName, 0); + + if (uid == 0) + break; + + desc.uid = uid; + desc.name = shellEffectName; + + aboutToScanVSTShellPlugin (desc); + + ScopedPointer shellInstance (createAndUpdateDesc (*this, desc)); + + if (shellInstance != nullptr) + { + jassert (desc.uid == uid); + desc.hasSharedContainer = true; + desc.name = shellEffectName; + + if (! arrayContainsPlugin (results, desc)) + results.add (new PluginDescription (desc)); + } + } + } +} + +void VSTPluginFormat::createPluginInstance (const PluginDescription& desc, + double sampleRate, + int blockSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) +{ + ScopedPointer result; + + if (fileMightContainThisPluginType (desc.fileOrIdentifier)) + { + File file (desc.fileOrIdentifier); + + auto previousWorkingDirectory = File::getCurrentWorkingDirectory(); + file.getParentDirectory().setAsCurrentWorkingDirectory(); + + if (auto module = ModuleHandle::findOrCreateModule (file)) + { + shellUIDToCreate = desc.uid; + + result = VSTPluginInstance::create (module, sampleRate, blockSize); + + if (result != nullptr && ! result->initialiseEffect (sampleRate, blockSize)) + result = nullptr; + } + + previousWorkingDirectory.setAsCurrentWorkingDirectory(); + } + + String errorMsg; + + if (result == nullptr) + errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "VST-2"); + + callback (userData, result.release(), errorMsg); +} + +bool VSTPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept +{ + return false; +} + +bool VSTPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) +{ + auto f = File::createFileWithoutCheckingPath (fileOrIdentifier); + + #if JUCE_MAC || JUCE_IOS + return f.isDirectory() && f.hasFileExtension (".vst"); + #elif JUCE_WINDOWS + return f.existsAsFile() && f.hasFileExtension (".dll"); + #elif JUCE_LINUX || JUCE_ANDROID + return f.existsAsFile() && f.hasFileExtension (".so"); + #endif +} + +String VSTPluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) +{ + return fileOrIdentifier; +} + +bool VSTPluginFormat::pluginNeedsRescanning (const PluginDescription& desc) +{ + return File (desc.fileOrIdentifier).getLastModificationTime() != desc.lastFileModTime; +} + +bool VSTPluginFormat::doesPluginStillExist (const PluginDescription& desc) +{ + return File (desc.fileOrIdentifier).exists(); +} + +StringArray VSTPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) +{ + StringArray results; + + for (int j = 0; j < directoriesToSearch.getNumPaths(); ++j) + recursiveFileSearch (results, directoriesToSearch [j], recursive); + + return results; +} + +void VSTPluginFormat::recursiveFileSearch (StringArray& results, const File& dir, const bool recursive) +{ + // avoid allowing the dir iterator to be recursive, because we want to avoid letting it delve inside + // .component or .vst directories. + DirectoryIterator iter (dir, false, "*", File::findFilesAndDirectories); + + while (iter.next()) + { + auto f = iter.getFile(); + bool isPlugin = false; + + if (fileMightContainThisPluginType (f.getFullPathName())) + { + isPlugin = true; + results.add (f.getFullPathName()); + } + + if (recursive && (! isPlugin) && f.isDirectory()) + recursiveFileSearch (results, f, true); + } +} + +FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() +{ + #if JUCE_MAC + return FileSearchPath ("~/Library/Audio/Plug-Ins/VST;/Library/Audio/Plug-Ins/VST"); + #elif JUCE_LINUX || JUCE_ANDROID + return FileSearchPath (SystemStats::getEnvironmentVariable ("VST_PATH", + "/usr/lib/vst;/usr/local/lib/vst;~/.vst") + .replace (":", ";")); + #elif JUCE_WINDOWS + auto programFiles = File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName(); + + FileSearchPath paths; + paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath")); + paths.addIfNotAlreadyThere (programFiles + "\\Steinberg\\VstPlugins"); + paths.removeNonExistentPaths(); + paths.addIfNotAlreadyThere (programFiles + "\\VstPlugins"); + paths.removeRedundantPaths(); + return paths; + #elif JUCE_IOS + // on iOS you can only load plug-ins inside the hosts bundle folder + CFURLRef relativePluginDir = CFBundleCopyBuiltInPlugInsURL (CFBundleGetMainBundle()); + CFURLRef pluginDir = CFURLCopyAbsoluteURL (relativePluginDir); + CFRelease (relativePluginDir); + + CFStringRef path = CFURLCopyFileSystemPath (pluginDir, kCFURLPOSIXPathStyle); + CFRelease (pluginDir); + + FileSearchPath retval (String (CFStringGetCStringPtr (path, kCFStringEncodingUTF8))); + CFRelease (path); + + return retval; + #endif +} + +const XmlElement* VSTPluginFormat::getVSTXML (AudioPluginInstance* plugin) +{ + if (auto* vst = dynamic_cast (plugin)) + if (vst->vstModule != nullptr) + return vst->vstModule->vstXml.get(); + + return nullptr; +} + +bool VSTPluginFormat::loadFromFXBFile (AudioPluginInstance* plugin, const void* data, size_t dataSize) +{ + if (auto* vst = dynamic_cast (plugin)) + return vst->loadFromFXBFile (data, dataSize); + + return false; +} + +bool VSTPluginFormat::saveToFXBFile (AudioPluginInstance* plugin, MemoryBlock& dest, bool asFXB) +{ + if (auto* vst = dynamic_cast (plugin)) + return vst->saveToFXBFile (dest, asFXB); + + return false; +} + +bool VSTPluginFormat::getChunkData (AudioPluginInstance* plugin, MemoryBlock& result, bool isPreset) +{ + if (auto* vst = dynamic_cast (plugin)) + return vst->getChunkData (result, isPreset, 128); + + return false; +} + +bool VSTPluginFormat::setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset) +{ + if (auto* vst = dynamic_cast (plugin)) + return vst->setChunkData (data, size, isPreset); + + return false; +} + +AudioPluginInstance* VSTPluginFormat::createCustomVSTFromMainCall (void* entryPointFunction, + double initialSampleRate, int initialBufferSize) +{ + ModuleHandle::Ptr module = new ModuleHandle (File(), (MainCall) entryPointFunction); + + if (module->open()) + if (ScopedPointer result = VSTPluginInstance::create (module, initialSampleRate, initialBufferSize)) + if (result->initialiseEffect (initialSampleRate, initialBufferSize)) + return result.release(); + + return nullptr; +} + +void VSTPluginFormat::setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions) +{ + ScopedPointer f (functions); + + if (auto* vst = dynamic_cast (plugin)) + vst->extraFunctions = f; +} + +AudioPluginInstance* VSTPluginFormat::getPluginInstanceFromVstEffectInterface (void* aEffect) +{ + if (auto* vstAEffect = reinterpret_cast (aEffect)) + if (auto* instanceVST = reinterpret_cast (vstAEffect->hostSpace2)) + return dynamic_cast (instanceVST); + + return nullptr; +} + +pointer_sized_int JUCE_CALLTYPE VSTPluginFormat::dispatcher (AudioPluginInstance* plugin, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) +{ + if (auto* vst = dynamic_cast (plugin)) + return vst->dispatch (opcode, index, value, ptr, opt); + + return {}; +} + +void VSTPluginFormat::aboutToScanVSTShellPlugin (const PluginDescription&) {} + +} // namespace juce + +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h new file mode 100644 index 000000000..1edeb83e1 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h @@ -0,0 +1,132 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 (JUCE_PLUGINHOST_VST || DOXYGEN) + +namespace juce +{ + +//============================================================================== +/** + Implements a plugin format manager for VSTs. +*/ +class JUCE_API VSTPluginFormat : public AudioPluginFormat +{ +public: + //============================================================================== + VSTPluginFormat(); + ~VSTPluginFormat(); + + //============================================================================== + /** Attempts to retrieve the VSTXML data from a plugin. + Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. + */ + static const XmlElement* getVSTXML (AudioPluginInstance* plugin); + + /** Attempts to reload a VST plugin's state from some FXB or FXP data. */ + static bool loadFromFXBFile (AudioPluginInstance* plugin, const void* data, size_t dataSize); + + /** Attempts to save a VST's state to some FXP or FXB data. */ + static bool saveToFXBFile (AudioPluginInstance* plugin, MemoryBlock& result, bool asFXB); + + /** Attempts to get a VST's state as a chunk of memory. */ + static bool getChunkData (AudioPluginInstance* plugin, MemoryBlock& result, bool isPreset); + + /** Attempts to set a VST's state from a chunk of memory. */ + static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); + + /** Given a suitable function pointer to a VSTPluginMain function, this will attempt to + instantiate and return a plugin for it. + */ + static AudioPluginInstance* createCustomVSTFromMainCall (void* entryPointFunction, + double initialSampleRate, + int initialBufferSize); + + //============================================================================== + /** Base class for some extra functions that can be attached to a VST plugin instance. */ + class ExtraFunctions + { + public: + virtual ~ExtraFunctions() {} + + /** This should return 10000 * the BPM at this position in the current edit. */ + virtual int64 getTempoAt (int64 samplePos) = 0; + + /** This should return the host's automation state. + @returns 0 = not supported, 1 = off, 2 = read, 3 = write, 4 = read/write + */ + virtual int getAutomationState() = 0; + }; + + /** Provides an ExtraFunctions callback object for a plugin to use. + The plugin will take ownership of the object and will delete it automatically. + */ + static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); + + //============================================================================== + /** This simply calls directly to the VST's AEffect::dispatcher() function. */ + static pointer_sized_int JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, pointer_sized_int, void*, float); + + /** Given a VstEffectInterface* (aka vst::AEffect*), this will return the juce AudioPluginInstance + that is being used to wrap it + */ + static AudioPluginInstance* getPluginInstanceFromVstEffectInterface (void* aEffect); + + //============================================================================== + String getName() const override { return "VST"; } + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; + bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; + String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; + bool pluginNeedsRescanning (const PluginDescription&) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; + bool doesPluginStillExist (const PluginDescription&) override; + FileSearchPath getDefaultLocationsToSearch() override; + bool canScanForPlugins() const override { return true; } + + /** Can be overridden to receive a callback when each member of a shell plugin is about to be + tested during a call to findAllTypesForFile(). + Only the name and uid members of the PluginDescription are guaranteed to be valid when + this is called. + */ + virtual void aboutToScanVSTShellPlugin (const PluginDescription&); + +private: + //============================================================================== + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + +private: + void recursiveFileSearch (StringArray&, const File&, bool recursive); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginFormat) +}; + +} // namespace juce + +#endif diff --git a/source/modules/juce_audio_processors/juce_audio_processors.cpp b/source/modules/juce_audio_processors/juce_audio_processors.cpp new file mode 100644 index 000000000..675784f1c --- /dev/null +++ b/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -0,0 +1,185 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +#ifdef JUCE_AUDIO_PROCESSORS_H_INCLUDED + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 + +#include "juce_audio_processors.h" +#include + +//============================================================================== +#if JUCE_MAC + #if JUCE_SUPPORT_CARBON \ + && ((JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU) \ + || ! (defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)) + #include + #include "../juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h" + #endif +#endif + +#if JUCE_PLUGINHOST_VST && JUCE_LINUX && ! JUCE_AUDIOPROCESSOR_NO_GUI + #include + #include + #undef KeyPress +#endif + +#if ! JUCE_WINDOWS && ! JUCE_MAC + #undef JUCE_PLUGINHOST_VST3 + #define JUCE_PLUGINHOST_VST3 0 +#endif + +#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) + #include +#endif + +//============================================================================== +namespace juce +{ + +static inline bool arrayContainsPlugin (const OwnedArray& list, + const PluginDescription& desc) +{ + for (auto* p : list) + if (p->isDuplicateOf (desc)) + return true; + + return false; +} + +#if JUCE_MAC || JUCE_IOS + +#if JUCE_IOS + #define JUCE_IOS_MAC_VIEW UIView + typedef UIViewComponent ViewComponentBaseClass; +#else + #define JUCE_IOS_MAC_VIEW NSView + typedef NSViewComponent ViewComponentBaseClass; +#endif + +//============================================================================== +struct AutoResizingNSViewComponent : public ViewComponentBaseClass, + private AsyncUpdater +{ + AutoResizingNSViewComponent(); + void childBoundsChanged (Component*) override; + void handleAsyncUpdate() override; + bool recursive; +}; + +//============================================================================== +struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, + private Timer +{ + AutoResizingNSViewComponentWithParent(); + JUCE_IOS_MAC_VIEW* getChildView() const; + void timerCallback() override; +}; + +//============================================================================== +AutoResizingNSViewComponent::AutoResizingNSViewComponent() + : recursive (false) {} + +void AutoResizingNSViewComponent::childBoundsChanged (Component*) override +{ + if (recursive) + { + triggerAsyncUpdate(); + } + else + { + recursive = true; + resizeToFitView(); + recursive = true; + } +} + +void AutoResizingNSViewComponent::handleAsyncUpdate() override +{ + resizeToFitView(); +} + +//============================================================================== +AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() +{ + JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init]; + setView (v); + [v release]; + + startTimer (30); +} + +JUCE_IOS_MAC_VIEW* AutoResizingNSViewComponentWithParent::getChildView() const +{ + if (JUCE_IOS_MAC_VIEW* parent = (JUCE_IOS_MAC_VIEW*) getView()) + if ([[parent subviews] count] > 0) + return [[parent subviews] objectAtIndex: 0]; + + return nil; +} + +void AutoResizingNSViewComponentWithParent::timerCallback() override +{ + if (JUCE_IOS_MAC_VIEW* child = getChildView()) + { + stopTimer(); + setView (child); + } +} +#endif + +} // namespace juce + +#if JUCE_CLANG + #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include "format/juce_AudioPluginFormat.cpp" +#include "format/juce_AudioPluginFormatManager.cpp" +#include "processors/juce_AudioProcessor.cpp" +#include "processors/juce_AudioProcessorGraph.cpp" +#if ! JUCE_AUDIOPROCESSOR_NO_GUI + #include "processors/juce_AudioProcessorEditor.cpp" + #include "processors/juce_GenericAudioProcessorEditor.cpp" +#endif +#include "processors/juce_PluginDescription.cpp" +#include "format_types/juce_LADSPAPluginFormat.cpp" +#include "format_types/juce_VSTPluginFormat.cpp" +#include "format_types/juce_VST3PluginFormat.cpp" +#include "format_types/juce_AudioUnitPluginFormat.mm" +#include "scanning/juce_KnownPluginList.cpp" +#include "scanning/juce_PluginDirectoryScanner.cpp" +#include "scanning/juce_PluginListComponent.cpp" +#include "utilities/juce_AudioProcessorParameters.cpp" +#include "utilities/juce_AudioProcessorValueTreeState.cpp" diff --git a/source/modules/juce_audio_processors/juce_audio_processors.h b/source/modules/juce_audio_processors/juce_audio_processors.h new file mode 100644 index 000000000..ff0b4f730 --- /dev/null +++ b/source/modules/juce_audio_processors/juce_audio_processors.h @@ -0,0 +1,124 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_processors + vendor: juce + version: 5.1.2 + name: JUCE audio processor classes + description: Classes for loading and playing VST, AU, or internally-generated audio processors. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_gui_extra, juce_audio_basics + OSXFrameworks: CoreAudio CoreMIDI AudioToolbox + iOSFrameworks: AudioToolbox + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + +#pragma once +#define JUCE_AUDIO_PROCESSORS_H_INCLUDED + +#include +#include + +//============================================================================== +/** Config: JUCE_PLUGINHOST_VST + Enables the VST audio plugin hosting classes. + + @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 +*/ +#ifndef JUCE_PLUGINHOST_VST + #define JUCE_PLUGINHOST_VST 0 +#endif + +/** Config: JUCE_PLUGINHOST_VST3 + Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be + installed on your machine. + + @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_AU +*/ +#ifndef JUCE_PLUGINHOST_VST3 + #define JUCE_PLUGINHOST_VST3 0 +#endif + +/** Config: JUCE_PLUGINHOST_AU + Enables the AudioUnit plugin hosting classes. This is Mac-only, of course. + + @see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST, JUCE_PLUGINHOST_VST3 +*/ +#ifndef JUCE_PLUGINHOST_AU + #define JUCE_PLUGINHOST_AU 0 +#endif + +#if ! (JUCE_PLUGINHOST_AU || JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) +// #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" +#endif + +#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT || JUCE_IOS) + #define JUCE_SUPPORT_CARBON 1 +#endif + +#ifndef JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR + #define JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR 1 +#endif + +//============================================================================== +#include "processors/juce_AudioProcessorEditor.h" +#include "processors/juce_AudioProcessorListener.h" +#include "processors/juce_AudioProcessorParameter.h" +#include "processors/juce_AudioProcessor.h" +#include "processors/juce_PluginDescription.h" +#include "processors/juce_AudioPluginInstance.h" +#include "processors/juce_AudioProcessorGraph.h" +#include "processors/juce_GenericAudioProcessorEditor.h" +#include "format/juce_AudioPluginFormat.h" +#include "format/juce_AudioPluginFormatManager.h" +#include "scanning/juce_KnownPluginList.h" +#include "format_types/juce_AudioUnitPluginFormat.h" +#include "format_types/juce_LADSPAPluginFormat.h" +#include "format_types/juce_VSTMidiEventList.h" +#include "format_types/juce_VSTPluginFormat.h" +#include "format_types/juce_VST3PluginFormat.h" +#include "scanning/juce_PluginDirectoryScanner.h" +#include "scanning/juce_PluginListComponent.h" +#include "utilities/juce_AudioProcessorParameterWithID.h" +#include "utilities/juce_AudioParameterFloat.h" +#include "utilities/juce_AudioParameterInt.h" +#include "utilities/juce_AudioParameterBool.h" +#include "utilities/juce_AudioParameterChoice.h" +#include "utilities/juce_AudioProcessorValueTreeState.h" diff --git a/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h b/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h new file mode 100644 index 000000000..5e5c21c50 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h @@ -0,0 +1,89 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Base class for an active instance of a plugin. + + This derives from the AudioProcessor class, and adds some extra functionality + that helps when wrapping dynamically loaded plugins. + + This class is not needed when writing plugins, and you should never need to derive + your own sub-classes from it. The plugin hosting classes use it internally and will + return AudioPluginInstance objects which wrap external plugins. + + @see AudioProcessor, AudioPluginFormat +*/ +class JUCE_API AudioPluginInstance : public AudioProcessor +{ +public: + //============================================================================== + /** Destructor. + + Make sure that you delete any UI components that belong to this plugin before + deleting the plugin. + */ + virtual ~AudioPluginInstance() {} + + //============================================================================== + /** Fills-in the appropriate parts of this plugin description object. */ + virtual void fillInPluginDescription (PluginDescription& description) const = 0; + + /** Returns a PluginDescription for this plugin. + This is just a convenience method to avoid calling fillInPluginDescription. + */ + PluginDescription getPluginDescription() const + { + PluginDescription desc; + fillInPluginDescription (desc); + return desc; + } + + /** Returns a pointer to some kind of platform-specific data about the plugin. + E.g. For a VST, this value can be cast to an AEffect*. For an AudioUnit, it can be + cast to an AudioUnit handle. + */ + virtual void* getPlatformSpecificData() { return nullptr; } + + /** For some formats (currently AudioUnit), this forces a reload of the list of + available parameters. + */ + virtual void refreshParameterList() {} + +protected: + //============================================================================== + AudioPluginInstance() {} + AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} + template + AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp new file mode 100644 index 000000000..bf0c76b3e --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -0,0 +1,1464 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +static ThreadLocalValue wrapperTypeBeingCreated; + +void JUCE_CALLTYPE AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::WrapperType type) +{ + wrapperTypeBeingCreated = type; +} + +AudioProcessor::AudioProcessor() +{ + initialise (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), false) + .withOutput ("Output", AudioChannelSet::stereo(), false)); +} + +AudioProcessor::AudioProcessor(const BusesProperties& ioConfig) +{ + initialise (ioConfig); +} + +void AudioProcessor::initialise (const BusesProperties& ioConfig) +{ + cachedTotalIns = 0; + cachedTotalOuts = 0; + + wrapperType = wrapperTypeBeingCreated.get(); + playHead = nullptr; + currentSampleRate = 0; + blockSize = 0; + latencySamples = 0; + + #if JUCE_DEBUG + textRecursionCheck = false; + #endif + + suspended = false; + nonRealtime = false; + + processingPrecision = singlePrecision; + + const int numInputBuses = ioConfig.inputLayouts.size(); + const int numOutputBuses = ioConfig.outputLayouts.size(); + + for (int i = 0; i < numInputBuses; ++i) + createBus (true, ioConfig.inputLayouts. getReference (i)); + + for (int i = 0; i < numOutputBuses; ++i) + createBus (false, ioConfig.outputLayouts.getReference (i)); + + updateSpeakerFormatStrings(); +} + +AudioProcessor::~AudioProcessor() +{ + #if ! JUCE_AUDIOPROCESSOR_NO_GUI + // ooh, nasty - the editor should have been deleted before the filter + // that it refers to is deleted.. + jassert (activeEditor == nullptr); + #endif + + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This will fail if you've called beginParameterChangeGesture() for one + // or more parameters without having made a corresponding call to endParameterChangeGesture... + jassert (changingParams.countNumberOfSetBits() == 0); + #endif +} + +//============================================================================== +StringArray AudioProcessor::getAlternateDisplayNames() const { return StringArray (getName()); } + +//============================================================================== +bool AudioProcessor::addBus (bool isInput) +{ + if (! canAddBus (isInput)) + return false; + + BusProperties busesProps; + if (! canApplyBusCountChange (isInput, true, busesProps)) + return false; + + createBus (isInput, busesProps); + return true; +} + +bool AudioProcessor::removeBus (bool inputBus) +{ + const int numBuses = getBusCount (inputBus); + if (numBuses == 0) + return false; + + if (! canRemoveBus (inputBus)) + return false; + + BusProperties busesProps; + if (! canApplyBusCountChange (inputBus, false, busesProps)) + return false; + + const int busIdx = numBuses - 1; + const int numChannels = getChannelCountOfBus (inputBus, busIdx); + (inputBus ? inputBuses : outputBuses).remove (busIdx); + + audioIOChanged (true, numChannels > 0); + + return true; +} + + +//============================================================================== +bool AudioProcessor::setBusesLayout (const BusesLayout& arr) +{ + jassert (arr.inputBuses. size() == getBusCount (true) + && arr.outputBuses.size() == getBusCount (false)); + + if (arr == getBusesLayout()) + return true; + + BusesLayout copy = arr; + if (! canApplyBusesLayout (copy)) + return false; + + return applyBusLayouts (copy); +} + +bool AudioProcessor::setBusesLayoutWithoutEnabling (const BusesLayout& arr) +{ + const int numIns = getBusCount (true); + const int numOuts = getBusCount (false); + + jassert (arr.inputBuses. size() == numIns + && arr.outputBuses.size() == numOuts); + + BusesLayout request = arr; + const BusesLayout current = getBusesLayout(); + + for (int i = 0; i < numIns; ++i) + if (request.getNumChannels (true, i) == 0) + request.getChannelSet (true, i) = current.getChannelSet (true, i); + + for (int i = 0; i < numOuts; ++i) + if (request.getNumChannels (false, i) == 0) + request.getChannelSet (false, i) = current.getChannelSet (false, i); + + if (! checkBusesLayoutSupported(request)) + return false; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir != 0); + + for (int i = 0; i < (isInput ? numIns : numOuts); ++i) + { + Bus& bus = *getBus (isInput, i); + AudioChannelSet& set = request.getChannelSet (isInput, i); + + if (! bus.isEnabled()) + { + if (! set.isDisabled()) + bus.lastLayout = set; + + set = AudioChannelSet::disabled(); + } + } + } + + return setBusesLayout (request); +} + +AudioProcessor::BusesLayout AudioProcessor::getBusesLayout() const +{ + BusesLayout layouts; + const int numInputs = getBusCount (true); + const int numOutputs = getBusCount (false); + + for (int i = 0; i < numInputs; ++i) + layouts.inputBuses. add (getBus (true, i)->getCurrentLayout()); + + for (int i = 0; i < numOutputs; ++i) + layouts.outputBuses.add (getBus (false, i)->getCurrentLayout()); + + return layouts; +} + +AudioChannelSet AudioProcessor::getChannelLayoutOfBus (bool isInput, int busIdx) const noexcept +{ + const OwnedArray& buses = (isInput ? inputBuses : outputBuses); + if (Bus* bus = buses[busIdx]) + return bus->getCurrentLayout(); + + return AudioChannelSet(); +} + +bool AudioProcessor::setChannelLayoutOfBus (bool isInputBus, int busIdx, const AudioChannelSet& layout) +{ + if (Bus* bus = getBus (isInputBus, busIdx)) + { + BusesLayout layouts = bus->getBusesLayoutForLayoutChangeOfBus (layout); + + if (layouts.getChannelSet (isInputBus, busIdx) == layout) + return applyBusLayouts (layouts); + + return false; + } + + // busIdx parameter is invalid + jassertfalse; + + return false; +} + +bool AudioProcessor::enableAllBuses() +{ + BusesLayout layouts; + const int numInputs = getBusCount (true); + const int numOutputs = getBusCount (false); + + for (int i = 0; i < numInputs; ++i) + layouts.inputBuses. add (getBus (true, i)->lastLayout); + + for (int i = 0; i < numOutputs; ++i) + layouts.outputBuses.add (getBus (false, i)->lastLayout); + + return setBusesLayout (layouts); +} + +bool AudioProcessor::checkBusesLayoutSupported (const BusesLayout& layouts) const +{ + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + if (layouts.inputBuses. size() == numInputBuses + && layouts.outputBuses.size() == numOutputBuses) + return isBusesLayoutSupported (layouts); + + return false; +} + +void AudioProcessor::getNextBestLayout (const BusesLayout& desiredLayout, BusesLayout& actualLayouts) const +{ + // if you are hitting this assertion then you are requesting a next + // best layout which does not have the same number of buses as the + // audio processor. + jassert (desiredLayout.inputBuses. size() == getBusCount (true) + && desiredLayout.outputBuses.size() == getBusCount (false)); + + if (checkBusesLayoutSupported (desiredLayout)) + { + actualLayouts = desiredLayout; + return; + } + + BusesLayout originalState = actualLayouts; + BusesLayout currentState = originalState; + BusesLayout bestSupported = currentState; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir > 0); + + Array& currentLayouts = (isInput ? currentState.inputBuses : currentState.outputBuses); + const Array& bestLayouts = (isInput ? bestSupported.inputBuses : bestSupported.outputBuses); + const Array& requestedLayouts = (isInput ? desiredLayout.inputBuses : desiredLayout.outputBuses); + const Array& originalLayouts = (isInput ? originalState.inputBuses : originalState.outputBuses); + + for (int busIdx = 0; busIdx < requestedLayouts.size(); ++busIdx) + { + AudioChannelSet& best = bestLayouts .getReference (busIdx); + const AudioChannelSet& requested = requestedLayouts.getReference (busIdx); + const AudioChannelSet& original = originalLayouts .getReference (busIdx); + + // do we need to do anything + if (original == requested) + continue; + + currentState = bestSupported; + AudioChannelSet& current = currentLayouts .getReference (busIdx); + + // already supported? + current = requested; + if (checkBusesLayoutSupported (currentState)) + { + bestSupported = currentState; + continue; + } + + // try setting the opposite bus to the identical layout + const bool oppositeDirection = ! isInput; + if (getBusCount (oppositeDirection) > busIdx) + { + AudioChannelSet& oppositeLayout = (oppositeDirection ? currentState.inputBuses : currentState.outputBuses).getReference (busIdx); + oppositeLayout = requested; + + if (checkBusesLayoutSupported (currentState)) + { + bestSupported = currentState; + continue; + } + + // try setting the default layout + oppositeLayout = getBus (oppositeDirection, busIdx)->getDefaultLayout(); + if (checkBusesLayoutSupported (currentState)) + { + bestSupported = currentState; + continue; + } + } + + // try setting all other buses to the identical layout + BusesLayout allTheSame; + for (int oDir = 0; oDir < 2; ++oDir) + { + const bool oIsInput = (oDir == 0); + const int oBusNum = getBusCount (oIsInput); + + for (int oBusIdx = 0; oBusIdx < oBusNum; ++oBusIdx) + (oIsInput ? allTheSame.inputBuses : allTheSame.outputBuses).add (requested); + } + + if (checkBusesLayoutSupported (allTheSame)) + { + bestSupported = allTheSame; + continue; + } + + // what is closer the default or the current layout? + int distance = abs (best.size() - requested.size()); + const AudioChannelSet& defaultLayout = getBus (isInput, busIdx)->getDefaultLayout(); + + if (abs (defaultLayout.size() - requested.size()) < distance) + { + current = defaultLayout; + if (checkBusesLayoutSupported (currentState)) + bestSupported = currentState; + } + } + } + + actualLayouts = bestSupported; +} + +//============================================================================== +void AudioProcessor::setPlayHead (AudioPlayHead* const newPlayHead) +{ + playHead = newPlayHead; +} + +void AudioProcessor::addListener (AudioProcessorListener* const newListener) +{ + const ScopedLock sl (listenerLock); + listeners.addIfNotAlreadyThere (newListener); +} + +void AudioProcessor::removeListener (AudioProcessorListener* const listenerToRemove) +{ + const ScopedLock sl (listenerLock); + listeners.removeFirstMatchingValue (listenerToRemove); +} + +void AudioProcessor::setPlayConfigDetails (const int newNumIns, + const int newNumOuts, + const double newSampleRate, + const int newBlockSize) +{ + bool success = true; + + if (getTotalNumInputChannels() != newNumIns) + success &= setChannelLayoutOfBus (true, 0, AudioChannelSet::canonicalChannelSet (newNumIns)); + + if (getTotalNumOutputChannels() != newNumOuts) + success &= setChannelLayoutOfBus (false, 0, AudioChannelSet::canonicalChannelSet (newNumOuts)); + + // if the user is using this method then they do not want any side-buses or aux outputs + success &= disableNonMainBuses(); + jassert (success); + + // the processor may not support this arrangement at all + jassert (success && newNumIns == getTotalNumInputChannels() && newNumOuts == getTotalNumOutputChannels()); + + setRateAndBufferSizeDetails (newSampleRate, newBlockSize); +} + +void AudioProcessor::setRateAndBufferSizeDetails (double newSampleRate, int newBlockSize) noexcept +{ + currentSampleRate = newSampleRate; + blockSize = newBlockSize; +} + +//============================================================================== +static int countTotalChannels (const OwnedArray& buses) noexcept +{ + int n = 0; + + for (int i = 0; i < buses.size(); ++i) + n += buses[i]->getNumberOfChannels(); + + return n; +} + +void AudioProcessor::numChannelsChanged() {} +void AudioProcessor::numBusesChanged() {} +void AudioProcessor::processorLayoutsChanged() {} + +int AudioProcessor::getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept +{ + const OwnedArray& ioBus = isInput ? inputBuses : outputBuses; + jassert (isPositiveAndBelow(busIndex, ioBus.size())); + + for (int i = 0; i < ioBus.size() && i < busIndex; ++i) + channelIndex += getChannelCountOfBus (isInput, i); + + return channelIndex; +} + +int AudioProcessor::getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept +{ + const int n = getBusCount (isInput); + int numChannels = 0; + + for (busIdx = 0; busIdx < n && absoluteChannelIndex >= (numChannels = getChannelLayoutOfBus (isInput, busIdx).size()); ++busIdx) + absoluteChannelIndex -= numChannels; + + return busIdx >= n ? -1 : absoluteChannelIndex; +} + +//============================================================================== +void AudioProcessor::setNonRealtime (const bool newNonRealtime) noexcept +{ + nonRealtime = newNonRealtime; +} + +void AudioProcessor::setLatencySamples (const int newLatency) +{ + if (latencySamples != newLatency) + { + latencySamples = newLatency; + updateHostDisplay(); + } +} + +void AudioProcessor::setParameterNotifyingHost (const int parameterIndex, + const float newValue) +{ + setParameter (parameterIndex, newValue); + sendParamChangeMessageToListeners (parameterIndex, newValue); +} + +AudioProcessorListener* AudioProcessor::getListenerLocked (const int index) const noexcept +{ + const ScopedLock sl (listenerLock); + return listeners [index]; +} + +void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex, const float newValue) +{ + if (isPositiveAndBelow (parameterIndex, getNumParameters())) + { + for (int i = listeners.size(); --i >= 0;) + if (auto* l = getListenerLocked (i)) + l->audioProcessorParameterChanged (this, parameterIndex, newValue); + } + else + { + jassertfalse; // called with an out-of-range parameter index! + } +} + +void AudioProcessor::beginParameterChangeGesture (int parameterIndex) +{ + if (isPositiveAndBelow (parameterIndex, getNumParameters())) + { + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This means you've called beginParameterChangeGesture twice in succession without a matching + // call to endParameterChangeGesture. That might be fine in most hosts, but better to avoid doing it. + jassert (! changingParams [parameterIndex]); + changingParams.setBit (parameterIndex); + #endif + + for (int i = listeners.size(); --i >= 0;) + if (auto* l = getListenerLocked (i)) + l->audioProcessorParameterChangeGestureBegin (this, parameterIndex); + } + else + { + jassertfalse; // called with an out-of-range parameter index! + } +} + +void AudioProcessor::endParameterChangeGesture (int parameterIndex) +{ + if (isPositiveAndBelow (parameterIndex, getNumParameters())) + { + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This means you've called endParameterChangeGesture without having previously called + // beginParameterChangeGesture. That might be fine in most hosts, but better to keep the + // calls matched correctly. + jassert (changingParams [parameterIndex]); + changingParams.clearBit (parameterIndex); + #endif + + for (int i = listeners.size(); --i >= 0;) + if (AudioProcessorListener* l = getListenerLocked (i)) + l->audioProcessorParameterChangeGestureEnd (this, parameterIndex); + } + else + { + jassertfalse; // called with an out-of-range parameter index! + } +} + +void AudioProcessor::updateHostDisplay() +{ + for (int i = listeners.size(); --i >= 0;) + if (AudioProcessorListener* l = getListenerLocked (i)) + l->audioProcessorChanged (this); +} + +const OwnedArray& AudioProcessor::getParameters() const noexcept +{ + return managedParameters; +} + +int AudioProcessor::getNumParameters() +{ + return managedParameters.size(); +} + +float AudioProcessor::getParameter (int index) +{ + if (auto* p = getParamChecked (index)) + return p->getValue(); + + return 0; +} + +void AudioProcessor::setParameter (int index, float newValue) +{ + if (auto* p = getParamChecked (index)) + p->setValue (newValue); +} + +float AudioProcessor::getParameterDefaultValue (int index) +{ + if (auto* p = managedParameters[index]) + return p->getDefaultValue(); + + return 0; +} + +const String AudioProcessor::getParameterName (int index) +{ + if (auto* p = getParamChecked (index)) + return p->getName (512); + + return {}; +} + +String AudioProcessor::getParameterID (int index) +{ + // Don't use getParamChecked here, as this must also work for legacy plug-ins + if (auto* p = dynamic_cast (managedParameters[index])) + return p->paramID; + + return String (index); +} + +String AudioProcessor::getParameterName (int index, int maximumStringLength) +{ + if (auto* p = managedParameters[index]) + return p->getName (maximumStringLength); + + return getParameterName (index).substring (0, maximumStringLength); +} + +const String AudioProcessor::getParameterText (int index) +{ + #if JUCE_DEBUG + // if you hit this, then you're probably using the old parameter control methods, + // but have forgotten to implement either of the getParameterText() methods. + jassert (! textRecursionCheck); + ScopedValueSetter sv (textRecursionCheck, true, false); + #endif + + return getParameterText (index, 1024); +} + +String AudioProcessor::getParameterText (int index, int maximumStringLength) +{ + if (auto* p = managedParameters[index]) + return p->getText (p->getValue(), maximumStringLength); + + return getParameterText (index).substring (0, maximumStringLength); +} + +int AudioProcessor::getParameterNumSteps (int index) +{ + if (auto* p = managedParameters[index]) + return p->getNumSteps(); + + return AudioProcessor::getDefaultNumParameterSteps(); +} + +int AudioProcessor::getDefaultNumParameterSteps() noexcept +{ + return 0x7fffffff; +} + +bool AudioProcessor::isParameterDiscrete (int index) const +{ + if (auto* p = managedParameters[index]) + return p->isDiscrete(); + + return false; +} + +String AudioProcessor::getParameterLabel (int index) const +{ + if (auto* p = managedParameters[index]) + return p->getLabel(); + + return {}; +} + +bool AudioProcessor::isParameterAutomatable (int index) const +{ + if (auto* p = managedParameters[index]) + return p->isAutomatable(); + + return true; +} + +bool AudioProcessor::isParameterOrientationInverted (int index) const +{ + if (auto* p = managedParameters[index]) + return p->isOrientationInverted(); + + return false; +} + +bool AudioProcessor::isMetaParameter (int index) const +{ + if (auto* p = managedParameters[index]) + return p->isMetaParameter(); + + return false; +} + +AudioProcessorParameter::Category AudioProcessor::getParameterCategory (int index) const +{ + if (auto* p = managedParameters[index]) + return p->getCategory(); + + return AudioProcessorParameter::genericParameter; +} + +AudioProcessorParameter* AudioProcessor::getParamChecked (int index) const noexcept +{ + AudioProcessorParameter* p = managedParameters[index]; + + // If you hit this, then you're either trying to access parameters that are out-of-range, + // or you're not using addParameter and the managed parameter list, but have failed + // to override some essential virtual methods and implement them appropriately. + jassert (p != nullptr); + return p; +} + +void AudioProcessor::addParameter (AudioProcessorParameter* p) +{ + p->processor = this; + p->parameterIndex = managedParameters.size(); + managedParameters.add (p); + + // if you're using parameter objects, then you must not override the + // deprecated getNumParameters() method! + jassert (getNumParameters() == AudioProcessor::getNumParameters()); + + // check that no two parameters have the same id + #ifdef JUCE_DEBUG + auto paramId = getParameterID (p->parameterIndex); + + for (auto q : managedParameters) + { + jassert (q == nullptr || q == p || paramId != getParameterID (q->parameterIndex)); + } + #endif +} + +void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) +{ + const ScopedLock sl (callbackLock); + suspended = shouldBeSuspended; +} + +void AudioProcessor::reset() {} + +template +void AudioProcessor::processBypassed (AudioBuffer& buffer, MidiBuffer&) +{ + for (int ch = getMainBusNumInputChannels(); ch < getTotalNumOutputChannels(); ++ch) + buffer.clear (ch, 0, buffer.getNumSamples()); +} + +void AudioProcessor::processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midi) { processBypassed (buffer, midi); } +void AudioProcessor::processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midi) { processBypassed (buffer, midi); } + +void AudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + ignoreUnused (buffer, midiMessages); + + // If you hit this assertion then either the caller called the double + // precision version of processBlock on a processor which does not support it + // (i.e. supportsDoublePrecisionProcessing() returns false), or the implementation + // of the AudioProcessor forgot to override the double precision version of this method + jassertfalse; +} + +bool AudioProcessor::supportsDoublePrecisionProcessing() const +{ + return false; +} + +void AudioProcessor::setProcessingPrecision (ProcessingPrecision precision) noexcept +{ + // If you hit this assertion then you're trying to use double precision + // processing on a processor which does not support it! + jassert (precision != doublePrecision || supportsDoublePrecisionProcessing()); + + processingPrecision = precision; +} + +//============================================================================== +static String getChannelName (const OwnedArray& buses, int index) +{ + return buses.size() > 0 ? AudioChannelSet::getChannelTypeName (buses[0]->getCurrentLayout().getTypeOfChannel (index)) : String(); +} + +const String AudioProcessor::getInputChannelName (int index) const { return getChannelName (inputBuses, index); } +const String AudioProcessor::getOutputChannelName (int index) const { return getChannelName (outputBuses, index); } + +static bool isStereoPair (const OwnedArray& buses, int index) +{ + return index < 2 + && buses.size() > 0 + && buses[0]->getCurrentLayout() == AudioChannelSet::stereo(); +} + +bool AudioProcessor::isInputChannelStereoPair (int index) const { return isStereoPair (inputBuses, index); } +bool AudioProcessor::isOutputChannelStereoPair (int index) const { return isStereoPair (outputBuses, index); } + +//============================================================================== +void AudioProcessor::createBus (bool inputBus, const BusProperties& ioConfig) +{ + (inputBus ? inputBuses : outputBuses).add (new Bus (*this, ioConfig.busName, ioConfig.defaultLayout, ioConfig.isActivatedByDefault)); + + audioIOChanged (true, ioConfig.isActivatedByDefault); +} + +//============================================================================== +AudioProcessor::BusesProperties AudioProcessor::busesPropertiesFromLayoutArray (const Array& config) +{ + BusesProperties ioProps; + + if (config[0].inChannels > 0) + ioProps.addBus (true, String ("Input"), AudioChannelSet::canonicalChannelSet (config[0].inChannels)); + + if (config[0].outChannels > 0) + ioProps.addBus (false, String ("Output"), AudioChannelSet::canonicalChannelSet (config[0].outChannels)); + + return ioProps; +} + +AudioProcessor::BusesLayout AudioProcessor::getNextBestLayoutInList (const BusesLayout& layouts, + const Array& legacyLayouts) const +{ + const int numChannelConfigs = legacyLayouts.size(); + jassert (numChannelConfigs > 0); + + bool hasInputs = false, hasOutputs = false; + + for (int i = 0; i < numChannelConfigs; ++i) + { + if (legacyLayouts[i].inChannels > 0) + { + hasInputs = true; + break; + } + } + + for (int i = 0; i < numChannelConfigs; ++i) + { + if (legacyLayouts[i].outChannels > 0) + { + hasOutputs = true; + break; + } + } + + BusesLayout nearest = layouts; + nearest.inputBuses .resize (hasInputs ? 1 : 0); + nearest.outputBuses.resize (hasOutputs ? 1 : 0); + + AudioChannelSet* inBus = (hasInputs ? &nearest.inputBuses. getReference (0) : nullptr); + AudioChannelSet* outBus = (hasOutputs ? &nearest.outputBuses.getReference (0) : nullptr); + + const int16 inNumChannelsRequested = static_cast (inBus != nullptr ? inBus->size() : 0); + const int16 outNumChannelsRequested = static_cast (outBus != nullptr ? outBus->size() : 0); + + int32 distance = std::numeric_limits::max(); + int bestConfiguration = 0; + + for (int i = 0; i < numChannelConfigs; ++i) + { + const int16 inChannels = legacyLayouts.getReference (i).inChannels; + const int16 outChannels = legacyLayouts.getReference (i).outChannels; + + const int32 channelDifference = ((std::abs (inChannels - inNumChannelsRequested) & 0xffff) << 16) | + ((std::abs (outChannels - outNumChannelsRequested) & 0xffff) << 0); + + if (channelDifference < distance) + { + distance = channelDifference; + bestConfiguration = i; + + // we can exit if we found a perfect match + if (distance == 0) return nearest; + } + } + + const int16 inChannels = legacyLayouts.getReference (bestConfiguration).inChannels; + const int16 outChannels = legacyLayouts.getReference (bestConfiguration).outChannels; + + BusesLayout currentState = getBusesLayout(); + AudioChannelSet currentInLayout = (getBusCount (true) > 0 ? currentState.inputBuses .getReference(0) : AudioChannelSet()); + AudioChannelSet currentOutLayout = (getBusCount (false) > 0 ? currentState.outputBuses.getReference(0) : AudioChannelSet()); + + + if (inBus != nullptr) + { + if (inChannels == 0) *inBus = AudioChannelSet::disabled(); + else if (inChannels == currentInLayout. size()) *inBus = currentInLayout; + else if (inChannels == currentOutLayout.size()) *inBus = currentOutLayout; + else *inBus = AudioChannelSet::canonicalChannelSet (inChannels); + } + + if (outBus != nullptr) + { + if (outChannels == 0) *outBus = AudioChannelSet::disabled(); + else if (outChannels == currentOutLayout.size()) *outBus = currentOutLayout; + else if (outChannels == currentInLayout .size()) *outBus = currentInLayout; + else *outBus = AudioChannelSet::canonicalChannelSet (outChannels); + } + + return nearest; +} + +bool AudioProcessor::containsLayout (const BusesLayout& layouts, const Array& channelLayouts) +{ + if (layouts.inputBuses.size() > 1 || layouts.outputBuses.size() > 1) + return false; + + const InOutChannelPair mainLayout (static_cast (layouts.getNumChannels (true, 0)), + static_cast (layouts.getNumChannels (false, 0))); + + return channelLayouts.contains (mainLayout); +} + +//============================================================================== +bool AudioProcessor::disableNonMainBuses() +{ + BusesLayout layouts = getBusesLayout(); + + for (int busIdx = 1; busIdx < layouts.inputBuses.size(); ++busIdx) + layouts.inputBuses.getReference (busIdx) = AudioChannelSet::disabled(); + + for (int busIdx = 1; busIdx < layouts.outputBuses.size(); ++busIdx) + layouts.outputBuses.getReference (busIdx) = AudioChannelSet::disabled(); + + return setBusesLayout (layouts); +} + +// Unfortunately the deprecated getInputSpeakerArrangement/getOutputSpeakerArrangement return +// references to strings. Therefore we need to keep a copy. Once getInputSpeakerArrangement is +// removed, we can also remove this function +void AudioProcessor::updateSpeakerFormatStrings() +{ + cachedInputSpeakerArrString.clear(); + cachedOutputSpeakerArrString.clear(); + + if (getBusCount (true) > 0) + cachedInputSpeakerArrString = getBus (true, 0)->getCurrentLayout().getSpeakerArrangementAsString(); + + if (getBusCount (false) > 0) + cachedOutputSpeakerArrString = getBus (false, 0)->getCurrentLayout().getSpeakerArrangementAsString(); +} + +bool AudioProcessor::applyBusLayouts (const BusesLayout& layouts) +{ + if (layouts == getBusesLayout()) + return true; + + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + const int oldNumberOfIns = getTotalNumInputChannels(); + const int oldNumberOfOuts = getTotalNumOutputChannels(); + + if (layouts.inputBuses. size() != numInputBuses + || layouts.outputBuses.size() != numOutputBuses) + return false; + + int newNumberOfIns = 0, newNumberOfOuts = 0; + + for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) + { + Bus& bus = *getBus (true, busIdx); + const AudioChannelSet& set = layouts.getChannelSet (true, busIdx); + + bus.layout = set; + if (! set.isDisabled()) + bus.lastLayout = set; + + newNumberOfIns += set.size(); + } + + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + { + Bus& bus = *getBus (false, busIdx); + const AudioChannelSet& set = layouts.getChannelSet (false, busIdx); + + bus.layout = set; + if (! set.isDisabled()) + bus.lastLayout = set; + + newNumberOfOuts += set.size(); + } + + const bool channelNumChanged = (oldNumberOfIns != newNumberOfIns || oldNumberOfOuts != newNumberOfOuts); + audioIOChanged (false, channelNumChanged); + + return true; +} + +void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChanged) +{ + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = (isInput ? numInputBuses : numOutputBuses); + + for (int i = 0; i < n; ++i) + { + if (Bus* bus = getBus (isInput, i)) + bus->updateChannelCount(); + } + } + + cachedTotalIns = countTotalChannels (inputBuses); + cachedTotalOuts = countTotalChannels (outputBuses); + + updateSpeakerFormatStrings(); + + if (busNumberChanged) + numBusesChanged(); + + if (channelNumChanged) + numChannelsChanged(); + + processorLayoutsChanged(); +} + +#if ! JUCE_AUDIOPROCESSOR_NO_GUI +//============================================================================== +void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept +{ + const ScopedLock sl (callbackLock); + + if (activeEditor == editor) + activeEditor = nullptr; +} + +AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() +{ + if (activeEditor != nullptr) + return activeEditor; + + AudioProcessorEditor* const ed = createEditor(); + + if (ed != nullptr) + { + // you must give your editor comp a size before returning it.. + jassert (ed->getWidth() > 0 && ed->getHeight() > 0); + + const ScopedLock sl (callbackLock); + activeEditor = ed; + } + + // You must make your hasEditor() method return a consistent result! + jassert (hasEditor() == (ed != nullptr)); + + return ed; +} +#endif + +//============================================================================== +void AudioProcessor::getCurrentProgramStateInformation (juce::MemoryBlock& destData) +{ + getStateInformation (destData); +} + +void AudioProcessor::setCurrentProgramStateInformation (const void* data, int sizeInBytes) +{ + setStateInformation (data, sizeInBytes); +} + +//============================================================================== +void AudioProcessor::updateTrackProperties (const AudioProcessor::TrackProperties&) {} + +//============================================================================== +// magic number to identify memory blocks that we've stored as XML +const uint32 magicXmlNumber = 0x21324356; + +void AudioProcessor::copyXmlToBinary (const XmlElement& xml, juce::MemoryBlock& destData) +{ + { + MemoryOutputStream out (destData, false); + out.writeInt (magicXmlNumber); + out.writeInt (0); + xml.writeToStream (out, String(), true, false); + out.writeByte (0); + } + + // go back and write the string length.. + static_cast (destData.getData())[1] + = ByteOrder::swapIfBigEndian ((uint32) destData.getSize() - 9); +} + +XmlElement* AudioProcessor::getXmlFromBinary (const void* data, const int sizeInBytes) +{ + if (sizeInBytes > 8 + && ByteOrder::littleEndianInt (data) == magicXmlNumber) + { + const int stringLength = (int) ByteOrder::littleEndianInt (addBytesToPointer (data, 4)); + + if (stringLength > 0) + return XmlDocument::parse (String::fromUTF8 (static_cast (data) + 8, + jmin ((sizeInBytes - 8), stringLength))); + } + + return nullptr; +} + +bool AudioProcessor::canApplyBusCountChange (bool isInput, bool isAdding, + AudioProcessor::BusProperties& outProperties) +{ + if ( isAdding && ! canAddBus (isInput)) return false; + if (! isAdding && ! canRemoveBus (isInput)) return false; + + const int num = getBusCount (isInput); + + // No way for me to find out the default layout if there are no other busses!! + if (num == 0) return false; + + if (isAdding) + { + outProperties.busName = String (isInput ? "Input #" : "Output #") + String (getBusCount (isInput)); + outProperties.defaultLayout = (num > 0 ? getBus (isInput, num - 1)->getDefaultLayout() : AudioChannelSet()); + outProperties.isActivatedByDefault = true; + } + + return true; +} + +//============================================================================== +AudioProcessor::Bus::Bus (AudioProcessor& processor, const String& busName, + const AudioChannelSet& defaultLayout, bool isDfltEnabled) + : owner (processor), name (busName), + layout (isDfltEnabled ? defaultLayout : AudioChannelSet()), + dfltLayout (defaultLayout), lastLayout (defaultLayout), + enabledByDefault (isDfltEnabled) +{ + // Your default layout cannot be disabled + jassert (! dfltLayout.isDisabled()); +} + +bool AudioProcessor::Bus::isInput() const +{ + return owner.inputBuses.contains (this); +} + +int AudioProcessor::Bus::getBusIndex() const +{ + bool ignore; + int idx; + busDirAndIndex (ignore, idx); + + return idx; +} + +void AudioProcessor::Bus::busDirAndIndex (bool& input, int& idx) const noexcept +{ + idx = owner.inputBuses.indexOf (this); + input = (idx >= 0); + + if (! input) + idx = owner.outputBuses.indexOf (this); +} + +bool AudioProcessor::Bus::setCurrentLayout (const AudioChannelSet& busLayout) +{ + bool isInput; + int idx; + busDirAndIndex (isInput, idx); + + return owner.setChannelLayoutOfBus (isInput, idx, busLayout); +} + +bool AudioProcessor::Bus::setCurrentLayoutWithoutEnabling (const AudioChannelSet& set) +{ + if (! set.isDisabled()) + { + if (isEnabled()) + return setCurrentLayout (set); + + if (isLayoutSupported (set)) + { + lastLayout = set; + return true; + } + + return false; + } + + return isLayoutSupported (set); +} + +bool AudioProcessor::Bus::setNumberOfChannels (int channels) +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + if (owner.setChannelLayoutOfBus (isInputBus, busIdx, AudioChannelSet::canonicalChannelSet (channels))) + return true; + + if (channels == 0) + return false; + + AudioChannelSet namedSet = AudioChannelSet::namedChannelSet (channels); + if (! namedSet.isDisabled() && owner.setChannelLayoutOfBus (isInputBus, busIdx, namedSet)) + return true; + + return owner.setChannelLayoutOfBus (isInputBus, busIdx, AudioChannelSet::discreteChannels (channels)); +} + +bool AudioProcessor::Bus::enable (bool shouldEnable) +{ + if (isEnabled() == shouldEnable) + return true; + + return setCurrentLayout (shouldEnable ? lastLayout : AudioChannelSet::disabled()); +} + +int AudioProcessor::Bus::getMaxSupportedChannels (int limit) const +{ + for (int ch = limit; ch > 0; --ch) + if (isNumberOfChannelsSupported (ch)) + return ch; + + return (isMain() && isLayoutSupported (AudioChannelSet::disabled())) ? 0 : -1; +} + +bool AudioProcessor::Bus::isLayoutSupported (const AudioChannelSet& set, BusesLayout* ioLayout) const +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + // check that supplied ioLayout is actually valid + if (ioLayout != nullptr) + { + bool suppliedCurrentSupported = owner.checkBusesLayoutSupported (*ioLayout); + + if (! suppliedCurrentSupported) + { + *ioLayout = owner.getBusesLayout(); + + // the current layout you supplied is not a valid layout + jassertfalse; + } + } + + BusesLayout currentLayout = (ioLayout != nullptr ? *ioLayout : owner.getBusesLayout()); + const Array& actualBuses = + (isInputBus ? currentLayout.inputBuses : currentLayout.outputBuses); + + if (actualBuses.getReference (busIdx) == set) + return true; + + BusesLayout desiredLayout = currentLayout; + { + Array& desiredBuses = + (isInputBus ? desiredLayout.inputBuses : desiredLayout.outputBuses); + + desiredBuses.getReference (busIdx) = set; + } + + owner.getNextBestLayout (desiredLayout, currentLayout); + + if (ioLayout != nullptr) + *ioLayout = currentLayout; + + // Nearest layout has a different number of buses. JUCE plug-ins MUST + // have fixed number of buses. + jassert (currentLayout.inputBuses. size() == owner.getBusCount (true) + && currentLayout.outputBuses.size() == owner.getBusCount (false)); + + return (actualBuses.getReference (busIdx) == set); +} + +bool AudioProcessor::Bus::isNumberOfChannelsSupported (int channels) const +{ + if (channels == 0) return isLayoutSupported(AudioChannelSet::disabled()); + + const AudioChannelSet set = supportedLayoutWithChannels (channels); + return (! set.isDisabled()) && isLayoutSupported (set); +} + +AudioChannelSet AudioProcessor::Bus::supportedLayoutWithChannels (int channels) const +{ + if (channels == 0) return AudioChannelSet::disabled(); + + { + AudioChannelSet set; + if (! (set = AudioChannelSet::namedChannelSet (channels)).isDisabled() && isLayoutSupported (set)) + return set; + + if (! (set = AudioChannelSet::discreteChannels (channels)).isDisabled() && isLayoutSupported (set)) + return set; + } + + Array sets = AudioChannelSet::channelSetsWithNumberOfChannels (channels); + const int n = sets.size(); + + for (int i = 0; i < n; ++i) + { + const AudioChannelSet set = sets.getReference (i); + + if (isLayoutSupported (set)) + return set; + } + + return AudioChannelSet::disabled(); +} + +AudioProcessor::BusesLayout AudioProcessor::Bus::getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + BusesLayout layouts = owner.getBusesLayout(); + isLayoutSupported (set, &layouts); + + return layouts; +} + +int AudioProcessor::Bus::getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + return owner.getChannelIndexInProcessBlockBuffer (isInputBus, busIdx, channelIndex); +} + +void AudioProcessor::Bus::updateChannelCount() noexcept +{ + cachedChannelCount = layout.size(); +} + +//============================================================================== +void AudioProcessor::BusesProperties::addBus (bool isInput, const String& name, + const AudioChannelSet& dfltLayout, bool isActivatedByDefault) +{ + jassert (dfltLayout.size() != 0); + + BusProperties props; + + props.busName = name; + props.defaultLayout = dfltLayout; + props.isActivatedByDefault = isActivatedByDefault; + + (isInput ? inputLayouts : outputLayouts).add (props); +} + +AudioProcessor::BusesProperties AudioProcessor::BusesProperties::withInput (const String& name, + const AudioChannelSet& dfltLayout, + bool isActivatedByDefault) const +{ + BusesProperties retval (*this); + retval.addBus (true, name, dfltLayout, isActivatedByDefault); + + return retval; +} + +AudioProcessor::BusesProperties AudioProcessor::BusesProperties::withOutput (const String& name, + const AudioChannelSet& dfltLayout, + bool isActivatedByDefault) const +{ + BusesProperties retval (*this); + retval.addBus (false, name, dfltLayout, isActivatedByDefault); + + return retval; +} + +//============================================================================== +int32 AudioProcessor::getAAXPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, + const AudioChannelSet& mainOutputLayout, + const bool idForAudioSuite) const +{ + int uniqueFormatId = 0; + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioChannelSet& set = (isInput ? mainInputLayout : mainOutputLayout); + int aaxFormatIndex = 0; + + if (set == AudioChannelSet::disabled()) aaxFormatIndex = 0; + else if (set == AudioChannelSet::mono()) aaxFormatIndex = 1; + else if (set == AudioChannelSet::stereo()) aaxFormatIndex = 2; + else if (set == AudioChannelSet::createLCR()) aaxFormatIndex = 3; + else if (set == AudioChannelSet::createLCRS()) aaxFormatIndex = 4; + else if (set == AudioChannelSet::quadraphonic()) aaxFormatIndex = 5; + else if (set == AudioChannelSet::create5point0()) aaxFormatIndex = 6; + else if (set == AudioChannelSet::create5point1()) aaxFormatIndex = 7; + else if (set == AudioChannelSet::create6point0()) aaxFormatIndex = 8; + else if (set == AudioChannelSet::create6point1()) aaxFormatIndex = 9; + else if (set == AudioChannelSet::create7point0()) aaxFormatIndex = 10; + else if (set == AudioChannelSet::create7point1()) aaxFormatIndex = 11; + else if (set == AudioChannelSet::create7point0SDDS()) aaxFormatIndex = 12; + else if (set == AudioChannelSet::create7point1SDDS()) aaxFormatIndex = 13; + else if (set == AudioChannelSet::create7point0point2()) aaxFormatIndex = 14; + else if (set == AudioChannelSet::create7point1point2()) aaxFormatIndex = 15; + else + { + // AAX does not support this format and the wrapper should not have + // called this method with this layout + jassertfalse; + } + + uniqueFormatId = (uniqueFormatId << 8) | aaxFormatIndex; + } + + return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; +} + + +//============================================================================== +void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} +void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} + +//============================================================================== +AudioProcessorParameter::AudioProcessorParameter() noexcept + : processor (nullptr), parameterIndex (-1) +{} + +AudioProcessorParameter::~AudioProcessorParameter() {} + +void AudioProcessorParameter::setValueNotifyingHost (float newValue) +{ + // This method can't be used until the parameter has been attached to a processor! + jassert (processor != nullptr && parameterIndex >= 0); + + return processor->setParameterNotifyingHost (parameterIndex, newValue); +} + +void AudioProcessorParameter::beginChangeGesture() +{ + // This method can't be used until the parameter has been attached to a processor! + jassert (processor != nullptr && parameterIndex >= 0); + + processor->beginParameterChangeGesture (parameterIndex); +} + +void AudioProcessorParameter::endChangeGesture() +{ + // This method can't be used until the parameter has been attached to a processor! + jassert (processor != nullptr && parameterIndex >= 0); + + processor->endParameterChangeGesture (parameterIndex); +} + +bool AudioProcessorParameter::isOrientationInverted() const { return false; } +bool AudioProcessorParameter::isAutomatable() const { return true; } +bool AudioProcessorParameter::isMetaParameter() const { return false; } +AudioProcessorParameter::Category AudioProcessorParameter::getCategory() const { return genericParameter; } +int AudioProcessorParameter::getNumSteps() const { return AudioProcessor::getDefaultNumParameterSteps(); } +bool AudioProcessorParameter::isDiscrete() const { return false; } + +String AudioProcessorParameter::getText (float value, int /*maximumStringLength*/) const +{ + return String (value, 2); +} + +//============================================================================== +bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const noexcept +{ + return timeInSamples == other.timeInSamples + && ppqPosition == other.ppqPosition + && editOriginTime == other.editOriginTime + && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart + && frameRate == other.frameRate + && isPlaying == other.isPlaying + && isRecording == other.isRecording + && bpm == other.bpm + && timeSigNumerator == other.timeSigNumerator + && timeSigDenominator == other.timeSigDenominator + && ppqLoopStart == other.ppqLoopStart + && ppqLoopEnd == other.ppqLoopEnd + && isLooping == other.isLooping; +} + +bool AudioPlayHead::CurrentPositionInfo::operator!= (const CurrentPositionInfo& other) const noexcept +{ + return ! operator== (other); +} + +void AudioPlayHead::CurrentPositionInfo::resetToDefault() +{ + zerostruct (*this); + timeSigNumerator = 4; + timeSigDenominator = 4; + bpm = 120; +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h new file mode 100644 index 000000000..3a9bdd1fe --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -0,0 +1,1641 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +struct PluginBusUtilities; + +//============================================================================== +/** + Base class for audio processing filters or plugins. + + This is intended to act as a base class of audio filter that is general enough to + be wrapped as a VST, AU, RTAS, etc, or used internally. + + It is also used by the plugin hosting code as the wrapper around an instance + of a loaded plugin. + + Derive your filter class from this base class, and if you're building a plugin, + you should implement a global function called createPluginFilter() which creates + and returns a new instance of your subclass. +*/ +class JUCE_API AudioProcessor +{ +protected: + struct BusesProperties; + + //============================================================================== + /** Constructor. + + This constructor will create a main input and output bus which are diabled + by default. If you need more fine grain control then use the other + constructors. + */ + AudioProcessor(); + + /** Constructor for multibus AudioProcessors + + If your AudioProcessor supports multiple buses than use this constructor + to initialise the bus layouts and bus names of your plug-in. + */ + AudioProcessor (const BusesProperties& ioLayouts); + + /** Constructor for AudioProcessors which use layout maps + + If your AudioProcessor uses layout maps then use this constructor. + */ + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + AudioProcessor (const std::initializer_list& channelLayoutList) + { + initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); + } + #else + template + AudioProcessor (const short (&channelLayoutList) [numLayouts][2]) + { + initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); + } + #endif + +public: + //============================================================================== + enum ProcessingPrecision + { + singlePrecision, + doublePrecision + }; + + //============================================================================== + /** Destructor. */ + virtual ~AudioProcessor(); + + //============================================================================== + /** Returns the name of this processor. */ + virtual const String getName() const = 0; + + /** Returns a list of alternative names to use for this processor. + + Some hosts truncate the name of your AudioProcessor when there isn't enough + space in the GUI to show the full name. Overriding this method, allows the host + to choose an alternative name (such as an abbreviation) to better fit the + available space. + */ + virtual StringArray getAlternateDisplayNames() const; + + //============================================================================== + /** Called before playback starts, to let the filter prepare itself. + + The sample rate is the target sample rate, and will remain constant until + playback stops. + + You can call getTotalNumInputChannels and getTotalNumOutputChannels + or query the busLayout member variable to find out the number of + channels your processBlock callback must process. + + The maximumExpectedSamplesPerBlock value is a strong hint about the maximum + number of samples that will be provided in each block. You may want to use + this value to resize internal buffers. You should program defensively in + case a buggy host exceeds this value. The actual block sizes that the host + uses may be different each time the callback happens: completely variable + block sizes can be expected from some hosts. + + @see busLayout, getTotalNumInputChannels, getTotalNumOutputChannels + */ + virtual void prepareToPlay (double sampleRate, + int maximumExpectedSamplesPerBlock) = 0; + + /** Called after playback has stopped, to let the filter free up any resources it + no longer needs. + */ + virtual void releaseResources() = 0; + + /** Renders the next block. + + When this method is called, the buffer contains a number of channels which is + at least as great as the maximum number of input and output channels that + this filter is using. It will be filled with the filter's input data and + should be replaced with the filter's output. + + So for example if your filter has a total of 2 input channels and 4 output + channels, then the buffer will contain 4 channels, the first two being filled + with the input data. Your filter should read these, do its processing, and + replace the contents of all 4 channels with its output. + + Or if your filter has a total of 5 inputs and 2 outputs, the buffer will have 5 + channels, all filled with data, and your filter should overwrite the first 2 of + these with its output. But be VERY careful not to write anything to the last 3 + channels, as these might be mapped to memory that the host assumes is read-only! + + If your plug-in has more than one input or output buses then the buffer passed + to the processBlock methods will contain a bundle of all channels of each bus. + Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a + particular bus. + + Note that if you have more outputs than inputs, then only those channels that + correspond to an input channel are guaranteed to contain sensible data - e.g. + in the case of 2 inputs and 4 outputs, the first two channels contain the input, + but the last two channels may contain garbage, so you should be careful not to + let this pass through without being overwritten or cleared. + + Also note that the buffer may have more channels than are strictly necessary, + but you should only read/write from the ones that your filter is supposed to + be using. + + The number of samples in these buffers is NOT guaranteed to be the same for every + callback, and may be more or less than the estimated value given to prepareToPlay(). + Your code must be able to cope with variable-sized blocks, or you're going to get + clicks and crashes! + + Also note that some hosts will occasionally decide to pass a buffer containing + zero samples, so make sure that your algorithm can deal with that! + + If the filter is receiving a midi input, then the midiMessages array will be filled + with the midi messages for this block. Each message's timestamp will indicate the + message's time, as a number of samples from the start of the block. + + Any messages left in the midi buffer when this method has finished are assumed to + be the filter's midi output. This means that your filter should be careful to + clear any incoming messages from the array if it doesn't want them to be passed-on. + + Be very careful about what you do in this callback - it's going to be called by + the audio thread, so any kind of interaction with the UI is absolutely + out of the question. If you change a parameter in here and need to tell your UI to + update itself, the best way is probably to inherit from a ChangeBroadcaster, let + the UI components register as listeners, and then call sendChangeMessage() inside the + processBlock() method to send out an asynchronous message. You could also use + the AsyncUpdater class in a similar way. + + @see AudiobusLayout::getBusBuffer + */ + + virtual void processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) = 0; + + /** Renders the next block. + + When this method is called, the buffer contains a number of channels which is + at least as great as the maximum number of input and output channels that + this filter is using. It will be filled with the filter's input data and + should be replaced with the filter's output. + + So for example if your filter has a combined total of 2 input channels and + 4 output channels, then the buffer will contain 4 channels, the first two + being filled with the input data. Your filter should read these, do its + processing, and replace the contents of all 4 channels with its output. + + Or if your filter has 5 inputs and 2 outputs, the buffer will have 5 channels, + all filled with data, and your filter should overwrite the first 2 of these + with its output. But be VERY careful not to write anything to the last 3 + channels, as these might be mapped to memory that the host assumes is read-only! + + If your plug-in has more than one input or output buses then the buffer passed + to the processBlock methods will contain a bundle of all channels of + each bus. Use AudiobusLayout::getBusBuffer to obtain a audio buffer + for a particular bus. + + Note that if you have more outputs than inputs, then only those channels that + correspond to an input channel are guaranteed to contain sensible data - e.g. + in the case of 2 inputs and 4 outputs, the first two channels contain the input, + but the last two channels may contain garbage, so you should be careful not to + let this pass through without being overwritten or cleared. + + Also note that the buffer may have more channels than are strictly necessary, + but you should only read/write from the ones that your filter is supposed to + be using. + + If your plugin uses buses, then you should use AudiobusLayout::getBusBuffer() + or AudiobusLayout::getChannelIndexInProcessBlockBuffer() to find out which + of the input and output channels correspond to which of the buses. + + The number of samples in these buffers is NOT guaranteed to be the same for every + callback, and may be more or less than the estimated value given to prepareToPlay(). + Your code must be able to cope with variable-sized blocks, or you're going to get + clicks and crashes! + + Also note that some hosts will occasionally decide to pass a buffer containing + zero samples, so make sure that your algorithm can deal with that! + + If the filter is receiving a midi input, then the midiMessages array will be filled + with the midi messages for this block. Each message's timestamp will indicate the + message's time, as a number of samples from the start of the block. + + Any messages left in the midi buffer when this method has finished are assumed to + be the filter's midi output. This means that your filter should be careful to + clear any incoming messages from the array if it doesn't want them to be passed-on. + + Be very careful about what you do in this callback - it's going to be called by + the audio thread, so any kind of interaction with the UI is absolutely + out of the question. If you change a parameter in here and need to tell your UI to + update itself, the best way is probably to inherit from a ChangeBroadcaster, let + the UI components register as listeners, and then call sendChangeMessage() inside the + processBlock() method to send out an asynchronous message. You could also use + the AsyncUpdater class in a similar way. + + @see AudiobusLayout::getBusBuffer + */ + virtual void processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages); + + /** Renders the next block when the processor is being bypassed. + + The default implementation of this method will pass-through any incoming audio, but + you may override this method e.g. to add latency compensation to the data to match + the processor's latency characteristics. This will avoid situations where bypassing + will shift the signal forward in time, possibly creating pre-echo effects and odd timings. + Another use for this method would be to cross-fade or morph between the wet (not bypassed) + and dry (bypassed) signals. + */ + virtual void processBlockBypassed (AudioBuffer& buffer, + MidiBuffer& midiMessages); + + /** Renders the next block when the processor is being bypassed. + + The default implementation of this method will pass-through any incoming audio, but + you may override this method e.g. to add latency compensation to the data to match + the processor's latency characteristics. This will avoid situations where bypassing + will shift the signal forward in time, possibly creating pre-echo effects and odd timings. + Another use for this method would be to cross-fade or morph between the wet (not bypassed) + and dry (bypassed) signals. + */ + virtual void processBlockBypassed (AudioBuffer& buffer, + MidiBuffer& midiMessages); + + + //============================================================================== + /** + Represents the bus layout state of a plug-in + */ + struct BusesLayout + { + /** An array containing the list of input buses that this processor supports. */ + Array inputBuses; + + /** An array containing the list of output buses that this processor supports. */ + Array outputBuses; + + /** Get the number of channels of a particular bus */ + int getNumChannels (bool isInput, int busIndex) const noexcept + { + const Array& bus = (isInput ? inputBuses : outputBuses); + return isPositiveAndBelow (busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; + } + + /** Get the channel set of a particular bus */ + AudioChannelSet& getChannelSet (bool isInput, int busIndex) + { + return (isInput ? inputBuses : outputBuses).getReference (busIndex); + } + + /** Get the channel set of a particular bus */ + AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept + { + return (isInput ? inputBuses : outputBuses) [busIndex]; + } + + /** Get the input channel layout on the main bus. */ + AudioChannelSet getMainInputChannelSet() const noexcept { return getChannelSet (true, 0); } + + /** Get the output channel layout on the main bus. */ + AudioChannelSet getMainOutputChannelSet() const noexcept { return getChannelSet (false, 0); } + + /** Get the number of input channels on the main bus. */ + int getMainInputChannels() const noexcept { return getNumChannels (true, 0); } + + /** Get the number of output channels on the main bus. */ + int getMainOutputChannels() const noexcept { return getNumChannels (false, 0); } + + bool operator== (const BusesLayout& other) const noexcept { return inputBuses == other.inputBuses && outputBuses == other.outputBuses; } + bool operator!= (const BusesLayout& other) const noexcept { return inputBuses != other.inputBuses || outputBuses != other.outputBuses; } + }; + + //============================================================================== + /** + Describes the layout and properties of an audio bus. + Effectively a bus description is a named set of channel types. + + @see AudioChannelSet, AudioProcessor::addBus + */ + class Bus + { + public: + /** Returns true if this bus is an input bus. */ + bool isInput() const; + + /** Returns the index of this bus. */ + int getBusIndex() const; + + /** Returns true if the current bus is the main input or output bus. */ + bool isMain() const { return getBusIndex() == 0; } + + //============================================================================== + /** The bus's name. */ + const String &getName() const noexcept { return name; } + + /** Get the default layout of this bus. + @see AudioChannelSet + */ + const AudioChannelSet& getDefaultLayout() const noexcept { return dfltLayout; } + + //============================================================================== + /** The bus's current layout. This will be AudioChannelSet::disabled() if the current + layout is dfisabled. + + @see AudioChannelSet + */ + const AudioChannelSet& getCurrentLayout() const noexcept { return layout; } + + /** Return the bus's last active channel layout. + If the bus is currently enabled then the result will be identical to getCurrentLayout + otherwise it will return the last enabled layout. + + @see AudioChannelSet + */ + const AudioChannelSet& getLastEnabledLayout() const noexcept { return lastLayout; } + + /** Sets the bus's current layout. + If the AudioProcessor does not support this layout then this will return false. + @see AudioChannelSet + */ + bool setCurrentLayout (const AudioChannelSet& layout); + + /** Sets the bus's current layout without changing the enabled state. + + If the AudioProcessor does not support this layout then this will return false. + + @see AudioChannelSet + */ + bool setCurrentLayoutWithoutEnabling (const AudioChannelSet& layout); + + /** Return the number of channels of the current bus. */ + inline int getNumberOfChannels() const noexcept { return cachedChannelCount; } + + /** Set the number of channles of this bus. This will return false if the AudioProcessor + does not support this layout. + */ + bool setNumberOfChannels (int channels); + + //============================================================================== + /** Checks if a particular layout is supported. + + @param set The AudioChannelSet which is to be probed. + @param currentLayout If non-null, pretend that the current layout of the AudioProcessor is + currentLayout. On exit, currentLayout will be modified to + to represent the buses layouts of the AudioProcessor as if the layout + of the reciever had been succesfully changed. This is useful as changing + the layout of the reciever may change the bus layout of other buses. + + @see AudioChannelSet + */ + bool isLayoutSupported (const AudioChannelSet& set, BusesLayout* currentLayout = nullptr) const; + + /** Checks if this bus can support a given number of channels. */ + bool isNumberOfChannelsSupported (int channels) const; + + /** Returns a ChannelSet that the bus supports with a given number of channels. */ + AudioChannelSet supportedLayoutWithChannels (int channels) const; + + /** Returns the maximum number of channels that this bus can support. + @param limit The maximum value to return. + */ + int getMaxSupportedChannels (int limit = AudioChannelSet::maxChannelsOfNamedLayout) const; + + /** Returns the resulting layouts of all buses after changing the layout of this bus. + + Changing an individual layout of a bus may also change the layout of all the other + buses. This method returns what the layouts of all the buses of the audio processor + would be, if you were to change the layout of this bus to the given layout. If there + is no way to support the given layout then this method will return the next best + layout. + */ + BusesLayout getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const; + + //============================================================================== + /** Returns true if the current bus is enabled. */ + bool isEnabled() const noexcept { return ! layout.isDisabled(); } + + /** Enable or disable this bus. This will return false if the AudioProcessor + does not support disabling this bus. */ + bool enable (bool shouldEnable = true); + + /** Returns if this bus is enabled by default. */ + bool isEnabledByDefault() const noexcept { return enabledByDefault; } + + //============================================================================== + /** Returns the position of a bus's channels within the processBlock buffer. + This can be called in processBlock to figure out which channel of the master AudioSampleBuffer + maps onto a specific bus's channel. + */ + int getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept; + + + /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. + This can be called in processBlock to get a buffer containing a sub-group of the master + AudioSampleBuffer which contains all the plugin channels. + */ + template + AudioBuffer getBusBuffer (AudioBuffer& processBlockBuffer) const + { + bool isIn; + int busIdx; + busDirAndIndex (isIn, busIdx); + return owner.getBusBuffer (processBlockBuffer, isIn, busIdx); + } + + private: + friend class AudioProcessor; + Bus (AudioProcessor&, const String&, const AudioChannelSet&, bool); + void busDirAndIndex (bool&, int&) const noexcept; + void updateChannelCount() noexcept; + + AudioProcessor& owner; + String name; + AudioChannelSet layout, dfltLayout, lastLayout; + bool enabledByDefault; + int cachedChannelCount; + + JUCE_DECLARE_NON_COPYABLE (Bus) + }; + + //============================================================================== + /** Returns the number of buses on the input or output side */ + int getBusCount (bool isInput) const noexcept { return (isInput ? inputBuses : outputBuses).size(); } + + /** Returns the audio bus with a given index and direction. + + If busIdx is invalid then this method will return a nullptr. + */ + Bus* getBus (bool isInput, int busIdx) noexcept { return (isInput ? inputBuses : outputBuses)[busIdx]; } + + /** Returns the audio bus with a given index and direction. + + If busIdx is invalid then this method will return a nullptr. + */ + const Bus* getBus (bool isInput, int busIdx) const noexcept { return const_cast (this)->getBus (isInput, busIdx); } + + //============================================================================== + /** Callback to query if a bus can currently be added. + + This callback probes if a bus can currently be added. You should override + this callback if you want to support dynamically adding/removing buses by + the host. This is useful for mixer audio processors. + + The default implementation will always return false. + + @see addBus + */ + virtual bool canAddBus (bool isInput) const { ignoreUnused (isInput); return false; } + + /** Callback to query if the last bus can currently be removed. + + This callback probes if the last bus can currently be removed. You should + override this callback if you want to support dynamically adding/removing + buses by the host. This is useful for mixer audio processors. + + If you return true in this callback then the AudioProcessor will go ahead + and delete the bus. + + The default implementation will always return false. + */ + virtual bool canRemoveBus (bool isInput) const { ignoreUnused (isInput); return false; } + + /** Dynamically request an additional bus. + + Request an additional bus from the audio processor. If the audio processor + does not support adding additional buses then this method will return false. + + Most audio processors will not allow you to dynamically add/remove + audio buses and will return false. + + This method will invoke the canApplyBusCountChange callback to probe + if a bus can be added and, if yes, will use the supplied bus properties + of the canApplyBusCountChange callback to create a new bus. + + @see canApplyBusCountChange, removeBus + */ + bool addBus (bool isInput); + + /** Dynamically remove the latest added bus. + + Request the removal of the last bus from the audio processor. If the + audio processor does not support removing buses then this method will + return false. + + Most audio processors will not allow you to dynamically add/remove + audio buses and will return false. + + The default implementation will return false. + + This method will invoke the canApplyBusCountChange callback to probe if + a bus can currently be removed and, if yes, will go ahead and remove it. + + @see addBus, canRemoveBus + */ + bool removeBus (bool isInput); + + //============================================================================== + /** Set the channel layouts of this audio processor. + + If the layout is not supported by this audio processor then + this method will return false. You can use the checkBusesLayoutSupported + and getNextBestLayout methods to probe which layouts this audio + processor supports. + */ + bool setBusesLayout (const BusesLayout&); + + /** Set the channel layouts of this audio processor without changing the + enablement state of the buses. + + If the layout is not supported by this audio processor then + this method will return false. You can use the checkBusesLayoutSupported + methods to probe which layouts this audio processor supports. + */ + bool setBusesLayoutWithoutEnabling (const BusesLayout&); + + /** Provides the current channel layouts of this audio processor. */ + BusesLayout getBusesLayout() const; + + /** Provides the channel layout of the bus with a given index and direction. + + If the index, direction combination is invalid then this will return an + AudioChannelSet with no channels. + */ + AudioChannelSet getChannelLayoutOfBus (bool isInput, int busIndex) const noexcept; + + /** Set the channel layout of the bus with a given index and direction. + + If the index, direction combination is invalid or the layout is not + supported by the audio processor then this method will return false. + */ + bool setChannelLayoutOfBus (bool isInput, int busIdx, const AudioChannelSet& layout); + + /** Provides the number of channels of the bus with a given index and direction. + + If the index, direction combination is invalid then this will return zero. + */ + inline int getChannelCountOfBus (bool isInput, int busIdx) const noexcept + { + if (const Bus* bus = getBus (isInput, busIdx)) + return bus->getNumberOfChannels(); + + return 0; + } + + /** Enables all buses */ + bool enableAllBuses(); + + /** Disables all non-main buses (aux and sidechains). */ + bool disableNonMainBuses(); + + //============================================================================== + /** Returns the position of a bus's channels within the processBlock buffer. + This can be called in processBlock to figure out which channel of the master AudioSampleBuffer + maps onto a specific bus's channel. + */ + int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; + + /** Returns the offset in a bus's buffer from an absolute channel indes. + + This method returns the offset in a bus's buffer given an absolute channel index. + It also provides the bus index. For example, this method would return one + for a processor with two stereo buses when given the absolute channel index. + */ + int getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept; + + /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. + This can be called in processBlock to get a buffer containing a sub-group of the master + AudioSampleBuffer which contains all the plugin channels. + */ + template + AudioBuffer getBusBuffer (AudioBuffer& processBlockBuffer, bool isInput, int busIndex) const + { + const int busNumChannels = getChannelCountOfBus (isInput, busIndex); + const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); + + return AudioBuffer (processBlockBuffer.getArrayOfWritePointers() + channelOffset, + busNumChannels, processBlockBuffer.getNumSamples()); + } + + //============================================================================== + /** Returns true if the Audio processor is likely to support a given layout. + This can be called regardless if the processor is currently running. + */ + bool checkBusesLayoutSupported (const BusesLayout&) const; + + //============================================================================== + /** Returns true if the Audio processor supports double precision floating point processing. + The default implementation will always return false. + If you return true here then you must override the double precision versions + of processBlock. Additionally, you must call getProcessingPrecision() in + your prepareToPlay method to determine the precision with which you need to + allocate your internal buffers. + @see getProcessingPrecision, setProcessingPrecision + */ + virtual bool supportsDoublePrecisionProcessing() const; + + /** Returns the precision-mode of the processor. + Depending on the result of this method you MUST call the corresponding version + of processBlock. The default processing precision is single precision. + @see setProcessingPrecision, supportsDoublePrecisionProcessing + */ + ProcessingPrecision getProcessingPrecision() const noexcept { return processingPrecision; } + + /** Returns true if the current precision is set to doublePrecision. */ + bool isUsingDoublePrecision() const noexcept { return processingPrecision == doublePrecision; } + + /** Changes the processing precision of the receiver. A client of the AudioProcessor + calls this function to indicate which version of processBlock (single or double + precision) it intends to call. The client MUST call this function before calling + the prepareToPlay method so that the receiver can do any necessary allocations + in the prepareToPlay() method. An implementation of prepareToPlay() should call + getProcessingPrecision() to determine with which precision it should allocate + it's internal buffers. + + Note that setting the processing precision to double floating point precision + on a receiver which does not support double precision processing (i.e. + supportsDoublePrecisionProcessing() returns false) will result in an assertion. + + @see getProcessingPrecision, supportsDoublePrecisionProcessing + */ + void setProcessingPrecision (ProcessingPrecision newPrecision) noexcept; + + //============================================================================== + /** Returns the current AudioPlayHead object that should be used to find + out the state and position of the playhead. + + You can ONLY call this from your processBlock() method! Calling it at other + times will produce undefined behaviour, as the host may not have any context + in which a time would make sense, and some hosts will almost certainly have + multithreading issues if it's not called on the audio thread. + + The AudioPlayHead object that is returned can be used to get the details about + the time of the start of the block currently being processed. But do not + store this pointer or use it outside of the current audio callback, because + the host may delete or re-use it. + + If the host can't or won't provide any time info, this will return nullptr. + */ + AudioPlayHead* getPlayHead() const noexcept { return playHead; } + + //============================================================================== + /** Returns the total number of input channels. + + This method will return the total number of input channels by accumulating + the number of channels on each input bus. The number of channels of the + buffer passed to your processBlock callback will be equivalent to either + getTotalNumInputChannels or getTotalNumOutputChannels - which ever + is greater. + + Note that getTotalNumInputChannels is equivalent to + getMainBusNumInputChannels if your processor does not have any sidechains + or aux buses. + */ + int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } + + /** Returns the total number of output channels. + + This method will return the total number of output channels by accumulating + the number of channels on each output bus. The number of channels of the + buffer passed to your processBlock callback will be equivalent to either + getTotalNumInputChannels or getTotalNumOutputChannels - which ever + is greater. + + Note that getTotalNumOutputChannels is equivalent to + getMainBusNumOutputChannels if your processor does not have any sidechains + or aux buses. + */ + int getTotalNumOutputChannels() const noexcept { return cachedTotalOuts; } + + /** Returns the number of input channels on the main bus. */ + inline int getMainBusNumInputChannels() const noexcept { return getChannelCountOfBus (true, 0); } + + /** Returns the number of output channels on the main bus. */ + inline int getMainBusNumOutputChannels() const noexcept { return getChannelCountOfBus (false, 0); } + + //============================================================================== + /** Returns true if the channel layout map contains a certain layout. + + You can use this method to help you implement the checkBusesLayoutSupported + method. For example + + @code + bool checkBusesLayoutSupported (const BusesLayout& layouts) override + { + return containsLayout (layouts, {{1,1},{2,2}}); + } + @endcode + */ + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + static bool containsLayout (const BusesLayout& layouts, const std::initializer_list& channelLayoutList) + { + return containsLayout (layouts, layoutListToArray (channelLayoutList)); + } + #endif + + template + static bool containsLayout (const BusesLayout& layouts, const short (&channelLayoutList) [numLayouts][2]) + { + return containsLayout (layouts, layoutListToArray (channelLayoutList)); + } + + /** Returns the next best layout which is contained in a channel layout map. + + You can use this mehtod to help you implement getNextBestLayout. For example: + + @code + BusesLayout getNextBestLayout (const BusesLayout& layouts) override + { + return getNextBestLayoutInLayoutList (layouts, {{1,1},{2,2}}); + } + @endcode + */ + template + BusesLayout getNextBestLayoutInLayoutList (const BusesLayout& layouts, + const short (&channelLayoutList) [numLayouts][2]) + { + return getNextBestLayoutInList (layouts, layoutListToArray (channelLayoutList)); + } + + //============================================================================== + /** Returns the current sample rate. + + This can be called from your processBlock() method - it's not guaranteed + to be valid at any other time, and may return 0 if it's unknown. + */ + double getSampleRate() const noexcept { return currentSampleRate; } + + /** Returns the current typical block size that is being used. + + This can be called from your processBlock() method - it's not guaranteed + to be valid at any other time. + + Remember it's not the ONLY block size that may be used when calling + processBlock, it's just the normal one. The actual block sizes used may be + larger or smaller than this, and will vary between successive calls. + */ + int getBlockSize() const noexcept { return blockSize; } + + //============================================================================== + + /** This returns the number of samples delay that the filter imposes on the audio + passing through it. + + The host will call this to find the latency - the filter itself should set this value + by calling setLatencySamples() as soon as it can during its initialisation. + */ + int getLatencySamples() const noexcept { return latencySamples; } + + /** The filter should call this to set the number of samples delay that it introduces. + + The filter should call this as soon as it can during initialisation, and can call it + later if the value changes. + */ + void setLatencySamples (int newLatency); + + /** Returns the length of the filter's tail, in seconds. */ + virtual double getTailLengthSeconds() const = 0; + + /** Returns true if the processor wants midi messages. */ + virtual bool acceptsMidi() const = 0; + + /** Returns true if the processor produces midi messages. */ + virtual bool producesMidi() const = 0; + + /** Returns true if the processor supports MPE. */ + virtual bool supportsMPE() const { return false; } + + /** Returns true if this is a midi effect plug-in and does no audio processing. */ + virtual bool isMidiEffect() const { return false; } + + //============================================================================== + /** This returns a critical section that will automatically be locked while the host + is calling the processBlock() method. + + Use it from your UI or other threads to lock access to variables that are used + by the process callback, but obviously be careful not to keep it locked for + too long, because that could cause stuttering playback. If you need to do something + that'll take a long time and need the processing to stop while it happens, use the + suspendProcessing() method instead. + + @see suspendProcessing + */ + const CriticalSection& getCallbackLock() const noexcept { return callbackLock; } + + /** Enables and disables the processing callback. + + If you need to do something time-consuming on a thread and would like to make sure + the audio processing callback doesn't happen until you've finished, use this + to disable the callback and re-enable it again afterwards. + + E.g. + @code + void loadNewPatch() + { + suspendProcessing (true); + + ..do something that takes ages.. + + suspendProcessing (false); + } + @endcode + + If the host tries to make an audio callback while processing is suspended, the + filter will return an empty buffer, but won't block the audio thread like it would + do if you use the getCallbackLock() critical section to synchronise access. + + Any code that calls processBlock() should call isSuspended() before doing so, and + if the processor is suspended, it should avoid the call and emit silence or + whatever is appropriate. + + @see getCallbackLock + */ + void suspendProcessing (bool shouldBeSuspended); + + /** Returns true if processing is currently suspended. + @see suspendProcessing + */ + bool isSuspended() const noexcept { return suspended; } + + /** A plugin can override this to be told when it should reset any playing voices. + + The default implementation does nothing, but a host may call this to tell the + plugin that it should stop any tails or sounds that have been left running. + */ + virtual void reset(); + + //============================================================================== + /** Returns true if the processor is being run in an offline mode for rendering. + + If the processor is being run live on realtime signals, this returns false. + If the mode is unknown, this will assume it's realtime and return false. + + This value may be unreliable until the prepareToPlay() method has been called, + and could change each time prepareToPlay() is called. + + @see setNonRealtime() + */ + bool isNonRealtime() const noexcept { return nonRealtime; } + + /** Called by the host to tell this processor whether it's being used in a non-realtime + capacity for offline rendering or bouncing. + */ + virtual void setNonRealtime (bool isNonRealtime) noexcept; + + #if ! JUCE_AUDIOPROCESSOR_NO_GUI + //============================================================================== + /** Creates the filter's UI. + + This can return nullptr if you want a UI-less filter, in which case the host may create + a generic UI that lets the user twiddle the parameters directly. + + If you do want to pass back a component, the component should be created and set to + the correct size before returning it. If you implement this method, you must + also implement the hasEditor() method and make it return true. + + Remember not to do anything silly like allowing your filter to keep a pointer to + the component that gets created - it could be deleted later without any warning, which + would make your pointer into a dangler. Use the getActiveEditor() method instead. + + The correct way to handle the connection between an editor component and its + filter is to use something like a ChangeBroadcaster so that the editor can + register itself as a listener, and be told when a change occurs. This lets them + safely unregister themselves when they are deleted. + + Here are a few things to bear in mind when writing an editor: + + - Initially there won't be an editor, until the user opens one, or they might + not open one at all. Your filter mustn't rely on it being there. + - An editor object may be deleted and a replacement one created again at any time. + - It's safe to assume that an editor will be deleted before its filter. + + @see hasEditor + */ + virtual AudioProcessorEditor* createEditor() = 0; + + /** Your filter must override this and return true if it can create an editor component. + @see createEditor + */ + virtual bool hasEditor() const = 0; + + //============================================================================== + /** Returns the active editor, if there is one. + Bear in mind this can return nullptr, even if an editor has previously been opened. + */ + AudioProcessorEditor* getActiveEditor() const noexcept { return activeEditor; } + + /** Returns the active editor, or if there isn't one, it will create one. + This may call createEditor() internally to create the component. + */ + AudioProcessorEditor* createEditorIfNeeded(); + #endif + + //============================================================================== + /** This must return the correct value immediately after the object has been + created, and mustn't change the number of parameters later. + + NOTE! This method will eventually be deprecated! It's recommended that you use the + AudioProcessorParameter class instead to manage your parameters. + */ + virtual int getNumParameters(); + + /** Returns the name of a particular parameter. + + NOTE! This method will eventually be deprecated! It's recommended that you use the + AudioProcessorParameter class instead to manage your parameters. + */ + virtual const String getParameterName (int parameterIndex); + + /** Returns the ID of a particular parameter. + + The ID is used to communicate the value or mapping of a particular parameter with + the host. By default this method will simply return a string representation of + index. + + NOTE! This method will eventually be deprecated! It's recommended that you use the + AudioProcessorParameterWithID class instead to manage your parameters. + */ + virtual String getParameterID (int index); + + /** Called by the host to find out the value of one of the filter's parameters. + + The host will expect the value returned to be between 0 and 1.0. + + This could be called quite frequently, so try to make your code efficient. + It's also likely to be called by non-UI threads, so the code in here should + be thread-aware. + + NOTE! This method will eventually be deprecated! It's recommended that you use the + AudioProcessorParameter class instead to manage your parameters. + */ + virtual float getParameter (int parameterIndex); + + /** Returns the name of a parameter as a text string with a preferred maximum length. + If you want to provide customised short versions of your parameter names that + will look better in constrained spaces (e.g. the displays on hardware controller + devices or mixing desks) then you should implement this method. + If you don't override it, the default implementation will call getParameterName(int), + and truncate the result. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getName() instead. + */ + virtual String getParameterName (int parameterIndex, int maximumStringLength); + + /** Returns the value of a parameter as a text string. + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getText() instead. + */ + virtual const String getParameterText (int parameterIndex); + + /** Returns the value of a parameter as a text string with a preferred maximum length. + If you want to provide customised short versions of your parameter values that + will look better in constrained spaces (e.g. the displays on hardware controller + devices or mixing desks) then you should implement this method. + If you don't override it, the default implementation will call getParameterText(int), + and truncate the result. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getText() instead. + */ + virtual String getParameterText (int parameterIndex, int maximumStringLength); + + /** Returns the number of discrete steps that this parameter can represent. + + The default return value if you don't implement this method is + AudioProcessor::getDefaultNumParameterSteps(). + + If your parameter is boolean, then you may want to make this return 2. + + If you want the host to display stepped automation values, rather than a + continuous interpolation between successive values, you should ensure that + isParameterDiscrete returns true. + + The value that is returned may or may not be used, depending on the host. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getNumSteps() instead. + + @see isParameterDiscrete + */ + virtual int getParameterNumSteps (int parameterIndex); + + /** Returns the default number of steps for a parameter. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getNumSteps() instead. + + @see getParameterNumSteps + */ + static int getDefaultNumParameterSteps() noexcept; + + /** Returns true if the parameter should take discrete, rather than continuous + values. + + If the parameter is boolean, this should return true (with getParameterNumSteps + returning 2). + + The value that is returned may or may not be used, depending on the host. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::isDiscrete() instead. + + @see getParameterNumSteps + */ + virtual bool isParameterDiscrete (int parameterIndex) const; + + /** Returns the default value for the parameter. + By default, this just returns 0. + The value that is returned may or may not be used, depending on the host. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getDefaultValue() instead. + */ + virtual float getParameterDefaultValue (int parameterIndex); + + /** Some plugin types may be able to return a label string for a + parameter's units. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getLabel() instead. + */ + virtual String getParameterLabel (int index) const; + + /** This can be overridden to tell the host that particular parameters operate in the + reverse direction. (Not all plugin formats or hosts will actually use this information). + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::isOrientationInverted() instead. + */ + virtual bool isParameterOrientationInverted (int index) const; + + /** The host will call this method to change the value of one of the filter's parameters. + + The host may call this at any time, including during the audio processing + callback, so the filter has to process this very fast and avoid blocking. + + If you want to set the value of a parameter internally, e.g. from your + editor component, then don't call this directly - instead, use the + setParameterNotifyingHost() method, which will also send a message to + the host telling it about the change. If the message isn't sent, the host + won't be able to automate your parameters properly. + + The value passed will be between 0 and 1.0. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::setValue() instead. + */ + virtual void setParameter (int parameterIndex, float newValue); + + /** Your filter can call this when it needs to change one of its parameters. + + This could happen when the editor or some other internal operation changes + a parameter. This method will call the setParameter() method to change the + value, and will then send a message to the host telling it about the change. + + Note that to make sure the host correctly handles automation, you should call + the beginParameterChangeGesture() and endParameterChangeGesture() methods to + tell the host when the user has started and stopped changing the parameter. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::setValueNotifyingHost() instead. + */ + void setParameterNotifyingHost (int parameterIndex, float newValue); + + /** Returns true if the host can automate this parameter. + By default, this returns true for all parameters. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::isAutomatable() instead. + */ + virtual bool isParameterAutomatable (int parameterIndex) const; + + /** Should return true if this parameter is a "meta" parameter. + A meta-parameter is a parameter that changes other params. It is used + by some hosts (e.g. AudioUnit hosts). + By default this returns false. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::isMetaParameter() instead. + */ + virtual bool isMetaParameter (int parameterIndex) const; + + /** Should return the parameter's category. + By default, this returns the "generic" category. + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::getCategory() instead. + */ + virtual AudioProcessorParameter::Category getParameterCategory (int parameterIndex) const; + + /** Sends a signal to the host to tell it that the user is about to start changing this + parameter. + + This allows the host to know when a parameter is actively being held by the user, and + it may use this information to help it record automation. + + If you call this, it must be matched by a later call to endParameterChangeGesture(). + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::beginChangeGesture() instead. + */ + void beginParameterChangeGesture (int parameterIndex); + + /** Tells the host that the user has finished changing this parameter. + + This allows the host to know when a parameter is actively being held by the user, and + it may use this information to help it record automation. + + A call to this method must follow a call to beginParameterChangeGesture(). + + NOTE! This method will eventually be deprecated! It's recommended that you use + AudioProcessorParameter::endChangeGesture() instead. + */ + void endParameterChangeGesture (int parameterIndex); + + /** The filter can call this when something (apart from a parameter value) has changed. + + It sends a hint to the host that something like the program, number of parameters, + etc, has changed, and that it should update itself. + */ + void updateHostDisplay(); + + //============================================================================== + /** Adds a parameter to the list. + The parameter object will be managed and deleted automatically by the list + when no longer needed. + */ + void addParameter (AudioProcessorParameter*); + + /** Returns the current list of parameters. */ + const OwnedArray& getParameters() const noexcept; + + //============================================================================== + /** Returns the number of preset programs the filter supports. + + The value returned must be valid as soon as this object is created, and + must not change over its lifetime. + + This value shouldn't be less than 1. + */ + virtual int getNumPrograms() = 0; + + /** Returns the number of the currently active program. */ + virtual int getCurrentProgram() = 0; + + /** Called by the host to change the current program. */ + virtual void setCurrentProgram (int index) = 0; + + /** Must return the name of a given program. */ + virtual const String getProgramName (int index) = 0; + + /** Called by the host to rename a program. */ + virtual void changeProgramName (int index, const String& newName) = 0; + + //============================================================================== + /** The host will call this method when it wants to save the filter's internal state. + + This must copy any info about the filter's state into the block of memory provided, + so that the host can store this and later restore it using setStateInformation(). + + Note that there's also a getCurrentProgramStateInformation() method, which only + stores the current program, not the state of the entire filter. + + See also the helper function copyXmlToBinary() for storing settings as XML. + + @see getCurrentProgramStateInformation + */ + virtual void getStateInformation (juce::MemoryBlock& destData) = 0; + + /** The host will call this method if it wants to save the state of just the filter's + current program. + + Unlike getStateInformation, this should only return the current program's state. + + Not all hosts support this, and if you don't implement it, the base class + method just calls getStateInformation() instead. If you do implement it, be + sure to also implement getCurrentProgramStateInformation. + + @see getStateInformation, setCurrentProgramStateInformation + */ + virtual void getCurrentProgramStateInformation (juce::MemoryBlock& destData); + + /** This must restore the filter's state from a block of data previously created + using getStateInformation(). + + Note that there's also a setCurrentProgramStateInformation() method, which tries + to restore just the current program, not the state of the entire filter. + + See also the helper function getXmlFromBinary() for loading settings as XML. + + @see setCurrentProgramStateInformation + */ + virtual void setStateInformation (const void* data, int sizeInBytes) = 0; + + /** The host will call this method if it wants to restore the state of just the filter's + current program. + + Not all hosts support this, and if you don't implement it, the base class + method just calls setStateInformation() instead. If you do implement it, be + sure to also implement getCurrentProgramStateInformation. + + @see setStateInformation, getCurrentProgramStateInformation + */ + virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); + + /** This method is called when the total number of input or output channels is changed. */ + virtual void numChannelsChanged(); + + /** This method is called when the number of buses is changed. */ + virtual void numBusesChanged(); + + /** This method is called when the layout of the audio processor changes. */ + virtual void processorLayoutsChanged(); + + //============================================================================== + /** LV2 specific calls, saving/restore as string. */ + virtual String getStateInformationString () { return String(); } + virtual void setStateInformationString (const String&) {} + + //============================================================================== + /** Adds a listener that will be called when an aspect of this processor changes. */ + virtual void addListener (AudioProcessorListener* newListener); + + /** Removes a previously added listener. */ + virtual void removeListener (AudioProcessorListener* listenerToRemove); + + //============================================================================== + /** Tells the processor to use this playhead object. + The processor will not take ownership of the object, so the caller must delete it when + it is no longer being used. + */ + virtual void setPlayHead (AudioPlayHead* newPlayHead); + + //============================================================================== + /** This is called by the processor to specify its details before being played. Use this + version of the function if you are not interested in any sidechain and/or aux buses + and do not care about the layout of channels. Otherwise use setRateAndBufferSizeDetails.*/ + void setPlayConfigDetails (int numIns, int numOuts, double sampleRate, int blockSize); + + /** This is called by the processor to specify its details before being played. You + should call this function after having informed the processor about the channel + and bus layouts via setBusesLayout. + + @see setBusesLayout + */ + void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; + + //============================================================================== + /** AAX plug-ins need to report a unique "plug-in id" for every audio layout + configuration that your AudioProcessor supports on the main bus. Override this + function if you want your AudioProcessor to use a custom "plug-in id" (for example + to stay backward compatible with older versions of JUCE). + + The default implementation will compute a unique integer from the input and output + layout and add this value to the 4 character code 'jcaa' (for native AAX) or 'jyaa' + (for AudioSuite plug-ins). + */ + virtual int32 getAAXPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, + const AudioChannelSet& mainOutputLayout, + bool idForAudioSuite) const; + + #if ! JUCE_AUDIOPROCESSOR_NO_GUI + //============================================================================== + /** Not for public use - this is called before deleting an editor component. */ + void editorBeingDeleted (AudioProcessorEditor*) noexcept; + #endif + + /** Flags to indicate the type of plugin context in which a processor is being used. */ + enum WrapperType + { + wrapperType_Undefined = 0, + wrapperType_VST, + wrapperType_VST3, + wrapperType_AudioUnit, + wrapperType_AudioUnitv3, + wrapperType_RTAS, + wrapperType_AAX, + wrapperType_LV2, + wrapperType_Standalone + }; + + /** When loaded by a plugin wrapper, this flag will be set to indicate the type + of plugin within which the processor is running. + */ + WrapperType wrapperType; + + /** A struct containing information about the DAW track inside which your + AudioProcessor is loaded. */ + struct TrackProperties + { + String name; // The name of the track - this will be empty if the track name is not known + Colour colour; // The colour of the track - this will be transparentBlack if the colour is not known + + // other properties may be added in the future + }; + + /** Informs the AudioProcessor that track properties such as the track's name or + colour has been changed. + + If you are hosting this AudioProcessor then use this method to inform the + AudioProcessor about which track the AudioProcessor is loaded on. This method + may only be called on the message thread. + + If you are implemeting an AudioProcessor then you can override this callback + to do something useful with the track properties such as changing the colour + of your AudioProcessor's editor. It's entirely up to the host when and how + often this callback will be called. + + The default implementation of this callback will do nothing. + */ + virtual void updateTrackProperties (const TrackProperties& properties); + + //============================================================================== + #ifndef DOXYGEN + /** Deprecated: use getTotalNumInputChannels instead. */ + JUCE_DEPRECATED_WITH_BODY (int getNumInputChannels() const noexcept, { return getTotalNumInputChannels(); }) + JUCE_DEPRECATED_WITH_BODY (int getNumOutputChannels() const noexcept, { return getTotalNumOutputChannels(); }) + + /** Returns a string containing a whitespace-separated list of speaker types + These functions are deprecated: use the methods provided in the AudioChannelSet + class. + */ + JUCE_DEPRECATED_WITH_BODY (const String getInputSpeakerArrangement() const noexcept, { return cachedInputSpeakerArrString; }) + JUCE_DEPRECATED_WITH_BODY (const String getOutputSpeakerArrangement() const noexcept, { return cachedOutputSpeakerArrString; }) + + /** Returns the name of one of the processor's input channels. + + These functions are deprecated: your audio processor can inform the host + on channel layouts and names via the methods in the AudiobusLayout class. + */ + JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); + JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); + + /** Returns true if the specified channel is part of a stereo pair with its neighbour. + + These functions are deprecated: your audio processor should specify the audio + channel pairing information by modifying the busLayout member variable in + the constructor. */ + JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); + JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); + #endif + + //============================================================================== + /** Helper function that just converts an xml element into a binary blob. + + Use this in your filter's getStateInformation() method if you want to + store its state as xml. + + Then use getXmlFromBinary() to reverse this operation and retrieve the XML + from a binary blob. + */ + static void copyXmlToBinary (const XmlElement& xml, + juce::MemoryBlock& destData); + + /** Retrieves an XML element that was stored as binary with the copyXmlToBinary() method. + + This might return nullptr if the data's unsuitable or corrupted. Otherwise it will return + an XmlElement object that the caller must delete when no longer needed. + */ + static XmlElement* getXmlFromBinary (const void* data, int sizeInBytes); + + /** @internal */ + static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); + +protected: + /** Callback to query if the AudioProcessor supports a specific layout. + + This callback is called when the host probes the supported bus layouts via + the checkBusesLayoutSupported method. You should override this callback if you + would like to limit the layouts that your AudioProcessor supports. The default + implementation will accept any layout. JUCE does basic sanity checks so that + the provided layouts parameter will have the same number of buses as your + AudioProcessor. + + @see checkBusesLayoutSupported + */ + virtual bool isBusesLayoutSupported (const BusesLayout&) const { return true; } + + /** Callback to check if a certain bus layout can now be applied + + Most subclasses will not need to override this method and should instead + override the isBusesLayoutSupported callback to reject certain layout changes. + + This callback is called when the user requests a layout change. It will only be + called if processing of the AudioProcessor has been stopped by a previous call to + releaseResources or after the construction of the AudioProcessor. It will be called + just before the actual layout change. By returning false you will abort the layout + change and setBusesLayout will return false indicating that the layout change + was not successful. + + The default implementation will simply call isBusesLayoutSupported. + + You only need to override this method if there is a chance that your AudioProcessor + may not accept a layout although you have previously claimed to support it via the + isBusesLayoutSupported callback. This can occur if your AudioProcessor's supported + layouts depend on other plug-in parameters which may have changed since the last + call to isBusesLayoutSupported, such as the format of an audio file which can be + selected by the user in the AudioProcessor's editor. This callback gives the + AudioProcessor a last chance to reject a layout if conditions have changed as it + is always called just before the actual layout change. + + As it is never called while the AudioProcessor is processing audio, it can also + be used for AudioProcessors which wrap other plug-in formats to apply the current + layout to the underlying plug-in. This callback gives such AudioProcessors a + chance to reject the layout change should an error occur with the underlying plug-in + during the layout change. + + @see isBusesLayoutSupported, setBusesLayout + */ + virtual bool canApplyBusesLayout (const BusesLayout& layouts) const { return isBusesLayoutSupported (layouts); } + + //============================================================================== + /** Structure used for AudioProcessor Callbacks */ + struct BusProperties + { + /** The name of the bus */ + String busName; + + /** The default layout of the bus */ + AudioChannelSet defaultLayout; + + /** Is this bus activated by default? */ + bool isActivatedByDefault; + }; + + struct BusesProperties + { + /** The layouts of the input buses */ + Array inputLayouts; + + /** The layouts of the output buses */ + Array outputLayouts; + + void addBus (bool isInput, const String& name, const AudioChannelSet& defaultLayout, bool isActivatedByDefault = true); + + BusesProperties withInput (const String& name, const AudioChannelSet& defaultLayout, bool isActivatedByDefault = true) const; + BusesProperties withOutput (const String& name, const AudioChannelSet& defaultLayout, bool isActivatedByDefault = true) const; + }; + + /** Callback to query if adding/removing buses currently possible. + + This callback is called when the host calls addBus or removeBus. + Similar to canApplyBusesLayout, this callback is only called while + the AudioProcessor is stopped and gives the processor a last + chance to reject a requested bus change. It can also be used to apply + the bus count change to an underlying wrapped plug-in. + + When adding a bus, isAddingBuses will be true and the plug-in is + expected to fill out outNewBusProperties with the properties of the + bus which will be created just after the succesful return of this callback. + + Implementations of AudioProcessor will rarely need to override this + method. Only override this method if your processor supports adding + and removing buses and if it needs more fine grain control over the + naming of new buses or may reject bus number changes although canAddBus + or canRemoveBus returned true. + + The default implementation will return false if canAddBus/canRemoveBus + returns false (the default behavior). Otherwise, this method returns + "Input #busIdx" for input buses and "Output #busIdx" for output buses + where busIdx is the index for newly created buses. The default layout + in this case will be the layout of the previous bus of the same direction. + */ + virtual bool canApplyBusCountChange (bool isInput, bool isAddingBuses, + BusProperties& outNewBusProperties); + + //============================================================================== + friend struct PluginBusUtilities; + + /** @internal */ + AudioPlayHead* playHead; + + /** @internal */ + void sendParamChangeMessageToListeners (int parameterIndex, float newValue); + +private: + //============================================================================== + struct InOutChannelPair + { + int16 inChannels, outChannels; + + InOutChannelPair() noexcept : inChannels (0), outChannels (0) {} + InOutChannelPair (const InOutChannelPair& o) noexcept : inChannels (o.inChannels), outChannels (o.outChannels) {} + InOutChannelPair (int16 inCh, int16 outCh) noexcept : inChannels (inCh), outChannels (outCh) {} + InOutChannelPair (const int16 (&config)[2]) noexcept : inChannels (config[0]), outChannels (config[1]) {} + + InOutChannelPair& operator= (const InOutChannelPair& o) noexcept { inChannels = o.inChannels; outChannels = o.outChannels; return *this; } + + bool operator== (const InOutChannelPair& other) const noexcept + { + return other.inChannels == inChannels && other.outChannels == outChannels; + } + }; + + template + static Array layoutListToArray (const short (&configuration) [numLayouts][2]) + { + Array layouts; + + for (int i = 0; i < numLayouts; ++i) + { + InOutChannelPair pair (configuration [i]); + layouts.add (pair); + } + + return layouts; + } + + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + static Array layoutListToArray (const std::initializer_list& configuration) + { + Array layouts; + + for (std::initializer_list::const_iterator it = configuration.begin(); + it != configuration.end(); ++it) + { + InOutChannelPair pair (*it); + layouts.add (pair); + } + + return layouts; + } + #endif + + //============================================================================== + static BusesProperties busesPropertiesFromLayoutArray (const Array&); + + BusesLayout getNextBestLayoutInList (const BusesLayout&, const Array&) const; + static bool containsLayout (const BusesLayout&, const Array&); + + //============================================================================== + void initialise (const BusesProperties&); + void createBus (bool isInput, const BusProperties&); + + //============================================================================== + Array listeners; + #if ! JUCE_AUDIOPROCESSOR_NO_GUI + Component::SafePointer activeEditor; + #endif + double currentSampleRate; + int blockSize, latencySamples; + #if JUCE_DEBUG + bool textRecursionCheck; + #endif + bool suspended, nonRealtime; + ProcessingPrecision processingPrecision; + CriticalSection callbackLock, listenerLock; + + friend class Bus; + mutable OwnedArray inputBuses, outputBuses; + + String cachedInputSpeakerArrString; + String cachedOutputSpeakerArrString; + + int cachedTotalIns, cachedTotalOuts; + + OwnedArray managedParameters; + AudioProcessorParameter* getParamChecked (int) const noexcept; + + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + BigInteger changingParams; + #endif + + AudioProcessorListener* getListenerLocked (int) const noexcept; + void updateSpeakerFormatStrings(); + bool applyBusLayouts (const BusesLayout&); + void audioIOChanged (bool busNumberChanged, bool channelNumChanged); + void getNextBestLayout (const BusesLayout&, BusesLayout&) const; + + template + void processBypassed (AudioBuffer&, MidiBuffer&); + + // This method is no longer used - you can delete it from your AudioProcessor classes. + JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }) + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessor) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp new file mode 100644 index 000000000..e7a3ace6f --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -0,0 +1,189 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p) +{ + initialise(); +} + +AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p) +{ + // the filter must be valid.. + jassert (p != nullptr); + initialise(); +} + +AudioProcessorEditor::~AudioProcessorEditor() +{ + // if this fails, then the wrapper hasn't called editorBeingDeleted() on the + // filter for some reason.. + jassert (processor.getActiveEditor() != this); + removeComponentListener (resizeListener); +} + +void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} +int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } + +bool AudioProcessorEditor::supportsHostMIDIControllerPresence (bool) { return true; } +void AudioProcessorEditor::hostMIDIControllerIsAvailable (bool) {} + +void AudioProcessorEditor::initialise() +{ + resizable = false; + + attachConstrainer (&defaultConstrainer); + addComponentListener (resizeListener = new AudioProcessorEditorListener (*this)); +} + +//============================================================================== +void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) +{ + if (shouldBeResizable != resizable) + { + resizable = shouldBeResizable; + + if (! resizable) + { + setConstrainer (&defaultConstrainer); + + if (auto w = getWidth()) + { + if (auto h = getHeight()) + { + defaultConstrainer.setSizeLimits (w, h, w, h); + resized(); + } + } + } + } + + bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); + + if (shouldHaveCornerResizer != (resizableCorner != nullptr)) + { + if (shouldHaveCornerResizer) + { + Component::addChildComponent (resizableCorner = new ResizableCornerComponent (this, constrainer)); + resizableCorner->setAlwaysOnTop (true); + } + else + { + resizableCorner = nullptr; + } + } +} + +void AudioProcessorEditor::setResizeLimits (int newMinimumWidth, + int newMinimumHeight, + int newMaximumWidth, + int newMaximumHeight) noexcept +{ + // if you've set up a custom constrainer then these settings won't have any effect.. + jassert (constrainer == &defaultConstrainer || constrainer == nullptr); + + const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); + const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); + + setResizable (shouldEnableResize, shouldHaveCornerResizer); + + if (constrainer == nullptr) + setConstrainer (&defaultConstrainer); + + defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, + newMaximumWidth, newMaximumHeight); + + setBoundsConstrained (getBounds()); +} + +void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstrainer) +{ + if (constrainer != newConstrainer) + { + resizable = true; + attachConstrainer (newConstrainer); + } +} + +void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) +{ + if (constrainer != newConstrainer) + { + constrainer = newConstrainer; + updatePeer(); + } +} + +void AudioProcessorEditor::setBoundsConstrained (Rectangle newBounds) +{ + if (constrainer != nullptr) + constrainer->setBoundsForComponent (this, newBounds, false, false, false, false); + else + setBounds (newBounds); +} + +void AudioProcessorEditor::editorResized (bool wasResized) +{ + if (wasResized) + { + bool resizerHidden = false; + + if (auto* peer = getPeer()) + resizerHidden = peer->isFullScreen() || peer->isKioskMode(); + + if (resizableCorner != nullptr) + { + resizableCorner->setVisible (! resizerHidden); + + const int resizerSize = 18; + resizableCorner->setBounds (getWidth() - resizerSize, + getHeight() - resizerSize, + resizerSize, resizerSize); + } + + if (! resizable) + if (auto w = getWidth()) + if (auto h = getHeight()) + defaultConstrainer.setSizeLimits (w, h, w, h); + } +} + +void AudioProcessorEditor::updatePeer() +{ + if (isOnDesktop()) + if (auto* peer = getPeer()) + peer->setConstrainer (constrainer); +} + +void AudioProcessorEditor::setScaleFactor (float newScale) +{ + setTransform (AffineTransform::scale (newScale)); + editorResized (true); +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h new file mode 100644 index 000000000..7017bd053 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -0,0 +1,209 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +class AudioProcessor; +class AudioProcessorEditorListener; + +//============================================================================== +/** + Base class for the component that acts as the GUI for an AudioProcessor. + + Derive your editor component from this class, and create an instance of it + by overriding the AudioProcessor::createEditor() method. + + @see AudioProcessor, GenericAudioProcessorEditor +*/ +class JUCE_API AudioProcessorEditor : public Component +{ +protected: + //============================================================================== + /** Creates an editor for the specified processor. */ + AudioProcessorEditor (AudioProcessor&) noexcept; + + /** Creates an editor for the specified processor. */ + AudioProcessorEditor (AudioProcessor*) noexcept; + +public: + /** Destructor. */ + ~AudioProcessorEditor(); + + + //============================================================================== + /** The AudioProcessor that this editor represents. */ + AudioProcessor& processor; + + /** Returns a pointer to the processor that this editor represents. + This method is here to support legacy code, but it's easier to just use the + AudioProcessorEditor::processor member variable directly to get this object. + */ + AudioProcessor* getAudioProcessor() const noexcept { return &processor; } + + //============================================================================== + /** Used by the setParameterHighlighting() method. */ + struct ParameterControlHighlightInfo + { + int parameterIndex; + bool isHighlighted; + Colour suggestedColour; + }; + + /** Some types of plugin can call this to suggest that the control for a particular + parameter should be highlighted. + Currently only AAX plugins will call this, and implementing it is optional. + */ + virtual void setControlHighlight (ParameterControlHighlightInfo); + + /** Called by certain plug-in wrappers to find out whether a component is used + to control a parameter. + + If the given component represents a particular plugin parameter, then this + method should return the index of that parameter. If not, it should return -1. + Currently only AAX plugins will call this, and implementing it is optional. + */ + virtual int getControlParameterIndex (Component&); + + /** Override this method to indicate if your editor supports the presence or + absence of a host-provided MIDI controller. + + Currently only AUv3 plug-ins compiled for MacOS 10.13 or iOS 11.0 (or later) + support this functionality, and even then the host may choose to ignore this + information. + + The default behaviour is to report support for both cases. + */ + virtual bool supportsHostMIDIControllerPresence (bool hostMIDIControllerIsAvailable); + + /** Called to indicate if a host is providing a MIDI controller when the host + reconfigures its layout. + + Use this as an opportunity to hide or display your own onscreen keyboard or + other input component. + + Currently only AUv3 plug-ins compiled for MacOS 10.13 or iOS 11.0 (or later) + support this functionality. + */ + virtual void hostMIDIControllerIsAvailable (bool controllerIsAvailable); + + /** Can be called by a host to tell the editor that it should use a non-unity + GUI scale. + */ + virtual void setScaleFactor (float newScale); + + //============================================================================== + /** Marks the host's editor window as resizable + + @param allowHostToResize whether the editor's parent window can be resized + by the user or the host. Even if this is false, you + can still resize your window yourself by calling + setBounds (for example, when a user clicks on a button + in your editor to drop out a panel) which will bypass any + resizable/constraints checks. If you are using + your own corner resizer than this will also bypass + any checks. + @param useBottomRightCornerResizer + @see setResizeLimits, isResizable + */ + void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); + + /** Returns true if the host is allowed to resize editor's parent window + + @see setResizable + */ + bool isResizable() const noexcept { return resizable; } + + /** This sets the maximum and minimum sizes for the window. + + If the window's current size is outside these limits, it will be resized to + make sure it's within them. + + A direct call to setBounds() will bypass any constraint checks, but when the + window is dragged by the user or resized by other indirect means, the constrainer + will limit the numbers involved. + + @see setResizable + */ + void setResizeLimits (int newMinimumWidth, + int newMinimumHeight, + int newMaximumWidth, + int newMaximumHeight) noexcept; + + + /** Returns the bounds constrainer object that this window is using. + You can access this to change its properties. + */ + ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } + + /** Sets the bounds-constrainer object to use for resizing and dragging this window. + + A pointer to the object you pass in will be kept, but it won't be deleted + by this object, so it's the caller's responsibility to manage it. + + If you pass a nullptr, then no contraints will be placed on the positioning of the window. + */ + void setConstrainer (ComponentBoundsConstrainer* newConstrainer); + + /** Calls the window's setBounds method, after first checking these bounds + with the current constrainer. + + @see setConstrainer + */ + void setBoundsConstrained (Rectangle newBounds); + + ScopedPointer resizableCorner; + +private: + //============================================================================== + struct AudioProcessorEditorListener : ComponentListener + { + AudioProcessorEditorListener (AudioProcessorEditor& e) : ed (e) {} + + void componentMovedOrResized (Component&, bool, bool wasResized) override { ed.editorResized (wasResized); } + void componentParentHierarchyChanged (Component&) override { ed.updatePeer(); } + + AudioProcessorEditor& ed; + + JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditorListener) + }; + + //============================================================================== + void initialise(); + void editorResized (bool wasResized); + void updatePeer(); + void attachConstrainer (ComponentBoundsConstrainer*); + + //============================================================================== + ScopedPointer resizeListener; + bool resizable; + ComponentBoundsConstrainer defaultConstrainer; + ComponentBoundsConstrainer* constrainer = {}; + + JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp new file mode 100644 index 000000000..ef59bc470 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -0,0 +1,1702 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +const int AudioProcessorGraph::midiChannelIndex = 0x1000; + +//============================================================================== +template struct FloatDoubleUtil {}; +template struct FloatDoubleType {}; + +template +struct FloatAndDoubleComposition +{ + typedef typename FloatDoubleType::Type FloatType; + typedef typename FloatDoubleType::Type DoubleType; + + template + inline typename FloatDoubleType::Type& get() noexcept + { + return FloatDoubleUtil >::get (*this); + } + + FloatType floatVersion; + DoubleType doubleVersion; +}; + +template struct FloatDoubleUtil { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } }; +template struct FloatDoubleUtil { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } }; + +struct FloatPlaceholder; + +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef AudioBuffer Type; }; +template struct FloatDoubleType*, FloatingType> { typedef AudioBuffer* Type; }; + +//============================================================================== +namespace GraphRenderingOps +{ + +struct AudioGraphRenderingOpBase +{ + AudioGraphRenderingOpBase() noexcept {} + virtual ~AudioGraphRenderingOpBase() {} + + virtual void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) = 0; + + virtual void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) = 0; + + JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase) +}; + +// use CRTP +template +struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase +{ + void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } + + void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } +}; + +//============================================================================== +struct ClearChannelOp : public AudioGraphRenderingOp +{ + ClearChannelOp (const int channel) noexcept : channelNum (channel) {} + + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + sharedBufferChans.clear (channelNum, 0, numSamples); + } + + const int channelNum; + + JUCE_DECLARE_NON_COPYABLE (ClearChannelOp) +}; + +//============================================================================== +struct CopyChannelOp : public AudioGraphRenderingOp +{ + CopyChannelOp (const int srcChan, const int dstChan) noexcept + : srcChannelNum (srcChan), dstChannelNum (dstChan) + {} + + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); + } + + const int srcChannelNum, dstChannelNum; + + JUCE_DECLARE_NON_COPYABLE (CopyChannelOp) +}; + +//============================================================================== +struct AddChannelOp : public AudioGraphRenderingOp +{ + AddChannelOp (const int srcChan, const int dstChan) noexcept + : srcChannelNum (srcChan), dstChannelNum (dstChan) + {} + + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); + } + + const int srcChannelNum, dstChannelNum; + + JUCE_DECLARE_NON_COPYABLE (AddChannelOp) +}; + +//============================================================================== +struct ClearMidiBufferOp : public AudioGraphRenderingOp +{ + ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} + + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) + { + sharedMidiBuffers.getUnchecked (bufferNum)->clear(); + } + + const int bufferNum; + + JUCE_DECLARE_NON_COPYABLE (ClearMidiBufferOp) +}; + +//============================================================================== +struct CopyMidiBufferOp : public AudioGraphRenderingOp +{ + CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept + : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) + {} + + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) + { + *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); + } + + const int srcBufferNum, dstBufferNum; + + JUCE_DECLARE_NON_COPYABLE (CopyMidiBufferOp) +}; + +//============================================================================== +struct AddMidiBufferOp : public AudioGraphRenderingOp +{ + AddMidiBufferOp (const int srcBuffer, const int dstBuffer) + : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) + {} + + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) + { + sharedMidiBuffers.getUnchecked (dstBufferNum) + ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); + } + + const int srcBufferNum, dstBufferNum; + + JUCE_DECLARE_NON_COPYABLE (AddMidiBufferOp) +}; + +//============================================================================== +struct DelayChannelOp : public AudioGraphRenderingOp +{ + DelayChannelOp (const int chan, const int delaySize) + : channel (chan), + bufferSize (delaySize + 1), + readIndex (0), writeIndex (delaySize) + { + buffer.floatVersion. calloc ((size_t) bufferSize); + buffer.doubleVersion.calloc ((size_t) bufferSize); + } + + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + FloatType* data = sharedBufferChans.getWritePointer (channel, 0); + HeapBlock& block = buffer.get(); + + for (int i = numSamples; --i >= 0;) + { + block [writeIndex] = *data; + *data++ = block [readIndex]; + + if (++readIndex >= bufferSize) readIndex = 0; + if (++writeIndex >= bufferSize) writeIndex = 0; + } + } + +private: + FloatAndDoubleComposition > buffer; + const int channel, bufferSize; + int readIndex, writeIndex; + + JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) +}; + +//============================================================================== +struct ProcessBufferOp : public AudioGraphRenderingOp +{ + ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, + const Array& audioChannelsUsed, + const int totalNumChans, + const int midiBuffer) + : node (n), + processor (n->getProcessor()), + audioChannelsToUse (audioChannelsUsed), + totalChans (jmax (1, totalNumChans)), + midiBufferToUse (midiBuffer) + { + audioChannels.floatVersion. calloc ((size_t) totalChans); + audioChannels.doubleVersion.calloc ((size_t) totalChans); + + while (audioChannelsToUse.size() < totalChans) + audioChannelsToUse.add (0); + } + + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) + { + HeapBlock& channels = audioChannels.get(); + + for (int i = totalChans; --i >= 0;) + channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); + + AudioBuffer buffer (channels, totalChans, numSamples); + + if (processor->isSuspended()) + { + buffer.clear(); + } + else + { + ScopedLock lock (processor->getCallbackLock()); + + callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + } + } + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + processor->processBlock (buffer, midiMessages); + } + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + if (processor->isUsingDoublePrecision()) + { + processor->processBlock (buffer, midiMessages); + } + else + { + // if the processor is in single precision mode but the graph in double + // precision then we need to convert between buffer formats. Note, that + // this will only happen if the processor does not support double + // precision processing. + tempBuffer.makeCopyOf (buffer, true); + processor->processBlock (tempBuffer, midiMessages); + buffer.makeCopyOf (tempBuffer, true); + } + } + + const AudioProcessorGraph::Node::Ptr node; + AudioProcessor* const processor; + +private: + Array audioChannelsToUse; + FloatAndDoubleComposition > audioChannels; + AudioBuffer tempBuffer; + const int totalChans; + const int midiBufferToUse; + + JUCE_DECLARE_NON_COPYABLE (ProcessBufferOp) +}; + +//============================================================================== +/** Used to calculate the correct sequence of rendering ops needed, based on + the best re-use of shared buffers at each stage. +*/ +struct RenderingOpSequenceCalculator +{ + RenderingOpSequenceCalculator (AudioProcessorGraph& g, + const Array& nodes, + Array& renderingOps) + : graph (g), + orderedNodes (nodes), + totalLatency (0) + { + nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros + channels.add (0); + + midiNodeIds.add ((uint32) zeroNodeID); + + for (int i = 0; i < orderedNodes.size(); ++i) + { + createRenderingOpsForNode (*orderedNodes.getUnchecked(i), renderingOps, i); + markAnyUnusedBuffersAsFree (i); + } + + graph.setLatencySamples (totalLatency); + } + + int getNumBuffersNeeded() const noexcept { return nodeIds.size(); } + int getNumMidiBuffersNeeded() const noexcept { return midiNodeIds.size(); } + +private: + //============================================================================== + AudioProcessorGraph& graph; + const Array& orderedNodes; + Array channels; + Array nodeIds, midiNodeIds; + + enum { freeNodeID = 0xffffffff, zeroNodeID = 0xfffffffe, anonymousNodeID = 0xfffffffd }; + + static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID && nodeID != zeroNodeID; } + + Array nodeDelayIDs; + Array nodeDelays; + int totalLatency; + + int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } + + void setNodeDelay (const uint32 nodeID, const int latency) + { + const int index = nodeDelayIDs.indexOf (nodeID); + + if (index >= 0) + { + nodeDelays.set (index, latency); + } + else + { + nodeDelayIDs.add (nodeID); + nodeDelays.add (latency); + } + } + + int getInputLatencyForNode (const uint32 nodeID) const + { + int maxLatency = 0; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == nodeID) + maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); + } + + return maxLatency; + } + + //============================================================================== + void createRenderingOpsForNode (AudioProcessorGraph::Node& node, + Array& renderingOps, + const int ourRenderingIndex) + { + AudioProcessor& processor = *node.getProcessor(); + const int numIns = processor.getTotalNumInputChannels(); + const int numOuts = processor.getTotalNumOutputChannels(); + const int totalChans = jmax (numIns, numOuts); + + Array audioChannelsToUse; + int midiBufferToUse = -1; + + int maxLatency = getInputLatencyForNode (node.nodeId); + + for (int inputChan = 0; inputChan < numIns; ++inputChan) + { + // get a list of all the inputs to this node + Array sourceNodes; + Array sourceOutputChans; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == node.nodeId && c->destChannelIndex == inputChan) + { + sourceNodes.add (c->sourceNodeId); + sourceOutputChans.add (c->sourceChannelIndex); + } + } + + int bufIndex = -1; + + if (sourceNodes.size() == 0) + { + // unconnected input channel + + if (inputChan >= numOuts) + { + bufIndex = getReadOnlyEmptyBuffer(); + jassert (bufIndex >= 0); + } + else + { + bufIndex = getFreeBuffer (false); + renderingOps.add (new ClearChannelOp (bufIndex)); + } + } + else if (sourceNodes.size() == 1) + { + // channel with a straightforward single input.. + const uint32 srcNode = sourceNodes.getUnchecked(0); + const int srcChan = sourceOutputChans.getUnchecked(0); + + bufIndex = getBufferContaining (srcNode, srcChan); + + if (bufIndex < 0) + { + // if not found, this is probably a feedback loop + bufIndex = getReadOnlyEmptyBuffer(); + jassert (bufIndex >= 0); + } + + if (inputChan < numOuts + && isBufferNeededLater (ourRenderingIndex, + inputChan, + srcNode, srcChan)) + { + // can't mess up this channel because it's needed later by another node, so we + // need to use a copy of it.. + const int newFreeBuffer = getFreeBuffer (false); + + renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer)); + + bufIndex = newFreeBuffer; + } + + const int nodeDelay = getNodeDelay (srcNode); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); + } + else + { + // channel with a mix of several inputs.. + + // try to find a re-usable channel from our inputs.. + int reusableInputIndex = -1; + + for (int i = 0; i < sourceNodes.size(); ++i) + { + const int sourceBufIndex = getBufferContaining (sourceNodes.getUnchecked(i), + sourceOutputChans.getUnchecked(i)); + + if (sourceBufIndex >= 0 + && ! isBufferNeededLater (ourRenderingIndex, + inputChan, + sourceNodes.getUnchecked(i), + sourceOutputChans.getUnchecked(i))) + { + // we've found one of our input chans that can be re-used.. + reusableInputIndex = i; + bufIndex = sourceBufIndex; + + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay)); + + break; + } + } + + if (reusableInputIndex < 0) + { + // can't re-use any of our input chans, so get a new one and copy everything into it.. + bufIndex = getFreeBuffer (false); + jassert (bufIndex != 0); + + markBufferAsContaining (bufIndex, static_cast (anonymousNodeID), 0); + + const int srcIndex = getBufferContaining (sourceNodes.getUnchecked (0), + sourceOutputChans.getUnchecked (0)); + if (srcIndex < 0) + { + // if not found, this is probably a feedback loop + renderingOps.add (new ClearChannelOp (bufIndex)); + } + else + { + renderingOps.add (new CopyChannelOp (srcIndex, bufIndex)); + } + + reusableInputIndex = 0; + const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); + } + + for (int j = 0; j < sourceNodes.size(); ++j) + { + if (j != reusableInputIndex) + { + int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), + sourceOutputChans.getUnchecked(j)); + if (srcIndex >= 0) + { + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); + + if (nodeDelay < maxLatency) + { + if (! isBufferNeededLater (ourRenderingIndex, inputChan, + sourceNodes.getUnchecked(j), + sourceOutputChans.getUnchecked(j))) + { + renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay)); + } + else // buffer is reused elsewhere, can't be delayed + { + const int bufferToDelay = getFreeBuffer (false); + renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay)); + renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay)); + srcIndex = bufferToDelay; + } + } + + renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + } + } + } + } + + jassert (bufIndex >= 0); + audioChannelsToUse.add (bufIndex); + + if (inputChan < numOuts) + markBufferAsContaining (bufIndex, node.nodeId, inputChan); + } + + for (int outputChan = numIns; outputChan < numOuts; ++outputChan) + { + const int bufIndex = getFreeBuffer (false); + jassert (bufIndex != 0); + audioChannelsToUse.add (bufIndex); + + markBufferAsContaining (bufIndex, node.nodeId, outputChan); + } + + // Now the same thing for midi.. + Array midiSourceNodes; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == node.nodeId && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex) + midiSourceNodes.add (c->sourceNodeId); + } + + if (midiSourceNodes.size() == 0) + { + // No midi inputs.. + midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + + if (processor.acceptsMidi() || processor.producesMidi()) + renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); + } + else if (midiSourceNodes.size() == 1) + { + // One midi input.. + midiBufferToUse = getBufferContaining (midiSourceNodes.getUnchecked(0), + AudioProcessorGraph::midiChannelIndex); + + if (midiBufferToUse >= 0) + { + if (isBufferNeededLater (ourRenderingIndex, + AudioProcessorGraph::midiChannelIndex, + midiSourceNodes.getUnchecked(0), + AudioProcessorGraph::midiChannelIndex)) + { + // can't mess up this channel because it's needed later by another node, so we + // need to use a copy of it.. + const int newFreeBuffer = getFreeBuffer (true); + renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer)); + midiBufferToUse = newFreeBuffer; + } + } + else + { + // probably a feedback loop, so just use an empty one.. + midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + } + } + else + { + // More than one midi input being mixed.. + int reusableInputIndex = -1; + + for (int i = 0; i < midiSourceNodes.size(); ++i) + { + const int sourceBufIndex = getBufferContaining (midiSourceNodes.getUnchecked(i), + AudioProcessorGraph::midiChannelIndex); + + if (sourceBufIndex >= 0 + && ! isBufferNeededLater (ourRenderingIndex, + AudioProcessorGraph::midiChannelIndex, + midiSourceNodes.getUnchecked(i), + AudioProcessorGraph::midiChannelIndex)) + { + // we've found one of our input buffers that can be re-used.. + reusableInputIndex = i; + midiBufferToUse = sourceBufIndex; + break; + } + } + + if (reusableInputIndex < 0) + { + // can't re-use any of our input buffers, so get a new one and copy everything into it.. + midiBufferToUse = getFreeBuffer (true); + jassert (midiBufferToUse >= 0); + + const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(0), + AudioProcessorGraph::midiChannelIndex); + if (srcIndex >= 0) + renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse)); + else + renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); + + reusableInputIndex = 0; + } + + for (int j = 0; j < midiSourceNodes.size(); ++j) + { + if (j != reusableInputIndex) + { + const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(j), + AudioProcessorGraph::midiChannelIndex); + if (srcIndex >= 0) + renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse)); + } + } + } + + if (processor.producesMidi()) + markBufferAsContaining (midiBufferToUse, node.nodeId, + AudioProcessorGraph::midiChannelIndex); + + setNodeDelay (node.nodeId, maxLatency + processor.getLatencySamples()); + + if (numOuts == 0) + totalLatency = maxLatency; + + renderingOps.add (new ProcessBufferOp (&node, audioChannelsToUse, + totalChans, midiBufferToUse)); + } + + //============================================================================== + int getFreeBuffer (const bool forMidi) + { + if (forMidi) + { + for (int i = 1; i < midiNodeIds.size(); ++i) + if (midiNodeIds.getUnchecked(i) == freeNodeID) + return i; + + midiNodeIds.add ((uint32) freeNodeID); + return midiNodeIds.size() - 1; + } + else + { + for (int i = 1; i < nodeIds.size(); ++i) + if (nodeIds.getUnchecked(i) == freeNodeID) + return i; + + nodeIds.add ((uint32) freeNodeID); + channels.add (0); + return nodeIds.size() - 1; + } + } + + int getReadOnlyEmptyBuffer() const noexcept + { + return 0; + } + + int getBufferContaining (const uint32 nodeId, const int outputChannel) const noexcept + { + if (outputChannel == AudioProcessorGraph::midiChannelIndex) + { + for (int i = midiNodeIds.size(); --i >= 0;) + if (midiNodeIds.getUnchecked(i) == nodeId) + return i; + } + else + { + for (int i = nodeIds.size(); --i >= 0;) + if (nodeIds.getUnchecked(i) == nodeId + && channels.getUnchecked(i) == outputChannel) + return i; + } + + return -1; + } + + void markAnyUnusedBuffersAsFree (const int stepIndex) + { + for (int i = 0; i < nodeIds.size(); ++i) + { + if (isNodeBusy (nodeIds.getUnchecked(i)) + && ! isBufferNeededLater (stepIndex, -1, + nodeIds.getUnchecked(i), + channels.getUnchecked(i))) + { + nodeIds.set (i, (uint32) freeNodeID); + } + } + + for (int i = 0; i < midiNodeIds.size(); ++i) + { + if (isNodeBusy (midiNodeIds.getUnchecked(i)) + && ! isBufferNeededLater (stepIndex, -1, + midiNodeIds.getUnchecked(i), + AudioProcessorGraph::midiChannelIndex)) + { + midiNodeIds.set (i, (uint32) freeNodeID); + } + } + } + + bool isBufferNeededLater (int stepIndexToSearchFrom, + int inputChannelOfIndexToIgnore, + const uint32 nodeId, + const int outputChanIndex) const + { + while (stepIndexToSearchFrom < orderedNodes.size()) + { + const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom); + + if (outputChanIndex == AudioProcessorGraph::midiChannelIndex) + { + if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex + && graph.getConnectionBetween (nodeId, AudioProcessorGraph::midiChannelIndex, + node->nodeId, AudioProcessorGraph::midiChannelIndex) != nullptr) + return true; + } + else + { + for (int i = 0; i < node->getProcessor()->getTotalNumInputChannels(); ++i) + if (i != inputChannelOfIndexToIgnore + && graph.getConnectionBetween (nodeId, outputChanIndex, + node->nodeId, i) != nullptr) + return true; + } + + inputChannelOfIndexToIgnore = -1; + ++stepIndexToSearchFrom; + } + + return false; + } + + void markBufferAsContaining (int bufferNum, uint32 nodeId, int outputIndex) + { + if (outputIndex == AudioProcessorGraph::midiChannelIndex) + { + jassert (bufferNum > 0 && bufferNum < midiNodeIds.size()); + + midiNodeIds.set (bufferNum, nodeId); + } + else + { + jassert (bufferNum >= 0 && bufferNum < nodeIds.size()); + + nodeIds.set (bufferNum, nodeId); + channels.set (bufferNum, outputIndex); + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderingOpSequenceCalculator) +}; + +//============================================================================== +// Holds a fast lookup table for checking which nodes are inputs to others. +class ConnectionLookupTable +{ +public: + explicit ConnectionLookupTable (const OwnedArray& connections) + { + for (int i = 0; i < connections.size(); ++i) + { + const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i); + + int index; + Entry* entry = findEntry (c->destNodeId, index); + + if (entry == nullptr) + { + entry = new Entry (c->destNodeId); + entries.insert (index, entry); + } + + entry->srcNodes.add (c->sourceNodeId); + } + } + + bool isAnInputTo (const uint32 possibleInputId, + const uint32 possibleDestinationId) const noexcept + { + return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size()); + } + +private: + //============================================================================== + struct Entry + { + explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {} + + const uint32 destNodeId; + SortedSet srcNodes; + + JUCE_DECLARE_NON_COPYABLE (Entry) + }; + + OwnedArray entries; + + bool isAnInputToRecursive (const uint32 possibleInputId, + const uint32 possibleDestinationId, + int recursionCheck) const noexcept + { + int index; + + if (const Entry* const entry = findEntry (possibleDestinationId, index)) + { + const SortedSet& srcNodes = entry->srcNodes; + + if (srcNodes.contains (possibleInputId)) + return true; + + if (--recursionCheck >= 0) + { + for (int i = 0; i < srcNodes.size(); ++i) + if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck)) + return true; + } + } + + return false; + } + + Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept + { + Entry* result = nullptr; + + int start = 0; + int end = entries.size(); + + for (;;) + { + if (start >= end) + { + break; + } + else if (destNodeId == entries.getUnchecked (start)->destNodeId) + { + result = entries.getUnchecked (start); + break; + } + else + { + const int halfway = (start + end) / 2; + + if (halfway == start) + { + if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) + ++start; + + break; + } + else if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) + start = halfway; + else + end = halfway; + } + } + + insertIndex = start; + return result; + } + + JUCE_DECLARE_NON_COPYABLE (ConnectionLookupTable) +}; + +//============================================================================== +struct ConnectionSorter +{ + static int compareElements (const AudioProcessorGraph::Connection* const first, + const AudioProcessorGraph::Connection* const second) noexcept + { + if (first->sourceNodeId < second->sourceNodeId) return -1; + if (first->sourceNodeId > second->sourceNodeId) return 1; + if (first->destNodeId < second->destNodeId) return -1; + if (first->destNodeId > second->destNodeId) return 1; + if (first->sourceChannelIndex < second->sourceChannelIndex) return -1; + if (first->sourceChannelIndex > second->sourceChannelIndex) return 1; + if (first->destChannelIndex < second->destChannelIndex) return -1; + if (first->destChannelIndex > second->destChannelIndex) return 1; + + return 0; + } +}; + +} + +//============================================================================== +AudioProcessorGraph::Connection::Connection (const uint32 sourceID, const int sourceChannel, + const uint32 destID, const int destChannel) noexcept + : sourceNodeId (sourceID), sourceChannelIndex (sourceChannel), + destNodeId (destID), destChannelIndex (destChannel) +{ +} + +//============================================================================== +AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) noexcept + : nodeId (nodeID), processor (p), isPrepared (false) +{ + jassert (processor != nullptr); +} + +void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, + AudioProcessorGraph* const graph, ProcessingPrecision precision) +{ + if (! isPrepared) + { + isPrepared = true; + setParentGraph (graph); + + // try to align the precision of the processor and the graph + processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision + : singlePrecision); + + processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); + processor->prepareToPlay (newSampleRate, newBlockSize); + } +} + +void AudioProcessorGraph::Node::unprepare() +{ + if (isPrepared) + { + isPrepared = false; + processor->releaseResources(); + } +} + +void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph) const +{ + if (AudioProcessorGraph::AudioGraphIOProcessor* const ioProc + = dynamic_cast (processor.get())) + ioProc->setParentGraph (graph); +} + +//============================================================================== +struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers +{ + AudioProcessorGraphBufferHelpers() + { + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + } + + void setRenderingBufferSize (int newNumChannels, int newNumSamples) + { + renderingBuffers.floatVersion. setSize (newNumChannels, newNumSamples); + renderingBuffers.doubleVersion.setSize (newNumChannels, newNumSamples); + + renderingBuffers.floatVersion. clear(); + renderingBuffers.doubleVersion.clear(); + } + + void release() + { + renderingBuffers.floatVersion. setSize (1, 1); + renderingBuffers.doubleVersion.setSize (1, 1); + + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + + currentAudioOutputBuffer.floatVersion. setSize (1, 1); + currentAudioOutputBuffer.doubleVersion.setSize (1, 1); + } + + void prepareInOutBuffers(int newNumChannels, int newNumSamples) + { + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + + currentAudioOutputBuffer.floatVersion. setSize (newNumChannels, newNumSamples); + currentAudioOutputBuffer.doubleVersion.setSize (newNumChannels, newNumSamples); + } + + FloatAndDoubleComposition > renderingBuffers; + FloatAndDoubleComposition*> currentAudioInputBuffer; + FloatAndDoubleComposition > currentAudioOutputBuffer; +}; + +//============================================================================== +AudioProcessorGraph::AudioProcessorGraph() + : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), + currentMidiInputBuffer (nullptr), isPrepared (false) +{ +} + +AudioProcessorGraph::~AudioProcessorGraph() +{ + clearRenderingSequence(); + clear(); +} + +const String AudioProcessorGraph::getName() const +{ + return "Audio Graph"; +} + +//============================================================================== +void AudioProcessorGraph::clear() +{ + nodes.clear(); + connections.clear(); + triggerAsyncUpdate(); +} + +AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const +{ + for (int i = nodes.size(); --i >= 0;) + if (nodes.getUnchecked(i)->nodeId == nodeId) + return nodes.getUnchecked(i); + + return nullptr; +} + +AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) +{ + if (newProcessor == nullptr || newProcessor == this) + { + jassertfalse; + return nullptr; + } + + for (int i = nodes.size(); --i >= 0;) + { + if (nodes.getUnchecked(i)->getProcessor() == newProcessor) + { + jassertfalse; // Cannot add the same object to the graph twice! + return nullptr; + } + } + + if (nodeId == 0) + { + nodeId = ++lastNodeId; + } + else + { + // you can't add a node with an id that already exists in the graph.. + jassert (getNodeForId (nodeId) == nullptr); + removeNode (nodeId); + + if (nodeId > lastNodeId) + lastNodeId = nodeId; + } + + newProcessor->setPlayHead (getPlayHead()); + + Node* const n = new Node (nodeId, newProcessor); + nodes.add (n); + + if (isPrepared) + triggerAsyncUpdate(); + + n->setParentGraph (this); + return n; +} + +bool AudioProcessorGraph::removeNode (const uint32 nodeId) +{ + disconnectNode (nodeId); + + for (int i = nodes.size(); --i >= 0;) + { + if (nodes.getUnchecked(i)->nodeId == nodeId) + { + nodes.remove (i); + + if (isPrepared) + triggerAsyncUpdate(); + + return true; + } + } + + return false; +} + +bool AudioProcessorGraph::removeNode (Node* node) +{ + if (node != nullptr) + return removeNode (node->nodeId); + + jassertfalse; + return false; +} + +//============================================================================== +const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, + const int sourceChannelIndex, + const uint32 destNodeId, + const int destChannelIndex) const +{ + const Connection c (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex); + GraphRenderingOps::ConnectionSorter sorter; + return connections [connections.indexOfSorted (sorter, &c)]; +} + +bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId, + const uint32 possibleDestNodeId) const +{ + for (int i = connections.size(); --i >= 0;) + { + const Connection* const c = connections.getUnchecked(i); + + if (c->sourceNodeId == possibleSourceNodeId + && c->destNodeId == possibleDestNodeId) + { + return true; + } + } + + return false; +} + +bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, + const int sourceChannelIndex, + const uint32 destNodeId, + const int destChannelIndex) const +{ + if (sourceChannelIndex < 0 + || destChannelIndex < 0 + || sourceNodeId == destNodeId + || (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex)) + return false; + + const Node* const source = getNodeForId (sourceNodeId); + + if (source == nullptr + || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) + || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) + return false; + + const Node* const dest = getNodeForId (destNodeId); + + if (dest == nullptr + || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) + || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) + return false; + + return getConnectionBetween (sourceNodeId, sourceChannelIndex, + destNodeId, destChannelIndex) == nullptr; +} + +bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, + const int sourceChannelIndex, + const uint32 destNodeId, + const int destChannelIndex) +{ + if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) + return false; + + GraphRenderingOps::ConnectionSorter sorter; + connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, + destNodeId, destChannelIndex)); + + if (isPrepared) + triggerAsyncUpdate(); + + return true; +} + +void AudioProcessorGraph::removeConnection (const int index) +{ + connections.remove (index); + + if (isPrepared) + triggerAsyncUpdate(); +} + +bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, + const uint32 destNodeId, const int destChannelIndex) +{ + bool doneAnything = false; + + for (int i = connections.size(); --i >= 0;) + { + const Connection* const c = connections.getUnchecked(i); + + if (c->sourceNodeId == sourceNodeId + && c->destNodeId == destNodeId + && c->sourceChannelIndex == sourceChannelIndex + && c->destChannelIndex == destChannelIndex) + { + removeConnection (i); + doneAnything = true; + } + } + + return doneAnything; +} + +bool AudioProcessorGraph::disconnectNode (const uint32 nodeId) +{ + bool doneAnything = false; + + for (int i = connections.size(); --i >= 0;) + { + const Connection* const c = connections.getUnchecked(i); + + if (c->sourceNodeId == nodeId || c->destNodeId == nodeId) + { + removeConnection (i); + doneAnything = true; + } + } + + return doneAnything; +} + +bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const +{ + jassert (c != nullptr); + + const Node* const source = getNodeForId (c->sourceNodeId); + const Node* const dest = getNodeForId (c->destNodeId); + + return source != nullptr + && dest != nullptr + && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) + : source->processor->producesMidi()) + && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) + : dest->processor->acceptsMidi()); +} + +bool AudioProcessorGraph::removeIllegalConnections() +{ + bool doneAnything = false; + + for (int i = connections.size(); --i >= 0;) + { + if (! isConnectionLegal (connections.getUnchecked(i))) + { + removeConnection (i); + doneAnything = true; + } + } + + return doneAnything; +} + +//============================================================================== +static void deleteRenderOpArray (Array& ops) +{ + for (int i = ops.size(); --i >= 0;) + delete static_cast (ops.getUnchecked(i)); +} + +void AudioProcessorGraph::clearRenderingSequence() +{ + Array oldOps; + + { + const ScopedLock sl (getCallbackLock()); + renderingOps.swapWith (oldOps); + } + + deleteRenderOpArray (oldOps); +} + +bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId, + const uint32 possibleDestinationId, + const int recursionCheck) const +{ + if (recursionCheck > 0) + { + for (int i = connections.size(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i); + + if (c->destNodeId == possibleDestinationId + && (c->sourceNodeId == possibleInputId + || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1))) + return true; + } + } + + return false; +} + +void AudioProcessorGraph::buildRenderingSequence() +{ + Array newRenderingOps; + int numRenderingBuffersNeeded = 2; + int numMidiBuffersNeeded = 1; + + { + MessageManagerLock mml; + + Array orderedNodes; + + { + const GraphRenderingOps::ConnectionLookupTable table (connections); + + for (int i = 0; i < nodes.size(); ++i) + { + Node* const node = nodes.getUnchecked(i); + + node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); + + int j = 0; + for (; j < orderedNodes.size(); ++j) + if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId)) + break; + + orderedNodes.insert (j, node); + } + } + + GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps); + + numRenderingBuffersNeeded = calculator.getNumBuffersNeeded(); + numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded(); + } + + { + // swap over to the new rendering sequence.. + const ScopedLock sl (getCallbackLock()); + + audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); + + for (int i = midiBuffers.size(); --i >= 0;) + midiBuffers.getUnchecked(i)->clear(); + + while (midiBuffers.size() < numMidiBuffersNeeded) + midiBuffers.add (new MidiBuffer()); + + renderingOps.swapWith (newRenderingOps); + } + + // delete the old ones.. + deleteRenderOpArray (newRenderingOps); +} + +void AudioProcessorGraph::handleAsyncUpdate() +{ + buildRenderingSequence(); +} + +//============================================================================== +void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) +{ + audioBuffers->prepareInOutBuffers (jmax (1, getTotalNumOutputChannels()), estimatedSamplesPerBlock); + + currentMidiInputBuffer = nullptr; + currentMidiOutputBuffer.clear(); + + clearRenderingSequence(); + buildRenderingSequence(); + + isPrepared = true; +} + +bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const +{ + return true; +} + +void AudioProcessorGraph::releaseResources() +{ + isPrepared = false; + + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->unprepare(); + + audioBuffers->release(); + midiBuffers.clear(); + + currentMidiInputBuffer = nullptr; + currentMidiOutputBuffer.clear(); +} + +void AudioProcessorGraph::reset() +{ + const ScopedLock sl (getCallbackLock()); + + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->getProcessor()->reset(); +} + +void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept +{ + const ScopedLock sl (getCallbackLock()); + + AudioProcessor::setNonRealtime (isProcessingNonRealtime); + + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->getProcessor()->setNonRealtime (isProcessingNonRealtime); +} + +void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) +{ + const ScopedLock sl (getCallbackLock()); + + AudioProcessor::setPlayHead (audioPlayHead); + + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); +} + +template +void AudioProcessorGraph::processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + AudioBuffer& renderingBuffers = audioBuffers->renderingBuffers.get(); + AudioBuffer*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer.get(); + AudioBuffer& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get(); + + const int numSamples = buffer.getNumSamples(); + jassert (numSamples <= getBlockSize()); + + currentAudioInputBuffer = &buffer; + currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); + currentAudioOutputBuffer.clear(); + currentMidiInputBuffer = &midiMessages; + currentMidiOutputBuffer.clear(); + + for (int i = 0; i < renderingOps.size(); ++i) + { + GraphRenderingOps::AudioGraphRenderingOpBase* const op + = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); + + op->perform (renderingBuffers, midiBuffers, numSamples); + } + + for (int i = 0; i < buffer.getNumChannels(); ++i) + buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); + + midiMessages.clear(); + midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); +} + +template +void AudioProcessorGraph::sliceAndProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + auto n = buffer.getNumSamples(); + auto ch = buffer.getNumChannels(); + auto max = 0; + + for (auto pos = 0; pos < n; pos += max) + { + max = jmin (n - pos, getBlockSize()); + + AudioBuffer audioSlice (buffer.getArrayOfWritePointers(), ch, pos, max); + MidiBuffer midiSlice; + + midiSlice.addEvents (midiMessages, pos, max, 0); + processAudio (audioSlice, midiSlice); + } +} + +double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } +bool AudioProcessorGraph::acceptsMidi() const { return true; } +bool AudioProcessorGraph::producesMidi() const { return true; } +void AudioProcessorGraph::getStateInformation (juce::MemoryBlock&) {} +void AudioProcessorGraph::setStateInformation (const void*, int) {} + +void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + sliceAndProcess (buffer, midiMessages); +} + +void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + sliceAndProcess (buffer, midiMessages); +} + +// explicit template instantiation +template void AudioProcessorGraph::processAudio ( AudioBuffer& buffer, + MidiBuffer& midiMessages); +template void AudioProcessorGraph::processAudio (AudioBuffer& buffer, + MidiBuffer& midiMessages); + +//============================================================================== +AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) + : type (deviceType), graph (nullptr) +{ +} + +AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() +{ +} + +const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const +{ + switch (type) + { + case audioOutputNode: return "Audio Output"; + case audioInputNode: return "Audio Input"; + case midiOutputNode: return "Midi Output"; + case midiInputNode: return "Midi Input"; + default: break; + } + + return {}; +} + +void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (PluginDescription& d) const +{ + d.name = getName(); + d.uid = d.name.hashCode(); + d.category = "I/O devices"; + d.pluginFormatName = "Internal"; + d.manufacturerName = "ROLI Ltd."; + d.version = "1.0"; + d.isInstrument = false; + + d.numInputChannels = getTotalNumInputChannels(); + if (type == audioOutputNode && graph != nullptr) + d.numInputChannels = graph->getTotalNumInputChannels(); + + d.numOutputChannels = getTotalNumOutputChannels(); + if (type == audioInputNode && graph != nullptr) + d.numOutputChannels = graph->getTotalNumOutputChannels(); +} + +void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) +{ + jassert (graph != nullptr); +} + +void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() +{ +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessing() const +{ + return true; +} + +template +void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + AudioBuffer*& currentAudioInputBuffer = + graph->audioBuffers->currentAudioInputBuffer.get(); + + AudioBuffer& currentAudioOutputBuffer = + graph->audioBuffers->currentAudioOutputBuffer.get(); + + jassert (graph != nullptr); + + switch (type) + { + case audioOutputNode: + { + for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), + buffer.getNumChannels()); --i >= 0;) + { + currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); + } + + break; + } + + case audioInputNode: + { + for (int i = jmin (currentAudioInputBuffer->getNumChannels(), + buffer.getNumChannels()); --i >= 0;) + { + buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); + } + + break; + } + + case midiOutputNode: + graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); + break; + + case midiInputNode: + midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); + break; + + default: + break; + } +} + +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const +{ + return 0; +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const +{ + return type == midiOutputNode; +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const +{ + return type == midiInputNode; +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; } +bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; } + +#if ! JUCE_AUDIOPROCESSOR_NO_GUI +bool AudioProcessorGraph::AudioGraphIOProcessor::hasEditor() const { return false; } +AudioProcessorEditor* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return nullptr; } +#endif + +int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } +int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } +void AudioProcessorGraph::AudioGraphIOProcessor::setCurrentProgram (int) { } + +const String AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return {}; } +void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String&) {} + +void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (juce::MemoryBlock&) {} +void AudioProcessorGraph::AudioGraphIOProcessor::setStateInformation (const void*, int) {} + +void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph) +{ + graph = newGraph; + + if (graph != nullptr) + { + setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, + type == audioInputNode ? graph->getTotalNumInputChannels() : 0, + getSampleRate(), + getBlockSize()); + + updateHostDisplay(); + } +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h new file mode 100644 index 000000000..bfd21d118 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -0,0 +1,411 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A type of AudioProcessor which plays back a graph of other AudioProcessors. + + Use one of these objects if you want to wire-up a set of AudioProcessors + and play back the result. + + Processors can be added to the graph as "nodes" using addNode(), and once + added, you can connect any of their input or output channels to other + nodes using addConnection(). + + To play back a graph through an audio device, you might want to use an + AudioProcessorPlayer object. +*/ +class JUCE_API AudioProcessorGraph : public AudioProcessor, + private AsyncUpdater +{ +public: + //============================================================================== + /** Creates an empty graph. */ + AudioProcessorGraph(); + + /** Destructor. + Any processor objects that have been added to the graph will also be deleted. + */ + ~AudioProcessorGraph(); + + //============================================================================== + /** Represents one of the nodes, or processors, in an AudioProcessorGraph. + + To create a node, call AudioProcessorGraph::addNode(). + */ + class JUCE_API Node : public ReferenceCountedObject + { + public: + //============================================================================== + /** The ID number assigned to this node. + This is assigned by the graph that owns it, and can't be changed. + */ + const uint32 nodeId; + + /** The actual processor object that this node represents. */ + AudioProcessor* getProcessor() const noexcept { return processor; } + + /** A set of user-definable properties that are associated with this node. + + This can be used to attach values to the node for whatever purpose seems + useful. For example, you might store an x and y position if your application + is displaying the nodes on-screen. + */ + NamedValueSet properties; + + //============================================================================== + /** A convenient typedef for referring to a pointer to a node object. */ + typedef ReferenceCountedObjectPtr Ptr; + + private: + //============================================================================== + friend class AudioProcessorGraph; + + const ScopedPointer processor; + bool isPrepared; + + Node (uint32 nodeId, AudioProcessor*) noexcept; + + void setParentGraph (AudioProcessorGraph*) const; + void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*, ProcessingPrecision); + void unprepare(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) + }; + + //============================================================================== + /** Represents a connection between two channels of two nodes in an AudioProcessorGraph. + + To create a connection, use AudioProcessorGraph::addConnection(). + */ + struct JUCE_API Connection + { + //============================================================================== + Connection (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex) noexcept; + + //============================================================================== + /** The ID number of the node which is the input source for this connection. + @see AudioProcessorGraph::getNodeForId + */ + uint32 sourceNodeId; + + /** The index of the output channel of the source node from which this + connection takes its data. + + If this value is the special number AudioProcessorGraph::midiChannelIndex, then + it is referring to the source node's midi output. Otherwise, it is the zero-based + index of an audio output channel in the source node. + */ + int sourceChannelIndex; + + /** The ID number of the node which is the destination for this connection. + @see AudioProcessorGraph::getNodeForId + */ + uint32 destNodeId; + + /** The index of the input channel of the destination node to which this + connection delivers its data. + + If this value is the special number AudioProcessorGraph::midiChannelIndex, then + it is referring to the destination node's midi input. Otherwise, it is the zero-based + index of an audio input channel in the destination node. + */ + int destChannelIndex; + + private: + //============================================================================== + JUCE_LEAK_DETECTOR (Connection) + }; + + //============================================================================== + /** Deletes all nodes and connections from this graph. + Any processor objects in the graph will be deleted. + */ + void clear(); + + /** Returns the number of nodes in the graph. */ + int getNumNodes() const noexcept { return nodes.size(); } + + /** Returns a pointer to one of the nodes in the graph. + This will return nullptr if the index is out of range. + @see getNodeForId + */ + Node* getNode (const int index) const noexcept { return nodes [index]; } + + /** Searches the graph for a node with the given ID number and returns it. + If no such node was found, this returns nullptr. + @see getNode + */ + Node* getNodeForId (const uint32 nodeId) const; + + /** Adds a node to the graph. + + This creates a new node in the graph, for the specified processor. Once you have + added a processor to the graph, the graph owns it and will delete it later when + it is no longer needed. + + The optional nodeId parameter lets you specify an ID to use for the node, but + if the value is already in use, this new node will overwrite the old one. + + If this succeeds, it returns a pointer to the newly-created node. + */ + Node* addNode (AudioProcessor* newProcessor, uint32 nodeId = 0); + + /** Deletes a node within the graph which has the specified ID. + This will also delete any connections that are attached to this node. + */ + bool removeNode (uint32 nodeId); + + /** Deletes a node within the graph. + This will also delete any connections that are attached to this node. + */ + bool removeNode (Node* node); + + //============================================================================== + /** Returns the number of connections in the graph. */ + int getNumConnections() const { return connections.size(); } + + /** Returns a pointer to one of the connections in the graph. */ + const Connection* getConnection (int index) const { return connections [index]; } + + /** Searches for a connection between some specified channels. + If no such connection is found, this returns nullptr. + */ + const Connection* getConnectionBetween (uint32 sourceNodeId, + int sourceChannelIndex, + uint32 destNodeId, + int destChannelIndex) const; + + /** Returns true if there is a connection between any of the channels of + two specified nodes. + */ + bool isConnected (uint32 possibleSourceNodeId, + uint32 possibleDestNodeId) const; + + /** Returns true if it would be legal to connect the specified points. */ + bool canConnect (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex) const; + + /** Attempts to connect two specified channels of two nodes. + + If this isn't allowed (e.g. because you're trying to connect a midi channel + to an audio one or other such nonsense), then it'll return false. + */ + bool addConnection (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex); + + /** Deletes the connection with the specified index. */ + void removeConnection (int index); + + /** Deletes any connection between two specified points. + Returns true if a connection was actually deleted. + */ + bool removeConnection (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex); + + /** Removes all connections from the specified node. */ + bool disconnectNode (uint32 nodeId); + + /** Returns true if the given connection's channel numbers map on to valid + channels at each end. + Even if a connection is valid when created, its status could change if + a node changes its channel config. + */ + bool isConnectionLegal (const Connection* connection) const; + + /** Performs a sanity checks of all the connections. + + This might be useful if some of the processors are doing things like changing + their channel counts, which could render some connections obsolete. + */ + bool removeIllegalConnections(); + + //============================================================================== + /** A special number that represents the midi channel of a node. + + This is used as a channel index value if you want to refer to the midi input + or output instead of an audio channel. + */ + static const int midiChannelIndex; + + + //============================================================================== + /** A special type of AudioProcessor that can live inside an AudioProcessorGraph + in order to use the audio that comes into and out of the graph itself. + + If you create an AudioGraphIOProcessor in "input" mode, it will act as a + node in the graph which delivers the audio that is coming into the parent + graph. This allows you to stream the data to other nodes and process the + incoming audio. + + Likewise, one of these in "output" mode can be sent data which it will add to + the sum of data being sent to the graph's output. + + @see AudioProcessorGraph + */ + class JUCE_API AudioGraphIOProcessor : public AudioPluginInstance + { + public: + /** Specifies the mode in which this processor will operate. + */ + enum IODeviceType + { + audioInputNode, /**< In this mode, the processor has output channels + representing all the audio input channels that are + coming into its parent audio graph. */ + audioOutputNode, /**< In this mode, the processor has input channels + representing all the audio output channels that are + going out of its parent audio graph. */ + midiInputNode, /**< In this mode, the processor has a midi output which + delivers the same midi data that is arriving at its + parent graph. */ + midiOutputNode /**< In this mode, the processor has a midi input and + any data sent to it will be passed out of the parent + graph. */ + }; + + //============================================================================== + /** Returns the mode of this processor. */ + IODeviceType getType() const noexcept { return type; } + + /** Returns the parent graph to which this processor belongs, or nullptr if it + hasn't yet been added to one. */ + AudioProcessorGraph* getParentGraph() const noexcept { return graph; } + + /** True if this is an audio or midi input. */ + bool isInput() const noexcept; + /** True if this is an audio or midi output. */ + bool isOutput() const noexcept; + + //============================================================================== + AudioGraphIOProcessor (const IODeviceType type); + ~AudioGraphIOProcessor(); + + const String getName() const override; + void fillInPluginDescription (PluginDescription&) const override; + void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; + void releaseResources() override; + void processBlock (AudioBuffer& , MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + bool supportsDoublePrecisionProcessing() const override; + + double getTailLengthSeconds() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + + #if ! JUCE_AUDIOPROCESSOR_NO_GUI + bool hasEditor() const override; + AudioProcessorEditor* createEditor() override; + #endif + + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram (int) override; + const String getProgramName (int) override; + void changeProgramName (int, const String&) override; + + void getStateInformation (juce::MemoryBlock& destData) override; + void setStateInformation (const void* data, int sizeInBytes) override; + + /** @internal */ + void setParentGraph (AudioProcessorGraph*); + + private: + const IODeviceType type; + AudioProcessorGraph* graph; + + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) + }; + + //============================================================================== + const String getName() const override; + void prepareToPlay (double, int) override; + void releaseResources() override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + bool supportsDoublePrecisionProcessing() const override; + + void reset() override; + void setNonRealtime (bool) noexcept override; + void setPlayHead (AudioPlayHead*) override; + + double getTailLengthSeconds() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + + #if ! JUCE_AUDIOPROCESSOR_NO_GUI + bool hasEditor() const override { return false; } + AudioProcessorEditor* createEditor() override { return nullptr; } + #endif + int getNumPrograms() override { return 0; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override { } + const String getProgramName (int) override { return {}; } + void changeProgramName (int, const String&) override { } + void getStateInformation (juce::MemoryBlock&) override; + void setStateInformation (const void* data, int sizeInBytes) override; + +private: + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + + template + void sliceAndProcess (AudioBuffer& buffer, MidiBuffer& midiMessages); + + //============================================================================== + ReferenceCountedArray nodes; + OwnedArray connections; + uint32 lastNodeId; + OwnedArray midiBuffers; + Array renderingOps; + + friend class AudioGraphIOProcessor; + struct AudioProcessorGraphBufferHelpers; + ScopedPointer audioBuffers; + + MidiBuffer* currentMidiInputBuffer; + MidiBuffer currentMidiOutputBuffer; + + bool isPrepared; + + void handleAsyncUpdate() override; + void clearRenderingSequence(); + void buildRenderingSequence(); + bool isAnInputTo (uint32 possibleInputId, uint32 possibleDestinationId, int recursionCheck) const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorGraph) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h new file mode 100644 index 000000000..7781af1fe --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h @@ -0,0 +1,107 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Base class for listeners that want to know about changes to an AudioProcessor. + + Use AudioProcessor::addListener() to register your listener with an AudioProcessor. + + @see AudioProcessor +*/ +class JUCE_API AudioProcessorListener +{ +public: + //============================================================================== + /** Destructor. */ + virtual ~AudioProcessorListener() {} + + //============================================================================== + /** Receives a callback when a parameter is changed. + + IMPORTANT NOTE: this will be called synchronously when a parameter changes, and + many audio processors will change their parameter during their audio callback. + This means that not only has your handler code got to be completely thread-safe, + but it's also got to be VERY fast, and avoid blocking. If you need to handle + this event on your message thread, use this callback to trigger an AsyncUpdater + or ChangeBroadcaster which you can respond to on the message thread. + */ + virtual void audioProcessorParameterChanged (AudioProcessor* processor, + int parameterIndex, + float newValue) = 0; + + /** Called to indicate that something else in the plugin has changed, like its + program, number of parameters, etc. + + IMPORTANT NOTE: this will be called synchronously, and many audio processors will + call it during their audio callback. This means that not only has your handler code + got to be completely thread-safe, but it's also got to be VERY fast, and avoid + blocking. If you need to handle this event on your message thread, use this callback + to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the + message thread. + */ + virtual void audioProcessorChanged (AudioProcessor* processor) = 0; + + /** Indicates that a parameter change gesture has started. + + E.g. if the user is dragging a slider, this would be called when they first + press the mouse button, and audioProcessorParameterChangeGestureEnd would be + called when they release it. + + IMPORTANT NOTE: this will be called synchronously, and many audio processors will + call it during their audio callback. This means that not only has your handler code + got to be completely thread-safe, but it's also got to be VERY fast, and avoid + blocking. If you need to handle this event on your message thread, use this callback + to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the + message thread. + + @see audioProcessorParameterChangeGestureEnd + */ + virtual void audioProcessorParameterChangeGestureBegin (AudioProcessor* processor, + int parameterIndex); + + /** Indicates that a parameter change gesture has finished. + + E.g. if the user is dragging a slider, this would be called when they release + the mouse button. + + IMPORTANT NOTE: this will be called synchronously, and many audio processors will + call it during their audio callback. This means that not only has your handler code + got to be completely thread-safe, but it's also got to be VERY fast, and avoid + blocking. If you need to handle this event on your message thread, use this callback + to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the + message thread. + + @see audioProcessorParameterChangeGestureBegin + */ + virtual void audioProcessorParameterChangeGestureEnd (AudioProcessor* processor, + int parameterIndex); +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h new file mode 100644 index 000000000..8dc6e5eb0 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h @@ -0,0 +1,197 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** An abstract base class for parameter objects that can be added to an + AudioProcessor. + + @see AudioProcessor::addParameter +*/ +class JUCE_API AudioProcessorParameter +{ +public: + AudioProcessorParameter() noexcept; + + /** Destructor. */ + virtual ~AudioProcessorParameter(); + + /** Called by the host to find out the value of this parameter. + + Hosts will expect the value returned to be between 0 and 1.0. + + This could be called quite frequently, so try to make your code efficient. + It's also likely to be called by non-UI threads, so the code in here should + be thread-aware. + */ + virtual float getValue() const = 0; + + /** The host will call this method to change the value of one of the filter's parameters. + + The host may call this at any time, including during the audio processing + callback, so the filter has to process this very fast and avoid blocking. + + If you want to set the value of a parameter internally, e.g. from your + editor component, then don't call this directly - instead, use the + setValueNotifyingHost() method, which will also send a message to + the host telling it about the change. If the message isn't sent, the host + won't be able to automate your parameters properly. + + The value passed will be between 0 and 1.0. + */ + virtual void setValue (float newValue) = 0; + + /** Your filter can call this when it needs to change one of its parameters. + + This could happen when the editor or some other internal operation changes + a parameter. This method will call the setValue() method to change the + value, and will then send a message to the host telling it about the change. + + Note that to make sure the host correctly handles automation, you should call + the beginChangeGesture() and endChangeGesture() methods to tell the host when + the user has started and stopped changing the parameter. + */ + void setValueNotifyingHost (float newValue); + + /** Sends a signal to the host to tell it that the user is about to start changing this + parameter. + This allows the host to know when a parameter is actively being held by the user, and + it may use this information to help it record automation. + If you call this, it must be matched by a later call to endChangeGesture(). + */ + void beginChangeGesture(); + + /** Tells the host that the user has finished changing this parameter. + This allows the host to know when a parameter is actively being held by the user, + and it may use this information to help it record automation. + A call to this method must follow a call to beginChangeGesture(). + */ + void endChangeGesture(); + + /** This should return the default value for this parameter. */ + virtual float getDefaultValue() const = 0; + + /** Returns the name to display for this parameter, which should be made + to fit within the given string length. + */ + virtual String getName (int maximumStringLength) const = 0; + + /** Some parameters may be able to return a label string for + their units. For example "Hz" or "%". + */ + virtual String getLabel() const = 0; + + /** Returns the number of steps that this parameter's range should be quantised into. + + If you want a continuous range of values, don't override this method, and allow + the default implementation to return AudioProcessor::getDefaultNumParameterSteps(). + + If your parameter is boolean, then you may want to make this return 2. + + The value that is returned may or may not be used, depending on the host. If you + want the host to display stepped automation values, rather than a continuous + interpolation between successive values, you should override isDiscrete to return true. + + @see isDiscrete + */ + virtual int getNumSteps() const; + + /** Returns whether the parameter uses discrete values, based on the result of + getNumSteps, or allows the host to select values continuously. + + This information may or may not be used, depending on the host. If you + want the host to display stepped automation values, rather than a continuous + interpolation between successive values, override this method to return true. + + @see getNumSteps + */ + virtual bool isDiscrete() const; + + /** Returns a textual version of the supplied parameter value. + The default implementation just returns the floating point value + as a string, but this could do anything you need for a custom type + of value. + */ + virtual String getText (float value, int /*maximumStringLength*/) const; + + /** Should parse a string and return the appropriate value for it. */ + virtual float getValueForText (const String& text) const = 0; + + /** This can be overridden to tell the host that this parameter operates in the + reverse direction. + (Not all plugin formats or hosts will actually use this information). + */ + virtual bool isOrientationInverted() const; + + /** Returns true if the host can automate this parameter. + By default, this returns true. + */ + virtual bool isAutomatable() const; + + /** Should return true if this parameter is a "meta" parameter. + A meta-parameter is a parameter that changes other params. It is used + by some hosts (e.g. AudioUnit hosts). + By default this returns false. + */ + virtual bool isMetaParameter() const; + + enum Category + { + genericParameter = (0 << 16) | 0, /** If your parameter is not a meter then you should use this category */ + + inputGain = (1 << 16) | 0, /** Currently not used */ + outputGain = (1 << 16) | 1, + + /** The following categories tell the host that this parameter is a meter level value + and therefore read-only. Most hosts will display these type of parameters as + a meter in the generic view of your plug-in. Pro-Tools will also show the meter + in the mixer view. + */ + inputMeter = (2 << 16) | 0, + outputMeter = (2 << 16) | 1, + compressorLimiterGainReductionMeter = (2 << 16) | 2, + expanderGateGainReductionMeter = (2 << 16) | 3, + analysisMeter = (2 << 16) | 4, + otherMeter = (2 << 16) | 5 + }; + + /** Returns the parameter's category. */ + virtual Category getCategory() const; + + /** Returns the index of this parameter in its parent processor's parameter list. */ + int getParameterIndex() const noexcept { return parameterIndex; } + +private: + friend class AudioProcessor; + AudioProcessor* processor; + int parameterIndex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameter) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp new file mode 100644 index 000000000..08f160932 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -0,0 +1,192 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +class ProcessorParameterPropertyComp : public PropertyComponent, + private AudioProcessorListener, + private Timer +{ +public: + ProcessorParameterPropertyComp (const String& name, AudioProcessor& p, int paramIndex) + : PropertyComponent (name), + owner (p), + index (paramIndex), + paramHasChanged (false), + slider (p, paramIndex) + { + startTimer (100); + addAndMakeVisible (slider); + owner.addListener (this); + } + + ~ProcessorParameterPropertyComp() + { + owner.removeListener (this); + } + + void refresh() override + { + paramHasChanged = false; + + if (slider.getThumbBeingDragged() < 0) + slider.setValue (owner.getParameter (index), dontSendNotification); + + slider.updateText(); + } + + void audioProcessorChanged (AudioProcessor*) override {} + + void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) override + { + if (parameterIndex == index) + paramHasChanged = true; + } + + void timerCallback() override + { + if (paramHasChanged) + { + refresh(); + startTimerHz (50); + } + else + { + startTimer (jmin (1000 / 4, getTimerInterval() + 10)); + } + } + +private: + //============================================================================== + class ParamSlider : public Slider + { + public: + ParamSlider (AudioProcessor& p, int paramIndex) : owner (p), index (paramIndex) + { + const int steps = owner.getParameterNumSteps (index); + const AudioProcessorParameter::Category category = p.getParameterCategory (index); + const bool isLevelMeter = (((category & 0xffff0000) >> 16) == 2); + + if (steps > 1 && steps < 0x7fffffff) + setRange (0.0, 1.0, 1.0 / (steps - 1.0)); + else + setRange (0.0, 1.0); + + setEnabled (! isLevelMeter); + setSliderStyle (Slider::LinearBar); + setTextBoxIsEditable (false); + setScrollWheelEnabled (true); + } + + void valueChanged() override + { + const float newVal = (float) getValue(); + + if (owner.getParameter (index) != newVal) + { + owner.setParameterNotifyingHost (index, newVal); + updateText(); + } + } + + void startedDragging() override + { + owner.beginParameterChangeGesture(index); + } + + void stoppedDragging() override + { + owner.endParameterChangeGesture(index); + } + + String getTextFromValue (double /*value*/) override + { + return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); + } + + private: + //============================================================================== + AudioProcessor& owner; + const int index; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParamSlider) + }; + + AudioProcessor& owner; + const int index; + bool volatile paramHasChanged; + ParamSlider slider; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorParameterPropertyComp) +}; + + +//============================================================================== +GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const p) + : AudioProcessorEditor (p) +{ + jassert (p != nullptr); + setOpaque (true); + + addAndMakeVisible (panel); + + Array params; + + const int numParams = p->getNumParameters(); + int totalHeight = 0; + + for (int i = 0; i < numParams; ++i) + { + String name (p->getParameterName (i)); + if (name.trim().isEmpty()) + name = "Unnamed"; + + ProcessorParameterPropertyComp* const pc = new ProcessorParameterPropertyComp (name, *p, i); + params.add (pc); + totalHeight += pc->getPreferredHeight(); + } + + panel.addProperties (params); + + setSize (400, jlimit (25, 400, totalHeight)); +} + +GenericAudioProcessorEditor::~GenericAudioProcessorEditor() +{ +} + +void GenericAudioProcessorEditor::paint (Graphics& g) +{ + g.fillAll (Colours::white); +} + +void GenericAudioProcessorEditor::resized() +{ + panel.setBounds (getLocalBounds()); +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.h b/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.h new file mode 100644 index 000000000..611d62129 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.h @@ -0,0 +1,58 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A type of UI component that displays the parameters of an AudioProcessor as + a simple list of sliders. + + This can be used for showing an editor for a processor that doesn't supply + its own custom editor. + + @see AudioProcessor +*/ +class JUCE_API GenericAudioProcessorEditor : public AudioProcessorEditor +{ +public: + //============================================================================== + GenericAudioProcessorEditor (AudioProcessor* owner); + ~GenericAudioProcessorEditor(); + + //============================================================================== + void paint (Graphics&) override; + void resized() override; + +private: + //============================================================================== + PropertyPanel panel; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericAudioProcessorEditor) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp b/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp new file mode 100644 index 000000000..6d885d722 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp @@ -0,0 +1,151 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +PluginDescription::PluginDescription() + : uid (0), + isInstrument (false), + numInputChannels (0), + numOutputChannels (0), + hasSharedContainer (false) +{ +} + +PluginDescription::~PluginDescription() +{ +} + +PluginDescription::PluginDescription (const PluginDescription& other) + : name (other.name), + descriptiveName (other.descriptiveName), + pluginFormatName (other.pluginFormatName), + category (other.category), + manufacturerName (other.manufacturerName), + version (other.version), + fileOrIdentifier (other.fileOrIdentifier), + lastFileModTime (other.lastFileModTime), + lastInfoUpdateTime (other.lastInfoUpdateTime), + uid (other.uid), + isInstrument (other.isInstrument), + numInputChannels (other.numInputChannels), + numOutputChannels (other.numOutputChannels), + hasSharedContainer (other.hasSharedContainer) +{ +} + +PluginDescription& PluginDescription::operator= (const PluginDescription& other) +{ + name = other.name; + descriptiveName = other.descriptiveName; + pluginFormatName = other.pluginFormatName; + category = other.category; + manufacturerName = other.manufacturerName; + version = other.version; + fileOrIdentifier = other.fileOrIdentifier; + uid = other.uid; + isInstrument = other.isInstrument; + lastFileModTime = other.lastFileModTime; + lastInfoUpdateTime = other.lastInfoUpdateTime; + numInputChannels = other.numInputChannels; + numOutputChannels = other.numOutputChannels; + hasSharedContainer = other.hasSharedContainer; + + return *this; +} + +bool PluginDescription::isDuplicateOf (const PluginDescription& other) const noexcept +{ + return fileOrIdentifier == other.fileOrIdentifier + && uid == other.uid; +} + +static String getPluginDescSuffix (const PluginDescription& d) +{ + return "-" + String::toHexString (d.fileOrIdentifier.hashCode()) + + "-" + String::toHexString (d.uid); +} + +bool PluginDescription::matchesIdentifierString (const String& identifierString) const +{ + return identifierString.endsWithIgnoreCase (getPluginDescSuffix (*this)); +} + +String PluginDescription::createIdentifierString() const +{ + return pluginFormatName + "-" + name + getPluginDescSuffix (*this); +} + +XmlElement* PluginDescription::createXml() const +{ + XmlElement* const e = new XmlElement ("PLUGIN"); + e->setAttribute ("name", name); + if (descriptiveName != name) + e->setAttribute ("descriptiveName", descriptiveName); + + e->setAttribute ("format", pluginFormatName); + e->setAttribute ("category", category); + e->setAttribute ("manufacturer", manufacturerName); + e->setAttribute ("version", version); + e->setAttribute ("file", fileOrIdentifier); + e->setAttribute ("uid", String::toHexString (uid)); + e->setAttribute ("isInstrument", isInstrument); + e->setAttribute ("fileTime", String::toHexString (lastFileModTime.toMilliseconds())); + e->setAttribute ("infoUpdateTime", String::toHexString (lastInfoUpdateTime.toMilliseconds())); + e->setAttribute ("numInputs", numInputChannels); + e->setAttribute ("numOutputs", numOutputChannels); + e->setAttribute ("isShell", hasSharedContainer); + + return e; +} + +bool PluginDescription::loadFromXml (const XmlElement& xml) +{ + if (xml.hasTagName ("PLUGIN")) + { + name = xml.getStringAttribute ("name"); + descriptiveName = xml.getStringAttribute ("descriptiveName", name); + pluginFormatName = xml.getStringAttribute ("format"); + category = xml.getStringAttribute ("category"); + manufacturerName = xml.getStringAttribute ("manufacturer"); + version = xml.getStringAttribute ("version"); + fileOrIdentifier = xml.getStringAttribute ("file"); + uid = xml.getStringAttribute ("uid").getHexValue32(); + isInstrument = xml.getBoolAttribute ("isInstrument", false); + lastFileModTime = Time (xml.getStringAttribute ("fileTime").getHexValue64()); + lastInfoUpdateTime = Time (xml.getStringAttribute ("infoUpdateTime").getHexValue64()); + numInputChannels = xml.getIntAttribute ("numInputs"); + numOutputChannels = xml.getIntAttribute ("numOutputs"); + hasSharedContainer = xml.getBoolAttribute ("isShell", false); + + return true; + } + + return false; +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/processors/juce_PluginDescription.h b/source/modules/juce_audio_processors/processors/juce_PluginDescription.h new file mode 100644 index 000000000..7642545a1 --- /dev/null +++ b/source/modules/juce_audio_processors/processors/juce_PluginDescription.h @@ -0,0 +1,156 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A small class to represent some facts about a particular type of plug-in. + + This class is for storing and managing the details about a plug-in without + actually having to load an instance of it. + + A KnownPluginList contains a list of PluginDescription objects. + + @see KnownPluginList +*/ +class JUCE_API PluginDescription +{ +public: + //============================================================================== + PluginDescription(); + PluginDescription (const PluginDescription& other); + PluginDescription& operator= (const PluginDescription& other); + ~PluginDescription(); + + //============================================================================== + /** The name of the plug-in. */ + String name; + + /** A more descriptive name for the plug-in. + This may be the same as the 'name' field, but some plug-ins may provide an + alternative name. + */ + String descriptiveName; + + /** The plug-in format, e.g. "VST", "AudioUnit", etc. */ + String pluginFormatName; + + /** A category, such as "Dynamics", "Reverbs", etc. */ + String category; + + /** The manufacturer. */ + String manufacturerName; + + /** The version. This string doesn't have any particular format. */ + String version; + + /** Either the file containing the plug-in module, or some other unique way + of identifying it. + + E.g. for an AU, this would be an ID string that the component manager + could use to retrieve the plug-in. For a VST, it's the file path. + */ + String fileOrIdentifier; + + /** The last time the plug-in file was changed. + This is handy when scanning for new or changed plug-ins. + */ + Time lastFileModTime; + + /** The last time that this information was updated. This would typically have + been during a scan when this plugin was first tested or found to have changed. + */ + Time lastInfoUpdateTime; + + /** A unique ID for the plug-in. + + Note that this might not be unique between formats, e.g. a VST and some + other format might actually have the same id. + + @see createIdentifierString + */ + int uid; + + /** True if the plug-in identifies itself as a synthesiser. */ + bool isInstrument; + + /** The number of inputs. */ + int numInputChannels; + + /** The number of outputs. */ + int numOutputChannels; + + /** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */ + bool hasSharedContainer; + + /** Returns true if the two descriptions refer to the same plug-in. + + This isn't quite as simple as them just having the same file (because of + shell plug-ins). + */ + bool isDuplicateOf (const PluginDescription& other) const noexcept; + + /** Return true if this description is equivalent to another one which created the + given identifier string. + + Note that this isn't quite as simple as them just calling createIdentifierString() + and comparing the strings, because the identifiers can differ (thanks to shell plug-ins). + */ + bool matchesIdentifierString (const String& identifierString) const; + + //============================================================================== + /** Returns a string that can be saved and used to uniquely identify the + plugin again. + + This contains less info than the XML encoding, and is independent of the + plug-in's file location, so can be used to store a plug-in ID for use + across different machines. + */ + String createIdentifierString() const; + + //============================================================================== + /** Creates an XML object containing these details. + + @see loadFromXml + */ + XmlElement* createXml() const; + + /** Reloads the info in this structure from an XML record that was previously + saved with createXML(). + + Returns true if the XML was a valid plug-in description. + */ + bool loadFromXml (const XmlElement& xml); + + +private: + //============================================================================== + JUCE_LEAK_DETECTOR (PluginDescription) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp new file mode 100644 index 000000000..f7c0bf71e --- /dev/null +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -0,0 +1,587 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +KnownPluginList::KnownPluginList() {} +KnownPluginList::~KnownPluginList() {} + +void KnownPluginList::clear() +{ + ScopedLock lock (typesArrayLock); + + if (! types.isEmpty()) + { + types.clear(); + sendChangeMessage(); + } +} + +PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const +{ + ScopedLock lock (typesArrayLock); + + for (auto* desc : types) + if (desc->fileOrIdentifier == fileOrIdentifier) + return desc; + + return nullptr; +} + +PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const +{ + ScopedLock lock (typesArrayLock); + + for (auto* desc : types) + if (desc->matchesIdentifierString (identifierString)) + return desc; + + return nullptr; +} + +bool KnownPluginList::addType (const PluginDescription& type) +{ + { + ScopedLock lock (typesArrayLock); + + for (auto* desc : types) + { + if (desc->isDuplicateOf (type)) + { + // strange - found a duplicate plugin with different info.. + jassert (desc->name == type.name); + jassert (desc->isInstrument == type.isInstrument); + + *desc = type; + return false; + } + } + + types.insert (0, new PluginDescription (type)); + } + + sendChangeMessage(); + return true; +} + +void KnownPluginList::removeType (const int index) +{ + { + ScopedLock lock (typesArrayLock); + types.remove (index); + } + + sendChangeMessage(); +} + +bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier, + AudioPluginFormat& formatToUse) const +{ + if (getTypeForFile (fileOrIdentifier) == nullptr) + return false; + + ScopedLock lock (typesArrayLock); + + for (auto* d : types) + if (d->fileOrIdentifier == fileOrIdentifier && formatToUse.pluginNeedsRescanning (*d)) + return false; + + return true; +} + +void KnownPluginList::setCustomScanner (CustomScanner* newScanner) +{ + scanner = newScanner; +} + +bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, + const bool dontRescanIfAlreadyInList, + OwnedArray& typesFound, + AudioPluginFormat& format) +{ + const ScopedLock sl (scanLock); + + if (dontRescanIfAlreadyInList + && getTypeForFile (fileOrIdentifier) != nullptr) + { + bool needsRescanning = false; + + ScopedLock lock (typesArrayLock); + + for (auto* d : types) + { + if (d->fileOrIdentifier == fileOrIdentifier && d->pluginFormatName == format.getName()) + { + if (format.pluginNeedsRescanning (*d)) + needsRescanning = true; + else + typesFound.add (new PluginDescription (*d)); + } + } + + if (! needsRescanning) + return false; + } + + if (blacklist.contains (fileOrIdentifier)) + return false; + + OwnedArray found; + + { + const ScopedUnlock sl2 (scanLock); + + if (scanner != nullptr) + { + if (! scanner->findPluginTypesFor (format, found, fileOrIdentifier)) + addToBlacklist (fileOrIdentifier); + } + else + { + format.findAllTypesForFile (found, fileOrIdentifier); + } + } + + for (auto* desc : found) + { + jassert (desc != nullptr); + addType (*desc); + typesFound.add (new PluginDescription (*desc)); + } + + return ! found.isEmpty(); +} + +void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, + const StringArray& files, + OwnedArray& typesFound) +{ + for (const auto& filenameOrID : files) + { + bool found = false; + + for (int j = 0; j < formatManager.getNumFormats(); ++j) + { + auto* format = formatManager.getFormat (j); + + if (format->fileMightContainThisPluginType (filenameOrID) + && scanAndAddFile (filenameOrID, true, typesFound, *format)) + { + found = true; + break; + } + } + + if (! found) + { + const File f (filenameOrID); + + if (f.isDirectory()) + { + StringArray s; + + { + Array subFiles; + f.findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (auto& subFile : subFiles) + s.add (subFile.getFullPathName()); + } + + scanAndAddDragAndDroppedFiles (formatManager, s, typesFound); + } + } + } + + scanFinished(); +} + +void KnownPluginList::scanFinished() +{ + if (scanner != nullptr) + scanner->scanFinished(); +} + +const StringArray& KnownPluginList::getBlacklistedFiles() const +{ + return blacklist; +} + +void KnownPluginList::addToBlacklist (const String& pluginID) +{ + if (! blacklist.contains (pluginID)) + { + blacklist.add (pluginID); + sendChangeMessage(); + } +} + +void KnownPluginList::removeFromBlacklist (const String& pluginID) +{ + const int index = blacklist.indexOf (pluginID); + + if (index >= 0) + { + blacklist.remove (index); + sendChangeMessage(); + } +} + +void KnownPluginList::clearBlacklistedFiles() +{ + if (blacklist.size() > 0) + { + blacklist.clear(); + sendChangeMessage(); + } +} + +//============================================================================== +struct PluginSorter +{ + PluginSorter (KnownPluginList::SortMethod sortMethod, bool forwards) noexcept + : method (sortMethod), direction (forwards ? 1 : -1) {} + + int compareElements (const PluginDescription* const first, + const PluginDescription* const second) const + { + int diff = 0; + + switch (method) + { + case KnownPluginList::sortByCategory: diff = first->category.compareNatural (second->category, true); break; + case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareNatural (second->manufacturerName, true); break; + case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break; + case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break; + case KnownPluginList::sortByInfoUpdateTime: diff = compare (first->lastInfoUpdateTime, second->lastInfoUpdateTime); break; + default: break; + } + + if (diff == 0) + diff = first->name.compareNatural (second->name, true); + + return diff * direction; + } + +private: + static String lastPathPart (const String& path) + { + return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false); + } + + static int compare (Time a, Time b) noexcept + { + if (a < b) return -1; + if (b < a) return 1; + + return 0; + } + + const KnownPluginList::SortMethod method; + const int direction; + + JUCE_DECLARE_NON_COPYABLE (PluginSorter) +}; + +void KnownPluginList::sort (const SortMethod method, bool forwards) +{ + if (method != defaultOrder) + { + Array oldOrder, newOrder; + + { + ScopedLock lock (typesArrayLock); + + oldOrder.addArray (types); + + PluginSorter sorter (method, forwards); + types.sort (sorter, true); + + newOrder.addArray (types); + } + + if (oldOrder != newOrder) + sendChangeMessage(); + } +} + +//============================================================================== +XmlElement* KnownPluginList::createXml() const +{ + auto e = new XmlElement ("KNOWNPLUGINS"); + + { + ScopedLock lock (typesArrayLock); + + for (int i = types.size(); --i >= 0;) + e->prependChildElement (types.getUnchecked(i)->createXml()); + } + + for (auto& b : blacklist) + e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", b); + + return e; +} + +void KnownPluginList::recreateFromXml (const XmlElement& xml) +{ + clear(); + clearBlacklistedFiles(); + + if (xml.hasTagName ("KNOWNPLUGINS")) + { + forEachXmlChildElement (xml, e) + { + PluginDescription info; + + if (e->hasTagName ("BLACKLISTED")) + blacklist.add (e->getStringAttribute ("id")); + else if (info.loadFromXml (*e)) + addType (info); + } + } +} + +//============================================================================== +struct PluginTreeUtils +{ + enum { menuIdBase = 0x324503f4 }; + + static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array& allPlugins) + { + for (auto* pd : allPlugins) + { + auto path = pd->fileOrIdentifier.replaceCharacter ('\\', '/') + .upToLastOccurrenceOf ("/", false, false); + + if (path.substring (1, 2) == ":") + path = path.substring (2); + + addPlugin (tree, pd, path); + } + + optimiseFolders (tree, false); + } + + static void optimiseFolders (KnownPluginList::PluginTree& tree, bool concatenateName) + { + for (int i = tree.subFolders.size(); --i >= 0;) + { + auto& sub = *tree.subFolders.getUnchecked(i); + optimiseFolders (sub, concatenateName || (tree.subFolders.size() > 1)); + + if (sub.plugins.isEmpty()) + { + for (auto* s : sub.subFolders) + { + if (concatenateName) + s->folder = sub.folder + "/" + s->folder; + + tree.subFolders.add (s); + } + + sub.subFolders.clear (false); + tree.subFolders.remove (i); + } + } + } + + static void buildTreeByCategory (KnownPluginList::PluginTree& tree, + const Array& sorted, + const KnownPluginList::SortMethod sortMethod) + { + String lastType; + ScopedPointer current (new KnownPluginList::PluginTree()); + + for (auto* pd : sorted) + { + auto thisType = (sortMethod == KnownPluginList::sortByCategory ? pd->category + : pd->manufacturerName); + + if (! thisType.containsNonWhitespaceChars()) + thisType = "Other"; + + if (! thisType.equalsIgnoreCase (lastType)) + { + if (current->plugins.size() + current->subFolders.size() > 0) + { + current->folder = lastType; + tree.subFolders.add (current.release()); + current = new KnownPluginList::PluginTree(); + } + + lastType = thisType; + } + + current->plugins.add (pd); + } + + if (current->plugins.size() + current->subFolders.size() > 0) + { + current->folder = lastType; + tree.subFolders.add (current.release()); + } + } + + static void addPlugin (KnownPluginList::PluginTree& tree, PluginDescription* const pd, String path) + { + if (path.isEmpty()) + { + tree.plugins.add (pd); + } + else + { + #if JUCE_MAC + if (path.containsChar (':')) + path = path.fromFirstOccurrenceOf (":", false, false); // avoid the special AU formatting nonsense on Mac.. + #endif + + auto firstSubFolder = path.upToFirstOccurrenceOf ("/", false, false); + auto remainingPath = path.fromFirstOccurrenceOf ("/", false, false); + + for (int i = tree.subFolders.size(); --i >= 0;) + { + KnownPluginList::PluginTree& subFolder = *tree.subFolders.getUnchecked(i); + + if (subFolder.folder.equalsIgnoreCase (firstSubFolder)) + { + addPlugin (subFolder, pd, remainingPath); + return; + } + } + + auto newFolder = new KnownPluginList::PluginTree(); + newFolder->folder = firstSubFolder; + tree.subFolders.add (newFolder); + addPlugin (*newFolder, pd, remainingPath); + } + } + + static bool containsDuplicateNames (const Array& plugins, const String& name) + { + int matches = 0; + + for (int i = 0; i < plugins.size(); ++i) + if (plugins.getUnchecked(i)->name == name) + if (++matches > 1) + return true; + + return false; + } + + static bool addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, + const OwnedArray& allPlugins, + const String& currentlyTickedPluginID) + { + bool isTicked = false; + + for (auto* sub : tree.subFolders) + { + PopupMenu subMenu; + const bool isItemTicked = addToMenu (*sub, subMenu, allPlugins, currentlyTickedPluginID); + isTicked = isTicked || isItemTicked; + + m.addSubMenu (sub->folder, subMenu, true, nullptr, isItemTicked, 0); + } + + for (auto* plugin : tree.plugins) + { + auto name = plugin->name; + + if (containsDuplicateNames (tree.plugins, name)) + name << " (" << plugin->pluginFormatName << ')'; + + const bool isItemTicked = plugin->matchesIdentifierString (currentlyTickedPluginID); + isTicked = isTicked || isItemTicked; + + m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, isItemTicked); + } + + return isTicked; + } +}; + +KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const +{ + Array sorted; + + { + ScopedLock lock (typesArrayLock); + PluginSorter sorter (sortMethod, true); + + for (auto* t : types) + sorted.addSorted (sorter, t); + } + + auto* tree = new PluginTree(); + + if (sortMethod == sortByCategory || sortMethod == sortByManufacturer || sortMethod == sortByFormat) + { + PluginTreeUtils::buildTreeByCategory (*tree, sorted, sortMethod); + } + else if (sortMethod == sortByFileSystemLocation) + { + PluginTreeUtils::buildTreeByFolder (*tree, sorted); + } + else + { + for (auto* p : sorted) + tree->plugins.add (p); + } + + return tree; +} + +//============================================================================== +void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod, + const String& currentlyTickedPluginID) const +{ + ScopedPointer tree (createTree (sortMethod)); + PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID); +} + +int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const +{ + const int i = menuResultCode - PluginTreeUtils::menuIdBase; + return isPositiveAndBelow (i, types.size()) ? i : -1; +} + +//============================================================================== +KnownPluginList::CustomScanner::CustomScanner() {} +KnownPluginList::CustomScanner::~CustomScanner() {} + +void KnownPluginList::CustomScanner::scanFinished() {} + +bool KnownPluginList::CustomScanner::shouldExit() const noexcept +{ + if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob()) + return job->shouldExit(); + + return false; +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h new file mode 100644 index 000000000..17ce28fb3 --- /dev/null +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h @@ -0,0 +1,225 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Manages a list of plugin types. + + This can be easily edited, saved and loaded, and used to create instances of + the plugin types in it. + + @see PluginListComponent +*/ +class JUCE_API KnownPluginList : public ChangeBroadcaster +{ +public: + //============================================================================== + /** Creates an empty list. */ + KnownPluginList(); + + /** Destructor. */ + ~KnownPluginList(); + + //============================================================================== + /** Clears the list. */ + void clear(); + + /** Returns the number of types currently in the list. + @see getType + */ + int getNumTypes() const noexcept { return types.size(); } + + /** Returns one of the types. + @see getNumTypes + */ + PluginDescription* getType (int index) const noexcept { return types [index]; } + + /** Type iteration. */ + PluginDescription** begin() const noexcept { return types.begin(); } + /** Type iteration. */ + PluginDescription** end() const noexcept { return types.end(); } + + /** Looks for a type in the list which comes from this file. */ + PluginDescription* getTypeForFile (const String& fileOrIdentifier) const; + + /** Looks for a type in the list which matches a plugin type ID. + + The identifierString parameter must have been created by + PluginDescription::createIdentifierString(). + */ + PluginDescription* getTypeForIdentifierString (const String& identifierString) const; + + /** Adds a type manually from its description. */ + bool addType (const PluginDescription& type); + + /** Removes a type. */ + void removeType (int index); + + /** Looks for all types that can be loaded from a given file, and adds them + to the list. + + If dontRescanIfAlreadyInList is true, then the file will only be loaded and + re-tested if it's not already in the list, or if the file's modification + time has changed since the list was created. If dontRescanIfAlreadyInList is + false, the file will always be reloaded and tested. + + Returns true if any new types were added, and all the types found in this + file (even if it was already known and hasn't been re-scanned) get returned + in the array. + */ + bool scanAndAddFile (const String& possiblePluginFileOrIdentifier, + bool dontRescanIfAlreadyInList, + OwnedArray & typesFound, + AudioPluginFormat& formatToUse); + + /** Tells a custom scanner that a scan has finished, and it can release any resources. */ + void scanFinished(); + + /** Returns true if the specified file is already known about and if it + hasn't been modified since our entry was created. + */ + bool isListingUpToDate (const String& possiblePluginFileOrIdentifier, + AudioPluginFormat& formatToUse) const; + + /** Scans and adds a bunch of files that might have been dragged-and-dropped. + If any types are found in the files, their descriptions are returned in the array. + */ + void scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, + const StringArray& filenames, + OwnedArray & typesFound); + + //============================================================================== + /** Returns the list of blacklisted files. */ + const StringArray& getBlacklistedFiles() const; + + /** Adds a plugin ID to the black-list. */ + void addToBlacklist (const String& pluginID); + + /** Removes a plugin ID from the black-list. */ + void removeFromBlacklist (const String& pluginID); + + /** Clears all the blacklisted files. */ + void clearBlacklistedFiles(); + + //============================================================================== + /** Sort methods used to change the order of the plugins in the list. + */ + enum SortMethod + { + defaultOrder = 0, + sortAlphabetically, + sortByCategory, + sortByManufacturer, + sortByFormat, + sortByFileSystemLocation, + sortByInfoUpdateTime + }; + + //============================================================================== + /** Adds all the plugin types to a popup menu so that the user can select one. + + Depending on the sort method, it may add sub-menus for categories, + manufacturers, etc. + + Use getIndexChosenByMenu() to find out the type that was chosen. + */ + void addToMenu (PopupMenu& menu, SortMethod sortMethod, + const String& currentlyTickedPluginID = String()) const; + + /** Converts a menu item index that has been chosen into its index in this list. + Returns -1 if it's not an ID that was used. + @see addToMenu + */ + int getIndexChosenByMenu (int menuResultCode) const; + + //============================================================================== + /** Sorts the list. */ + void sort (SortMethod method, bool forwards); + + //============================================================================== + /** Creates some XML that can be used to store the state of this list. */ + XmlElement* createXml() const; + + /** Recreates the state of this list from its stored XML format. */ + void recreateFromXml (const XmlElement& xml); + + //============================================================================== + /** A structure that recursively holds a tree of plugins. + @see KnownPluginList::createTree() + */ + struct PluginTree + { + String folder; /**< The name of this folder in the tree */ + OwnedArray subFolders; + Array plugins; + }; + + /** Creates a PluginTree object containing all the known plugins. */ + PluginTree* createTree (const SortMethod sortMethod) const; + + //============================================================================== + class CustomScanner + { + public: + CustomScanner(); + virtual ~CustomScanner(); + + /** Attempts to load the given file and find a list of plugins in it. + @returns true if the plugin loaded, false if it crashed + */ + virtual bool findPluginTypesFor (AudioPluginFormat& format, + OwnedArray & result, + const String& fileOrIdentifier) = 0; + + /** Called when a scan has finished, to allow clean-up of resources. */ + virtual void scanFinished(); + + /** Returns true if the current scan should be abandoned. + Any blocking methods should check this value repeatedly and return if + if becomes true. + */ + bool shouldExit() const noexcept; + }; + + /** Supplies a custom scanner to be used in future scans. + The KnownPluginList will take ownership of the object passed in. + */ + void setCustomScanner (CustomScanner*); + +private: + //============================================================================== + OwnedArray types; + StringArray blacklist; + ScopedPointer scanner; + CriticalSection scanLock, typesArrayLock; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp new file mode 100644 index 000000000..1c68861b6 --- /dev/null +++ b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp @@ -0,0 +1,136 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +static StringArray readDeadMansPedalFile (const File& file) +{ + StringArray lines; + file.readLines (lines); + lines.removeEmptyStrings(); + return lines; +} + +PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, + AudioPluginFormat& formatToLookFor, + FileSearchPath directoriesToSearch, + const bool recursive, + const File& deadMansPedal, + bool allowPluginsWhichRequireAsynchronousInstantiation) + : list (listToAddTo), + format (formatToLookFor), + deadMansPedalFile (deadMansPedal), + allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) +{ + directoriesToSearch.removeRedundantPaths(); + + filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive, allowAsync); + + // If any plugins have crashed recently when being loaded, move them to the + // end of the list to give the others a chance to load correctly.. + for (auto& crashed : readDeadMansPedalFile (deadMansPedalFile)) + for (int j = filesOrIdentifiersToScan.size(); --j >= 0;) + if (crashed == filesOrIdentifiersToScan[j]) + filesOrIdentifiersToScan.move (j, -1); + + applyBlacklistingsFromDeadMansPedal (listToAddTo, deadMansPedalFile); + nextIndex.set (filesOrIdentifiersToScan.size()); +} + +PluginDirectoryScanner::~PluginDirectoryScanner() +{ + list.scanFinished(); +} + +//============================================================================== +String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const +{ + return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex.get() - 1]); +} + +void PluginDirectoryScanner::updateProgress() +{ + progress = (1.0f - nextIndex.get() / (float) filesOrIdentifiersToScan.size()); +} + +bool PluginDirectoryScanner::scanNextFile (bool dontRescanIfAlreadyInList, + String& nameOfPluginBeingScanned) +{ + const int index = --nextIndex; + + if (index >= 0) + { + auto file = filesOrIdentifiersToScan [index]; + + if (file.isNotEmpty() && ! (dontRescanIfAlreadyInList && list.isListingUpToDate (file, format))) + { + nameOfPluginBeingScanned = format.getNameOfPluginFromIdentifier (file); + + OwnedArray typesFound; + + // Add this plugin to the end of the dead-man's pedal list in case it crashes... + auto crashedPlugins = readDeadMansPedalFile (deadMansPedalFile); + crashedPlugins.removeString (file); + crashedPlugins.add (file); + setDeadMansPedalFile (crashedPlugins); + + list.scanAndAddFile (file, dontRescanIfAlreadyInList, typesFound, format); + + // Managed to load without crashing, so remove it from the dead-man's-pedal.. + crashedPlugins.removeString (file); + setDeadMansPedalFile (crashedPlugins); + + if (typesFound.size() == 0 && ! list.getBlacklistedFiles().contains (file)) + failedFiles.add (file); + } + } + + updateProgress(); + return index > 0; +} + +bool PluginDirectoryScanner::skipNextFile() +{ + updateProgress(); + return --nextIndex > 0; +} + +void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents) +{ + if (deadMansPedalFile.getFullPathName().isNotEmpty()) + deadMansPedalFile.replaceWithText (newContents.joinIntoString ("\n"), true, true); +} + +void PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (KnownPluginList& list, const File& file) +{ + // If any plugins have crashed recently when being loaded, move them to the + // end of the list to give the others a chance to load correctly.. + for (auto& crashedPlugin : readDeadMansPedalFile (file)) + list.addToBlacklist (crashedPlugin); +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h new file mode 100644 index 000000000..200835a1e --- /dev/null +++ b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h @@ -0,0 +1,131 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Scans a directory for plugins, and adds them to a KnownPluginList. + + To use one of these, create it and call scanNextFile() repeatedly, until + it returns false. +*/ +class JUCE_API PluginDirectoryScanner +{ +public: + //============================================================================== + /** + Creates a scanner. + + @param listToAddResultsTo this will get the new types added to it. + @param formatToLookFor this is the type of format that you want to look for + @param directoriesToSearch the path to search + @param searchRecursively true to search recursively + @param deadMansPedalFile if this isn't File(), then it will be used as a file + to store the names of any plugins that crash during + initialisation. If there are any plugins listed in it, + then these will always be scanned after all other possible + files have been tried - in this way, even if there's a few + dodgy plugins in your path, then a couple of rescans + will still manage to find all the proper plugins. + It's probably best to choose a file in the user's + application data directory (alongside your app's + settings file) for this. The file format it uses + is just a list of filenames of the modules that + failed. + @param allowPluginsWhichRequireAsynchronousInstantiation + If this is false then the scanner will exclude plug-ins + asynchronous creation - such as AUv3 plug-ins. + */ + PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, + AudioPluginFormat& formatToLookFor, + FileSearchPath directoriesToSearch, + bool searchRecursively, + const File& deadMansPedalFile, + bool allowPluginsWhichRequireAsynchronousInstantiation = false); + + /** Destructor. */ + ~PluginDirectoryScanner(); + + //============================================================================== + /** Tries the next likely-looking file. + + If dontRescanIfAlreadyInList is true, then the file will only be loaded and + re-tested if it's not already in the list, or if the file's modification + time has changed since the list was created. If dontRescanIfAlreadyInList is + false, the file will always be reloaded and tested. + The nameOfPluginBeingScanned will be updated to the name of the plugin being + scanned before the scan starts. + + Returns false when there are no more files to try. + */ + bool scanNextFile (bool dontRescanIfAlreadyInList, + String& nameOfPluginBeingScanned); + + /** Skips over the next file without scanning it. + Returns false when there are no more files to try. + */ + bool skipNextFile(); + + /** Returns the description of the plugin that will be scanned during the next + call to scanNextFile(). + + This is handy if you want to show the user which file is currently getting + scanned. + */ + String getNextPluginFileThatWillBeScanned() const; + + /** Returns the estimated progress, between 0 and 1. */ + float getProgress() const { return progress; } + + /** This returns a list of all the filenames of things that looked like being + a plugin file, but which failed to open for some reason. + */ + const StringArray& getFailedFiles() const noexcept { return failedFiles; } + + /** Reads the given dead-mans-pedal file and applies its contents to the list. */ + static void applyBlacklistingsFromDeadMansPedal (KnownPluginList& listToApplyTo, + const File& deadMansPedalFile); + +private: + //============================================================================== + KnownPluginList& list; + AudioPluginFormat& format; + StringArray filesOrIdentifiersToScan; + File deadMansPedalFile; + StringArray failedFiles; + Atomic nextIndex; + float progress = 0; + const bool allowAsync; + + void updateProgress(); + void setDeadMansPedalFile (const StringArray& newContents); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginDirectoryScanner) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp new file mode 100644 index 000000000..733842ffb --- /dev/null +++ b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -0,0 +1,593 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +class PluginListComponent::TableModel : public TableListBoxModel +{ +public: + TableModel (PluginListComponent& c, KnownPluginList& l) : owner (c), list (l) {} + + int getNumRows() override + { + return list.getNumTypes() + list.getBlacklistedFiles().size(); + } + + void paintRowBackground (Graphics& g, int /*rowNumber*/, int /*width*/, int /*height*/, bool rowIsSelected) override + { + const auto defaultColour = owner.findColour (ListBox::backgroundColourId); + const auto c = rowIsSelected ? defaultColour.interpolatedWith (owner.findColour (ListBox::textColourId), 0.5f) + : defaultColour; + + g.fillAll (c); + } + + enum + { + nameCol = 1, + typeCol = 2, + categoryCol = 3, + manufacturerCol = 4, + descCol = 5 + }; + + void paintCell (Graphics& g, int row, int columnId, int width, int height, bool /*rowIsSelected*/) override + { + String text; + bool isBlacklisted = row >= list.getNumTypes(); + + if (isBlacklisted) + { + if (columnId == nameCol) + text = list.getBlacklistedFiles() [row - list.getNumTypes()]; + else if (columnId == descCol) + text = TRANS("Deactivated after failing to initialise correctly"); + } + else if (const PluginDescription* const desc = list.getType (row)) + { + switch (columnId) + { + case nameCol: text = desc->name; break; + case typeCol: text = desc->pluginFormatName; break; + case categoryCol: text = desc->category.isNotEmpty() ? desc->category : "-"; break; + case manufacturerCol: text = desc->manufacturerName; break; + case descCol: text = getPluginDescription (*desc); break; + + default: jassertfalse; break; + } + } + + if (text.isNotEmpty()) + { + const auto defaultTextColour = owner.findColour (ListBox::textColourId); + g.setColour (isBlacklisted ? Colours::red + : columnId == nameCol ? defaultTextColour + : defaultTextColour.interpolatedWith (Colours::transparentBlack, 0.3f)); + g.setFont (Font (height * 0.7f, Font::bold)); + g.drawFittedText (text, 4, 0, width - 6, height, Justification::centredLeft, 1, 0.9f); + } + } + + void deleteKeyPressed (int) override + { + owner.removeSelectedPlugins(); + } + + void sortOrderChanged (int newSortColumnId, bool isForwards) override + { + switch (newSortColumnId) + { + case nameCol: list.sort (KnownPluginList::sortAlphabetically, isForwards); break; + case typeCol: list.sort (KnownPluginList::sortByFormat, isForwards); break; + case categoryCol: list.sort (KnownPluginList::sortByCategory, isForwards); break; + case manufacturerCol: list.sort (KnownPluginList::sortByManufacturer, isForwards); break; + case descCol: break; + + default: jassertfalse; break; + } + } + + static String getPluginDescription (const PluginDescription& desc) + { + StringArray items; + + if (desc.descriptiveName != desc.name) + items.add (desc.descriptiveName); + + items.add (desc.version); + + items.removeEmptyStrings(); + return items.joinIntoString (" - "); + } + + PluginListComponent& owner; + KnownPluginList& list; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableModel) +}; + +//============================================================================== +PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, + const File& deadMansPedal, PropertiesFile* const props, + bool allowPluginsWhichRequireAsynchronousInstantiation) + : formatManager (manager), + list (listToEdit), + deadMansPedalFile (deadMansPedal), + optionsButton ("Options..."), + propertiesToUse (props), + allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), + numThreads (allowAsync ? 1 : 0) +{ + tableModel = new TableModel (*this, listToEdit); + + TableHeaderComponent& header = table.getHeader(); + + header.addColumn (TRANS("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards); + header.addColumn (TRANS("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable); + header.addColumn (TRANS("Category"), TableModel::categoryCol, 100, 100, 200); + header.addColumn (TRANS("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300); + header.addColumn (TRANS("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable); + + table.setHeaderHeight (22); + table.setRowHeight (20); + table.setModel (tableModel); + table.setMultipleSelectionEnabled (true); + addAndMakeVisible (table); + + addAndMakeVisible (optionsButton); + optionsButton.addListener (this); + optionsButton.setTriggeredOnMouseDown (true); + + setSize (400, 600); + list.addChangeListener (this); + updateList(); + table.getHeader().reSortTable(); + + PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile); + deadMansPedalFile.deleteFile(); +} + +PluginListComponent::~PluginListComponent() +{ + list.removeChangeListener (this); +} + +void PluginListComponent::setOptionsButtonText (const String& newText) +{ + optionsButton.setButtonText (newText); + resized(); +} + +void PluginListComponent::setScanDialogText (const String& title, const String& content) +{ + dialogTitle = title; + dialogText = content; +} + +void PluginListComponent::setNumberOfThreadsForScanning (int num) +{ + numThreads = num; +} + +void PluginListComponent::resized() +{ + Rectangle r (getLocalBounds().reduced (2)); + + optionsButton.setBounds (r.removeFromBottom (24)); + optionsButton.changeWidthToFitText (24); + + r.removeFromBottom (3); + table.setBounds (r); +} + +void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) +{ + table.getHeader().reSortTable(); + updateList(); +} + +void PluginListComponent::updateList() +{ + table.updateContent(); + table.repaint(); +} + +void PluginListComponent::removeSelectedPlugins() +{ + const SparseSet selected (table.getSelectedRows()); + + for (int i = table.getNumRows(); --i >= 0;) + if (selected.contains (i)) + removePluginItem (i); +} + +void PluginListComponent::setTableModel (TableListBoxModel* model) +{ + table.setModel (nullptr); + tableModel = model; + table.setModel (tableModel); + + table.getHeader().reSortTable(); + table.updateContent(); + table.repaint(); +} + +bool PluginListComponent::canShowSelectedFolder() const +{ + if (const PluginDescription* const desc = list.getType (table.getSelectedRow())) + return File::createFileWithoutCheckingPath (desc->fileOrIdentifier).exists(); + + return false; +} + +void PluginListComponent::showSelectedFolder() +{ + if (canShowSelectedFolder()) + if (const PluginDescription* const desc = list.getType (table.getSelectedRow())) + File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); +} + +void PluginListComponent::removeMissingPlugins() +{ + for (int i = list.getNumTypes(); --i >= 0;) + if (! formatManager.doesPluginStillExist (*list.getType (i))) + list.removeType (i); +} + +void PluginListComponent::removePluginItem (int index) +{ + if (index < list.getNumTypes()) + list.removeType (index); + else + list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]); +} + +void PluginListComponent::optionsMenuStaticCallback (int result, PluginListComponent* pluginList) +{ + if (pluginList != nullptr) + pluginList->optionsMenuCallback (result); +} + +void PluginListComponent::optionsMenuCallback (int result) +{ + switch (result) + { + case 0: break; + case 1: list.clear(); break; + case 2: removeSelectedPlugins(); break; + case 3: showSelectedFolder(); break; + case 4: removeMissingPlugins(); break; + + default: + if (AudioPluginFormat* format = formatManager.getFormat (result - 10)) + scanFor (*format); + + break; + } +} + +void PluginListComponent::buttonClicked (Button* button) +{ + if (button == &optionsButton) + { + PopupMenu menu; + menu.addItem (1, TRANS("Clear list")); + menu.addItem (2, TRANS("Remove selected plug-in from list"), table.getNumSelectedRows() > 0); + menu.addItem (3, TRANS("Show folder containing selected plug-in"), canShowSelectedFolder()); + menu.addItem (4, TRANS("Remove any plug-ins whose files no longer exist")); + menu.addSeparator(); + + for (int i = 0; i < formatManager.getNumFormats(); ++i) + { + AudioPluginFormat* const format = formatManager.getFormat (i); + + if (format->canScanForPlugins()) + menu.addItem (10 + i, "Scan for new or updated " + format->getName() + " plug-ins"); + } + + menu.showMenuAsync (PopupMenu::Options().withTargetComponent (&optionsButton), + ModalCallbackFunction::forComponent (optionsMenuStaticCallback, this)); + } +} + +bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/) +{ + return true; +} + +void PluginListComponent::filesDropped (const StringArray& files, int, int) +{ + OwnedArray typesFound; + list.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound); +} + +FileSearchPath PluginListComponent::getLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format) +{ + return FileSearchPath (properties.getValue ("lastPluginScanPath_" + format.getName(), + format.getDefaultLocationsToSearch().toString())); +} + +void PluginListComponent::setLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format, + const FileSearchPath& newPath) +{ + properties.setValue ("lastPluginScanPath_" + format.getName(), newPath.toString()); +} + +//============================================================================== +class PluginListComponent::Scanner : private Timer +{ +public: + Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, + bool allowPluginsWhichRequireAsynchronousInstantiation, int threads, + const String& title, const String& text) + : owner (plc), formatToScan (format), propertiesToUse (properties), + pathChooserWindow (TRANS("Select folders to scan..."), String(), AlertWindow::NoIcon), + progressWindow (title, text, AlertWindow::NoIcon), + progress (0.0), numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), + finished (false) + { + FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); + + // You need to use at least one thread when scanning plug-ins asynchronously + jassert (! allowAsync || (numThreads > 0)); + + if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. + { + #if ! JUCE_IOS + if (propertiesToUse != nullptr) + path = getLastSearchPath (*propertiesToUse, formatToScan); + #endif + + pathList.setSize (500, 300); + pathList.setPath (path); + + pathChooserWindow.addCustomComponent (&pathList); + pathChooserWindow.addButton (TRANS("Scan"), 1, KeyPress (KeyPress::returnKey)); + pathChooserWindow.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + + pathChooserWindow.enterModalState (true, + ModalCallbackFunction::forComponent (startScanCallback, + &pathChooserWindow, this), + false); + } + else + { + startScan(); + } + } + + ~Scanner() + { + if (pool != nullptr) + { + pool->removeAllJobs (true, 60000); + pool = nullptr; + } + } + +private: + PluginListComponent& owner; + AudioPluginFormat& formatToScan; + PropertiesFile* propertiesToUse; + ScopedPointer scanner; + AlertWindow pathChooserWindow, progressWindow; + FileSearchPathListComponent pathList; + String pluginBeingScanned; + double progress; + int numThreads; + bool allowAsync, finished; + ScopedPointer pool; + + static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) + { + if (alert != nullptr && scanner != nullptr) + { + if (result != 0) + scanner->warnUserAboutStupidPaths(); + else + scanner->finishedScan(); + } + } + + // Try to dissuade people from to scanning their entire C: drive, or other system folders. + void warnUserAboutStupidPaths() + { + for (int i = 0; i < pathList.getPath().getNumPaths(); ++i) + { + const File f (pathList.getPath()[i]); + + if (isStupidPath (f)) + { + AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, + TRANS("Plugin Scanning"), + TRANS("If you choose to scan folders that contain non-plugin files, " + "then scanning may take a long time, and can cause crashes when " + "attempting to load unsuitable files.") + + newLine + + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") + .replace ("XYZ", f.getFullPathName()), + TRANS ("Scan"), + String(), + nullptr, + ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); + return; + } + } + + startScan(); + } + + static bool isStupidPath (const File& f) + { + Array roots; + File::findFileSystemRoots (roots); + + if (roots.contains (f)) + return true; + + File::SpecialLocationType pathsThatWouldBeStupidToScan[] + = { File::globalApplicationsDirectory, + File::userHomeDirectory, + File::userDocumentsDirectory, + File::userDesktopDirectory, + File::tempDirectory, + File::userMusicDirectory, + File::userMoviesDirectory, + File::userPicturesDirectory }; + + for (int i = 0; i < numElementsInArray (pathsThatWouldBeStupidToScan); ++i) + { + const File sillyFolder (File::getSpecialLocation (pathsThatWouldBeStupidToScan[i])); + + if (f == sillyFolder || sillyFolder.isAChildOf (f)) + return true; + } + + return false; + } + + static void warnAboutStupidPathsCallback (int result, Scanner* scanner) + { + if (result != 0) + scanner->startScan(); + else + scanner->finishedScan(); + } + + void startScan() + { + pathChooserWindow.setVisible (false); + + scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), + true, owner.deadMansPedalFile, allowAsync); + + if (propertiesToUse != nullptr) + { + setLastSearchPath (*propertiesToUse, formatToScan, pathList.getPath()); + propertiesToUse->saveIfNeeded(); + } + + progressWindow.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + progressWindow.addProgressBarComponent (progress); + progressWindow.enterModalState(); + + if (numThreads > 0) + { + pool = new ThreadPool (numThreads); + + for (int i = numThreads; --i >= 0;) + pool->addJob (new ScanJob (*this), true); + } + + startTimer (20); + } + + void finishedScan() + { + owner.scanFinished (scanner != nullptr ? scanner->getFailedFiles() + : StringArray()); + } + + void timerCallback() override + { + if (pool == nullptr) + { + if (doNextScan()) + startTimer (20); + } + + if (! progressWindow.isCurrentlyModal()) + finished = true; + + if (finished) + finishedScan(); + else + progressWindow.setMessage (TRANS("Testing") + ":\n\n" + pluginBeingScanned); + } + + bool doNextScan() + { + if (scanner->scanNextFile (true, pluginBeingScanned)) + { + progress = scanner->getProgress(); + return true; + } + + finished = true; + return false; + } + + struct ScanJob : public ThreadPoolJob + { + ScanJob (Scanner& s) : ThreadPoolJob ("pluginscan"), scanner (s) {} + + JobStatus runJob() + { + while (scanner.doNextScan() && ! shouldExit()) + {} + + return jobHasFinished; + } + + Scanner& scanner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScanJob) + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Scanner) +}; + +void PluginListComponent::scanFor (AudioPluginFormat& format) +{ + currentScanner = new Scanner (*this, format, propertiesToUse, allowAsync, numThreads, + dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), + dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); +} + +bool PluginListComponent::isScanning() const noexcept +{ + return currentScanner != nullptr; +} + +void PluginListComponent::scanFinished (const StringArray& failedFiles) +{ + StringArray shortNames; + + for (int i = 0; i < failedFiles.size(); ++i) + shortNames.add (File::createFileWithoutCheckingPath (failedFiles[i]).getFileName()); + + currentScanner = nullptr; // mustn't delete this before using the failed files array + + if (shortNames.size() > 0) + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, + TRANS("Scan complete"), + TRANS("Note that the following files appeared to be plugin files, but failed to load correctly") + + ":\n\n" + + shortNames.joinIntoString (", ")); +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h new file mode 100644 index 000000000..df3ec367c --- /dev/null +++ b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h @@ -0,0 +1,132 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A component displaying a list of plugins, with options to scan for them, + add, remove and sort them. +*/ +class JUCE_API PluginListComponent : public Component, + public FileDragAndDropTarget, + private ChangeListener, + private Button::Listener +{ +public: + //============================================================================== + /** + Creates the list component. + + For info about the deadMansPedalFile, see the PluginDirectoryScanner constructor. + The properties file, if supplied, is used to store the user's last search paths. + */ + PluginListComponent (AudioPluginFormatManager& formatManager, + KnownPluginList& listToRepresent, + const File& deadMansPedalFile, + PropertiesFile* propertiesToUse, + bool allowPluginsWhichRequireAsynchronousInstantiation = false); + + /** Destructor. */ + ~PluginListComponent(); + + /** Changes the text in the panel's options button. */ + void setOptionsButtonText (const String& newText); + + /** Changes the text in the progress dialog box that is shown when scanning. */ + void setScanDialogText (const String& textForProgressWindowTitle, + const String& textForProgressWindowDescription); + + /** Sets how many threads to simultaneously scan for plugins. + If this is 0, then all scanning happens on the message thread (this is the default when + allowPluginsWhichRequireAsynchronousInstantiation is false). If + allowPluginsWhichRequireAsynchronousInstantiation is true then numThreads must not + be zero (it is one by default). */ + void setNumberOfThreadsForScanning (int numThreads); + + /** Returns the last search path stored in a given properties file for the specified format. */ + static FileSearchPath getLastSearchPath (PropertiesFile&, AudioPluginFormat&); + + /** Stores a search path in a properties file for the given format. */ + static void setLastSearchPath (PropertiesFile&, AudioPluginFormat&, const FileSearchPath&); + + /** Triggers an asynchronous scan for the given format. */ + void scanFor (AudioPluginFormat&); + + /** Returns true if there's currently a scan in progress. */ + bool isScanning() const noexcept; + + /** Removes the plugins currently selected in the table. */ + void removeSelectedPlugins(); + + /** Sets a custom table model to be used. + This will take ownership of the model and delete it when no longer needed. + */ + void setTableModel (TableListBoxModel* model); + + /** Returns the table used to display the plugin list. */ + TableListBox& getTableListBox() noexcept { return table; } + +private: + //============================================================================== + AudioPluginFormatManager& formatManager; + KnownPluginList& list; + File deadMansPedalFile; + TableListBox table; + TextButton optionsButton; + PropertiesFile* propertiesToUse; + String dialogTitle, dialogText; + bool allowAsync; + int numThreads; + + class TableModel; + ScopedPointer tableModel; + + class Scanner; + friend class Scanner; + friend struct ContainerDeletePolicy; + ScopedPointer currentScanner; + + void scanFinished (const StringArray&); + static void optionsMenuStaticCallback (int, PluginListComponent*); + void optionsMenuCallback (int); + void updateList(); + void showSelectedFolder(); + bool canShowSelectedFolder() const; + void removeMissingPlugins(); + void removePluginItem (int index); + + void resized() override; + bool isInterestedInFileDrag (const StringArray&) override; + void filesDropped (const StringArray&, int, int) override; + void buttonClicked (Button*) override; + void changeListenerCallback (ChangeBroadcaster*) override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListComponent) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h new file mode 100644 index 000000000..c664ce801 --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h @@ -0,0 +1,71 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Provides a class of AudioProcessorParameter that can be used as a boolean value. + + @see AudioParameterFloat, AudioParameterInt, AudioParameterChoice +*/ +class JUCE_API AudioParameterBool : public AudioProcessorParameterWithID +{ +public: + /** Creates a AudioParameterBool with an ID and name. + On creation, its value is set to the default value. + */ + AudioParameterBool (const String& parameterID, const String& name, bool defaultValue, + const String& label = String()); + + /** Destructor. */ + ~AudioParameterBool(); + + /** Returns the parameter's current boolean value. */ + bool get() const noexcept { return value >= 0.5f; } + /** Returns the parameter's current boolean value. */ + operator bool() const noexcept { return get(); } + + /** Changes the parameter's current value to a new boolean. */ + AudioParameterBool& operator= (bool newValue); + + +private: + //============================================================================== + float value, defaultValue; + + float getValue() const override; + void setValue (float newValue) override; + float getDefaultValue() const override; + int getNumSteps() const override; + bool isDiscrete() const override; + String getText (float, int) const override; + float getValueForText (const String&) const override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterBool) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h new file mode 100644 index 000000000..dc711d583 --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h @@ -0,0 +1,86 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Provides a class of AudioProcessorParameter that can be used to select + an indexed, named choice from a list. + + @see AudioParameterFloat, AudioParameterInt, AudioParameterBool +*/ +class JUCE_API AudioParameterChoice : public AudioProcessorParameterWithID +{ +public: + /** Creates a AudioParameterChoice with an ID, name, and list of items. + On creation, its value is set to the default index. + */ + AudioParameterChoice (const String& parameterID, const String& name, + const StringArray& choices, + int defaultItemIndex, + const String& label = String()); + + /** Destructor. */ + ~AudioParameterChoice(); + + /** Returns the current index of the selected item. */ + int getIndex() const noexcept { return roundToInt (value); } + /** Returns the current index of the selected item. */ + operator int() const noexcept { return getIndex(); } + + /** Returns the name of the currently selected item. */ + String getCurrentChoiceName() const noexcept { return choices[getIndex()]; } + /** Returns the name of the currently selected item. */ + operator String() const noexcept { return getCurrentChoiceName(); } + + /** Changes the selected item to a new index. */ + AudioParameterChoice& operator= (int newValue); + + /** Provides access to the list of choices that this parameter is working with. */ + const StringArray choices; + + +private: + //============================================================================== + float value, defaultValue; + + float getValue() const override; + void setValue (float newValue) override; + float getDefaultValue() const override; + int getNumSteps() const override; + bool isDiscrete() const override; + String getText (float, int) const override; + float getValueForText (const String&) const override; + + int limitRange (int) const noexcept; + float convertTo0to1 (int) const noexcept; + int convertFrom0to1 (float) const noexcept; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterChoice) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h new file mode 100644 index 000000000..c422749ed --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h @@ -0,0 +1,87 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +/** + A subclass of AudioProcessorParameter that provides an easy way to create a + parameter which maps onto a given NormalisableRange. + + @see AudioParameterInt, AudioParameterBool, AudioParameterChoice +*/ +class JUCE_API AudioParameterFloat : public AudioProcessorParameterWithID +{ +public: + /** Creates a AudioParameterFloat with an ID, name, and range. + On creation, its value is set to the default value. + */ + AudioParameterFloat (const String& parameterID, const String& name, + NormalisableRange normalisableRange, + float defaultValue, + const String& label = String(), + Category category = AudioProcessorParameter::genericParameter); + + /** Creates a AudioParameterFloat with an ID, name, and range. + On creation, its value is set to the default value. + For control over skew factors, you can use the other + constructor and provide a NormalisableRange. + */ + AudioParameterFloat (String parameterID, String name, + float minValue, + float maxValue, + float defaultValue); + + /** Destructor. */ + ~AudioParameterFloat(); + + /** Returns the parameter's current value. */ + float get() const noexcept { return value; } + /** Returns the parameter's current value. */ + operator float() const noexcept { return value; } + + /** Changes the parameter's current value. */ + AudioParameterFloat& operator= (float newValue); + + /** Provides access to the parameter's range. */ + NormalisableRange range; + + +private: + //============================================================================== + float value, defaultValue; + + float getValue() const override; + void setValue (float newValue) override; + float getDefaultValue() const override; + int getNumSteps() const override; + String getText (float, int) const override; + float getValueForText (const String&) const override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterFloat) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h new file mode 100644 index 000000000..4c2f02c33 --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h @@ -0,0 +1,84 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Provides a class of AudioProcessorParameter that can be used as an + integer value with a given range. + + @see AudioParameterFloat, AudioParameterBool, AudioParameterChoice +*/ +class JUCE_API AudioParameterInt : public AudioProcessorParameterWithID +{ +public: + /** Creates an AudioParameterInt with an ID, name, and range. + Note that the min and max range values are inclusive. + On creation, its value is set to the default value. + */ + AudioParameterInt (const String& parameterID, const String& name, + int minValue, int maxValue, + int defaultValue, + const String& label = String()); + + /** Destructor. */ + ~AudioParameterInt(); + + /** Returns the parameter's current value as an integer. */ + int get() const noexcept { return roundToInt (value); } + /** Returns the parameter's current value as an integer. */ + operator int() const noexcept { return get(); } + + /** Changes the parameter's current value to a new integer. + The value passed in will be snapped to the permitted range before being used. + */ + AudioParameterInt& operator= (int newValue); + + /** Returns the parameter's range. */ + Range getRange() const noexcept { return Range (minValue, maxValue); } + + +private: + //============================================================================== + int minValue, maxValue; + float value, defaultValue; + + float getValue() const override; + void setValue (float newValue) override; + float getDefaultValue() const override; + int getNumSteps() const override; + String getText (float, int) const override; + float getValueForText (const String&) const override; + + int limitRange (int) const noexcept; + float convertTo0to1 (int) const noexcept; + int convertFrom0to1 (float) const noexcept; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterInt) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h new file mode 100644 index 000000000..d5d005234 --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h @@ -0,0 +1,69 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +/** + This abstract base class is used by some AudioProcessorParameter helper classes. + + @see AudioParameterFloat, AudioParameterInt, AudioParameterBool, AudioParameterChoice +*/ +class JUCE_API AudioProcessorParameterWithID : public AudioProcessorParameter +{ +public: + /** Creation of this object requires providing a name and ID which will be + constant for its lifetime. + */ + AudioProcessorParameterWithID (const String& parameterID, + const String& name, + const String& label = String(), + Category category = AudioProcessorParameter::genericParameter); + + /** Destructor. */ + ~AudioProcessorParameterWithID(); + + /** Provides access to the parameter's ID string. */ + const String paramID; + + /** Provides access to the parameter's name. */ + const String name; + + /** Provides access to the parameter's label. */ + const String label; + + /** Provides access to the parameter's category. */ + const Category category; + +private: + String getName (int) const override; + String getLabel() const override; + Category getCategory() const override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameterWithID) +}; + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp new file mode 100644 index 000000000..88da76bc0 --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp @@ -0,0 +1,175 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +// This file contains the implementations of the various AudioParameter[XYZ] classes.. + + +AudioProcessorParameterWithID::AudioProcessorParameterWithID (const String& idToUse, + const String& nameToUse, + const String& labelToUse, + AudioProcessorParameter::Category categoryToUse) + : paramID (idToUse), name (nameToUse), label (labelToUse), category (categoryToUse) {} +AudioProcessorParameterWithID::~AudioProcessorParameterWithID() {} + +String AudioProcessorParameterWithID::getName (int maximumStringLength) const { return name.substring (0, maximumStringLength); } +String AudioProcessorParameterWithID::getLabel() const { return label; } +AudioProcessorParameter::Category AudioProcessorParameterWithID::getCategory() const { return category; } + + +//============================================================================== +AudioParameterFloat::AudioParameterFloat (const String& idToUse, const String& nameToUse, + NormalisableRange r, float def, + const String& labelToUse, Category categoryToUse) + : AudioProcessorParameterWithID (idToUse, nameToUse, labelToUse, categoryToUse), + range (r), value (def), defaultValue (def) +{ +} + +AudioParameterFloat::AudioParameterFloat (String pid, String nm, float minValue, float maxValue, float def) + : AudioProcessorParameterWithID (pid, nm), range (minValue, maxValue), value (def), defaultValue (def) +{ +} + +AudioParameterFloat::~AudioParameterFloat() {} + +float AudioParameterFloat::getValue() const { return range.convertTo0to1 (value); } +void AudioParameterFloat::setValue (float newValue) { value = range.convertFrom0to1 (newValue); } +float AudioParameterFloat::getDefaultValue() const { return range.convertTo0to1 (defaultValue); } +int AudioParameterFloat::getNumSteps() const { return AudioProcessorParameterWithID::getNumSteps(); } +float AudioParameterFloat::getValueForText (const String& text) const { return range.convertTo0to1 (text.getFloatValue()); } + +String AudioParameterFloat::getText (float v, int length) const +{ + String asText (range.convertFrom0to1 (v), 2); + return length > 0 ? asText.substring (0, length) : asText; +} + +AudioParameterFloat& AudioParameterFloat::operator= (float newValue) +{ + if (value != newValue) + setValueNotifyingHost (range.convertTo0to1 (newValue)); + + return *this; +} + +//============================================================================== +AudioParameterInt::AudioParameterInt (const String& idToUse, const String& nameToUse, + int mn, int mx, int def, + const String& labelToUse) + : AudioProcessorParameterWithID (idToUse, nameToUse, labelToUse), + minValue (mn), maxValue (mx), + value ((float) def), + defaultValue (convertTo0to1 (def)) +{ + jassert (minValue < maxValue); // must have a non-zero range of values! +} + +AudioParameterInt::~AudioParameterInt() {} + +int AudioParameterInt::limitRange (int v) const noexcept { return jlimit (minValue, maxValue, v); } +float AudioParameterInt::convertTo0to1 (int v) const noexcept { return (limitRange (v) - minValue) / (float) (maxValue - minValue); } +int AudioParameterInt::convertFrom0to1 (float v) const noexcept { return limitRange (roundToInt ((v * (float) (maxValue - minValue)) + minValue)); } + +float AudioParameterInt::getValue() const { return convertTo0to1 (roundToInt (value)); } +void AudioParameterInt::setValue (float newValue) { value = (float) convertFrom0to1 (newValue); } +float AudioParameterInt::getDefaultValue() const { return defaultValue; } +int AudioParameterInt::getNumSteps() const { return AudioProcessorParameterWithID::getNumSteps(); } +float AudioParameterInt::getValueForText (const String& text) const { return convertTo0to1 (text.getIntValue()); } +String AudioParameterInt::getText (float v, int /*length*/) const { return String (convertFrom0to1 (v)); } + +AudioParameterInt& AudioParameterInt::operator= (int newValue) +{ + if (get() != newValue) + setValueNotifyingHost (convertTo0to1 (newValue)); + + return *this; +} + + +//============================================================================== +AudioParameterBool::AudioParameterBool (const String& idToUse, const String& nameToUse, + bool def, const String& labelToUse) + : AudioProcessorParameterWithID (idToUse, nameToUse, labelToUse), + value (def ? 1.0f : 0.0f), + defaultValue (value) +{ +} + +AudioParameterBool::~AudioParameterBool() {} + +float AudioParameterBool::getValue() const { return value; } +void AudioParameterBool::setValue (float newValue) { value = newValue; } +float AudioParameterBool::getDefaultValue() const { return defaultValue; } +int AudioParameterBool::getNumSteps() const { return 2; } +bool AudioParameterBool::isDiscrete() const { return true; } +float AudioParameterBool::getValueForText (const String& text) const { return text.getIntValue() != 0 ? 1.0f : 0.0f; } +String AudioParameterBool::getText (float v, int /*length*/) const { return String ((int) (v > 0.5f ? 1 : 0)); } + +AudioParameterBool& AudioParameterBool::operator= (bool newValue) +{ + if (get() != newValue) + setValueNotifyingHost (newValue ? 1.0f : 0.0f); + + return *this; +} + + +//============================================================================== +AudioParameterChoice::AudioParameterChoice (const String& idToUse, const String& nameToUse, + const StringArray& c, int def, const String& labelToUse) + : AudioProcessorParameterWithID (idToUse, nameToUse, labelToUse), choices (c), + value ((float) def), + defaultValue (convertTo0to1 (def)) +{ + jassert (choices.size() > 0); // you must supply an actual set of items to choose from! +} + +AudioParameterChoice::~AudioParameterChoice() {} + +int AudioParameterChoice::limitRange (int v) const noexcept { return jlimit (0, choices.size() - 1, v); } +float AudioParameterChoice::convertTo0to1 (int v) const noexcept { return jlimit (0.0f, 1.0f, (v + 0.5f) / (float) choices.size()); } +int AudioParameterChoice::convertFrom0to1 (float v) const noexcept { return limitRange ((int) (v * (float) choices.size())); } + +float AudioParameterChoice::getValue() const { return convertTo0to1 (roundToInt (value)); } +void AudioParameterChoice::setValue (float newValue) { value = (float) convertFrom0to1 (newValue); } +float AudioParameterChoice::getDefaultValue() const { return defaultValue; } +int AudioParameterChoice::getNumSteps() const { return choices.size(); } +bool AudioParameterChoice::isDiscrete() const { return true; } +float AudioParameterChoice::getValueForText (const String& text) const { return convertTo0to1 (choices.indexOf (text)); } +String AudioParameterChoice::getText (float v, int /*length*/) const { return choices [convertFrom0to1 (v)]; } + +AudioParameterChoice& AudioParameterChoice::operator= (int newValue) +{ + if (getIndex() != newValue) + setValueNotifyingHost (convertTo0to1 (newValue)); + + return *this; +} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp new file mode 100644 index 000000000..055b1566c --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -0,0 +1,576 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParameterWithID, + private ValueTree::Listener +{ + Parameter (AudioProcessorValueTreeState& s, + const String& parameterID, const String& paramName, const String& labelText, + NormalisableRange r, float defaultVal, + std::function valueToText, + std::function textToValue, + bool meta, + bool automatable, + bool discrete) + : AudioProcessorParameterWithID (parameterID, paramName, labelText), + owner (s), valueToTextFunction (valueToText), textToValueFunction (textToValue), + range (r), value (defaultVal), defaultValue (defaultVal), + listenersNeedCalling (true), + isMetaParam (meta), + isAutomatableParam (automatable), + isDiscreteParam (discrete) + { + state.addListener (this); + needsUpdate.set (1); + } + + ~Parameter() + { + // should have detached all callbacks before destroying the parameters! + jassert (listeners.size() <= 1); + } + + float getValue() const override { return range.convertTo0to1 (value); } + float getDefaultValue() const override { return range.convertTo0to1 (defaultValue); } + + float getValueForText (const String& text) const override + { + return range.convertTo0to1 (textToValueFunction != nullptr ? textToValueFunction (text) + : text.getFloatValue()); + } + + String getText (float v, int length) const override + { + return valueToTextFunction != nullptr ? valueToTextFunction (range.convertFrom0to1 (v)) + : AudioProcessorParameter::getText (v, length); + } + + int getNumSteps() const override + { + if (range.interval > 0) + return (static_cast ((range.end - range.start) / range.interval) + 1); + + return AudioProcessor::getDefaultNumParameterSteps(); + } + + void setValue (float newValue) override + { + newValue = range.snapToLegalValue (range.convertFrom0to1 (newValue)); + + if (value != newValue || listenersNeedCalling) + { + value = newValue; + + listeners.call (&AudioProcessorValueTreeState::Listener::parameterChanged, paramID, value); + listenersNeedCalling = false; + + needsUpdate.set (1); + } + } + + void setNewState (const ValueTree& v) + { + state = v; + updateFromValueTree(); + } + + void setUnnormalisedValue (float newUnnormalisedValue) + { + if (value != newUnnormalisedValue) + { + const float newValue = range.convertTo0to1 (newUnnormalisedValue); + setValueNotifyingHost (newValue); + } + } + + void updateFromValueTree() + { + setUnnormalisedValue (state.getProperty (owner.valuePropertyID, defaultValue)); + } + + void copyValueToValueTree() + { + if (state.isValid()) + state.setPropertyExcludingListener (this, owner.valuePropertyID, value, owner.undoManager); + } + + void valueTreePropertyChanged (ValueTree&, const Identifier& property) override + { + if (property == owner.valuePropertyID) + updateFromValueTree(); + } + + void valueTreeChildAdded (ValueTree&, ValueTree&) override {} + void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} + void valueTreeChildOrderChanged (ValueTree&, int, int) override {} + void valueTreeParentChanged (ValueTree&) override {} + + static Parameter* getParameterForID (AudioProcessor& processor, StringRef paramID) noexcept + { + const int numParams = processor.getParameters().size(); + + for (int i = 0; i < numParams; ++i) + { + AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i); + + // When using this class, you must allow it to manage all the parameters in your AudioProcessor, and + // not add any parameter objects of other types! + jassert (dynamic_cast (ap) != nullptr); + + Parameter* const p = static_cast (ap); + + if (paramID == p->paramID) + return p; + } + + return nullptr; + } + + bool isMetaParameter() const override { return isMetaParam; } + bool isAutomatable() const override { return isAutomatableParam; } + bool isDiscrete() const override { return isDiscreteParam; } + + AudioProcessorValueTreeState& owner; + ValueTree state; + ListenerList listeners; + std::function valueToTextFunction; + std::function textToValueFunction; + NormalisableRange range; + float value, defaultValue; + Atomic needsUpdate; + bool listenersNeedCalling; + const bool isMetaParam, isAutomatableParam, isDiscreteParam; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter) +}; + +//============================================================================== +AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um) + : processor (p), + undoManager (um), + valueType ("PARAM"), + valuePropertyID ("value"), + idPropertyID ("id"), + updatingConnections (false) +{ + startTimerHz (10); + state.addListener (this); +} + +AudioProcessorValueTreeState::~AudioProcessorValueTreeState() {} + +AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParameter (const String& paramID, const String& paramName, + const String& labelText, NormalisableRange r, + float defaultVal, std::function valueToTextFunction, + std::function textToValueFunction, + bool isMetaParameter, + bool isAutomatableParameter, + bool isDiscreteParameter) +{ + // All parameters must be created before giving this manager a ValueTree state! + jassert (! state.isValid()); + #if ! JUCE_LINUX + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #endif + + Parameter* p = new Parameter (*this, paramID, paramName, labelText, r, + defaultVal, valueToTextFunction, textToValueFunction, + isMetaParameter, isAutomatableParameter, + isDiscreteParameter); + processor.addParameter (p); + return p; +} + +void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener) +{ + if (Parameter* p = Parameter::getParameterForID (processor, paramID)) + p->listeners.add (listener); +} + +void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener) +{ + if (Parameter* p = Parameter::getParameterForID (processor, paramID)) + p->listeners.remove (listener); +} + +Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const +{ + if (Parameter* p = Parameter::getParameterForID (processor, paramID)) + return p->state.getPropertyAsValue (valuePropertyID, undoManager); + + return Value(); +} + +NormalisableRange AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept +{ + if (Parameter* p = Parameter::getParameterForID (processor, paramID)) + return p->range; + + return NormalisableRange(); +} + +AudioProcessorParameterWithID* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept +{ + return Parameter::getParameterForID (processor, paramID); +} + +float* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept +{ + if (Parameter* p = Parameter::getParameterForID (processor, paramID)) + return &(p->value); + + return nullptr; +} + +ValueTree AudioProcessorValueTreeState::getOrCreateChildValueTree (const String& paramID) +{ + ValueTree v (state.getChildWithProperty (idPropertyID, paramID)); + + if (! v.isValid()) + { + v = ValueTree (valueType); + v.setProperty (idPropertyID, paramID, undoManager); + state.addChild (v, -1, undoManager); + } + + return v; +} + +void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees() +{ + if (! updatingConnections) + { + ScopedValueSetter svs (updatingConnections, true, false); + + const int numParams = processor.getParameters().size(); + + for (int i = 0; i < numParams; ++i) + { + AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i); + jassert (dynamic_cast (ap) != nullptr); + + Parameter* p = static_cast (ap); + p->setNewState (getOrCreateChildValueTree (p->paramID)); + } + } +} + +void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier& property) +{ + if (property == idPropertyID && tree.hasType (valueType) && tree.getParent() == state) + updateParameterConnectionsToChildTrees(); +} + +void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree) +{ + if (parent == state && tree.hasType (valueType)) + updateParameterConnectionsToChildTrees(); +} + +void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree& tree, int) +{ + if (parent == state && tree.hasType (valueType)) + updateParameterConnectionsToChildTrees(); +} + +void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v) +{ + if (v == state) + updateParameterConnectionsToChildTrees(); +} + +void AudioProcessorValueTreeState::valueTreeChildOrderChanged (ValueTree&, int, int) {} +void AudioProcessorValueTreeState::valueTreeParentChanged (ValueTree&) {} + +void AudioProcessorValueTreeState::timerCallback() +{ + const int numParams = processor.getParameters().size(); + bool anythingUpdated = false; + + for (int i = 0; i < numParams; ++i) + { + AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i); + jassert (dynamic_cast (ap) != nullptr); + + Parameter* p = static_cast (ap); + + if (p->needsUpdate.compareAndSetBool (0, 1)) + { + p->copyValueToValueTree(); + anythingUpdated = true; + } + } + + startTimer (anythingUpdated ? 1000 / 50 + : jlimit (50, 500, getTimerInterval() + 20)); +} + +AudioProcessorValueTreeState::Listener::Listener() {} +AudioProcessorValueTreeState::Listener::~Listener() {} + +//============================================================================== +struct AttachedControlBase : public AudioProcessorValueTreeState::Listener, + public AsyncUpdater +{ + AttachedControlBase (AudioProcessorValueTreeState& s, const String& p) + : state (s), paramID (p), lastValue (0) + { + state.addParameterListener (paramID, this); + } + + void removeListener() + { + state.removeParameterListener (paramID, this); + } + + void setNewUnnormalisedValue (float newUnnormalisedValue) + { + if (AudioProcessorParameter* p = state.getParameter (paramID)) + { + const float newValue = state.getParameterRange (paramID) + .convertTo0to1 (newUnnormalisedValue); + + if (p->getValue() != newValue) + p->setValueNotifyingHost (newValue); + } + } + + void sendInitialUpdate() + { + if (float* v = state.getRawParameterValue (paramID)) + parameterChanged (paramID, *v); + } + + void parameterChanged (const String&, float newValue) override + { + lastValue = newValue; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cancelPendingUpdate(); + setValue (newValue); + } + else + { + triggerAsyncUpdate(); + } + } + + void beginParameterChange() + { + if (AudioProcessorParameter* p = state.getParameter (paramID)) + p->beginChangeGesture(); + } + + void endParameterChange() + { + if (AudioProcessorParameter* p = state.getParameter (paramID)) + p->endChangeGesture(); + } + + void handleAsyncUpdate() override + { + setValue (lastValue); + } + + virtual void setValue (float) = 0; + + AudioProcessorValueTreeState& state; + String paramID; + float lastValue; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttachedControlBase) +}; + +//============================================================================== +struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private AttachedControlBase, + private Slider::Listener +{ + Pimpl (AudioProcessorValueTreeState& s, const String& p, Slider& sl) + : AttachedControlBase (s, p), slider (sl), ignoreCallbacks (false) + { + NormalisableRange range (s.getParameterRange (paramID)); + slider.setRange (range.start, range.end, range.interval); + slider.setSkewFactor (range.skew, range.symmetricSkew); + + if (AudioProcessorParameter* param = state.getParameter (paramID)) + slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getDefaultValue())); + + sendInitialUpdate(); + slider.addListener (this); + } + + ~Pimpl() + { + slider.removeListener (this); + removeListener(); + } + + void setValue (float newValue) override + { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + { + ScopedValueSetter svs (ignoreCallbacks, true); + slider.setValue (newValue, sendNotificationSync); + } + } + + void sliderValueChanged (Slider* s) override + { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + if ((! ignoreCallbacks) && (! ModifierKeys::getCurrentModifiers().isRightButtonDown())) + setNewUnnormalisedValue ((float) s->getValue()); + } + + void sliderDragStarted (Slider*) override { beginParameterChange(); } + void sliderDragEnded (Slider*) override { endParameterChange(); } + + Slider& slider; + bool ignoreCallbacks; + CriticalSection selfCallbackMutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& s, const String& p, Slider& sl) + : pimpl (new Pimpl (s, p, sl)) +{ +} + +AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() {} + +//============================================================================== +struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl : private AttachedControlBase, + private ComboBox::Listener +{ + Pimpl (AudioProcessorValueTreeState& s, const String& p, ComboBox& c) + : AttachedControlBase (s, p), combo (c), ignoreCallbacks (false) + { + sendInitialUpdate(); + combo.addListener (this); + } + + ~Pimpl() + { + combo.removeListener (this); + removeListener(); + } + + void setValue (float newValue) override + { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + { + ScopedValueSetter svs (ignoreCallbacks, true); + combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync); + } + } + + void comboBoxChanged (ComboBox* comboBox) override + { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + if (! ignoreCallbacks) + { + beginParameterChange(); + setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f); + endParameterChange(); + } + } + + ComboBox& combo; + bool ignoreCallbacks; + CriticalSection selfCallbackMutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& s, const String& p, ComboBox& c) + : pimpl (new Pimpl (s, p, c)) +{ +} + +AudioProcessorValueTreeState::ComboBoxAttachment::~ComboBoxAttachment() {} + +//============================================================================== +struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl : private AttachedControlBase, + private Button::Listener +{ + Pimpl (AudioProcessorValueTreeState& s, const String& p, Button& b) + : AttachedControlBase (s, p), button (b), ignoreCallbacks (false) + { + sendInitialUpdate(); + button.addListener (this); + } + + ~Pimpl() + { + button.removeListener (this); + removeListener(); + } + + void setValue (float newValue) override + { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + { + ScopedValueSetter svs (ignoreCallbacks, true); + button.setToggleState (newValue >= 0.5f, sendNotificationSync); + } + } + + void buttonClicked (Button* b) override + { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + if (! ignoreCallbacks) + { + beginParameterChange(); + setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f); + endParameterChange(); + } + } + + Button& button; + bool ignoreCallbacks; + CriticalSection selfCallbackMutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& s, const String& p, Button& b) + : pimpl (new Pimpl (s, p, b)) +{ +} + +AudioProcessorValueTreeState::ButtonAttachment::~ButtonAttachment() {} + +} // namespace juce diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h new file mode 100644 index 000000000..13ace9595 --- /dev/null +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h @@ -0,0 +1,238 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +namespace juce +{ + +/** + This class contains a ValueTree which is used to manage an AudioProcessor's entire state. + + It has its own internal class of parameter object which are linked to values + within its ValueTree, and which are each identified by a string ID. + + You can get access to the underlying ValueTree object via the state member variable, + so you can add extra properties to it as necessary. + + It also provides some utility child classes for connecting parameters directly to + GUI controls like sliders. + + To use: + 1) Create an AudioProcessorValueTreeState, and give it some parameters using createAndAddParameter(). + 2) Initialise the state member variable with a type name. +*/ +class JUCE_API AudioProcessorValueTreeState : private Timer, + private ValueTree::Listener +{ +public: + /** Creates a state object for a given processor. + + The UndoManager is optional and can be a nullptr. + After creating your state object, you should add parameters with the + createAndAddParameter() method. Note that each AudioProcessorValueTreeState + should be attached to only one processor, and must have the same lifetime as the + processor, as they will have dependencies on each other. + */ + AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo, + UndoManager* undoManagerToUse); + + /** Destructor. */ + ~AudioProcessorValueTreeState(); + + /** Creates and returns a new parameter object for controlling a parameter + with the given ID. + + Calling this will create and add a special type of AudioProcessorParameter to the + AudioProcessor to which this state is attached. + + @param parameterID A unique string ID for the new parameter + @param parameterName The name that the parameter will return from AudioProcessorParameter::getName() + @param labelText The label that the parameter will return from AudioProcessorParameter::getLabel() + @param valueRange A mapping that will be used to determine the value range which this parameter uses + @param defaultValue A default value for the parameter (in non-normalised units) + @param valueToTextFunction A function that will convert a non-normalised value to a string for the + AudioProcessorParameter::getText() method. This can be nullptr to use the + default implementation + @param textToValueFunction The inverse of valueToTextFunction + @param isMetaParameter Set this value to true if this should be a meta parameter + @param isAutomatableParameter Set this value to false if this parameter should not be automatable + @param isDiscrete Set this value to true to make this parameter take discrete values in a host. + @see AudioProcessorParameter::isDiscrete + + @returns the parameter object that was created + */ + AudioProcessorParameterWithID* createAndAddParameter (const String& parameterID, + const String& parameterName, + const String& labelText, + NormalisableRange valueRange, + float defaultValue, + std::function valueToTextFunction, + std::function textToValueFunction, + bool isMetaParameter = false, + bool isAutomatableParameter = true, + bool isDiscrete = false); + + /** Returns a parameter by its ID string. */ + AudioProcessorParameterWithID* getParameter (StringRef parameterID) const noexcept; + + /** Returns a pointer to a floating point representation of a particular + parameter which a realtime process can read to find out its current value. + */ + float* getRawParameterValue (StringRef parameterID) const noexcept; + + /** A listener class that can be attached to an AudioProcessorValueTreeState. + Use AudioProcessorValueTreeState::addParameterListener() to register a callback. + */ + struct JUCE_API Listener + { + Listener(); + virtual ~Listener(); + + /** This callback method is called by the AudioProcessorValueTreeState when a parameter changes. */ + virtual void parameterChanged (const String& parameterID, float newValue) = 0; + }; + + /** Attaches a callback to one of the parameters, which will be called when the parameter changes. */ + void addParameterListener (StringRef parameterID, Listener* listener); + + /** Removes a callback that was previously added with addParameterCallback(). */ + void removeParameterListener (StringRef parameterID, Listener* listener); + + /** Returns a Value object that can be used to control a particular parameter. */ + Value getParameterAsValue (StringRef parameterID) const; + + /** Returns the range that was set when the given parameter was created. */ + NormalisableRange getParameterRange (StringRef parameterID) const noexcept; + + /** A reference to the processor with which this state is associated. */ + AudioProcessor& processor; + + /** The state of the whole processor. + + This must be initialised after all calls to createAndAddParameter(). + You can replace this with your own ValueTree object, and can add properties and + children to the tree. This class will automatically add children for each of the + parameter objects that are created by createAndAddParameter(). + */ + ValueTree state; + + /** Provides access to the undo manager that this object is using. */ + UndoManager* const undoManager; + + //============================================================================== + /** An object of this class maintains a connection between a Slider and a parameter + in an AudioProcessorValueTreeState. + + During the lifetime of this SliderAttachment object, it keeps the two things in + sync, making it easy to connect a slider to a parameter. When this object is + deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState + and Slider aren't deleted before this object! + */ + class JUCE_API SliderAttachment + { + public: + SliderAttachment (AudioProcessorValueTreeState& stateToControl, + const String& parameterID, + Slider& sliderToControl); + ~SliderAttachment(); + + private: + struct Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAttachment) + }; + + //============================================================================== + /** An object of this class maintains a connection between a ComboBox and a parameter + in an AudioProcessorValueTreeState. + + During the lifetime of this ComboBoxAttachment object, it keeps the two things in + sync, making it easy to connect a combo box to a parameter. When this object is + deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState + and ComboBox aren't deleted before this object! + */ + class JUCE_API ComboBoxAttachment + { + public: + ComboBoxAttachment (AudioProcessorValueTreeState& stateToControl, + const String& parameterID, + ComboBox& comboBoxToControl); + ~ComboBoxAttachment(); + + private: + struct Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAttachment) + }; + + //============================================================================== + /** An object of this class maintains a connection between a Button and a parameter + in an AudioProcessorValueTreeState. + + During the lifetime of this ButtonAttachment object, it keeps the two things in + sync, making it easy to connect a button to a parameter. When this object is + deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState + and Button aren't deleted before this object! + */ + class JUCE_API ButtonAttachment + { + public: + ButtonAttachment (AudioProcessorValueTreeState& stateToControl, + const String& parameterID, + Button& buttonToControl); + ~ButtonAttachment(); + + private: + struct Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAttachment) + }; + +private: + //============================================================================== + struct Parameter; + friend struct Parameter; + + ValueTree getOrCreateChildValueTree (const String&); + void timerCallback() override; + + void valueTreePropertyChanged (ValueTree&, const Identifier&) override; + void valueTreeChildAdded (ValueTree&, ValueTree&) override; + void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override; + void valueTreeChildOrderChanged (ValueTree&, int, int) override; + void valueTreeParentChanged (ValueTree&) override; + void valueTreeRedirected (ValueTree&) override; + void updateParameterConnectionsToChildTrees(); + + Identifier valueType, valuePropertyID, idPropertyID; + bool updatingConnections; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorValueTreeState) +}; + +} // namespace juce