This reverts commit b1a15e0cd6.
Signed-off-by: falkTX <falktx@gmail.com>
tags/v2.1-alpha1-winvst
| @@ -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) | |||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -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<AudioPluginFormat::InstantiationCompletionCallback> compCallback; | |||
| ScopedPointer<CallbackInvoker> 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<CallbackInvoker*> (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<EventSignaler*> (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> 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<void (AudioPluginInstance*, const String&)> f) | |||
| { | |||
| struct CallbackInvoker : public AudioPluginFormat::InstantiationCompletionCallback | |||
| { | |||
| CallbackInvoker (std::function<void (AudioPluginInstance*, const String&)> inCompletion) | |||
| : completion (inCompletion) | |||
| {} | |||
| void completionCallback (AudioPluginInstance* instance, const String& error) override | |||
| { | |||
| completion (instance, error); | |||
| } | |||
| std::function<void (AudioPluginInstance*, const String&)> 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 | |||
| @@ -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<PluginDescription>& 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<void (AudioPluginInstance*, const String&)> 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 | |||
| @@ -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<AudioPluginFormat::InstantiationCompletionCallback> callback; | |||
| }; | |||
| struct ErrorLambdaOnMessageThread : public CallbackMessage | |||
| { | |||
| ErrorLambdaOnMessageThread (const String& inError, | |||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||
| : error (inError), lambda (f) | |||
| { | |||
| } | |||
| void messageCallback() override { lambda (nullptr, error); } | |||
| String error; | |||
| std::function<void (AudioPluginInstance*, const String&)> 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<VSTPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||
| jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||
| jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_LADSPA && JUCE_LINUX | |||
| jassert (dynamic_cast<LADSPAPluginFormat*> (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<void (AudioPluginInstance*, const String&)> 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 | |||
| @@ -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<void (AudioPluginInstance*, const String&)> 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<AudioPluginFormat> formats; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | |||
| }; | |||
| } // namespace juce | |||
| @@ -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<int*> inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; | |||
| HeapBlock<int> inputLayoutMapStorage, outputLayoutMapStorage; | |||
| int** inputLayoutMap; | |||
| int** outputLayoutMap; | |||
| //============================================================================== | |||
| void initializeChannelMapArray (bool isInput, const int numBuses) | |||
| { | |||
| HeapBlock<int*>& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; | |||
| HeapBlock<int>& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; | |||
| int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; | |||
| const int totalInChannels = processor.getTotalNumInputChannels(); | |||
| const int totalOutChannels = processor.getTotalNumOutputChannels(); | |||
| layoutMapPtrStorage.calloc (static_cast<size_t> (numBuses)); | |||
| layoutMapStorage.calloc (static_cast<size_t> (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<size_t> (numChannels)); | |||
| reset(); | |||
| } | |||
| void release() | |||
| { | |||
| scratch.setSize (0, 0); | |||
| channels.free(); | |||
| } | |||
| void reset() noexcept | |||
| { | |||
| pushIdx = 0; | |||
| popIdx = 0; | |||
| zeromem (channels.get(), sizeof(float*) * static_cast<size_t> (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<int> (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<float*> (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<int> (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<int> (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<float*> 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<int> (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<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||
| const float* src = static_cast<const float*> (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<int> (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<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||
| float* dst = static_cast<float*> (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 <int numLayouts> | |||
| 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<AUChannelInfo> getAUChannelInfo (const AudioProcessor& processor) | |||
| { | |||
| Array<AUChannelInfo> 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<int> 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<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||
| info.outChannels = static_cast<SInt16> (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<int> (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<AudioChannelSet>& 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 | |||
| @@ -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<PluginDescription>&, 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 | |||
| } | |||
| @@ -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<LADSPAModuleHandle> Ptr; | |||
| static Array <LADSPAModuleHandle*>& getActiveModules() | |||
| { | |||
| static Array <LADSPAModuleHandle*> 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<LADSPAModuleHandle> 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<const float*> (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<int> 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<ParameterValue> 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 <PluginDescription>& results, | |||
| const String& fileOrIdentifier) | |||
| { | |||
| if (! fileMightContainThisPluginType (fileOrIdentifier)) | |||
| return; | |||
| PluginDescription desc; | |||
| desc.fileOrIdentifier = fileOrIdentifier; | |||
| desc.uid = 0; | |||
| ScopedPointer<LADSPAPluginInstance> instance (dynamic_cast<LADSPAPluginInstance*> (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<LADSPAPluginInstance> 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 | |||
| @@ -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<PluginDescription>&, 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 | |||
| } | |||
| @@ -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<ClassType*> (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<SourceClassType*> (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<const Steinberg::char16*> (string)); } | |||
| inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (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<Steinberg::Vst::TChar*> (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<int> (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<Steinberg::Vst::Speaker> (type) - (static_cast<Steinberg::Vst::Speaker> (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<AudioChannelSet::ChannelType> ((int)AudioChannelSet::discreteChannel0 + 3); /* kSpeakerBfl */ | |||
| case (1 << 29): return static_cast<AudioChannelSet::ChannelType> ((int)AudioChannelSet::discreteChannel0 + 4); /* kSpeakerBfc */ | |||
| case (1 << 30): return static_cast<AudioChannelSet::ChannelType> ((int)AudioChannelSet::discreteChannel0 + 5); /* kSpeakerBfr */ | |||
| case kSpeakerPl: return AudioChannelSet::wideLeft; | |||
| case kSpeakerPr: return AudioChannelSet::wideRight; | |||
| default: break; | |||
| } | |||
| auto channelType = BigInteger (static_cast<int64> (type)).findNextSetBit (0); | |||
| // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum | |||
| jassert (channelType >= 33); | |||
| return static_cast<AudioChannelSet::ChannelType> (static_cast<int> (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<AudioChannelSet::ChannelType> 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<int64> (arr)); | |||
| for (auto bit = vstChannels.findNextSetBit (0); bit != -1; bit = vstChannels.findNextSetBit (bit + 1)) | |||
| { | |||
| AudioChannelSet::ChannelType channelType = getChannelType (arr, 1ull << static_cast<uint64> (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 ObjectType> | |||
| 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<Steinberg::Vst::Event, CriticalSection> events; | |||
| Atomic<int> 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 <typename FloatType> | |||
| struct VST3BufferExchange | |||
| { | |||
| typedef Array<FloatType*> Bus; | |||
| typedef Array<Bus> 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<FloatType>& 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<Steinberg::Vst::AudioBusBuffers>& result, | |||
| BusMap& busMapToUse, const AudioChannelSet& arrangement, | |||
| AudioBuffer<FloatType>& 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<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||
| const Array<AudioChannelSet>& arrangements, | |||
| AudioBuffer<FloatType>& 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<Steinberg::Vst::AudioBusBuffers>& result, | |||
| Steinberg::Vst::IAudioProcessor& processor, | |||
| BusMap& busMapToUse, bool isInput, int numBuses, | |||
| AudioBuffer<FloatType>& source) | |||
| { | |||
| int channelIndexOffset = 0; | |||
| for (int i = 0; i < numBuses; ++i) | |||
| mapArrangementToBuses (channelIndexOffset, i, | |||
| result, busMapToUse, | |||
| getArrangementForBus (&processor, isInput, i), | |||
| source); | |||
| } | |||
| }; | |||
| template <typename FloatType> | |||
| struct VST3FloatAndDoubleBusMapCompositeHelper {}; | |||
| struct VST3FloatAndDoubleBusMapComposite | |||
| { | |||
| VST3BufferExchange<float>::BusMap floatVersion; | |||
| VST3BufferExchange<double>::BusMap doubleVersion; | |||
| template <typename FloatType> | |||
| inline typename VST3BufferExchange<FloatType>::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper<FloatType>::get (*this); } | |||
| }; | |||
| template <> struct VST3FloatAndDoubleBusMapCompositeHelper<float> | |||
| { | |||
| static inline VST3BufferExchange<float>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; } | |||
| }; | |||
| template <> struct VST3FloatAndDoubleBusMapCompositeHelper<double> | |||
| { | |||
| static inline VST3BufferExchange<double>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; } | |||
| }; | |||
| } // namespace juce | |||
| @@ -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 <base/source/fstring.h> | |||
| #include <pluginterfaces/base/conststringtable.h> | |||
| #include <pluginterfaces/base/funknown.h> | |||
| #include <pluginterfaces/base/ipluginbase.h> | |||
| #include <pluginterfaces/base/ustring.h> | |||
| #include <pluginterfaces/gui/iplugview.h> | |||
| #include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | |||
| #include <pluginterfaces/vst/ivstattributes.h> | |||
| #include <pluginterfaces/vst/ivstaudioprocessor.h> | |||
| #include <pluginterfaces/vst/ivstcomponent.h> | |||
| #include <pluginterfaces/vst/ivstcontextmenu.h> | |||
| #include <pluginterfaces/vst/ivsteditcontroller.h> | |||
| #include <pluginterfaces/vst/ivstevents.h> | |||
| #include <pluginterfaces/vst/ivsthostapplication.h> | |||
| #include <pluginterfaces/vst/ivstmessage.h> | |||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
| #include <pluginterfaces/vst/ivstparameterchanges.h> | |||
| #include <pluginterfaces/vst/ivstplugview.h> | |||
| #include <pluginterfaces/vst/ivstprocesscontext.h> | |||
| #include <pluginterfaces/vst/vsttypes.h> | |||
| #include <pluginterfaces/vst/ivstunits.h> | |||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
| #include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||
| #include <public.sdk/source/common/memorystream.h> | |||
| #include <public.sdk/source/vst/vsteditcontroller.h> | |||
| #else | |||
| #if JUCE_MINGW | |||
| #define _set_abort_behavior(...) | |||
| #endif | |||
| #include <base/source/baseiids.cpp> | |||
| #include <base/source/fatomic.cpp> | |||
| #include <base/source/fbuffer.cpp> | |||
| #include <base/source/fdebug.cpp> | |||
| #include <base/source/fobject.cpp> | |||
| #include <base/source/fstreamer.cpp> | |||
| #include <base/source/fstring.cpp> | |||
| #include <base/source/fthread.cpp> | |||
| #include <base/source/updatehandler.cpp> | |||
| #include <pluginterfaces/base/conststringtable.cpp> | |||
| #include <pluginterfaces/base/funknown.cpp> | |||
| #include <pluginterfaces/base/ipluginbase.h> | |||
| #include <pluginterfaces/base/ustring.cpp> | |||
| #include <pluginterfaces/gui/iplugview.h> | |||
| #include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | |||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
| #include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||
| #include <public.sdk/source/common/memorystream.cpp> | |||
| #include <public.sdk/source/common/pluginview.cpp> | |||
| #include <public.sdk/source/vst/vsteditcontroller.cpp> | |||
| #include <public.sdk/source/vst/vstbus.cpp> | |||
| #include <public.sdk/source/vst/vstinitiids.cpp> | |||
| #include <public.sdk/source/vst/vstcomponent.cpp> | |||
| #include <public.sdk/source/vst/vstcomponentbase.cpp> | |||
| #include <public.sdk/source/vst/vstparameters.cpp> | |||
| #include <public.sdk/source/vst/hosting/hostclasses.cpp> | |||
| //============================================================================== | |||
| 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 <windows.h> | |||
| #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 | |||
| @@ -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<PluginDescription>&, 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 | |||
| @@ -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<ChannelType>& 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<AudioChannelSet::ChannelType> 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<HeapBlock<VstSpeakerConfiguration>&&> (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<HeapBlock<VstSpeakerConfiguration>&&> (vstConfig.storage); | |||
| vstConfig.clear(); | |||
| return *this; | |||
| } | |||
| const VstSpeakerConfiguration& get() const { return *storage.get(); } | |||
| private: | |||
| JUCE_LEAK_DETECTOR (VstSpeakerConfigurationHolder) | |||
| HeapBlock<VstSpeakerConfiguration> storage; | |||
| VstSpeakerConfiguration* allocate (int numChannels) | |||
| { | |||
| auto arrangementSize = sizeof (VstSpeakerConfiguration) | |||
| + sizeof (VstIndividualSpeakerInfo) * static_cast<size_t> (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 | |||
| @@ -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 | |||
| @@ -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<VstEventBlock> 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 | |||
| @@ -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<PluginDescription>&, 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 | |||
| @@ -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 <juce_gui_extra/juce_gui_extra.h> | |||
| //============================================================================== | |||
| #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 <Carbon/Carbon.h> | |||
| #include "../juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h" | |||
| #endif | |||
| #endif | |||
| #if JUCE_PLUGINHOST_VST && JUCE_LINUX && ! JUCE_AUDIOPROCESSOR_NO_GUI | |||
| #include <X11/Xlib.h> | |||
| #include <X11/Xutil.h> | |||
| #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 <AudioUnit/AudioUnit.h> | |||
| #endif | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& 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" | |||
| @@ -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 <juce_gui_basics/juce_gui_basics.h> | |||
| #include <juce_audio_basics/juce_audio_basics.h> | |||
| //============================================================================== | |||
| /** 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" | |||
| @@ -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 <int numLayouts> | |||
| AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | |||
| }; | |||
| } // namespace juce | |||
| @@ -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<int> 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 | |||
| @@ -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<int> newBounds); | |||
| ScopedPointer<ResizableCornerComponent> 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<AudioProcessorEditorListener> resizeListener; | |||
| bool resizable; | |||
| ComponentBoundsConstrainer defaultConstrainer; | |||
| ComponentBoundsConstrainer* constrainer = {}; | |||
| JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | |||
| }; | |||
| } // namespace juce | |||
| @@ -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<Node> Ptr; | |||
| private: | |||
| //============================================================================== | |||
| friend class AudioProcessorGraph; | |||
| const ScopedPointer<AudioProcessor> 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<float>& , MidiBuffer&) override; | |||
| void processBlock (AudioBuffer<double>&, 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 <typename floatType> | |||
| void processAudio (AudioBuffer<floatType>& 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<float>&, MidiBuffer&) override; | |||
| void processBlock (AudioBuffer<double>&, 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 <typename floatType> | |||
| void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||
| template <typename floatType> | |||
| void sliceAndProcess (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||
| //============================================================================== | |||
| ReferenceCountedArray<Node> nodes; | |||
| OwnedArray<Connection> connections; | |||
| uint32 lastNodeId; | |||
| OwnedArray<MidiBuffer> midiBuffers; | |||
| Array<void*> renderingOps; | |||
| friend class AudioGraphIOProcessor; | |||
| struct AudioProcessorGraphBufferHelpers; | |||
| ScopedPointer<AudioProcessorGraphBufferHelpers> 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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 <PropertyComponent*> 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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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<PluginDescription>& 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<PluginDescription> 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<PluginDescription>& 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<File> 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<PluginDescription*> 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<PluginDescription*>& 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<PluginDescription*>& sorted, | |||
| const KnownPluginList::SortMethod sortMethod) | |||
| { | |||
| String lastType; | |||
| ScopedPointer<KnownPluginList::PluginTree> 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<const PluginDescription*>& 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<PluginDescription>& 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<PluginDescription*> 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<PluginTree> 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 | |||
| @@ -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 <PluginDescription>& 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 <PluginDescription>& 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<PluginTree> subFolders; | |||
| Array<const PluginDescription*> 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 <PluginDescription>& 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<PluginDescription> types; | |||
| StringArray blacklist; | |||
| ScopedPointer<CustomScanner> scanner; | |||
| CriticalSection scanLock, typesArrayLock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | |||
| }; | |||
| } // namespace juce | |||
| @@ -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<PluginDescription> 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 | |||
| @@ -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<int> nextIndex; | |||
| float progress = 0; | |||
| const bool allowAsync; | |||
| void updateProgress(); | |||
| void setDeadMansPedalFile (const StringArray& newContents); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginDirectoryScanner) | |||
| }; | |||
| } // namespace juce | |||
| @@ -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<int> 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<int> 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<PluginDescription> 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<PluginDirectoryScanner> scanner; | |||
| AlertWindow pathChooserWindow, progressWindow; | |||
| FileSearchPathListComponent pathList; | |||
| String pluginBeingScanned; | |||
| double progress; | |||
| int numThreads; | |||
| bool allowAsync, finished; | |||
| ScopedPointer<ThreadPool> 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<File> 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 | |||
| @@ -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<TableListBoxModel> tableModel; | |||
| class Scanner; | |||
| friend class Scanner; | |||
| friend struct ContainerDeletePolicy<Scanner>; | |||
| ScopedPointer<Scanner> 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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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<float> 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<float> 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 | |||
| @@ -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<int> getRange() const noexcept { return Range<int> (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 | |||
| @@ -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 | |||
| @@ -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<float> 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 | |||
| @@ -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<float> r, float defaultVal, | |||
| std::function<String (float)> valueToText, | |||
| std::function<float (const String&)> 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<int> ((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<Parameter*> (ap) != nullptr); | |||
| Parameter* const p = static_cast<Parameter*> (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<AudioProcessorValueTreeState::Listener> listeners; | |||
| std::function<String (float)> valueToTextFunction; | |||
| std::function<float (const String&)> textToValueFunction; | |||
| NormalisableRange<float> range; | |||
| float value, defaultValue; | |||
| Atomic<int> 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<float> r, | |||
| float defaultVal, std::function<String (float)> valueToTextFunction, | |||
| std::function<float (const String&)> 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<float> AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept | |||
| { | |||
| if (Parameter* p = Parameter::getParameterForID (processor, paramID)) | |||
| return p->range; | |||
| return NormalisableRange<float>(); | |||
| } | |||
| 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<bool> 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<Parameter*> (ap) != nullptr); | |||
| Parameter* p = static_cast<Parameter*> (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<Parameter*> (ap) != nullptr); | |||
| Parameter* p = static_cast<Parameter*> (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<float> 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<bool> 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<bool> 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<bool> 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 | |||
| @@ -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<float> valueRange, | |||
| float defaultValue, | |||
| std::function<String (float)> valueToTextFunction, | |||
| std::function<float (const String&)> 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<float> 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<Pimpl>; | |||
| ScopedPointer<Pimpl> 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<Pimpl>; | |||
| ScopedPointer<Pimpl> 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<Pimpl>; | |||
| ScopedPointer<Pimpl> 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 | |||