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 | |||||